mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-16 04:44:35 +00:00
detect unused data on initial render
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
@@ -394,11 +397,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 = {
|
||||||
@@ -420,6 +418,11 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let { data: preloaded_proxies, unwrap } = wrap_data(preloaded);
|
||||||
|
|
||||||
|
// this is an easy way to 'reify' top-level values
|
||||||
|
const reified = preloaded_proxies.map((x: any) => x);
|
||||||
|
|
||||||
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];
|
||||||
@@ -431,7 +434,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])
|
}, reified[i + 1])
|
||||||
});
|
});
|
||||||
|
|
||||||
level.props.child = <Props["child"]>{
|
level.props.child = <Props["child"]>{
|
||||||
@@ -450,6 +453,41 @@ 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 = unwrap();
|
||||||
|
|
||||||
|
const preloaded_serialized = preloaded.map((data, i) => {
|
||||||
|
const all = try_serialize(data);
|
||||||
|
const used = try_serialize(unwrapped[i]);
|
||||||
|
|
||||||
|
if (all !== used) {
|
||||||
|
const props = list_unused_properties(data, unwrapped[i]);
|
||||||
|
|
||||||
|
const part = page.parts[i - 1] || { file: 'routes/_layout.html' };
|
||||||
|
console.log(`${part.file} is preloading more data (${prettyBytes(all.length)}) than is initially rendered (${prettyBytes(used.length)}). The following properties are unused...`);
|
||||||
|
|
||||||
|
const slice = props.length > 22
|
||||||
|
? props.slice(0, 20)
|
||||||
|
: props;
|
||||||
|
|
||||||
|
console.log(slice.join('\n'));
|
||||||
|
|
||||||
|
if (props.length > slice.length) {
|
||||||
|
console.log(`...and ${props.length - slice.length} more`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
});
|
||||||
|
|
||||||
|
const serialized = {
|
||||||
|
preloaded: `[${preloaded_serialized.join(',')}]`,
|
||||||
|
store: store && try_serialize(store.get())
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serialized.preloaded.length > 10000) {
|
||||||
|
console.log(`preloaded data is ${prettyBytes(serialized.preloaded.length)}. that's too much!`)
|
||||||
|
}
|
||||||
|
|
||||||
let inline_script = `__SAPPER__={${[
|
let inline_script = `__SAPPER__={${[
|
||||||
error && `error:1`,
|
error && `error:1`,
|
||||||
`baseUrl:"${req.baseUrl}"`,
|
`baseUrl:"${req.baseUrl}"`,
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user