mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-22 15:15:19 +00:00
basic sapper dev task, with HMR
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
"express": "^4.16.2",
|
"express": "^4.16.2",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
"locate-character": "^2.0.5",
|
"locate-character": "^2.0.5",
|
||||||
|
"mime": "^2.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mri": "^1.1.0",
|
"mri": "^1.1.0",
|
||||||
"node-fetch": "^1.7.3",
|
"node-fetch": "^1.7.3",
|
||||||
@@ -36,8 +37,7 @@
|
|||||||
"serialize-javascript": "^1.4.0",
|
"serialize-javascript": "^1.4.0",
|
||||||
"url-parse": "^1.2.0",
|
"url-parse": "^1.2.0",
|
||||||
"walk-sync": "^0.3.2",
|
"walk-sync": "^0.3.2",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0"
|
||||||
"webpack-hot-middleware": "^2.21.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@std/esm": "^0.19.7",
|
"@std/esm": "^0.19.7",
|
||||||
@@ -63,9 +63,7 @@
|
|||||||
"svelte-loader": "^2.3.2",
|
"svelte-loader": "^2.3.2",
|
||||||
"ts-node": "^4.1.0",
|
"ts-node": "^4.1.0",
|
||||||
"tslib": "^1.8.1",
|
"tslib": "^1.8.1",
|
||||||
"typescript": "^2.6.2",
|
"typescript": "^2.6.2"
|
||||||
"wait-on": "^2.0.2",
|
|
||||||
"wait-port": "^0.2.2"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
|
|||||||
210
src/cli/dev.ts
210
src/cli/dev.ts
@@ -1,12 +1,17 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as net from 'net';
|
||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import * as child_process from 'child_process';
|
import * as child_process from 'child_process';
|
||||||
|
import * as http from 'http';
|
||||||
|
import mkdirp from 'mkdirp';
|
||||||
|
import rimraf from 'rimraf';
|
||||||
|
import { wait_for_port } from './utils';
|
||||||
import { create_compilers, create_app, create_routes, create_serviceworker, create_template } from 'sapper/core.js';
|
import { create_compilers, create_app, create_routes, create_serviceworker, create_template } from 'sapper/core.js';
|
||||||
|
|
||||||
type Deferred = {
|
type Deferred = {
|
||||||
promise?: Promise<any>;
|
promise?: Promise<any>;
|
||||||
fulfil?: (value: any) => void;
|
fulfil?: (value?: any) => void;
|
||||||
reject?: (err: Error) => void;
|
reject?: (err: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,61 +26,61 @@ function deferred() {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create_hot_update_server(port: number, interval = 10000) {
|
||||||
|
const clients = new Set();
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
if (req.url !== '/hmr') return;
|
||||||
|
|
||||||
|
req.socket.setKeepAlive(true);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Type': 'text/event-stream;charset=utf-8',
|
||||||
|
'Cache-Control': 'no-cache, no-transform',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
// While behind nginx, event stream should not be buffered:
|
||||||
|
// http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering
|
||||||
|
'X-Accel-Buffering': 'no'
|
||||||
|
});
|
||||||
|
|
||||||
|
res.write('\n');
|
||||||
|
|
||||||
|
clients.add(res);
|
||||||
|
req.on('close', () => {
|
||||||
|
clients.delete(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
|
||||||
|
function send(data: any) {
|
||||||
|
clients.forEach(client => {
|
||||||
|
client.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
send(null)
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
return { send };
|
||||||
|
}
|
||||||
|
|
||||||
export default function create_watcher(src: string, dir: string) {
|
export default function create_watcher(src: string, dir: string) {
|
||||||
|
rimraf.sync(dir);
|
||||||
|
mkdirp.sync(dir);
|
||||||
|
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
|
||||||
// initial build
|
// initial build
|
||||||
const routes = create_routes({ src });
|
const routes = create_routes({ src });
|
||||||
create_app({ routes, src, dev: true });
|
create_app({ routes, src, dev: true });
|
||||||
|
|
||||||
|
const hot_update_server = create_hot_update_server(23456); // TODO robustify port selection
|
||||||
|
|
||||||
|
// TODO watch the configs themselves?
|
||||||
const compilers = create_compilers();
|
const compilers = create_compilers();
|
||||||
|
|
||||||
const deferreds = {
|
|
||||||
client: deferred(),
|
|
||||||
server: deferred()
|
|
||||||
};
|
|
||||||
|
|
||||||
const invalidate = () => Promise.all([
|
|
||||||
deferreds.client.promise,
|
|
||||||
deferreds.server.promise
|
|
||||||
]).then(([client_stats, server_stats]) => {
|
|
||||||
const client_info = client_stats.toJson();
|
|
||||||
fs.writeFileSync(path.join(dir, 'stats.client.json'), JSON.stringify(client_info, null, ' '));
|
|
||||||
|
|
||||||
const server_info = server_stats.toJson();
|
|
||||||
fs.writeFileSync(path.join(dir, 'stats.server.json'), JSON.stringify(server_info, null, ' '));
|
|
||||||
|
|
||||||
const client_files = client_info.assets.map((chunk: { name: string }) => `/client/${chunk.name}`);
|
|
||||||
|
|
||||||
return create_serviceworker({
|
|
||||||
routes: create_routes({ src }),
|
|
||||||
client_files,
|
|
||||||
src
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function watch_compiler(type: 'client' | 'server', callback: (err: Error) => void) {
|
|
||||||
const compiler = compilers[type];
|
|
||||||
|
|
||||||
compiler.plugin('invalid', (filename: string) => {
|
|
||||||
console.log(chalk.cyan(`${type} bundle invalidated, file changed: ${chalk.bold(filename)}`));
|
|
||||||
deferreds[type] = deferred();
|
|
||||||
watcher.ready = invalidate();
|
|
||||||
});
|
|
||||||
|
|
||||||
compiler.plugin('failed', (err: Error) => {
|
|
||||||
deferreds[type].reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return compiler.watch({}, (err: Error, stats: any) => {
|
|
||||||
if (stats.hasErrors()) {
|
|
||||||
deferreds[type].reject(stats.toJson().errors[0]);
|
|
||||||
} else {
|
|
||||||
deferreds[type].fulfil(stats);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const chokidar = require('chokidar');
|
|
||||||
|
|
||||||
function watch_files(pattern: string, callback: () => void) {
|
function watch_files(pattern: string, callback: () => void) {
|
||||||
const watcher = chokidar.watch(pattern, {
|
const watcher = chokidar.watch(pattern, {
|
||||||
persistent: false
|
persistent: false
|
||||||
@@ -96,11 +101,114 @@ export default function create_watcher(src: string, dir: string) {
|
|||||||
// TODO reload current page?
|
// TODO reload current page?
|
||||||
});
|
});
|
||||||
|
|
||||||
watch_compiler('client', () => {
|
let proc: child_process.ChildProcess;
|
||||||
|
|
||||||
|
const deferreds = {
|
||||||
|
server: deferred(),
|
||||||
|
client: deferred()
|
||||||
|
};
|
||||||
|
|
||||||
|
const times = {
|
||||||
|
client_start: Date.now(),
|
||||||
|
server_start: Date.now(),
|
||||||
|
serviceworker_start: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
compilers.server.plugin('invalid', () => {
|
||||||
|
times.server_start = Date.now();
|
||||||
|
// TODO print message
|
||||||
|
deferreds.server = deferred();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch_compiler('server', () => {
|
compilers.server.watch({}, (err: Error, stats: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(chalk.red(err.message));
|
||||||
|
} else if (stats.hasErrors()) {
|
||||||
|
// print errors. TODO notify client
|
||||||
|
stats.toJson().errors.forEach((error: Error) => {
|
||||||
|
console.error(error); // TODO make this look nice
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`built server in ${Date.now() - times.server_start}ms`); // TODO prettify
|
||||||
|
|
||||||
|
const server_info = stats.toJson();
|
||||||
|
fs.writeFileSync(path.join(dir, 'server_info.json'), JSON.stringify(server_info, null, ' '));
|
||||||
|
|
||||||
|
deferreds.client.promise.then(() => {
|
||||||
|
if (proc) proc.kill();
|
||||||
|
|
||||||
|
proc = child_process.fork(`${dir}/server.js`, [], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: Object.assign({}, process.env)
|
||||||
|
});
|
||||||
|
|
||||||
|
wait_for_port(3000, deferreds.server.fulfil); // TODO control port
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
compilers.client.plugin('invalid', (filename: string) => {
|
||||||
|
times.client_start = Date.now();
|
||||||
|
|
||||||
|
deferreds.client = deferred();
|
||||||
|
|
||||||
|
// TODO print message
|
||||||
|
fs.readdirSync(path.join(dir, 'client')).forEach(file => {
|
||||||
|
fs.unlinkSync(path.join(dir, 'client', file));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
compilers.client.watch({}, (err: Error, stats: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(chalk.red(err.message));
|
||||||
|
} else if (stats.hasErrors()) {
|
||||||
|
// print errors. TODO notify client
|
||||||
|
stats.toJson().errors.forEach((error: Error) => {
|
||||||
|
console.error(error); // TODO make this look nice
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`built client in ${Date.now() - times.client_start}ms`); // TODO prettify
|
||||||
|
|
||||||
|
const client_info = stats.toJson();
|
||||||
|
fs.writeFileSync(path.join(dir, 'client_info.json'), JSON.stringify(client_info, null, ' '));
|
||||||
|
deferreds.client.fulfil();
|
||||||
|
|
||||||
|
const client_files = client_info.assets.map((chunk: { name: string }) => `/client/${chunk.name}`);
|
||||||
|
|
||||||
|
deferreds.server.promise.then(() => {
|
||||||
|
hot_update_server.send({
|
||||||
|
status: 'completed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return create_serviceworker({
|
||||||
|
routes: create_routes({ src }),
|
||||||
|
client_files,
|
||||||
|
src
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compilers.serviceworker) {
|
||||||
|
compilers.serviceworker.plugin('invalid', (filename: string) => {
|
||||||
|
times.serviceworker_start = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
compilers.client.watch({}, (err: Error, stats: any) => {
|
||||||
|
if (err) {
|
||||||
|
// TODO notify client
|
||||||
|
} else if (stats.hasErrors()) {
|
||||||
|
// print errors. TODO notify client
|
||||||
|
stats.toJson().errors.forEach((error: Error) => {
|
||||||
|
console.error(error); // TODO make this look nice
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`built service worker in ${Date.now() - times.serviceworker_start}ms`); // TODO prettify
|
||||||
|
|
||||||
|
const serviceworker_info = stats.toJson();
|
||||||
|
fs.writeFileSync(path.join(dir, 'serviceworker_info.json'), JSON.stringify(serviceworker_info, null, ' '));
|
||||||
|
// TODO trigger reload?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import express from 'express';
|
|||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
import URL from 'url-parse';
|
import URL from 'url-parse';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
import { wait_for_port } from './utils';
|
||||||
|
|
||||||
const { OUTPUT_DIR = 'dist' } = process.env;
|
const { OUTPUT_DIR = 'dist' } = process.env;
|
||||||
|
|
||||||
@@ -55,8 +56,6 @@ export default async function exporter(dir: string) { // dir === '.sapper'
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await require('wait-port')({ port });
|
|
||||||
|
|
||||||
function handle(url: URL) {
|
function handle(url: URL) {
|
||||||
if (url.origin !== origin) return;
|
if (url.origin !== origin) return;
|
||||||
|
|
||||||
@@ -85,6 +84,8 @@ export default async function exporter(dir: string) { // dir === '.sapper'
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return handle(new URL(origin)) // TODO all static routes
|
wait_for_port(port, () => {
|
||||||
.then(() => proc.kill());
|
handle(new URL(origin)) // TODO all static routes
|
||||||
|
.then(() => proc.kill())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
18
src/cli/utils.ts
Normal file
18
src/cli/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as net from 'net';
|
||||||
|
|
||||||
|
export function wait_for_port(port: number, cb: () => void) {
|
||||||
|
const socket = net.createConnection({ port }, () => {
|
||||||
|
cb();
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', err => {
|
||||||
|
setTimeout(() => {
|
||||||
|
wait_for_port(port, cb);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.destroy();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
@@ -18,7 +18,8 @@ export default function create_app({ routes, src, dev }: {
|
|||||||
|
|
||||||
function generate_client(routes: Route[], src: string, dev: boolean) {
|
function generate_client(routes: Route[], src: string, dev: boolean) {
|
||||||
let code = `
|
let code = `
|
||||||
// This file is generated by Sapper — do not edit it!\nexport const routes = [
|
// This file is generated by Sapper — do not edit it!
|
||||||
|
export const routes = [
|
||||||
${routes
|
${routes
|
||||||
.filter(route => route.type === 'page')
|
.filter(route => route.type === 'page')
|
||||||
.map(route => {
|
.map(route => {
|
||||||
@@ -34,10 +35,18 @@ function generate_client(routes: Route[], src: string, dev: boolean) {
|
|||||||
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
const hmr_client = posixify(
|
const hmr_client = posixify(
|
||||||
require.resolve(`webpack-hot-middleware/client`)
|
path.resolve(__dirname, 'src/hmr-client.js')
|
||||||
);
|
);
|
||||||
|
|
||||||
code += `\n\nimport('${hmr_client}?path=/__webpack_hmr&timeout=20000'); if (module.hot) module.hot.accept();`;
|
const PORT = 23456; // TODO robustify this — needs to be controlled by the dev task
|
||||||
|
|
||||||
|
code += `
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
import('${hmr_client}').then(client => {
|
||||||
|
client.connect(${PORT});
|
||||||
|
});
|
||||||
|
}`.replace(/^\t{3}/gm, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default function create_serviceworker({ routes, client_files, src }: {
|
|||||||
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
|
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
|
||||||
|
|
||||||
let code = `
|
let code = `
|
||||||
|
// This file is generated by Sapper — do not edit it!
|
||||||
export const timestamp = ${Date.now()};
|
export const timestamp = ${Date.now()};
|
||||||
|
|
||||||
export const assets = [\n\t${assets.map((x: string) => `"${x}"`).join(',\n\t')}\n];
|
export const assets = [\n\t${assets.map((x: string) => `"${x}"`).join(',\n\t')}\n];
|
||||||
@@ -20,7 +21,7 @@ export default function create_serviceworker({ routes, client_files, src }: {
|
|||||||
export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n];
|
export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n];
|
||||||
|
|
||||||
export const routes = [\n\t${routes.filter((r: Route) => r.type === 'page').map((r: Route) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n];
|
export const routes = [\n\t${routes.filter((r: Route) => r.type === 'page').map((r: Route) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n];
|
||||||
`.replace(/^\t\t/gm, '');
|
`.replace(/^\t\t/gm, '').trim();
|
||||||
|
|
||||||
write('app/manifest/service-worker.js', code);
|
write('app/manifest/service-worker.js', code);
|
||||||
}
|
}
|
||||||
28
src/hmr-client.js
Normal file
28
src/hmr-client.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
let source;
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
if (module.hot.status() === 'idle') {
|
||||||
|
module.hot.check(true).then(modules => {
|
||||||
|
console.log(`HMR updated`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connect(port) {
|
||||||
|
if (source || !window.EventSource) return;
|
||||||
|
|
||||||
|
source = new EventSource(`http://localhost:${port}/hmr`);
|
||||||
|
|
||||||
|
source.onopen = function(event) {
|
||||||
|
console.log(`HMR connected`);
|
||||||
|
};
|
||||||
|
|
||||||
|
source.onmessage = function(event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (!data) return; // just a heartbeat
|
||||||
|
|
||||||
|
if (data.status === 'completed') {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { create_app, create_serviceworker, create_routes, create_template } from 'sapper/core.js';
|
|
||||||
import { dest } from '../config.js';
|
|
||||||
|
|
||||||
type Deferred = {
|
|
||||||
promise?: Promise<any>;
|
|
||||||
fulfil?: (value: any) => void;
|
|
||||||
reject?: (err: Error) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deferred() {
|
|
||||||
const d: Deferred = {};
|
|
||||||
|
|
||||||
d.promise = new Promise((fulfil, reject) => {
|
|
||||||
d.fulfil = fulfil;
|
|
||||||
d.reject = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function create_watcher({ compilers, dev, entry, src, onroutes, ontemplate }) {
|
|
||||||
const deferreds = {
|
|
||||||
client: deferred(),
|
|
||||||
server: deferred()
|
|
||||||
};
|
|
||||||
|
|
||||||
const invalidate = () => Promise.all([
|
|
||||||
deferreds.client.promise,
|
|
||||||
deferreds.server.promise
|
|
||||||
]).then(([client_stats, server_stats]) => {
|
|
||||||
const client_info = client_stats.toJson();
|
|
||||||
fs.writeFileSync(path.join(dest, 'stats.client.json'), JSON.stringify(client_info, null, ' '));
|
|
||||||
|
|
||||||
const server_info = server_stats.toJson();
|
|
||||||
fs.writeFileSync(path.join(dest, 'stats.server.json'), JSON.stringify(server_info, null, ' '));
|
|
||||||
|
|
||||||
const client_files = client_info.assets.map((chunk: { name: string }) => `/client/${chunk.name}`);
|
|
||||||
|
|
||||||
return create_serviceworker({
|
|
||||||
routes: create_routes({ src }),
|
|
||||||
client_files,
|
|
||||||
src
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function watch_compiler(type: 'client' | 'server') {
|
|
||||||
const compiler = compilers[type];
|
|
||||||
|
|
||||||
compiler.plugin('invalid', (filename: string) => {
|
|
||||||
console.log(chalk.cyan(`${type} bundle invalidated, file changed: ${chalk.bold(filename)}`));
|
|
||||||
deferreds[type] = deferred();
|
|
||||||
watcher.ready = invalidate();
|
|
||||||
});
|
|
||||||
|
|
||||||
compiler.plugin('failed', (err: Error) => {
|
|
||||||
deferreds[type].reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return compiler.watch({}, (err: Error, stats: any) => {
|
|
||||||
if (stats.hasErrors()) {
|
|
||||||
deferreds[type].reject(stats.toJson().errors[0]);
|
|
||||||
} else {
|
|
||||||
deferreds[type].fulfil(stats);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const chokidar = require('chokidar');
|
|
||||||
|
|
||||||
function watch_files(pattern: string, callback: () => void) {
|
|
||||||
const watcher = chokidar.watch(pattern, {
|
|
||||||
persistent: false
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher.on('add', callback);
|
|
||||||
watcher.on('change', callback);
|
|
||||||
watcher.on('unlink', callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch_files('routes/**/*.+(html|js|mjs)', () => {
|
|
||||||
const routes = create_routes({ src });
|
|
||||||
onroutes(routes);
|
|
||||||
|
|
||||||
create_app({ routes, src, dev });
|
|
||||||
});
|
|
||||||
|
|
||||||
watch_files('app/template.html', () => {
|
|
||||||
const template = create_template();
|
|
||||||
ontemplate(template);
|
|
||||||
|
|
||||||
// TODO reload current page?
|
|
||||||
});
|
|
||||||
|
|
||||||
const watcher = {
|
|
||||||
ready: invalidate(),
|
|
||||||
client: watch_compiler('client'),
|
|
||||||
server: watch_compiler('server'),
|
|
||||||
|
|
||||||
close: () => {
|
|
||||||
watcher.client.close();
|
|
||||||
watcher.server.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return watcher;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
// import * as mime from 'mime';
|
||||||
import mkdirp from 'mkdirp';
|
import mkdirp from 'mkdirp';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import serialize from 'serialize-javascript';
|
import serialize from 'serialize-javascript';
|
||||||
@@ -49,12 +50,26 @@ export default function middleware({ routes }: {
|
|||||||
fn: () => assets.service_worker
|
fn: () => assets.service_worker
|
||||||
}),
|
}),
|
||||||
|
|
||||||
get_asset_handler({
|
(req, res, next) => {
|
||||||
filter: (pathname: string) => pathname.startsWith('/client/'),
|
if (req.pathname.startsWith('/client/')) {
|
||||||
type: 'application/javascript',
|
// const type = mime.getType(req.pathname);
|
||||||
cache: 'max-age=31536000',
|
const type = 'application/javascript'; // TODO might not be, if using e.g. CSS plugin
|
||||||
fn: (pathname: string) => assets.client[pathname.replace('/client/', '')]
|
|
||||||
}),
|
// TODO cache?
|
||||||
|
const rs = fs.createReadStream(path.join(dest, req.pathname.slice(1)));
|
||||||
|
|
||||||
|
rs.on('error', error => {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end('not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', type);
|
||||||
|
res.setHeader('Cache-Control', 'max-age=31536000');
|
||||||
|
rs.pipe(res);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
get_route_handler(client_info.assetsByChunkName, () => assets, () => routes, () => template),
|
get_route_handler(client_info.assetsByChunkName, () => assets, () => routes, () => template),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user