diff --git a/Examples/NodeJS/.gitignore b/Examples/NodeJS/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Examples/NodeJS/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/NodeJS/Package.swift b/Examples/NodeJS/Package.swift new file mode 100644 index 00000000..83a84fc8 --- /dev/null +++ b/Examples/NodeJS/Package.swift @@ -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] +) diff --git a/Examples/NodeJS/README.md b/Examples/NodeJS/README.md new file mode 100644 index 00000000..2b4e0321 --- /dev/null +++ b/Examples/NodeJS/README.md @@ -0,0 +1,9 @@ +# Node.js 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 +``` + diff --git a/Examples/NodeJS/Sources/NodeJS/NodeJS.swift b/Examples/NodeJS/Sources/NodeJS/NodeJS.swift new file mode 100644 index 00000000..deebc1bd --- /dev/null +++ b/Examples/NodeJS/Sources/NodeJS/NodeJS.swift @@ -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 + } +} diff --git a/Examples/NodeJS/main.mjs b/Examples/NodeJS/main.mjs new file mode 100644 index 00000000..5aca7d0a --- /dev/null +++ b/Examples/NodeJS/main.mjs @@ -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() diff --git a/Plugins/PackageToJS/Templates/platforms/browser.d.ts b/Plugins/PackageToJS/Templates/platforms/browser.d.ts index babe3f48..dcfb5f61 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/browser.d.ts @@ -11,7 +11,7 @@ export function defaultBrowserSetup(options: { getImports: () => Imports, /* #endif */ /* #if USE_SHARED_MEMORY */ - spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, + spawnWorker?: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, /* #endif */ }): Promise diff --git a/Plugins/PackageToJS/Templates/platforms/browser.js b/Plugins/PackageToJS/Templates/platforms/browser.js index 3fce7c55..d471e841 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.js @@ -118,7 +118,7 @@ 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 { diff --git a/Plugins/PackageToJS/Templates/platforms/node.d.ts b/Plugins/PackageToJS/Templates/platforms/node.d.ts index ca0e826c..4b8d9584 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/node.d.ts @@ -7,10 +7,10 @@ export type DefaultNodeSetupOptions = { /* #endif */ onExit?: (code: number) => void, /* #if USE_SHARED_MEMORY */ - spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, + spawnWorker?: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, /* #endif */ } -export function defaultNodeSetup(options: DefaultNodeSetupOptions): Promise +export function defaultNodeSetup(options?: DefaultNodeSetupOptions): Promise export function createDefaultWorkerFactory(preludeScript?: string): (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index 2fc0e8d1..3e125396 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -113,7 +113,7 @@ class DefaultNodeThreadRegistry { /* #endif */ /** @type {import('./node.d.ts').defaultNodeSetup} */ -export async function defaultNodeSetup(options) { +export async function defaultNodeSetup(options = {}) { const path = await import("node:path"); const { fileURLToPath } = await import("node:url"); const { readFile } = await import("node:fs/promises") @@ -134,7 +134,7 @@ export async function defaultNodeSetup(options) { const module = await WebAssembly.compile(new Uint8Array(await readFile(path.join(pkgDir, MODULE_PATH)))) /* #if USE_SHARED_MEMORY */ const memory = new WebAssembly.Memory(MEMORY_TYPE); - const threadChannel = new DefaultNodeThreadRegistry(options.spawnWorker) + const threadChannel = new DefaultNodeThreadRegistry(options.spawnWorker || createDefaultWorkerFactory()) /* #endif */ return { diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md new file mode 100644 index 00000000..af9b0d2b --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md @@ -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 + + + + + + +``` + +### 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 [Node.js example](https://github.com/swiftwasm/JavaScriptKit/tree/main/Examples/NodeJS). + +### 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. + diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index d506c386..60808a89 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -51,6 +51,7 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex ### Articles +- - - -