node-swift

repository·main·Indexed 20 days ago

https://github.com/kabiroberai/node-swift

A bridge enabling bidirectional interoperability between Node.js and Swift. It allows developers to use native macOS APIs and SwiftPM within Electron apps, access NPM libraries from Swift applications (macOS, iOS, or Vapor servers), and optimize performance-critical JavaScript by rewriting logic in Swift. The library wraps the C-based Node-API with a memory-safe Swift module called NodeAPI and provides a CLI tool for managing the build process of Swift packages into .node native modules.

Tokens
2.5K
Snippets
8
Records
11
Agent score
70%

What's inside node-swift

  1. Bridge Node.js and Swift code with NodeSwift

    main

    NodeSwift is a bridge that allows bidirectional communication between Node.js and Swift. It enables you to:

    • Use native macOS APIs and SwiftPM within an Electron app.
    • Access NPM libraries and APIs from a Swift program (such as a macOS app, iOS app, or a Vapor server).
    • Optimize performance-critical JavaScript logic by rewriting it in Swift.

    NodeSwift works by wrapping the C-based Node-API with a Swift module called NodeAPI, providing a memory-safe and idiomatic Swift interface.

  2. How NodeSwift modules are structured

    main

    A NodeSwift module is composed of two interconnected packages residing in the same folder:

    1. A SwiftPM package: This contains your Swift source code and defines the logic.
    2. An NPM package: This manages the Node.js side of the integration.

    Both packages must express NodeSwift as a dependency. The Swift package is compiled into a native Node.js module (a .node binary) which is then consumed by the JavaScript code via require().

  3. Export Swift values and functions to Node.js

    main

    You can export Swift data types and functions to JavaScript using the #NodeModule macro. This macro takes an exports dictionary where keys represent the names available in the required Node.js module.

    • Static Values: You can export standard Swift types like Array, String, or Double directly.
    • Functions: Use the NodeFunction wrapper to define Swift closures that can be called from JavaScript. These functions can take Swift types as arguments and return values that are compatible with Node.js.

    Once compiled, the resulting .node file can be loaded using require() in Node.js.

    import NodeAPI
    
    #NodeModule(exports: [
        "nums": [Double.pi.rounded(.down), Double.pi.rounded(.up)],
        "str": String(repeating: "NodeSwift! ", count: 3),
        "add": try NodeFunction { (a: Double, b: Double) in
            "\(a) + \(b) = \(a + b)"
        },
    ])
    const { nums, str, add } = require("./build/MyModule.node");
    console.log(nums); // [ 3, 4 ]
    console.log(str); // NodeSwift! NodeSwift! NodeSwift!
    console.log(add(5, 10)); // 5.0 + 10.0 = 15.0
  4. Configure the build via the Config interface

    main

    The Config object allows fine-grained control over the compilation process. You can specify paths, compiler flags, and the builder type.

    Available Configuration Keys:

    • buildPath (string): The directory where build artifacts are stored (defaults to .build in the current working directory).
    • packagePath (string): The path to the Swift package root.
    • product (string): The name of the specific dynamic library product to build.
    • static (boolean): Whether to build a static library.
    • napi (number | "experimental"): Sets the N-API version or enables experimental N-API flags.
    • cFlags (string | string[]): C compiler flags.
    • swiftFlags (string | string[]): Swift compiler flags.
    • cxxFlags (string | string[]): C++ compiler flags.
    • linkerFlags (string | string[]): Linker flags.
    • dumpFlags (string | string[]): Flags passed to swift package dump-package.
    • builder (SwiftPMBuilder | XcodeBuilder | string): Specifies the build engine.
    const config: Config = {
      packagePath: './my-swift-package',
      product: 'MyLibrary',
      static: false,
      cFlags: ['-O3'],
      swiftFlags: ['-DDEBUG'],
      builder: { type: 'swiftpm', settings: ['--enable-test'] }
    };
  5. Configure node-swift via package.json

    main

    The node-swift build tool looks for a swift configuration object within your project's package.json file. This object is passed to the builder to customize the build process.

    Example package.json structure:

    {
      "name": "my-project",
      "swift": {
        "option1": "value1"
      }
    }
    {
      "swift": {
        "key": "value"
      }
    }
  6. Configure ESLint for NodeSwift

    main

    NodeSwift uses ESLint with TypeScript support via typescript-eslint. The configuration applies recommended and stylistic rules and is configured to use the TypeScript project service for type-aware linting.

    Key configuration details:

    • Parser Options: Uses projectService: true and sets tsconfigRootDir to the current directory to enable efficient type-checking during linting.
    • Ignored Directories: The following directories are excluded from linting: dist, lib, test, example, and eslint.config.mjs.
    • Custom Rules:
      • @typescript-eslint/no-explicit-any is set to off.
      • @typescript-eslint/no-inferrable-types is set to off.
    import eslint from '@eslint/js';
    import tseslint from 'typescript-eslint';
    
    export default tseslint.config(
      eslint.configs.recommended,
      tseslint.configs.recommended,
      tseslint.configs.stylistic,
      {
        ignores: [
          'dist',
          'lib',
          'test',
          'example',
          'eslint.config.mjs',
        ],
      },
      {
        rules: {
          '@typescript-eslint/no-explicit-any': 'off',
          '@typescript-eslint/no-inferrable-types': 'off',
        },
      },
      {
        languageOptions: {
          parserOptions: {
            projectService: true,
            tsconfigRootDir: import.meta.dirname,
          },
        },
      },
    );
  7. Build Swift modules with build()

    main

    The build function orchestrates the compilation of Swift packages into .node native modules. It supports both swiftpm (default) and xcode builders. It automatically handles platform-specific linking (e.g., .dylib on macOS, .so on Linux, .dll on Windows) and renames the resulting binary to the required .node extension.

    Key behaviors:

    • Mode: Accepts release or debug modes.
    • Product Selection: If config.product is not specified, it attempts to find a single dynamic library product in the Swift package. If multiple exist, you must specify the product name.
    • Static vs Dynamic: Use config.static: true to build a static library; otherwise, it defaults to dynamic.
    • N-API Support: You can pass a napi version (number) or `
  8. Clean build artifacts with clean()

    main

    The clean function removes the build directory to ensure a fresh state. By default, it removes the .build directory in the current working directory.

    import { clean } from './builder';
    
    // Cleans the default .build directory
    await clean();
    
    // Cleans a specific directory
    await clean({ buildPath: './custom-build-dir' });
    import { clean } from './builder';
    
    await clean({ buildPath: './custom-build-dir' });
  9. Use the node-swift CLI

    main

    The node-swift CLI tool is used to manage the build process for Swift projects within a Node.js environment. It supports building in different modes, cleaning build artifacts, or performing a full rebuild.

    By default, running the command without arguments triggers a rebuild.

    Available Commands:

    • rebuild: Cleans existing build artifacts and then performs a new build.
    • build: Performs a new build.
    • clean: Removes existing build artifacts.

    Global Flags:

    • --debug: When used with build or rebuild, switches the build mode from release to debug.
    # Default behavior (rebuild in release mode)
    node-swift
    
    # Build in release mode
    node-swift build
    
    # Build in debug mode
    node-swift build --debug
    
    # Full rebuild in debug mode
    node-swift rebuild --debug
    
    # Clean build artifacts
    node-swift clean
  10. Use SwiftPMBuilder for Swift Package Manager builds

    main

    When using the default swiftpm builder, you can pass specific settings to the swift build command.

    Properties:

    • type: Must be "swiftpm".
    • settings (string | string[]): Additional flags passed directly to swift build.
    • triple (string): The target triple (e.g., arm64-apple-macosx11.0). Warning: Cross-compilation triples may break macros in Swift 5.9+.
    const swiftPMBuilder: SwiftPMBuilder = {
      type: 'swiftpm',
      settings: ['--enable-test'],
      triple: 'aarch64-apple-macosx11.0'
    };
  11. Use XcodeBuilder for Xcode-based builds

    main

    If you need to build using xcodebuild (e.g., for specific macOS environment requirements), use the XcodeBuilder configuration.

    Properties:

    • type: Must be "xcode".
    • settings (string | string[]): Flags passed directly to xcodebuild.
    • destinations (string[]): The -destination parameters for xcodebuild (e.g., ['generic/platform=macOS']).
    const xcodeBuilder: XcodeBuilder = {
      type: 'xcode',
      settings: ['-Xdeployment-target', '13.0'],
      destinations: ['generic/platform=macOS']
    };