diff --git a/src/babel-plugins/strip-node-prefix.mjs b/src/babel-plugins/strip-node-prefix.mjs index 191fce47951ee..c078b0e0e051f 100644 --- a/src/babel-plugins/strip-node-prefix.mjs +++ b/src/babel-plugins/strip-node-prefix.mjs @@ -23,7 +23,7 @@ export default function ({ types: t, targets }) { }, // e.g. `await import('node:fs')` - // Note: only here for reference, it's mangled with EMSCRIPTEN$AWAIT$IMPORT below. + // Note: when using Closure, it's mangled with EMSCRIPTEN$AWAIT$IMPORT below. ImportExpression({ node }) { if (t.isStringLiteral(node.source) && node.source.value.startsWith('node:')) { node.source.value = node.source.value.slice(5); @@ -34,8 +34,7 @@ export default function ({ types: t, targets }) { CallExpression({ node }) { if ( (t.isIdentifier(node.callee, { name: 'require' }) || - // Special placeholder for `await import` - // FIXME: Remove after PR https://github.com/emscripten-core/emscripten/pull/23730 is landed. + // Special placeholder for `await import` when using Closure t.isIdentifier(node.callee, { name: 'EMSCRIPTEN$AWAIT$IMPORT' })) && t.isStringLiteral(node.arguments[0]) && node.arguments[0].value.startsWith('node:') diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index 774f46f847460..b0f465435ff99 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -11,8 +11,7 @@ * The closure_compiler() method in tools/shared.py refers to this file when calling closure. */ -// Special placeholder for `import.meta` and `await import`. -var EMSCRIPTEN$IMPORT$META; +// Special placeholder for `await import` and `await`. var EMSCRIPTEN$AWAIT$IMPORT; var EMSCRIPTEN$AWAIT; diff --git a/src/modules.mjs b/src/modules.mjs index 83fcf1d86d8b3..520d6cc7c74ea 100644 --- a/src/modules.mjs +++ b/src/modules.mjs @@ -329,6 +329,15 @@ export const LibraryManager = { preprocessedName = path.join(getTempDir(), path.basename(filename)); } + if (EXPORT_ES6) { + // `vm.runInContext` doesn't support module syntax; to allow it, + // we need to temporarily replace `import.meta` usages with + // placeholders during the JS compile phase, then in Python + // we reverse this replacement. + // See also: `emscript` in emscripten.py. + contents = contents.replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META'); + } + try { runInMacroContext(contents, {filename: preprocessedName}) } catch (e) { diff --git a/src/parseTools.mjs b/src/parseTools.mjs index 544d7bd808250..2d1626f17c836 100644 --- a/src/parseTools.mjs +++ b/src/parseTools.mjs @@ -73,18 +73,15 @@ function findIncludeFile(filename, currentDir) { // Also handles #include x.js (similar to C #include ) export function preprocess(filename) { let text = readFile(filename); - if (EXPORT_ES6) { - // `eval`, Terser and Closure don't support module syntax; to allow it, - // we need to temporarily replace `import.meta` and `await import` usages - // with placeholders during preprocess phase, and back after all the other ops. - // See also: `phase_final_emitting` in emcc.py. - text = text - .replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META') - .replace(/\bawait import\b/g, 'EMSCRIPTEN$AWAIT$IMPORT'); - } - if (MODULARIZE) { - // Same for out use of "top-level-await" which is not actually top level - // in the case of MODULARIZE. + if (MODULARIZE && USE_CLOSURE_COMPILER) { + // Closure doesn't support "top-level await" which is not actually the top + // level in case of MODULARIZE. Temporarily replace `await` usages with + // placeholders during preprocess phase, and back after all the other ops. + // See also: `fix_js_mangling` in emcc.py. + // FIXME: Remove after https://github.com/google/closure-compiler/issues/3835 is fixed. + if (EXPORT_ES6) { + text = text.replace(/\bawait import\b/g, 'EMSCRIPTEN$AWAIT$IMPORT'); + } text = text.replace(/\bawait createWasm\(\)/g, 'EMSCRIPTEN$AWAIT(createWasm())'); } // Remove windows line endings, if any diff --git a/src/shell.js b/src/shell.js index 41294749487a4..0eedcc91fe17c 100644 --- a/src/shell.js +++ b/src/shell.js @@ -112,8 +112,7 @@ if (ENVIRONMENT_IS_NODE) { // When building an ES module `require` is not normally available. // We need to use `createRequire()` to construct the require()` function. const { createRequire } = await import('node:module'); - /** @suppress{duplicate} */ - var require = createRequire(import.meta.url); + global.require = createRequire(import.meta.url); #endif #if PTHREADS || WASM_WORKERS @@ -130,7 +129,7 @@ if (ENVIRONMENT_IS_NODE) { #endif #endif // PTHREADS || WASM_WORKERS } -#endif // ENVIRONMENT_MAY_BE_NODE +#endif // ENVIRONMENT_MAY_BE_NODE && (EXPORT_ES6 || PTHREADS || WASM_WORKERS) // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) diff --git a/test/codesize/test_codesize_minimal_esm.json b/test/codesize/test_codesize_minimal_esm.json index 742d414e173f6..ae07df4480880 100644 --- a/test/codesize/test_codesize_minimal_esm.json +++ b/test/codesize/test_codesize_minimal_esm.json @@ -1,10 +1,10 @@ { - "a.out.js": 2448, - "a.out.js.gz": 1175, + "a.out.js": 2445, + "a.out.js.gz": 1172, "a.out.nodebug.wasm": 75, "a.out.nodebug.wasm.gz": 87, - "total": 2523, - "total_gz": 1262, + "total": 2520, + "total_gz": 1259, "sent": [], "imports": [], "exports": [ diff --git a/test/test_other.py b/test/test_other.py index 81d892921162c..756b83d8a9058 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13423,6 +13423,11 @@ def test_node_prefix_transpile(self): content = read_file('a.out.js') self.assertNotContained('node:', content) + # Test that the leading `node:` prefix is removed from imports when `--closure=1` + self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6', '-sMIN_NODE_VERSION=150000', '-Wno-transpile', '--closure=1']) + content = read_file('a.out.js') + self.assertNotContained('node:', content) + def test_gmtime_noleak(self): # Confirm that gmtime_r does not leak when called in isolation. self.do_other_test('test_gmtime_noleak.c', cflags=['-fsanitize=leak']) diff --git a/third_party/terser/README.md b/third_party/terser/README.md index bae2adf9c6c60..7945ad521ba0d 100644 --- a/third_party/terser/README.md +++ b/third_party/terser/README.md @@ -7,7 +7,7 @@ version published in npm. This `terser.js` bundle in this directory was built from our fork of terser which lives at: https://github.com/emscripten-core/terser/ -The current patches are stored in the `emscripten_patches_v4.8.0` branch. +The current patches are stored in the `emscripten_patches_v5.18.2` branch. To make changes to this code please submit patches to https://github.com/emscripten-core/terser/ and then re-create this bundle diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 5fd4c6c098637..311c087cc99e9 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -1718,6 +1718,7 @@ const params = { ecmaVersion: 'latest', sourceType: exportES6 ? 'module' : 'script', allowAwaitOutsideFunction: true, + allowImportExportEverywhere: exportES6, }; if (closureFriendly) { const currentComments = []; diff --git a/tools/building.py b/tools/building.py index bc09df6d26012..31b07d9444e57 100644 --- a/tools/building.py +++ b/tools/building.py @@ -541,11 +541,21 @@ def version_split(v): @ToolchainProfiler.profile() def transpile(filename): config = { - 'sourceType': 'script', - 'presets': ['@babel/preset-env'], + 'sourceType': 'module' if settings.EXPORT_ES6 else 'script', + 'presets': [ + ['@babel/preset-env', { + # Avoid transforming `import()` expressions to non-ESM module formats. + 'exclude': ['@babel/plugin-transform-dynamic-import'], + }], + ], 'plugins': [], 'targets': {}, 'parserOpts': { + # Allow Babel to parse "top-level await" which is not actually the top + # level in case of MODULARIZE. + 'allowAwaitOutsideFunction': True, + # Allow Babel to parse "import.meta". + 'allowImportExportEverywhere': settings.EXPORT_ES6, # FIXME: Remove when updating to Babel 8, see: # https://babeljs.io/docs/v8-migration-api#javascript-nodes 'createImportExpressions': True, @@ -637,6 +647,14 @@ def closure_compiler(filename, advanced=True, extra_closure_args=None): args = ['--compilation_level', 'ADVANCED_OPTIMIZATIONS' if advanced else 'SIMPLE_OPTIMIZATIONS'] args += ['--language_in', 'UNSTABLE'] + # Make Closure aware of the ES6 module syntax; + # i.e. the `import.meta` and `await import` usages + if settings.EXPORT_ES6: + args += ['--chunk_output_type', 'ES_MODULES'] + if settings.ENVIRONMENT_MAY_BE_NODE: + args += ['--module_resolution', 'NODE'] + # https://github.com/google/closure-compiler/issues/3740 + args += ['--jscomp_off=moduleLoad'] # We do transpilation using babel args += ['--language_out', 'NO_TRANSPILE'] # Tell closure never to inject the 'use strict' directive. diff --git a/tools/emscripten.py b/tools/emscripten.py index d9e7384a0c934..bc7c32a15bb6c 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -427,6 +427,11 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat assert '//FORWARDED_DATA:' in out, 'Did not receive forwarded data in pre output - process failed?' glue, forwarded_data = out.split('//FORWARDED_DATA:', 1) + # Unmangle previously mangled `import.meta` references in lib*.js. + # See also: `LibraryManager.load` in modules.mjs. + if settings.EXPORT_ES6: + glue = glue.replace('EMSCRIPTEN$IMPORT$META', 'import.meta') + forwarded_json = json.loads(forwarded_data) if forwarded_json['warnings']: diff --git a/tools/link.py b/tools/link.py index aaffe9eae7066..e0bb33c406c97 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2083,19 +2083,28 @@ def phase_source_transforms(options): save_intermediate('transformed') -# Unmangle previously mangled `import.meta` and `await import` references in +# Unmangle previously mangled `await import` and `await` references in # both main code and libraries. # See also: `preprocess` in parseTools.js. def fix_js_mangling(js_file): - # We don't apply these mangliings except in MODULARIZE/EXPORT_ES6 modes. - if not settings.MODULARIZE: + # Mangling only takes place under closure in MODULARIZE mode. + if not settings.MODULARIZE or not settings.USE_CLOSURE_COMPILER: return src = read_file(js_file) - write_file(js_file, src - .replace('EMSCRIPTEN$IMPORT$META', 'import.meta') - .replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import') - .replace('EMSCRIPTEN$AWAIT(', 'await (')) + + if settings.EXPORT_ES6: + # Also remove the line containing `export{};`, which is inserted by + # Closure to mark the file as an ES6 module. + # https://github.com/google/closure-compiler/issues/4084#issuecomment-1505056519 + # https://github.com/google/closure-compiler/blob/v20260401/src/com/google/javascript/jscomp/ConvertChunksToESModules.java#L111-L113 + src = src \ + .replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import') \ + .replace('export{};\n', '') + + src = src.replace('EMSCRIPTEN$AWAIT(', 'await (') + + write_file(js_file, src) save_intermediate('js-mangling')