Skip to content

Commit 740e721

Browse files
authored
Bundle dependencies (sveltejs#67)
* zip dependencies instead of installing with turbo * convince esbuild to work * remove logging * install deps * try this * exclude sourcemaps * minimise bundle * remove jszip, which ended up being surplus to requirements * remove unused code
1 parent 14b06a0 commit 740e721

File tree

11 files changed

+166
-35
lines changed

11 files changed

+166
-35
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
!.env.example
99
.apps
1010
.vercel
11+
/content/tutorial/common/.svelte-kit
12+
/content/tutorial/common/node_modules
13+
/content/tutorial/common/package-lock.json

content/tutorial/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"prepare": "svelte-kit sync"
1010
},
1111
"devDependencies": {
12-
"@sveltejs/adapter-auto": "next",
1312
"@sveltejs/kit": "next",
13+
"esbuild-wasm": "^0.14.43",
1414
"svelte": "^3.44.0"
1515
},
1616
"type": "module"
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import adapter from '@sveltejs/adapter-auto';
2-
31
/** @type {import('@sveltejs/kit').Config} */
42
const config = {
5-
kit: {
6-
adapter: adapter()
7-
}
3+
kit: {}
84
};
95

106
export default config;

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.1",
44
"scripts": {
55
"dev": "svelte-kit dev",
6-
"build": "svelte-kit build && node scripts/create-middleware.js",
6+
"build": "node scripts/create-common-bundle && svelte-kit build && node scripts/create-middleware",
77
"package": "svelte-kit package",
88
"preview": "svelte-kit preview",
99
"prepare": "svelte-kit sync",
@@ -17,16 +17,19 @@
1717
"@sveltejs/kit": "next",
1818
"@sveltejs/site-kit": "^2.1.0",
1919
"diff": "^5.1.0",
20+
"esbuild": "^0.14.43",
2021
"prettier": "^2.6.2",
2122
"prettier-plugin-svelte": "^2.7.0",
2223
"svelte": "^3.48.0",
2324
"svelte-check": "^2.7.2",
25+
"tiny-glob": "^0.2.9",
2426
"typescript": "~4.6.4"
2527
},
2628
"type": "module",
2729
"dependencies": {
2830
"@fontsource/roboto-mono": "^4.5.7",
2931
"@webcontainer/api": "^0.0.1",
32+
"adm-zip": "^0.5.9",
3033
"base64-js": "^1.5.1",
3134
"marked": "^4.0.16",
3235
"monaco-editor": "^0.33.0",

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/create-common-bundle/boot.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import fs from 'fs';
2+
import AdmZip from 'adm-zip';
3+
4+
const zip = new AdmZip('common.zip');
5+
zip.extractAllTo('.');
6+
7+
fs.mkdirSync('node_modules/.bin');
8+
9+
fs.symlinkSync('../@sveltejs/kit/svelte-kit.js', 'node_modules/.bin/svelte-kit');
10+
fs.chmodSync('node_modules/.bin/svelte-kit', 0o777);
11+
12+
fs.symlinkSync('../esbuild/bin/esbuild', 'node_modules/.bin/esbuild');
13+
fs.chmodSync('node_modules/.bin/esbuild', 0o777);

scripts/create-common-bundle/index.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import fs from 'fs';
2+
import esbuild from 'esbuild';
3+
import AdmZip from 'adm-zip';
4+
import glob from 'tiny-glob/sync.js';
5+
import { fileURLToPath } from 'url';
6+
import { execSync } from 'child_process';
7+
8+
const cwd = 'content/tutorial/common';
9+
10+
if (!fs.existsSync(`${cwd}/node_modules`)) {
11+
execSync('npm install', { cwd });
12+
}
13+
14+
const zip = new AdmZip();
15+
16+
// we selectively exclude certain files to minimise the bundle size.
17+
// this is a bit ropey, but it works
18+
const ignored_basenames = ['.DS_Store', 'LICENSE'];
19+
const ignored_extensions = ['.d.ts', '.map', '.md'];
20+
const ignored_directories = ['.svelte-kit', 'node_modules/.bin', 'node_modules/rollup/dist/shared'];
21+
22+
const ignored_files = new Set([
23+
'node_modules/vite/dist/node/terser.js',
24+
'node_modules/rollup/dist/es/rollup.browser.js',
25+
'node_modules/rollup/dist/rollup.browser.js',
26+
'node_modules/svelte/compiler.js'
27+
]);
28+
29+
for (const file of glob('**', { cwd, filesOnly: true, dot: true })) {
30+
if (ignored_extensions.find((ext) => file.endsWith(ext))) continue;
31+
if (ignored_basenames.find((basename) => file.endsWith('/' + basename))) continue;
32+
if (ignored_directories.find((dir) => file.startsWith(dir + '/'))) continue;
33+
34+
if (ignored_files.has(file)) {
35+
ignored_files.delete(file);
36+
continue;
37+
}
38+
39+
// esbuild is a special case
40+
if (file.startsWith('node_modules/esbuild-wasm/')) {
41+
zip.addFile(
42+
file.replace('node_modules/esbuild-wasm', 'node_modules/esbuild'),
43+
fs.readFileSync(`${cwd}/${file}`)
44+
);
45+
continue;
46+
} else if (file.startsWith('node_modules/esbuild')) {
47+
continue;
48+
}
49+
50+
zip.addFile(file, fs.readFileSync(`${cwd}/${file}`));
51+
}
52+
53+
if (ignored_files.size > 0) {
54+
throw new Error(`expected to find ${Array.from(ignored_files).join(', ')}`);
55+
}
56+
57+
const out = zip.toBuffer();
58+
59+
fs.writeFileSync(`src/lib/client/adapters/common/common.zip`, out);
60+
61+
// bundle adm-zip so we can use it in the webcontainer
62+
esbuild.buildSync({
63+
entryPoints: [fileURLToPath(new URL('./boot.js', import.meta.url))],
64+
bundle: true,
65+
platform: 'node',
66+
minify: true,
67+
outfile: 'src/lib/client/adapters/common/boot.cjs',
68+
format: 'cjs'
69+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/boot.cjs
2+
/common.zip
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import zipped from './common.zip?url';
2+
import boot from './boot.cjs?url';
3+
4+
async function load() {
5+
const result = await Promise.all([
6+
fetch(zipped).then((r) => r.arrayBuffer()),
7+
fetch(boot).then((r) => r.text())
8+
]);
9+
10+
return {
11+
zipped: result[0],
12+
boot: result[1]
13+
};
14+
}
15+
16+
export const ready = load();

src/lib/client/adapters/webcontainer/index.js

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { load } from '@webcontainer/api';
2+
23
import base64 from 'base64-js';
4+
import { ready } from '../common/index.js';
35

46
const WebContainer = await load();
57

@@ -8,29 +10,42 @@ const WebContainer = await load();
810
* @returns {Promise<import('$lib/types').Adapter>}
911
*/
1012
export async function create(stubs) {
11-
const vm = await WebContainer.boot();
12-
1313
const tree = convert_stubs_to_tree(stubs);
14+
15+
const common = await ready;
16+
tree['common.zip'] = file(new Uint8Array(common.zipped));
17+
tree['boot.cjs'] = file(common.boot);
18+
19+
const vm = await WebContainer.boot();
1420
await vm.loadFiles(tree);
1521

22+
const boot = await vm.run(
23+
{
24+
command: 'node',
25+
args: ['boot.cjs']
26+
},
27+
{
28+
stderr: (data) => console.error(`[boot] ${data}`)
29+
}
30+
);
31+
32+
const code = await boot.onExit;
33+
34+
if (code !== 0) {
35+
throw new Error('Failed to initialize WebContainer');
36+
}
37+
1638
const base = await new Promise(async (fulfil, reject) => {
1739
vm.on('server-ready', (port, base) => {
1840
fulfil(base);
1941
});
2042

21-
const install = await vm.run({
22-
command: 'turbo',
23-
args: ['install']
24-
});
25-
26-
const code = await install.onExit;
27-
28-
if (code !== 0) {
29-
reject(new Error('Installation failed'));
30-
return;
31-
}
32-
33-
await vm.run({ command: 'turbo', args: ['run', 'dev'] });
43+
await vm.run(
44+
{ command: 'turbo', args: ['run', 'dev'] },
45+
{
46+
stderr: (data) => console.error(`[dev] ${data}`)
47+
}
48+
);
3449
});
3550

3651
let current = stubs;
@@ -97,11 +112,7 @@ export async function create(stubs) {
97112
tree = /** @type {import('@webcontainer/api').DirectoryEntry} */ (tree[part]).directory;
98113
}
99114

100-
tree[basename] = {
101-
file: {
102-
contents: stub.text ? stub.contents : base64.toByteArray(stub.contents)
103-
}
104-
};
115+
tree[basename] = file(stub.text ? stub.contents : base64.toByteArray(stub.contents));
105116
}
106117

107118
await vm.loadFiles(root);
@@ -132,14 +143,17 @@ function convert_stubs_to_tree(stubs, depth = 1) {
132143
directory: convert_stubs_to_tree(children, depth + 1)
133144
};
134145
} else {
135-
tree[stub.basename] = {
136-
file: {
137-
contents: stub.text ? stub.contents : base64.toByteArray(stub.contents)
138-
}
139-
};
146+
tree[stub.basename] = file(stub.text ? stub.contents : base64.toByteArray(stub.contents));
140147
}
141148
}
142149
}
143150

144151
return tree;
145152
}
153+
154+
/** @param {string | Uint8Array} contents */
155+
function file(contents) {
156+
return {
157+
file: { contents }
158+
};
159+
}

src/lib/server/content.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const text_files = new Set([
1414
'.md'
1515
]);
1616

17-
const excluded = new Set(['.DS_Store', '.gitkeep']);
17+
const excluded = new Set(['.DS_Store', '.gitkeep', '.svelte-kit']);
1818

1919
/** @param {string} file */
2020
function json(file) {
@@ -104,7 +104,7 @@ export function get_section(slug) {
104104
if (section.slug !== slug) continue;
105105

106106
const a = {
107-
...walk('content/tutorial/common'),
107+
...walk('content/tutorial/common', { exclude: ['node_modules'] }),
108108
...walk(`content/tutorial/${part.slug}/common`),
109109
...walk(`${section.dir}/app-a`)
110110
};
@@ -163,8 +163,11 @@ function extract_frontmatter(markdown, dir) {
163163
/**
164164
* Get a list of all files in a directory
165165
* @param {string} cwd - the directory to walk
166+
* @param {{
167+
* exclude?: string[]
168+
* }} options
166169
*/
167-
export function walk(cwd) {
170+
export function walk(cwd, options = {}) {
168171
/** @type {Record<string, import('$lib/types').FileStub | import('$lib/types').DirectoryStub>} */
169172
const result = {};
170173

@@ -179,6 +182,7 @@ export function walk(cwd) {
179182

180183
for (const basename of files) {
181184
if (excluded.has(basename)) continue;
185+
if (options.exclude?.includes(basename)) continue;
182186

183187
const name = dir + basename;
184188
const resolved = path.join(cwd, name);

0 commit comments

Comments
 (0)