Skip to content

Commit bc4290e

Browse files
committed
feat: module mode
1 parent e3aa554 commit bc4290e

17 files changed

+156
-95
lines changed

.github/workflows/main.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,8 @@ jobs:
379379
env:
380380
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
381381

382-
- name: Run TLA Tests
383-
run: SUFFIX_STRING=${{matrix.profile}} node integration-tests/js-compute/test.js --tla ${{ matrix.platform == 'viceroy' && '--local' || '' }} ${{ matrix.profile == 'weval' && '--aot' || '' }}
382+
- name: Run Module Mode Tests
383+
run: SUFFIX_STRING=${{matrix.profile}} node integration-tests/js-compute/test.js --module-mode ${{ matrix.platform == 'viceroy' && '--local' || '' }} ${{ matrix.profile == 'weval' && '--aot' || '' }}
384384
env:
385385
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
386386

@@ -442,7 +442,7 @@ jobs:
442442
env:
443443
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
444444

445-
- name: Run TLA Tests
446-
run: SUFFIX_STRING=debug node integration-tests/js-compute/test.js --tla --debug-build ${{ matrix.platform == 'viceroy' && '--local' || '' }}
445+
- name: Run Module Mode Tests
446+
run: SUFFIX_STRING=debug node integration-tests/js-compute/test.js --module-mode --debug-build ${{ matrix.platform == 'viceroy' && '--local' || '' }}
447447
env:
448448
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}

integration-tests/cli/help.test.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ test('--help should return help on stdout and zero exit code', async function (t
3030
'-V, --version Prints version information',
3131
'OPTIONS:',
3232
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
33+
'--module-mode [experimental] Run all sources as native modules,',
34+
'with full error stack support.',
35+
'--bundle Runs the esbuild bundler (enabled by default, unless using module mode).',
3336
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
34-
'--enable-experimental-top-level-await Enable experimental top level await',
3537
'ARGS:',
3638
"<input> The input JS script's file path [default: bin/index.js]",
3739
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]',
@@ -56,8 +58,10 @@ test('-h should return help on stdout and zero exit code', async function (t) {
5658
'-V, --version Prints version information',
5759
'OPTIONS:',
5860
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
61+
'--module-mode [experimental] Run all sources as native modules,',
62+
'with full error stack support.',
63+
'--bundle Runs the esbuild bundler (enabled by default, unless using module mode).',
5964
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
60-
'--enable-experimental-top-level-await Enable experimental top level await',
6165
'ARGS:',
6266
"<input> The input JS script's file path [default: bin/index.js]",
6367
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]',

integration-tests/js-compute/test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let args = argv.slice(2);
3636

3737
const local = args.includes('--local');
3838
const verbose = args.includes('--verbose');
39-
const tla = args.includes('--tla');
39+
const moduleMode = args.includes('--module-mode');
4040
const aot = args.includes('--aot');
4141
const debugBuild = args.includes('--debug-build');
4242
const filter = args.filter((arg) => !arg.startsWith('--'));
@@ -68,7 +68,7 @@ const branchName = (await zx`git branch --show-current`).stdout
6868
.trim()
6969
.replace(/[^a-zA-Z0-9_-]/g, '_');
7070

71-
const fixture = tla ? 'tla' : 'app';
71+
const fixture = moduleMode ? 'module-mode' : 'app';
7272
const serviceName = `${fixture}--${branchName}${aot ? '--aot' : ''}${process.env.SUFFIX_STRING || ''}`;
7373
let domain;
7474
const fixturePath = join(__dirname, 'fixtures', fixture);

js-compute-runtime-cli.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ const {
99
enableAOT,
1010
aotCache,
1111
enableExperimentalHighResolutionTimeMethods,
12-
enableExperimentalTopLevelAwait,
12+
moduleMode,
13+
bundle,
1314
wasmEngine,
1415
input,
1516
output,
@@ -36,9 +37,10 @@ if (version) {
3637
output,
3738
wasmEngine,
3839
enableExperimentalHighResolutionTimeMethods,
39-
enableExperimentalTopLevelAwait,
4040
enableAOT,
4141
aotCache,
42+
moduleMode,
43+
bundle,
4244
);
4345
await addSdkMetadataField(output, enableAOT);
4446
}

src/bundle.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,13 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry;
115115
},
116116
};
117117

118-
export async function bundle(input, enableExperimentalTopLevelAwait = false) {
118+
export async function bundle(input, moduleMode = false) {
119119
return await build({
120120
conditions: ['fastly'],
121121
entryPoints: [input],
122122
bundle: true,
123123
write: false,
124-
format: enableExperimentalTopLevelAwait ? 'esm' : 'iife',
124+
format: moduleMode ? 'esm' : 'iife',
125125
tsconfig: undefined,
126126
plugins: [fastlyPlugin],
127127
});

src/compileApplicationToWasm.js

+122-73
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export async function compileApplicationToWasm(
2626
output,
2727
wasmEngine,
2828
enableExperimentalHighResolutionTimeMethods = false,
29-
enableExperimentalTopLevelAwait = false,
3029
enableAOT = false,
3130
aotCache = '',
31+
moduleMode = false,
32+
doBundle = false,
3233
) {
3334
try {
3435
if (!(await isFile(input))) {
@@ -94,86 +95,132 @@ export async function compileApplicationToWasm(
9495
process.exit(1);
9596
}
9697

97-
let contents;
98-
try {
99-
contents = await bundle(input, enableExperimentalTopLevelAwait);
100-
} catch (error) {
101-
console.error(`Error:`, error.message);
102-
process.exit(1);
103-
}
98+
let tmpDir;
99+
if (doBundle) {
100+
let contents;
101+
try {
102+
contents = await bundle(input, moduleMode);
103+
} catch (error) {
104+
console.error(`Error:`, error.message);
105+
process.exit(1);
106+
}
104107

105-
let wizerInput = precompile(
106-
contents.outputFiles[0].text,
107-
undefined,
108-
enableExperimentalTopLevelAwait,
109-
);
108+
const precompiled = precompile(
109+
contents.outputFiles[0].text,
110+
undefined,
111+
moduleMode,
112+
);
110113

111-
// for StarlingMonkey, we need to write to a tmpdir pending streaming source hooks or similar
112-
const tmpDir = await getTmpDir();
113-
const outPath = resolve(tmpDir, 'input.js');
114-
await writeFile(outPath, wizerInput);
115-
wizerInput = outPath;
114+
tmpDir = await getTmpDir();
115+
const outPath = resolve(tmpDir, 'input.js');
116+
await writeFile(outPath, precompiled);
116117

117-
try {
118-
if (enableAOT) {
119-
const wevalBin = await weval();
118+
// the bundled output is now the Wizer input
119+
input = outPath;
120+
}
120121

121-
let wevalProcess = spawnSync(
122-
`"${wevalBin}"`,
123-
[
124-
'weval',
125-
...(aotCache ? [`--cache-ro ${aotCache}`] : []),
126-
'--dir .',
127-
`--dir ${maybeWindowsPath(dirname(wizerInput))}`,
128-
'-w',
129-
`-i "${wasmEngine}"`,
130-
`-o "${output}"`,
131-
],
132-
{
133-
stdio: [null, process.stdout, process.stderr],
134-
input: `${maybeWindowsPath(wizerInput)}${enableExperimentalTopLevelAwait ? '' : ' --legacy-script'}`,
135-
shell: true,
136-
encoding: 'utf-8',
137-
env: {
138-
...process.env,
139-
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
140-
enableExperimentalHighResolutionTimeMethods ? '1' : '0',
141-
},
122+
try {
123+
if (!doBundle) {
124+
// assert(moduleMode);
125+
const spawnOpts = {
126+
stdio: [null, process.stdout, process.stderr],
127+
input: maybeWindowsPath(input),
128+
shell: true,
129+
encoding: 'utf-8',
130+
env: {
131+
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
132+
enableExperimentalHighResolutionTimeMethods ? '1' : '0',
142133
},
143-
);
144-
if (wevalProcess.status !== 0) {
145-
throw new Error(`Weval initialization failure`);
134+
};
135+
if (enableAOT) {
136+
const wevalBin = await weval();
137+
138+
let wevalProcess = spawnSync(
139+
`"${wevalBin}"`,
140+
[
141+
'weval',
142+
...(aotCache ? [`--cache-ro ${aotCache}`] : []),
143+
`--dir="${maybeWindowsPath(process.cwd())}"`,
144+
'-w',
145+
`-i "${wasmEngine}"`,
146+
`-o "${output}"`,
147+
],
148+
spawnOpts,
149+
);
150+
if (wevalProcess.status !== 0) {
151+
throw new Error(`Weval initialization failure`);
152+
}
153+
process.exitCode = wevalProcess.status;
154+
} else {
155+
let wizerProcess = spawnSync(
156+
`"${wizer}"`,
157+
[
158+
'--allow-wasi',
159+
`--wasm-bulk-memory=true`,
160+
`--dir="${maybeWindowsPath(process.cwd())}"`,
161+
'-r _start=wizer.resume',
162+
`-o="${output}"`,
163+
`"${wasmEngine}"`,
164+
],
165+
spawnOpts,
166+
);
167+
if (wizerProcess.status !== 0) {
168+
throw new Error(`Wizer initialization failure`);
169+
}
170+
process.exitCode = wizerProcess.status;
146171
}
147-
process.exitCode = wevalProcess.status;
148172
} else {
149-
let wizerProcess = spawnSync(
150-
`"${wizer}"`,
151-
[
152-
'--inherit-env=true',
153-
'--allow-wasi',
154-
'--dir=.',
155-
`--dir=${maybeWindowsPath(dirname(wizerInput))}`,
156-
`--wasm-bulk-memory=true`,
157-
'-r _start=wizer.resume',
158-
`-o="${output}"`,
159-
`"${wasmEngine}"`,
160-
],
161-
{
162-
stdio: [null, process.stdout, process.stderr],
163-
input: `${maybeWindowsPath(wizerInput)}${enableExperimentalTopLevelAwait ? '' : ' --legacy-script'}`,
164-
shell: true,
165-
encoding: 'utf-8',
166-
env: {
167-
...process.env,
168-
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
169-
enableExperimentalHighResolutionTimeMethods ? '1' : '0',
170-
},
173+
const spawnOpts = {
174+
stdio: [null, process.stdout, process.stderr],
175+
input: `${maybeWindowsPath(input)}${moduleMode ? '' : ' --legacy-script'}`,
176+
shell: true,
177+
encoding: 'utf-8',
178+
env: {
179+
...process.env,
180+
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
181+
enableExperimentalHighResolutionTimeMethods ? '1' : '0',
171182
},
172-
);
173-
if (wizerProcess.status !== 0) {
174-
throw new Error(`Wizer initialization failure`);
183+
};
184+
if (enableAOT) {
185+
const wevalBin = await weval();
186+
187+
let wevalProcess = spawnSync(
188+
`"${wevalBin}"`,
189+
[
190+
'weval',
191+
...(aotCache ? [`--cache-ro ${aotCache}`] : []),
192+
'--dir .',
193+
`--dir ${maybeWindowsPath(dirname(input))}`,
194+
'-w',
195+
`-i "${wasmEngine}"`,
196+
`-o "${output}"`,
197+
],
198+
spawnOpts,
199+
);
200+
if (wevalProcess.status !== 0) {
201+
throw new Error(`Weval initialization failure`);
202+
}
203+
process.exitCode = wevalProcess.status;
204+
} else {
205+
let wizerProcess = spawnSync(
206+
`"${wizer}"`,
207+
[
208+
'--inherit-env=true',
209+
'--allow-wasi',
210+
'--dir=.',
211+
`--dir=${maybeWindowsPath(dirname(input))}`,
212+
'-r _start=wizer.resume',
213+
`--wasm-bulk-memory=true`,
214+
`-o="${output}"`,
215+
`"${wasmEngine}"`,
216+
],
217+
spawnOpts,
218+
);
219+
if (wizerProcess.status !== 0) {
220+
throw new Error(`Wizer initialization failure`);
221+
}
222+
process.exitCode = wizerProcess.status;
175223
}
176-
process.exitCode = wizerProcess.status;
177224
}
178225
} catch (error) {
179226
console.error(
@@ -182,6 +229,8 @@ export async function compileApplicationToWasm(
182229
);
183230
process.exit(1);
184231
} finally {
185-
rmSync(tmpDir, { recursive: true });
232+
if (doBundle) {
233+
rmSync(tmpDir, { recursive: true });
234+
}
186235
}
187236
}

src/parseInputs.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ export async function parseInputs(cliInputs) {
77
const __dirname = dirname(fileURLToPath(import.meta.url));
88

99
let enableExperimentalHighResolutionTimeMethods = false;
10-
let enableExperimentalTopLevelAwait = false;
1110
let enableAOT = false;
1211
let customEngineSet = false;
12+
let moduleMode = false;
13+
let bundle = true;
1314
let wasmEngine = join(__dirname, '../fastly.wasm');
1415
let aotCache = join(__dirname, '../fastly-ics.wevalcache');
1516
let customInputSet = false;
@@ -28,8 +29,14 @@ export async function parseInputs(cliInputs) {
2829
enableExperimentalHighResolutionTimeMethods = true;
2930
break;
3031
}
32+
case '--module-mode': {
33+
moduleMode = true;
34+
bundle = false;
35+
break;
36+
}
3137
case '--enable-experimental-top-level-await': {
32-
enableExperimentalTopLevelAwait = true;
38+
moduleMode = true;
39+
bundle = true;
3340
break;
3441
}
3542
case '--enable-experimental-aot': {
@@ -128,7 +135,8 @@ export async function parseInputs(cliInputs) {
128135

129136
return {
130137
enableExperimentalHighResolutionTimeMethods,
131-
enableExperimentalTopLevelAwait,
138+
moduleMode,
139+
bundle,
132140
enableAOT,
133141
aotCache,
134142
input,

src/precompile.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,14 @@ const POSTAMBLE = '}';
1212
/// will intern regular expressions, duplicating them at the top level and testing them with both
1313
/// an ascii and utf8 string should ensure that they won't be re-compiled when run in the fetch
1414
/// handler.
15-
export function precompile(
16-
source,
17-
filename = '<input>',
18-
enableExperimentalTopLevelAwait = false,
19-
) {
15+
export function precompile(source, filename = '<input>', moduleMode = false) {
2016
const magicString = new MagicString(source, {
2117
filename,
2218
});
2319

2420
const ast = parse(source, {
2521
ecmaVersion: 'latest',
26-
sourceType: enableExperimentalTopLevelAwait ? 'module' : 'script',
22+
sourceType: moduleMode ? 'module' : 'script',
2723
});
2824

2925
const precompileCalls = [];

src/printHelp.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ FLAGS:
1414
1515
OPTIONS:
1616
--engine-wasm <engine-wasm> The JS engine Wasm file path
17+
--module-mode [experimental] Run all sources as native modules,
18+
with full error stack support.
19+
--bundle Runs the esbuild bundler (enabled by default, unless using module mode).
1720
--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method
18-
--enable-experimental-top-level-await Enable experimental top level await
1921
2022
ARGS:
2123
<input> The input JS script's file path [default: bin/index.js]

0 commit comments

Comments
 (0)