Skip to content

Add support for source-phase imports (import source / import.source(...))#1

Merged
sbc100 merged 1 commit into
emscripten-core:emscripten_patches_v5.18.2from
guybedford:emscripten-source-phase-imports
May 21, 2026
Merged

Add support for source-phase imports (import source / import.source(...))#1
sbc100 merged 1 commit into
emscripten-core:emscripten_patches_v5.18.2from
guybedford:emscripten-source-phase-imports

Conversation

@guybedford
Copy link
Copy Markdown

@guybedford guybedford commented May 16, 2026

Adds support for the TC39 source-phase imports proposal (https://github.com/tc39/proposal-source-phase-imports) for both static and dynamic forms:

// static
import source wasmModule from "./foo.wasm";
import defer * as ns from "./mod.js";

// dynamic
const m = await import.source("./foo.wasm");
const m = await import.defer("./mod.js");

The source phase tells the host to materialize the module's source representation (e.g. a WebAssembly.Module for .wasm) instead of its exports namespace; defer requests a lazily-evaluated namespace. Dropping the phase keyword silently changes runtime semantics, which is what terser was doing on the mozilla-AST round-trip used by tools like Emscripten's acorn-optimizer.

This is the downstream port of upstream terser PR terser#1682 released in 5.48.0.

Static side

  • Parser recognises the contextual source / defer phase keyword after import, with peek-based disambiguation so existing patterns like import source from "x" and import defer, {y} from "x" keep parsing as default-name imports.
  • AST_Import gains a phase field, null for plain imports.
  • mozilla-ast from_moz / to_moz carry phase across, matching the shape produced by acorn-import-phases.
  • DEFPRINT(AST_Import) emits the phase keyword between import and the specifier; printing self.phase verbatim handles both source and defer.
  • equivalent-to includes phase in shallow_cmp.

Before / after a round-trip through the optimizer:

// before this patch
import wasmModule from "./foo.wasm";        // ! semantics changed

// after this patch
import source wasmModule from "./foo.wasm";

Dynamic side

  • New AST_DynamicImport node used to represent a phased dynamic import; carries the phase. Plain dynamic import(x) continues to use the synthetic AST_SymbolRef("import") callee for back-compat with downstream code that already groks that pattern.
  • Parser import_meta dispatches on meta / source / defer; import.source / import.defer must be followed by a call, else a parse error is raised, mirroring the proposal.
  • mozilla-ast: from_moz ImportExpression builds the AST_DynamicImport callee when M.phase is set; to_moz AST_Call emits a phased ImportExpression { phase } when the callee is AST_DynamicImport.
  • DEFPRINT(AST_DynamicImport) prints import.source / import.defer for the callee.
  • size, equivalent-to, has_side_effects and may_throw all updated for the new node.

Tests are also included in the backport.

Copy link
Copy Markdown

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too familiar with acorn internals. Do we need/want to land this right away or can we wait for the upstream terser patch to get reviewed?

lgtm assuming upstream thinks its reasonable.

Copy link
Copy Markdown

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't want to wait for upstream we can probably still go ahead, just let me know and I'll take a deeper look.

@guybedford guybedford force-pushed the emscripten-source-phase-imports branch from a537c17 to 6f9925b Compare May 16, 2026 01:45
@guybedford guybedford changed the base branch from emscripten_patches_v4.8.0 to emscripten_patches_v5.18.2 May 16, 2026 01:45
guybedford added a commit to guybedford/emscripten that referenced this pull request May 16, 2026
When -sSOURCE_PHASE_IMPORTS=1, emcc emits
```js
import source wasmModule from './foo.wasm';
```
in its JS runtime. At -O2/-O3/-Os/-Oz the emitted JS is run through
tools/acorn-optimizer.mjs which currently fails with a SyntaxError at
parse time because acorn 8.x does not yet understand the source-phase
imports proposal (https://github.com/tc39/proposal-source-phase-imports).

Wire in the acorn-import-phases plugin so acorn can parse the syntax,
and pull in the matching terser support (downstream of emscripten-core/terser#1)
so the round-trip through from_mozilla_ast / to_mozilla_ast preserves
the `phase` keyword on the way back out. Without the terser side,
terser would silently drop the keyword and the host would return the
module's exports namespace instead of a WebAssembly.Module, changing
runtime semantics.

* package.json: add acorn-import-phases dependency.
* tools/acorn-optimizer.mjs: extend acorn with the plugin and use the
  extended parser at the parse site.
* third_party/terser/terser.js: rebuilt from the emscripten-core/terser
  branch with source-phase imports support (PR
  emscripten-core/terser#1, the v5.18.2
  downstream port of upstream terser PR
  terser/terser#1682).
* test/js_optimizer/sourcePhaseImports{,-output}.js: new fixture that
  feeds two `import source` declarations through the JSDCE pass and
  checks the keyword survives.
* test/test_other.py: register the fixture in test_js_optimizer, and
  parametrize test_esm_source_phase_imports across no-args and -O2 to
  exercise the optimizer pipeline.
guybedford added a commit to guybedford/emscripten that referenced this pull request May 16, 2026
When -sSOURCE_PHASE_IMPORTS=1, emcc emits
```js
import source wasmModule from './foo.wasm';
```
in its JS runtime. At -O2/-O3/-Os/-Oz the emitted JS is run through
tools/acorn-optimizer.mjs which currently fails with a SyntaxError at
parse time because acorn 8.x does not yet understand the source-phase
imports proposal (https://github.com/tc39/proposal-source-phase-imports).

Wire in the acorn-import-phases plugin so acorn can parse the syntax,
and pull in the matching terser support (downstream of emscripten-core/terser#1)
so the round-trip through from_mozilla_ast / to_mozilla_ast preserves
the `phase` keyword on the way back out. Without the terser side,
terser would silently drop the keyword and the host would return the
module's exports namespace instead of a WebAssembly.Module, changing
runtime semantics.

* package.json: add acorn-import-phases dependency.
* tools/acorn-optimizer.mjs: extend acorn with the plugin and use the
  extended parser at the parse site.
* third_party/terser/terser.js: rebuilt from the emscripten-core/terser
  branch with source-phase imports support (PR
  emscripten-core/terser#1, the v5.18.2
  downstream port of upstream terser PR
  terser/terser#1682).
* test/js_optimizer/sourcePhaseImports{,-output}.js: new fixture that
  feeds two `import source` declarations through the JSDCE pass and
  checks the keyword survives.
* test/test_other.py: register the fixture in test_js_optimizer, and
  parametrize test_esm_source_phase_imports across no-args and -O2 to
  exercise the optimizer pipeline.
@guybedford
Copy link
Copy Markdown
Author

Will update here after upstream review process rather.

…namic `import.source(...)` / `import.defer(...)`) (terser#1682)

* add support for source-phase imports (`import source`, `import.source(...)`)

Implements the TC39 source-phase imports proposal
(https://github.com/tc39/proposal-source-phase-imports) for both static
and dynamic forms:

  // static
  import source wasmModule from "./foo.wasm";
  import defer * as ns from "./mod.js";

  // dynamic
  const m = await import.source("./foo.wasm");
  const m = await import.defer("./mod.js");

Without this, terser dropped the phase keyword on the mozilla-AST
round-trip used by tools like Emscripten's acorn-optimizer, silently
changing runtime semantics.

Static side:
* Parser recognises the contextual `source`/`defer` phase keyword
  after `import`, with peek-based disambiguation so existing patterns
  like `import source from "x"` keep parsing as default-name imports.
* AST_Import gains a `phase` field (null for plain imports).
* mozilla-ast and the printer carry phase through.

Dynamic side:
* New AST_DynamicImport node owns the whole `import.source(...)` /
  `import.defer(...)` expression, including its args. It is NOT an
  AST_Call. Plain `import(x)` is still parsed as an AST_Call with a
  synthetic `import` SymbolRef callee.
* mozilla-ast: ImportExpression with `phase` round-trips to/from
  AST_DynamicImport.
* size, equivalent-to and has_side_effects/may_throw updated.

* address review feedback: tidy comments and use (x || null) === pattern

* Update test/compress/harmony.js

* Update test/compress/harmony.js

---------

Co-authored-by: Fábio Santos <fabiosantosart@gmail.com>
@guybedford guybedford force-pushed the emscripten-source-phase-imports branch from 6f9925b to 79b61c4 Compare May 21, 2026 19:44
guybedford added a commit to guybedford/emscripten that referenced this pull request May 21, 2026
When -sSOURCE_PHASE_IMPORTS=1, emcc emits
```js
import source wasmModule from './foo.wasm';
```
in its JS runtime. At -O2/-O3/-Os/-Oz the emitted JS is run through
tools/acorn-optimizer.mjs which currently fails with a SyntaxError at
parse time because acorn 8.x does not yet understand the source-phase
imports proposal (https://github.com/tc39/proposal-source-phase-imports).

Wire in the acorn-import-phases plugin so acorn can parse the syntax,
and pull in the matching terser support (downstream of emscripten-core/terser#1)
so the round-trip through from_mozilla_ast / to_mozilla_ast preserves
the `phase` keyword on the way back out. Without the terser side,
terser would silently drop the keyword and the host would return the
module's exports namespace instead of a WebAssembly.Module, changing
runtime semantics.

* package.json: add acorn-import-phases dependency.
* tools/acorn-optimizer.mjs: extend acorn with the plugin and use the
  extended parser at the parse site.
* third_party/terser/terser.js: rebuilt from the emscripten-core/terser
  branch with source-phase imports support (PR
  emscripten-core/terser#1, the v5.18.2
  downstream port of upstream terser PR
  terser/terser#1682).
* test/js_optimizer/sourcePhaseImports{,-output}.js: new fixture that
  feeds two `import source` declarations through the JSDCE pass and
  checks the keyword survives.
* test/test_other.py: register the fixture in test_js_optimizer, and
  parametrize test_esm_source_phase_imports across no-args and -O2 to
  exercise the optimizer pipeline.
@guybedford
Copy link
Copy Markdown
Author

The upstream PR has now been landed and released and this PR implementation is now based to that updated upstream PR change.

@sbc100 PTAL

Copy link
Copy Markdown

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Can you maybe update the description now that the upstream change has landed?

@guybedford
Copy link
Copy Markdown
Author

Sure, updated the description to match upstream.

@sbc100 sbc100 merged commit 9759c2a into emscripten-core:emscripten_patches_v5.18.2 May 21, 2026
0 of 10 checks passed
guybedford added a commit to guybedford/emscripten that referenced this pull request May 21, 2026
When -sSOURCE_PHASE_IMPORTS=1, emcc emits
```js
import source wasmModule from './foo.wasm';
```
in its JS runtime. At -O2/-O3/-Os/-Oz the emitted JS is run through
tools/acorn-optimizer.mjs which currently fails with a SyntaxError at
parse time because acorn 8.x does not yet understand the source-phase
imports proposal (https://github.com/tc39/proposal-source-phase-imports).

Wire in the acorn-import-phases plugin so acorn can parse the syntax,
and pull in the matching terser support (downstream of emscripten-core/terser#1)
so the round-trip through from_mozilla_ast / to_mozilla_ast preserves
the `phase` keyword on the way back out. Without the terser side,
terser would silently drop the keyword and the host would return the
module's exports namespace instead of a WebAssembly.Module, changing
runtime semantics.

* package.json: add acorn-import-phases dependency.
* tools/acorn-optimizer.mjs: extend acorn with the plugin and use the
  extended parser at the parse site.
* third_party/terser/terser.js: rebuilt from the emscripten-core/terser
  branch with source-phase imports support (PR
  emscripten-core/terser#1, the v5.18.2
  downstream port of upstream terser PR
  terser/terser#1682).
* test/js_optimizer/sourcePhaseImports{,-output}.js: new fixture that
  feeds two `import source` declarations through the JSDCE pass and
  checks the keyword survives.
* test/test_other.py: register the fixture in test_js_optimizer, and
  parametrize test_esm_source_phase_imports across no-args and -O2 to
  exercise the optimizer pipeline.
guybedford added a commit to guybedford/emscripten that referenced this pull request May 21, 2026
When -sSOURCE_PHASE_IMPORTS=1, emcc emits
```js
import source wasmModule from './foo.wasm';
```
in its JS runtime. At -O2/-O3/-Os/-Oz the emitted JS is run through
tools/acorn-optimizer.mjs which currently fails with a SyntaxError at
parse time because acorn 8.x does not yet understand the source-phase
imports proposal (https://github.com/tc39/proposal-source-phase-imports).

Wire in the acorn-import-phases plugin so acorn can parse the syntax,
and pull in the matching terser support (downstream of emscripten-core/terser#1)
so the round-trip through from_mozilla_ast / to_mozilla_ast preserves
the `phase` keyword on the way back out. Without the terser side,
terser would silently drop the keyword and the host would return the
module's exports namespace instead of a WebAssembly.Module, changing
runtime semantics.

* package.json: add acorn-import-phases dependency.
* tools/acorn-optimizer.mjs: extend acorn with the plugin and use the
  extended parser at the parse site.
* third_party/terser/terser.js: rebuilt from the emscripten-core/terser
  branch with source-phase imports support (PR
  emscripten-core/terser#1, the v5.18.2
  downstream port of upstream terser PR
  terser/terser#1682).
* test/js_optimizer/sourcePhaseImports{,-output}.js: new fixture that
  feeds two `import source` declarations through the JSDCE pass and
  checks the keyword survives.
* test/test_other.py: register the fixture in test_js_optimizer, and
  parametrize test_esm_source_phase_imports across no-args and -O2 to
  exercise the optimizer pipeline.
sbc100 pushed a commit to emscripten-core/emscripten that referenced this pull request May 21, 2026
…26967)

With `-sSOURCE_PHASE_IMPORTS=1`, emcc emits `import source wasmModule
from './foo.wasm'` in its JS runtime, but at `-O2`+ the optimizer
crashes because acorn 8.x doesn't yet understand the syntax. Even once
acorn parses it, the bundled terser silently drops the `source` keyword
on the mozilla-AST round-trip, which changes runtime semantics (host
returns the exports namespace instead of a `WebAssembly.Module`).

This wires in the `acorn-import-phases` plugin so acorn parses the
syntax, and pulls in matching terser support so the keyword survives
printing. Terser side is
emscripten-core/terser#1, which is vendored via
`third_party/terser/terser.js` rebuilt from that branch.

New fixture `test/js_optimizer/sourcePhaseImports.js` runs two `import
source` declarations through JSDCE and checks the keyword survives; runs
via `./test/runner other.test_js_optimizer_sourcePhaseImports`.

That terser PR should ideally land first so this can be rebuilt from a
merged branch; happy to rebase the bundle afterwards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants