Skip to content

Commit 8f9f0c6

Browse files
authored
fix: Restore kernel-browser-runtime sourcemaps in extension (#575)
When we introduced the `kernel-browser-runtime` package, we effectively broke debugging the kernel by breaking its sourcemaps. This happened because we copied its build output (also from Vite) using `vite-plugin-static-copy`. To fix this, this PR modifies the extension `vite.config.ts` to build `kernel-browser-runtime` directly. A bigger mystery is why Vite doesn't pull in the sourcemaps of bundled dependencies that ship them. The symptom of this is that we see transpiled `.js` and `.mjs` files in the debugger rather than original sources. However, this is still a significant improvement over the status quo, and we can address the larger sourcemap problem later.
1 parent 600cd72 commit 8f9f0c6

11 files changed

Lines changed: 122 additions & 79 deletions

File tree

packages/extension/scripts/build-constants.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
import path from 'path';
44

5-
export const sourceDir = './src';
6-
export const buildDir = path.resolve(sourceDir, '../dist');
5+
const dirname = path.dirname(new URL(import.meta.url).pathname);
6+
export const rootDir = path.resolve(dirname, '../..');
7+
const extensionDir = path.resolve(rootDir, 'extension');
8+
export const sourceDir = path.resolve(extensionDir, 'src');
9+
export const outDir = path.resolve(extensionDir, 'dist');
10+
export const kernelBrowserRuntimeSrcDir = path.resolve(
11+
rootDir,
12+
'kernel-browser-runtime/src',
13+
);
714

815
/**
916
* @type {import('@ocap/vite-plugins').PreludeRecord}
@@ -12,4 +19,5 @@ export const trustedPreludes = {
1219
background: {
1320
path: path.resolve(sourceDir, 'env/background-trusted-prelude.js'),
1421
},
22+
'kernel-worker': { content: "import './endoify.js';" },
1523
};

packages/extension/src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"permissions": ["offscreen", "unlimitedStorage"],
1414
"sandbox": {
15-
"pages": ["browser-runtime/vat/iframe.html"]
15+
"pages": ["iframe.html"]
1616
},
1717
"content_security_policy": {
1818
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';",

packages/extension/src/offscreen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async function makeKernelWorker(): Promise<{
5151
kernelStream: DuplexStream<JsonRpcResponse, JsonRpcCall>;
5252
vatWorkerService: VatWorkerServer;
5353
}> {
54-
const worker = new Worker('browser-runtime/kernel-worker/index.js', {
54+
const worker = new Worker('kernel-worker.js', {
5555
type: 'module',
5656
});
5757

@@ -69,7 +69,7 @@ async function makeKernelWorker(): Promise<{
6969
(vatId) =>
7070
makeIframeVatWorker({
7171
id: vatId,
72-
iframeUri: 'browser-runtime/vat/iframe.html',
72+
iframeUri: 'iframe.html',
7373
getPort: initializeMessageChannel,
7474
logger: logger.subLogger({
7575
tags: ['iframe-vat-worker', vatId],

packages/extension/test/build/build-tests.mjs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
22
import path from 'path';
33

44
import {
5-
buildDir,
5+
outDir,
66
sourceDir,
77
trustedPreludes,
88
} from '../../scripts/build-constants.mjs';
@@ -12,31 +12,24 @@ const { hasOwn } = Object;
1212
const untransformedFiles = [
1313
{
1414
sourcePath: path.resolve('../kernel-shims/dist/endoify.js'),
15-
buildPath: path.resolve(buildDir, 'endoify.js'),
15+
buildPath: path.resolve(outDir, 'endoify.js'),
1616
},
1717
{
1818
sourcePath: path.resolve(sourceDir, 'env/dev-console.js'),
19-
buildPath: path.resolve(buildDir, 'dev-console.js'),
19+
buildPath: path.resolve(outDir, 'dev-console.js'),
2020
},
2121
...Object.values(trustedPreludes).map((prelude) => {
2222
if (hasOwn(prelude, 'path')) {
2323
return {
2424
sourcePath: prelude.path,
25-
buildPath: path.join(buildDir, path.basename(prelude.path)),
25+
buildPath: path.join(outDir, path.basename(prelude.path)),
2626
};
2727
}
2828

29-
const preludePath = /^import ["']([^"']+)["']/iu.exec(prelude.content)[1];
30-
if (!preludePath) {
31-
throw new Error('No prelude path found in content');
32-
}
33-
34-
return {
35-
sourcePath: preludePath,
36-
buildPath: path.join(buildDir, path.basename(preludePath)),
37-
};
29+
// This is a "content" prelude, which does not specify an original source path.
30+
return undefined;
3831
}),
39-
];
32+
].filter(Boolean);
4033

4134
await runTests();
4235

@@ -58,7 +51,7 @@ async function runTests() {
5851
* Test that shims and preludes are packaged untransformed.
5952
*/
6053
async function checkUntransformed() {
61-
for (const { buildPath, sourcePath } of untransformedFiles) {
54+
for (const { sourcePath, buildPath } of untransformedFiles) {
6255
const [originalContent, builtContent] = await Promise.all([
6356
fs.readFile(sourcePath, 'utf8'),
6457
fs.readFile(buildPath, 'utf8'),
@@ -76,7 +69,7 @@ async function checkUntransformed() {
7669
*/
7770
async function checkTrustedPreludes() {
7871
for (const [outputFileName, prelude] of Object.entries(trustedPreludes)) {
79-
const outputFilePath = path.join(buildDir, `${outputFileName}.js`);
72+
const outputFilePath = path.join(outDir, `${outputFileName}.js`);
8073
const outputFileContent = await fs.readFile(outputFilePath, 'utf8');
8174
const expectedImportStatement = hasOwn(prelude, 'path')
8275
? `import "./${path.basename(prelude.path)}";`

packages/extension/vite.config.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// <reference types="vitest" />
33

44
import {
5+
deduplicateAssets,
56
extensionDev,
67
htmlTrustedPrelude,
78
jsTrustedPrelude,
@@ -14,8 +15,10 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
1415
import type { Target } from 'vite-plugin-static-copy';
1516

1617
import {
18+
rootDir,
19+
kernelBrowserRuntimeSrcDir,
20+
outDir,
1721
sourceDir,
18-
buildDir,
1922
trustedPreludes,
2023
} from './scripts/build-constants.mjs';
2124

@@ -25,15 +28,11 @@ import {
2528
*/
2629
const staticCopyTargets: readonly (string | Target)[] = [
2730
// The extension manifest
28-
'manifest.json',
29-
// External modules
30-
'env/dev-console.js',
31-
'env/background-trusted-prelude.js',
32-
'../../kernel-shims/dist/endoify.js',
33-
{
34-
src: '../../kernel-browser-runtime/dist/static/*',
35-
dest: './browser-runtime',
36-
},
31+
path.resolve(sourceDir, 'manifest.json'),
32+
// Trusted prelude-related
33+
path.resolve(sourceDir, 'env/dev-console.js'),
34+
path.resolve(sourceDir, 'env/background-trusted-prelude.js'),
35+
path.resolve(rootDir, 'kernel-shims/dist/endoify.js'),
3736
];
3837

3938
// https://vitejs.dev/config/
@@ -45,16 +44,26 @@ export default defineConfig(({ mode }) => {
4544
}
4645

4746
return {
48-
root: sourceDir,
47+
root: rootDir,
4948

5049
build: {
50+
assetsDir: '',
5151
emptyOutDir: true,
52-
outDir: buildDir,
52+
// Disable Vite's module preload, which may cause SES-dependent code to run before lockdown.
53+
modulePreload: false,
54+
outDir,
5355
rollupOptions: {
5456
input: {
5557
background: path.resolve(sourceDir, 'background.ts'),
5658
offscreen: path.resolve(sourceDir, 'offscreen.html'),
5759
popup: path.resolve(sourceDir, 'popup.html'),
60+
// kernel-browser-runtime
61+
'kernel-worker': path.resolve(
62+
kernelBrowserRuntimeSrcDir,
63+
'kernel-worker',
64+
'kernel-worker.ts',
65+
),
66+
vat: path.resolve(kernelBrowserRuntimeSrcDir, 'vat', 'iframe.html'),
5867
},
5968
output: {
6069
entryFileNames: '[name].js',
@@ -82,10 +91,30 @@ export default defineConfig(({ mode }) => {
8291
silent: isDev,
8392
}),
8493
viteChecker({ typescript: { tsconfigPath: 'tsconfig.build.json' } }),
85-
// Import sourcemaps from our own libraries
86-
// For whatever reason, the types don't match, but it works
94+
// Deduplicate sqlite-wasm assets
95+
deduplicateAssets({
96+
assetFilter: (fileName) =>
97+
fileName.includes('sqlite3-') &&
98+
!fileName.includes('sqlite3-opfs-async-proxy'),
99+
expectedCount: 2,
100+
}),
101+
// Would you believe that there's no other way to do this?
102+
{
103+
name: 'move-html-files-to-root',
104+
generateBundle: {
105+
order: 'post',
106+
handler(_, bundle) {
107+
for (const chunk of Object.values(bundle)) {
108+
if (!chunk.fileName.endsWith('.html')) {
109+
continue;
110+
}
111+
chunk.fileName = path.basename(chunk.fileName);
112+
}
113+
},
114+
},
115+
},
87116
// Open the extension in the browser when watching
88-
isWatching && extensionDev({ extensionPath: buildDir }),
117+
isWatching && extensionDev({ extensionPath: outDir }),
89118
],
90119
};
91120
});

packages/kernel-browser-runtime/vite.config.ts

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// eslint-disable-next-line spaced-comment
22
/// <reference types="vitest" />
33

4-
import { jsTrustedPrelude } from '@ocap/vite-plugins';
4+
import { deduplicateAssets, jsTrustedPrelude } from '@ocap/vite-plugins';
55
import type { PreludeRecord } from '@ocap/vite-plugins';
66
import path from 'path';
77
import { defineConfig } from 'vite';
@@ -62,12 +62,11 @@ export default defineConfig(({ mode }) => {
6262
vat: path.resolve(sourceDir, 'vat', 'iframe.html'),
6363
},
6464
output: {
65-
format: 'esm',
6665
// Basically, create directories for each entry point and put all related
6766
// files in them.
6867
entryFileNames: (chunkInfo) => {
69-
// This property isn't really documented, but it appears to be equivalent
70-
// to the keys of `rollupOptions.input`.
68+
// This property isn't really documented, but it appears to be the absolute path
69+
// to the entry point.
7170
if (!chunkInfo.facadeModuleId) {
7271
return '[name].js';
7372
}
@@ -85,7 +84,6 @@ export default defineConfig(({ mode }) => {
8584
},
8685
chunkFileNames: '[name].js',
8786
assetFileNames: '[name].[ext]',
88-
preserveModulesRoot: sourceDir,
8987
},
9088
},
9189
...(isDev
@@ -110,28 +108,13 @@ export default defineConfig(({ mode }) => {
110108
// (It's probably related to the the file being conditionally imported in multiple places.)
111109
// To avoid bloating the bundle, we delete the duplicate files. Thankfully, these files are
112110
// extraneous because we don't hit their code paths in practice. (If we did, things would
113-
// blow up spectacularly.)
114-
{
115-
name: 'deduplicate-sqlite-wasm',
116-
enforce: 'post',
117-
generateBundle(_, bundle) {
118-
const extraneousAssets = Object.values(bundle).filter(
119-
(assetOrChunk) =>
120-
assetOrChunk.fileName.startsWith('sqlite3-') &&
121-
!assetOrChunk.fileName.includes('sqlite3-opfs-async-proxy'),
122-
);
123-
124-
if (extraneousAssets.length !== 2) {
125-
throw new Error(
126-
`Expected 2 extraneous sqlite3.wasm assets, got ${extraneousAssets.length}: ${extraneousAssets.map((asset) => asset.fileName).join(', ')}`,
127-
);
128-
}
129-
130-
for (const asset of extraneousAssets) {
131-
delete bundle[asset.fileName];
132-
}
133-
},
134-
},
111+
// blow up.)
112+
deduplicateAssets({
113+
assetFilter: (fileName) =>
114+
fileName.startsWith('sqlite3-') &&
115+
!fileName.includes('sqlite3-opfs-async-proxy'),
116+
expectedCount: 2,
117+
}),
135118
],
136119
};
137120
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Plugin as VitePlugin } from 'vite';
2+
3+
type Options = {
4+
assetFilter: (fileName: string) => boolean;
5+
expectedCount: number;
6+
};
7+
8+
/**
9+
* Vite plugin that deletes extraneous assets from the bundle.
10+
*
11+
* @param options - Options for the plugin
12+
* @param options.assetFilter - A function that filters the assets to be deleted
13+
* @param options.expectedCount - The expected number of assets to be deleted
14+
* @throws If the number of extraneous assets is not equal to the expected count.
15+
* @returns The Vite plugin.
16+
*/
17+
export function deduplicateAssets({
18+
assetFilter,
19+
expectedCount,
20+
}: Options): VitePlugin {
21+
return {
22+
name: 'ocap-kernel:deduplicate-assets',
23+
enforce: 'post',
24+
generateBundle(_, bundle) {
25+
const extraneousAssets = Object.values(bundle).filter((assetOrChunk) =>
26+
assetFilter(assetOrChunk.fileName),
27+
);
28+
29+
if (extraneousAssets.length !== expectedCount) {
30+
throw new Error(
31+
`Expected ${expectedCount} extraneous assets, got ${extraneousAssets.length}: ${extraneousAssets.map((asset) => asset.fileName).join(', ')}`,
32+
);
33+
}
34+
35+
for (const asset of extraneousAssets) {
36+
delete bundle[asset.fileName];
37+
}
38+
},
39+
};
40+
}

packages/vite-plugins/src/extension-dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function extensionDev({
2323
};
2424

2525
return {
26-
name: 'vite:extension-dev',
26+
name: 'ocap-kernel:extension-dev',
2727

2828
// This is called when the server starts
2929
async configureServer(server) {

packages/vite-plugins/src/html-trusted-prelude.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { load as loadHtml } from 'cheerio';
2-
import path from 'path';
32
import { format as prettierFormat } from 'prettier';
43
import type { Plugin as VitePlugin } from 'vite';
54

65
/**
76
* Vite plugin to insert the endoify script before the first script in the head element.
7+
* Assumes that `endoify.js` is located in the root of the web app.
88
*
99
* @throws If the HTML document already references the endoify script or lacks the expected
1010
* structure.
@@ -13,7 +13,7 @@ import type { Plugin as VitePlugin } from 'vite';
1313
export function htmlTrustedPrelude(): VitePlugin {
1414
return {
1515
name: 'ocap-kernel:html-trusted-prelude',
16-
async transformIndexHtml(htmlString, ctx): Promise<string> {
16+
async transformIndexHtml(htmlString): Promise<string> {
1717
const htmlDoc = loadHtml(htmlString);
1818

1919
if (htmlDoc('script[src="endoify.ts"]').length > 0) {
@@ -34,19 +34,7 @@ export function htmlTrustedPrelude(): VitePlugin {
3434
);
3535
}
3636

37-
// Calculate relative path to endoify.js based on HTML file location
38-
const htmlFilePath = ctx.filename;
39-
const htmlDirPath = path.dirname(htmlFilePath);
40-
const rootDir = path.resolve(process.cwd(), 'src');
41-
const relativePathToRoot = path.relative(htmlDirPath, rootDir);
42-
43-
// Create the relative path to endoify.js
44-
const endoifyPath = path
45-
.join(relativePathToRoot, 'endoify.js')
46-
.split(path.sep)
47-
.join('/');
48-
49-
const endoifyElement = `<script src="${endoifyPath}" type="module"></script>`;
37+
const endoifyElement = `<script src="/endoify.js" type="module"></script>`;
5038
htmlDoc(endoifyElement).insertBefore('head:first script:first');
5139

5240
return await prettierFormat(htmlDoc.html(), {

packages/vite-plugins/src/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as indexModule from './index.ts';
55
describe('index', () => {
66
it('has the expected exports', () => {
77
expect(Object.keys(indexModule).sort()).toStrictEqual([
8+
'deduplicateAssets',
89
'extensionDev',
910
'htmlTrustedPrelude',
1011
'jsTrustedPrelude',

0 commit comments

Comments
 (0)