Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: swiftwasm/JavaScriptKit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.25.0
Choose a base ref
...
head repository: swiftwasm/JavaScriptKit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Mar 25, 2025

  1. Copy the full SHA
    6eb534b View commit details
  2. Copy the full SHA
    5c7e75e View commit details
  3. Copy the full SHA
    0d7d3de View commit details
  4. Make JSObject conform to ExpressibleByDictionaryLiteral directly

    kateinoigakukun committed Mar 25, 2025
    Copy the full SHA
    ec0bbe6 View commit details
  5. Copy the full SHA
    bb47d01 View commit details
  6. Copy the full SHA
    0581f76 View commit details
  7. make regenerate_swiftpm_resources

    kateinoigakukun committed Mar 25, 2025
    Copy the full SHA
    90d7238 View commit details

Commits on Mar 26, 2025

  1. Add JSTypedArray.copyMemory(to:) method

    This method allows copying the contents of a typed array to a Swift
    memory buffer.
    kateinoigakukun committed Mar 26, 2025
    Copy the full SHA
    2e7aa2f View commit details
  2. Merge pull request #315 from swiftwasm/yt/add-copy-memory

    Add `JSTypedArray.copyMemory(to:)` method
    kateinoigakukun authored Mar 26, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c0ddc7d View commit details
  3. PackageToJS: Inherit swift package -c configuration by default

    #309 introduced `-c`
    option to the plugin side in addition to the `swift package`'s one.
    However `swift package -c release js` was building with release config
    before 0.25.0 but it started to build with debug config as the default
    in 0.25.0. This change makes the plugin to respect the `swift package`'s
    one if `-c` is not specified after `js` plugin name
    kateinoigakukun committed Mar 26, 2025
    Copy the full SHA
    3fa6555 View commit details
  4. Merge pull request #316 from swiftwasm/yt/inherit-config-by-default

    PackageToJS: Inherit `swift package -c` configuration by default
    kateinoigakukun authored Mar 26, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    334f297 View commit details

Commits on Mar 27, 2025

  1. Merge pull request #312 from swiftwasm/maxd/jsdictionary

    Make `JSObject` conform to `ExpressibleByDictionaryLiteral`
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    cdbac17 View commit details
  2. Merge pull request #313 from swiftwasm/maxd/jsstring-elementsequal

    Use JS's `==` operator for `JSString` equality comparison
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    fe6d2e5 View commit details
  3. Copy the full SHA
    4c7ea17 View commit details
  4. Add an example of using TypedArray in an embedded app

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    f41f234 View commit details
  5. Fix JSTypedArrayTests to follow API change

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    af86aee View commit details
  6. Remove possible use of fatalError in JSTypedArray

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    e99b990 View commit details
  7. Merge pull request #317 from swiftwasm/katei/embedded-typed-array

    Unlock `JSTypedArray` for Embedded Swift
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    9b48693 View commit details
  8. Fix potential use-after-free in JSString

    The guts' lifetime was not guaranteed to be longer than
    `swjs_value_equals` call, which could lead to a use-after-free.
    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    a4376e3 View commit details
  9. Merge pull request #318 from swiftwasm/katei/fix-potential-uaf-jsstring

    Fix potential use-after-free in JSString
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    b006cf6 View commit details
  10. Copy the full SHA
    bb6fad8 View commit details
  11. Copy the full SHA
    f9b3973 View commit details
  12. Copy the full SHA
    64fb506 View commit details
  13. Detect Embedded build mode by compilation condition

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    047e5a6 View commit details
  14. Merge pull request #319 from swiftwasm/katei/package-trait

    Use package-trait to enable Embedded specific options
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    f52dc50 View commit details
  15. Enable Embedded feature for more modules

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    b77d015 View commit details
  16. Copy the full SHA
    6105c33 View commit details
  17. Merge pull request #320 from swiftwasm/katei/embedded-more-modules

    Enable `Embedded` feature for more modules
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    6224238 View commit details
  18. Make JSValue's subscript setter nonmutating

    Close #132
    
    Co-authored-by: Casper Zandbergen <info@casperzandbergen.nl>
    kateinoigakukun and Amzd committed Mar 27, 2025
    Copy the full SHA
    7e7fe97 View commit details
  19. Update examples and tests to use let for JSValue

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    ff7ca3a View commit details
  20. Merge pull request #321 from swiftwasm/katei/nonmutating-subscript-set

    Make JSValue's subscript setter nonmutating
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    2989d8d View commit details
  21. 'async' modifier cannot be used in an ambient context.

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    5149b77 View commit details
  22. Merge pull request #322 from swiftwasm/katei/async-dts

    'async' modifier cannot be used in an ambient context.
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    55ca25a View commit details
  23. Place the runtime JS files beside the template files

    So that we can remove @ts-ignore hacks in the template files.
    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    397e57c View commit details
  24. Put the runtime file symlinks to keep compatibility with the legacy mode

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    5d69e81 View commit details
  25. Merge pull request #323 from swiftwasm/katei/move-runtime-files-template

    Place the runtime JS files beside the template files
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    fdeefea View commit details
  26. Copy the full SHA
    73c972e View commit details
  27. Copy the full SHA
    d14dffb View commit details
  28. Copy the full SHA
    6557767 View commit details
  29. Change SwiftRuntime.wasmImports to return WebAssembly.ModuleImports

    kateinoigakukun committed Mar 27, 2025
    Copy the full SHA
    8df1a9d View commit details
  30. Merge pull request #324 from swiftwasm/katei/move-runtime-files-template

    Apply tsc check for `Plugins/PackageToJS/Templates` files
    kateinoigakukun authored Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c7d9fe0 View commit details

Commits on Mar 28, 2025

  1. Copy the full SHA
    f32f3bc View commit details
  2. Revert "Update Examples/Embedded to use the new Embedded trait"

    This reverts commit 64fb506.
    kateinoigakukun committed Mar 28, 2025
    Copy the full SHA
    8d8dada View commit details
  3. Merge pull request #325 from swiftwasm/katei/revert-trait

    Effectively revert the trait-based manifest change
    kateinoigakukun authored Mar 28, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c882581 View commit details

Commits on Mar 31, 2025

  1. docs: Add a guide on deploying with Vite

    kateinoigakukun committed Mar 31, 2025
    Copy the full SHA
    f591be4 View commit details
  2. Merge pull request #327 from swiftwasm/katei/vite-build

    docs: Add a guide on deploying with Vite
    kateinoigakukun authored Mar 31, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    4546366 View commit details
  3. Fix node version diagnostic handling on test harness

    The CompileError usually happens during `defaultNodeSetup`, so we
    should catch it there. Also `process.version` is a string with a `v`
    prefix, so we should use `process.versions.node`, which doesn't have
    the prefix instead.
    kateinoigakukun committed Mar 31, 2025
    Copy the full SHA
    fccfd97 View commit details

Commits on Apr 1, 2025

  1. Merge pull request #328 from swiftwasm/katei/fix-node-version-hint

    Fix node version diagnostic handling on test harness
    kateinoigakukun authored Apr 1, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    bad9706 View commit details
  2. build: Fix native build for missing symbol

    ```
    $s13JavaScriptKit8JSObjectC2idACs6UInt32V_tcfc: error: undefined reference to 'swjs_get_worker_thread_id_cached'
    ```
    kateinoigakukun committed Apr 1, 2025
    Copy the full SHA
    c80eed3 View commit details
  3. CI: Ensure that linking works correctly for native targets

    kateinoigakukun committed Apr 1, 2025
    Copy the full SHA
    4709005 View commit details
Showing with 14,793 additions and 4,815 deletions.
  1. +7 −0 .editorconfig
  2. +0 −21 .github/workflows/perf.yml
  3. +46 −4 .github/workflows/test.yml
  4. +1 −0 .gitignore
  5. +30 −0 Benchmarks/Package.swift
  6. +30 −0 Benchmarks/README.md
  7. +98 −0 Benchmarks/Sources/Benchmarks.swift
  8. +17 −0 Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift
  9. +56 −0 Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift
  10. +23 −0 Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json
  11. +67 −0 Benchmarks/Sources/Generated/JavaScript/BridgeJS.ImportTS.json
  12. +1 −0 Benchmarks/Sources/bridge-js.config.json
  13. +3 −0 Benchmarks/Sources/bridge-js.d.ts
  14. +1 −0 Benchmarks/package.json
  15. +449 −0 Benchmarks/run.js
  16. +9 −10 Examples/ActorOnWebWorker/Sources/MyApp.swift
  17. +3 −1 Examples/ActorOnWebWorker/build.sh
  18. +3 −3 Examples/Basic/Sources/main.swift
  19. +2 −2 Examples/Basic/build.sh
  20. +36 −5 Examples/Embedded/Sources/EmbeddedApp/main.swift
  21. +1 −0 Examples/Embedded/build.sh
  22. +25 −0 Examples/ExportSwift/Package.swift
  23. +16 −0 Examples/ExportSwift/README.md
  24. +34 −0 Examples/ExportSwift/Sources/main.swift
  25. +12 −0 Examples/ExportSwift/index.html
  26. +14 −0 Examples/ExportSwift/index.js
  27. +29 −0 Examples/ImportTS/Package.swift
  28. +24 −0 Examples/ImportTS/Sources/bridge-js.d.ts
  29. +26 −0 Examples/ImportTS/Sources/main.swift
  30. +16 −0 Examples/ImportTS/index.html
  31. +13 −0 Examples/ImportTS/index.js
  32. +10 −1 Examples/Multithreading/Package.resolved
  33. +0 −1 Examples/Multithreading/Sources/MyApp/main.swift
  34. +3 −1 Examples/Multithreading/build.sh
  35. +0 −1 Examples/OffscrenCanvas/Sources/MyApp/main.swift
  36. +3 −1 Examples/OffscrenCanvas/build.sh
  37. +45 −0 Examples/PlayBridgeJS/Package.swift
  38. +9 −0 Examples/PlayBridgeJS/README.md
  39. +165 −0 Examples/PlayBridgeJS/Sources/JavaScript/app.js
  40. +250 −0 Examples/PlayBridgeJS/Sources/JavaScript/editor.js
  41. +14 −0 Examples/PlayBridgeJS/Sources/JavaScript/index.js
  42. +1 −0 Examples/PlayBridgeJS/Sources/JavaScript/processor.js
  43. +269 −0 Examples/PlayBridgeJS/Sources/JavaScript/styles.css
  44. +1 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSCore
  45. +1 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSLink
  46. +1 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSSkeleton
  47. +115 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift
  48. +59 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift
  49. +124 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json
  50. +48 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ImportTS.json
  51. 0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json
  52. +5 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.d.ts
  53. +73 −0 Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift
  54. +3 −0 Examples/PlayBridgeJS/build.sh
  55. +90 −0 Examples/PlayBridgeJS/index.html
  56. +5 −0 Examples/Testing/package.json
  57. +0 −36 IntegrationTests/Makefile
  58. +0 −5 IntegrationTests/TestSuites/.gitignore
  59. +0 −24 IntegrationTests/TestSuites/Package.swift
  60. +0 −19 IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift
  61. +0 −85 IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift
  62. +0 −4 IntegrationTests/TestSuites/Sources/CHelpers/helpers.c
  63. +0 −10 IntegrationTests/TestSuites/Sources/CHelpers/include/helpers.h
  64. +0 −4 IntegrationTests/TestSuites/Sources/CHelpers/include/module.modulemap
  65. +0 −70 IntegrationTests/bin/benchmark-tests.js
  66. +0 −86 IntegrationTests/lib.js
  67. +0 −86 IntegrationTests/package-lock.json
  68. +0 −8 IntegrationTests/package.json
  69. +4 −24 Makefile
  70. +85 −9 Package.swift
  71. +30 −0 Plugins/BridgeJS/Package.swift
  72. +138 −0 Plugins/BridgeJS/README.md
  73. +100 −0 Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift
  74. +210 −0 Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift
  75. +7 −0 Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift
  76. +23 −0 Plugins/BridgeJS/Sources/BridgeJSCore/DiagnosticError.swift
  77. +693 −0 Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
  78. +506 −0 Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
  79. +19 −0 Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift
  80. +112 −0 Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift
  81. +693 −0 Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
  82. +120 −0 Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift
  83. +1 −0 Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore
  84. +1 −0 Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton
  85. +305 −0 Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift
  86. +1 −0 Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton
  87. +3 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/README.md
  88. +14 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/bin/ts2skeleton.js
  89. +9 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json
  90. +151 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js
  91. +44 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts
  92. +414 −0 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js
  93. +112 −0 Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift
  94. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore
  95. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink
  96. +77 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift
  97. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton
  98. +55 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift
  99. +31 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift
  100. +3 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/ArrayParameter.d.ts
  101. +6 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Interface.d.ts
  102. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PrimitiveParameters.d.ts
  103. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PrimitiveParameters.swift
  104. +2 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PrimitiveReturn.d.ts
  105. +4 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PrimitiveReturn.swift
  106. +2 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringParameter.d.ts
  107. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringParameter.swift
  108. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringReturn.d.ts
  109. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringReturn.swift
  110. +17 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift
  111. +3 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Throws.swift
  112. +3 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeAlias.d.ts
  113. +7 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TypeScriptClass.d.ts
  114. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/VoidParameterVoidReturn.d.ts
  115. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/VoidParameterVoidReturn.swift
  116. +43 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/SnapshotTesting.swift
  117. +1 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton
  118. +27 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/TemporaryDirectory.swift
  119. +20 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.d.ts
  120. +88 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js
  121. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts
  122. +94 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js
  123. +18 −0 .../BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.d.ts
  124. +69 −0 ...ns/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js
  125. +18 −0 .../BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.d.ts
  126. +74 −0 ...ns/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js
  127. +21 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.d.ts
  128. +82 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js
  129. +19 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.d.ts
  130. +85 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js
  131. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.d.ts
  132. +72 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js
  133. +19 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.d.ts
  134. +85 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js
  135. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.d.ts
  136. +72 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js
  137. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.d.ts
  138. +76 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js
  139. +32 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts
  140. +107 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js
  141. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.d.ts
  142. +75 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js
  143. +18 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.d.ts
  144. +74 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js
  145. +20 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts
  146. +123 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js
  147. +18 −0 ...dgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.d.ts
  148. +69 −0 ...ridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js
  149. +18 −0 ...dgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.d.ts
  150. +74 −0 ...ridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js
  151. +58 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json
  152. +17 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift
  153. +71 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json
  154. +51 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift
  155. +31 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json
  156. +21 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift
  157. +23 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json
  158. +20 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift
  159. +93 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json
  160. +65 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift
  161. +23 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json
  162. +31 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.swift
  163. +23 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json
  164. +17 −0 ...ins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.swift
  165. +52 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift
  166. +68 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift
  167. +22 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift
  168. +39 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift
  169. +45 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift
  170. +26 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift
  171. +22 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift
  172. +136 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift
  173. +22 −0 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift
  174. +17 −0 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Package.swift
  175. +5 −0 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/SwiftTesting/Tests/CheckTests.swift
  176. +17 −0 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Package.swift
  177. +7 −0 Plugins/PackageToJS/Fixtures/ContinuationLeakInTest/XCTest/Tests/CheckTests.swift
  178. +1 −0 Plugins/PackageToJS/Sources/BridgeJSLink
  179. +1 −0 Plugins/PackageToJS/Sources/BridgeJSSkeleton
  180. +59 −4 Plugins/PackageToJS/Sources/PackageToJS.swift
  181. +159 −22 Plugins/PackageToJS/Sources/PackageToJSPlugin.swift
  182. +42 −23 Plugins/PackageToJS/Templates/bin/test.js
  183. +8 −2 Plugins/PackageToJS/Templates/index.d.ts
  184. +11 −1 Plugins/PackageToJS/Templates/index.js
  185. +49 −11 Plugins/PackageToJS/Templates/instantiate.d.ts
  186. +30 −13 Plugins/PackageToJS/Templates/instantiate.js
  187. +6 −1 Plugins/PackageToJS/Templates/package.json
  188. +5 −2 Plugins/PackageToJS/Templates/platforms/browser.d.ts
  189. +5 −1 Plugins/PackageToJS/Templates/platforms/browser.js
  190. +1 −1 Plugins/PackageToJS/Templates/platforms/node.d.ts
  191. +1 −1 Plugins/PackageToJS/Templates/platforms/node.js
  192. +210 −0 Plugins/PackageToJS/Templates/runtime.d.ts
  193. +859 −0 Plugins/PackageToJS/Templates/runtime.mjs
  194. +9 −2 Plugins/PackageToJS/Templates/test.d.ts
  195. +3 −2 Plugins/PackageToJS/Templates/test.js
  196. +10 −0 Plugins/PackageToJS/Templates/tsconfig.json
  197. +60 −0 Plugins/PackageToJS/Tests/DefaultPackagingSystemTests.swift
  198. +79 −26 Plugins/PackageToJS/Tests/ExampleTests.swift
  199. +4 −0 Plugins/PackageToJS/Tests/PackagingPlannerTests.swift
  200. +20 −0 Plugins/PackageToJS/Tests/TemplatesTests.swift
  201. +12 −12 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planBuild_debug.json
  202. +12 −12 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planBuild_release.json
  203. +12 −12 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planBuild_release_dwarf.json
  204. +12 −12 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planBuild_release_name.json
  205. +12 −12 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planBuild_release_no_optimize.json
  206. +16 −16 Plugins/PackageToJS/Tests/__Snapshots__/PackagingPlannerTests/planTestBuild.json
  207. +0 −5 Runtime/rollup.config.mjs
  208. +131 −49 Runtime/src/index.ts
  209. +2 −2 Runtime/src/itc.ts
  210. +20 −20 Runtime/src/js-value.ts
  211. +0 −36 Runtime/src/memory.ts
  212. +10 −6 Runtime/src/object-heap.ts
  213. +0 −120 Runtime/src/types.ts
  214. +1 −0 Sources/JavaScriptEventLoop/JSSending.swift
  215. +92 −0 Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift
  216. +107 −0 Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift
  217. +37 −115 Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
  218. +1 −0 Sources/JavaScriptEventLoop/JobQueue.swift
  219. +4 −1 Sources/JavaScriptEventLoop/WebWorkerDedicatedExecutor.swift
  220. +101 −107 Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift
  221. +0 −5 Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift
  222. +42 −0 Sources/JavaScriptFoundationCompat/Data+JSValue.swift
  223. +27 −22 Sources/JavaScriptKit/BasicObjects/JSPromise.swift
  224. +50 −57 Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
  225. +74 −0 Sources/JavaScriptKit/BridgeJSInstrincics.swift
  226. +16 −35 Sources/JavaScriptKit/ConvertibleToJSValue.swift
  227. +177 −0 Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md
  228. +97 −0 Sources/JavaScriptKit/Documentation.docc/Articles/Deploying-Pages.md
  229. +164 −0 Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md
  230. +174 −0 Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md
  231. +12 −4 Sources/JavaScriptKit/Documentation.docc/Documentation.md
  232. +1 −1 Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Hello-World.tutorial
  233. +1 −2 Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-3-2-server.txt
  234. +2 −3 Sources/JavaScriptKit/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-3-3-open.txt
  235. +116 −20 Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
  236. +17 −2 Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
  237. +5 −10 Sources/JavaScriptKit/FundamentalObjects/JSString.swift
  238. +5 −0 Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
  239. +26 −2 Sources/JavaScriptKit/JSException.swift
  240. +2 −2 Sources/JavaScriptKit/JSValue.swift
  241. +35 −0 Sources/JavaScriptKit/Macros.swift
  242. +0 −262 Sources/JavaScriptKit/Runtime/index.d.ts
  243. +1 −0 Sources/JavaScriptKit/Runtime/index.d.ts
  244. +0 −829 Sources/JavaScriptKit/Runtime/index.js
  245. +0 −821 Sources/JavaScriptKit/Runtime/index.mjs
  246. +1 −0 Sources/JavaScriptKit/Runtime/index.mjs
  247. +16 −11 Sources/_CJavaScriptKit/_CJavaScriptKit.c
  248. +11 −0 Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h
  249. +45 −0 Sources/_CJavaScriptKit/include/WasmGlobalMacros.h
  250. +24 −0 Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
  251. +1 −0 Sources/_CJavaScriptKit/include/module.modulemap
  252. +86 −0 Tests/BridgeJSRuntimeTests/ExportAPITests.swift
  253. +363 −0 Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift
  254. +279 −0 Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift
  255. +422 −0 Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json
  256. +222 −0 Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json
  257. +87 −0 Tests/BridgeJSRuntimeTests/ImportAPITests.swift
  258. +1 −0 Tests/BridgeJSRuntimeTests/bridge-js.config.json
  259. +16 −0 Tests/BridgeJSRuntimeTests/bridge-js.d.ts
  260. +110 −0 Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift
  261. +3 −3 Tests/JavaScriptEventLoopTests/JSPromiseTests.swift
  262. +7 −7 Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift
  263. +193 −3 Tests/JavaScriptEventLoopTests/WebWorkerTaskExecutorTests.swift
  264. +55 −0 Tests/JavaScriptFoundationCompatTests/Data+JSValueTests.swift
  265. +153 −0 Tests/JavaScriptKitTests/JSClosureTests.swift
  266. +64 −0 Tests/JavaScriptKitTests/JSExceptionTests.swift
  267. +28 −0 Tests/JavaScriptKitTests/JSObjectTests.swift
  268. +13 −0 Tests/JavaScriptKitTests/JSStringTests.swift
  269. +22 −8 Tests/JavaScriptKitTests/JSTypedArrayTests.swift
  270. +1 −143 Tests/JavaScriptKitTests/JavaScriptKitTests.swift
  271. +134 −15 Tests/prelude.mjs
  272. +6 −0 Utilities/bridge-js-generate.sh
  273. +12 −0 Utilities/build-examples.sh
  274. +2 −1 Utilities/format.swift
  275. +0 −924 ci/perf-tester/package-lock.json
  276. +0 −9 ci/perf-tester/package.json
  277. +0 −212 ci/perf-tester/src/index.js
  278. +0 −221 ci/perf-tester/src/utils.js
  279. +31 −10 package-lock.json
  280. +3 −1 package.json
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
root = true

[*]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
21 changes: 0 additions & 21 deletions .github/workflows/perf.yml

This file was deleted.

50 changes: 46 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -16,12 +16,17 @@ jobs:
target: "wasm32-unknown-wasi"
- os: ubuntu-22.04
toolchain:
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
download-url: https://download.swift.org/swift-6.1-release/ubuntu2204/swift-6.1-RELEASE/swift-6.1-RELEASE-ubuntu22.04.tar.gz
wasi-backend: Node
target: "wasm32-unknown-wasi"
- os: ubuntu-22.04
toolchain:
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a-ubuntu22.04.tar.gz
wasi-backend: Node
target: "wasm32-unknown-wasi"
- os: ubuntu-22.04
toolchain:
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a-ubuntu22.04.tar.gz
wasi-backend: Node
target: "wasm32-unknown-wasip1-threads"

@@ -52,6 +57,7 @@ jobs:
make regenerate_swiftpm_resources
git diff --exit-code Sources/JavaScriptKit/Runtime
- run: swift test --package-path ./Plugins/PackageToJS
- run: swift test --package-path ./Plugins/BridgeJS

native-build:
# Check native build to make it easy to develop applications by Xcode
@@ -60,11 +66,11 @@ jobs:
matrix:
include:
- os: macos-15
xcode: Xcode_16
xcode: Xcode_16.4
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: swift build
- run: swift build --package-path ./Examples/Basic
env:
DEVELOPER_DIR: /Applications/${{ matrix.xcode }}.app/Contents/Developer/

@@ -82,3 +88,39 @@ jobs:
echo "::error::The formatting changed some files. Please run \`./Utilities/format.swift\` and commit the changes."
exit 1
}
build-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/install-swift
with:
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a-ubuntu22.04.tar.gz
- uses: swiftwasm/setup-swiftwasm@v2
id: setup-wasm32-unknown-wasi
with: { target: wasm32-unknown-wasi }
- uses: swiftwasm/setup-swiftwasm@v2
id: setup-wasm32-unknown-wasip1-threads
with: { target: wasm32-unknown-wasip1-threads }
- run: ./Utilities/build-examples.sh
env:
SWIFT_SDK_ID_wasm32_unknown_wasip1_threads: ${{ steps.setup-wasm32-unknown-wasip1-threads.outputs.swift-sdk-id }}
SWIFT_SDK_ID_wasm32_unknown_wasi: ${{ steps.setup-wasm32-unknown-wasi.outputs.swift-sdk-id }}
- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v3
with:
path: Examples/
deploy-examples:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build-examples
permissions:
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -10,3 +10,4 @@ xcuserdata/
Examples/*/Bundle
Examples/*/package-lock.json
Package.resolved
Plugins/BridgeJS/Sources/JavaScript/package-lock.json
30 changes: 30 additions & 0 deletions Benchmarks/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "Benchmarks",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
dependencies: [
.package(path: "../")
],
targets: [
.executableTarget(
name: "Benchmarks",
dependencies: [
"JavaScriptKit",
.product(name: "JavaScriptFoundationCompat", package: "JavaScriptKit"),
],
exclude: ["Generated/JavaScript", "bridge-js.d.ts"],
swiftSettings: [
.enableExperimentalFeature("Extern")
]
)
]
)
30 changes: 30 additions & 0 deletions Benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# JavaScriptKit Benchmarks

This directory contains performance benchmarks for JavaScriptKit.

## Building Benchmarks

Before running the benchmarks, you need to build the test suite:

```bash
JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk $SWIFT_SDK_ID js -c release
```

## Running Benchmarks

```bash
# Run with default settings
node run.js

# Save results to a JSON file
node run.js --output=results.json

# Specify number of iterations
node run.js --runs=20

# Run in adaptive mode until results stabilize
node run.js --adaptive --output=stable-results.json

# Run benchmarks and compare with previous results
node run.js --baseline=previous-results.json
```
98 changes: 98 additions & 0 deletions Benchmarks/Sources/Benchmarks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import JavaScriptKit
import JavaScriptFoundationCompat
import Foundation

class Benchmark {
init(_ title: String) {
self.title = title
}

let title: String

func testSuite(_ name: String, _ body: @escaping () -> Void) {
let jsBody = JSClosure { arguments -> JSValue in
body()
return .undefined
}
benchmarkRunner("\(title)/\(name)", jsBody)
}
}

@JS func run() {

let call = Benchmark("Call")

call.testSuite("JavaScript function call through Wasm import") {
for _ in 0..<20_000_000 {
benchmarkHelperNoop()
}
}

call.testSuite("JavaScript function call through Wasm import with int") {
for _ in 0..<10_000_000 {
benchmarkHelperNoopWithNumber(42)
}
}

let propertyAccess = Benchmark("Property access")

do {
let swiftInt: Double = 42
let object = JSObject()
object.jsNumber = JSValue.number(swiftInt)
propertyAccess.testSuite("Write Number") {
for _ in 0..<1_000_000 {
object.jsNumber = JSValue.number(swiftInt)
}
}
}

do {
let object = JSObject()
object.jsNumber = JSValue.number(42)
propertyAccess.testSuite("Read Number") {
for _ in 0..<1_000_000 {
_ = object.jsNumber.number
}
}
}

do {
let swiftString = "Hello, world"
let object = JSObject()
object.jsString = swiftString.jsValue
propertyAccess.testSuite("Write String") {
for _ in 0..<1_000_000 {
object.jsString = swiftString.jsValue
}
}
}

do {
let object = JSObject()
object.jsString = JSValue.string("Hello, world")
propertyAccess.testSuite("Read String") {
for _ in 0..<1_000_000 {
_ = object.jsString.string
}
}
}

do {
let conversion = Benchmark("Conversion")
let data = Data(repeating: 0, count: 10_000)
conversion.testSuite("Data to JSTypedArray") {
for _ in 0..<1_000_000 {
_ = data.jsTypedArray
}
}

let uint8Array = data.jsTypedArray

conversion.testSuite("JSTypedArray to Data") {
for _ in 0..<1_000_000 {
_ = Data.construct(from: uint8Array)
}
}
}
}
17 changes: 17 additions & 0 deletions Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
// DO NOT EDIT.
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(BridgeJS) import JavaScriptKit

@_expose(wasm, "bjs_run")
@_cdecl("bjs_run")
public func _bjs_run() -> Void {
#if arch(wasm32)
run()
#else
fatalError("Only available on WebAssembly")
#endif
}
56 changes: 56 additions & 0 deletions Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
// DO NOT EDIT.
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(BridgeJS) import JavaScriptKit

func benchmarkHelperNoop() throws(JSException) -> Void {
#if arch(wasm32)
@_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoop")
func bjs_benchmarkHelperNoop() -> Void
#else
func bjs_benchmarkHelperNoop() -> Void {
fatalError("Only available on WebAssembly")
}
#endif
bjs_benchmarkHelperNoop()
if let error = _swift_js_take_exception() {
throw error
}
}

func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void {
#if arch(wasm32)
@_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoopWithNumber")
func bjs_benchmarkHelperNoopWithNumber(_ n: Float64) -> Void
#else
func bjs_benchmarkHelperNoopWithNumber(_ n: Float64) -> Void {
fatalError("Only available on WebAssembly")
}
#endif
bjs_benchmarkHelperNoopWithNumber(n)
if let error = _swift_js_take_exception() {
throw error
}
}

func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void {
#if arch(wasm32)
@_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkRunner")
func bjs_benchmarkRunner(_ name: Int32, _ body: Int32) -> Void
#else
func bjs_benchmarkRunner(_ name: Int32, _ body: Int32) -> Void {
fatalError("Only available on WebAssembly")
}
#endif
var name = name
let nameId = name.withUTF8 { b in
_swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count))
}
bjs_benchmarkRunner(nameId, Int32(bitPattern: body.id))
if let error = _swift_js_take_exception() {
throw error
}
}
23 changes: 23 additions & 0 deletions Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"classes" : [

],
"functions" : [
{
"abiName" : "bjs_run",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"name" : "run",
"parameters" : [

],
"returnType" : {
"void" : {

}
}
}
]
}
67 changes: 67 additions & 0 deletions Benchmarks/Sources/Generated/JavaScript/BridgeJS.ImportTS.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"children" : [
{
"functions" : [
{
"name" : "benchmarkHelperNoop",
"parameters" : [

],
"returnType" : {
"void" : {

}
}
},
{
"name" : "benchmarkHelperNoopWithNumber",
"parameters" : [
{
"name" : "n",
"type" : {
"double" : {

}
}
}
],
"returnType" : {
"void" : {

}
}
},
{
"name" : "benchmarkRunner",
"parameters" : [
{
"name" : "name",
"type" : {
"string" : {

}
}
},
{
"name" : "body",
"type" : {
"jsObject" : {

}
}
}
],
"returnType" : {
"void" : {

}
}
}
],
"types" : [

]
}
],
"moduleName" : "Benchmarks"
}
1 change: 1 addition & 0 deletions Benchmarks/Sources/bridge-js.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions Benchmarks/Sources/bridge-js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare function benchmarkHelperNoop(): void;
declare function benchmarkHelperNoopWithNumber(n: number): void;
declare function benchmarkRunner(name: string, body: (n: number) => void): void;
1 change: 1 addition & 0 deletions Benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "type": "module" }
449 changes: 449 additions & 0 deletions Benchmarks/run.js

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions Examples/ActorOnWebWorker/Sources/MyApp.swift
Original file line number Diff line number Diff line change
@@ -120,13 +120,13 @@ final class App {
private let alert = JSObject.global.alert.function!

// UI elements
private var container: JSValue
private var urlInput: JSValue
private var indexButton: JSValue
private var searchInput: JSValue
private var searchButton: JSValue
private var statusElement: JSValue
private var resultsElement: JSValue
private let container: JSValue
private let urlInput: JSValue
private let indexButton: JSValue
private let searchInput: JSValue
private let searchButton: JSValue
private let statusElement: JSValue
private let resultsElement: JSValue

// Search service
private let service: SearchService
@@ -214,13 +214,13 @@ final class App {
resultsElement.innerHTML = .string("")

if results.isEmpty {
var noResults = document.createElement("p")
let noResults = document.createElement("p")
noResults.innerText = .string("No results found.")
_ = resultsElement.appendChild(noResults)
} else {
// Display up to 10 results
for (index, result) in results.prefix(10).enumerated() {
var resultItem = document.createElement("div")
let resultItem = document.createElement("div")
resultItem.style = .string(
"padding: 10px; margin: 5px 0; background: #f5f5f5; border-left: 3px solid blue;"
)
@@ -255,7 +255,6 @@ enum OwnedExecutor {

static func main() {
JavaScriptEventLoop.installGlobalExecutor()
WebWorkerTaskExecutor.installGlobalExecutor()
let useDedicatedWorker = !(JSObject.global.disableDedicatedWorker.boolean ?? false)

Task {
4 changes: 3 additions & 1 deletion Examples/ActorOnWebWorker/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
swift package --swift-sdk "${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}" -c release \
#!/bin/bash
set -euxo pipefail
swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasip1_threads:-${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}}" -c release \
plugin --allow-writing-to-package-directory \
js --use-cdn --output ./Bundle
6 changes: 3 additions & 3 deletions Examples/Basic/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -4,11 +4,11 @@ import JavaScriptKit
let alert = JSObject.global.alert.function!
let document = JSObject.global.document

var divElement = document.createElement("div")
let divElement = document.createElement("div")
divElement.innerText = "Hello, world"
_ = document.body.appendChild(divElement)

var buttonElement = document.createElement("button")
let buttonElement = document.createElement("button")
buttonElement.innerText = "Alert demo"
buttonElement.onclick = .object(
JSClosure { _ in
@@ -30,7 +30,7 @@ struct Response: Decodable {
let uuid: String
}

var asyncButtonElement = document.createElement("button")
let asyncButtonElement = document.createElement("button")
asyncButtonElement.innerText = "Fetch UUID demo"
asyncButtonElement.onclick = .object(
JSClosure { _ in
4 changes: 2 additions & 2 deletions Examples/Basic/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
set -ex
swift package --swift-sdk "${SWIFT_SDK_ID:-wasm32-unknown-wasi}" -c "${1:-debug}" js --use-cdn
set -euxo pipefail
swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasi:-${SWIFT_SDK_ID:-wasm32-unknown-wasi}}" -c "${1:-debug}" js --use-cdn
41 changes: 36 additions & 5 deletions Examples/Embedded/Sources/EmbeddedApp/main.swift
Original file line number Diff line number Diff line change
@@ -7,22 +7,53 @@ print("Hello from WASM, document title: \(document.title.string ?? "")")

var count = 0

var divElement = document.createElement("div")
let divElement = document.createElement("div")
divElement.innerText = .string("Count \(count)")
_ = document.body.appendChild(divElement)

var buttonElement = document.createElement("button")
buttonElement.innerText = "Click me"
buttonElement.onclick = JSValue.object(
let clickMeElement = document.createElement("button")
clickMeElement.innerText = "Click me"
clickMeElement.onclick = JSValue.object(
JSClosure { _ in
count += 1
divElement.innerText = .string("Count \(count)")
return .undefined
}
)
_ = document.body.appendChild(clickMeElement)

_ = document.body.appendChild(buttonElement)
let encodeResultElement = document.createElement("pre")
let textInputElement = document.createElement("input")
textInputElement.type = "text"
textInputElement.placeholder = "Enter text to encode to UTF-8"
textInputElement.oninput = JSValue.object(
JSClosure { _ in
let textEncoder = JSObject.global.TextEncoder.function!.new()
let encode = textEncoder.encode.function!
let encodedData = JSTypedArray<UInt8>(
unsafelyWrapping: encode(this: textEncoder, textInputElement.value).object!
)
encodeResultElement.innerText = .string(
encodedData.withUnsafeBytes { bytes in
bytes.map { hex($0) }.joined(separator: " ")
}
)
return .undefined
}
)
let encoderContainer = document.createElement("div")
_ = encoderContainer.appendChild(textInputElement)
_ = encoderContainer.appendChild(encodeResultElement)
_ = document.body.appendChild(encoderContainer)

func print(_ message: String) {
_ = JSObject.global.console.log(message)
}

func hex(_ value: UInt8) -> String {
var result = "0x"
let hexChars: [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]
result.append(hexChars[Int(value / 16)])
result.append(hexChars[Int(value % 16)])
return result
}
1 change: 1 addition & 0 deletions Examples/Embedded/build.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
set -euxo pipefail
package_dir="$(cd "$(dirname "$0")" && pwd)"
JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM=true \
swift package --package-path "$package_dir" \
25 changes: 25 additions & 0 deletions Examples/ExportSwift/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// swift-tools-version:6.0

import PackageDescription

let package = Package(
name: "MyApp",
platforms: [
.macOS(.v14)
],
dependencies: [.package(name: "JavaScriptKit", path: "../../")],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [
"JavaScriptKit"
],
swiftSettings: [
.enableExperimentalFeature("Extern")
],
plugins: [
.plugin(name: "BridgeJS", package: "JavaScriptKit")
]
)
]
)
16 changes: 16 additions & 0 deletions Examples/ExportSwift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ExportSwift Example
This example demonstrates how to export Swift functions to JavaScript.

## Building and Running

1. Build the project:
```sh
env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk $SWIFT_SDK_ID js --use-cdn
```

2. Serve the files:
```sh
npx serve
```

Then open your browser to `http://localhost:3000`.
34 changes: 34 additions & 0 deletions Examples/ExportSwift/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import JavaScriptKit

// Mark functions you want to export to JavaScript with the @JS attribute
// This function will be available as `renderCircleSVG(size)` in JavaScript
@JS public func renderCircleSVG(size: Int) -> String {
let strokeWidth = 3
let strokeColor = "black"
let fillColor = "red"
let cx = size / 2
let cy = size / 2
let r = (size / 2) - strokeWidth
var svg = "<svg width=\"\(size)px\" height=\"\(size)px\">"
svg +=
"<circle cx=\"\(cx)\" cy=\"\(cy)\" r=\"\(r)\" stroke=\"\(strokeColor)\" stroke-width=\"\(strokeWidth)\" fill=\"\(fillColor)\" />"
svg += "</svg>"
return svg
}

// Classes can also be exported using the @JS attribute
// This class will be available as a constructor in JavaScript: new Greeter("name")
@JS class Greeter {
var name: String

// Use @JS for initializers you want to expose
@JS init(name: String) {
self.name = name
}

// Methods need the @JS attribute to be accessible from JavaScript
// This method will be available as greeter.greet() in JavaScript
@JS public func greet() -> String {
"Hello, \(name)!"
}
}
12 changes: 12 additions & 0 deletions Examples/ExportSwift/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>

<head>
<title>Getting Started</title>
</head>

<body>
<script type="module" src="index.js"></script>
</body>

</html>
14 changes: 14 additions & 0 deletions Examples/ExportSwift/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
const { exports } = await init({});

const Greeter = exports.Greeter;
const greeter = new Greeter("World");
const circle = exports.renderCircleSVG(100);

// Display the results
const textOutput = document.createElement("div");
textOutput.innerText = greeter.greet()
document.body.appendChild(textOutput);
const circleOutput = document.createElement("div");
circleOutput.innerHTML = circle;
document.body.appendChild(circleOutput);
29 changes: 29 additions & 0 deletions Examples/ImportTS/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// swift-tools-version:6.0

import PackageDescription

let package = Package(
name: "MyApp",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
dependencies: [.package(name: "JavaScriptKit", path: "../../")],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [
"JavaScriptKit"
],
swiftSettings: [
.enableExperimentalFeature("Extern")
],
plugins: [
.plugin(name: "BridgeJS", package: "JavaScriptKit")
]
)
]
)
24 changes: 24 additions & 0 deletions Examples/ImportTS/Sources/bridge-js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Function definition to expose console.log to Swift
// Will be imported as a Swift function: consoleLog(message: String)
export function consoleLog(message: string): void

// TypeScript interface types are converted to Swift structs
// This defines a subset of the browser's HTMLElement interface
type HTMLElement = Pick<globalThis.HTMLElement, "innerText"> & {
// Methods with object parameters are properly handled
appendChild(child: HTMLElement): void
}

// TypeScript object type with read-only properties
// Properties will become Swift properties with appropriate access level
type Document = {
// Regular property - will be read/write in Swift
title: string
// Read-only property - will be read-only in Swift
readonly body: HTMLElement
// Method returning an object - will become a Swift method returning an HTMLElement
createElement(tagName: string): HTMLElement
}
// Function returning a complex object
// Will be imported as a Swift function: getDocument() -> Document
export function getDocument(): Document
26 changes: 26 additions & 0 deletions Examples/ImportTS/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import JavaScriptKit

// This function is automatically generated by the @JS plugin
// It demonstrates how to use TypeScript functions and types imported from bridge-js.d.ts
@JS public func run() {
// Call the imported consoleLog function defined in bridge-js.d.ts
consoleLog("Hello, World!")

// Get the document object - this comes from the imported getDocument() function
let document = getDocument()

// Access and modify properties - the title property is read/write
document.title = "Hello, World!"

// Access read-only properties - body is defined as readonly in TypeScript
let body = document.body

// Create a new element using the document.createElement method
let h1 = document.createElement("h1")

// Set properties on the created element
h1.innerText = "Hello, World!"

// Call methods on objects - appendChild is defined in the HTMLElement interface
body.appendChild(h1)
}
16 changes: 16 additions & 0 deletions Examples/ImportTS/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>

<head>
<title>Getting Started</title>
</head>

<body>
<script type="module" src="index.js"></script>

<div id="exports-result"></div>
<div id="imports-result"></div>
<pre id="code"></pre>
</body>

</html>
13 changes: 13 additions & 0 deletions Examples/ImportTS/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
const { exports } = await init({
imports: {
consoleLog: (message) => {
console.log(message);
},
getDocument: () => {
return document;
},
}
});

exports.run()
11 changes: 10 additions & 1 deletion Examples/Multithreading/Package.resolved
1 change: 0 additions & 1 deletion Examples/Multithreading/Sources/MyApp/main.swift
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ import JavaScriptEventLoop
import JavaScriptKit

JavaScriptEventLoop.installGlobalExecutor()
WebWorkerTaskExecutor.installGlobalExecutor()

func renderInCanvas(ctx: JSObject, image: ImageView) {
let imageData = ctx.createImageData!(image.width, image.height).object!
4 changes: 3 additions & 1 deletion Examples/Multithreading/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
swift package --swift-sdk "${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}" -c release \
#!/bin/bash
set -euxo pipefail
swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasip1_threads:-${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}}" -c release \
plugin --allow-writing-to-package-directory \
js --use-cdn --output ./Bundle
1 change: 0 additions & 1 deletion Examples/OffscrenCanvas/Sources/MyApp/main.swift
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import JavaScriptEventLoop
import JavaScriptKit

JavaScriptEventLoop.installGlobalExecutor()
WebWorkerTaskExecutor.installGlobalExecutor()

protocol CanvasRenderer {
func render(canvas: JSObject, size: Int) async throws
4 changes: 3 additions & 1 deletion Examples/OffscrenCanvas/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
swift package --swift-sdk "${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}" -c release \
#!/bin/bash
set -euxo pipefail
swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasip1_threads:-${SWIFT_SDK_ID:-wasm32-unknown-wasip1-threads}}" -c release \
plugin --allow-writing-to-package-directory \
js --use-cdn --output ./Bundle
45 changes: 45 additions & 0 deletions Examples/PlayBridgeJS/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// swift-tools-version:6.0

import PackageDescription

let package = Package(
name: "PlayBridgeJS",
platforms: [
.macOS(.v14)
],
dependencies: [
.package(name: "JavaScriptKit", path: "../../"),
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"),
],
targets: [
.executableTarget(
name: "PlayBridgeJS",
dependencies: [
"JavaScriptKit",
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
],
exclude: [
"bridge-js.d.ts",
"bridge-js.config.json",
"Generated/JavaScript",
],
swiftSettings: [
.enableExperimentalFeature("Extern")
],
linkerSettings: [
.unsafeFlags(
[
"-Xlinker", "--stack-first",
"-Xlinker", "--global-base=524288",
"-Xlinker", "-z", "-Xlinker", "stack-size=524288",
],
.when(platforms: [.wasi])
)
]
)
]
)
9 changes: 9 additions & 0 deletions Examples/PlayBridgeJS/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Playground for BridgeJS example

Install Development Snapshot toolchain `DEVELOPMENT-SNAPSHOT-2024-07-08-a` from [swift.org/install](https://www.swift.org/install/) and run the following commands:

```sh
$ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip
$ ./build.sh
$ npx serve
```
165 changes: 165 additions & 0 deletions Examples/PlayBridgeJS/Sources/JavaScript/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// BridgeJS Playground Main Application
import { EditorSystem } from './editor.js';
import ts from 'typescript';
import { TypeProcessor } from './processor.js';

export class BridgeJSPlayground {
constructor() {
this.editorSystem = new EditorSystem();
this.playBridgeJS = null;
this.generateTimeout = null;
this.isInitialized = false;

// DOM Elements
this.errorDisplay = document.getElementById('errorDisplay');
this.errorMessage = document.getElementById('errorMessage');
}

// Initialize the application
async initialize() {
if (this.isInitialized) {
return;
}

try {
// Initialize editor system
await this.editorSystem.init();

// Initialize BridgeJS
await this.initializeBridgeJS();

// Set up event listeners
this.setupEventListeners();

// Load sample code
this.editorSystem.loadSampleCode();

this.isInitialized = true;
console.log('BridgeJS Playground initialized successfully');
} catch (error) {
console.error('Failed to initialize BridgeJS Playground:', error);
this.showError('Failed to initialize application: ' + error.message);
}
}

// Initialize BridgeJS
async initializeBridgeJS() {
try {
// Import the BridgeJS module
const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js");
const { exports } = await init({
imports: {
createTS2Skeleton: this.createTS2Skeleton
}
});
this.playBridgeJS = new exports.PlayBridgeJS();
console.log('BridgeJS initialized successfully');
} catch (error) {
console.error('Failed to initialize BridgeJS:', error);
throw new Error('BridgeJS initialization failed: ' + error.message);
}
}

// Set up event listeners
setupEventListeners() {
// Add change listeners for real-time generation
this.editorSystem.addChangeListeners(() => {
// Debounce generation to avoid excessive calls
if (this.generateTimeout) {
clearTimeout(this.generateTimeout);
}
this.generateTimeout = setTimeout(() => this.generateCode(), 300);
});
}

createTS2Skeleton() {
return {
convert: (dtsCode) => {
const virtualFilePath = "bridge-js.d.ts"
const virtualHost = {
fileExists: fileName => fileName === virtualFilePath,
readFile: fileName => dtsCode,
getSourceFile: (fileName, languageVersion) => {
const sourceText = dtsCode;
if (sourceText === undefined) return undefined;
return ts.createSourceFile(fileName, sourceText, languageVersion);
},
getDefaultLibFileName: options => "lib.d.ts",
writeFile: (fileName, data) => {
console.log(`[emit] ${fileName}:\n${data}`);
},
getCurrentDirectory: () => "",
getDirectories: () => [],
getCanonicalFileName: fileName => fileName,
getNewLine: () => "\n",
useCaseSensitiveFileNames: () => true
}
// Create TypeScript program from d.ts content
const tsProgram = ts.createProgram({
rootNames: [virtualFilePath],
host: virtualHost,
options: {
noEmit: true,
declaration: true,
}
})

// Create diagnostic engine for error reporting
const diagnosticEngine = {
print: (level, message, node) => {
console.log(`[${level}] ${message}`);
if (level === 'error') {
this.showError(`TypeScript Error: ${message}`);
}
}
};

// Process the TypeScript definitions to generate skeleton
const processor = new TypeProcessor(tsProgram.getTypeChecker(), diagnosticEngine);

const skeleton = processor.processTypeDeclarations(tsProgram, virtualFilePath);

return JSON.stringify(skeleton);
}
}
}

// Generate code through BridgeJS
async generateCode() {
if (!this.playBridgeJS) {
this.showError('BridgeJS is not initialized');
return;
}

try {
this.hideError();

const inputs = this.editorSystem.getInputs();
const swiftCode = inputs.swift;
const dtsCode = inputs.dts;

// Process the code and get PlayBridgeJSOutput
const result = this.playBridgeJS.update(swiftCode, dtsCode);

// Update outputs using the PlayBridgeJSOutput object
this.editorSystem.updateOutputs(result);

console.log('Code generated successfully');

} catch (error) {
console.error('Error generating code:', error);
this.showError('Error generating code: ' + error.message);
}
}

// Show error message
showError(message) {
this.errorMessage.textContent = message;
this.errorDisplay.classList.add('show');
}

// Hide error message
hideError() {
this.errorDisplay.classList.remove('show');
}
}
250 changes: 250 additions & 0 deletions Examples/PlayBridgeJS/Sources/JavaScript/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
export class EditorSystem {
constructor() {
this.editors = new Map();
this.config = {
input: [
{
key: 'swift',
id: 'swiftEditor',
language: 'swift',
placeholder: '',
readOnly: false,
modelUri: 'Playground.swift'
},
{
key: 'dts',
id: 'dtsEditor',
language: 'typescript',
placeholder: '',
readOnly: false,
modelUri: 'bridge-js.d.ts'
}
],
output: [
{
key: 'dts-generated',
id: 'dtsOutput',
language: 'typescript',
placeholder: '// Generated TypeScript will appear here...',
readOnly: true,
modelUri: 'Playground.d.ts'
},
{
key: 'import-glue',
id: 'importGlueOutput',
language: 'swift',
placeholder: '// Import Swift Glue will appear here...',
readOnly: true,
modelUri: 'ImportTS.swift'
},
{
key: 'export-glue',
id: 'exportGlueOutput',
language: 'swift',
placeholder: '// Export Swift Glue will appear here...',
readOnly: true,
modelUri: 'ExportTS.swift'
},
{
key: 'js-generated',
id: 'jsOutput',
language: 'javascript',
placeholder: '// Generated JavaScript will appear here...',
readOnly: true,
modelUri: 'bridge-js.js'
}
]
};
this.activeTabs = {
input: this.config.input[0]?.key,
output: this.config.output[0]?.key
};
}

async init() {
await this.loadMonaco();
this.createEditors();
this.setupTabSystem();
this.setupResizeHandling();
}

async loadMonaco() {
return new Promise((resolve) => {
require.config({ paths: { vs: 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], resolve);
});
}

createEditors() {
const commonOptions = {
automaticLayout: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
fontFamily: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
lineNumbers: 'on',
roundedSelection: false,
scrollbar: { vertical: 'visible', horizontal: 'visible' },
fixedOverflowWidgets: true,
renderWhitespace: 'none',
wordWrap: 'on'
};

// Create all editors from config
[...this.config.input, ...this.config.output].forEach(config => {
const element = document.getElementById(config.id);
if (!element) {
console.warn(`Editor element not found: ${config.id}`);
return;
}

const model = monaco.editor.createModel(
config.placeholder,
config.language,
monaco.Uri.parse(config.modelUri)
);

const editor = monaco.editor.create(element, {
...commonOptions,
value: config.placeholder,
language: config.language,
readOnly: config.readOnly,
model: model
});

this.editors.set(config.key, editor);
});
}

setupTabSystem() {
// Setup tab listeners
[...this.config.input, ...this.config.output].forEach(config => {
const button = document.querySelector(`[data-tab="${config.key}"]`);
if (button) {
button.addEventListener('click', () => this.switchTab(config.key));
}
});

// Initial tab state
this.updateTabStates();
}

switchTab(tabKey) {
const config = this.getConfigByKey(tabKey);
if (!config) return;

if (this.config.input.some(c => c.key === tabKey)) {
this.activeTabs.input = tabKey;
} else {
this.activeTabs.output = tabKey;
}

this.updateTabStates();
this.updateLayout();
}

updateTabStates() {
// Update all tab buttons
[...this.config.input, ...this.config.output].forEach(config => {
const button = document.querySelector(`[data-tab="${config.key}"]`);
const content = document.getElementById(`${config.id}Tab`);

if (button) {
const isActive = config.key === this.activeTabs.input || config.key === this.activeTabs.output;
button.classList.toggle('active', isActive);
}

if (content) {
const isActive = config.key === this.activeTabs.input || config.key === this.activeTabs.output;
content.classList.toggle('active', isActive);
}
});
}

setupResizeHandling() {
const layoutEditor = (editor) => {
editor.layout({ width: 0, height: 0 });
window.requestAnimationFrame(() => {
const { width, height } = editor.getContainerDomNode().getBoundingClientRect();
editor.layout({ width, height });
});
};

window.addEventListener("resize", () => {
this.editors.forEach(editor => layoutEditor(editor));
});
}

// Data access
getInputs() {
return {
swift: this.editors.get('swift')?.getValue() || '',
dts: this.editors.get('dts')?.getValue() || ''
};
}

updateOutputs(result) {
const outputMap = {
'import-glue': () => result.importSwiftGlue(),
'export-glue': () => result.exportSwiftGlue(),
'js-generated': () => result.outputJs(),
'dts-generated': () => result.outputDts()
};

Object.entries(outputMap).forEach(([key, getContent]) => {
const editor = this.editors.get(key);
if (editor) {
const content = getContent();
editor.setValue(content || `// No ${key} output generated`);
}
});
}

loadSampleCode() {
const sampleSwift = `import JavaScriptKit
@JS public func calculateTotal(price: Double, quantity: Int) -> Double {
return price * Double(quantity)
}
@JS class ShoppingCart {
private var items: [(name: String, price: Double, quantity: Int)] = []
@JS init() {}
@JS public func addItem(name: String, price: Double, quantity: Int) {
items.append((name, price, quantity))
}
@JS public func getTotal() -> Double {
return items.reduce(0) { $0 + $1.price * Double($1.quantity) }
}
}`;

const sampleDts = `export type Console = {
log: (message: string) => void;
}
export function console(): Console;`;

this.editors.get('swift')?.setValue(sampleSwift);
this.editors.get('dts')?.setValue(sampleDts);
}

addChangeListeners(callback) {
this.config.input.forEach(config => {
const editor = this.editors.get(config.key);
if (editor) {
editor.onDidChangeModelContent(callback);
}
});
}

// Utility methods
getConfigByKey(key) {
return [...this.config.input, ...this.config.output].find(c => c.key === key);
}

getActiveTabs() {
return this.activeTabs;
}
}
14 changes: 14 additions & 0 deletions Examples/PlayBridgeJS/Sources/JavaScript/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @ts-check
import { BridgeJSPlayground } from './app.js';

Error.stackTraceLimit = Infinity;

// Initialize the playground when the page loads
document.addEventListener('DOMContentLoaded', async () => {
try {
const playground = new BridgeJSPlayground();
await playground.initialize();
} catch (error) {
console.error('Failed to initialize playground:', error);
}
});
1 change: 1 addition & 0 deletions Examples/PlayBridgeJS/Sources/JavaScript/processor.js
269 changes: 269 additions & 0 deletions Examples/PlayBridgeJS/Sources/JavaScript/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
:root {
--color-fill: rgb(255, 255, 255);
--color-fill-secondary: rgb(247, 247, 247);
--color-fill-tertiary: rgb(240, 240, 240);
--color-fill-blue: rgb(0, 0, 255);
--color-fill-gray: rgb(204, 204, 204);
--color-fill-gray-secondary: rgb(245, 245, 245);
--color-fill-green-secondary: rgb(240, 255, 240);
--color-fill-red-secondary: rgb(255, 240, 245);

--color-figure-blue: rgb(51, 102, 255);
--color-figure-gray: rgb(0, 0, 0);
--color-figure-gray-secondary: rgb(102, 102, 102);
--color-figure-green: rgb(0, 128, 0);
--color-figure-red: rgb(255, 0, 0);

--color-text: var(--color-figure-gray);
--color-text-background: var(--color-fill);
--color-secondary-label: var(--color-figure-gray-secondary);

--color-code-background: var(--color-fill-secondary);
--color-code-plain: var(--color-figure-gray);
--color-code-line-highlight: rgba(51, 102, 255, 0.08);
--color-code-line-highlight-border: var(--color-figure-blue);

--color-button-background: var(--color-fill-blue);
--color-button-background-hover: var(--color-figure-blue);
--color-button-text: var(--color-fill);

--color-form-error: var(--color-figure-red);
--color-form-error-background: var(--color-fill-red-secondary);
--color-form-valid: var(--color-figure-green);
--color-form-valid-background: var(--color-fill-green-secondary);

--color-border: var(--color-fill-gray);
--color-border-secondary: var(--color-fill-gray-secondary);

--color-focus-border-color: var(--color-fill-blue);
--color-focus-color: rgba(0, 125, 250, 0.6);
}

* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 0;
background-color: var(--color-fill);
color: var(--color-text);
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
height: 100vh;
overflow: hidden;
line-height: 1.5;
}

.container {
height: 100vh;
padding: 24px;
display: flex;
flex-direction: column;
max-width: 1400px;
margin: 0 auto;
}

.header {
margin-bottom: 32px;
text-align: center;
}

.header h1 {
margin: 0 0 8px 0;
color: var(--color-text);
font-size: 32px;
font-weight: 600;
letter-spacing: -0.025em;
}

.header p {
margin: 0;
color: var(--color-secondary-label);
font-size: 16px;
font-weight: 400;
}

.error-display {
margin-bottom: 24px;
padding: 16px;
background-color: var(--color-form-error-background);
border: 1px solid var(--color-form-error);
border-radius: 8px;
display: none;
color: var(--color-form-error);
}

.error-display.show {
display: block;
}

.error-display h3 {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
}

.error-display pre {
margin: 0;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
font-size: 14px;
white-space: pre-wrap;
word-break: break-word;
}

.main-content {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
min-height: 0;
}

.section {
display: flex;
flex-direction: column;
background-color: var(--color-fill);
border: 1px solid var(--color-border);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.section-header {
padding: 20px 24px 0 24px;
background-color: var(--color-fill-secondary);
border-bottom: 1px solid var(--color-border);
}

.section-header h2 {
margin: 0 0 16px 0;
font-size: 20px;
font-weight: 600;
letter-spacing: -0.025em;
}

.section-header h2.input-title {
color: var(--color-text);
}

.section-header h2.output-title {
color: var(--color-text);
}

.tab-group {
display: flex;
gap: 0;
margin: 0;
background-color: var(--color-fill-secondary);
border-bottom: 1px solid var(--color-border);
}

.tab-button {
padding: 12px 20px;
border: none;
background-color: transparent;
color: var(--color-secondary-label);
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
position: relative;
border-bottom: 2px solid transparent;
}

.tab-button:hover {
background-color: var(--color-fill-tertiary);
color: var(--color-text);
}

.tab-button.active {
background-color: var(--color-fill);
color: var(--color-text);
border-bottom-color: var(--color-figure-blue);
}

.tab-content {
flex: 1;
display: none;
background-color: var(--color-fill);
}

.tab-content.active {
display: block;
}

.editor-container {
height: 100%;
width: 100%;
min-height: 400px;
}

/* Monaco Editor overrides */
.editor-container .monaco-editor {
height: 100%;
width: 100%;
}

.editor-container .monaco-editor .margin {
background-color: var(--color-fill-secondary);
}

.editor-container .monaco-editor .monaco-editor-background {
background-color: var(--color-fill);
}

@media (max-width: 1024px) {
.container {
padding: 16px;
}

.main-content {
gap: 16px;
}

.section-header {
padding: 16px 20px 0 20px;
}

.tab-button {
padding: 10px 16px;
font-size: 13px;
}
}

@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
gap: 16px;
}

.container {
padding: 12px;
}

.header h1 {
font-size: 24px;
}

.header p {
font-size: 14px;
}

.tab-group {
flex-wrap: wrap;
}

.tab-button {
flex: 1;
min-width: 0;
text-align: center;
}

.section-header {
padding: 12px 16px 0 16px;
}

.section-header h2 {
font-size: 18px;
}
}
1 change: 1 addition & 0 deletions Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSCore
1 change: 1 addition & 0 deletions Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSLink
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
// DO NOT EDIT.
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(BridgeJS) import JavaScriptKit

@_expose(wasm, "bjs_PlayBridgeJS_init")
@_cdecl("bjs_PlayBridgeJS_init")
public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer {
#if arch(wasm32)
let ret = PlayBridgeJS()
return Unmanaged.passRetained(ret).toOpaque()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJS_update")
@_cdecl("bjs_PlayBridgeJS_update")
public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLen: Int32, dtsSourceBytes: Int32, dtsSourceLen: Int32) -> UnsafeMutableRawPointer {
#if arch(wasm32)
do {
let swiftSource = String(unsafeUninitializedCapacity: Int(swiftSourceLen)) { b in
_swift_js_init_memory(swiftSourceBytes, b.baseAddress.unsafelyUnwrapped)
return Int(swiftSourceLen)
}
let dtsSource = String(unsafeUninitializedCapacity: Int(dtsSourceLen)) { b in
_swift_js_init_memory(dtsSourceBytes, b.baseAddress.unsafelyUnwrapped)
return Int(dtsSourceLen)
}
let ret = try Unmanaged<PlayBridgeJS>.fromOpaque(_self).takeUnretainedValue().update(swiftSource: swiftSource, dtsSource: dtsSource)
return Unmanaged.passRetained(ret).toOpaque()
} catch let error {
if let error = error.thrownValue.object {
withExtendedLifetime(error) {
_swift_js_throw(Int32(bitPattern: $0.id))
}
} else {
let jsError = JSError(message: String(describing: error))
withExtendedLifetime(jsError.jsObject) {
_swift_js_throw(Int32(bitPattern: $0.id))
}
}
return UnsafeMutableRawPointer(bitPattern: -1).unsafelyUnwrapped
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJS_deinit")
@_cdecl("bjs_PlayBridgeJS_deinit")
public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) {
Unmanaged<PlayBridgeJS>.fromOpaque(pointer).release()
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs")
@_cdecl("bjs_PlayBridgeJSOutput_outputJs")
public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
var ret = Unmanaged<PlayBridgeJSOutput>.fromOpaque(_self).takeUnretainedValue().outputJs()
return ret.withUTF8 { ptr in
_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_outputDts")
@_cdecl("bjs_PlayBridgeJSOutput_outputDts")
public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
var ret = Unmanaged<PlayBridgeJSOutput>.fromOpaque(_self).takeUnretainedValue().outputDts()
return ret.withUTF8 { ptr in
_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_importSwiftGlue")
@_cdecl("bjs_PlayBridgeJSOutput_importSwiftGlue")
public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
var ret = Unmanaged<PlayBridgeJSOutput>.fromOpaque(_self).takeUnretainedValue().importSwiftGlue()
return ret.withUTF8 { ptr in
_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_exportSwiftGlue")
@_cdecl("bjs_PlayBridgeJSOutput_exportSwiftGlue")
public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
var ret = Unmanaged<PlayBridgeJSOutput>.fromOpaque(_self).takeUnretainedValue().exportSwiftGlue()
return ret.withUTF8 { ptr in
_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_deinit")
@_cdecl("bjs_PlayBridgeJSOutput_deinit")
public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) {
Unmanaged<PlayBridgeJSOutput>.fromOpaque(pointer).release()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
// DO NOT EDIT.
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(BridgeJS) import JavaScriptKit

func createTS2Skeleton() throws(JSException) -> TS2Skeleton {
#if arch(wasm32)
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_createTS2Skeleton")
func bjs_createTS2Skeleton() -> Int32
#else
func bjs_createTS2Skeleton() -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
let ret = bjs_createTS2Skeleton()
if let error = _swift_js_take_exception() {
throw error
}
return TS2Skeleton(takingThis: ret)
}

struct TS2Skeleton {
let this: JSObject

init(this: JSObject) {
self.this = this
}

init(takingThis this: Int32) {
self.this = JSObject(id: UInt32(bitPattern: this))
}

func convert(_ ts: String) throws(JSException) -> String {
#if arch(wasm32)
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_TS2Skeleton_convert")
func bjs_TS2Skeleton_convert(_ self: Int32, _ ts: Int32) -> Int32
#else
func bjs_TS2Skeleton_convert(_ self: Int32, _ ts: Int32) -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
var ts = ts
let tsId = ts.withUTF8 { b in
_swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count))
}
let ret = bjs_TS2Skeleton_convert(Int32(bitPattern: self.this.id), tsId)
if let error = _swift_js_take_exception() {
throw error
}
return String(unsafeUninitializedCapacity: Int(ret)) { b in
_swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret))
return Int(ret)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"classes" : [
{
"constructor" : {
"abiName" : "bjs_PlayBridgeJS_init",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [
{
"abiName" : "bjs_PlayBridgeJS_update",
"effects" : {
"isAsync" : false,
"isThrows" : true
},
"name" : "update",
"parameters" : [
{
"label" : "swiftSource",
"name" : "swiftSource",
"type" : {
"string" : {

}
}
},
{
"label" : "dtsSource",
"name" : "dtsSource",
"type" : {
"string" : {

}
}
}
],
"returnType" : {
"swiftHeapObject" : {
"_0" : "PlayBridgeJSOutput"
}
}
}
],
"name" : "PlayBridgeJS"
},
{
"methods" : [
{
"abiName" : "bjs_PlayBridgeJSOutput_outputJs",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"name" : "outputJs",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
},
{
"abiName" : "bjs_PlayBridgeJSOutput_outputDts",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"name" : "outputDts",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
},
{
"abiName" : "bjs_PlayBridgeJSOutput_importSwiftGlue",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"name" : "importSwiftGlue",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
},
{
"abiName" : "bjs_PlayBridgeJSOutput_exportSwiftGlue",
"effects" : {
"isAsync" : false,
"isThrows" : false
},
"name" : "exportSwiftGlue",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
}
],
"name" : "PlayBridgeJSOutput"
}
],
"functions" : [

]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"children" : [
{
"functions" : [
{
"name" : "createTS2Skeleton",
"parameters" : [

],
"returnType" : {
"jsObject" : {
"_0" : "TS2Skeleton"
}
}
}
],
"types" : [
{
"methods" : [
{
"name" : "convert",
"parameters" : [
{
"name" : "ts",
"type" : {
"string" : {

}
}
}
],
"returnType" : {
"string" : {

}
}
}
],
"name" : "TS2Skeleton",
"properties" : [

]
}
]
}
],
"moduleName" : "PlayBridgeJS"
}
Empty file.
5 changes: 5 additions & 0 deletions Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface TS2Skeleton {
convert(ts: string): string;
}

export function createTS2Skeleton(): TS2Skeleton;
73 changes: 73 additions & 0 deletions Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import JavaScriptEventLoop
import JavaScriptKit
import SwiftParser
import class Foundation.JSONDecoder

@JS class PlayBridgeJS {
@JS init() {}

@JS func update(swiftSource: String, dtsSource: String) throws(JSException) -> PlayBridgeJSOutput {
do {
return try _update(swiftSource: swiftSource, dtsSource: dtsSource)
} catch let error as JSException {
throw error
} catch {
throw JSException(message: String(describing: error))
}
}

func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput {
let exportSwift = ExportSwift(progress: .silent)
let sourceFile = Parser.parse(source: swiftSource)
try exportSwift.addSourceFile(sourceFile, "Playground.swift")
let exportResult = try exportSwift.finalize()
var importTS = ImportTS(progress: .silent, moduleName: "Playground")
let ts2skeleton = try createTS2Skeleton()
let skeletonJSONString = try ts2skeleton.convert(dtsSource)
let decoder = JSONDecoder()
let importSkeleton = try decoder.decode(
ImportedFileSkeleton.self,
from: skeletonJSONString.data(using: .utf8)!
)
importTS.addSkeleton(importSkeleton)
let importSwiftGlue = try importTS.finalize()

let linker = BridgeJSLink(
exportedSkeletons: exportResult.map { [$0.outputSkeleton] } ?? [],
importedSkeletons: [
ImportedModuleSkeleton(
moduleName: "Playground",
children: [importSkeleton]
)
],
sharedMemory: false
)
let linked = try linker.link()

return PlayBridgeJSOutput(
outputJs: linked.outputJs,
outputDts: linked.outputDts,
importSwiftGlue: importSwiftGlue ?? "",
exportSwiftGlue: exportResult?.outputSwift ?? ""
)
}
}

@JS class PlayBridgeJSOutput {
let _outputJs: String
let _outputDts: String
let _importSwiftGlue: String
let _exportSwiftGlue: String

init(outputJs: String, outputDts: String, importSwiftGlue: String, exportSwiftGlue: String) {
self._outputJs = outputJs
self._outputDts = outputDts
self._importSwiftGlue = importSwiftGlue
self._exportSwiftGlue = exportSwiftGlue
}

@JS func outputJs() -> String { self._outputJs }
@JS func outputDts() -> String { self._outputDts }
@JS func importSwiftGlue() -> String { self._importSwiftGlue }
@JS func exportSwiftGlue() -> String { self._exportSwiftGlue }
}
3 changes: 3 additions & 0 deletions Examples/PlayBridgeJS/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
set -euxo pipefail
env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk "${SWIFT_SDK_ID_wasm32_unknown_wasi:-${SWIFT_SDK_ID:-wasm32-unknown-wasi}}" -c "${1:-debug}" js --use-cdn
90 changes: 90 additions & 0 deletions Examples/PlayBridgeJS/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BridgeJS Playground</title>

<!-- Import Map for TypeScript and processor -->
<script type="importmap">
{
"imports": {
"typescript": "https://esm.sh/typescript@5.3.3"
}
}
</script>

<!-- Monaco Editor CDN -->
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>

<!-- Custom Styles -->
<link rel="stylesheet" href="./Sources/JavaScript/styles.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>BridgeJS Playground</h1>
<p>Interactive playground to preview bridged code generated by BridgeJS</p>
</header>



<!-- Error Display -->
<div id="errorDisplay" class="error-display">
<h3>Error</h3>
<pre id="errorMessage"></pre>
</div>

<!-- Main Layout -->
<div class="main-content">
<!-- Input Section -->
<div class="section">
<div class="section-header">
<h2 class="input-title">Input</h2>
</div>
<div class="tab-group">
<button class="tab-button active" data-tab="swift">Playground.swift</button>
<button class="tab-button" data-tab="dts">bridge-js.d.ts</button>
</div>
<div id="swiftEditorTab" class="tab-content active">
<div id="swiftEditor" class="editor-container"></div>
</div>
<div id="dtsEditorTab" class="tab-content">
<div id="dtsEditor" class="editor-container"></div>
</div>
</div>

<!-- Output Section -->
<div class="section">
<div class="section-header">
<h2 class="output-title">Output</h2>
</div>
<div class="tab-group">
<button class="tab-button active" data-tab="dts-generated">Playground.d.ts</button>
<button class="tab-button" data-tab="js-generated">Playground.js</button>
<button class="tab-button" data-tab="import-glue">ImportTS.swift</button>
<button class="tab-button" data-tab="export-glue">ExportSwift.swift</button>
</div>
<div id="importGlueOutputTab" class="tab-content active">
<div id="importGlueOutput" class="editor-container"></div>
</div>
<div id="exportGlueOutputTab" class="tab-content">
<div id="exportGlueOutput" class="editor-container"></div>
</div>
<div id="jsOutputTab" class="tab-content">
<div id="jsOutput" class="editor-container"></div>
</div>
<div id="dtsOutputTab" class="tab-content">
<div id="dtsOutput" class="editor-container"></div>
</div>
</div>
</div>
</div>

<!-- Scripts -->
<script type="module" src="./Sources/JavaScript/index.js"></script>
<script type="module" src="./Sources/JavaScript/editor.js"></script>
<script type="module" src="./Sources/JavaScript/app.js"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions Examples/Testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"playwright": "^1.52.0"
}
}
36 changes: 0 additions & 36 deletions IntegrationTests/Makefile

This file was deleted.

5 changes: 0 additions & 5 deletions IntegrationTests/TestSuites/.gitignore

This file was deleted.

24 changes: 0 additions & 24 deletions IntegrationTests/TestSuites/Package.swift

This file was deleted.

19 changes: 0 additions & 19 deletions IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift

This file was deleted.

85 changes: 0 additions & 85 deletions IntegrationTests/TestSuites/Sources/BenchmarkTests/main.swift

This file was deleted.

4 changes: 0 additions & 4 deletions IntegrationTests/TestSuites/Sources/CHelpers/helpers.c

This file was deleted.

10 changes: 0 additions & 10 deletions IntegrationTests/TestSuites/Sources/CHelpers/include/helpers.h

This file was deleted.

This file was deleted.

70 changes: 0 additions & 70 deletions IntegrationTests/bin/benchmark-tests.js

This file was deleted.

86 changes: 0 additions & 86 deletions IntegrationTests/lib.js

This file was deleted.

86 changes: 0 additions & 86 deletions IntegrationTests/package-lock.json

This file was deleted.

8 changes: 0 additions & 8 deletions IntegrationTests/package.json

This file was deleted.

28 changes: 4 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,42 +1,22 @@
MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST)))

SWIFT_SDK_ID ?= wasm32-unknown-wasi
SWIFT_BUILD_FLAGS := --swift-sdk $(SWIFT_SDK_ID)

.PHONY: bootstrap
bootstrap:
npm ci
npx playwright install

.PHONY: build
build:
swift build --triple wasm32-unknown-wasi
npm run build

.PHONY: unittest
unittest:
@echo Running unit tests
swift package --swift-sdk "$(SWIFT_SDK_ID)" \
env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk "$(SWIFT_SDK_ID)" \
--disable-sandbox \
-Xlinker --stack-first \
-Xlinker --global-base=524288 \
-Xlinker -z \
-Xlinker stack-size=524288 \
js test --prelude ./Tests/prelude.mjs

.PHONY: benchmark_setup
benchmark_setup:
SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" CONFIGURATION=release $(MAKE) -C IntegrationTests benchmark_setup

.PHONY: run_benchmark
run_benchmark:
SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" CONFIGURATION=release $(MAKE) -s -C IntegrationTests run_benchmark

.PHONY: perf-tester
perf-tester:
cd ci/perf-tester && npm ci
js test --prelude ./Tests/prelude.mjs -Xnode --expose-gc

.PHONY: regenerate_swiftpm_resources
regenerate_swiftpm_resources:
npm run build
cp Runtime/lib/index.js Runtime/lib/index.mjs Runtime/lib/index.d.ts Sources/JavaScriptKit/Runtime
cp Runtime/lib/index.mjs Plugins/PackageToJS/Templates/runtime.mjs
cp Runtime/lib/index.d.ts Plugins/PackageToJS/Templates/runtime.d.ts
94 changes: 85 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// swift-tools-version:6.0

import CompilerPluginSupport
import PackageDescription

// NOTE: needed for embedded customizations, ideally this will not be necessary at all in the future, or can be replaced with traits
@@ -9,12 +10,25 @@ let useLegacyResourceBundling =

let package = Package(
name: "JavaScriptKit",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
products: [
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
.library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]),
.library(name: "JavaScriptFoundationCompat", targets: ["JavaScriptFoundationCompat"]),
.library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]),
.plugin(name: "PackageToJS", targets: ["PackageToJS"]),
.plugin(name: "BridgeJS", targets: ["BridgeJS"]),
.plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]),
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"601.0.0")
],
targets: [
.target(
@@ -26,12 +40,14 @@ let package = Package(
? [
.unsafeFlags(["-fdeclspec"])
] : nil,
swiftSettings: shouldBuildForEmbedded
? [
.enableExperimentalFeature("Embedded"),
.enableExperimentalFeature("Extern"),
.unsafeFlags(["-Xfrontend", "-emit-empty-object-file"]),
] : nil
swiftSettings: [
.enableExperimentalFeature("Extern")
]
+ (shouldBuildForEmbedded
? [
.enableExperimentalFeature("Embedded"),
.unsafeFlags(["-Xfrontend", "-emit-empty-object-file"]),
] : [])
),
.target(name: "_CJavaScriptKit"),
.testTarget(
@@ -44,7 +60,12 @@ let package = Package(

.target(
name: "JavaScriptBigIntSupport",
dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"]
dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"],
swiftSettings: shouldBuildForEmbedded
? [
.enableExperimentalFeature("Embedded"),
.unsafeFlags(["-Xfrontend", "-emit-empty-object-file"]),
] : []
),
.target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]),
.testTarget(
@@ -54,7 +75,12 @@ let package = Package(

.target(
name: "JavaScriptEventLoop",
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"],
swiftSettings: shouldBuildForEmbedded
? [
.enableExperimentalFeature("Embedded"),
.unsafeFlags(["-Xfrontend", "-emit-empty-object-file"]),
] : []
),
.target(name: "_CJavaScriptEventLoop"),
.testTarget(
@@ -83,12 +109,62 @@ let package = Package(
"JavaScriptEventLoopTestSupport",
]
),
.target(
name: "JavaScriptFoundationCompat",
dependencies: [
"JavaScriptKit"
]
),
.testTarget(
name: "JavaScriptFoundationCompatTests",
dependencies: [
"JavaScriptFoundationCompat"
]
),
.plugin(
name: "PackageToJS",
capability: .command(
intent: .custom(verb: "js", description: "Convert a Swift package to a JavaScript package")
),
sources: ["Sources"]
path: "Plugins/PackageToJS/Sources"
),
.plugin(
name: "BridgeJS",
capability: .buildTool(),
dependencies: ["BridgeJSTool"],
path: "Plugins/BridgeJS/Sources/BridgeJSBuildPlugin"
),
.plugin(
name: "BridgeJSCommandPlugin",
capability: .command(
intent: .custom(verb: "bridge-js", description: "Generate bridging code"),
permissions: [.writeToPackageDirectory(reason: "Generate bridging code")]
),
dependencies: ["BridgeJSTool"],
path: "Plugins/BridgeJS/Sources/BridgeJSCommandPlugin"
),
.executableTarget(
name: "BridgeJSTool",
dependencies: [
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
],
path: "Plugins/BridgeJS/Sources/BridgeJSTool",
exclude: ["TS2Skeleton/JavaScript"]
),
.testTarget(
name: "BridgeJSRuntimeTests",
dependencies: ["JavaScriptKit"],
exclude: [
"bridge-js.config.json",
"bridge-js.d.ts",
"Generated/JavaScript",
],
swiftSettings: [
.enableExperimentalFeature("Extern")
]
),
]
)
30 changes: 30 additions & 0 deletions Plugins/BridgeJS/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 6.0

import PackageDescription

let coreDependencies: [Target.Dependency] = [
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
]

let package = Package(
name: "BridgeJS",
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1")
],
targets: [
.target(name: "BridgeJSBuildPlugin"),
.executableTarget(
name: "BridgeJSTool",
dependencies: coreDependencies
),
.testTarget(
name: "BridgeJSToolTests",
dependencies: coreDependencies,
exclude: ["__Snapshots__", "Inputs"]
),
]
)
138 changes: 138 additions & 0 deletions Plugins/BridgeJS/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# BridgeJS

> [!IMPORTANT]
> This feature is still experimental, and the API may change frequently. Use at your own risk with `JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1` environment variable.
> [!NOTE]
> This documentation is intended for JavaScriptKit developers, not JavaScriptKit users.
## Overview

BridgeJS provides easy interoperability between Swift and JavaScript/TypeScript. It enables:

1. **Importing TypeScript APIs into Swift**: Use TypeScript/JavaScript APIs directly from Swift code
2. **Exporting Swift APIs to JavaScript**: Make your Swift APIs available to JavaScript code

## Architecture Diagram

```mermaid
graph LR
E1 --> G3[ExportSwift.json]
subgraph ModuleA
A.swift --> E1[[bridge-js export]]
B.swift --> E1
E1 --> G1[ExportSwift.swift]
B1[bridge-js.d.ts]-->I1[[bridge-js import]]
I1 --> G2[ImportTS.swift]
end
I1 --> G4[ImportTS.json]
E2 --> G7[ExportSwift.json]
subgraph ModuleB
C.swift --> E2[[bridge-js export]]
D.swift --> E2
E2 --> G5[ExportSwift.swift]
B2[bridge-js.d.ts]-->I2[[bridge-js import]]
I2 --> G6[ImportTS.swift]
end
I2 --> G8[ImportTS.json]
G3 --> L1[[bridge-js link]]
G4 --> L1
G7 --> L1
G8 --> L1
L1 --> F1[bridge-js.js]
L1 --> F2[bridge-js.d.ts]
ModuleA -----> App[App.wasm]
ModuleB -----> App
App --> PKG[[PackageToJS]]
F1 --> PKG
F2 --> PKG
```

## Type Mapping

### Primitive Type Conversions

TBD

| Swift Type | JS Type | Wasm Core Type |
|:--------------|:-----------|:---------------|
| `Int` | `number` | `i32` |
| `UInt` | `number` | `i32` |
| `Int8` | `number` | `i32` |
| `UInt8` | `number` | `i32` |
| `Int16` | `number` | `i32` |
| `UInt16` | `number` | `i32` |
| `Int32` | `number` | `i32` |
| `UInt32` | `number` | `i32` |
| `Int64` | `bigint` | `i64` |
| `UInt64` | `bigint` | `i64` |
| `Float` | `number` | `f32` |
| `Double` | `number` | `f64` |
| `Bool` | `boolean` | `i32` |
| `Void` | `void` | - |
| `String` | `string` | `i32` |

## Type Modeling

TypeScript uses [structural subtyping](https://www.typescriptlang.org/docs/handbook/type-compatibility.html), but Swift doesn't directly offer it. We can't map every TypeScript type to Swift, so we made several give-ups and heuristics.

### `interface`

We intentionally don't simulate TS's `interface` with Swift's `protocol` even though they are quite similar for the following reasons:

* Adding a protocol conformance for each `interface` implementation adds binary size cost in debug build because it's not easy to DCE.
* No straightforward way to represent the use of `interface` type on the return type position of TS function. Which concrete type it should it be?
* For Embedded Swift, we should avoid use of existential type as much as possible.

Instead of simulating the subtyping-rule with Swift's `protocol`, we represent each `interface` with Swift's struct.
In this way, we lose implicit type coercion but it makes things simpler and clear.

TBD: Consider providing type-conversion methods to simulate subtyping rule like `func asIface()`

### Anonymous type literals

Swift offers a few non-nominal types, tuple and function types, but they are not enough to provide access to the underlying storage lazily. So we gave up importing them in typed way.

## ABI

This section describes the ABI contract used between JavaScript and Swift.
The ABI will not be stable, and not meant to be interposed by other tools.

### Parameter Passing

Parameter passing follows Wasm calling conventions, with custom handling for complex types like strings and objects.

TBD

### Return Values

TBD

## Future Work

- [ ] Struct on parameter or return type
- [ ] Throws functions
- [ ] Async functions
- [ ] Cast between TS interface
- [ ] Closure support
- [ ] Simplify constructor pattern
* https://github.com/ocsigen/ts2ocaml/blob/main/docs/js_of_ocaml.md#feature-immediate-constructor
```typescript
interface Foo = {
someMethod(value: number): void;
}

interface FooConstructor {
new(name: string) : Foo;

anotherMethod(): number;
}

declare var Foo: FooConstructor;
```
- [ ] Use `externref` once it's widely available
- [ ] Test SwiftObject roundtrip
100 changes: 100 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#if canImport(PackagePlugin)
import PackagePlugin
import Foundation

/// Build plugin for runtime code generation with BridgeJS.
/// This plugin automatically generates bridge code between Swift and JavaScript
/// during each build process.
@main
struct BridgeJSBuildPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
guard let swiftSourceModuleTarget = target as? SwiftSourceModuleTarget else {
return []
}
var commands: [Command] = []
commands.append(try createExportSwiftCommand(context: context, target: swiftSourceModuleTarget))
if let importCommand = try createImportTSCommand(context: context, target: swiftSourceModuleTarget) {
commands.append(importCommand)
}
return commands
}

private func pathToConfigFile(target: SwiftSourceModuleTarget) -> URL {
return target.directoryURL.appending(path: "bridge-js.config.json")
}

private func createExportSwiftCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command {
let outputSwiftPath = context.pluginWorkDirectoryURL.appending(path: "BridgeJS.ExportSwift.swift")
let outputSkeletonPath = context.pluginWorkDirectoryURL.appending(path: "BridgeJS.ExportSwift.json")
let inputSwiftFiles = target.sourceFiles.filter {
!$0.url.path.hasPrefix(context.pluginWorkDirectoryURL.path + "/")
}
.map(\.url)
let configFile = pathToConfigFile(target: target)
let inputFiles: [URL]
if FileManager.default.fileExists(atPath: configFile.path) {
inputFiles = inputSwiftFiles + [configFile]
} else {
inputFiles = inputSwiftFiles
}
return .buildCommand(
displayName: "Export Swift API",
executable: try context.tool(named: "BridgeJSTool").url,
arguments: [
"export",
"--output-skeleton",
outputSkeletonPath.path,
"--output-swift",
outputSwiftPath.path,
// Generate the output files even if nothing is exported not to surprise
// the build system.
"--always-write", "true",
] + inputSwiftFiles.map(\.path),
inputFiles: inputFiles,
outputFiles: [
outputSwiftPath
]
)
}

private func createImportTSCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command? {
let outputSwiftPath = context.pluginWorkDirectoryURL.appending(path: "BridgeJS.ImportTS.swift")
let outputSkeletonPath = context.pluginWorkDirectoryURL.appending(path: "BridgeJS.ImportTS.json")
let inputTSFile = target.directoryURL.appending(path: "bridge-js.d.ts")
guard FileManager.default.fileExists(atPath: inputTSFile.path) else {
return nil
}

let configFile = pathToConfigFile(target: target)
let inputFiles: [URL]
if FileManager.default.fileExists(atPath: configFile.path) {
inputFiles = [inputTSFile, configFile]
} else {
inputFiles = [inputTSFile]
}
return .buildCommand(
displayName: "Import TypeScript API",
executable: try context.tool(named: "BridgeJSTool").url,
arguments: [
"import",
"--output-skeleton",
outputSkeletonPath.path,
"--output-swift",
outputSwiftPath.path,
"--module-name",
target.name,
// Generate the output files even if nothing is imported not to surprise
// the build system.
"--always-write", "true",
"--project",
context.package.directoryURL.appending(path: "tsconfig.json").path,
inputTSFile.path,
],
inputFiles: inputFiles,
outputFiles: [
outputSwiftPath
]
)
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#if canImport(PackagePlugin)
import PackagePlugin
@preconcurrency import Foundation

/// Command plugin for ahead-of-time (AOT) code generation with BridgeJS.
/// This plugin allows you to generate bridge code between Swift and JavaScript
/// before the build process, improving build times for larger projects.
/// See documentation: Ahead-of-Time-Code-Generation.md
@main
struct BridgeJSCommandPlugin: CommandPlugin {
static let JAVASCRIPTKIT_PACKAGE_NAME: String = "JavaScriptKit"

struct Options {
var targets: [String]
var verbose: Bool

static func parse(extractor: inout ArgumentExtractor) -> Options {
let targets = extractor.extractOption(named: "target")
let verbose = extractor.extractFlag(named: "verbose")
return Options(targets: targets, verbose: verbose != 0)
}

static func help() -> String {
return """
OVERVIEW: Generate ahead-of-time (AOT) bridge code between Swift and JavaScript.
This command generates bridge code before the build process, which can significantly
improve build times for larger projects by avoiding runtime code generation.
Generated code will be placed in the target's 'Generated' directory.
OPTIONS:
--target <target> Specify target(s) to generate bridge code for. If omitted,
generates for all targets with JavaScriptKit dependency.
--verbose Print verbose output.
"""
}
}

func performCommand(context: PluginContext, arguments: [String]) throws {
// Check for help flags to display usage information
if arguments.contains(where: { ["-h", "--help"].contains($0) }) {
printStderr(Options.help())
return
}

var extractor = ArgumentExtractor(arguments)
let options = Options.parse(extractor: &extractor)
let remainingArguments = extractor.remainingArguments

let context = Context(options: options, context: context)

if options.targets.isEmpty {
try context.runOnTargets(
remainingArguments: remainingArguments,
where: { target in
target.hasDependency(named: Self.JAVASCRIPTKIT_PACKAGE_NAME)
}
)
} else {
try context.runOnTargets(
remainingArguments: remainingArguments,
where: { options.targets.contains($0.name) }
)
}
}

struct Context {
let options: Options
let context: PluginContext
}
}

extension BridgeJSCommandPlugin.Context {
func runOnTargets(
remainingArguments: [String],
where predicate: (SwiftSourceModuleTarget) -> Bool
) throws {
for target in context.package.targets {
guard let target = target as? SwiftSourceModuleTarget else {
continue
}
let configFilePath = target.directoryURL.appending(path: "bridge-js.config.json")
if !FileManager.default.fileExists(atPath: configFilePath.path) {
printVerbose("No bridge-js.config.json found for \(target.name), skipping...")
continue
}
guard predicate(target) else {
continue
}
try runSingleTarget(target: target, remainingArguments: remainingArguments)
}
}

private func runSingleTarget(
target: SwiftSourceModuleTarget,
remainingArguments: [String]
) throws {
printStderr("Generating bridge code for \(target.name)...")

printVerbose("Exporting Swift API for \(target.name)...")

let generatedDirectory = target.directoryURL.appending(path: "Generated")
let generatedJavaScriptDirectory = generatedDirectory.appending(path: "JavaScript")

try runBridgeJSTool(
arguments: [
"export",
"--output-skeleton",
generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path,
"--output-swift",
generatedDirectory.appending(path: "BridgeJS.ExportSwift.swift").path,
"--verbose",
options.verbose ? "true" : "false",
]
+ target.sourceFiles.filter {
!$0.url.path.hasPrefix(generatedDirectory.path + "/")
}.map(\.url.path) + remainingArguments
)

printVerbose("Importing TypeScript API for \(target.name)...")

let bridgeDtsPath = target.directoryURL.appending(path: "bridge-js.d.ts")
// Execute import only if bridge-js.d.ts exists
if FileManager.default.fileExists(atPath: bridgeDtsPath.path) {
try runBridgeJSTool(
arguments: [
"import",
"--output-skeleton",
generatedJavaScriptDirectory.appending(path: "BridgeJS.ImportTS.json").path,
"--output-swift",
generatedDirectory.appending(path: "BridgeJS.ImportTS.swift").path,
"--verbose",
options.verbose ? "true" : "false",
"--module-name",
target.name,
"--project",
context.package.directoryURL.appending(path: "tsconfig.json").path,
bridgeDtsPath.path,
] + remainingArguments
)
}
}

private func runBridgeJSTool(arguments: [String]) throws {
let tool = try context.tool(named: "BridgeJSTool").url
printVerbose("$ \(tool.path) \(arguments.joined(separator: " "))")
let process = Process()
process.executableURL = tool
process.arguments = arguments
try process.forwardTerminationSignals {
try process.run()
process.waitUntilExit()
}
if process.terminationStatus != 0 {
exit(process.terminationStatus)
}
}

private func printVerbose(_ message: String) {
if options.verbose {
printStderr(message)
}
}
}

private func printStderr(_ message: String) {
fputs(message + "\n", stderr)
}

extension SwiftSourceModuleTarget {
func hasDependency(named name: String) -> Bool {
return dependencies.contains(where: {
switch $0 {
case .product(let product):
return product.name == name
case .target(let target):
return target.name == name
@unknown default:
return false
}
})
}
}

extension Foundation.Process {
// Monitor termination/interrruption signals to forward them to child process
func setSignalForwarding(_ signalNo: Int32) -> DispatchSourceSignal {
let signalSource = DispatchSource.makeSignalSource(signal: signalNo)
signalSource.setEventHandler { [self] in
signalSource.cancel()
kill(processIdentifier, signalNo)
}
signalSource.resume()
return signalSource
}

func forwardTerminationSignals(_ body: () throws -> Void) rethrows {
let sources = [
setSignalForwarding(SIGINT),
setSignalForwarding(SIGTERM),
]
defer {
for source in sources {
source.cancel()
}
}
try body()
}
}
#endif
7 changes: 7 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
struct BridgeJSCoreError: Swift.Error, CustomStringConvertible {
let description: String

init(_ message: String) {
self.description = message
}
}
23 changes: 23 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/DiagnosticError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import SwiftSyntax

struct DiagnosticError: Error {
let node: Syntax
let message: String
let hint: String?

init(node: some SyntaxProtocol, message: String, hint: String? = nil) {
self.node = Syntax(node)
self.message = message
self.hint = hint
}

func formattedDescription(fileName: String) -> String {
let locationConverter = SourceLocationConverter(fileName: fileName, tree: node.root)
let location = locationConverter.location(for: node.position)
var description = "\(fileName):\(location.line):\(location.column): error: \(message)"
if let hint {
description += "\nHint: \(hint)"
}
return description
}
}
Loading