Skip to content

add JS-snippet support using from: .module("/path/to/js-module.js") syntax - #792

Open
sliemeobn wants to merge 10 commits into
swiftwasm:mainfrom
sliemeobn:feature/js-snippets
Open

add JS-snippet support using from: .module("/path/to/js-module.js") syntax#792
sliemeobn wants to merge 10 commits into
swiftwasm:mainfrom
sliemeobn:feature/js-snippets

Conversation

@sliemeobn

@sliemeobn sliemeobn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

allow to ship JS code as the source for imports (in addition to ".global" and explicitly provided imports)

@JSFunction(from: .module("/Modules/JSImportModule.mjs"))
func moduleAdd(_ lhs: Int, _ rhs: Int) throws(JSException) -> Int

@JSFunction(jsName: "renamedFunction", from: .module("/Modules/JSImportModule.mjs"))
func moduleRenamed() throws(JSException) -> String

@JSGetter(jsName: "version", from: .module("/Modules/JSImportModule.mjs"))
var moduleVersion: String
// Modules/JSImportModule.mjs
export function moduleAdd(lhs, rhs) {
    return lhs + rhs;
}

export function renamedFunction() {
    return "renamed";
}

export const version = "1.0";

I noticed that currently there is no diagnostic when from: is used in places that do not support it, I did not add these checks (eg: class members, setters) to this PR more focussed. (#793)

@sliemeobn

Copy link
Copy Markdown
Contributor Author

closes #508

@krodak krodak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice feature — the design fits the existing patterns well and test coverage is strong. Details in the inline comments.

Two tiny things not worth their own threads: the registry error messages render a double slash (TestModule//Modules/x.mjs — path already starts with /), and a runtime test for a throwing module export would pin down the setException path too.

Comment thread Plugins/PackageToJS/Sources/PackageToJS.swift Outdated
for skeleton in skeletons {
for module in skeleton.imported?.modules ?? [] {
guard module.path.hasPrefix("/") else {
throw BridgeJSLinkError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

relativePath is spliced straight into outputDir.appending(path:) in PackagingPlanner. makeJavaScriptModuleFileLookup guarantees no traversal for locally generated skeletons, so this is safe today — but skeletons also arrive from dependency packages' committed BridgeJS.json, and a path like /../../evil.js in one of those would write outside the output directory (and emit an escaping import line). Cheap to close here since we're already validating:

guard module.path.hasPrefix("/"),
    !module.path.split(separator: "/").contains("..")
else {
    throw BridgeJSLinkError(
        message: "JavaScript module path must start with '/' and must not contain '..': \(module.path)"
    )
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense, will add that

Comment thread Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift
Comment thread Plugins/BridgeJS/Sources/BridgeJSPluginUtilities/InputDiscovery.swift Outdated

You can bring JavaScript into Swift in two ways:
You can bring JavaScript into Swift in three ways:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tiny one: the intro at line 11 of this article still says imports work in "two ways", so the article now disagrees with itself:

You can import JavaScript APIs into Swift in three ways:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually, IIUC, this refers to "either via macros, or via bridge-js.d.ts file.
I'll change it to You can define JavaScript bindings for Swift in two ways: to fit the rest of the doc better.

@kateinoigakukun kateinoigakukun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC the current approach requires developers to run the build plugins and link steps to reflect their changes against snippet js files into the final output, right? This is another challenge I wanted to explore to have faster iterations.

Comment thread Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift Outdated
@sliemeobn

Copy link
Copy Markdown
Contributor Author

@krodak @kateinoigakukun I reworked the whole thing to not copy anything to the skeletons and only use the "from" paths during the PackageToJS / link run. I think this is better ultimately.

the only thing that is a bit worse is that I always load and decode all skeletons up-front to extract all js-files as mini-make inputs. I though about adding some caching or smarter loading, but I wanted to keep this PR more straight forward.

maybe a separate PR "optimize PackageToJS" would be a good idea anyway eventually - I am sure there are milliseconds to be found here and there.

@kateinoigakukun kateinoigakukun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me!

@sliemeobn

Copy link
Copy Markdown
Contributor Author

any idea what causes the diff? I ran the generate stuff locally and get no changes - but CI has this error

diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift
index 181224c..39de49c 100644
--- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift
+++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift
@@ -17043,8 +17043,8 @@ fileprivate func bjs_moduleAdd_extern(_ lhs: Int32, _ rhs: Int32) -> Int32 {
 }
 
 func _$moduleAdd(_ lhs: Int, _ rhs: Int) throws(JSException) -> Int {
-    let lhsValue = lhs.bridgeJSLowerParameter()
     let rhsValue = rhs.bridgeJSLowerParameter()
+    let lhsValue = lhs.bridgeJSLowerParameter()
     let ret = bjs_moduleAdd(lhsValue, rhsValue)
     if let error = _swift_js_take_exception() {
         throw error
@@ -17179,8 +17179,8 @@ func _$ModuleCounter_value_get(_ self: JSObject) throws(JSException) -> Int {
 }
 
 func _$ModuleCounter_value_set(_ self: JSObject, _ newValue: Int) throws(JSException) -> Void {
-    let selfValue = self.bridgeJSLowerParameter()
     let newValueValue = newValue.bridgeJSLowerParameter()
+    let selfValue = self.bridgeJSLowerParameter()
     bjs_ModuleCounter_value_set(selfValue, newValueValue)
     if let error = _swift_js_take_exception() {
         throw error

@sliemeobn
sliemeobn force-pushed the feature/js-snippets branch from cac4b67 to 936b5c5 Compare July 29, 2026 18:44
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.

3 participants