Skip to content

Support ES6 module syntax in Acorn and Closure #23730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions src/closure-externs/closure-externs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +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;
var EMSCRIPTEN$AWAIT$IMPORT;

// Don't minify createRequire
// Don't minify createRequire (i.e. when doing -sEXPORT_ES6 + -sENVIRONMENT=node)
var createRequire;

// Don't minify startWorker which we use to start workers once the runtime is ready.
Expand Down Expand Up @@ -160,9 +156,10 @@ var wakaUnknownBefore;
// Module loaders externs, for AMD etc.

/**
* @param {Object} deps
* @param {Function} wrapper
*/
var define = function (wrapper) {};
var define = function (deps, wrapper) {};

/**
* @type {Worker}
Expand Down Expand Up @@ -231,20 +228,6 @@ var sampleRate;
*/
var id;

/**
* Used in MODULARIZE mode as the name of the incoming module argument.
* This is generated outside of the code we pass to closure so from closure's
* POV this is "extern".
*/
var moduleArg;

/**
* Used in MODULARIZE mode.
* We need to access this after the code we pass to closure so from closure's
* POV this is "extern".
*/
var moduleRtn;

/**
* This was removed from upstream closure compiler in
* https://github.com/google/closure-compiler/commit/f83322c1b.
Expand Down
7 changes: 0 additions & 7 deletions src/closure-externs/modularize-externs.js

This file was deleted.

30 changes: 13 additions & 17 deletions src/lib/libhtml5.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ var LibraryHTML5 = {
// eventStruct: the structure to populate.
// e: The JS mouse event to read data from.
// target: Specifies a target DOM element that will be used as the reference to populate targetX and targetY parameters.
$fillMouseEventData__deps: ['$JSEvents', '$getBoundingClientRect', '$specialHTMLTargets'],
$fillMouseEventData__deps: ['$getBoundingClientRect', '$specialHTMLTargets'],
$fillMouseEventData: (eventStruct, e, target) => {
#if ASSERTIONS
assert(eventStruct % 4 == 0);
Expand Down Expand Up @@ -608,7 +608,7 @@ var LibraryHTML5 = {
},

emscripten_set_wheel_callback_on_thread__proxy: 'sync',
emscripten_set_wheel_callback_on_thread__deps: ['$JSEvents', '$registerWheelEventCallback', '$findEventTarget'],
emscripten_set_wheel_callback_on_thread__deps: ['$registerWheelEventCallback', '$findEventTarget'],
emscripten_set_wheel_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => {
target = findEventTarget(target);
if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}};
Expand Down Expand Up @@ -747,7 +747,6 @@ var LibraryHTML5 = {
emscripten_set_focusout_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) =>
registerFocusEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FOCUSOUT }}}, "focusout", targetThread),

$fillDeviceOrientationEventData__deps: ['$JSEvents'],
$fillDeviceOrientationEventData: (eventStruct, e, target) => {
{{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.alpha, 'e.alpha', 'double') }}};
{{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.beta, 'e.beta', 'double') }}};
Expand Down Expand Up @@ -802,7 +801,6 @@ var LibraryHTML5 = {
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}};
},

$fillDeviceMotionEventData__deps: ['$JSEvents'],
$fillDeviceMotionEventData: (eventStruct, e, target) => {
var supportedFields = 0;
var a = e['acceleration'];
Expand Down Expand Up @@ -1178,7 +1176,7 @@ var LibraryHTML5 = {
return restoreOldStyle;
},

$registerRestoreOldStyle__deps: ['$JSEvents', '$getCanvasElementSize', '$setCanvasElementSize'],
$registerRestoreOldStyle__deps: ['$getCanvasElementSize', '$setCanvasElementSize'],
$registerRestoreOldStyle: (canvas) => {
var canvasSize = getCanvasElementSize(canvas);
var oldWidth = canvasSize[0];
Expand Down Expand Up @@ -1297,7 +1295,6 @@ var LibraryHTML5 = {
},

// Add letterboxes to a fullscreen element in a cross-browser way.
$setLetterbox__deps: ['$JSEvents'],
$setLetterbox: (element, topBottom, leftRight) => {
// Cannot use margin to specify letterboxes in FF or Chrome, since those ignore margins in fullscreen mode.
element.style.paddingLeft = element.style.paddingRight = leftRight + 'px';
Expand All @@ -1307,7 +1304,7 @@ var LibraryHTML5 = {
$currentFullscreenStrategy: {},
$restoreOldWindowedStyle: null,

$softFullscreenResizeWebGLRenderTarget__deps: ['$JSEvents', '$setLetterbox', '$currentFullscreenStrategy', '$getCanvasElementSize', '$setCanvasElementSize', '$jstoi_q'],
$softFullscreenResizeWebGLRenderTarget__deps: ['$setLetterbox', '$currentFullscreenStrategy', '$getCanvasElementSize', '$setCanvasElementSize', '$jstoi_q'],
$softFullscreenResizeWebGLRenderTarget: () => {
var dpr = devicePixelRatio;
var inHiDPIFullscreenMode = currentFullscreenStrategy.canvasResolutionScaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF }}};
Expand Down Expand Up @@ -1582,7 +1579,7 @@ var LibraryHTML5 = {
},

emscripten_set_pointerlockchange_callback_on_thread__proxy: 'sync',
emscripten_set_pointerlockchange_callback_on_thread__deps: ['$JSEvents', '$registerPointerlockChangeEventCallback', '$findEventTarget', '$specialHTMLTargets'],
emscripten_set_pointerlockchange_callback_on_thread__deps: ['$registerPointerlockChangeEventCallback', '$findEventTarget', '$specialHTMLTargets'],
emscripten_set_pointerlockchange_callback_on_thread__docs: '/** @suppress {missingProperties} */', // Closure does not see document.body.mozRequestPointerLock etc.
emscripten_set_pointerlockchange_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => {
// TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined)
Expand Down Expand Up @@ -1627,7 +1624,7 @@ var LibraryHTML5 = {
},

emscripten_set_pointerlockerror_callback_on_thread__proxy: 'sync',
emscripten_set_pointerlockerror_callback_on_thread__deps: ['$JSEvents', '$registerPointerlockErrorEventCallback', '$findEventTarget', '$specialHTMLTargets'],
emscripten_set_pointerlockerror_callback_on_thread__deps: ['$registerPointerlockErrorEventCallback', '$findEventTarget', '$specialHTMLTargets'],
emscripten_set_pointerlockerror_callback_on_thread__docs: '/** @suppress {missingProperties} */', // Closure does not see document.body.mozRequestPointerLock etc.
emscripten_set_pointerlockerror_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => {
// TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined)
Expand Down Expand Up @@ -2162,7 +2159,6 @@ var LibraryHTML5 = {

#if PTHREADS
$setCanvasElementSizeCallingThread__deps: [
'$JSEvents',
#if OFFSCREENCANVAS_SUPPORT
'$setOffscreenCanvasSizeOnTargetThread',
#endif
Expand Down Expand Up @@ -2240,7 +2236,7 @@ var LibraryHTML5 = {
$setCanvasElementSizeMainThread__deps: ['$setCanvasElementSizeCallingThread'],
$setCanvasElementSizeMainThread: (target, width, height) => setCanvasElementSizeCallingThread(target, width, height),

emscripten_set_canvas_element_size__deps: ['$JSEvents', '$setCanvasElementSizeCallingThread', '$setCanvasElementSizeMainThread', '$findCanvasEventTarget'],
emscripten_set_canvas_element_size__deps: ['$setCanvasElementSizeCallingThread', '$setCanvasElementSizeMainThread', '$findCanvasEventTarget'],
emscripten_set_canvas_element_size: (target, width, height) => {
#if GL_DEBUG
dbg(`emscripten_set_canvas_element_size(target=${target},width=${width},height=${height}`);
Expand All @@ -2252,7 +2248,7 @@ var LibraryHTML5 = {
return setCanvasElementSizeMainThread(target, width, height);
},
#else
emscripten_set_canvas_element_size__deps: ['$JSEvents', '$findCanvasEventTarget'],
emscripten_set_canvas_element_size__deps: ['$findCanvasEventTarget'],
emscripten_set_canvas_element_size: (target, width, height) => {
#if GL_DEBUG
dbg(`emscripten_set_canvas_element_size(target=${target},width=${width},height=${height}`);
Expand Down Expand Up @@ -2287,7 +2283,7 @@ var LibraryHTML5 = {
},

#if PTHREADS
$getCanvasSizeCallingThread__deps: ['$JSEvents', '$findCanvasEventTarget'],
$getCanvasSizeCallingThread__deps: ['$findCanvasEventTarget'],
$getCanvasSizeCallingThread: (target, width, height) => {
var canvas = findCanvasEventTarget(target);
if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}};
Expand Down Expand Up @@ -2322,7 +2318,7 @@ var LibraryHTML5 = {
$getCanvasSizeMainThread__deps: ['$getCanvasSizeCallingThread'],
$getCanvasSizeMainThread: (target, width, height) => getCanvasSizeCallingThread(target, width, height),

emscripten_get_canvas_element_size__deps: ['$JSEvents', '$getCanvasSizeCallingThread', '$getCanvasSizeMainThread', '$findCanvasEventTarget'],
emscripten_get_canvas_element_size__deps: ['$getCanvasSizeCallingThread', '$getCanvasSizeMainThread', '$findCanvasEventTarget'],
emscripten_get_canvas_element_size: (target, width, height) => {
var canvas = findCanvasEventTarget(target);
if (canvas) {
Expand All @@ -2331,7 +2327,7 @@ var LibraryHTML5 = {
return getCanvasSizeMainThread(target, width, height);
},
#else
emscripten_get_canvas_element_size__deps: ['$JSEvents', '$findCanvasEventTarget'],
emscripten_get_canvas_element_size__deps: ['$findCanvasEventTarget'],
emscripten_get_canvas_element_size: (target, width, height) => {
var canvas = findCanvasEventTarget(target);
if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}};
Expand All @@ -2355,7 +2351,7 @@ var LibraryHTML5 = {
},

emscripten_set_element_css_size__proxy: 'sync',
emscripten_set_element_css_size__deps: ['$JSEvents', '$findEventTarget'],
emscripten_set_element_css_size__deps: ['$findEventTarget'],
emscripten_set_element_css_size: (target, width, height) => {
#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR
target = findEventTarget(target);
Expand All @@ -2371,7 +2367,7 @@ var LibraryHTML5 = {
},

emscripten_get_element_css_size__proxy: 'sync',
emscripten_get_element_css_size__deps: ['$JSEvents', '$findEventTarget', '$getBoundingClientRect'],
emscripten_get_element_css_size__deps: ['$findEventTarget', '$getBoundingClientRect'],
emscripten_get_element_css_size: (target, width, height) => {
#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR
target = findEventTarget(target);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/libhtml5_webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var LibraryHtml5WebGL = {
#if PTHREADS && OFFSCREEN_FRAMEBUFFER
'emscripten_webgl_create_context_proxied',
#endif
'$JSEvents', '$webglPowerPreferences', '$findEventTarget', '$findCanvasEventTarget'],
'$webglPowerPreferences', '$findEventTarget', '$findCanvasEventTarget'],
// This function performs proxying manually, depending on the style of context that is to be created.
emscripten_webgl_do_create_context: (target, attributes) => {
#if ASSERTIONS
Expand Down
8 changes: 8 additions & 0 deletions src/modules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ export const LibraryManager = {
pushCurrentFile(filename);
try {
processed = processMacros(preprocess(filename), 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: `compile_javascript` in emscripten.py.
processed = processed.replace(/\bimport\.meta\b/g, 'EMSCRIPTEN$IMPORT$META');
}
runInMacroContext(processed, {filename: filename.replace(/\.\w+$/, '.preprocessed$&')});
} catch (e) {
error(`failure to execute js library "${filename}":`);
Expand Down
36 changes: 27 additions & 9 deletions src/parseTools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,6 @@ function findIncludeFile(filename, currentDir) {
// Also handles #include x.js (similar to C #include <file>)
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');
}
// Remove windows line endings, if any
text = text.replace(/\r\n/g, '\n');

Expand Down Expand Up @@ -1104,6 +1095,31 @@ function ENVIRONMENT_IS_WORKER_THREAD() {
return '(' + envs.join('||') + ')';
}

function nodePthreadDetection() {
// Under node we detect that we are running in a pthread by checking the
// workerData property.
if (EXPORT_ES6) {
return "(await import('worker_threads'))['workerData'] === 'em-pthread'";
} else {
return "require('worker_threads')['workerData'] === 'em-pthread'";
}
}

function declareInstanceExports() {
const allExports = Array.from(EXPORTED_FUNCTIONS.keys()).concat(
Array.from(EXPORTED_RUNTIME_METHODS.keys()),
);
const mangledExports = allExports.map((e) => `__exp_${e}`);
const mangledExportsAs = allExports.map((e) => `__exp_${e} as ${e}`);
// Declare a top level var for each export so that code in the init function
// can assign to it and update the live module bindings.
if (allExports.length == 0) return '';
let rtn = 'var ' + mangledExports.join(', ') + ';\n';
// Export the functions with their original name.
rtn += 'export {' + mangledExportsAs.join(', ') + '};\n';
return rtn;
}

addToCompileTimeContext({
ATEXITS,
ATPRERUNS,
Expand Down Expand Up @@ -1176,4 +1192,6 @@ addToCompileTimeContext({
storeException,
to64,
toIndexType,
nodePthreadDetection,
declareInstanceExports,
});
73 changes: 61 additions & 12 deletions src/postamble_modularize.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@

#if WASM_ASYNC_COMPILATION

#if USE_READY_PROMISE
moduleRtn = readyPromise;
#else
moduleRtn = {};
#endif

#else // WASM_ASYNC_COMPILATION

moduleRtn = Module;

#endif // WASM_ASYNC_COMPILATION

#if ASSERTIONS
// Assertion for attempting to access module properties on the incoming
// moduleArg. In the past we used this object as the prototype of the module
Expand All @@ -35,3 +23,64 @@ for (const prop of Object.keys(Module)) {
}
}
#endif

#if USE_READY_PROMISE
return readyPromise;
#else
return {};
#endif
#else // WASM_ASYNC_COMPILATION
return Module;
#endif // WASM_ASYNC_COMPILATION
}; // End factory function

#if ASSERTIONS && MODULARIZE != 'instance'
(() => {
// Create a small, never-async wrapper around {{{ EXPORT_NAME }}} which
// checks for callers incorrectly using it with `new`.
var real_{{{ EXPORT_NAME }}} = {{{ EXPORT_NAME }}};
{{{ EXPORT_NAME }}} = function(arg) {
if (new.target) throw new Error("{{{ EXPORT_NAME }}}() should not be called with `new {{{ EXPORT_NAME }}}()`");
return real_{{{ EXPORT_NAME }}}(arg);
}
})();
#endif

#if EXPORT_ES6
#if MODULARIZE == 'instance'
// FIXME: This doesn't work with Closure.
{{{ declareInstanceExports() }}}
#endif
#else
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = {{{ EXPORT_NAME }}};
// This default export looks redundant, but it allows TS to import this
// commonjs style module.
module.exports.default = {{{ EXPORT_NAME }}};
} else if (typeof define === 'function' && define['amd']) {
define([], () => {{{ EXPORT_NAME }}});
}
#endif


#if PTHREADS

// Create code for detecting if we are running in a pthread.
// Normally this detection is done when the module is itself run but
// when running in MODULARIZE mode we need use this to know if we should
// run the module constructor on startup (true only for pthreads).
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
var isPthread = globalThis.self?.name?.startsWith('em-pthread');
#if ENVIRONMENT_MAY_BE_NODE
// In order to support both web and node we also need to detect node here.
var isNode = typeof globalThis.process?.versions?.node == 'string';
if (isNode) isPthread = {{{ nodePthreadDetection() }}};
#endif
#elif ENVIRONMENT_MAY_BE_NODE
var isPthread = {{{ nodePthreadDetection() }}};
#endif ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER

// When running as a pthread, construct a new instance on startup
isPthread && {{{ EXPORT_NAME }}}();

#endif // PTHREADS
21 changes: 21 additions & 0 deletions src/preamble_modularize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#if !MINIMAL_RUNTIME || PTHREADS
#if EXPORT_ES6
var _scriptName = import.meta.url;
#else
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
#if ENVIRONMENT_MAY_BE_NODE
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
#endif
#endif
#endif

#if EXPORT_ES6
// Ensure Closure and Acorn (specifically the JSDCE pass) is aware of the export.
// This gets replaced by `export default` in the final output.
// https://stackoverflow.com/questions/46092308
window["{{{ EXPORT_NAME }}}"] = {{{ asyncIf(WASM_ASYNC_COMPILATION || ENVIRONMENT_MAY_BE_NODE) }}}function(moduleArg = {}) {
#else
var {{{ EXPORT_NAME }}} = {{{ asyncIf(WASM_ASYNC_COMPILATION) }}}function(moduleArg = {}) {
#endif

var Module = moduleArg;
Loading
Loading