Skip to content

Commit 437a20d

Browse files
authored
feat: add new flag --experimental-enable-top-level-await (#742)
1 parent 8959e79 commit 437a20d

8 files changed

+85
-8
lines changed

integration-tests/cli/help.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ test('--help should return help on stdout and zero exit code', async function (t
3131
'OPTIONS:',
3232
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
3333
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
34+
'--enable-experimental-top-level-await Enable experimental top level await',
3435
'ARGS:',
3536
"<input> The input JS script's file path [default: bin/index.js]",
3637
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'
@@ -56,6 +57,7 @@ test('-h should return help on stdout and zero exit code', async function (t) {
5657
'OPTIONS:',
5758
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
5859
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
60+
'--enable-experimental-top-level-await Enable experimental top level await',
5961
'ARGS:',
6062
"<input> The input JS script's file path [default: bin/index.js]",
6163
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'

js-compute-runtime-cli.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { addSdkMetadataField } from "./src/addSdkMetadataField.js";
88
const {
99
enablePBL,
1010
enableExperimentalHighResolutionTimeMethods,
11+
enableExperimentalTopLevelAwait,
1112
wasmEngine,
1213
input,
1314
component,
@@ -29,7 +30,7 @@ if (version) {
2930
// it could be that the user is using an older version of js-compute-runtime
3031
// and a newer version does not support the platform they are using.
3132
const {compileApplicationToWasm} = await import('./src/compileApplicationToWasm.js')
32-
await compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods, enablePBL);
33+
await compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods, enablePBL, enableExperimentalTopLevelAwait);
3334
if (component) {
3435
const {compileComponent} = await import('./src/component.js');
3536
await compileComponent(output, adapter);

src/bundle.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry;
6262
},
6363
}
6464

65-
export async function bundle(input) {
65+
export async function bundle(input, enableExperimentalTopLevelAwait = false) {
6666
return await build({
6767
conditions: ['fastly'],
6868
entryPoints: [input],
6969
bundle: true,
7070
write: false,
71+
format: enableExperimentalTopLevelAwait ? 'esm' : 'iife',
7172
tsconfig: undefined,
7273
plugins: [fastlyPlugin],
7374
})

src/compileApplicationToWasm.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { isFile } from "./isFile.js";
55
import { isFileOrDoesNotExist } from "./isFileOrDoesNotExist.js";
66
import wizer from "@bytecodealliance/wizer";
77
import { precompile } from "./precompile.js";
8+
import { enableTopLevelAwait } from "./enableTopLevelAwait.js";
89
import { bundle } from "./bundle.js";
910
import { containsSyntaxErrors } from "./containsSyntaxErrors.js";
1011

11-
export async function compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods = false, enablePBL = false) {
12+
export async function compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods = false, enablePBL = false, enableExperimentalTopLevelAwait = false) {
1213
try {
1314
if (!(await isFile(input))) {
1415
console.error(
@@ -77,9 +78,12 @@ export async function compileApplicationToWasm(input, output, wasmEngine, enable
7778
process.exit(1);
7879
}
7980

80-
let contents = await bundle(input);
81+
let contents = await bundle(input, enableExperimentalTopLevelAwait);
8182

82-
let application = precompile(contents.outputFiles[0].text);
83+
let application = precompile(contents.outputFiles[0].text, undefined, enableExperimentalTopLevelAwait);
84+
if (enableExperimentalTopLevelAwait) {
85+
application = enableTopLevelAwait(application);
86+
}
8387

8488
try {
8589
let wizerProcess = spawnSync(

src/enableTopLevelAwait.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { parse } from "acorn";
2+
import MagicString from "magic-string";
3+
import { simple as simpleWalk } from "acorn-walk";
4+
5+
/// Emit a block of javascript that enables top level awaits
6+
/// * removes any top-level exports
7+
/// * wraps source with async function and executes it
8+
export function enableTopLevelAwait(source, filename = "<input>") {
9+
const magicString = new MagicString(source, {
10+
filename,
11+
});
12+
13+
const ast = parse(source, {
14+
ecmaVersion: "latest",
15+
sourceType: "module",
16+
});
17+
18+
function replaceExportWithDeclaration(node) {
19+
let body = '';
20+
if (node.declaration != null) {
21+
// The following may have declarations:
22+
// ExportNamedDeclaration, e.g.:
23+
// export var i = 0;
24+
// export const y = foo();
25+
// ExportDefaultDeclaration, e.g.:
26+
// export default 1;
27+
// export default foo();
28+
body = magicString
29+
.snip(node.declaration.start, node.declaration.end)
30+
.toString();
31+
}
32+
magicString.overwrite(node.start, node.end, body);
33+
}
34+
35+
simpleWalk(ast, {
36+
ExportNamedDeclaration(node) {
37+
replaceExportWithDeclaration(node);
38+
},
39+
ExportSpecifier(node) {
40+
replaceExportWithDeclaration(node);
41+
},
42+
ExportDefaultDeclaration(node) {
43+
replaceExportWithDeclaration(node);
44+
},
45+
ExportAllDeclaration(node) {
46+
replaceExportWithDeclaration(node);
47+
},
48+
});
49+
50+
magicString.prepend(';((async()=>{');
51+
magicString.append('})())');
52+
53+
// When we're ready to pipe in source maps:
54+
// const map = magicString.generateMap({
55+
// source: 'source.js',
56+
// file: 'converted.js.map',
57+
// includeContent: true
58+
// });
59+
60+
return magicString.toString();
61+
62+
}
63+

src/parseInputs.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export async function parseInputs(cliInputs) {
99
let component = false;
1010
let adapter;
1111
let enableExperimentalHighResolutionTimeMethods = false;
12+
let enableExperimentalTopLevelAwait = false;
1213
let enablePBL = false;
1314
let customEngineSet = false;
1415
let wasmEngine = join(__dirname, "../js-compute-runtime.wasm");
@@ -33,6 +34,10 @@ export async function parseInputs(cliInputs) {
3334
enableExperimentalHighResolutionTimeMethods = true;
3435
break;
3536
}
37+
case "--enable-experimental-top-level-await": {
38+
enableExperimentalTopLevelAwait = true;
39+
break;
40+
}
3641
case "--enable-pbl": {
3742
enablePBL = true;
3843
break;
@@ -109,5 +114,5 @@ export async function parseInputs(cliInputs) {
109114
}
110115
}
111116
}
112-
return { wasmEngine, component, adapter, input, output, enableExperimentalHighResolutionTimeMethods, enablePBL };
117+
return { wasmEngine, component, adapter, input, output, enableExperimentalHighResolutionTimeMethods, enablePBL, enableExperimentalTopLevelAwait };
113118
}

src/precompile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +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(source, filename = "<input>") {
15+
export function precompile(source, filename = "<input>", enableExperimentalTopLevelAwait = false) {
1616
const magicString = new MagicString(source, {
1717
filename,
1818
});
1919

2020
const ast = parse(source, {
2121
ecmaVersion: "latest",
22-
sourceType: "script",
22+
sourceType: enableExperimentalTopLevelAwait ? "module" : "script",
2323
});
2424

2525
const precompileCalls = [];

src/printHelp.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ FLAGS:
1313
OPTIONS:
1414
--engine-wasm <engine-wasm> The JS engine Wasm file path
1515
--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method
16+
--enable-experimental-top-level-await Enable experimental top level await
1617
1718
ARGS:
1819
<input> The input JS script's file path [default: bin/index.js]

0 commit comments

Comments
 (0)