Skip to content

Commit e7ac6d6

Browse files
committed
Allow specifying a 404 page
1 parent 86aa9a4 commit e7ac6d6

File tree

4 files changed

+80
-7
lines changed

4 files changed

+80
-7
lines changed

resources/index.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="@fastly/js-compute" />
22

33
import { Router } from '@fastly/expressly';
4-
import { assets, spaFile, autoIndex, autoExt } from './statics';
4+
import { assets, spaFile, notFoundPageFile, autoIndex, autoExt } from './statics';
55

66
const router = new Router();
77

@@ -41,6 +41,16 @@ function getMatchingRequestPath(path) {
4141
return null;
4242
}
4343

44+
function requestAcceptsTextHtml(req) {
45+
const accept = (req.headers.get('Accept') ?? '')
46+
.split(',')
47+
.map(x => x.split(';')[0]);
48+
if(!accept.includes('text/html') && !accept.includes('*/*') && accept.includes('*')) {
49+
return false;
50+
}
51+
return true;
52+
}
53+
4454
router.get("*", (req, res) => {
4555
const assetPath = getMatchingRequestPath(req.urlObj.pathname);
4656
if(assetPath == null) {
@@ -72,10 +82,7 @@ router.get("*", (req, res) => {
7282
if(!spaFile) {
7383
return;
7484
}
75-
const accept = (req.headers.get('Accept') ?? '')
76-
.split(',')
77-
.map(x => x.split(';')[0]);
78-
if(!accept.includes('text/html') && !accept.includes('*/*') && accept.includes('*')) {
85+
if(!requestAcceptsTextHtml(req)) {
7986
return;
8087
}
8188

@@ -90,6 +97,18 @@ router.get("*", (req, res) => {
9097
});
9198

9299
router.all("*", (req, res) => {
100+
if(notFoundPageFile && requestAcceptsTextHtml(req)) {
101+
const staticFile = assets[notFoundPageFile];
102+
res.send(new Response(staticFile.content, {
103+
status: 404,
104+
headers: {
105+
'Cache-Control': 'no-cache',
106+
'Content-Type': 'text/html',
107+
}
108+
}));
109+
return;
110+
}
111+
93112
res.send(new Response("404 Not Found", {
94113
status: 404,
95114
headers: {

src/build-static.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ export async function buildStaticLoader() {
169169

170170
fileContents += `\nexport const spaFile = ${JSON.stringify(spaFile)};\n`;
171171

172+
let notFoundPageFile: string | false = config.notFoundPage ?? false;
173+
if(notFoundPageFile) {
174+
console.log(`Application 'not found (404)' file '${notFoundPageFile}'.`);
175+
if(!knownAssets[notFoundPageFile] || knownAssets[notFoundPageFile].contentType !== 'text/html') {
176+
console.warn(`'${notFoundPageFile}' does not exist or is not of type 'text/html'. Ignoring.`);
177+
notFoundPageFile = false;
178+
}
179+
} else {
180+
console.log(`Application specifies no 'not found (404)' page.`);
181+
}
182+
183+
fileContents += `\nexport const notFoundPageFile = ${JSON.stringify(notFoundPageFile)};\n`;
184+
172185
let autoIndex: string | false = config.autoIndex ?? null;
173186
fileContents += `\nexport const autoIndex = ${JSON.stringify(autoIndex)};\n`;
174187

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const optionDefinitions: OptionDefinition[] = [
5959
},
6060

6161
{ name: 'spa', type: String, },
62+
{ name: 'not-found-page', type: String, },
6263
{ name: 'name', type: String, },
6364
{ name: 'author', type: String, },
6465
{ name: 'description', type: String, },

src/init-app.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type AppOptions = {
1414
'public-dir': string | undefined,
1515
'static-dir': string | undefined,
1616
spa: string | null | undefined,
17+
'not-found-page': string | null | ((options: AppOptions) => string | undefined) | undefined,
1718
'auto-index': string[] | null | undefined,
1819
'auto-ext': string[] | null | undefined,
1920
name: string,
@@ -99,6 +100,9 @@ const defaultOptions: AppOptions = {
99100
'public-dir': undefined,
100101
'static-dir': undefined,
101102
spa: undefined,
103+
'not-found-page': (options) => {
104+
return options['public-dir'] + '/404.html';
105+
},
102106
'auto-index': [ 'index.html', 'index.htm' ],
103107
'auto-ext': [ '.html', '.htm' ],
104108
author: '[email protected]',
@@ -154,10 +158,13 @@ export function initApp(commandLineValues: CommandLineOptions) {
154158
options = {
155159
...options,
156160
...pickKeys(['author', 'name', 'description'], packageJson ?? {}),
157-
...pickKeys(['public-dir', 'static-dir', 'spa', 'auto-index', 'auto-ext', 'author', 'name', 'description'], commandLineValues)
161+
...pickKeys(['public-dir', 'static-dir', 'spa', 'not-found-page', 'auto-index', 'auto-ext', 'author', 'name', 'description'], commandLineValues)
158162
};
159163

160-
console.log('options', options);
164+
if(typeof options['not-found-page'] === 'function') {
165+
// Apply function for this one
166+
options['not-found-page'] = options['not-found-page'](options);
167+
}
161168

162169
if(check != null) {
163170
if(!check(packageJson, options)) {
@@ -180,6 +187,7 @@ export function initApp(commandLineValues: CommandLineOptions) {
180187
const buildStaticDir = BUILD_STATIC_DIR != null ? path.resolve(BUILD_STATIC_DIR) : null;
181188

182189
const spa = options['spa'] as string | null | undefined;
190+
const notFoundPage = options['not-found-page'] as string | null | undefined;
183191

184192
const autoIndex = options['auto-index'] as string[] | null | undefined;
185193
const autoExt = options['auto-ext'] as string[] | null | undefined;
@@ -204,6 +212,26 @@ export function initApp(commandLineValues: CommandLineOptions) {
204212
}
205213
}
206214

215+
let notFoundPageFilename = notFoundPage;
216+
217+
// Specifically check for null instead of undefined
218+
if(notFoundPage === null) {
219+
notFoundPageFilename = path.resolve(publicDir, './404.html');
220+
let rel = path.relative(path.resolve(), notFoundPageFilename);
221+
if(!rel.startsWith('..')) {
222+
rel = './' + rel;
223+
}
224+
console.log('--not-found-page provided with no value, assuming ' + rel);
225+
}
226+
227+
if(notFoundPageFilename != null) {
228+
notFoundPageFilename = path.resolve(notFoundPageFilename);
229+
if(!notFoundPageFilename.startsWith(publicDir)) {
230+
console.error(`❌ --not-found-page file '${notFoundPageFilename}' not inside public directory!`);
231+
process.exit(1);
232+
}
233+
}
234+
207235
const exists = fs.existsSync(computeJsDir);
208236
if(exists) {
209237
console.error(`❌ '${COMPUTE_JS_DIR}' directory already exists!`);
@@ -219,10 +247,16 @@ export function initApp(commandLineValues: CommandLineOptions) {
219247
spaRel = './' + spaRel;
220248
}
221249

250+
let notFoundRel: string | null = notFoundPageFilename != null ? path.relative(path.resolve(), notFoundPageFilename) : null;
251+
if(notFoundRel != null && !notFoundRel.startsWith('..')) {
252+
notFoundRel = './' + notFoundRel;
253+
}
254+
222255
console.log('');
223256
console.log('Public Dir :', PUBLIC_DIR);
224257
console.log('Static Dir :', BUILD_STATIC_DIR ?? '(None)');
225258
console.log('SPA :', spaRel != null ? spaRel : '(None)');
259+
console.log('404 Page :', notFoundRel != null ? notFoundRel : '(None)');
226260
console.log('Auto-Index :', autoIndex != null ? autoIndex : '(None)')
227261
console.log('Auto-Ext :', autoExt != null ? autoExt : '(None)')
228262
console.log('name :', name);
@@ -320,13 +354,19 @@ service_id = ""
320354
spaFileRel = '/' + path.relative(publicDir, spaFilename);
321355
}
322356

357+
let notFoundFileRel: string | false = false;
358+
if(notFoundPageFilename != null) {
359+
notFoundFileRel = '/' + path.relative(publicDir, notFoundPageFilename);
360+
}
361+
323362
const staticPublishJsContent = `\
324363
module.exports = {
325364
publicDir: ${JSON.stringify(publicDirRel)},
326365
excludeDirs: [ './node_modules' ],
327366
includeDirs: [ './.well-known' ],
328367
staticDirs: ${JSON.stringify(staticDirsRel)},
329368
spa: ${JSON.stringify(spaFileRel)},
369+
notFoundPage: ${JSON.stringify(notFoundFileRel)},
330370
autoIndex: ${JSON.stringify(autoIndex)},
331371
autoExt: ${JSON.stringify(autoExt)},
332372
};`;

0 commit comments

Comments
 (0)