Skip to content

Support JS libraries self-registering their exports with visibility attributes - #27436

Merged
sbc100 merged 7 commits into
emscripten-core:mainfrom
guybedford:jslib-exported-functions
Aug 5, 2026
Merged

Support JS libraries self-registering their exports with visibility attributes#27436
sbc100 merged 7 commits into
emscripten-core:mainfrom
guybedford:jslib-exported-functions

Conversation

@guybedford

@guybedford guybedford commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

This implements support for JS libraries declaring their own public exports using per-symbol attributes, split out from #27208 as requested in review there.

The attributes separate symbol inclusion from export visibility:

  • __export: true exports the symbol if it is otherwise included.
  • __force: true includes the symbol even when nothing references it, without exporting it, analogous to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.
  • Using both unconditionally includes and exports the symbol.

This allows binding layers such as wasm-bindgen to define a public JS API surface distinct from the wasm export names, including exported classes and namespaces:

addToLibrary({
  $Counter__export: true,
  $Counter__force: true,
  $Counter: class Counter { /* ... */ },
});

The attributes are consumed as JS library compile-time metadata and do not appear in generated output. The JS compiler forwards only the symbols actually emitted through extraExports, allowing the WASM_ESM_INTEGRATION wrapper to re-export them without mutating or returning the global EXPORTED_FUNCTIONS setting. Decorated exports are also retained through meta-DCE.

Test coverage verifies the independent attribute semantics under legacy modularized output, MODULARIZE=instance, WASM_ESM_INTEGRATION, and optimized -O3 builds, together with decorator validation.

Made with AI assistance under my review

@sbc100

sbc100 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

I think supporting something like this is a great idea but maybe we can bikeshed the design a little.

How about limiting this to symbols actually defined in the JS library itself using symbol attributes. Perhaps something like:

myLibrary = {
   foo: 42,
   foo__export: true,  // Export this symbol outside the module
   foo__alwasyInclude: true, // Include this symbol in the generated code, even if no other code depends on it.
}

I don't love the alwasyInclude name.. perhaps force?

In this scenario it would be possible to specify one of these without that other.

If you specified __export without __force then them symbol would be exported, but only if it was included for some reason.

If you specified __force without __export then the symbol would be included in the generated JS but not exported (similar to (similar to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE).

I would hope that the information could all stay on the JS side, but if it needs to be plumbed back to python then it could then just be small extraExports list perhaps?

@guybedford

Copy link
Copy Markdown
Collaborator Author

I've updated this following the suggestion here for JS libraries to use the __export and __force symbol attributes:

  • __export: export only when otherwise included
  • __force: always include without exporting
  • both: always include and export

The attributes are compile-time metadata and are not emitted. We no longer mutate or forward EXPORTED_FUNCTIONS, only actually emitted exports are returned as extraExports for the ESM wrapper.

Coverage includes legacy modules, instance ESM, WASM_ESM_INTEGRATION, and O3/meta-DCE.

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is looking much better now! Thanks for being flexible.

Comment thread src/jsifier.mjs Outdated
Comment thread src/jsifier.mjs Outdated
Comment thread src/modules.mjs Outdated
@guybedford guybedford changed the title Support JS libraries self-registering their exports via EXPORTED_FUNCTIONS Support JS libraries self-registering their exports with visibility attributes Aug 5, 2026
Comment thread src/jsifier.mjs Outdated
Comment thread test/test_jslib.py

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm % comment.

Can you add a ChangeLog entry for this?

@guybedford
guybedford force-pushed the jslib-exported-functions branch from 281ec85 to bc3d67c Compare August 5, 2026 20:18
@guybedford

Copy link
Copy Markdown
Collaborator Author

Changelog added now as well.

@sbc100

sbc100 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

6.0.6 is about to be released in next few minutes/hours, so maybe we hold off landing this until after that happens in order to avoid having to move the changelog entry. See emscripten-core/emsdk#1774

@sbc100

sbc100 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

OK, 6.0.6 is now releases. Please rebase.

…TIONS

JS library code can already mutate the compile-time EXPORTED_FUNCTIONS set
at library load time, which under MODULARIZE=instance causes jsifier to emit
the symbol with an `export` declaration. This makes that flow fully work by
forwarding the final EXPORTED_FUNCTIONS set back from the JS compiler so the
linker can derive which JS library symbols were exported, and have the
WASM_ESM_INTEGRATION wrapper re-export them.

This is used by binding layers (e.g. wasm-bindgen) that define a public JS
API surface distinct from the wasm export names, registering it from their
generated JS library.
@guybedford
guybedford force-pushed the jslib-exported-functions branch from bc3d67c to ab22aee Compare August 5, 2026 22:12
@guybedford

Copy link
Copy Markdown
Collaborator Author

Ok the Changelog entry is now updated to 6.0.7.

Comment thread ChangeLog.md Outdated
@guybedford
guybedford force-pushed the jslib-exported-functions branch from ab22aee to d1fb203 Compare August 5, 2026 22:31
@sbc100
sbc100 enabled auto-merge (squash) August 5, 2026 23:12
@sbc100
sbc100 merged commit 0134648 into emscripten-core:main Aug 5, 2026
45 checks passed
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