@sveltejs/kit
Version:
Here be dragons, etc etc.
510 lines (428 loc) • 14.1 kB
JavaScript
;
require('./package.js');
var path = require('path');
require('module');
var fs = require('fs');
var child_process = require('child_process');
var utils = require('./utils.js');
var create_app = require('./create_app.js');
var util = require('util');
var rollup = require('rollup');
var rollupPluginTerser = require('rollup-plugin-terser');
var css_chunks = require('rollup-plugin-css-chunks');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var child_process__default = /*#__PURE__*/_interopDefaultLegacy(child_process);
var css_chunks__default = /*#__PURE__*/_interopDefaultLegacy(css_chunks);
let FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, isTTY=true;
if (typeof process !== 'undefined') {
({ FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env);
isTTY = process.stdout && process.stdout.isTTY;
}
const $ = {
enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && (
FORCE_COLOR != null && FORCE_COLOR !== '0' || isTTY
)
};
function init(x, y) {
let rgx = new RegExp(`\\x1b\\[${y}m`, 'g');
let open = `\x1b[${x}m`, close = `\x1b[${y}m`;
return function (txt) {
if (!$.enabled || txt == null) return txt;
return open + ((''+txt).includes(close) ? txt.replace(rgx, close + open) : txt) + close;
};
}
const bold = init(1, 22);
const green = init(32, 39);
const cyan = init(36, 39);
const gray = init(90, 39);
const inject_styles = `
export default function(files) {
return Promise.all(files.map(function(file) { return new Promise(function(fulfil, reject) {
var href = new URL(file, import.meta.url);
var baseURI = document.baseURI;
if (!baseURI) {
var baseTags = document.getElementsByTagName('base');
baseURI = baseTags.length ? baseTags[0].href : document.URL;
}
var relative = ('' + href).substring(baseURI.length);
var link = document.querySelector('link[rel=stylesheet][href="' + relative + '"]')
|| document.querySelector('link[rel=stylesheet][href="' + href + '"]');
if (!link) {
link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
if (link.sheet) {
fulfil();
} else {
link.onload = function() { return fulfil() };
link.onerror = reject;
}
})}));
};`.trim();
const INJECT_STYLES_NAME = 'inject_styles';
const INJECT_STYLES_ID = 'inject_styles.js';
const find_css = (chunk, bundle) => {
const css_files = new Set();
const visited = new Set();
const recurse = (c) => {
if (visited.has(c)) return;
visited.add(c);
if (c.imports) {
c.imports.forEach((file) => {
if (file.endsWith('.css')) {
css_files.add(file);
} else {
const imported_chunk = bundle[file];
if (imported_chunk) {
recurse(imported_chunk);
}
}
});
}
};
recurse(chunk);
return Array.from(css_files);
};
const css_injection = {
name: 'svelte-css-injection',
buildStart() {
this.emitFile({
type: 'chunk',
id: INJECT_STYLES_ID,
name: INJECT_STYLES_NAME,
preserveSignature: 'allow-extension'
});
},
load(id) {
return id === INJECT_STYLES_ID ? inject_styles : null;
},
resolveId(importee) {
return importee === INJECT_STYLES_ID ? INJECT_STYLES_ID : null;
},
renderDynamicImport({ targetModuleId }) {
if (targetModuleId) {
const t = Buffer.from(targetModuleId).toString('hex');
return {
left: 'Promise.all([import(',
right: `), ___SVELTE_CSS_INJECTION___${t}___]).then(function(x) { return x[0]; })`
};
} else {
return {
left: 'import(',
right: ')'
};
}
},
async generateBundle(
_options,
bundle
) {
const inject_styles_file = Object.keys(bundle).find((f) => f.startsWith('inject_styles'));
let has_css = false;
for (const name in bundle) {
const chunk = bundle[name];
let chunk_has_css = false;
if (chunk.code) {
chunk.code = chunk.code.replace(/___SVELTE_CSS_INJECTION___([0-9a-f]+)___/g, (_m, id) => {
id = Buffer.from(id, 'hex').toString();
const target = (
Object.values(bundle).find(
(c) => (c).modules && (c).modules[id]
)
);
if (target) {
const css_files = find_css(target, bundle);
if (css_files.length > 0) {
chunk_has_css = true;
return `__inject_styles(${JSON.stringify(css_files)})`;
}
}
return '';
});
if (chunk_has_css) {
has_css = true;
chunk.code += `\nimport __inject_styles from './${inject_styles_file}';`;
}
}
}
if (!has_css) {
delete bundle[inject_styles_file];
}
// console.log(bundle);
}
};
const exec = util.promisify(child_process__default['default'].exec);
const snowpack_main = require.resolve('snowpack');
const snowpack_pkg_file = path__default['default'].join(snowpack_main, '../../package.json');
const snowpack_pkg = require(snowpack_pkg_file); // eslint-disable-line
const snowpack_bin = path__default['default'].resolve(path__default['default'].dirname(snowpack_pkg_file), snowpack_pkg.bin.snowpack);
const ignorable_warnings = new Set(['EMPTY_BUNDLE', 'MISSING_EXPORT']);
const onwarn = (warning, handler) => {
// TODO would be nice to just eliminate the circular dependencies instead of
// squelching these warnings (it happens when e.g. the root layout imports
// from $app/navigation)
if (ignorable_warnings.has(warning.code)) return;
handler(warning);
};
const DIR = '.svelte';
const ASSETS = `${DIR}/assets`;
const UNOPTIMIZED = `${DIR}/build/unoptimized`;
const OPTIMIZED = `${DIR}/build/optimized`;
const s = JSON.stringify;
async function build(config) {
const manifest = create_app.create_manifest_data(config.paths.routes);
utils.mkdirp(ASSETS);
await rimraf(UNOPTIMIZED);
await rimraf(OPTIMIZED);
create_app.create_app({
manifest_data: manifest,
output: '.svelte/assets'
});
utils.copy_assets();
const progress = {
transformed_client: false,
transformed_server: false,
optimized_client: false,
optimized_server: false
};
process.stdout.write('\x1b[s');
const tick = bold(green('✔'));
const render = () => process.stdout.write('\x1b[u' + `
${bold(cyan('Transforming...'))}
${progress.transformed_client ? `${tick} client` : gray('⧗ client')}
${progress.transformed_server ? `${tick} server` : gray('⧗ server')}
${bold(cyan('Optimizing...'))}
${progress.optimized_client ? `${tick} client ` : gray('⧗ client')}
${progress.optimized_server ? `${tick} server ` : gray('⧗ server')}
`.replace(/^\t/gm, '').trimStart());
render();
const mount = `--mount.${config.paths.routes}=/_app/routes --mount.${config.paths.setup}=/_app/setup`;
const promises = {
transform_client: exec(`node ${snowpack_bin} build ${mount} --out=${UNOPTIMIZED}/client`).then(() => {
progress.transformed_client = true;
render();
}),
transform_server: exec(`node ${snowpack_bin} build ${mount} --out=${UNOPTIMIZED}/server --ssr`).then(() => {
progress.transformed_server = true;
render();
})
};
// we await this promise because we can't start optimizing the server
// until client optimization is complete
await promises.transform_client;
const client = {
entry: null,
deps: {}
};
const entry = path__default['default'].resolve(`${UNOPTIMIZED}/client/_app/assets/runtime/internal/start.js`);
const client_chunks = await rollup.rollup({
input: {
entry
},
plugins: [
{
name: 'deproxy-css',
async resolveId(importee, importer) {
if (/\.css\.proxy\.js$/.test(importee)) {
const deproxied = importee.replace(/\.css\.proxy\.js$/, '.css');
const resolved = await this.resolve(deproxied, importer);
return resolved.id;
}
}
},
css_chunks__default['default']({
injectImports: true,
sourcemap: true
}),
css_injection,
{
name: 'generate-client-manifest',
generateBundle(_options, bundle) {
const reverse_lookup = new Map();
const routes = path__default['default'].resolve(`${UNOPTIMIZED}/client/_app/routes`);
let inject_styles;
for (const key in bundle) {
const chunk = bundle[key];
if (chunk.facadeModuleId === entry) {
client.entry = key;
} else if (chunk.facadeModuleId === 'inject_styles.js') {
inject_styles = key;
} else if (chunk.modules) {
for (const id in chunk.modules) {
if (id.startsWith(routes) && id.endsWith('.js')) {
const file = id.slice(routes.length + 1);
reverse_lookup.set(file, key);
}
}
}
}
const find_deps = (key, js, css) => {
if (js.has(key)) return;
js.add(key);
const chunk = bundle[key];
if (chunk) {
const imports = chunk.imports;
if (imports) {
imports.forEach((key) => {
if (key.endsWith('.css')) {
js.add(inject_styles);
css.add(key);
} else {
find_deps(key, js, css);
}
});
}
} else {
this.error(`'${key}' is imported but could not be bundled`);
}
return { js, css };
};
const get_deps = (key) => {
const js = new Set();
const css = new Set();
find_deps(key, js, css);
return {
js: Array.from(js),
css: Array.from(css)
};
};
client.deps.__entry__ = get_deps(client.entry);
manifest.components.forEach((component) => {
const file = path__default['default'].normalize(component.file.replace(/\.svelte$/, '.js'));
const key = reverse_lookup.get(file);
client.deps[component.name] = get_deps(key);
});
}
},
rollupPluginTerser.terser()
],
onwarn,
// TODO ensure this works with external node modules (on server)
external: (id) => id[0] !== '.' && !path__default['default'].isAbsolute(id)
});
await client_chunks.write({
dir: `${OPTIMIZED}/client/_app`,
entryFileNames: '[name]-[hash].js',
chunkFileNames: '[name]-[hash].js',
assetFileNames: '[name]-[hash].js', // TODO CSS filenames aren't hashed?
format: 'esm',
sourcemap: true
});
progress.optimized_client = true;
render();
// just in case the server is still transforming...
await promises.transform_server;
const setup_file = `${UNOPTIMIZED}/server/_app/setup/index.js`;
if (!fs__default['default'].existsSync(setup_file)) {
utils.mkdirp(path__default['default'].dirname(setup_file));
fs__default['default'].writeFileSync(setup_file, '');
}
// TODO do we need component file names in build?
const stringify_component = (c) => `{ name: ${s(c.name)}, file: ${s(c.file)}, url: ${s(c.url)} }`;
const stringified_manifest = `
{
layout: ${stringify_component(manifest.layout)},
error: ${stringify_component(manifest.error)},
components: [
${manifest.components.map(c => stringify_component(c)).join(',\n\t\t\t\t')}
],
pages: [
${manifest.pages
.map(({ path, pattern, parts }) => {
return `{ path: ${s(path)}, pattern: ${pattern}, parts: ${s(parts)} }`;
})
.join(',\n\t\t\t\t')}
],
endpoints: [
${manifest.endpoints
.map(({ name, pattern, file, params }) => {
return `{ name: ${s(name)}, pattern: ${pattern}, file: ${s(file)}, params: ${s(params)} }`;
})
.join(',\n\t\t\t\t')}
]
}
`.replace(/^\t{2}/gm, '').trim();
fs__default['default'].writeFileSync('.svelte/build/manifest.js', `export default ${stringified_manifest};`);
fs__default['default'].writeFileSync('.svelte/build/manifest.cjs', `module.exports = ${stringified_manifest};`);
fs__default['default'].writeFileSync(`${UNOPTIMIZED}/server/app.js`, `
import * as renderer from '@sveltejs/kit/dist/renderer';
import root from './_app/assets/generated/root.js';
import * as setup from './_app/setup/index.js';
import manifest from '../../manifest.js';
const template = ({ head, body }) => ${s(fs__default['default'].readFileSync(config.paths.template, 'utf-8'))
.replace('%svelte.head%', '" + head + "')
.replace('%svelte.body%', '" + body + "')};
const client = ${s(client)};
export const paths = {
static: ${s(config.paths.static)}
};
export function render(request, { only_prerender = false } = {}) {
return renderer.render(request, {
static_dir: paths.static,
template,
manifest,
target: ${s(config.target)},${config.startGlobal ? `\n\t\t\t\t\tstart_global: ${s(config.startGlobal)},` : ''}
client,
root,
setup,
load: (route) => require(\`./routes/\${route.name}.js\`),
dev: false,
only_prerender
});
}
`.replace(/^\t{3}/gm, '').trim());
const server_input = {
app: `${UNOPTIMIZED}/server/app.js`
};
[
manifest.layout,
manifest.error,
...manifest.components,
...manifest.endpoints
].forEach((item) => {
server_input[`routes/${item.name}`] = `${UNOPTIMIZED}/server${item.url.replace(
/\.\w+$/,
'.js'
)}`;
});
const server_chunks = await rollup.rollup({
input: server_input,
plugins: [
{
name: 'remove-css',
load(id) {
if (/\.css\.proxy\.js$/.test(id)) return '';
}
},
// TODO add server manifest generation so we can prune
// imports before zipping for cloud functions
rollupPluginTerser.terser()
],
onwarn,
// TODO ensure this works with external node modules (on server)
external: (id) => id[0] !== '.' && !path__default['default'].isAbsolute(id)
});
await server_chunks.write({
dir: `${OPTIMIZED}/server`,
format: 'cjs', // TODO some adapters might want ESM?
exports: 'named',
entryFileNames: '[name].js',
chunkFileNames: 'chunks/[name].js',
assetFileNames: 'assets/[name].js',
sourcemap: true
});
progress.optimized_server = true;
render();
console.log();
}
async function rimraf(path) {
return new Promise((resolve) => {
(fs__default['default'].rm || fs__default['default'].rmdir)(path, { recursive: true, force: true }, () => resolve());
});
}
exports.build = build;
//# sourceMappingURL=index2.js.map