mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-22 15:15:19 +00:00
various stability improvements
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
"middleware.js",
|
"middleware.js",
|
||||||
"runtime",
|
"runtime",
|
||||||
"runtime.js",
|
"runtime.js",
|
||||||
"hmr-client.js",
|
"sapper-dev-client.js",
|
||||||
"webpack"
|
"webpack"
|
||||||
],
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ let source;
|
|||||||
function check() {
|
function check() {
|
||||||
if (module.hot.status() === 'idle') {
|
if (module.hot.status() === 'idle') {
|
||||||
module.hot.check(true).then(modules => {
|
module.hot.check(true).then(modules => {
|
||||||
console.log(`HMR updated`);
|
console.log(`[SAPPER] applied HMR update`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,16 +11,26 @@ function check() {
|
|||||||
export function connect(port) {
|
export function connect(port) {
|
||||||
if (source || !window.EventSource) return;
|
if (source || !window.EventSource) return;
|
||||||
|
|
||||||
source = new EventSource(`http://localhost:${port}/hmr`);
|
source = new EventSource(`http://localhost:${port}/__sapper__`);
|
||||||
|
|
||||||
|
window.source = source;
|
||||||
|
|
||||||
source.onopen = function(event) {
|
source.onopen = function(event) {
|
||||||
console.log(`HMR connected`);
|
console.log(`[SAPPER] dev client connected`);
|
||||||
|
};
|
||||||
|
|
||||||
|
source.onerror = function(error) {
|
||||||
|
console.error(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
source.onmessage = function(event) {
|
source.onmessage = function(event) {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (!data) return; // just a heartbeat
|
if (!data) return; // just a heartbeat
|
||||||
|
|
||||||
|
if (data.action === 'reload') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
if (data.status === 'completed') {
|
if (data.status === 'completed') {
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import mkdirp from 'mkdirp';
|
|||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import { wait_for_port } from './utils';
|
import { wait_for_port } from './utils';
|
||||||
import { dest } from '../config';
|
import { dest } from '../config';
|
||||||
import { create_compilers, create_app, create_routes, create_serviceworker, create_template } from 'sapper/core.js';
|
import { create_compilers, create_app, create_routes, create_serviceworker } from 'sapper/core.js';
|
||||||
|
|
||||||
type Deferred = {
|
type Deferred = {
|
||||||
promise?: Promise<any>;
|
promise?: Promise<any>;
|
||||||
@@ -31,11 +31,12 @@ function create_hot_update_server(port: number, interval = 10000) {
|
|||||||
const clients = new Set();
|
const clients = new Set();
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
if (req.url !== '/hmr') return;
|
if (req.url !== '/__sapper__') return;
|
||||||
|
|
||||||
req.socket.setKeepAlive(true);
|
req.socket.setKeepAlive(true);
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': 'Cache-Control',
|
||||||
'Content-Type': 'text/event-stream;charset=utf-8',
|
'Content-Type': 'text/event-stream;charset=utf-8',
|
||||||
'Cache-Control': 'no-cache, no-transform',
|
'Cache-Control': 'no-cache, no-transform',
|
||||||
'Connection': 'keep-alive',
|
'Connection': 'keep-alive',
|
||||||
@@ -103,8 +104,9 @@ export default async function dev() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch_files('app/template.html', ['change'], () => {
|
watch_files('app/template.html', ['change'], () => {
|
||||||
const template = create_template();
|
hot_update_server.send({
|
||||||
// TODO reload current page?
|
action: 'reload'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let proc: child_process.ChildProcess;
|
let proc: child_process.ChildProcess;
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ function generate_client(routes: Route[], dev_port?: number) {
|
|||||||
];`.replace(/^\t\t/gm, '').trim();
|
];`.replace(/^\t\t/gm, '').trim();
|
||||||
|
|
||||||
if (dev()) {
|
if (dev()) {
|
||||||
const hmr_client = posixify(
|
const sapper_dev_client = posixify(
|
||||||
path.resolve(__dirname, 'hmr-client.js')
|
path.resolve(__dirname, 'sapper-dev-client.js')
|
||||||
);
|
);
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
import('${hmr_client}').then(client => {
|
import('${sapper_dev_client}').then(client => {
|
||||||
client.connect(${dev_port});
|
client.connect(${dev_port});
|
||||||
});
|
});
|
||||||
}`.replace(/^\t{3}/gm, '');
|
}`.replace(/^\t{3}/gm, '');
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import framer from 'code-frame';
|
|
||||||
import { locate } from 'locate-character';
|
|
||||||
|
|
||||||
function error(e: any) {
|
|
||||||
if (e.title) console.error(chalk.bold.red(e.title));
|
|
||||||
if (e.body) console.error(chalk.red(e.body));
|
|
||||||
if (e.url) console.error(chalk.cyan(e.url));
|
|
||||||
if (e.frame) console.error(chalk.grey(e.frame));
|
|
||||||
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function create_templates() {
|
|
||||||
const template = fs.readFileSync(`app/template.html`, 'utf-8');
|
|
||||||
|
|
||||||
const index = template.indexOf('%sapper.main%');
|
|
||||||
if (index !== -1) {
|
|
||||||
// TODO remove this in a future version
|
|
||||||
const { line, column } = locate(template, index, { offsetLine: 1 });
|
|
||||||
const frame = framer(template, line, column);
|
|
||||||
|
|
||||||
error({
|
|
||||||
title: `app/template.html`,
|
|
||||||
body: `<script src='%sapper.main%'> is unsupported — use %sapper.scripts% (without the <script> tag) instead`,
|
|
||||||
url: 'https://github.com/sveltejs/sapper/issues/86',
|
|
||||||
frame
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
render: (data: Record<string, string>) => {
|
|
||||||
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
|
|
||||||
return key in data ? data[key] : '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export { default as create_app } from './create_app';
|
export { default as create_app } from './create_app';
|
||||||
export { default as create_serviceworker } from './create_serviceworker';
|
export { default as create_serviceworker } from './create_serviceworker';
|
||||||
export { default as create_compilers } from './create_compilers';
|
export { default as create_compilers } from './create_compilers';
|
||||||
export { default as create_routes } from './create_routes';
|
export { default as create_routes } from './create_routes';
|
||||||
export { default as create_template } from './create_template';
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { ClientRequest, ServerResponse } from 'http';
|
import { ClientRequest, ServerResponse } from 'http';
|
||||||
// import * as mime from 'mime';
|
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';
|
||||||
import escape_html from 'escape-html';
|
import escape_html from 'escape-html';
|
||||||
import { create_routes, templates, create_compilers, create_template } from 'sapper/core.js';
|
import { create_routes, templates, create_compilers } from 'sapper/core.js';
|
||||||
import { dest } from '../config';
|
import { dest, dev } from '../config';
|
||||||
import { Route, Template } from '../interfaces';
|
import { Route, Template } from '../interfaces';
|
||||||
import sourceMapSupport from 'source-map-support';
|
import sourceMapSupport from 'source-map-support';
|
||||||
|
|
||||||
@@ -45,58 +45,70 @@ export default function middleware({ routes }: {
|
|||||||
|
|
||||||
const client_info = JSON.parse(fs.readFileSync(path.join(output, 'client_info.json'), 'utf-8'));
|
const client_info = JSON.parse(fs.readFileSync(path.join(output, 'client_info.json'), 'utf-8'));
|
||||||
|
|
||||||
const template = create_template();
|
|
||||||
|
|
||||||
const shell = try_read(path.join(output, 'index.html'));
|
|
||||||
const serviceworker = try_read(path.join(output, 'service-worker.js'));
|
|
||||||
|
|
||||||
const middleware = compose_handlers([
|
const middleware = compose_handlers([
|
||||||
(req: Req, res: ServerResponse, next: () => void) => {
|
(req: Req, res: ServerResponse, next: () => void) => {
|
||||||
req.pathname = req.url.replace(/\?.*/, '');
|
req.pathname = req.url.replace(/\?.*/, '');
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
shell && get_asset_handler({
|
exists(path.join(output, 'index.html')) && serve({
|
||||||
pathname: '/index.html',
|
pathname: '/index.html',
|
||||||
type: 'text/html',
|
cache_control: 'max-age=600'
|
||||||
cache: 'max-age=600',
|
|
||||||
body: shell
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
serviceworker && get_asset_handler({
|
exists(path.join(output, 'service-worker.js')) && serve({
|
||||||
pathname: '/service-worker.js',
|
pathname: '/service-worker.js',
|
||||||
type: 'application/javascript',
|
cache_control: 'max-age=600'
|
||||||
cache: 'max-age=600',
|
|
||||||
body: serviceworker
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
(req: Req, res: ServerResponse, next: () => void) => {
|
serve({
|
||||||
if (req.pathname.startsWith('/client/')) {
|
prefix: '/client/',
|
||||||
// const type = mime.getType(req.pathname);
|
cache_control: 'max-age=31536000'
|
||||||
const type = 'application/javascript'; // TODO might not be, if using e.g. CSS plugin
|
}),
|
||||||
|
|
||||||
// TODO cache?
|
get_route_handler(client_info.assetsByChunkName, routes)
|
||||||
const rs = fs.createReadStream(path.join(output, 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, routes, template)
|
|
||||||
].filter(Boolean));
|
].filter(Boolean));
|
||||||
|
|
||||||
return middleware;
|
return middleware;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serve({ prefix, pathname, cache_control }: {
|
||||||
|
prefix?: string,
|
||||||
|
pathname?: string,
|
||||||
|
cache_control: string
|
||||||
|
}) {
|
||||||
|
const filter = pathname
|
||||||
|
? (req: Req) => req.pathname === pathname
|
||||||
|
: (req: Req) => req.pathname.startsWith(prefix);
|
||||||
|
|
||||||
|
const output = dest();
|
||||||
|
|
||||||
|
const cache: Map<string, Buffer> = new Map();
|
||||||
|
|
||||||
|
const read = dev()
|
||||||
|
? (file: string) => fs.readFileSync(path.resolve(output, file))
|
||||||
|
: (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(output, file)))).get(file)
|
||||||
|
|
||||||
|
return (req: Req, res: ServerResponse, next: () => void) => {
|
||||||
|
if (filter(req)) {
|
||||||
|
const type = mime.getType(req.pathname);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = read(req.pathname.slice(1));
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', type);
|
||||||
|
res.setHeader('Cache-Control', cache_control);
|
||||||
|
res.end(data);
|
||||||
|
} catch (err) {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end('not found');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function get_asset_handler({ pathname, type, cache, body }: {
|
function get_asset_handler({ pathname, type, cache, body }: {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -114,7 +126,11 @@ function get_asset_handler({ pathname, type, cache, body }: {
|
|||||||
|
|
||||||
const resolved = Promise.resolve();
|
const resolved = Promise.resolve();
|
||||||
|
|
||||||
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], template: Template) {
|
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]) {
|
||||||
|
const template = dev()
|
||||||
|
? () => fs.readFileSync('app/template.html', 'utf-8')
|
||||||
|
: (str => () => str)(fs.readFileSync('app/template.html', 'utf-8'));
|
||||||
|
|
||||||
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
|
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
|
||||||
req.params = route.params(route.pattern.exec(req.pathname));
|
req.params = route.params(route.pattern.exec(req.pathname));
|
||||||
|
|
||||||
@@ -174,12 +190,11 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
|||||||
|
|
||||||
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
|
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
|
||||||
|
|
||||||
const page = template.render({
|
const page = template()
|
||||||
scripts,
|
.replace('%sapper.scripts%', scripts)
|
||||||
html,
|
.replace('%sapper.html%', html)
|
||||||
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
|
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
|
||||||
styles: (css && css.code ? `<style>${css.code}</style>` : '')
|
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
|
||||||
});
|
|
||||||
|
|
||||||
res.end(page);
|
res.end(page);
|
||||||
|
|
||||||
@@ -271,17 +286,19 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
|||||||
|
|
||||||
const { head, css, html } = rendered;
|
const { head, css, html } = rendered;
|
||||||
|
|
||||||
res.end(template.render({
|
const page = template()
|
||||||
scripts: `<script src='/client/${chunks.main}'></script>`,
|
.replace('%sapper.scripts%', `<script src='/client/${chunks.main}'></script>`)
|
||||||
html,
|
.replace('%sapper.html%', html)
|
||||||
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
|
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
|
||||||
styles: (css && css.code ? `<style>${css.code}</style>` : '')
|
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
|
||||||
}));
|
|
||||||
|
res.end(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const error_route = routes.find((route: RouteObject) => route.error === '5xx');
|
const error_route = routes.find((route: RouteObject) => route.error === '5xx');
|
||||||
|
|
||||||
function handle_error(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
|
function handle_error(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
|
||||||
|
// TODO lot of repetition between this and handle_not_found
|
||||||
if (statusCode >= 400 && statusCode < 500) {
|
if (statusCode >= 400 && statusCode < 500) {
|
||||||
return handle_not_found(req, res, statusCode, message);
|
return handle_not_found(req, res, statusCode, message);
|
||||||
}
|
}
|
||||||
@@ -298,12 +315,13 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
|||||||
|
|
||||||
const { head, css, html } = rendered;
|
const { head, css, html } = rendered;
|
||||||
|
|
||||||
res.end(template.render({
|
const page = template()
|
||||||
scripts: `<script src='/client/${chunks.main}'></script>`,
|
.replace('%sapper.scripts%', `<script src='/client/${chunks.main}'></script>`)
|
||||||
html,
|
.replace('%sapper.html%', html)
|
||||||
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
|
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
|
||||||
styles: (css && css.code ? `<style>${css.code}</style>` : '')
|
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
|
||||||
}));
|
|
||||||
|
res.end(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function find_route(req: Req, res: ServerResponse) {
|
return function find_route(req: Req, res: ServerResponse) {
|
||||||
@@ -353,10 +371,11 @@ function try_serialize(data: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function try_read(file: string) {
|
function exists(file: string) {
|
||||||
try {
|
try {
|
||||||
return fs.readFileSync(file, 'utf-8');
|
fs.statSync(file);
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user