UNPKG

@sveltejs/kit

Version:

Here be dragons, etc etc.

510 lines (428 loc) 14.1 kB
'use strict'; 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