Skip to content

Commit

Permalink
feat: add new flag --experimental-enable-top-level-await (#742)
Browse files Browse the repository at this point in the history
  • Loading branch information
harmony7 committed Mar 14, 2024
1 parent 8959e79 commit 437a20d
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 8 deletions.
2 changes: 2 additions & 0 deletions integration-tests/cli/help.test.js
Expand Up @@ -31,6 +31,7 @@ test('--help should return help on stdout and zero exit code', async function (t
'OPTIONS:',
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
'--enable-experimental-top-level-await Enable experimental top level await',
'ARGS:',
"<input> The input JS script's file path [default: bin/index.js]",
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'
Expand All @@ -56,6 +57,7 @@ test('-h should return help on stdout and zero exit code', async function (t) {
'OPTIONS:',
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
'--enable-experimental-top-level-await Enable experimental top level await',
'ARGS:',
"<input> The input JS script's file path [default: bin/index.js]",
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'
Expand Down
3 changes: 2 additions & 1 deletion js-compute-runtime-cli.js
Expand Up @@ -8,6 +8,7 @@ import { addSdkMetadataField } from "./src/addSdkMetadataField.js";
const {
enablePBL,
enableExperimentalHighResolutionTimeMethods,
enableExperimentalTopLevelAwait,
wasmEngine,
input,
component,
Expand All @@ -29,7 +30,7 @@ if (version) {
// it could be that the user is using an older version of js-compute-runtime
// and a newer version does not support the platform they are using.
const {compileApplicationToWasm} = await import('./src/compileApplicationToWasm.js')
await compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods, enablePBL);
await compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods, enablePBL, enableExperimentalTopLevelAwait);
if (component) {
const {compileComponent} = await import('./src/component.js');
await compileComponent(output, adapter);
Expand Down
3 changes: 2 additions & 1 deletion src/bundle.js
Expand Up @@ -62,12 +62,13 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry;
},
}

export async function bundle(input) {
export async function bundle(input, enableExperimentalTopLevelAwait = false) {
return await build({
conditions: ['fastly'],
entryPoints: [input],
bundle: true,
write: false,
format: enableExperimentalTopLevelAwait ? 'esm' : 'iife',
tsconfig: undefined,
plugins: [fastlyPlugin],
})
Expand Down
10 changes: 7 additions & 3 deletions src/compileApplicationToWasm.js
Expand Up @@ -5,10 +5,11 @@ import { isFile } from "./isFile.js";
import { isFileOrDoesNotExist } from "./isFileOrDoesNotExist.js";
import wizer from "@bytecodealliance/wizer";
import { precompile } from "./precompile.js";
import { enableTopLevelAwait } from "./enableTopLevelAwait.js";
import { bundle } from "./bundle.js";
import { containsSyntaxErrors } from "./containsSyntaxErrors.js";

export async function compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods = false, enablePBL = false) {
export async function compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods = false, enablePBL = false, enableExperimentalTopLevelAwait = false) {
try {
if (!(await isFile(input))) {
console.error(
Expand Down Expand Up @@ -77,9 +78,12 @@ export async function compileApplicationToWasm(input, output, wasmEngine, enable
process.exit(1);
}

let contents = await bundle(input);
let contents = await bundle(input, enableExperimentalTopLevelAwait);

let application = precompile(contents.outputFiles[0].text);
let application = precompile(contents.outputFiles[0].text, undefined, enableExperimentalTopLevelAwait);
if (enableExperimentalTopLevelAwait) {
application = enableTopLevelAwait(application);
}

try {
let wizerProcess = spawnSync(
Expand Down
63 changes: 63 additions & 0 deletions src/enableTopLevelAwait.js
@@ -0,0 +1,63 @@
import { parse } from "acorn";
import MagicString from "magic-string";
import { simple as simpleWalk } from "acorn-walk";

/// Emit a block of javascript that enables top level awaits
/// * removes any top-level exports
/// * wraps source with async function and executes it
export function enableTopLevelAwait(source, filename = "<input>") {
const magicString = new MagicString(source, {
filename,
});

const ast = parse(source, {
ecmaVersion: "latest",
sourceType: "module",
});

function replaceExportWithDeclaration(node) {
let body = '';
if (node.declaration != null) {
// The following may have declarations:
// ExportNamedDeclaration, e.g.:
// export var i = 0;
// export const y = foo();
// ExportDefaultDeclaration, e.g.:
// export default 1;
// export default foo();
body = magicString
.snip(node.declaration.start, node.declaration.end)
.toString();
}
magicString.overwrite(node.start, node.end, body);
}

simpleWalk(ast, {
ExportNamedDeclaration(node) {
replaceExportWithDeclaration(node);
},
ExportSpecifier(node) {
replaceExportWithDeclaration(node);
},
ExportDefaultDeclaration(node) {
replaceExportWithDeclaration(node);
},
ExportAllDeclaration(node) {
replaceExportWithDeclaration(node);
},
});

magicString.prepend(';((async()=>{');
magicString.append('})())');

// When we're ready to pipe in source maps:
// const map = magicString.generateMap({
// source: 'source.js',
// file: 'converted.js.map',
// includeContent: true
// });

return magicString.toString();

}

7 changes: 6 additions & 1 deletion src/parseInputs.js
Expand Up @@ -9,6 +9,7 @@ export async function parseInputs(cliInputs) {
let component = false;
let adapter;
let enableExperimentalHighResolutionTimeMethods = false;
let enableExperimentalTopLevelAwait = false;
let enablePBL = false;
let customEngineSet = false;
let wasmEngine = join(__dirname, "../js-compute-runtime.wasm");
Expand All @@ -33,6 +34,10 @@ export async function parseInputs(cliInputs) {
enableExperimentalHighResolutionTimeMethods = true;
break;
}
case "--enable-experimental-top-level-await": {
enableExperimentalTopLevelAwait = true;
break;
}
case "--enable-pbl": {
enablePBL = true;
break;
Expand Down Expand Up @@ -109,5 +114,5 @@ export async function parseInputs(cliInputs) {
}
}
}
return { wasmEngine, component, adapter, input, output, enableExperimentalHighResolutionTimeMethods, enablePBL };
return { wasmEngine, component, adapter, input, output, enableExperimentalHighResolutionTimeMethods, enablePBL, enableExperimentalTopLevelAwait };
}
4 changes: 2 additions & 2 deletions src/precompile.js
Expand Up @@ -12,14 +12,14 @@ const POSTAMBLE = "}";
/// will intern regular expressions, duplicating them at the top level and testing them with both
/// an ascii and utf8 string should ensure that they won't be re-compiled when run in the fetch
/// handler.
export function precompile(source, filename = "<input>") {
export function precompile(source, filename = "<input>", enableExperimentalTopLevelAwait = false) {
const magicString = new MagicString(source, {
filename,
});

const ast = parse(source, {
ecmaVersion: "latest",
sourceType: "script",
sourceType: enableExperimentalTopLevelAwait ? "module" : "script",
});

const precompileCalls = [];
Expand Down
1 change: 1 addition & 0 deletions src/printHelp.js
Expand Up @@ -13,6 +13,7 @@ FLAGS:
OPTIONS:
--engine-wasm <engine-wasm> The JS engine Wasm file path
--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method
--enable-experimental-top-level-await Enable experimental top level await
ARGS:
<input> The input JS script's file path [default: bin/index.js]
Expand Down

0 comments on commit 437a20d

Please sign in to comment.