mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-14 03:54:46 +00:00
Compare commits
4 Commits
v0.15.6
...
proxy-data
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b799a3f1e | ||
|
|
18d15c0120 | ||
|
|
b20e15721c | ||
|
|
06cc22b10d |
@@ -18,4 +18,4 @@ addons:
|
|||||||
install:
|
install:
|
||||||
- export DISPLAY=':99.0'
|
- export DISPLAY=':99.0'
|
||||||
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||||
- npm ci || npm i
|
- npm install
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
# sapper changelog
|
# sapper changelog
|
||||||
|
|
||||||
## 0.15.6
|
|
||||||
|
|
||||||
* Fix exporting with custom basepath ([#342](https://github.com/sveltejs/sapper/pull/342))
|
|
||||||
|
|
||||||
## 0.15.5
|
## 0.15.5
|
||||||
|
|
||||||
* Faster `export` with more explanatory output ([#335](https://github.com/sveltejs/sapper/pull/335))
|
* Faster `export` with more explanatory output ([#335](https://github.com/sveltejs/sapper/pull/335))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ environment:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node $env:nodejs_version
|
- ps: Install-Product node $env:nodejs_version
|
||||||
- npm ci
|
- npm install
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- node --version && npm --version
|
- node --version && npm --version
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sapper",
|
"name": "sapper",
|
||||||
"version": "0.15.6",
|
"version": "0.15.4",
|
||||||
"description": "Military-grade apps, engineered by Svelte",
|
"description": "Military-grade apps, engineered by Svelte",
|
||||||
"main": "dist/middleware.ts.js",
|
"main": "dist/middleware.ts.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -224,11 +224,8 @@ class Watcher extends EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.proc.on('message', message => {
|
this.proc.on('message', message => {
|
||||||
if (message.__sapper__ && message.event === 'basepath') {
|
if (!message.__sapper__) return;
|
||||||
this.emit('basepath', {
|
this.emit(message.event, message);
|
||||||
basepath: message.basepath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.proc.on('exit', emitFatal);
|
this.proc.on('exit', emitFatal);
|
||||||
|
|||||||
@@ -51,10 +51,9 @@ async function execute(emitter: EventEmitter, {
|
|||||||
const port = await ports.find(3000);
|
const port = await ports.find(3000);
|
||||||
|
|
||||||
const origin = `http://localhost:${port}`;
|
const origin = `http://localhost:${port}`;
|
||||||
const root = new URL(basepath || '', origin);
|
|
||||||
|
|
||||||
emitter.emit('info', {
|
emitter.emit('info', {
|
||||||
message: `Crawling ${root.href}`
|
message: `Crawling ${origin}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const proc = child_process.fork(path.resolve(`${build}/server.js`), [], {
|
const proc = child_process.fork(path.resolve(`${build}/server.js`), [], {
|
||||||
@@ -72,10 +71,8 @@ async function execute(emitter: EventEmitter, {
|
|||||||
const deferreds = new Map();
|
const deferreds = new Map();
|
||||||
|
|
||||||
function get_deferred(pathname: string) {
|
function get_deferred(pathname: string) {
|
||||||
pathname = pathname.replace(root.pathname, '');
|
|
||||||
|
|
||||||
if (!deferreds.has(pathname)) {
|
if (!deferreds.has(pathname)) {
|
||||||
deferreds.set(pathname, new Deferred());
|
deferreds.set(pathname, new Deferred()) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
return deferreds.get(pathname);
|
return deferreds.get(pathname);
|
||||||
@@ -110,7 +107,7 @@ async function execute(emitter: EventEmitter, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handle(url: URL) {
|
async function handle(url: URL) {
|
||||||
const pathname = (url.pathname.replace(root.pathname, '') || '/');
|
const pathname = url.pathname || '/';
|
||||||
|
|
||||||
if (seen.has(pathname)) return;
|
if (seen.has(pathname)) return;
|
||||||
seen.add(pathname);
|
seen.add(pathname);
|
||||||
@@ -141,9 +138,6 @@ async function execute(emitter: EventEmitter, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ports.wait(port)
|
return ports.wait(port)
|
||||||
.then(() => {
|
.then(() => handle(new URL(`/${basepath}`, origin))) // TODO all static routes
|
||||||
// TODO all static routes
|
|
||||||
return handle(root);
|
|
||||||
})
|
|
||||||
.then(() => proc.kill());
|
.then(() => proc.kill());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as path from 'path';
|
|||||||
import * as colors from 'ansi-colors';
|
import * as colors from 'ansi-colors';
|
||||||
import * as child_process from 'child_process';
|
import * as child_process from 'child_process';
|
||||||
import prettyMs from 'pretty-ms';
|
import prettyMs from 'pretty-ms';
|
||||||
|
import pb from 'pretty-bytes';
|
||||||
import { dev as _dev } from '../api/dev';
|
import { dev as _dev } from '../api/dev';
|
||||||
import * as events from '../api/interfaces';
|
import * as events from '../api/interfaces';
|
||||||
|
|
||||||
@@ -44,6 +45,32 @@ export function dev(opts: { port: number, open: boolean }) {
|
|||||||
if (event.log) console.log(event.log);
|
if (event.log) console.log(event.log);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watcher.on('preload', (event) => {
|
||||||
|
if (event.size > 25000) {
|
||||||
|
console.log(colors.bold.yellow(`${event.url} — large amount of preloaded data`));
|
||||||
|
console.log(`${colors.bold(pb(event.size))} of data was preloaded in total, above the recommended limit of ${pb(25000)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.on('unused_data', (event) => {
|
||||||
|
console.log(colors.bold.yellow(`${event.url} — unused preloaded data`));
|
||||||
|
console.log(`More data was returned from \`preload\` than was used during the initial render. Consider only returning essential data.`);
|
||||||
|
|
||||||
|
event.discrepancies.forEach(discrepancy => {
|
||||||
|
console.log(`${colors.bold(discrepancy.file)} loaded ${colors.bold(pb(discrepancy.preloaded))}, of which ${discrepancy.rendered > 2 ? `only ${colors.bold(pb(discrepancy.rendered))}` : 'none'} was used. The following properties were not referenced:`);
|
||||||
|
|
||||||
|
const slice = discrepancy.props.length > 12
|
||||||
|
? discrepancy.props.slice(0, 10)
|
||||||
|
: discrepancy.props;
|
||||||
|
|
||||||
|
console.log(slice.map((prop: string) => `• ${prop}`).join('\n'));
|
||||||
|
|
||||||
|
if (discrepancy.props.length > slice.length) {
|
||||||
|
console.log(`...and ${discrepancy.props.length - slice.length} more`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
watcher.on('build', (event: events.BuildEvent) => {
|
watcher.on('build', (event: events.BuildEvent) => {
|
||||||
if (event.errors.length) {
|
if (event.errors.length) {
|
||||||
console.log(`${colors.bold.red(`✗ ${event.type}`)}`);
|
console.log(`${colors.bold.red(`✗ ${event.type}`)}`);
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ function generate_server(
|
|||||||
|
|
||||||
const props = [
|
const props = [
|
||||||
`name: "${part.component.name}"`,
|
`name: "${part.component.name}"`,
|
||||||
|
`file: "${part.component.file}"`,
|
||||||
`component: ${part.component.name}`
|
`component: ${part.component.name}`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import fetch from 'node-fetch';
|
|||||||
import { lookup } from './middleware/mime';
|
import { lookup } from './middleware/mime';
|
||||||
import { locations, dev } from './config';
|
import { locations, dev } from './config';
|
||||||
import sourceMapSupport from 'source-map-support';
|
import sourceMapSupport from 'source-map-support';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
import { wrap_data } from './middleware/wrap_data';
|
||||||
|
import { list_unused_properties } from './middleware/list_unused_properties';
|
||||||
|
|
||||||
sourceMapSupport.install();
|
sourceMapSupport.install();
|
||||||
|
|
||||||
@@ -290,6 +293,8 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
const { server_routes, pages } = manifest;
|
const { server_routes, pages } = manifest;
|
||||||
const error_route = manifest.error;
|
const error_route = manifest.error;
|
||||||
|
|
||||||
|
const should_wrap_data = dev() || process.env.SAPPER_EXPORT;
|
||||||
|
|
||||||
function handle_error(req: Req, res: ServerResponse, statusCode: number, error: Error | string) {
|
function handle_error(req: Req, res: ServerResponse, statusCode: number, error: Error | string) {
|
||||||
handle_page({
|
handle_page({
|
||||||
pattern: null,
|
pattern: null,
|
||||||
@@ -428,11 +433,6 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serialized = {
|
|
||||||
preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`,
|
|
||||||
store: store && try_serialize(store.get())
|
|
||||||
};
|
|
||||||
|
|
||||||
const segments = req.path.split('/').filter(Boolean);
|
const segments = req.path.split('/').filter(Boolean);
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
@@ -454,6 +454,15 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// in dev and export modes, we wrap data in proxies to see
|
||||||
|
// how much of it is used in the initial render
|
||||||
|
const wrapped = should_wrap_data && wrap_data(preloaded);
|
||||||
|
|
||||||
|
// this is an easy way to 'reify' top-level values
|
||||||
|
const _preloaded = should_wrap_data
|
||||||
|
? wrapped.data.map((x: any) => x)
|
||||||
|
: preloaded;
|
||||||
|
|
||||||
let level = data.child;
|
let level = data.child;
|
||||||
for (let i = 0; i < page.parts.length; i += 1) {
|
for (let i = 0; i < page.parts.length; i += 1) {
|
||||||
const part = page.parts[i];
|
const part = page.parts[i];
|
||||||
@@ -465,7 +474,7 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
component: part.component,
|
component: part.component,
|
||||||
props: Object.assign({}, props, {
|
props: Object.assign({}, props, {
|
||||||
params: get_params(match)
|
params: get_params(match)
|
||||||
}, preloaded[i + 1])
|
}, _preloaded[i + 1])
|
||||||
});
|
});
|
||||||
|
|
||||||
level.props.child = <Props["child"]>{
|
level.props.child = <Props["child"]>{
|
||||||
@@ -484,6 +493,47 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
.map(file => `<script src='${req.baseUrl}/client/${file}'></script>`)
|
.map(file => `<script src='${req.baseUrl}/client/${file}'></script>`)
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
|
const unwrapped = should_wrap_data && wrapped.unwrap();
|
||||||
|
|
||||||
|
const preloaded_serialized = preloaded.map(try_serialize);
|
||||||
|
|
||||||
|
if (should_wrap_data && process.send) {
|
||||||
|
const discrepancies = [];
|
||||||
|
|
||||||
|
unwrapped.forEach((clone, i) => {
|
||||||
|
const loaded = preloaded_serialized[i];
|
||||||
|
if (!loaded) return;
|
||||||
|
|
||||||
|
const rendered = try_serialize(clone);
|
||||||
|
|
||||||
|
if (rendered !== loaded) {
|
||||||
|
const part = page.parts[i - 1];
|
||||||
|
const file = part ? part.file : '_layout.html';
|
||||||
|
|
||||||
|
discrepancies.push({
|
||||||
|
file,
|
||||||
|
preloaded: loaded.length,
|
||||||
|
rendered: rendered.length,
|
||||||
|
props: list_unused_properties(preloaded[i], clone)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (discrepancies.length) {
|
||||||
|
process.send({
|
||||||
|
__sapper__: true,
|
||||||
|
event: 'unused_data',
|
||||||
|
url: req.url,
|
||||||
|
discrepancies
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialized = {
|
||||||
|
preloaded: `[${preloaded_serialized.join(',')}]`,
|
||||||
|
store: store && try_serialize(store.get())
|
||||||
|
};
|
||||||
|
|
||||||
let inline_script = `__SAPPER__={${[
|
let inline_script = `__SAPPER__={${[
|
||||||
error && `error:1`,
|
error && `error:1`,
|
||||||
`baseUrl:"${req.baseUrl}"`,
|
`baseUrl:"${req.baseUrl}"`,
|
||||||
@@ -507,6 +557,13 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
res.end(body);
|
res.end(body);
|
||||||
|
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
__sapper__: true,
|
||||||
|
event: 'preload',
|
||||||
|
url: req.url,
|
||||||
|
size: serialized.preloaded.length
|
||||||
|
});
|
||||||
|
|
||||||
process.send({
|
process.send({
|
||||||
__sapper__: true,
|
__sapper__: true,
|
||||||
event: 'file',
|
event: 'file',
|
||||||
|
|||||||
34
src/middleware/list_unused_properties.ts
Normal file
34
src/middleware/list_unused_properties.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export function list_unused_properties(all: any, used: any) {
|
||||||
|
const props: string[] = [];
|
||||||
|
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
function walk(keypath: string, a: any, b: any) {
|
||||||
|
if (seen.has(a)) return;
|
||||||
|
seen.add(a);
|
||||||
|
|
||||||
|
if (!a || typeof a !== 'object') return;
|
||||||
|
|
||||||
|
const is_array = Array.isArray(a);
|
||||||
|
|
||||||
|
for (const key in a) {
|
||||||
|
const child_keypath = keypath
|
||||||
|
? is_array ? `${keypath}[${key}]` : `${keypath}.${key}`
|
||||||
|
: key;
|
||||||
|
|
||||||
|
if (hasProp.call(b, key)) {
|
||||||
|
const a_child = a[key];
|
||||||
|
const b_child = b[key];
|
||||||
|
|
||||||
|
walk(child_keypath, a_child, b_child);
|
||||||
|
} else {
|
||||||
|
props.push(child_keypath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(null, all, used);
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasProp = Object.prototype.hasOwnProperty;
|
||||||
85
src/middleware/wrap_data.ts
Normal file
85
src/middleware/wrap_data.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
type Obj = Record<string, any>;
|
||||||
|
|
||||||
|
export function wrap_data(data: any) {
|
||||||
|
const proxies = new Map();
|
||||||
|
const clones = new Map();
|
||||||
|
|
||||||
|
const handler = {
|
||||||
|
get(target: any, property: string): any {
|
||||||
|
const value = target[property];
|
||||||
|
const intercepted = intercept(value);
|
||||||
|
|
||||||
|
const target_clone = clones.get(target);
|
||||||
|
const child_clone = clones.get(value);
|
||||||
|
|
||||||
|
if (target_clone && target.hasOwnProperty(property)) {
|
||||||
|
target_clone[property] = child_clone || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return intercepted;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function get_or_create_proxy(obj: any) {
|
||||||
|
if (!proxies.has(obj)) {
|
||||||
|
proxies.set(obj, new Proxy(obj, handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies.get(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function intercept(obj: any) {
|
||||||
|
if (clones.has(obj)) return obj;
|
||||||
|
|
||||||
|
if (obj && typeof obj === 'object') {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
clones.set(obj, []);
|
||||||
|
return get_or_create_proxy(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (isPlainObject(obj)) {
|
||||||
|
clones.set(obj, {});
|
||||||
|
return get_or_create_proxy(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clones.set(obj, obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: intercept(data),
|
||||||
|
unwrap: () => {
|
||||||
|
return clones.get(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0')
|
||||||
|
|
||||||
|
function isPlainObject(obj: any) {
|
||||||
|
const proto = Object.getPrototypeOf(obj);
|
||||||
|
|
||||||
|
if (
|
||||||
|
proto !== Object.prototype &&
|
||||||
|
proto !== null &&
|
||||||
|
Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols(obj).length > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function pick(obj: Obj, props: string[]) {
|
||||||
|
const picked: Obj = {};
|
||||||
|
props.forEach(prop => {
|
||||||
|
picked[prop] = obj[prop];
|
||||||
|
});
|
||||||
|
return picked;
|
||||||
|
}
|
||||||
@@ -59,19 +59,9 @@ describe('sapper', function() {
|
|||||||
basepath: '/custom-basepath'
|
basepath: '/custom-basepath'
|
||||||
});
|
});
|
||||||
|
|
||||||
testExport({});
|
describe('export', () => {
|
||||||
|
|
||||||
testExport({ basepath: '/custom-basepath' });
|
|
||||||
});
|
|
||||||
|
|
||||||
function testExport({ basepath = '' }) {
|
|
||||||
describe(basepath ? `export --basepath ${basepath}` : 'export', () => {
|
|
||||||
before(() => {
|
before(() => {
|
||||||
if (basepath) {
|
return exec(`node ${cli} export`);
|
||||||
process.env.BASEPATH = basepath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return exec(`node ${cli} export ${basepath ? `--basepath ${basepath}` : ''}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('export all pages', () => {
|
it('export all pages', () => {
|
||||||
@@ -106,10 +96,7 @@ function testExport({ basepath = '' }) {
|
|||||||
'service-worker.js',
|
'service-worker.js',
|
||||||
'svelte-logo-192.png',
|
'svelte-logo-192.png',
|
||||||
'svelte-logo-512.png',
|
'svelte-logo-512.png',
|
||||||
].map(file => {
|
];
|
||||||
return basepath ? path.join(basepath.replace(/^\//, ''), file) : file;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Client scripts that should show up in the extraction directory.
|
// Client scripts that should show up in the extraction directory.
|
||||||
const expectedClientRegexes = [
|
const expectedClientRegexes = [
|
||||||
/client\/[^/]+\/main(\.\d+)?\.js/,
|
/client\/[^/]+\/main(\.\d+)?\.js/,
|
||||||
@@ -139,7 +126,7 @@ function testExport({ basepath = '' }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
function run({ mode, basepath = '' }) {
|
function run({ mode, basepath = '' }) {
|
||||||
describe(`mode=${mode}`, function () {
|
describe(`mode=${mode}`, function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user