Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Examples/NodeJS/.gitignore
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
15 changes: 15 additions & 0 deletions Examples/NodeJS/Package.swift
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]
)
9 changes: 9 additions & 0 deletions Examples/NodeJS/README.md
Original file line number Diff line number Diff line change
@@ -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
```

12 changes: 12 additions & 0 deletions Examples/NodeJS/Sources/NodeJS/NodeJS.swift
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
}
}
18 changes: 18 additions & 0 deletions Examples/NodeJS/main.mjs
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()
2 changes: 1 addition & 1 deletion Plugins/PackageToJS/Templates/platforms/browser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<InstantiateOptions>

Expand Down
2 changes: 1 addition & 1 deletion Plugins/PackageToJS/Templates/platforms/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions Plugins/PackageToJS/Templates/platforms/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<InstantiateOptions>
export function defaultNodeSetup(options?: DefaultNodeSetupOptions): Promise<InstantiateOptions>

export function createDefaultWorkerFactory(preludeScript?: string): (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker
4 changes: 2 additions & 2 deletions Plugins/PackageToJS/Templates/platforms/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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 {
Expand Down
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 [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.

1 change: 1 addition & 0 deletions Sources/JavaScriptKit/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex

### Articles

- <doc:Package-Output-Structure>
- <doc:Deploying-Pages>
- <doc:JavaScript-Environment-Requirements>
- <doc:Testing>
Expand Down