@oddsquad/vite-plugin-lit 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +2 -0
- package/bin.mjs +20 -0
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CRHFLjFo.js → browser-agent-rpc-BXhoSh1z-F2EfZsFX.js} +3 -3
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CRHFLjFo.js.map → browser-agent-rpc-BXhoSh1z-F2EfZsFX.js.map} +1 -1
- package/dist/client/assets/{index-Gn4Hk_9s.js → index-B2HYO7lk.js} +52 -19
- package/dist/client/assets/index-B2HYO7lk.js.map +1 -0
- package/dist/client/index.html +1 -1
- package/lib/confine.d.ts +22 -0
- package/lib/confine.d.ts.map +1 -0
- package/lib/confine.js +39 -0
- package/lib/confine.js.map +1 -0
- package/lib/devframe/definition.d.ts +10 -2
- package/lib/devframe/definition.d.ts.map +1 -1
- package/lib/devframe/definition.js +31 -9
- package/lib/devframe/definition.js.map +1 -1
- package/lib/devframe/protocol.d.ts +7 -3
- package/lib/devframe/protocol.d.ts.map +1 -1
- package/lib/devframe/protocol.js.map +1 -1
- package/lib/devframe/vite.d.ts.map +1 -1
- package/lib/devframe/vite.js +3 -0
- package/lib/devframe/vite.js.map +1 -1
- package/lib/http.d.ts +5 -0
- package/lib/http.d.ts.map +1 -1
- package/lib/http.js +5 -0
- package/lib/http.js.map +1 -1
- package/lib/plugins/open-in-editor.d.ts.map +1 -1
- package/lib/plugins/open-in-editor.js +11 -19
- package/lib/plugins/open-in-editor.js.map +1 -1
- package/lib/runtime/timeline/chrome-tracks.d.ts +22 -0
- package/lib/runtime/timeline/chrome-tracks.d.ts.map +1 -0
- package/lib/runtime/timeline/chrome-tracks.js +74 -0
- package/lib/runtime/timeline/chrome-tracks.js.map +1 -0
- package/lib/runtime/timeline/clock.d.ts +6 -0
- package/lib/runtime/timeline/clock.d.ts.map +1 -1
- package/lib/runtime/timeline/clock.js +6 -0
- package/lib/runtime/timeline/clock.js.map +1 -1
- package/lib/runtime/timeline/install.js +27 -9
- package/lib/runtime/timeline/install.js.map +1 -1
- package/lib/snapshot.d.ts +2 -1
- package/lib/snapshot.d.ts.map +1 -1
- package/lib/snapshot.js +17 -1
- package/lib/snapshot.js.map +1 -1
- package/lib/source-meta.d.ts.map +1 -1
- package/lib/source-meta.js +40 -1
- package/lib/source-meta.js.map +1 -1
- package/package.json +6 -6
- package/types/timeline.d.ts +6 -0
- package/types/timeline.d.ts.map +1 -1
- package/types/timeline.js.map +1 -1
- package/dist/client/assets/index-Gn4Hk_9s.js.map +0 -1
package/lib/devframe/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/lib/devframe/vite.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Mounts the Lit devframe ({@link createLitDevframe}) into the Vite DevTools\n * hub and bridges the page runtime's `import.meta.hot` transport to the\n * definition's `TimelineSource` port. Everything Vite-specific lives here:\n * the definition itself must keep running under `createDevServer()` with no\n * Vite installed.\n *\n * Vite DevTools 0.7+ is itself built on devframe, so its plugin context is a\n * devframe hub context: `ctx.install(definition)` serves the panel SPA from\n * the definition's `clientAssets`, registers its dock, and exposes its RPC\n * over the hub's authenticated WebSocket. That is all\n * `createPluginFromDevframe()` from `@vitejs/devtools-kit` does, so calling\n * `install` directly keeps DevTools an optional peer we never import at\n * runtime — the hook below is duck-typed, exactly as the panel's previous\n * dock registration was.\n *\n * @see plans/devframe-foundation.md\n */\n\nimport type {Plugin, ViteDevServer} from 'vite';\nimport type {DevframeDefinition} from 'devframe';\nimport {\n INSPECT_CMD_CHANNEL,\n INSPECT_DATA_CHANNEL,\n INSPECT_OVERLAY_TOGGLE_CHANNEL,\n} from '../../types/inspector.js';\nimport type {\n InspectorCommand,\n InspectorMessage,\n} from '../../types/inspector.js';\nimport {HMR_INCOMPATIBLE_CHANNEL} from '../../types/hmr-incompatibility.js';\nimport type {HmrIncompatibilityEvent} from '../../types/hmr-incompatibility.js';\nimport {SETTINGS_OVERRIDE_CHANNEL} from '../../types/timeline.js';\nimport type {\n FeatureSettings,\n SettingsOverride,\n TimelineEvent,\n TimelineLayer,\n TimelineLayersState,\n} from '../../types/timeline.js';\nimport {createLitDevframe} from './definition.js';\nimport {LIT_DEVFRAME_ID} from './protocol.js';\nimport {layersWireFormat} from './source.js';\nimport type {TimelineSink, TimelineSource} from './source.js';\n\nexport {createLitDevframe};\n\n/**\n * The slice of the Vite DevTools hub context this plugin uses. Structurally a\n * subset of `ViteDevToolsNodeContext` from `@vitejs/devtools-kit/node`;\n * declared here so the package never imports that optional peer at runtime.\n * Install `@vitejs/devtools-kit` as a dev dependency to check it against the\n * real declarations.\n */\ninterface DevToolsHubContext {\n readonly viteServer?: ViteDevServer;\n /** Serve a devframe's SPA, register its dock, and run its `setup()`. */\n install: (\n devframe: DevframeDefinition,\n options?: {base?: string; dock?: Record<string, unknown>}\n ) => Promise<void>;\n docks?: {\n activate?: (dockId: string, params?: Record<string, unknown>) => void;\n };\n commands?: {register?: (command: Record<string, unknown>) => unknown};\n /**\n * The hub's host runtime. Structurally the slice of devframe's\n * `DevframeHost` {@link registerInstance} needs; `DevframeHubContext`\n * extends `DevframeNodeContext`, which carries it.\n */\n host?: {resolveOrigin?: () => string};\n}\n\ndeclare module 'vite' {\n interface Plugin {\n /** Vite DevTools plugin hook, contributed by `@vitejs/devtools`. */\n devtools?: {\n capabilities?: {dev?: boolean; build?: boolean};\n setup: (ctx: DevToolsHubContext) => void | Promise<void>;\n };\n }\n}\n\nexport interface CreateLitDevframePluginOptions {\n version: string;\n features?: () => FeatureSettings | null;\n /** See {@link CreateLitDevframeOptions.configuredEditor}. */\n configuredEditor?: () => string | undefined;\n /** Override the built panel SPA directory. Defaults to `dist/client`. */\n clientAssets?: string;\n}\n\n/**\n * `TimelineSource` over a Vite dev server's `hot` channel.\n *\n * `install()` runs the definition's `setup()` — and therefore\n * `source.attach()` — before it resolves, so this is attached before a dev\n * server is necessarily in hand. Calls made before {@link bind} are dropped\n * rather than buffered: nothing can emit a page-runtime event until a page\n * has connected to a bound server.\n */\nexport class HotTimelineSource implements TimelineSource {\n #hot: ViteDevServer['hot'] | undefined;\n #sink: TimelineSink | undefined;\n #hub: DevToolsHubContext | undefined;\n\n /**\n * Wire the source to a live dev server. Call once, from `devtools.setup()`.\n * `hub` is optional: without it the bridge still carries page traffic both\n * ways, it just cannot bring the dock to the front on an overlay pick.\n */\n bind(server: ViteDevServer, hub?: DevToolsHubContext): void {\n this.#hub = hub;\n const hot = server.hot;\n this.#hot = hot;\n\n hot.on('lit:timeline:push-event', (data: {events?: TimelineEvent[]}) => {\n this.#sink?.pushEvents(data.events ?? []);\n });\n\n hot.on('lit:timeline:custom-layer', (data: {layer?: TimelineLayer}) => {\n if (!data.layer?.id) return;\n this.#sink?.addLayer(data.layer);\n });\n\n hot.on(INSPECT_DATA_CHANNEL, (data: InspectorMessage) => {\n // An overlay pick means the developer clicked an element in the page and\n // wants the Components tab. Bring the dock forward from the node side\n // rather than having the panel reach into the parent frame.\n if (data.type === 'pick') {\n // Carry the target with the activation, not just the dock id: the\n // params ride the hub's `devframe:docks:active` state, so a panel\n // that mounts *because of* this pick still lands on the right\n // element instead of racing the separate `pick` message.\n this.#hub?.docks?.activate?.(LIT_DEVFRAME_ID, {componentId: data.id});\n }\n this.#sink?.inspectorMessage(data);\n });\n\n hot.on(HMR_INCOMPATIBLE_CHANNEL, (event: HmrIncompatibilityEvent) => {\n this.#sink?.hmrIncompatible(event);\n });\n\n // The runtime announces itself on every connect. It boots from the\n // compiled-in defaults (recording off), so the session's current state\n // has to be pushed back down or a page that loads while recording is on\n // captures nothing while every surface still reports `recording: true`.\n hot.on('lit:timeline:runtime-ready', () => {\n this.#sink?.runtimeReady();\n });\n }\n\n /** Toggle the page's inspect overlay. Also used by the palette command. */\n toggleOverlay(): void {\n this.#hot?.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);\n }\n\n attach(sink: TimelineSink): () => void {\n this.#sink = sink;\n return () => {\n if (this.#sink === sink) this.#sink = undefined;\n };\n }\n\n sendInspector(cmd: InspectorCommand): void {\n this.#hot?.send(INSPECT_CMD_CHANNEL, cmd);\n }\n\n setRecording(recording: boolean): void {\n this.#hot?.send('lit:timeline:recording-changed', {recording});\n }\n\n setLayers(layers: TimelineLayersState): void {\n this.#hot?.send('lit:timeline:layers-changed', layersWireFormat(layers));\n }\n\n setSettingsOverride(override: SettingsOverride): void {\n this.#hot?.send(SETTINGS_OVERRIDE_CHANNEL, override);\n }\n}\n\n/**\n * One warning per process, not per dev server: a restart-heavy session\n * should not paper the terminal with the same advisory. Carries the reason,\n * because \"discovery unavailable\" with no cause is the kind of message that\n * costs an afternoon when the guess below finally goes stale.\n */\nlet warnedDiscoveryUnavailable = false;\n\nconst warnDiscoveryUnavailable = (reason: string): void => {\n if (warnedDiscoveryUnavailable) return;\n warnedDiscoveryUnavailable = true;\n console.warn(\n `[lit-plugin] devframe instance-registry discovery unavailable ` +\n `(${reason}); stdio MCP clients cannot auto-discover this dev ` +\n `server. The MCP endpoint itself is unaffected — point clients at ` +\n `<dev-server-origin>/__devtools/__mcp directly.`\n );\n};\n\n/**\n * Publish this devframe in devframe's global instance registry\n * (`~/.devframe/instances/`), so stdio MCP connectors — `devframe connect`,\n * this package's own `lit-devtools mcp` — find the running dev server\n * without port guessing.\n *\n * `createDevServer()` registers itself, but this plugin never goes through\n * it: it mounts into the Vite DevTools hub, and `@vitejs/devtools@0.7.5`\n * calls `initHub()` with no `register` option, which defaults to off. So a\n * Vite-hosted devframe is invisible to discovery unless a plugin publishes\n * the record itself — which is all this does. `registerDevframeInstance()`\n * is documented for exactly this case (\"custom hosts that serve a devframe\n * in-process call this explicitly with the origin they are reachable at\"),\n * and the registry is a directory of per-`pid`+`port` JSON files, so\n * registrants coexist.\n *\n * The mount path is the soft spot: `/__devtools/` is where this version of\n * Vite DevTools mounts, not a promise about where a devframe host mounts in\n * general. So the guess is verified before it is published — the same\n * `probeDevframeOrigin()` primitive the registry's own liveness check uses\n * has to answer at that base, or nothing is registered. A record that\n * doesn't answer is worse than no record: an agent dials it and gets 404s\n * from a server that claimed to be there.\n *\n * The MCP path is then taken from what that probe *reports* rather than\n * assumed to be the `__mcp` default, which removes the second guess\n * entirely. Note the two path conventions in play: `__connection.json`\n * serves `mcp.path` **relative** to the base (`\"__mcp\"`), while a registry\n * record stores it **absolute** (`\"/__devtools/__mcp\"`) because the\n * connector dials `origin + record.mcp.path` directly. Joining the two is\n * exactly what devframe's own `probePort()` does with its root-mounted\n * probe.\n *\n * Known sharp edge: records are keyed by `pid`+`port` alone. Another\n * devframe-hosting plugin in the same Vite process would share this file,\n * and whichever unregisters first removes it for both. Single-devframe use\n * is the common case and there is no cross-plugin coordination primitive\n * today.\n */\nasync function registerInstance(\n ctx: DevToolsHubContext,\n name: string\n): Promise<{unregister: () => void} | {reason: string}> {\n // Both imports are dynamic and optional: `@vitejs/devtools-kit` is an\n // optional peer this package must never hard-resolve, and `devframe`'s\n // `internal` subpath is only needed on this path.\n const constants = await import('@vitejs/devtools-kit/constants').catch(\n () => null\n );\n const internal = await import('devframe/internal').catch(() => null);\n if (!constants) return {reason: '@vitejs/devtools-kit not resolvable'};\n if (!internal) return {reason: 'devframe/internal not resolvable'};\n\n const basePath = constants.DEVTOOLS_MOUNT_PATH;\n const origin = ctx.host?.resolveOrigin?.();\n if (!origin) return {reason: 'host origin unavailable'};\n\n const probed = await internal.probeDevframeOrigin(origin, basePath);\n if (!probed) {\n return {reason: `no devframe answered at ${origin}${basePath}`};\n }\n\n // Relative (`\"__mcp\"`) as served, absolute as stored — see above. A host\n // that already reports an absolute path is passed through unchanged.\n const reported = probed.meta.mcp?.path;\n const mcpPath =\n reported === undefined\n ? null\n : reported.startsWith('/')\n ? reported\n : `${basePath}${reported}`;\n\n const {unregister} = internal.registerDevframeInstance({\n pid: process.pid,\n // The probe reports the origin that actually answered — a `localhost`\n // bind may only be dialable as `127.0.0.1` or `[::1]` — so its port is\n // the one truly listening, which `config.server.port` is not when the\n // configured port was taken.\n port: Number(new URL(probed.origin).port) || 0,\n origin: probed.origin,\n basePath,\n id: LIT_DEVFRAME_ID,\n name,\n rootDir: process.cwd(),\n // Registering with `mcp: null` is honest rather than useless: the\n // connector surfaces the instance with its own \"MCP disabled\" hint,\n // which beats the instance being invisible.\n mcp: mcpPath === null ? null : {path: mcpPath},\n startedAt: Date.now(),\n });\n return {unregister};\n}\n\n/**\n * Register once the dev server is actually accepting connections. The\n * DevTools `setup()` hook runs during server construction, well before\n * `listen()` resolves, so probing from there always fails to connect — the\n * record has to wait for the port it is about to advertise to be real.\n */\nfunction registerInstanceWhenListening(\n ctx: DevToolsHubContext,\n name: string\n): void {\n const httpServer = ctx.viteServer?.httpServer;\n if (!httpServer) {\n // Middleware mode: no server of our own to advertise.\n warnDiscoveryUnavailable('no HTTP server (middleware mode?)');\n return;\n }\n\n const run = (): void => {\n registerInstance(ctx, name)\n .then((result) => {\n if ('unregister' in result) {\n httpServer.once('close', result.unregister);\n } else {\n warnDiscoveryUnavailable(result.reason);\n }\n })\n .catch((error: unknown) => {\n warnDiscoveryUnavailable(String(error));\n });\n };\n\n if (httpServer.listening) run();\n else httpServer.once('listening', run);\n}\n\n/**\n * The Vite plugin that mounts the Lit devframe on the DevTools hub. A no-op\n * when DevTools is not active: without it nothing calls `devtools.setup()`,\n * so the definition is never installed and the panel simply isn't there.\n */\nexport function createLitDevframePlugin(\n options: CreateLitDevframePluginOptions\n): Plugin {\n const source = new HotTimelineSource();\n // Only known once `setup()` has the dev server, which is after the\n // definition is built -- hence the getter.\n let viteRoot: string | undefined;\n const definition = createLitDevframe({\n source,\n version: options.version,\n features: options.features,\n configuredEditor: options.configuredEditor,\n clientAssets: options.clientAssets,\n sourceRoot: () => viteRoot,\n });\n\n return {\n name: `devframe:${LIT_DEVFRAME_ID}`,\n devtools: {\n // The panel reflects a running page, so there is nothing to mount\n // during `vite build`. A static snapshot goes through\n // `devframe/adapters/build` instead.\n capabilities: {dev: true, build: false},\n async setup(ctx) {\n // Bind before installing: `install()` runs the definition's `setup()`,\n // which attaches to this source, and the dev server is already\n // available on the context by now.\n if (ctx.viteServer) source.bind(ctx.viteServer, ctx);\n viteRoot = ctx.viteServer?.config.root;\n await ctx.install(definition);\n\n // The overlay picker as a palette command with a managed shortcut.\n // Only meaningful when the source overlay is enabled, since that is\n // what draws the picker in the page.\n if (options.features?.()?.sourceOverlay.enabled) {\n ctx.commands?.register?.({\n id: 'lit:overlay:toggle',\n title: 'Pick Lit Element',\n description:\n 'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',\n icon: 'ph:crosshair-duotone',\n category: 'Lit',\n keybindings: [{key: 'Meta+Shift+E'}],\n handler: () => source.toggleOverlay(),\n });\n }\n\n // Discovery is a convenience on top of a panel that already works,\n // so this never blocks `setup()` and never throws: a failure costs\n // the user the stdio MCP shortcut, and the direct URL still works.\n registerInstanceWhenListening(ctx, definition.name);\n },\n },\n };\n}\n\n/**\n * The page-runtime bridge on its own, for a host that drives its own mount\n * (or a test that wants to observe page traffic without a hub). Bind it to a\n * dev server, then either hand it to {@link createLitDevframe} or\n * {@link TimelineSource.attach} your own sink.\n */\nexport const createHotTimelineSource = (): HotTimelineSource =>\n new HotTimelineSource();\n"]}
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/lib/devframe/vite.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Mounts the Lit devframe ({@link createLitDevframe}) into the Vite DevTools\n * hub and bridges the page runtime's `import.meta.hot` transport to the\n * definition's `TimelineSource` port. Everything Vite-specific lives here:\n * the definition itself must keep running under `createDevServer()` with no\n * Vite installed.\n *\n * Vite DevTools 0.7+ is itself built on devframe, so its plugin context is a\n * devframe hub context: `ctx.install(definition)` serves the panel SPA from\n * the definition's `clientAssets`, registers its dock, and exposes its RPC\n * over the hub's authenticated WebSocket. That is all\n * `createPluginFromDevframe()` from `@vitejs/devtools-kit` does, so calling\n * `install` directly keeps DevTools an optional peer we never import at\n * runtime — the hook below is duck-typed, exactly as the panel's previous\n * dock registration was.\n *\n * @see plans/devframe-foundation.md\n */\n\nimport type {Plugin, ViteDevServer} from 'vite';\nimport type {DevframeDefinition} from 'devframe';\nimport {\n INSPECT_CMD_CHANNEL,\n INSPECT_DATA_CHANNEL,\n INSPECT_OVERLAY_TOGGLE_CHANNEL,\n} from '../../types/inspector.js';\nimport type {\n InspectorCommand,\n InspectorMessage,\n} from '../../types/inspector.js';\nimport {HMR_INCOMPATIBLE_CHANNEL} from '../../types/hmr-incompatibility.js';\nimport type {HmrIncompatibilityEvent} from '../../types/hmr-incompatibility.js';\nimport {SETTINGS_OVERRIDE_CHANNEL} from '../../types/timeline.js';\nimport type {\n FeatureSettings,\n SettingsOverride,\n TimelineEvent,\n TimelineLayer,\n TimelineLayersState,\n} from '../../types/timeline.js';\nimport {createLitDevframe} from './definition.js';\nimport {LIT_DEVFRAME_ID} from './protocol.js';\nimport {layersWireFormat} from './source.js';\nimport type {TimelineSink, TimelineSource} from './source.js';\n\nexport {createLitDevframe};\n\n/**\n * The slice of the Vite DevTools hub context this plugin uses. Structurally a\n * subset of `ViteDevToolsNodeContext` from `@vitejs/devtools-kit/node`;\n * declared here so the package never imports that optional peer at runtime.\n * Install `@vitejs/devtools-kit` as a dev dependency to check it against the\n * real declarations.\n */\ninterface DevToolsHubContext {\n readonly viteServer?: ViteDevServer;\n /** Serve a devframe's SPA, register its dock, and run its `setup()`. */\n install: (\n devframe: DevframeDefinition,\n options?: {base?: string; dock?: Record<string, unknown>}\n ) => Promise<void>;\n docks?: {\n activate?: (dockId: string, params?: Record<string, unknown>) => void;\n };\n commands?: {register?: (command: Record<string, unknown>) => unknown};\n /**\n * The hub's host runtime. Structurally the slice of devframe's\n * `DevframeHost` {@link registerInstance} needs; `DevframeHubContext`\n * extends `DevframeNodeContext`, which carries it.\n */\n host?: {resolveOrigin?: () => string};\n}\n\ndeclare module 'vite' {\n interface Plugin {\n /** Vite DevTools plugin hook, contributed by `@vitejs/devtools`. */\n devtools?: {\n capabilities?: {dev?: boolean; build?: boolean};\n setup: (ctx: DevToolsHubContext) => void | Promise<void>;\n };\n }\n}\n\nexport interface CreateLitDevframePluginOptions {\n version: string;\n features?: () => FeatureSettings | null;\n /** See {@link CreateLitDevframeOptions.configuredEditor}. */\n configuredEditor?: () => string | undefined;\n /** Override the built panel SPA directory. Defaults to `dist/client`. */\n clientAssets?: string;\n}\n\n/**\n * `TimelineSource` over a Vite dev server's `hot` channel.\n *\n * `install()` runs the definition's `setup()` — and therefore\n * `source.attach()` — before it resolves, so this is attached before a dev\n * server is necessarily in hand. Calls made before {@link bind} are dropped\n * rather than buffered: nothing can emit a page-runtime event until a page\n * has connected to a bound server.\n */\nexport class HotTimelineSource implements TimelineSource {\n #hot: ViteDevServer['hot'] | undefined;\n #sink: TimelineSink | undefined;\n #hub: DevToolsHubContext | undefined;\n\n /**\n * Wire the source to a live dev server. Call once, from `devtools.setup()`.\n * `hub` is optional: without it the bridge still carries page traffic both\n * ways, it just cannot bring the dock to the front on an overlay pick.\n */\n bind(server: ViteDevServer, hub?: DevToolsHubContext): void {\n this.#hub = hub;\n const hot = server.hot;\n this.#hot = hot;\n\n hot.on('lit:timeline:push-event', (data: {events?: TimelineEvent[]}) => {\n this.#sink?.pushEvents(data.events ?? []);\n });\n\n hot.on('lit:timeline:custom-layer', (data: {layer?: TimelineLayer}) => {\n if (!data.layer?.id) return;\n this.#sink?.addLayer(data.layer);\n });\n\n hot.on(INSPECT_DATA_CHANNEL, (data: InspectorMessage) => {\n // An overlay pick means the developer clicked an element in the page and\n // wants the Components tab. Bring the dock forward from the node side\n // rather than having the panel reach into the parent frame.\n if (data.type === 'pick') {\n // Carry the target with the activation, not just the dock id: the\n // params ride the hub's `devframe:docks:active` state, so a panel\n // that mounts *because of* this pick still lands on the right\n // element instead of racing the separate `pick` message.\n this.#hub?.docks?.activate?.(LIT_DEVFRAME_ID, {componentId: data.id});\n }\n this.#sink?.inspectorMessage(data);\n });\n\n hot.on(HMR_INCOMPATIBLE_CHANNEL, (event: HmrIncompatibilityEvent) => {\n this.#sink?.hmrIncompatible(event);\n });\n\n // The runtime announces itself on every connect. It boots from the\n // compiled-in defaults (recording off), so the session's current state\n // has to be pushed back down or a page that loads while recording is on\n // captures nothing while every surface still reports `recording: true`.\n hot.on('lit:timeline:runtime-ready', () => {\n this.#sink?.runtimeReady();\n });\n }\n\n /** Toggle the page's inspect overlay. Also used by the palette command. */\n toggleOverlay(): void {\n this.#hot?.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);\n }\n\n attach(sink: TimelineSink): () => void {\n this.#sink = sink;\n return () => {\n if (this.#sink === sink) this.#sink = undefined;\n };\n }\n\n sendInspector(cmd: InspectorCommand): void {\n this.#hot?.send(INSPECT_CMD_CHANNEL, cmd);\n }\n\n setRecording(recording: boolean): void {\n this.#hot?.send('lit:timeline:recording-changed', {recording});\n }\n\n setLayers(layers: TimelineLayersState): void {\n this.#hot?.send('lit:timeline:layers-changed', layersWireFormat(layers));\n }\n\n setSettingsOverride(override: SettingsOverride): void {\n this.#hot?.send(SETTINGS_OVERRIDE_CHANNEL, override);\n }\n}\n\n/**\n * One warning per process, not per dev server: a restart-heavy session\n * should not paper the terminal with the same advisory. Carries the reason,\n * because \"discovery unavailable\" with no cause is the kind of message that\n * costs an afternoon when the guess below finally goes stale.\n */\nlet warnedDiscoveryUnavailable = false;\n\nconst warnDiscoveryUnavailable = (reason: string): void => {\n if (warnedDiscoveryUnavailable) return;\n warnedDiscoveryUnavailable = true;\n console.warn(\n `[lit-plugin] devframe instance-registry discovery unavailable ` +\n `(${reason}); stdio MCP clients cannot auto-discover this dev ` +\n `server. The MCP endpoint itself is unaffected — point clients at ` +\n `<dev-server-origin>/__devtools/__mcp directly.`\n );\n};\n\n/**\n * Publish this devframe in devframe's global instance registry\n * (`~/.devframe/instances/`), so stdio MCP connectors — `devframe connect`,\n * this package's own `lit-devtools mcp` — find the running dev server\n * without port guessing.\n *\n * `createDevServer()` registers itself, but this plugin never goes through\n * it: it mounts into the Vite DevTools hub, and `@vitejs/devtools@0.7.5`\n * calls `initHub()` with no `register` option, which defaults to off. So a\n * Vite-hosted devframe is invisible to discovery unless a plugin publishes\n * the record itself — which is all this does. `registerDevframeInstance()`\n * is documented for exactly this case (\"custom hosts that serve a devframe\n * in-process call this explicitly with the origin they are reachable at\"),\n * and the registry is a directory of per-`pid`+`port` JSON files, so\n * registrants coexist.\n *\n * The mount path is the soft spot: `/__devtools/` is where this version of\n * Vite DevTools mounts, not a promise about where a devframe host mounts in\n * general. So the guess is verified before it is published — the same\n * `probeDevframeOrigin()` primitive the registry's own liveness check uses\n * has to answer at that base, or nothing is registered. A record that\n * doesn't answer is worse than no record: an agent dials it and gets 404s\n * from a server that claimed to be there.\n *\n * The MCP path is then taken from what that probe *reports* rather than\n * assumed to be the `__mcp` default, which removes the second guess\n * entirely. Note the two path conventions in play: `__connection.json`\n * serves `mcp.path` **relative** to the base (`\"__mcp\"`), while a registry\n * record stores it **absolute** (`\"/__devtools/__mcp\"`) because the\n * connector dials `origin + record.mcp.path` directly. Joining the two is\n * exactly what devframe's own `probePort()` does with its root-mounted\n * probe.\n *\n * Known sharp edge: records are keyed by `pid`+`port` alone. Another\n * devframe-hosting plugin in the same Vite process would share this file,\n * and whichever unregisters first removes it for both. Single-devframe use\n * is the common case and there is no cross-plugin coordination primitive\n * today.\n */\nasync function registerInstance(\n ctx: DevToolsHubContext,\n name: string\n): Promise<{unregister: () => void} | {reason: string}> {\n // Both imports are dynamic and optional: `@vitejs/devtools-kit` is an\n // optional peer this package must never hard-resolve, and `devframe`'s\n // `internal` subpath is only needed on this path.\n const constants = await import('@vitejs/devtools-kit/constants').catch(\n () => null\n );\n const internal = await import('devframe/internal').catch(() => null);\n if (!constants) return {reason: '@vitejs/devtools-kit not resolvable'};\n if (!internal) return {reason: 'devframe/internal not resolvable'};\n\n const basePath = constants.DEVTOOLS_MOUNT_PATH;\n const origin = ctx.host?.resolveOrigin?.();\n if (!origin) return {reason: 'host origin unavailable'};\n\n const probed = await internal.probeDevframeOrigin(origin, basePath);\n if (!probed) {\n return {reason: `no devframe answered at ${origin}${basePath}`};\n }\n\n // Relative (`\"__mcp\"`) as served, absolute as stored — see above. A host\n // that already reports an absolute path is passed through unchanged.\n const reported = probed.meta.mcp?.path;\n const mcpPath =\n reported === undefined\n ? null\n : reported.startsWith('/')\n ? reported\n : `${basePath}${reported}`;\n\n const {unregister} = internal.registerDevframeInstance({\n pid: process.pid,\n // The probe reports the origin that actually answered — a `localhost`\n // bind may only be dialable as `127.0.0.1` or `[::1]` — so its port is\n // the one truly listening, which `config.server.port` is not when the\n // configured port was taken.\n port: Number(new URL(probed.origin).port) || 0,\n origin: probed.origin,\n basePath,\n id: LIT_DEVFRAME_ID,\n name,\n rootDir: process.cwd(),\n // Registering with `mcp: null` is honest rather than useless: the\n // connector surfaces the instance with its own \"MCP disabled\" hint,\n // which beats the instance being invisible.\n mcp: mcpPath === null ? null : {path: mcpPath},\n startedAt: Date.now(),\n });\n return {unregister};\n}\n\n/**\n * Register once the dev server is actually accepting connections. The\n * DevTools `setup()` hook runs during server construction, well before\n * `listen()` resolves, so probing from there always fails to connect — the\n * record has to wait for the port it is about to advertise to be real.\n */\nfunction registerInstanceWhenListening(\n ctx: DevToolsHubContext,\n name: string\n): void {\n const httpServer = ctx.viteServer?.httpServer;\n if (!httpServer) {\n // Middleware mode: no server of our own to advertise.\n warnDiscoveryUnavailable('no HTTP server (middleware mode?)');\n return;\n }\n\n const run = (): void => {\n registerInstance(ctx, name)\n .then((result) => {\n if ('unregister' in result) {\n httpServer.once('close', result.unregister);\n } else {\n warnDiscoveryUnavailable(result.reason);\n }\n })\n .catch((error: unknown) => {\n warnDiscoveryUnavailable(String(error));\n });\n };\n\n if (httpServer.listening) run();\n else httpServer.once('listening', run);\n}\n\n/**\n * The Vite plugin that mounts the Lit devframe on the DevTools hub. A no-op\n * when DevTools is not active: without it nothing calls `devtools.setup()`,\n * so the definition is never installed and the panel simply isn't there.\n */\nexport function createLitDevframePlugin(\n options: CreateLitDevframePluginOptions\n): Plugin {\n const source = new HotTimelineSource();\n // Only known once `setup()` has the dev server, which is after the\n // definition is built -- hence the getter.\n let viteRoot: string | undefined;\n let fsAllow: readonly string[] = [];\n const definition = createLitDevframe({\n source,\n version: options.version,\n features: options.features,\n configuredEditor: options.configuredEditor,\n clientAssets: options.clientAssets,\n sourceRoot: () => viteRoot,\n allowedRoots: () => fsAllow,\n });\n\n return {\n name: `devframe:${LIT_DEVFRAME_ID}`,\n devtools: {\n // The panel reflects a running page, so there is nothing to mount\n // during `vite build`. A static snapshot goes through\n // `devframe/adapters/build` instead.\n capabilities: {dev: true, build: false},\n async setup(ctx) {\n // Bind before installing: `install()` runs the definition's `setup()`,\n // which attaches to this source, and the dev server is already\n // available on the context by now.\n if (ctx.viteServer) source.bind(ctx.viteServer, ctx);\n viteRoot = ctx.viteServer?.config.root;\n fsAllow = ctx.viteServer?.config.server.fs?.allow ?? [];\n await ctx.install(definition);\n\n // The overlay picker as a palette command with a managed shortcut.\n // Only meaningful when the source overlay is enabled, since that is\n // what draws the picker in the page.\n if (options.features?.()?.sourceOverlay.enabled) {\n ctx.commands?.register?.({\n id: 'lit:overlay:toggle',\n title: 'Pick Lit Element',\n description:\n 'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',\n icon: 'ph:crosshair-duotone',\n category: 'Lit',\n keybindings: [{key: 'Meta+Shift+E'}],\n handler: () => source.toggleOverlay(),\n });\n }\n\n // Discovery is a convenience on top of a panel that already works,\n // so this never blocks `setup()` and never throws: a failure costs\n // the user the stdio MCP shortcut, and the direct URL still works.\n registerInstanceWhenListening(ctx, definition.name);\n },\n },\n };\n}\n\n/**\n * The page-runtime bridge on its own, for a host that drives its own mount\n * (or a test that wants to observe page traffic without a hub). Bind it to a\n * dev server, then either hand it to {@link createLitDevframe} or\n * {@link TimelineSource.attach} your own sink.\n */\nexport const createHotTimelineSource = (): HotTimelineSource =>\n new HotTimelineSource();\n"]}
|
package/lib/http.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface TrustHeaders {
|
|
|
24
24
|
* user-typed URL) are ours, `cross-site`/`same-site` are not.
|
|
25
25
|
* A request is trusted only when every signal it does carry says same-origin,
|
|
26
26
|
* and at least one of them is present.
|
|
27
|
+
*
|
|
28
|
+
* This does not stop DNS rebinding, where a hostile page reaches the server
|
|
29
|
+
* under its own name and `Origin` and `Host` agree. Vite's
|
|
30
|
+
* `server.allowedHosts` check does, and it runs before any plugin middleware,
|
|
31
|
+
* so this doesn't repeat it (source-overlay_test pins that ordering).
|
|
27
32
|
*/
|
|
28
33
|
export declare const isTrustedRequest: (headers: TrustHeaders) => boolean;
|
|
29
34
|
//# sourceMappingURL=http.d.ts.map
|
package/lib/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/lib/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/lib/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,YAAa,YAAY,KAAG,OAgBxD,CAAC"}
|
package/lib/http.js
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
* user-typed URL) are ours, `cross-site`/`same-site` are not.
|
|
15
15
|
* A request is trusted only when every signal it does carry says same-origin,
|
|
16
16
|
* and at least one of them is present.
|
|
17
|
+
*
|
|
18
|
+
* This does not stop DNS rebinding, where a hostile page reaches the server
|
|
19
|
+
* under its own name and `Origin` and `Host` agree. Vite's
|
|
20
|
+
* `server.allowedHosts` check does, and it runs before any plugin middleware,
|
|
21
|
+
* so this doesn't repeat it (source-overlay_test pins that ordering).
|
|
17
22
|
*/
|
|
18
23
|
export const isTrustedRequest = (headers) => {
|
|
19
24
|
const site = headers['sec-fetch-site'];
|
package/lib/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/lib/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/lib/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAqB,EAAW,EAAE;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;IAC9D,CAAC;IACD,MAAM,EAAC,MAAM,EAAE,IAAI,EAAC,GAAG,OAAO,CAAC;IAC/B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9C,IAAI,CAAC;YACH,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,sEAAsE;IACtE,OAAO,IAAI,KAAK,SAAS,CAAC;AAC5B,CAAC,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Headers the trust check looks at. Both the open-in-editor endpoint and the\n * timeline/inspector endpoints are local-only dev tooling: only the page the\n * dev server itself served may drive them.\n */\nexport interface TrustHeaders {\n origin?: string;\n host?: string;\n 'sec-fetch-site'?: string;\n}\n\n/**\n * True when the request demonstrably comes from the dev server's own page.\n *\n * Two independent signals, because neither is present on every request:\n * - `Origin` is sent on CORS-mode fetches and on every non-GET/HEAD request,\n * but NOT on a plain cross-site GET (`<img>`, `<iframe>`, a navigation).\n * - `Sec-Fetch-Site` is sent by Chromium and Firefox on every request and is\n * the signal that covers exactly that gap; `same-origin` and `none` (a\n * user-typed URL) are ours, `cross-site`/`same-site` are not.\n * A request is trusted only when every signal it does carry says same-origin,\n * and at least one of them is present.\n *\n * This does not stop DNS rebinding, where a hostile page reaches the server\n * under its own name and `Origin` and `Host` agree. Vite's\n * `server.allowedHosts` check does, and it runs before any plugin middleware,\n * so this doesn't repeat it (source-overlay_test pins that ordering).\n */\nexport const isTrustedRequest = (headers: TrustHeaders): boolean => {\n const site = headers['sec-fetch-site'];\n if (site !== undefined) {\n if (site !== 'same-origin' && site !== 'none') return false;\n }\n const {origin, host} = headers;\n if (origin !== undefined && origin !== 'null') {\n try {\n if (new URL(origin).host !== host) return false;\n } catch {\n return false;\n }\n return true;\n }\n // No `Origin`: trust only when `Sec-Fetch-Site` vouched for it above.\n return site !== undefined;\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-in-editor.d.ts","sourceRoot":"","sources":["../../src/lib/plugins/open-in-editor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAmB,KAAK,YAAY,EAAC,MAAM,YAAY,CAAC;AAE/D,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,eAAO,MAAM,4BAA4B,iBACzB,SAAS,MAAM,EAAE,WAMxB;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAC,OACvD;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAC,QAC/C,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"open-in-editor.d.ts","sourceRoot":"","sources":["../../src/lib/plugins/open-in-editor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAmB,KAAK,YAAY,EAAC,MAAM,YAAY,CAAC;AAE/D,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,eAAO,MAAM,4BAA4B,iBACzB,SAAS,MAAM,EAAE,WAMxB;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAC,OACvD;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAC,QAC/C,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,SAyFhC,CAAC"}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Copyright 2026 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import {resolve as resolvePath} from 'node:path';
|
|
7
|
+
import {confineToRoots} from '../confine.js';
|
|
8
8
|
import {toLaunchEditor} from '../devframe/launch-editor.js';
|
|
9
9
|
import {isTrustedRequest} from '../http.js';
|
|
10
10
|
export const OPEN_IN_EDITOR_PATH = '/__lit-open-in-editor';
|
|
@@ -37,25 +37,17 @@ export const createOpenInEditorMiddleware = (allowedRoots) => {
|
|
|
37
37
|
res.end('missing file parameter');
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
// Confine the open to the allowed roots:
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(
|
|
48
|
-
);
|
|
49
|
-
if (!allowed) {
|
|
50
|
-
res.statusCode = 403;
|
|
51
|
-
res.end('file outside allowed roots');
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (!existsSync(resolved)) {
|
|
55
|
-
res.statusCode = 404;
|
|
56
|
-
res.end('file not found');
|
|
40
|
+
// Confine the open to the allowed roots: `launch-editor` spawns the
|
|
41
|
+
// user's editor on this path, so an unvalidated `file` is a
|
|
42
|
+
// local-file-open / arg-injection vector.
|
|
43
|
+
const confined = confineToRoots(roots, file);
|
|
44
|
+
if ('failure' in confined) {
|
|
45
|
+
const outside = confined.failure === 'outside';
|
|
46
|
+
res.statusCode = outside ? 403 : 404;
|
|
47
|
+
res.end(outside ? 'file outside allowed roots' : 'file not found');
|
|
57
48
|
return;
|
|
58
49
|
}
|
|
50
|
+
const resolved = confined.path;
|
|
59
51
|
// Coerce to integers so a crafted `line`/`column` can't smuggle extra
|
|
60
52
|
// shell-visible content through the `file:line:column` ref.
|
|
61
53
|
const line = String(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-in-editor.js","sourceRoot":"","sources":["../../src/lib/plugins/open-in-editor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"open-in-editor.js","sourceRoot":"","sources":["../../src/lib/plugins/open-in-editor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\nimport {resolve as resolvePath} from 'node:path';\nimport {confineToRoots} from '../confine.js';\nimport {toLaunchEditor} from '../devframe/launch-editor.js';\nimport {isTrustedRequest, type TrustHeaders} from '../http.js';\n\nexport const OPEN_IN_EDITOR_PATH = '/__lit-open-in-editor';\n\nexport const createOpenInEditorMiddleware = (\n allowedRoots: readonly string[]\n) => {\n // First entry is the primary root: relative `file` params resolve against\n // it. Every entry grants open access to files beneath it.\n const roots = allowedRoots.map((r) => resolvePath(r));\n return (\n req: {url?: string; method?: string; headers?: TrustHeaders},\n res: {statusCode: number; end: (msg: string) => void},\n next: (err?: unknown) => void\n ) => {\n if (req.url === undefined) {\n next();\n return;\n }\n if (req.method !== undefined && req.method !== 'GET') {\n res.statusCode = 405;\n res.end('method not allowed');\n return;\n }\n if (!isTrustedRequest(req.headers ?? {})) {\n res.statusCode = 403;\n res.end('forbidden');\n return;\n }\n const query = req.url.includes('?')\n ? req.url.slice(req.url.indexOf('?') + 1)\n : '';\n const params = new URLSearchParams(query);\n const file = params.get('file');\n if (file === null) {\n res.statusCode = 400;\n res.end('missing file parameter');\n return;\n }\n // Confine the open to the allowed roots: `launch-editor` spawns the\n // user's editor on this path, so an unvalidated `file` is a\n // local-file-open / arg-injection vector.\n const confined = confineToRoots(roots, file);\n if ('failure' in confined) {\n const outside = confined.failure === 'outside';\n res.statusCode = outside ? 403 : 404;\n res.end(outside ? 'file outside allowed roots' : 'file not found');\n return;\n }\n const resolved = confined.path;\n // Coerce to integers so a crafted `line`/`column` can't smuggle extra\n // shell-visible content through the `file:line:column` ref.\n const line = String(\n Math.max(1, Number.parseInt(params.get('line') ?? '1', 10) || 1)\n );\n const column = String(\n Math.max(1, Number.parseInt(params.get('column') ?? '1', 10) || 1)\n );\n // The overlay names the editor it is using (the panel's override, else the\n // configured one). Only keys `toLaunchEditor` knows become a command;\n // anything else, or nothing, leaves `launch-editor` to auto-detect.\n const editor = toLaunchEditor(params.get('editor') ?? undefined);\n const fileRef = `${resolved}:${line}:${column}`;\n import('launch-editor')\n .then(\n (mod: {\n default?: (\n file: string,\n editor: string | undefined,\n cb: (fileName: string, errorMessage: string | null) => void\n ) => void;\n }) => {\n const launch = (mod.default ?? mod) as (\n file: string,\n editor: string | undefined,\n cb: (fileName: string, errorMessage: string | null) => void\n ) => void;\n // `launch-editor` invokes the callback only on failure — synchronously\n // when it can't guess an editor, later if the spawn errors. Reply `ok`\n // right after launching; the guard keeps a sync failure's 500 first\n // and drops a late async failure (the response is long gone).\n let finished = false;\n const finish = (statusCode: number, msg: string) => {\n if (finished) return;\n finished = true;\n res.statusCode = statusCode;\n res.end(msg);\n };\n launch(\n fileRef,\n editor,\n (_fileName: string, errorMessage: string | null) => {\n if (errorMessage !== null) {\n finish(500, errorMessage);\n }\n }\n );\n finish(200, 'ok');\n }\n )\n .catch(next);\n };\n};\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors timeline events into Chrome DevTools' Performance panel as custom
|
|
8
|
+
* tracks, via `console.timeStamp(label, start, end, track, group, color)`.
|
|
9
|
+
*
|
|
10
|
+
* Start/end are `performance.now()` values. Paired `:start`/`:end` events
|
|
11
|
+
* become a range; everything else becomes a zero-length marker.
|
|
12
|
+
*/
|
|
13
|
+
import type { TimelineEvent } from '../../../types/timeline.js';
|
|
14
|
+
type TrackColor = 'primary' | 'primary-light' | 'primary-dark' | 'secondary' | 'secondary-light' | 'secondary-dark' | 'tertiary' | 'tertiary-light' | 'tertiary-dark' | 'error';
|
|
15
|
+
export type TimeStamp = (label: string, start: number, end: number, track: string, group: string, color: TrackColor) => void;
|
|
16
|
+
export interface ChromeTracksSink {
|
|
17
|
+
push(event: TimelineEvent): void;
|
|
18
|
+
reset(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const createChromeTracksSink: (stamp?: TimeStamp, toPerf?: (t: number) => number) => ChromeTracksSink;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=chrome-tracks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-tracks.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/chrome-tracks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAG9D,KAAK,UAAU,GACX,SAAS,GACT,eAAe,GACf,cAAc,GACd,WAAW,GACX,iBAAiB,GACjB,gBAAgB,GAChB,UAAU,GACV,gBAAgB,GAChB,eAAe,GACf,OAAO,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,KACd,IAAI,CAAC;AAEV,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,KAAK,IAAI,IAAI,CAAC;CACf;AAuBD,eAAO,MAAM,sBAAsB,WAC1B,SAAS,WACR,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC5B,gBAgDF,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
import {toPerfTime} from './clock.js';
|
|
7
|
+
const GROUP = 'Lit';
|
|
8
|
+
const MAX_PENDING = 1000;
|
|
9
|
+
const TRACKS = {
|
|
10
|
+
'lit-lifecycle': {track: 'Lifecycle', color: 'primary', lit: true},
|
|
11
|
+
'lit-render': {track: 'Render', color: 'secondary', lit: true},
|
|
12
|
+
'lit-render-verbose': {
|
|
13
|
+
track: 'Render (verbose)',
|
|
14
|
+
color: 'secondary-light',
|
|
15
|
+
lit: true,
|
|
16
|
+
},
|
|
17
|
+
mouse: {track: 'Input', color: 'tertiary', lit: false},
|
|
18
|
+
keyboard: {track: 'Input', color: 'tertiary', lit: false},
|
|
19
|
+
};
|
|
20
|
+
const consoleStamp = (...args) => {
|
|
21
|
+
const c = console;
|
|
22
|
+
if (typeof c.timeStamp === 'function') c.timeStamp(...args);
|
|
23
|
+
};
|
|
24
|
+
export const createChromeTracksSink = (
|
|
25
|
+
stamp = consoleStamp,
|
|
26
|
+
toPerf = toPerfTime
|
|
27
|
+
) => {
|
|
28
|
+
// Start times are converted at receipt because the clock epoch can reset
|
|
29
|
+
// before the matching :end arrives.
|
|
30
|
+
const pending = new Map();
|
|
31
|
+
return {
|
|
32
|
+
push(event) {
|
|
33
|
+
const cfg = TRACKS[event.layerId];
|
|
34
|
+
if (cfg === undefined) return;
|
|
35
|
+
const title = event.title ?? '';
|
|
36
|
+
const isError = event.logType === 'error';
|
|
37
|
+
const isStart = title.endsWith(':start');
|
|
38
|
+
const isEnd = title.endsWith(':end');
|
|
39
|
+
const phase =
|
|
40
|
+
isStart || isEnd ? title.slice(0, title.lastIndexOf(':')) : title;
|
|
41
|
+
const tag = cfg.lit ? (event.meta?.tagName ?? event.subtitle) : undefined;
|
|
42
|
+
const label = tag ? `<${tag}> ${phase}` : phase;
|
|
43
|
+
if (isStart) {
|
|
44
|
+
const key = `${event.layerId}|${event.groupId}|${phase}`;
|
|
45
|
+
pending.delete(key);
|
|
46
|
+
pending.set(key, {start: toPerf(event.time), error: isError});
|
|
47
|
+
if (pending.size > MAX_PENDING) {
|
|
48
|
+
const oldest = pending.keys().next().value;
|
|
49
|
+
if (oldest !== undefined) pending.delete(oldest);
|
|
50
|
+
}
|
|
51
|
+
} else if (isEnd) {
|
|
52
|
+
const key = `${event.layerId}|${event.groupId}|${phase}`;
|
|
53
|
+
const p = pending.get(key);
|
|
54
|
+
if (p === undefined) return;
|
|
55
|
+
pending.delete(key);
|
|
56
|
+
stamp(
|
|
57
|
+
label,
|
|
58
|
+
p.start,
|
|
59
|
+
toPerf(event.time),
|
|
60
|
+
cfg.track,
|
|
61
|
+
GROUP,
|
|
62
|
+
p.error || isError ? 'error' : cfg.color
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
const t = toPerf(event.time);
|
|
66
|
+
stamp(label, t, t, cfg.track, GROUP, isError ? 'error' : cfg.color);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
reset() {
|
|
70
|
+
pending.clear();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=chrome-tracks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-tracks.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/chrome-tracks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Mirrors timeline events into Chrome DevTools' Performance panel as custom\n * tracks, via `console.timeStamp(label, start, end, track, group, color)`.\n *\n * Start/end are `performance.now()` values. Paired `:start`/`:end` events\n * become a range; everything else becomes a zero-length marker.\n */\n\nimport type {TimelineEvent} from '../../../types/timeline.js';\nimport {toPerfTime} from './clock.js';\n\ntype TrackColor =\n | 'primary'\n | 'primary-light'\n | 'primary-dark'\n | 'secondary'\n | 'secondary-light'\n | 'secondary-dark'\n | 'tertiary'\n | 'tertiary-light'\n | 'tertiary-dark'\n | 'error';\n\nexport type TimeStamp = (\n label: string,\n start: number,\n end: number,\n track: string,\n group: string,\n color: TrackColor\n) => void;\n\nexport interface ChromeTracksSink {\n push(event: TimelineEvent): void;\n reset(): void;\n}\n\nconst GROUP = 'Lit';\nconst MAX_PENDING = 1000;\n\nconst TRACKS: Record<string, {track: string; color: TrackColor; lit: boolean}> =\n {\n 'lit-lifecycle': {track: 'Lifecycle', color: 'primary', lit: true},\n 'lit-render': {track: 'Render', color: 'secondary', lit: true},\n 'lit-render-verbose': {\n track: 'Render (verbose)',\n color: 'secondary-light',\n lit: true,\n },\n mouse: {track: 'Input', color: 'tertiary', lit: false},\n keyboard: {track: 'Input', color: 'tertiary', lit: false},\n };\n\nconst consoleStamp: TimeStamp = (...args) => {\n const c = console as unknown as {timeStamp?: TimeStamp};\n if (typeof c.timeStamp === 'function') c.timeStamp(...args);\n};\n\nexport const createChromeTracksSink = (\n stamp: TimeStamp = consoleStamp,\n toPerf: (t: number) => number = toPerfTime\n): ChromeTracksSink => {\n // Start times are converted at receipt because the clock epoch can reset\n // before the matching :end arrives.\n const pending = new Map<string, {start: number; error: boolean}>();\n\n return {\n push(event) {\n const cfg = TRACKS[event.layerId];\n if (cfg === undefined) return;\n const title = event.title ?? '';\n const isError = event.logType === 'error';\n const isStart = title.endsWith(':start');\n const isEnd = title.endsWith(':end');\n const phase =\n isStart || isEnd ? title.slice(0, title.lastIndexOf(':')) : title;\n const tag = cfg.lit ? (event.meta?.tagName ?? event.subtitle) : undefined;\n const label = tag ? `<${tag}> ${phase}` : phase;\n\n if (isStart) {\n const key = `${event.layerId}|${event.groupId}|${phase}`;\n pending.delete(key);\n pending.set(key, {start: toPerf(event.time), error: isError});\n if (pending.size > MAX_PENDING) {\n const oldest = pending.keys().next().value;\n if (oldest !== undefined) pending.delete(oldest);\n }\n } else if (isEnd) {\n const key = `${event.layerId}|${event.groupId}|${phase}`;\n const p = pending.get(key);\n if (p === undefined) return;\n pending.delete(key);\n stamp(\n label,\n p.start,\n toPerf(event.time),\n cfg.track,\n GROUP,\n p.error || isError ? 'error' : cfg.color\n );\n } else {\n const t = toPerf(event.time);\n stamp(label, t, t, cfg.track, GROUP, isError ? 'error' : cfg.color);\n }\n },\n reset() {\n pending.clear();\n },\n };\n};\n"]}
|
|
@@ -7,4 +7,10 @@
|
|
|
7
7
|
export declare const resetClock: () => void;
|
|
8
8
|
/** Milliseconds since the last {@link resetClock}. */
|
|
9
9
|
export declare const now: () => number;
|
|
10
|
+
/**
|
|
11
|
+
* Convert a recording-relative time (from {@link now}) back to a
|
|
12
|
+
* `performance.now()` value, i.e. milliseconds since `performance.timeOrigin`.
|
|
13
|
+
* Chrome DevTools' `console.timeStamp` expects that timebase.
|
|
14
|
+
*/
|
|
15
|
+
export declare const toPerfTime: (t: number) => number;
|
|
10
16
|
//# sourceMappingURL=clock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/clock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,qEAAqE;AACrE,eAAO,MAAM,UAAU,QAAO,IAE7B,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,GAAG,QAAO,MAAmC,CAAC"}
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/clock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,qEAAqE;AACrE,eAAO,MAAM,UAAU,QAAO,IAE7B,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,GAAG,QAAO,MAAmC,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,MAAO,MAAM,KAAG,MAAmB,CAAC"}
|
|
@@ -19,4 +19,10 @@ export const resetClock = () => {
|
|
|
19
19
|
};
|
|
20
20
|
/** Milliseconds since the last {@link resetClock}. */
|
|
21
21
|
export const now = () => performance.now() - epoch;
|
|
22
|
+
/**
|
|
23
|
+
* Convert a recording-relative time (from {@link now}) back to a
|
|
24
|
+
* `performance.now()` value, i.e. milliseconds since `performance.timeOrigin`.
|
|
25
|
+
* Chrome DevTools' `console.timeStamp` expects that timebase.
|
|
26
|
+
*/
|
|
27
|
+
export const toPerfTime = (t) => t + epoch;
|
|
22
28
|
//# sourceMappingURL=clock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/clock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH,IAAI,KAAK,GAAG,CAAC,CAAC;AAEd,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAAG,GAAS,EAAE;IACnC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;AAC5B,CAAC,CAAC;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,GAAG,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Recording-relative clock for the timeline.\n *\n * Built-in capture layers timestamp events with {@link now} rather than raw\n * `performance.now()`, so event times read as \"ms since recording started\"\n * (the first event is ~0) instead of \"ms since the app page loaded\" — which is\n * also meaningless to the panel, an iframe with its own time origin. The origin\n * is re-zeroed on each rising edge of recording via {@link resetClock}.\n */\n\nlet epoch = 0;\n\n/** Re-zero the clock; called when recording transitions off → on. */\nexport const resetClock = (): void => {\n epoch = performance.now();\n};\n\n/** Milliseconds since the last {@link resetClock}. */\nexport const now = (): number => performance.now() - epoch;\n"]}
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/clock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH,IAAI,KAAK,GAAG,CAAC,CAAC;AAEd,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAAG,GAAS,EAAE;IACnC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;AAC5B,CAAC,CAAC;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,GAAG,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;AAE3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Recording-relative clock for the timeline.\n *\n * Built-in capture layers timestamp events with {@link now} rather than raw\n * `performance.now()`, so event times read as \"ms since recording started\"\n * (the first event is ~0) instead of \"ms since the app page loaded\" — which is\n * also meaningless to the panel, an iframe with its own time origin. The origin\n * is re-zeroed on each rising edge of recording via {@link resetClock}.\n */\n\nlet epoch = 0;\n\n/** Re-zero the clock; called when recording transitions off → on. */\nexport const resetClock = (): void => {\n epoch = performance.now();\n};\n\n/** Milliseconds since the last {@link resetClock}. */\nexport const now = (): number => performance.now() - epoch;\n\n/**\n * Convert a recording-relative time (from {@link now}) back to a\n * `performance.now()` value, i.e. milliseconds since `performance.timeOrigin`.\n * Chrome DevTools' `console.timeStamp` expects that timebase.\n */\nexport const toPerfTime = (t: number): number => t + epoch;\n"]}
|
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
* transport.
|
|
12
12
|
*
|
|
13
13
|
* Recording starts off; the panel iframe toggles it via the Vite HMR channel
|
|
14
|
-
* (Phase 0/1) or devframe shared state (Phase 2+).
|
|
14
|
+
* (Phase 0/1) or devframe shared state (Phase 2+). Layers capture while the
|
|
15
|
+
* panel is recording OR the "chrome performance tracks" preference is on; the
|
|
16
|
+
* panel transport only receives events while recording, the Chrome tracks sink
|
|
17
|
+
* only while the preference is on.
|
|
15
18
|
*/
|
|
16
19
|
import {emit, setHotClient} from './transport.js';
|
|
17
20
|
import {resetClock} from './clock.js';
|
|
21
|
+
import {createChromeTracksSink} from './chrome-tracks.js';
|
|
18
22
|
import {installLifecycleLayer, setUpdateHook} from './lifecycle.js';
|
|
19
23
|
import {installRenderLayer, setRenderDebugEnabled} from './render.js';
|
|
20
24
|
import {installMouseLayer, installKeyboardLayer} from './input.js';
|
|
@@ -32,28 +36,36 @@ const state = {
|
|
|
32
36
|
mouseEventEnabled: false,
|
|
33
37
|
keyboardEventEnabled: false,
|
|
34
38
|
};
|
|
35
|
-
|
|
39
|
+
// Mirror to Chrome DevTools' Performance panel; a page-side preference driven
|
|
40
|
+
// by the settings override, independent of panel recording.
|
|
41
|
+
let chromeTracks = false;
|
|
42
|
+
const sink = createChromeTracksSink();
|
|
43
|
+
// Layers run when either consumer wants events.
|
|
44
|
+
const capturing = () => state.recordingState || chromeTracks;
|
|
45
|
+
const out = (e) => {
|
|
46
|
+
if (state.recordingState) emit(e);
|
|
47
|
+
if (chromeTracks) sink.push(e);
|
|
48
|
+
};
|
|
36
49
|
const lifecycleEnabled = () => state.litLifecycleEnabled;
|
|
37
50
|
const renderEnabled = () => state.litRenderEnabled;
|
|
38
51
|
const renderVerboseEnabled = () => state.litRenderVerboseEnabled;
|
|
39
52
|
const mouseEnabled = () => state.mouseEventEnabled;
|
|
40
53
|
const keyboardEnabled = () => state.keyboardEventEnabled;
|
|
41
|
-
// Drive Lit's debug event flag from
|
|
54
|
+
// Drive Lit's debug event flag from capturing × (render-layer-enabled OR
|
|
42
55
|
// verbose-layer-enabled) so lit-html only pays the per-render CustomEvent
|
|
43
56
|
// dispatch cost while we're actually capturing one of the two render layers.
|
|
44
57
|
const syncRenderDebug = () => {
|
|
45
58
|
setRenderDebugEnabled(
|
|
46
|
-
state.
|
|
47
|
-
(state.litRenderEnabled || state.litRenderVerboseEnabled)
|
|
59
|
+
capturing() && (state.litRenderEnabled || state.litRenderVerboseEnabled)
|
|
48
60
|
);
|
|
49
61
|
};
|
|
50
62
|
// ---------------------------------------------------------------------------
|
|
51
63
|
// Capture layer installation
|
|
52
64
|
// ---------------------------------------------------------------------------
|
|
53
|
-
installLifecycleLayer(
|
|
54
|
-
installRenderLayer(
|
|
55
|
-
installMouseLayer(
|
|
56
|
-
installKeyboardLayer(
|
|
65
|
+
installLifecycleLayer(out, capturing, lifecycleEnabled);
|
|
66
|
+
installRenderLayer(out, capturing, renderEnabled, renderVerboseEnabled);
|
|
67
|
+
installMouseLayer(out, capturing, mouseEnabled);
|
|
68
|
+
installKeyboardLayer(out, capturing, keyboardEnabled);
|
|
57
69
|
// Flash-on-update rides the lifecycle wrappers but not the recording gate:
|
|
58
70
|
// it's a page-side visual the panel toggles as a preference, so it follows the
|
|
59
71
|
// settings override (read at boot, pushed live) rather than the layer state.
|
|
@@ -67,6 +79,12 @@ const hot = import.meta.hot;
|
|
|
67
79
|
subscribeOverride(hot, (o) => {
|
|
68
80
|
setFlashEnabled(o.flashUpdates ?? false);
|
|
69
81
|
setFlashRamp(o.flashUpdatesRamp ?? false);
|
|
82
|
+
const next = o.chromeTracks ?? false;
|
|
83
|
+
if (next !== chromeTracks) {
|
|
84
|
+
chromeTracks = next;
|
|
85
|
+
if (!next) sink.reset();
|
|
86
|
+
syncRenderDebug();
|
|
87
|
+
}
|
|
70
88
|
});
|
|
71
89
|
if (hot !== undefined) {
|
|
72
90
|
pageChannel.useViteHot(hot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/install.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/lib/runtime/timeline/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Timeline runtime install entry point.\n *\n * Injected by the plugin into the host page via `transformIndexHtml` when\n * `timeline: true` is set. Sets up all capture layers and wires them to the\n * transport.\n *\n * Recording starts off; the panel iframe toggles it via the Vite HMR channel\n * (Phase 0/1) or devframe shared state (Phase 2+). Layers capture while the\n * panel is recording OR the \"chrome performance tracks\" preference is on; the\n * panel transport only receives events while recording, the Chrome tracks sink\n * only while the preference is on.\n */\n\nimport {emit, setHotClient} from './transport.js';\nimport {resetClock} from './clock.js';\nimport {createChromeTracksSink} from './chrome-tracks.js';\nimport {installLifecycleLayer, setUpdateHook} from './lifecycle.js';\nimport {installRenderLayer, setRenderDebugEnabled} from './render.js';\nimport {installMouseLayer, installKeyboardLayer} from './input.js';\nimport {flashUpdate, setFlashEnabled, setFlashRamp} from './flash.js';\nimport {subscribeOverride} from '../overrides.js';\nimport {pageChannel} from '../page-channel.js';\nimport type {ViteHotLike} from '../page-channel.js';\nimport type {\n TimelineEvent,\n TimelineLayersState,\n} from '../../../types/timeline.js';\n\n// ---------------------------------------------------------------------------\n// Recording state — written by the panel toggle, read by all capture layers.\n// ---------------------------------------------------------------------------\n\nconst state: TimelineLayersState = {\n recordingState: false,\n litLifecycleEnabled: true,\n litRenderEnabled: true,\n litRenderVerboseEnabled: false,\n mouseEventEnabled: false,\n keyboardEventEnabled: false,\n};\n\n// Mirror to Chrome DevTools' Performance panel; a page-side preference driven\n// by the settings override, independent of panel recording.\nlet chromeTracks = false;\nconst sink = createChromeTracksSink();\n\n// Layers run when either consumer wants events.\nconst capturing = (): boolean => state.recordingState || chromeTracks;\nconst out = (e: TimelineEvent): void => {\n if (state.recordingState) emit(e);\n if (chromeTracks) sink.push(e);\n};\nconst lifecycleEnabled = (): boolean => state.litLifecycleEnabled;\nconst renderEnabled = (): boolean => state.litRenderEnabled;\nconst renderVerboseEnabled = (): boolean => state.litRenderVerboseEnabled;\nconst mouseEnabled = (): boolean => state.mouseEventEnabled;\nconst keyboardEnabled = (): boolean => state.keyboardEventEnabled;\n\n// Drive Lit's debug event flag from capturing × (render-layer-enabled OR\n// verbose-layer-enabled) so lit-html only pays the per-render CustomEvent\n// dispatch cost while we're actually capturing one of the two render layers.\nconst syncRenderDebug = (): void => {\n setRenderDebugEnabled(\n capturing() && (state.litRenderEnabled || state.litRenderVerboseEnabled)\n );\n};\n\n// ---------------------------------------------------------------------------\n// Capture layer installation\n// ---------------------------------------------------------------------------\n\ninstallLifecycleLayer(out, capturing, lifecycleEnabled);\ninstallRenderLayer(out, capturing, renderEnabled, renderVerboseEnabled);\ninstallMouseLayer(out, capturing, mouseEnabled);\ninstallKeyboardLayer(out, capturing, keyboardEnabled);\n\n// Flash-on-update rides the lifecycle wrappers but not the recording gate:\n// it's a page-side visual the panel toggles as a preference, so it follows the\n// settings override (read at boot, pushed live) rather than the layer state.\nsetUpdateHook(flashUpdate);\n\n// ---------------------------------------------------------------------------\n// Page channel wiring\n// ---------------------------------------------------------------------------\n\n// `import.meta.hot` exists in Vite dev builds; the tsconfig lib set doesn't\n// include it, so we access it through the un-typed global shape.\nconst hot = (import.meta as {hot?: ViteHotLike}).hot;\n\nsubscribeOverride(hot, (o) => {\n setFlashEnabled(o.flashUpdates ?? false);\n setFlashRamp(o.flashUpdatesRamp ?? false);\n const next = o.chromeTracks ?? false;\n if (next !== chromeTracks) {\n chromeTracks = next;\n if (!next) sink.reset();\n syncRenderDebug();\n }\n});\n\nif (hot !== undefined) {\n pageChannel.useViteHot(hot);\n setHotClient(pageChannel);\n\n // Panel → app: toggle recording and per-layer flags.\n pageChannel.on('lit:timeline:recording-changed', (data) => {\n const next = (data as {recording: boolean}).recording;\n // Re-zero the timeline clock on the rising edge so event times read as\n // \"ms since recording started\" rather than since page load.\n if (next && !state.recordingState) resetClock();\n state.recordingState = next;\n syncRenderDebug();\n });\n\n pageChannel.on('lit:timeline:layers-changed', (data) => {\n Object.assign(state, data as Partial<TimelineLayersState>);\n syncRenderDebug();\n });\n\n // Announce readiness so the panel can detect the runtime.\n pageChannel.send('lit:timeline:runtime-ready', {});\n // A carrier attached later starts with no idea a runtime exists.\n pageChannel.onAttach(() =>\n pageChannel.send('lit:timeline:runtime-ready', {})\n );\n}\n"]}
|
package/lib/snapshot.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export interface SnapshotBuildResult {
|
|
|
13
13
|
details: number;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Write `snapshot` to `outDir` as a self-contained static panel
|
|
16
|
+
* Write `snapshot` to `outDir` as a self-contained static panel, replacing an
|
|
17
|
+
* earlier snapshot there. Any other existing directory is refused.
|
|
17
18
|
*
|
|
18
19
|
* The build adapter is imported dynamically: it reaches for `node:fs`, and
|
|
19
20
|
* `definition.ts` is deliberately framework- and environment-neutral, so a
|
package/lib/snapshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/lib/snapshot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAE1D,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/lib/snapshot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAE1D,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,aACd,eAAe,WAChB;IACP,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,KACA,OAAO,CAAC,mBAAmB,CAyC7B,CAAC"}
|
package/lib/snapshot.js
CHANGED
|
@@ -22,7 +22,8 @@ import {createNullSource} from './devframe/source.js';
|
|
|
22
22
|
const countNodes = (nodes) =>
|
|
23
23
|
nodes.reduce((total, node) => total + 1 + countNodes(node.children), 0);
|
|
24
24
|
/**
|
|
25
|
-
* Write `snapshot` to `outDir` as a self-contained static panel
|
|
25
|
+
* Write `snapshot` to `outDir` as a self-contained static panel, replacing an
|
|
26
|
+
* earlier snapshot there. Any other existing directory is refused.
|
|
26
27
|
*
|
|
27
28
|
* The build adapter is imported dynamically: it reaches for `node:fs`, and
|
|
28
29
|
* `definition.ts` is deliberately framework- and environment-neutral, so a
|
|
@@ -31,6 +32,21 @@ const countNodes = (nodes) =>
|
|
|
31
32
|
*/
|
|
32
33
|
export const buildSnapshot = async (snapshot, options) => {
|
|
33
34
|
const {createBuild} = await import('devframe/adapters/build');
|
|
35
|
+
// `createBuild` deletes `outDir` recursively before writing. Replacing an
|
|
36
|
+
// earlier export is the point, but a typo'd `outDir` that names the project
|
|
37
|
+
// (or `src`) must not cost the developer their tree: only a directory that
|
|
38
|
+
// already holds a snapshot may be replaced.
|
|
39
|
+
const {existsSync} = await import('node:fs');
|
|
40
|
+
const {join} = await import('node:path');
|
|
41
|
+
if (
|
|
42
|
+
existsSync(options.outDir) &&
|
|
43
|
+
!existsSync(join(options.outDir, '__connection.json'))
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[lit-devtools] ${options.outDir} exists and is not a snapshot; ` +
|
|
47
|
+
'refusing to replace it'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
34
50
|
const definition = createLitDevframe({
|
|
35
51
|
// Nothing to attach to: the page this session describes is gone, and
|
|
36
52
|
// every answer the frozen panel needs is already in `replay`.
|