-
-
Notifications
You must be signed in to change notification settings - Fork 59
Add Node.js example #473
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
Add Node.js example #473
Changes from 3 commits
b8bd915
15b2a0d
cf25af7
71b4bde
b5e27bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| .DS_Store | ||
| /.build | ||
| /Packages | ||
| xcuserdata/ | ||
| DerivedData/ | ||
| .swiftpm/configuration/registries.json | ||
| .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
| .netrc |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // swift-tools-version: 6.2 | ||
|
|
||
| import PackageDescription | ||
|
|
||
| let package = Package( | ||
| name: "NodeJS", | ||
| dependencies: [.package(name: "JavaScriptKit", path: "../../")], | ||
| targets: [ | ||
| .executableTarget( | ||
| name: "NodeJS", | ||
| dependencies: ["JavaScriptKit"] | ||
| ) | ||
| ], | ||
| swiftLanguageModes: [.v6] | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # NodeJS example | ||
|
|
||
| This example demonstrates how to use JavaScriptKit with Node.js. It shows how to export Swift functions to JavaScript and run them in a Node.js environment. | ||
|
|
||
| ```sh | ||
| $ swift package --swift-sdk $SWIFT_SDK_ID js | ||
| $ node main.mjs | ||
| ``` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import JavaScriptKit | ||
|
|
||
| @main | ||
| struct NodeJS { | ||
| static func main() { | ||
| JSObject.global["greet"] = | ||
| JSClosure { args in | ||
| let nameString = args[0].string! | ||
| return .string("Hello, \(nameString) from NodeJS!") | ||
| }.jsValue | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // @ts-check | ||
|
|
||
| import { instantiate } from "./.build/plugins/PackageToJS/outputs/Package/instantiate.js" | ||
| import { defaultNodeSetup } from "./.build/plugins/PackageToJS/outputs/Package/platforms/node.js" | ||
|
|
||
| async function main() { | ||
| // Create a default Node.js option object | ||
| const options = await defaultNodeSetup(); | ||
| // Instantiate the Swift code, executing | ||
| // NodeJS.main() in NodeJS.swift | ||
| await instantiate(options); | ||
|
|
||
| // Call the greet function set by NodeJS.swift | ||
| const greet = globalThis.greet; | ||
| console.log(greet("World")); | ||
| } | ||
|
|
||
| main() |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -102,9 +102,9 @@ class DefaultBrowserThreadRegistry { | |||||
| /** @type {import('./browser.d.ts').defaultBrowserSetup} */ | ||||||
| export async function defaultBrowserSetup(options) { | ||||||
| /* #if IS_WASI */ | ||||||
| const args = options.args ?? [] | ||||||
| const onStdoutLine = options.onStdoutLine ?? ((line) => console.log(line)) | ||||||
| const onStderrLine = options.onStderrLine ?? ((line) => console.error(line)) | ||||||
| const args = options?.args ?? [] | ||||||
| const onStdoutLine = options?.onStdoutLine ?? ((line) => console.log(line)) | ||||||
| const onStderrLine = options?.onStderrLine ?? ((line) => console.error(line)) | ||||||
| const wasi = new WASI(/* args */[MODULE_PATH, ...args], /* env */[], /* fd */[ | ||||||
| new OpenFile(new File([])), // stdin | ||||||
| ConsoleStdout.lineBuffered((stdout) => { | ||||||
|
|
@@ -118,13 +118,13 @@ export async function defaultBrowserSetup(options) { | |||||
| /* #endif */ | ||||||
| /* #if USE_SHARED_MEMORY */ | ||||||
| const memory = new WebAssembly.Memory(MEMORY_TYPE); | ||||||
| const threadChannel = new DefaultBrowserThreadRegistry(options.spawnWorker) | ||||||
| const threadChannel = new DefaultBrowserThreadRegistry(options?.spawnWorker || createDefaultWorkerFactory()) | ||||||
| /* #endif */ | ||||||
|
|
||||||
| return { | ||||||
| module: options.module, | ||||||
| module: options?.module, | ||||||
|
||||||
| /* #if HAS_IMPORTS */ | ||||||
| getImports() { return options.getImports() }, | ||||||
| getImports() { return options?.getImports() }, | ||||||
|
||||||
| getImports() { return options?.getImports() }, | |
| getImports() { return options?.getImports?.() ?? {} }, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| # Package Output Structure | ||
|
|
||
| Understand the structure and contents of the JavaScript package generated by the `swift package js` command. | ||
|
|
||
| ## Overview | ||
|
|
||
| When you run `swift package --swift-sdk $SWIFT_SDK_ID js`, the PackageToJS plugin compiles your Swift code to WebAssembly and generates a JavaScript package in `.build/plugins/PackageToJS/outputs/Package/`. This package contains all the necessary files to run your Swift application in JavaScript environments (browser or Node.js). | ||
|
|
||
| ## Package Structure | ||
|
|
||
| The output package has the following structure: | ||
|
|
||
| ``` | ||
| .build/plugins/PackageToJS/outputs/Package/ | ||
| ├── ProductName.wasm # Compiled WebAssembly module | ||
| ├── index.js # Main entry point for browser environments | ||
| ├── index.d.ts # TypeScript type definitions for index.js | ||
| ├── instantiate.js # Low-level instantiation API | ||
| ├── instantiate.d.ts # TypeScript type definitions for instantiate.js | ||
| ├── package.json # npm package metadata | ||
| └── platforms/ | ||
| ├── browser.js # Browser-specific platform setup | ||
| ├── browser.d.ts # TypeScript definitions for browser.js | ||
| ├── node.js # Node.js-specific platform setup | ||
| └── node.d.ts # TypeScript definitions for node.js | ||
| ``` | ||
|
|
||
| ## Using the Package | ||
|
|
||
| ### In Browser | ||
|
|
||
| ```html | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <body> | ||
| <script type="module"> | ||
| import { init } from './.build/plugins/PackageToJS/outputs/Package/index.js'; | ||
| await init(); | ||
| </script> | ||
| </body> | ||
| </html> | ||
| ``` | ||
|
|
||
| ### In Node.js | ||
|
|
||
| ```javascript | ||
| import { instantiate } from './.build/plugins/PackageToJS/outputs/Package/instantiate.js'; | ||
| import { defaultNodeSetup } from './.build/plugins/PackageToJS/outputs/Package/platforms/node.js'; | ||
|
|
||
| async function main() { | ||
| const options = await defaultNodeSetup(); | ||
| await instantiate(options); | ||
| } | ||
|
|
||
| main(); | ||
| ``` | ||
|
|
||
| > Tip: For a complete Node.js setup example, see the [NodeJS example](https://github.com/swiftwasm/JavaScriptKit/tree/main/Examples/NodeJS). | ||
kateinoigakukun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ### With Bundlers (Vite, Webpack, etc.) | ||
|
|
||
| The generated package can be consumed by JavaScript bundlers: | ||
|
|
||
| ```bash | ||
| npm install .build/plugins/PackageToJS/outputs/Package | ||
| ``` | ||
|
|
||
| Then import it in your JavaScript code: | ||
|
|
||
| ```javascript | ||
| import { init } from 'package-name'; | ||
| await init(); | ||
| ``` | ||
|
|
||
| ## Core Files | ||
|
|
||
| ### WebAssembly Module (`ProductName.wasm`) | ||
|
|
||
| The compiled WebAssembly binary containing your Swift code. The filename matches your SwiftPM product name (e.g., `Basic.wasm` for a product named "Basic"). | ||
|
|
||
| ### Entry Point (`index.js`) | ||
|
|
||
| The main entry point for browser environments. It provides a convenient `init()` function that handles module instantiation with default settings. | ||
|
|
||
| ```javascript | ||
| import { init } from './.build/plugins/PackageToJS/outputs/Package/index.js'; | ||
|
|
||
| // Initialize with default browser setup | ||
| await init(); | ||
| ``` | ||
|
|
||
| For packages with BridgeJS imports, you can provide custom imports: | ||
|
|
||
| ```javascript | ||
| import { init } from './.build/plugins/PackageToJS/outputs/Package/index.js'; | ||
|
|
||
| await init({ | ||
| getImports: () => ({ | ||
| // Your custom imports | ||
| }) | ||
| }); | ||
| ``` | ||
|
|
||
| ### Instantiation API (`instantiate.js`) | ||
|
|
||
| A lower-level API for more control over module instantiation. Use this when you need to customize the WebAssembly instantiation process or WASI setup. | ||
|
|
||
| ```javascript | ||
| import { instantiate } from './.build/plugins/PackageToJS/outputs/Package/instantiate.js'; | ||
| import { defaultBrowserSetup } from './.build/plugins/PackageToJS/outputs/Package/platforms/browser.js'; | ||
|
|
||
| const options = await defaultBrowserSetup({ | ||
| module: fetch('./ProductName.wasm'), | ||
| // ... other options | ||
| }); | ||
|
|
||
| const { instance, swift, exports } = await instantiate(options); | ||
| ``` | ||
|
|
||
| ### Platform-Specific Setup | ||
|
|
||
| The `platforms/` directory contains platform-specific setup functions: | ||
| - `platforms/browser.js` - Provides `defaultBrowserSetup()` for browser environments | ||
| - `platforms/node.js` - Provides `defaultNodeSetup()` for Node.js environments | ||
|
|
||
| ## Package Metadata (`package.json`) | ||
|
|
||
| The generated `package.json` includes: | ||
|
|
||
| ```json | ||
| { | ||
| "name": "package-name", | ||
| "version": "0.0.0", | ||
| "type": "module", | ||
| "private": true, | ||
| "exports": { | ||
| ".": "./index.js", | ||
| "./wasm": "./ProductName.wasm" | ||
| }, | ||
| "dependencies": { | ||
| "@bjorn3/browser_wasi_shim": "0.3.0" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `exports` field allows importing the package as an npm dependency: | ||
|
|
||
| ```javascript | ||
| import { init } from '.build/plugins/PackageToJS/outputs/Package'; | ||
| ``` | ||
|
|
||
| ## TypeScript Support | ||
|
|
||
| All JavaScript files have corresponding `.d.ts` TypeScript definition files, providing full type safety when using the package in TypeScript projects. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.