Title: Opt-in Call Tracing Mode · Issue #547 · swiftwasm/JavaScriptKit · GitHub
Open Graph Title: Opt-in Call Tracing Mode · Issue #547 · swiftwasm/JavaScriptKit
X Title: Opt-in Call Tracing Mode · Issue #547 · swiftwasm/JavaScriptKit
Description: Motivation Crossing the Swift <-> JavaScript bridge is expensive, but today JavaScriptKit users have almost no visibility into how the bridge is actually used at runtime. As a result, performance issues related to excessive calls or unin...
Open Graph Description: Motivation Crossing the Swift <-> JavaScript bridge is expensive, but today JavaScriptKit users have almost no visibility into how the bridge is actually used at runtime. As a result, performance i...
X Description: Motivation Crossing the Swift <-> JavaScript bridge is expensive, but today JavaScriptKit users have almost no visibility into how the bridge is actually used at runtime. As a result, perform...
Opengraph URL: https://github.com/swiftwasm/JavaScriptKit/issues/547
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Opt-in Call Tracing Mode","articleBody":"## Motivation\n\nCrossing the Swift \u003c-\u003e JavaScript bridge is expensive, but today JavaScriptKit users have almost no visibility into how the bridge is actually used at runtime. As a result, performance issues related to excessive calls or unintended data bouncing across the bridge are often identified only by intuition.\n\nTo make performance work more data-driven, we want a minimal observability mechanism that allows users to trace when and how JavaScriptKit bridge calls happen, without imposing overhead on production builds.\n\n## Overview\n\nThis proposal introduces an optional tracing hook API, focused purely on providing hook points at bridge crossings.\n\nThe key design constraints are:\n\n- Tracing must be opt-in and compile-time gated\n- When disabled, there must be no runtime overhead\n- JavaScriptKit itself should not perform logging or aggregation\n- The API should be flexible enough to integrate with user-defined tracing systems\n\nTo achieve this, tracing hooks are only compiled when a specific SwiftPM package trait is enabled. When the trait is not enabled (default), the feature is entirely absent from the binary.\n\nFor each bridge crossing, JavaScriptKit calls a *start hook* provided by the user. That hook returns a closure, which JavaScriptKit later invokes at the *end* of the bridge crossing. This closure-based pairing avoids the need for JavaScriptKit to manage call IDs or start/end bookkeeping.\n\n## API Design (draft)\n\nThe initial scope covers two kinds of bridge crossings.\n\n### Swift -\u003e JavaScript calls\n\nWhen Swift code calls into JavaScript via JavaScriptKit, a tracing hook is invoked at the start and end of the call.\n\n- If a JavaScript function is called directly (i.e. without a receiver), the hook receives the function object and arguments.\n- If a JavaScript method is called, the hook receives the receiver object, the method name, and arguments.\n\n### JavaScript -\u003e Swift calls (JSClosure)\n\nWhen a JSClosure is invoked from the JavaScript side, a tracing hook is invoked.\n\nThe hook receives the file name and line number where the JSClosure was created. This information is already tracked by JavaScriptKit and can be forwarded without additional runtime cost.\n\n### API shape\n\n```swift\n#if Tacing\npublic struct JSTracing {\n public static let default = JSTracing()\n\n public enum JSCallInfo {\n case function(function: JSObject, arguments: [JSValue])\n case method(receiver: JSObject, methodName: String, arguments: [JSValue])\n }\n\n /// Register a hook for Swift to JavaScript calls.\n ///\n /// - Returns: A cleanup closure that unregisters the hook.\n public func addJSCallHook(\n _ hook: @escaping (_ info: JSCallInfo) -\u003e (() -\u003e Void)?\n ) -\u003e () -\u003e Void\n\n public struct JSClosureCallInfo {\n /// The file identifier where the called JSClosure is defined\n public let fileID: StaticString\n /// The line number where the called JSClosure is defined\n public let line: UInt\n }\n /// Register a hook for JavaScript to Swift calls via JSClosure.\n ///\n /// - Returns: A cleanup closure that unregisters the hook.\n public func addJSClosureCallHook(\n _ hook: @escaping (_ info: JSClosureCallInfo) -\u003e (() -\u003e Void)?\n ) -\u003e () -\u003e Void\n}\n#endif\n```\n\nUsage:\n\n```swift\nlet remove = JSTracing.default.addJSCallHook { info in\n let start = now()\n return {\n record(info, now() - start)\n }\n}\n\ndefer { remove() }\n```","author":{"url":"https://github.com/kateinoigakukun","@type":"Person","name":"kateinoigakukun"},"datePublished":"2026-01-29T05:09:23.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/547/JavaScriptKit/issues/547"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:81e2086d-130e-377a-582e-6ef26d35f4fb |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | BDE8:2E134A:734C6F6:95824C3:697E5896 |
| html-safe-nonce | b1622b8631928c0eea4961088f98e5c2abb03ae53d50318f07e3a485938bd6dc |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCREU4OjJFMTM0QTo3MzRDNkY2Ojk1ODI0QzM6Njk3RTU4OTYiLCJ2aXNpdG9yX2lkIjoiMTQzODk0MDA4NjA0MDE1NjMxMCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | e968102dd877fe1bf40938b25985e7019828cef178cef4ec168305dbbf248d22 |
| hovercard-subject-tag | issue:3868859346 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/swiftwasm/JavaScriptKit/547/issue_layout |
| twitter:image | https://opengraph.githubassets.com/8468fecf0cb568c8f8f2eca26b5707c9b85e93d75bc8fa652e2e3acb816d12e9/swiftwasm/JavaScriptKit/issues/547 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/8468fecf0cb568c8f8f2eca26b5707c9b85e93d75bc8fa652e2e3acb816d12e9/swiftwasm/JavaScriptKit/issues/547 |
| og:image:alt | Motivation Crossing the Swift <-> JavaScript bridge is expensive, but today JavaScriptKit users have almost no visibility into how the bridge is actually used at runtime. As a result, performance i... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | kateinoigakukun |
| hostname | github.com |
| expected-hostname | github.com |
| None | 60279d4097367e16897439d16d6bbe4180663db828c666eeed2656988ffe59f6 |
| turbo-cache-control | no-preview |
| go-import | github.com/swiftwasm/JavaScriptKit git https://github.com/swiftwasm/JavaScriptKit.git |
| octolytics-dimension-user_id | 49500752 |
| octolytics-dimension-user_login | swiftwasm |
| octolytics-dimension-repository_id | 244832006 |
| octolytics-dimension-repository_nwo | swiftwasm/JavaScriptKit |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 244832006 |
| octolytics-dimension-repository_network_root_nwo | swiftwasm/JavaScriptKit |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7c85641c598ad130c74f7bcc27f58575cac69551 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width