Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] differential legacy builds #2745

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kit/package.json
Expand Up @@ -10,6 +10,7 @@
"type": "module",
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"@vitejs/plugin-legacy": "^1.6.2",
Copy link
Contributor

@bfanger bfanger Jan 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could @vitejs/plugin-legacy become an optional dependency?
That would keep the installation fast for non legacy projects

"cheap-watch": "^1.0.4",
"sade": "^1.7.4",
"vite": "^2.6.12"
Expand Down
73 changes: 60 additions & 13 deletions packages/kit/src/core/build/index.js
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import glob from 'tiny-glob/sync.js';
import vite from 'vite';
import legacy from '@vitejs/plugin-legacy';

import { rimraf } from '../../utils/filesystem.js';
import { deep_merge } from '../../utils/object.js';
Expand Down Expand Up @@ -137,6 +138,20 @@ async function build_client({
}
};

let plugins = [
svelte({
extensions: config.extensions,
emitCss: !config.kit.amp,
compilerOptions: {
hydratable: !!config.kit.hydrate
}
})
];

if (config.kit.legacy) {
plugins = [...plugins, legacy(config.kit.legacy)];
}

// don't warn on overriding defaults
const [modified_vite_config] = deep_merge(default_config, vite_config);

Expand Down Expand Up @@ -166,15 +181,7 @@ async function build_client({
$lib: config.kit.files.lib
}
},
plugins: [
svelte({
extensions: config.extensions,
emitCss: !config.kit.amp,
compilerOptions: {
hydratable: !!config.kit.hydrate
}
})
]
plugins
});

print_config_conflicts(conflicts, 'kit.vite.', 'build_client');
Expand Down Expand Up @@ -280,13 +287,40 @@ async function build_server(
};
});

/** @type {(filename: string) => string} */
const make_legacy_file_name = (file) => {
const f = file.split('.');
return `${f.slice(0, f.length - 1).join('.')}-legacy.${f.slice(f.length - 1)}`;
};

if (config.kit.legacy) {
['vite/legacy-polyfills', ...manifest.components.map(make_legacy_file_name)].forEach((file) => {
if (client_manifest[file]) {
const js_deps = new Set();

find_deps(file, js_deps, new Set());

const js_legacy = Array.from(js_deps);

metadata_lookup[file] = {
entry: client_manifest[file].file,
css: [],
js: js_legacy,
styles: []
};
}
});
}

/** @type {Set<string>} */
const entry_js = new Set();
/** @type {Set<string>} */
const entry_css = new Set();

find_deps(client_entry_file, entry_js, entry_css);

const legacy_entry_file = make_legacy_file_name(client_entry_file);

// prettier-ignore
fs.writeFileSync(
app_file,
Expand All @@ -297,9 +331,10 @@ async function build_server(
import { set_prerendering } from './runtime/env.js';
import * as user_hooks from ${s(app_relative(hooks_file))};

const template = ({ head, body }) => ${s(fs.readFileSync(config.kit.files.template, 'utf-8'))
const template = ({ head, body, legacy_scripts = '' }) => ${s(fs.readFileSync(config.kit.files.template, 'utf-8'))
.replace('%svelte.head%', '" + head + "')
.replace('%svelte.body%', '" + body + "')};
.replace('%svelte.body%', '" + body + "')
.replace('%svelte.legacy_scripts%', '" + legacy_scripts + "')};

let options = null;

Expand All @@ -321,6 +356,12 @@ async function build_server(
css: [${Array.from(entry_css).map(dep => 'assets + ' + s(prefix + dep))}],
js: [${Array.from(entry_js).map(dep => 'assets + ' + s(prefix + dep))}]
},
entry_legacy: ${config.kit.legacy ? `{
file: assets + ${s(prefix + client_manifest[legacy_entry_file].file)},
css: [],
js: [],
polyfills: assets + ${s(prefix + client_manifest['vite/legacy-polyfills'].file)}
},` : 'null,'}
fetched: undefined,
floc: ${config.kit.floc},
get_component_path: id => assets + ${s(prefix)} + entry_lookup[id],
Expand Down Expand Up @@ -411,15 +452,21 @@ async function build_server(
};

const metadata_lookup = ${s(metadata_lookup)};

${config.kit.legacy ?
`\nconst make_legacy_file_name = (filename) => {
let f = filename.split(".");
let end = f.pop();
return f.join(".").concat("-legacy.", end);
}\n` : ''}
async function load_component(file) {
const { entry, css, js, styles } = metadata_lookup[file];
return {
module: await module_lookup[file](),
entry: assets + ${s(prefix)} + entry,
css: css.map(dep => assets + ${s(prefix)} + dep),
js: js.map(dep => assets + ${s(prefix)} + dep),
styles
styles,
legacy: ${config.kit.legacy ? `assets + ${s(prefix)} + metadata_lookup[make_legacy_file_name(file)]["entry"]` : '[]'}
};
}

Expand Down
7 changes: 7 additions & 0 deletions packages/kit/src/core/config/index.js
Expand Up @@ -22,6 +22,13 @@ function validate_template(cwd, validated) {
throw new Error(`${relative} is missing ${tag}`);
}
});

if (validated.kit.legacy) {
const tag = '%svelte.legacy_scripts%';
if (contents.indexOf(tag) === -1) {
throw new Error(`${relative} is missing ${tag}`);
}
}
} else {
throw new Error(`${relative} does not exist`);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/kit/src/core/config/index.spec.js
Expand Up @@ -52,7 +52,8 @@ test('fills in defaults', () => {
router: true,
ssr: true,
target: null,
trailingSlash: 'never'
trailingSlash: 'never',
legacy: null
}
});
});
Expand Down Expand Up @@ -152,7 +153,8 @@ test('fills in partial blanks', () => {
router: true,
ssr: true,
target: null,
trailingSlash: 'never'
trailingSlash: 'never',
legacy: null
}
});
});
Expand Down
11 changes: 10 additions & 1 deletion packages/kit/src/core/config/options.js
Expand Up @@ -187,7 +187,16 @@ const options = object(

return input;
}
)
),
legacy: validate(null, (input, keypath) => {
if (typeof input !== 'object' || Array.isArray(input)) {
throw new Error(
`${keypath} should be an object. See https://github.com/vitejs/vite/tree/main/packages/plugin-legacy for all avaliable options`
);
}

return input;
})
})
},
true
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/config/test/index.js
Expand Up @@ -54,7 +54,8 @@ async function testLoadDefaultConfig(path) {
router: true,
ssr: true,
target: null,
trailingSlash: 'never'
trailingSlash: 'never',
legacy: null
}
});
}
Expand Down
5 changes: 3 additions & 2 deletions packages/kit/src/core/dev/index.js
Expand Up @@ -458,11 +458,12 @@ async function create_plugin(config, dir, cwd, get_manifest) {
router: config.kit.router,
ssr: config.kit.ssr,
target: config.kit.target,
template: ({ head, body }) => {
template: ({ head, body, legacy_scripts = '' }) => {
let rendered = fs
.readFileSync(config.kit.files.template, 'utf8')
.replace('%svelte.head%', () => head)
.replace('%svelte.body%', () => body);
.replace('%svelte.body%', () => body)
.replace('%svelte.legacy_scripts%', () => legacy_scripts);

if (config.kit.amp && validator) {
const result = validator.validateString(rendered);
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/client/renderer.js
Expand Up @@ -67,7 +67,7 @@ export class Renderer {
* @param {{
* Root: CSRComponent;
* fallback: [CSRComponent, CSRComponent];
* target: Node;
* target: Element;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why needed?

* session: any;
* host: string;
* }} opts
Expand Down
19 changes: 15 additions & 4 deletions packages/kit/src/runtime/client/start.js
Expand Up @@ -13,7 +13,7 @@ import { set_paths } from '../paths.js';
* assets: string;
* base: string;
* },
* target: Node;
* target: string;
* session: any;
* host: string;
* route: boolean;
Expand All @@ -22,7 +22,8 @@ import { set_paths } from '../paths.js';
* hydrate: {
* status: number;
* error: Error;
* nodes: Array<Promise<import('types/internal').CSRComponent>>;
* nodes: Array<import('types/internal').CSRComponent>;
* legacy_nodes: Array<import('types/internal').CSRComponent>;
* page: import('types/page').Page;
* };
* }} opts
Expand All @@ -35,7 +36,7 @@ export async function start({ paths, target, session, host, route, spa, trailing
const renderer = new Renderer({
Root,
fallback,
target,
target: document.querySelector(target) ?? document.body,
session,
host
});
Expand All @@ -52,7 +53,17 @@ export async function start({ paths, target, session, host, route, spa, trailing
init(router);
set_paths(paths);

if (hydrate) await renderer.start(hydrate);
if (hydrate) {
if (import.meta.env.LEGACY) {
hydrate.nodes = hydrate.legacy_nodes;
}

hydrate.nodes = await Promise.all(hydrate.nodes.map((n) => import(/* @vite-ignore */ n)));
hydrate.page.query = new URLSearchParams(hydrate.page.query);

await renderer.start(hydrate);
}

if (router) {
if (spa) router.goto(location.href, { replaceState: true }, []);
router.init_listeners();
Expand Down
99 changes: 67 additions & 32 deletions packages/kit/src/runtime/server/page/render.js
Expand Up @@ -115,39 +115,58 @@ export async function render_response({
<script async src="https://cdn.ampproject.org/v0.js"></script>`;
} else if (include_js) {
// prettier-ignore
init = `<script type="module">
import { start } from ${s(options.entry.file)};
start({
target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
paths: ${s(options.paths)},
session: ${try_serialize($session, (error) => {
throw new Error(`Failed to serialize session data: ${error.message}`);
})},
host: ${page && page.host ? s(page.host) : 'location.host'},
route: ${!!page_config.router},
spa: ${!page_config.ssr},
trailing_slash: ${s(options.trailing_slash)},
hydrate: ${page_config.ssr && page_config.hydrate ? `{
status: ${status},
error: ${serialize_error(error)},
nodes: [
${(branch || [])
.map(({ node }) => `import(${s(node.entry)})`)
.join(',\n\t\t\t\t\t\t')}
],
page: {
host: ${page && page.host ? s(page.host) : 'location.host'}, // TODO this is redundant
path: ${page && page.path ? try_serialize(page.path, error => {
throw new Error(`Failed to serialize page.path: ${error.message}`);
}) : null},
query: new URLSearchParams(${page && page.query ? s(page.query.toString()) : ''}),
params: ${page && page.params ? try_serialize(page.params, error => {
throw new Error(`Failed to serialize page.params: ${error.message}`);
}) : null}
init = `<script>window.__KIT_DATA__ = {
target: ${options.target ? `${s(options.target)}` : 'body'},
paths: ${s(options.paths)},
session: ${try_serialize($session, (error) => {
throw new Error(`Failed to serialize session data: ${error.message}`);
})},
host: ${page && page.host ? s(page.host) : 'location.host'},
route: ${!!page_config.router},
spa: ${!page_config.ssr},
trailing_slash: ${s(options.trailing_slash)},
hydrate: ${
page_config.ssr && page_config.hydrate
? `{
status: ${status},
error: ${serialize_error(error)},
nodes: [
${(branch || []).map(({ node }) => `${s(node.entry)}`).join(',\n\t\t\t\t\t\t')}
],
legacy_nodes: [
${(branch || []).map(({ node }) => `${s(node.legacy)}`).join(',\n\t\t\t\t\t\t')},
],
page: {
host: ${page && page.host ? s(page.host) : 'location.host'}, // TODO this is redundant
path: ${
page && page.path
? try_serialize(page.path, (error) => {
throw new Error(`Failed to serialize page.path: ${error.message}`);
})
: null
},
query: ${page && page.query ? s(page.query.toString()) : ''},
params: ${
page && page.params
? try_serialize(page.params, (error) => {
throw new Error(`Failed to serialize page.params: ${error.message}`);
})
: null
}
}` : 'null'}
});
}
}`
: 'null'
}
}</script>\n`;
init += `<script type="module">
import { start } from ${s(options.entry.file)};
start(window.__KIT_DATA__);
</script>`;

if (options.entry_legacy) {
init = `${init}
<script type="module">!function(){try{new Function("m","return import(m)")}catch(o){console.warn("vite: loading legacy build because dynamic import is unsupported, syntax error above should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}}();</script>`;
}
}

if (options.service_worker) {
Expand Down Expand Up @@ -196,10 +215,26 @@ export async function render_response({
headers['permissions-policy'] = 'interest-cohort=()';
}

let legacy_scripts = '';
if (options.entry_legacy) {
legacy_scripts = [
'<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>',
`<script nomodule id="vite-legacy-polyfill" src=${s(
options.entry_legacy.polyfills
)}></script>`,
`<script nomodule id="vite-legacy-entry" data-src="${s(
options.entry_legacy.file
)}">System.import(${s(
options.entry_legacy.file
)}).then(function (m){m.start(window.__KIT_DATA__);});
</script>`
].join('\n\t\t');
}

return {
status,
headers,
body: options.template({ head, body })
body: options.template({ head, body, legacy_scripts })
};
}

Expand Down