From 6eb534b932fc5843492aac731eb3901cb2d9f8e8 Mon Sep 17 00:00:00 2001
From: Max Desiatov <m_desiatov@apple.com>
Date: Fri, 21 Mar 2025 18:10:49 +0000
Subject: [PATCH 01/18] Add `JSDictionary` type for Embedded Swift compat

---
 Runtime/src/index.ts                          |  2 ++
 Runtime/src/types.ts                          |  1 +
 .../FundamentalObjects/JSDictionary.swift     | 34 +++++++++++++++++++
 .../_CJavaScriptKit/include/_CJavaScriptKit.h |  2 ++
 4 files changed, 39 insertions(+)
 create mode 100644 Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift

diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts
index 3f23ed753..2bd4ffcef 100644
--- a/Runtime/src/index.ts
+++ b/Runtime/src/index.ts
@@ -517,6 +517,8 @@ export class SwiftRuntime {
                 return this.memory.retain(array.slice());
             },
 
+            swjs_create_object: () => { return this.memory.retain({}); },
+
             swjs_load_typed_array: (ref: ref, buffer: pointer) => {
                 const memory = this.memory;
                 const typedArray = memory.getObject(ref);
diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts
index 587b60770..5348f4c16 100644
--- a/Runtime/src/types.ts
+++ b/Runtime/src/types.ts
@@ -102,6 +102,7 @@ export interface ImportedFunctions {
         elementsPtr: pointer,
         length: number
     ): number;
+    swjs_create_object(): number;
     swjs_load_typed_array(ref: ref, buffer: pointer): void;
     swjs_release(ref: number): void;
     swjs_release_remote(tid: number, ref: number): void;
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift b/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
new file mode 100644
index 000000000..51957b279
--- /dev/null
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
@@ -0,0 +1,34 @@
+import _CJavaScriptKit
+
+public final class JSDictionary {
+  private let ref: JSObject
+
+  init(ref: JSObject) { self.ref = ref }
+
+  public init() { ref = JSObject(id: swjs_create_object()) }
+
+  public subscript(key: String) -> JSValue {
+    get { ref[dynamicMember: key] }
+    set { ref[dynamicMember: key] = newValue }
+  }
+}
+
+extension JSDictionary: ExpressibleByDictionaryLiteral {
+  public convenience init(dictionaryLiteral elements: (String, JSValue)...) {
+    self.init()
+
+    for (key, value) in elements { self[key] = value }
+  }
+}
+
+extension JSDictionary: ConvertibleToJSValue {
+  public var jsValue: JSValue { .object(ref) }
+}
+
+extension JSDictionary: ConstructibleFromJSValue {
+  public static func construct(from value: JSValue) -> JSDictionary? {
+    guard let object = value.object else { return nil }
+
+    return JSDictionary(ref: object)
+  }
+}
diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
index 2b96a81ea..6416379f8 100644
--- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
+++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
@@ -314,6 +314,8 @@ IMPORT_JS_FUNCTION(swjs_terminate_worker_thread, void, (int tid))
 
 IMPORT_JS_FUNCTION(swjs_get_worker_thread_id, int, (void))
 
+IMPORT_JS_FUNCTION(swjs_create_object, JavaScriptObjectRef, (void))
+
 int swjs_get_worker_thread_id_cached(void);
 
 /// Requests sending a JavaScript object to another worker thread.

From 5c7e75e526fe6cf971d3653bb38e3de8494651e2 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 25 Mar 2025 15:13:37 +0000
Subject: [PATCH 02/18] ./Utilities/format.swift

---
 .../FundamentalObjects/JSDictionary.swift     | 32 +++++++++----------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift b/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
index 51957b279..d56cedf17 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
@@ -1,34 +1,34 @@
 import _CJavaScriptKit
 
 public final class JSDictionary {
-  private let ref: JSObject
+    private let ref: JSObject
 
-  init(ref: JSObject) { self.ref = ref }
+    init(ref: JSObject) { self.ref = ref }
 
-  public init() { ref = JSObject(id: swjs_create_object()) }
+    public init() { ref = JSObject(id: swjs_create_object()) }
 
-  public subscript(key: String) -> JSValue {
-    get { ref[dynamicMember: key] }
-    set { ref[dynamicMember: key] = newValue }
-  }
+    public subscript(key: String) -> JSValue {
+        get { ref[dynamicMember: key] }
+        set { ref[dynamicMember: key] = newValue }
+    }
 }
 
 extension JSDictionary: ExpressibleByDictionaryLiteral {
-  public convenience init(dictionaryLiteral elements: (String, JSValue)...) {
-    self.init()
+    public convenience init(dictionaryLiteral elements: (String, JSValue)...) {
+        self.init()
 
-    for (key, value) in elements { self[key] = value }
-  }
+        for (key, value) in elements { self[key] = value }
+    }
 }
 
 extension JSDictionary: ConvertibleToJSValue {
-  public var jsValue: JSValue { .object(ref) }
+    public var jsValue: JSValue { .object(ref) }
 }
 
 extension JSDictionary: ConstructibleFromJSValue {
-  public static func construct(from value: JSValue) -> JSDictionary? {
-    guard let object = value.object else { return nil }
+    public static func construct(from value: JSValue) -> JSDictionary? {
+        guard let object = value.object else { return nil }
 
-    return JSDictionary(ref: object)
-  }
+        return JSDictionary(ref: object)
+    }
 }

From 0d7d3de483bc687942f90e0d0c18ec34ff9f6319 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 25 Mar 2025 15:37:57 +0000
Subject: [PATCH 03/18] make regenerate_swiftpm_resources

---
 Sources/JavaScriptKit/Runtime/index.d.ts | 1 +
 Sources/JavaScriptKit/Runtime/index.js   | 1 +
 Sources/JavaScriptKit/Runtime/index.mjs  | 1 +
 3 files changed, 3 insertions(+)

diff --git a/Sources/JavaScriptKit/Runtime/index.d.ts b/Sources/JavaScriptKit/Runtime/index.d.ts
index 5bfa4c242..6bffb1ba4 100644
--- a/Sources/JavaScriptKit/Runtime/index.d.ts
+++ b/Sources/JavaScriptKit/Runtime/index.d.ts
@@ -52,6 +52,7 @@ interface ImportedFunctions {
     swjs_instanceof(obj_ref: ref, constructor_ref: ref): boolean;
     swjs_create_function(host_func_id: number, line: number, file: ref): number;
     swjs_create_typed_array(constructor_ref: ref, elementsPtr: pointer, length: number): number;
+    swjs_create_object(): number;
     swjs_load_typed_array(ref: ref, buffer: pointer): void;
     swjs_release(ref: number): void;
     swjs_release_remote(tid: number, ref: number): void;
diff --git a/Sources/JavaScriptKit/Runtime/index.js b/Sources/JavaScriptKit/Runtime/index.js
index a3bc31397..0ef6c9b32 100644
--- a/Sources/JavaScriptKit/Runtime/index.js
+++ b/Sources/JavaScriptKit/Runtime/index.js
@@ -627,6 +627,7 @@
                     // Call `.slice()` to copy the memory
                     return this.memory.retain(array.slice());
                 },
+                swjs_create_object: () => { return this.memory.retain({}); },
                 swjs_load_typed_array: (ref, buffer) => {
                     const memory = this.memory;
                     const typedArray = memory.getObject(ref);
diff --git a/Sources/JavaScriptKit/Runtime/index.mjs b/Sources/JavaScriptKit/Runtime/index.mjs
index ba1b6beaf..8f85b2c47 100644
--- a/Sources/JavaScriptKit/Runtime/index.mjs
+++ b/Sources/JavaScriptKit/Runtime/index.mjs
@@ -621,6 +621,7 @@ class SwiftRuntime {
                 // Call `.slice()` to copy the memory
                 return this.memory.retain(array.slice());
             },
+            swjs_create_object: () => { return this.memory.retain({}); },
             swjs_load_typed_array: (ref, buffer) => {
                 const memory = this.memory;
                 const typedArray = memory.getObject(ref);

From ec0bbe6b5ebd1da915ef7c0a35518f3f750be583 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 25 Mar 2025 15:39:57 +0000
Subject: [PATCH 04/18] Make JSObject conform to ExpressibleByDictionaryLiteral
 directly

---
 .../FundamentalObjects/JSClosure.swift        | 10 ++++++
 .../FundamentalObjects/JSDictionary.swift     | 34 -------------------
 .../FundamentalObjects/JSObject.swift         | 15 +++++++-
 .../FundamentalObjects/JSSymbol.swift         |  5 +++
 Tests/JavaScriptKitTests/JSObjectTests.swift  | 28 +++++++++++++++
 5 files changed, 57 insertions(+), 35 deletions(-)
 delete mode 100644 Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
 create mode 100644 Tests/JavaScriptKitTests/JSObjectTests.swift

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
index 66ce009bf..fa713c3b9 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
@@ -35,6 +35,11 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
         )
     }
 
+    @available(*, unavailable, message: "JSOneshotClosure does not support dictionary literal initialization")
+    public required init(dictionaryLiteral elements: (String, JSValue)...) {
+        fatalError("JSOneshotClosure does not support dictionary literal initialization")
+    }
+
     #if compiler(>=5.5) && !hasFeature(Embedded)
     @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
     public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure
@@ -122,6 +127,11 @@ public class JSClosure: JSFunction, JSClosureProtocol {
         Self.sharedClosures.wrappedValue[hostFuncRef] = (self, body)
     }
 
+    @available(*, unavailable, message: "JSClosure does not support dictionary literal initialization")
+    public required init(dictionaryLiteral elements: (String, JSValue)...) {
+        fatalError("JSClosure does not support dictionary literal initialization")
+    }
+
     #if compiler(>=5.5) && !hasFeature(Embedded)
     @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
     public static func async(_ body: @Sendable @escaping (sending [JSValue]) async throws -> JSValue) -> JSClosure {
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift b/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
deleted file mode 100644
index d56cedf17..000000000
--- a/Sources/JavaScriptKit/FundamentalObjects/JSDictionary.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-import _CJavaScriptKit
-
-public final class JSDictionary {
-    private let ref: JSObject
-
-    init(ref: JSObject) { self.ref = ref }
-
-    public init() { ref = JSObject(id: swjs_create_object()) }
-
-    public subscript(key: String) -> JSValue {
-        get { ref[dynamicMember: key] }
-        set { ref[dynamicMember: key] = newValue }
-    }
-}
-
-extension JSDictionary: ExpressibleByDictionaryLiteral {
-    public convenience init(dictionaryLiteral elements: (String, JSValue)...) {
-        self.init()
-
-        for (key, value) in elements { self[key] = value }
-    }
-}
-
-extension JSDictionary: ConvertibleToJSValue {
-    public var jsValue: JSValue { .object(ref) }
-}
-
-extension JSDictionary: ConstructibleFromJSValue {
-    public static func construct(from value: JSValue) -> JSDictionary? {
-        guard let object = value.object else { return nil }
-
-        return JSDictionary(ref: object)
-    }
-}
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
index 33a20f3b5..12dbf9e02 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
@@ -15,7 +15,7 @@ import _CJavaScriptKit
 /// The lifetime of this object is managed by the JavaScript and Swift runtime bridge library with
 /// reference counting system.
 @dynamicMemberLookup
-public class JSObject: Equatable {
+public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
     internal static var constructor: JSFunction { _constructor.wrappedValue }
     private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Object.function! })
 
@@ -38,6 +38,19 @@ public class JSObject: Equatable {
         #endif
     }
 
+    /// Creates an empty JavaScript object.
+    public convenience init() {
+        self.init(id: swjs_create_object())
+    }
+
+    /// Creates a new object with the key-value pairs in the dictionary literal.
+    ///
+    /// - Parameter elements: A variadic list of key-value pairs where all keys are strings
+    public convenience required init(dictionaryLiteral elements: (String, JSValue)...) {
+        self.init()
+        for (key, value) in elements { self[key] = value }
+    }
+
     /// Asserts that the object is being accessed from the owner thread.
     ///
     /// - Parameter hint: A string to provide additional context for debugging.
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
index 42f63e010..a9461317b 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
@@ -24,6 +24,11 @@ public class JSSymbol: JSObject {
         super.init(id: id)
     }
 
+    @available(*, unavailable, message: "JSSymbol does not support dictionary literal initialization")
+    public required init(dictionaryLiteral elements: (String, JSValue)...) {
+        fatalError("JSSymbol does not support dictionary literal initialization")
+    }
+
     public static func `for`(key: JSString) -> JSSymbol {
         Symbol.for!(key).symbol!
     }
diff --git a/Tests/JavaScriptKitTests/JSObjectTests.swift b/Tests/JavaScriptKitTests/JSObjectTests.swift
new file mode 100644
index 000000000..e283da608
--- /dev/null
+++ b/Tests/JavaScriptKitTests/JSObjectTests.swift
@@ -0,0 +1,28 @@
+import JavaScriptKit
+import XCTest
+
+final class JSObjectTests: XCTestCase {
+    func testEmptyObject() {
+        let object = JSObject()
+        let keys = JSObject.global.Object.function!.keys.function!(object)
+        XCTAssertEqual(keys.array?.count, 0)
+    }
+
+    func testInitWithDictionaryLiteral() {
+        let object: JSObject = [
+            "key1": 1,
+            "key2": "value2",
+            "key3": .boolean(true),
+            "key4": .object(JSObject()),
+            "key5": [1, 2, 3].jsValue,
+            "key6": ["key": "value"].jsValue,
+        ]
+        XCTAssertEqual(object.key1, .number(1))
+        XCTAssertEqual(object.key2, "value2")
+        XCTAssertEqual(object.key3, .boolean(true))
+        let getKeys = JSObject.global.Object.function!.keys.function!
+        XCTAssertEqual(getKeys(object.key4).array?.count, 0)
+        XCTAssertEqual(object.key5.array.map(Array.init), [1, 2, 3])
+        XCTAssertEqual(object.key6.object?.key, "value")
+    }
+}

From bb47d01e8dbcebb3ae204122cc359ba2598d7396 Mon Sep 17 00:00:00 2001
From: Max Desiatov <m_desiatov@apple.com>
Date: Fri, 21 Mar 2025 18:27:32 +0000
Subject: [PATCH 05/18] Use `elementsEqual` in `JSString: Equatable`
 conformance

---
 Sources/JavaScriptKit/FundamentalObjects/JSString.swift | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
index cd88a5302..9c2370859 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
@@ -77,7 +77,11 @@ public struct JSString: LosslessStringConvertible, Equatable {
     ///   - lhs: A string to compare.
     ///   - rhs: Another string to compare.
     public static func == (lhs: JSString, rhs: JSString) -> Bool {
-        return lhs.guts.buffer == rhs.guts.buffer
+        guard !(lhs.guts.shouldDeallocateRef && rhs.guts.shouldDeallocateRef) else {
+            return lhs.guts.jsRef == rhs.guts.jsRef
+        }
+
+        return lhs.guts.buffer.utf8.elementsEqual(rhs.guts.buffer.utf8)
     }
 }
 

From 0581f76930152dc2b8c687343b1275e2c38c9a1e Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 25 Mar 2025 15:01:52 +0000
Subject: [PATCH 06/18] Use JS's `==` operator for `JSString` equality
 comparison

---
 Runtime/src/index.ts                                |  7 +++++++
 Runtime/src/types.ts                                |  1 +
 .../JavaScriptKit/FundamentalObjects/JSString.swift |  6 +-----
 Sources/_CJavaScriptKit/include/_CJavaScriptKit.h   | 10 ++++++++++
 Tests/JavaScriptKitTests/JSStringTests.swift        | 13 +++++++++++++
 5 files changed, 32 insertions(+), 5 deletions(-)
 create mode 100644 Tests/JavaScriptKitTests/JSStringTests.swift

diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts
index 3f23ed753..fef4c2f21 100644
--- a/Runtime/src/index.ts
+++ b/Runtime/src/index.ts
@@ -479,6 +479,13 @@ export class SwiftRuntime {
                 return obj instanceof constructor;
             },
 
+            swjs_value_equals: (lhs_ref: ref, rhs_ref: ref) => {
+                const memory = this.memory;
+                const lhs = memory.getObject(lhs_ref);
+                const rhs = memory.getObject(rhs_ref);
+                return lhs == rhs;
+            },
+
             swjs_create_function: (
                 host_func_id: number,
                 line: number,
diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts
index 587b60770..b81818ade 100644
--- a/Runtime/src/types.ts
+++ b/Runtime/src/types.ts
@@ -96,6 +96,7 @@ export interface ImportedFunctions {
         exception_payload2_ptr: pointer
     ): number;
     swjs_instanceof(obj_ref: ref, constructor_ref: ref): boolean;
+    swjs_value_equals(lhs_ref: ref, rhs_ref: ref): boolean;
     swjs_create_function(host_func_id: number, line: number, file: ref): number;
     swjs_create_typed_array(
         constructor_ref: ref,
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
index 9c2370859..b4ad10237 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
@@ -77,11 +77,7 @@ public struct JSString: LosslessStringConvertible, Equatable {
     ///   - lhs: A string to compare.
     ///   - rhs: Another string to compare.
     public static func == (lhs: JSString, rhs: JSString) -> Bool {
-        guard !(lhs.guts.shouldDeallocateRef && rhs.guts.shouldDeallocateRef) else {
-            return lhs.guts.jsRef == rhs.guts.jsRef
-        }
-
-        return lhs.guts.buffer.utf8.elementsEqual(rhs.guts.buffer.utf8)
+        return swjs_value_equals(lhs.guts.jsRef, rhs.guts.jsRef)
     }
 }
 
diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
index 2b96a81ea..05f4ed0f8 100644
--- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
+++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
@@ -257,6 +257,16 @@ IMPORT_JS_FUNCTION(swjs_call_throwing_new, JavaScriptObjectRef, (const JavaScrip
 IMPORT_JS_FUNCTION(swjs_instanceof, bool, (const JavaScriptObjectRef obj,
                                            const JavaScriptObjectRef constructor))
 
+/// Acts like JavaScript `==` operator.
+/// Performs "==" comparison, a.k.a the "Abstract Equality Comparison"
+/// algorithm defined in the ECMAScript.
+/// https://262.ecma-international.org/11.0/#sec-abstract-equality-comparison
+///
+/// @param lhs The left-hand side value to compare.
+/// @param rhs The right-hand side value to compare.
+/// @result Return `true` if `lhs` is `==` to `rhs`. Return `false` if not.
+IMPORT_JS_FUNCTION(swjs_value_equals, bool, (const JavaScriptObjectRef lhs, const JavaScriptObjectRef rhs))
+
 /// Creates a JavaScript thunk function that calls Swift side closure.
 /// See also comments on JSFunction.swift
 ///
diff --git a/Tests/JavaScriptKitTests/JSStringTests.swift b/Tests/JavaScriptKitTests/JSStringTests.swift
new file mode 100644
index 000000000..456c24147
--- /dev/null
+++ b/Tests/JavaScriptKitTests/JSStringTests.swift
@@ -0,0 +1,13 @@
+import JavaScriptKit
+import XCTest
+
+final class JSStringTests: XCTestCase {
+    func testEquatable() {
+        let string1 = JSString("Hello, world!")
+        let string2 = JSString("Hello, world!")
+        let string3 = JSString("Hello, world")
+        XCTAssertEqual(string1, string1)
+        XCTAssertEqual(string1, string2)
+        XCTAssertNotEqual(string1, string3)
+    }
+}

From 90d7238ebaf2a430a3176cf02f80f5cf5419b3e1 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Tue, 25 Mar 2025 15:41:19 +0000
Subject: [PATCH 07/18] make regenerate_swiftpm_resources

---
 Sources/JavaScriptKit/Runtime/index.d.ts | 1 +
 Sources/JavaScriptKit/Runtime/index.js   | 6 ++++++
 Sources/JavaScriptKit/Runtime/index.mjs  | 6 ++++++
 3 files changed, 13 insertions(+)

diff --git a/Sources/JavaScriptKit/Runtime/index.d.ts b/Sources/JavaScriptKit/Runtime/index.d.ts
index 5bfa4c242..adfa8161d 100644
--- a/Sources/JavaScriptKit/Runtime/index.d.ts
+++ b/Sources/JavaScriptKit/Runtime/index.d.ts
@@ -50,6 +50,7 @@ interface ImportedFunctions {
     swjs_call_new(ref: number, argv: pointer, argc: number): number;
     swjs_call_throwing_new(ref: number, argv: pointer, argc: number, exception_kind_ptr: pointer, exception_payload1_ptr: pointer, exception_payload2_ptr: pointer): number;
     swjs_instanceof(obj_ref: ref, constructor_ref: ref): boolean;
+    swjs_value_equals(lhs_ref: ref, rhs_ref: ref): boolean;
     swjs_create_function(host_func_id: number, line: number, file: ref): number;
     swjs_create_typed_array(constructor_ref: ref, elementsPtr: pointer, length: number): number;
     swjs_load_typed_array(ref: ref, buffer: pointer): void;
diff --git a/Sources/JavaScriptKit/Runtime/index.js b/Sources/JavaScriptKit/Runtime/index.js
index a3bc31397..840d843b5 100644
--- a/Sources/JavaScriptKit/Runtime/index.js
+++ b/Sources/JavaScriptKit/Runtime/index.js
@@ -604,6 +604,12 @@
                     const constructor = memory.getObject(constructor_ref);
                     return obj instanceof constructor;
                 },
+                swjs_value_equals: (lhs_ref, rhs_ref) => {
+                    const memory = this.memory;
+                    const lhs = memory.getObject(lhs_ref);
+                    const rhs = memory.getObject(rhs_ref);
+                    return lhs == rhs;
+                },
                 swjs_create_function: (host_func_id, line, file) => {
                     var _a;
                     const fileString = this.memory.getObject(file);
diff --git a/Sources/JavaScriptKit/Runtime/index.mjs b/Sources/JavaScriptKit/Runtime/index.mjs
index ba1b6beaf..50799f2c1 100644
--- a/Sources/JavaScriptKit/Runtime/index.mjs
+++ b/Sources/JavaScriptKit/Runtime/index.mjs
@@ -598,6 +598,12 @@ class SwiftRuntime {
                 const constructor = memory.getObject(constructor_ref);
                 return obj instanceof constructor;
             },
+            swjs_value_equals: (lhs_ref, rhs_ref) => {
+                const memory = this.memory;
+                const lhs = memory.getObject(lhs_ref);
+                const rhs = memory.getObject(rhs_ref);
+                return lhs == rhs;
+            },
             swjs_create_function: (host_func_id, line, file) => {
                 var _a;
                 const fileString = this.memory.getObject(file);

From 4c7ea176f58180421ac00a3272d05c2fce6df386 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 09:23:51 +0000
Subject: [PATCH 08/18] Unlock `JSTypedArray` for Embedded Swift

---
 .../BasicObjects/JSTypedArray.swift           | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
index 2d6fc33b8..3104fa1c5 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
@@ -1,11 +1,11 @@
 //
 //  Created by Manuel Burghard. Licensed unter MIT.
 //
-#if !hasFeature(Embedded)
 import _CJavaScriptKit
 
 /// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type
-public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValue {
+public protocol TypedArrayElement {
+    associatedtype Element: ConvertibleToJSValue, ConstructibleFromJSValue = Self
     /// The constructor function for the TypedArray class for this particular kind of number
     static var typedArrayClass: JSFunction { get }
 }
@@ -13,8 +13,9 @@ public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValu
 /// A wrapper around all [JavaScript `TypedArray`
 /// classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
 /// that exposes their properties in a type-safe way.
-public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement {
-    public class var constructor: JSFunction? { Element.typedArrayClass }
+public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiteral where Traits: TypedArrayElement {
+    public typealias Element = Traits.Element
+    public class var constructor: JSFunction? { Traits.typedArrayClass }
     public var jsObject: JSObject
 
     public subscript(_ index: Int) -> Element {
@@ -176,13 +177,6 @@ extension UInt8: TypedArrayElement {
     public static var typedArrayClass: JSFunction { JSObject.global.Uint8Array.function! }
 }
 
-/// A wrapper around [the JavaScript `Uint8ClampedArray`
-/// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
-/// that exposes its properties in a type-safe and Swifty way.
-public class JSUInt8ClampedArray: JSTypedArray<UInt8> {
-    override public class var constructor: JSFunction? { JSObject.global.Uint8ClampedArray.function! }
-}
-
 extension Int16: TypedArrayElement {
     public static var typedArrayClass: JSFunction { JSObject.global.Int16Array.function! }
 }
@@ -206,4 +200,10 @@ extension Float32: TypedArrayElement {
 extension Float64: TypedArrayElement {
     public static var typedArrayClass: JSFunction { JSObject.global.Float64Array.function! }
 }
-#endif
+
+public enum JSUInt8Clamped: TypedArrayElement {
+    public typealias Element = UInt8
+    public static var typedArrayClass: JSFunction { JSObject.global.Uint8ClampedArray.function! }
+}
+
+public typealias JSUInt8ClampedArray = JSTypedArray<JSUInt8Clamped>

From f41f2340279641428ef3a3072867489015926660 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 09:24:30 +0000
Subject: [PATCH 09/18] Add an example of using TypedArray in an embedded app

---
 .../Embedded/Sources/EmbeddedApp/main.swift   | 39 +++++++++++++++++--
 1 file changed, 35 insertions(+), 4 deletions(-)

diff --git a/Examples/Embedded/Sources/EmbeddedApp/main.swift b/Examples/Embedded/Sources/EmbeddedApp/main.swift
index 3f8c18ca6..37b2334ba 100644
--- a/Examples/Embedded/Sources/EmbeddedApp/main.swift
+++ b/Examples/Embedded/Sources/EmbeddedApp/main.swift
@@ -11,18 +11,49 @@ var 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(
+var 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)
+var encodeResultElement = document.createElement("pre")
+var 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
+}

From af86aee0601a84b99fc8d7e88eba4fd27d8d3a8c Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 09:41:47 +0000
Subject: [PATCH 10/18] Fix JSTypedArrayTests to follow API change

---
 Tests/JavaScriptKitTests/JSTypedArrayTests.swift | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/Tests/JavaScriptKitTests/JSTypedArrayTests.swift b/Tests/JavaScriptKitTests/JSTypedArrayTests.swift
index 0465b1e43..a4649879e 100644
--- a/Tests/JavaScriptKitTests/JSTypedArrayTests.swift
+++ b/Tests/JavaScriptKitTests/JSTypedArrayTests.swift
@@ -17,8 +17,8 @@ final class JSTypedArrayTests: XCTestCase {
     }
 
     func testTypedArray() {
-        func checkArray<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
-            XCTAssertEqual(toString(JSTypedArray(array).jsValue.object!), jsStringify(array))
+        func checkArray<T>(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T {
+            XCTAssertEqual(toString(JSTypedArray<T>(array).jsValue.object!), jsStringify(array))
             checkArrayUnsafeBytes(array)
         }
 
@@ -30,20 +30,20 @@ final class JSTypedArrayTests: XCTestCase {
             array.map({ String(describing: $0) }).joined(separator: ",")
         }
 
-        func checkArrayUnsafeBytes<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
-            let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in
+        func checkArrayUnsafeBytes<T>(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T {
+            let copyOfArray: [T] = JSTypedArray<T>(array).withUnsafeBytes { buffer in
                 Array(buffer)
             }
             XCTAssertEqual(copyOfArray, array)
         }
 
         let numbers = [UInt8](0...255)
-        let typedArray = JSTypedArray(numbers)
+        let typedArray = JSTypedArray<UInt8>(numbers)
         XCTAssertEqual(typedArray[12], 12)
         XCTAssertEqual(numbers.count, typedArray.lengthInBytes)
 
         let numbersSet = Set(0...255)
-        let typedArrayFromSet = JSTypedArray(numbersSet)
+        let typedArrayFromSet = JSTypedArray<Int>(numbersSet)
         XCTAssertEqual(typedArrayFromSet.jsObject.length, 256)
         XCTAssertEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout<Int>.size)
 
@@ -63,7 +63,7 @@ final class JSTypedArrayTests: XCTestCase {
             0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
             .leastNormalMagnitude, 42,
         ]
-        let jsFloat32Array = JSTypedArray(float32Array)
+        let jsFloat32Array = JSTypedArray<Float32>(float32Array)
         for (i, num) in float32Array.enumerated() {
             XCTAssertEqual(num, jsFloat32Array[i])
         }
@@ -72,7 +72,7 @@ final class JSTypedArrayTests: XCTestCase {
             0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
             .leastNormalMagnitude, 42,
         ]
-        let jsFloat64Array = JSTypedArray(float64Array)
+        let jsFloat64Array = JSTypedArray<Float64>(float64Array)
         for (i, num) in float64Array.enumerated() {
             XCTAssertEqual(num, jsFloat64Array[i])
         }

From e99b99096ed322b19fefef49ccdbdab795aa1be3 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 09:52:56 +0000
Subject: [PATCH 11/18] Remove possible use of `fatalError` in `JSTypedArray`

---
 .../BasicObjects/JSTypedArray.swift           | 41 ++++++++-----------
 1 file changed, 18 insertions(+), 23 deletions(-)

diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
index 3104fa1c5..47919b17d 100644
--- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
+++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
@@ -140,33 +140,28 @@ public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiter
     }
 }
 
-// MARK: - Int and UInt support
-
-// FIXME: Should be updated to support wasm64 when that becomes available.
-func valueForBitWidth<T>(typeName: String, bitWidth: Int, when32: T) -> T {
-    if bitWidth == 32 {
-        return when32
-    } else if bitWidth == 64 {
-        fatalError("64-bit \(typeName)s are not yet supported in JSTypedArray")
-    } else {
-        fatalError(
-            "Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)"
-        )
-    }
-}
-
 extension Int: TypedArrayElement {
-    public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
-    private static let _typedArrayClass = LazyThreadLocal(initialize: {
-        valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObject.global.Int32Array).function!
-    })
+    public static var typedArrayClass: JSFunction {
+        #if _pointerBitWidth(_32)
+        return JSObject.global.Int32Array.function!
+        #elseif _pointerBitWidth(_64)
+        return JSObject.global.Int64Array.function!
+        #else
+        #error("Unsupported pointer width")
+        #endif
+    }
 }
 
 extension UInt: TypedArrayElement {
-    public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
-    private static let _typedArrayClass = LazyThreadLocal(initialize: {
-        valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function!
-    })
+    public static var typedArrayClass: JSFunction {
+        #if _pointerBitWidth(_32)
+        return JSObject.global.Uint32Array.function!
+        #elseif _pointerBitWidth(_64)
+        return JSObject.global.Uint64Array.function!
+        #else
+        #error("Unsupported pointer width")
+        #endif
+    }
 }
 
 extension Int8: TypedArrayElement {

From a4376e3e36532673c8907c4cd0b8ea88e5fa273a Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 12:14:14 +0000
Subject: [PATCH 12/18] 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.
---
 Sources/JavaScriptKit/FundamentalObjects/JSString.swift | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
index b4ad10237..f084ffc81 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
@@ -77,7 +77,11 @@ public struct JSString: LosslessStringConvertible, Equatable {
     ///   - lhs: A string to compare.
     ///   - rhs: Another string to compare.
     public static func == (lhs: JSString, rhs: JSString) -> Bool {
-        return swjs_value_equals(lhs.guts.jsRef, rhs.guts.jsRef)
+        withExtendedLifetime(lhs.guts) { lhsGuts in
+            withExtendedLifetime(rhs.guts) { rhsGuts in
+                return swjs_value_equals(lhsGuts.jsRef, rhsGuts.jsRef)
+            }
+        }
     }
 }
 

From bb6fad8a3d1ed694d568409b6e2fb07aadfe0a77 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 10:44:28 +0000
Subject: [PATCH 13/18] Rename Package.swift -> Package@swift-6.0.swift

---
 Package.swift => Package@swift-6.0.swift | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename Package.swift => Package@swift-6.0.swift (100%)

diff --git a/Package.swift b/Package@swift-6.0.swift
similarity index 100%
rename from Package.swift
rename to Package@swift-6.0.swift

From f9b3973044cb2cde6a335f09ddcb7c958c97a798 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 10:45:06 +0000
Subject: [PATCH 14/18] Add package-trait based Package.swift

---
 Package.swift | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 Package.swift

diff --git a/Package.swift b/Package.swift
new file mode 100644
index 000000000..5a3a1ca5d
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,94 @@
+// swift-tools-version:6.1
+
+import PackageDescription
+
+// NOTE: needed for embedded customizations, ideally this will not be necessary at all in the future, or can be replaced with traits
+let useLegacyResourceBundling =
+    Context.environment["JAVASCRIPTKIT_USE_LEGACY_RESOURCE_BUNDLING"].flatMap(Bool.init) ?? false
+
+let package = Package(
+    name: "JavaScriptKit",
+    products: [
+        .library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
+        .library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
+        .library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]),
+        .library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]),
+        .plugin(name: "PackageToJS", targets: ["PackageToJS"]),
+    ],
+    traits: [
+        "Embedded"
+    ],
+    targets: [
+        .target(
+            name: "JavaScriptKit",
+            dependencies: ["_CJavaScriptKit"],
+            exclude: useLegacyResourceBundling ? [] : ["Runtime"],
+            resources: useLegacyResourceBundling ? [.copy("Runtime")] : [],
+            cSettings: [
+                .unsafeFlags(["-fdeclspec"], .when(traits: ["Embedded"]))
+            ],
+            swiftSettings: [
+                .enableExperimentalFeature("Embedded", .when(traits: ["Embedded"])),
+                .enableExperimentalFeature("Extern", .when(traits: ["Embedded"])),
+                .unsafeFlags(["-Xfrontend", "-emit-empty-object-file"], .when(traits: ["Embedded"])),
+            ]
+        ),
+        .target(name: "_CJavaScriptKit"),
+        .testTarget(
+            name: "JavaScriptKitTests",
+            dependencies: ["JavaScriptKit"],
+            swiftSettings: [
+                .enableExperimentalFeature("Extern")
+            ]
+        ),
+
+        .target(
+            name: "JavaScriptBigIntSupport",
+            dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"]
+        ),
+        .target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]),
+        .testTarget(
+            name: "JavaScriptBigIntSupportTests",
+            dependencies: ["JavaScriptBigIntSupport", "JavaScriptKit"]
+        ),
+
+        .target(
+            name: "JavaScriptEventLoop",
+            dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
+        ),
+        .target(name: "_CJavaScriptEventLoop"),
+        .testTarget(
+            name: "JavaScriptEventLoopTests",
+            dependencies: [
+                "JavaScriptEventLoop",
+                "JavaScriptKit",
+                "JavaScriptEventLoopTestSupport",
+            ],
+            swiftSettings: [
+                .enableExperimentalFeature("Extern")
+            ]
+        ),
+        .target(
+            name: "JavaScriptEventLoopTestSupport",
+            dependencies: [
+                "_CJavaScriptEventLoopTestSupport",
+                "JavaScriptEventLoop",
+            ]
+        ),
+        .target(name: "_CJavaScriptEventLoopTestSupport"),
+        .testTarget(
+            name: "JavaScriptEventLoopTestSupportTests",
+            dependencies: [
+                "JavaScriptKit",
+                "JavaScriptEventLoopTestSupport",
+            ]
+        ),
+        .plugin(
+            name: "PackageToJS",
+            capability: .command(
+                intent: .custom(verb: "js", description: "Convert a Swift package to a JavaScript package")
+            ),
+            sources: ["Sources"]
+        ),
+    ]
+)

From 64fb506b50c6ac1e35118eb01b8e471ed63a6208 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 12:11:53 +0000
Subject: [PATCH 15/18] Update Examples/Embedded to use the new `Embedded`
 trait

---
 Examples/Embedded/Package.swift | 4 ++--
 Examples/Embedded/build.sh      | 3 +--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/Examples/Embedded/Package.swift b/Examples/Embedded/Package.swift
index 5ae19adc6..aae080022 100644
--- a/Examples/Embedded/Package.swift
+++ b/Examples/Embedded/Package.swift
@@ -1,11 +1,11 @@
-// swift-tools-version:6.0
+// swift-tools-version:6.1
 
 import PackageDescription
 
 let package = Package(
     name: "Embedded",
     dependencies: [
-        .package(name: "JavaScriptKit", path: "../../"),
+        .package(name: "JavaScriptKit", path: "../../", traits: ["Embedded"]),
         .package(url: "https://github.com/swiftwasm/swift-dlmalloc", branch: "0.1.0"),
     ],
     targets: [
diff --git a/Examples/Embedded/build.sh b/Examples/Embedded/build.sh
index f807cdbf5..81840e76f 100755
--- a/Examples/Embedded/build.sh
+++ b/Examples/Embedded/build.sh
@@ -1,5 +1,4 @@
 #!/bin/bash
 package_dir="$(cd "$(dirname "$0")" && pwd)"
-JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM=true \
-  swift package --package-path "$package_dir" \
+swift package --package-path "$package_dir" \
   -c release --triple wasm32-unknown-none-wasm js

From 047e5a631393f94d877a12ebcfc39aa9cd637223 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 13:15:05 +0000
Subject: [PATCH 16/18] Detect Embedded build mode by compilation condition

---
 .../Sources/PackageToJSPlugin.swift           | 52 +++++++++++--------
 1 file changed, 31 insertions(+), 21 deletions(-)

diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift
index 9d0b2c19d..559022c2c 100644
--- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift
+++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift
@@ -161,9 +161,11 @@ struct PackageToJSPlugin: CommandPlugin {
         }
 
         // Build products
+        let selfPackage = try findSelfPackage(in: context.package)
         let productName = try buildOptions.product ?? deriveDefaultProduct(package: context.package)
         let build = try buildWasm(
             productName: productName,
+            selfPackage: selfPackage,
             context: context,
             options: buildOptions.packageOptions
         )
@@ -178,14 +180,6 @@ struct PackageToJSPlugin: CommandPlugin {
             } else {
                 context.pluginWorkDirectoryURL.appending(path: "Package")
             }
-        guard
-            let selfPackage = findPackageInDependencies(
-                package: context.package,
-                id: Self.JAVASCRIPTKIT_PACKAGE_ID
-            )
-        else {
-            throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?")
-        }
         var make = MiniMake(
             explain: buildOptions.packageOptions.explain,
             printProgress: self.printProgress
@@ -226,9 +220,11 @@ struct PackageToJSPlugin: CommandPlugin {
             exit(1)
         }
 
+        let selfPackage = try findSelfPackage(in: context.package)
         let productName = "\(context.package.displayName)PackageTests"
         let build = try buildWasm(
             productName: productName,
+            selfPackage: selfPackage,
             context: context,
             options: testOptions.packageOptions
         )
@@ -264,14 +260,6 @@ struct PackageToJSPlugin: CommandPlugin {
             } else {
                 context.pluginWorkDirectoryURL.appending(path: "PackageTests")
             }
-        guard
-            let selfPackage = findPackageInDependencies(
-                package: context.package,
-                id: Self.JAVASCRIPTKIT_PACKAGE_ID
-            )
-        else {
-            throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?")
-        }
         var make = MiniMake(
             explain: testOptions.packageOptions.explain,
             printProgress: self.printProgress
@@ -311,6 +299,7 @@ struct PackageToJSPlugin: CommandPlugin {
 
     private func buildWasm(
         productName: String,
+        selfPackage: Package,
         context: PluginContext,
         options: PackageToJS.PackageOptions
     ) throws
@@ -331,11 +320,7 @@ struct PackageToJSPlugin: CommandPlugin {
         )
         parameters.echoLogs = true
         parameters.otherSwiftcFlags = ["-color-diagnostics"]
-        let buildingForEmbedded =
-            ProcessInfo.processInfo.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap(
-                Bool.init
-            ) ?? false
-        if !buildingForEmbedded {
+        if !isBuildingForEmbedded(selfPackage: selfPackage) {
             // NOTE: We only support static linking for now, and the new SwiftDriver
             // does not infer `-static-stdlib` for WebAssembly targets intentionally
             // for future dynamic linking support.
@@ -355,6 +340,31 @@ struct PackageToJSPlugin: CommandPlugin {
         return try self.packageManager.build(.product(productName), parameters: parameters)
     }
 
+    /// Check if the build is for embedded WebAssembly
+    private func isBuildingForEmbedded(selfPackage: Package) -> Bool {
+        let coreTarget = selfPackage.targets.first { $0.name == "JavaScriptKit" }
+        guard let swiftTarget = coreTarget as? SwiftSourceModuleTarget else {
+            return false
+        }
+        // SwiftPM defines "Embedded" compilation condition when `Embedded` experimental
+        // feature is enabled.
+        // TODO: This should be replaced with a proper trait-based solution in the future.
+        return swiftTarget.compilationConditions.contains("Embedded")
+    }
+
+    /// Find JavaScriptKit package in the dependencies of the given package recursively
+    private func findSelfPackage(in package: Package) throws -> Package {
+        guard
+            let selfPackage = findPackageInDependencies(
+                package: package,
+                id: Self.JAVASCRIPTKIT_PACKAGE_ID
+            )
+        else {
+            throw PackageToJSError("Failed to find JavaScriptKit in dependencies!?")
+        }
+        return selfPackage
+    }
+
     /// Clean if the build graph of the packaging process has changed
     ///
     /// This is especially important to detect user changes debug/release

From b77d01564de4b50a9eafc266647b2ad919fd5825 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 13:45:35 +0000
Subject: [PATCH 17/18] Enable `Embedded` feature for more modules

---
 Package.swift           | 12 ++++++++++--
 Package@swift-6.0.swift | 14 ++++++++++++--
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/Package.swift b/Package.swift
index 5a3a1ca5d..cf88b3db9 100644
--- a/Package.swift
+++ b/Package.swift
@@ -44,7 +44,11 @@ let package = Package(
 
         .target(
             name: "JavaScriptBigIntSupport",
-            dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"]
+            dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"],
+            swiftSettings: [
+                .enableExperimentalFeature("Embedded", .when(traits: ["Embedded"])),
+                .unsafeFlags(["-Xfrontend", "-emit-empty-object-file"], .when(traits: ["Embedded"])),
+            ]
         ),
         .target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]),
         .testTarget(
@@ -54,7 +58,11 @@ let package = Package(
 
         .target(
             name: "JavaScriptEventLoop",
-            dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
+            dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"],
+            swiftSettings: [
+                .enableExperimentalFeature("Embedded", .when(traits: ["Embedded"])),
+                .unsafeFlags(["-Xfrontend", "-emit-empty-object-file"], .when(traits: ["Embedded"])),
+            ]
         ),
         .target(name: "_CJavaScriptEventLoop"),
         .testTarget(
diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift
index 85a9a616d..fcf40524a 100644
--- a/Package@swift-6.0.swift
+++ b/Package@swift-6.0.swift
@@ -44,7 +44,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 +59,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(

From 6105c33b9e922139cc4e2979be5c0b3311660843 Mon Sep 17 00:00:00 2001
From: Yuta Saito <kateinoigakukun@gmail.com>
Date: Thu, 27 Mar 2025 14:16:20 +0000
Subject: [PATCH 18/18] [skip ci] Fix comment in Utilities/format.swift

---
 Utilities/format.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Utilities/format.swift b/Utilities/format.swift
index 9700e9ea3..be6e70858 100755
--- a/Utilities/format.swift
+++ b/Utilities/format.swift
@@ -67,7 +67,7 @@ let excluded: Set<String> = [
     URL(fileURLWithPath: #filePath).lastPathComponent,
 ]
 
-/// Returns a list of directories to format.
+/// Returns a list of file paths to format.
 func filesToFormat() -> [String] {
     var files: [String] = []
     let fileManager = FileManager.default