@oddsquad/vite-plugin-lit 0.2.2 → 0.3.0

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +28 -0
  3. package/README.md +153 -0
  4. package/bin.mjs +154 -0
  5. package/client.d.ts +61 -0
  6. package/dist/client/assets/browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js +69 -0
  7. package/dist/client/assets/browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js.map +1 -0
  8. package/dist/client/assets/index-CFrVgNfS.js +9740 -0
  9. package/dist/client/assets/index-CFrVgNfS.js.map +1 -0
  10. package/{panel → dist/client}/index.html +1 -0
  11. package/index.d.ts.map +1 -1
  12. package/index.js +2 -2
  13. package/index.js.map +1 -1
  14. package/lib/color-scheme.d.ts.map +1 -1
  15. package/lib/color-scheme.js +17 -21
  16. package/lib/color-scheme.js.map +1 -1
  17. package/lib/devframe/definition.d.ts +45 -0
  18. package/lib/devframe/definition.d.ts.map +1 -0
  19. package/lib/devframe/definition.js +547 -0
  20. package/lib/devframe/definition.js.map +1 -0
  21. package/lib/devframe/icon.d.ts +8 -0
  22. package/lib/devframe/icon.d.ts.map +1 -0
  23. package/lib/devframe/icon.js +21 -0
  24. package/lib/devframe/icon.js.map +1 -0
  25. package/lib/devframe/paths.d.ts +12 -0
  26. package/lib/devframe/paths.d.ts.map +1 -0
  27. package/lib/devframe/paths.js +38 -0
  28. package/lib/devframe/paths.js.map +1 -0
  29. package/lib/devframe/protocol.d.ts +218 -0
  30. package/lib/devframe/protocol.d.ts.map +1 -0
  31. package/lib/devframe/protocol.js +76 -0
  32. package/lib/devframe/protocol.js.map +1 -0
  33. package/lib/devframe/source.d.ts +49 -0
  34. package/lib/devframe/source.d.ts.map +1 -0
  35. package/lib/devframe/source.js +23 -0
  36. package/lib/devframe/source.js.map +1 -0
  37. package/lib/devframe/vite.d.ts +116 -0
  38. package/lib/devframe/vite.d.ts.map +1 -0
  39. package/lib/devframe/vite.js +291 -0
  40. package/lib/devframe/vite.js.map +1 -0
  41. package/lib/http.d.ts +29 -0
  42. package/lib/http.d.ts.map +1 -0
  43. package/lib/http.js +35 -0
  44. package/lib/http.js.map +1 -0
  45. package/lib/icons.d.ts +2 -0
  46. package/lib/icons.d.ts.map +1 -1
  47. package/lib/icons.js +10 -4
  48. package/lib/icons.js.map +1 -1
  49. package/lib/plugin.d.ts +15 -4
  50. package/lib/plugin.d.ts.map +1 -1
  51. package/lib/plugin.js +640 -574
  52. package/lib/plugin.js.map +1 -1
  53. package/lib/runtime/css.d.ts +2 -16
  54. package/lib/runtime/css.d.ts.map +1 -1
  55. package/lib/runtime/css.js +43 -36
  56. package/lib/runtime/css.js.map +1 -1
  57. package/lib/runtime/edge-panel.d.ts.map +1 -1
  58. package/lib/runtime/edge-panel.js +82 -91
  59. package/lib/runtime/edge-panel.js.map +1 -1
  60. package/lib/runtime/fonts.d.ts.map +1 -1
  61. package/lib/runtime/fonts.js +1 -1
  62. package/lib/runtime/fonts.js.map +1 -1
  63. package/lib/runtime/indicator.d.ts.map +1 -1
  64. package/lib/runtime/indicator.js +71 -74
  65. package/lib/runtime/indicator.js.map +1 -1
  66. package/lib/runtime/inspector/collect.d.ts.map +1 -1
  67. package/lib/runtime/inspector/collect.js +83 -90
  68. package/lib/runtime/inspector/collect.js.map +1 -1
  69. package/lib/runtime/inspector/highlight.d.ts +16 -0
  70. package/lib/runtime/inspector/highlight.d.ts.map +1 -0
  71. package/lib/runtime/inspector/highlight.js +66 -0
  72. package/lib/runtime/inspector/highlight.js.map +1 -0
  73. package/lib/runtime/inspector/install.d.ts.map +1 -1
  74. package/lib/runtime/inspector/install.js +180 -205
  75. package/lib/runtime/inspector/install.js.map +1 -1
  76. package/lib/runtime/inspector/serialize.d.ts.map +1 -1
  77. package/lib/runtime/inspector/serialize.js +86 -107
  78. package/lib/runtime/inspector/serialize.js.map +1 -1
  79. package/lib/runtime/intern.d.ts.map +1 -1
  80. package/lib/runtime/intern.js +31 -29
  81. package/lib/runtime/intern.js.map +1 -1
  82. package/lib/runtime/overrides.d.ts.map +1 -1
  83. package/lib/runtime/overrides.js +14 -13
  84. package/lib/runtime/overrides.js.map +1 -1
  85. package/lib/runtime/patch.d.ts +8 -0
  86. package/lib/runtime/patch.d.ts.map +1 -1
  87. package/lib/runtime/patch.js +321 -282
  88. package/lib/runtime/patch.js.map +1 -1
  89. package/lib/runtime/source-meta.d.ts.map +1 -1
  90. package/lib/runtime/source-meta.js +1 -1
  91. package/lib/runtime/source-meta.js.map +1 -1
  92. package/lib/runtime/source-overlay/editors.d.ts.map +1 -1
  93. package/lib/runtime/source-overlay/editors.js +27 -27
  94. package/lib/runtime/source-overlay/editors.js.map +1 -1
  95. package/lib/runtime/source-overlay/mask-path.d.ts.map +1 -1
  96. package/lib/runtime/source-overlay/mask-path.js +15 -15
  97. package/lib/runtime/source-overlay/mask-path.js.map +1 -1
  98. package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
  99. package/lib/runtime/source-overlay/overlay-element.js +347 -356
  100. package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
  101. package/lib/runtime/source-overlay/source-host.d.ts.map +1 -1
  102. package/lib/runtime/source-overlay/source-host.js +64 -71
  103. package/lib/runtime/source-overlay/source-host.js.map +1 -1
  104. package/lib/runtime/source-overlay/template.d.ts.map +1 -1
  105. package/lib/runtime/source-overlay/template.js +3 -3
  106. package/lib/runtime/source-overlay/template.js.map +1 -1
  107. package/lib/runtime/source-overlay.d.ts.map +1 -1
  108. package/lib/runtime/source-overlay.js +5 -2
  109. package/lib/runtime/source-overlay.js.map +1 -1
  110. package/lib/runtime/timeline/clock.d.ts.map +1 -1
  111. package/lib/runtime/timeline/clock.js +2 -2
  112. package/lib/runtime/timeline/clock.js.map +1 -1
  113. package/lib/runtime/timeline/flash.d.ts +39 -0
  114. package/lib/runtime/timeline/flash.d.ts.map +1 -0
  115. package/lib/runtime/timeline/flash.js +169 -0
  116. package/lib/runtime/timeline/flash.js.map +1 -0
  117. package/lib/runtime/timeline/identity.d.ts.map +1 -1
  118. package/lib/runtime/timeline/identity.js +27 -27
  119. package/lib/runtime/timeline/identity.js.map +1 -1
  120. package/lib/runtime/timeline/input.d.ts.map +1 -1
  121. package/lib/runtime/timeline/input.js +54 -67
  122. package/lib/runtime/timeline/input.js.map +1 -1
  123. package/lib/runtime/timeline/install.d.ts.map +1 -1
  124. package/lib/runtime/timeline/install.js +38 -29
  125. package/lib/runtime/timeline/install.js.map +1 -1
  126. package/lib/runtime/timeline/lifecycle.d.ts +8 -0
  127. package/lib/runtime/timeline/lifecycle.d.ts.map +1 -1
  128. package/lib/runtime/timeline/lifecycle.js +149 -133
  129. package/lib/runtime/timeline/lifecycle.js.map +1 -1
  130. package/lib/runtime/timeline/public-api.d.ts.map +1 -1
  131. package/lib/runtime/timeline/public-api.js +9 -9
  132. package/lib/runtime/timeline/public-api.js.map +1 -1
  133. package/lib/runtime/timeline/render.d.ts.map +1 -1
  134. package/lib/runtime/timeline/render.js +70 -75
  135. package/lib/runtime/timeline/render.js.map +1 -1
  136. package/lib/runtime/timeline/transport.d.ts.map +1 -1
  137. package/lib/runtime/timeline/transport.js +39 -43
  138. package/lib/runtime/timeline/transport.js.map +1 -1
  139. package/lib/segmented-tabs.d.ts +3 -1
  140. package/lib/segmented-tabs.d.ts.map +1 -1
  141. package/lib/segmented-tabs.js +136 -107
  142. package/lib/segmented-tabs.js.map +1 -1
  143. package/lib/snapshot.d.ts +28 -0
  144. package/lib/snapshot.d.ts.map +1 -0
  145. package/lib/snapshot.js +57 -0
  146. package/lib/snapshot.js.map +1 -0
  147. package/lib/source-meta.d.ts.map +1 -1
  148. package/lib/source-meta.js +176 -162
  149. package/lib/source-meta.js.map +1 -1
  150. package/lib/timeline/derive.d.ts +152 -0
  151. package/lib/timeline/derive.d.ts.map +1 -0
  152. package/lib/timeline/derive.js +242 -0
  153. package/lib/timeline/derive.js.map +1 -0
  154. package/lib/timeline-plugin.d.ts.map +1 -1
  155. package/lib/timeline-plugin.js +304 -321
  156. package/lib/timeline-plugin.js.map +1 -1
  157. package/lib/tokens.d.ts.map +1 -1
  158. package/lib/tokens.js +17 -18
  159. package/lib/tokens.js.map +1 -1
  160. package/lib/transform.d.ts.map +1 -1
  161. package/lib/transform.js +62 -57
  162. package/lib/transform.js.map +1 -1
  163. package/lib/types.d.ts.map +1 -1
  164. package/lib/types.js +1 -1
  165. package/lib/types.js.map +1 -1
  166. package/lib/wrap-table.d.ts.map +1 -1
  167. package/lib/wrap-table.js +19 -17
  168. package/lib/wrap-table.js.map +1 -1
  169. package/package.json +77 -13
  170. package/types/hmr-incompatibility.d.ts +59 -0
  171. package/types/hmr-incompatibility.d.ts.map +1 -0
  172. package/types/hmr-incompatibility.js +30 -0
  173. package/types/hmr-incompatibility.js.map +1 -0
  174. package/types/in-page.d.ts +49 -0
  175. package/types/in-page.d.ts.map +1 -0
  176. package/types/in-page.js +33 -0
  177. package/types/in-page.js.map +1 -0
  178. package/types/inspector.d.ts.map +1 -1
  179. package/types/inspector.js.map +1 -1
  180. package/types/snapshot.d.ts +44 -0
  181. package/types/snapshot.d.ts.map +1 -0
  182. package/types/snapshot.js +7 -0
  183. package/types/snapshot.js.map +1 -0
  184. package/types/timeline.d.ts +11 -0
  185. package/types/timeline.d.ts.map +1 -1
  186. package/types/timeline.js.map +1 -1
  187. package/panel/components-view.d.ts +0 -60
  188. package/panel/components-view.d.ts.map +0 -1
  189. package/panel/components-view.js +0 -587
  190. package/panel/components-view.js.map +0 -1
  191. package/panel/devtools-settings.d.ts +0 -41
  192. package/panel/devtools-settings.d.ts.map +0 -1
  193. package/panel/devtools-settings.js +0 -463
  194. package/panel/devtools-settings.js.map +0 -1
  195. package/panel/lit-devtools-panel.d.ts +0 -37
  196. package/panel/lit-devtools-panel.d.ts.map +0 -1
  197. package/panel/lit-devtools-panel.js +0 -185
  198. package/panel/lit-devtools-panel.js.map +0 -1
  199. package/panel/timeline-event-list.d.ts +0 -43
  200. package/panel/timeline-event-list.d.ts.map +0 -1
  201. package/panel/timeline-event-list.js +0 -416
  202. package/panel/timeline-event-list.js.map +0 -1
  203. package/panel/timeline-layers.d.ts +0 -25
  204. package/panel/timeline-layers.d.ts.map +0 -1
  205. package/panel/timeline-layers.js +0 -96
  206. package/panel/timeline-layers.js.map +0 -1
  207. package/panel/timeline-view.d.ts +0 -38
  208. package/panel/timeline-view.d.ts.map +0 -1
  209. package/panel/timeline-view.js +0 -234
  210. package/panel/timeline-view.js.map +0 -1
package/lib/plugin.js CHANGED
@@ -3,35 +3,32 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: BSD-3-Clause
5
5
  */
6
- import { existsSync } from 'node:fs';
7
- import { resolve as resolvePath, sep } from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import { loadEnv } from 'vite';
6
+ import {existsSync} from 'node:fs';
7
+ import {resolve as resolvePath, sep} from 'node:path';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {loadEnv} from 'vite';
10
10
  import MagicString from 'magic-string';
11
- import { injectSourceMeta } from './source-meta.js';
12
- import { INSTALL_ID, VIRTUAL_PREFIX, transformLitModule } from './transform.js';
13
- import { WRAP_TABLE } from './wrap-table.js';
14
- import { litTimelinePlugin } from './timeline-plugin.js';
15
- const CSS_SHEET_BUILD_MODES = [
16
- 'auto',
17
- 'url',
18
- 'inline',
19
- 'inline-raw',
20
- ];
11
+ import {isTrustedRequest} from './http.js';
12
+ import {injectSourceMeta} from './source-meta.js';
13
+ import {INSTALL_ID, VIRTUAL_PREFIX, transformLitModule} from './transform.js';
14
+ import {WRAP_TABLE} from './wrap-table.js';
15
+ import {createLitDevframePlugin} from './devframe/vite.js';
16
+ import {PACKAGE_VERSION} from './devframe/paths.js';
17
+ const CSS_SHEET_BUILD_MODES = ['auto', 'url', 'inline', 'inline-raw'];
21
18
  /** Env var prefix consumed at config time. */
22
19
  const ENV_PREFIX = 'LIT_PLUGIN';
23
20
  /** Parse a boolean-ish env string; `undefined` when unset/unrecognized. */
24
- const envBool = (v) => v === 'true' || v === '1'
21
+ const envBool = (v) =>
22
+ v === 'true' || v === '1'
25
23
  ? true
26
24
  : v === 'false' || v === '0'
27
- ? false
28
- : undefined;
25
+ ? false
26
+ : undefined;
29
27
  /** Parse a numeric env string; `undefined` when unset/non-numeric. */
30
28
  const envNum = (v) => {
31
- if (v === undefined || v === '')
32
- return undefined;
33
- const n = Number(v);
34
- return Number.isNaN(n) ? undefined : n;
29
+ if (v === undefined || v === '') return undefined;
30
+ const n = Number(v);
31
+ return Number.isNaN(n) ? undefined : n;
35
32
  };
36
33
  /**
37
34
  * Parse a string against a closed set of values; `undefined` (with a warning)
@@ -39,60 +36,73 @@ const envNum = (v) => {
39
36
  * instead of silently disabling a feature.
40
37
  */
41
38
  const envEnum = (v, allowed, source) => {
42
- if (v === undefined || v === '')
43
- return undefined;
44
- if (allowed.includes(v))
45
- return v;
46
- console.warn(`[lit-plugin] ignoring ${source}=${JSON.stringify(v)}; expected one of ` +
47
- allowed.map((a) => JSON.stringify(a)).join(', '));
48
- return undefined;
39
+ if (v === undefined || v === '') return undefined;
40
+ if (allowed.includes(v)) return v;
41
+ console.warn(
42
+ `[lit-plugin] ignoring ${source}=${JSON.stringify(v)}; expected one of ` +
43
+ allowed.map((a) => JSON.stringify(a)).join(', ')
44
+ );
45
+ return undefined;
49
46
  };
50
47
  /**
51
48
  * Merge explicit options over env vars over defaults (in that precedence) into
52
49
  * the flat shape the plugin hooks consume.
53
50
  */
54
51
  export const resolveOptions = (options, env) => {
55
- const hmr = options.hmr;
56
- const hmrObj = typeof hmr === 'object' ? hmr : undefined;
57
- const hmrEnabled = (typeof hmr === 'boolean' ? hmr : hmrObj?.enabled) ??
58
- envBool(env[`${ENV_PREFIX}_HMR`]) ??
59
- true;
60
- const reconnect = hmrObj?.reconnect ?? envBool(env[`${ENV_PREFIX}_HMR_RECONNECT`]) ?? false;
61
- const onIncompatible = hmrObj?.onIncompatible ??
62
- env[`${ENV_PREFIX}_HMR_ON_INCOMPATIBLE`] ??
63
- 'reload';
64
- const ind = hmrObj?.indicator;
65
- const indObj = typeof ind === 'object' ? ind : undefined;
66
- const indEnabled = (typeof ind === 'boolean' ? ind : indObj?.enabled) ??
67
- envBool(env[`${ENV_PREFIX}_HMR_INDICATOR`]) ??
68
- true;
69
- const indCount = indObj?.count ?? envBool(env[`${ENV_PREFIX}_HMR_INDICATOR_COUNT`]) ?? false;
70
- // The indicator is meaningless without HMR, so it follows the master toggle.
71
- const indicator = hmrEnabled && indEnabled ? { count: indCount } : false;
72
- const so = options.sourceOverlay;
73
- const soExplicit = typeof so === 'boolean' ? so : so === undefined ? undefined : true;
74
- const soEnabled = soExplicit ?? envBool(env[`${ENV_PREFIX}_SOURCE_OVERLAY`]) ?? false;
75
- let sourceOverlay = false;
76
- if (soEnabled) {
77
- const base = typeof so === 'object' ? { ...so } : {};
78
- base.key ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_KEY`] || undefined;
79
- base.editor ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_EDITOR`] || undefined;
80
- base.throttleMs ??= envNum(env[`${ENV_PREFIX}_SOURCE_OVERLAY_THROTTLE_MS`]);
81
- sourceOverlay = base;
82
- }
83
- const timeline = options.timeline ?? envBool(env[`${ENV_PREFIX}_TIMELINE`]) ?? false;
84
- const cssSheetBuild = envEnum(options.cssSheetBuild, CSS_SHEET_BUILD_MODES, 'cssSheetBuild') ??
85
- envEnum(env[`${ENV_PREFIX}_CSS_SHEET_BUILD`], CSS_SHEET_BUILD_MODES, `${ENV_PREFIX}_CSS_SHEET_BUILD`) ??
86
- 'auto';
87
- return {
88
- hmrEnabled,
89
- reconnect,
90
- onIncompatible,
91
- indicator,
92
- sourceOverlay,
93
- timeline,
94
- cssSheetBuild,
95
- };
52
+ const hmr = options.hmr;
53
+ const hmrObj = typeof hmr === 'object' ? hmr : undefined;
54
+ const hmrEnabled =
55
+ (typeof hmr === 'boolean' ? hmr : hmrObj?.enabled) ??
56
+ envBool(env[`${ENV_PREFIX}_HMR`]) ??
57
+ true;
58
+ const reconnect =
59
+ hmrObj?.reconnect ?? envBool(env[`${ENV_PREFIX}_HMR_RECONNECT`]) ?? false;
60
+ const onIncompatible =
61
+ hmrObj?.onIncompatible ??
62
+ env[`${ENV_PREFIX}_HMR_ON_INCOMPATIBLE`] ??
63
+ 'reload';
64
+ const ind = hmrObj?.indicator;
65
+ const indObj = typeof ind === 'object' ? ind : undefined;
66
+ const indEnabled =
67
+ (typeof ind === 'boolean' ? ind : indObj?.enabled) ??
68
+ envBool(env[`${ENV_PREFIX}_HMR_INDICATOR`]) ??
69
+ true;
70
+ const indCount =
71
+ indObj?.count ?? envBool(env[`${ENV_PREFIX}_HMR_INDICATOR_COUNT`]) ?? false;
72
+ // The indicator is meaningless without HMR, so it follows the master toggle.
73
+ const indicator = hmrEnabled && indEnabled ? {count: indCount} : false;
74
+ const so = options.sourceOverlay;
75
+ const soExplicit =
76
+ typeof so === 'boolean' ? so : so === undefined ? undefined : true;
77
+ const soEnabled =
78
+ soExplicit ?? envBool(env[`${ENV_PREFIX}_SOURCE_OVERLAY`]) ?? false;
79
+ let sourceOverlay = false;
80
+ if (soEnabled) {
81
+ const base = typeof so === 'object' ? {...so} : {};
82
+ base.key ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_KEY`] || undefined;
83
+ base.editor ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_EDITOR`] || undefined;
84
+ base.throttleMs ??= envNum(env[`${ENV_PREFIX}_SOURCE_OVERLAY_THROTTLE_MS`]);
85
+ sourceOverlay = base;
86
+ }
87
+ const timeline =
88
+ options.timeline ?? envBool(env[`${ENV_PREFIX}_TIMELINE`]) ?? false;
89
+ const cssSheetBuild =
90
+ envEnum(options.cssSheetBuild, CSS_SHEET_BUILD_MODES, 'cssSheetBuild') ??
91
+ envEnum(
92
+ env[`${ENV_PREFIX}_CSS_SHEET_BUILD`],
93
+ CSS_SHEET_BUILD_MODES,
94
+ `${ENV_PREFIX}_CSS_SHEET_BUILD`
95
+ ) ??
96
+ 'auto';
97
+ return {
98
+ hmrEnabled,
99
+ reconnect,
100
+ onIncompatible,
101
+ indicator,
102
+ sourceOverlay,
103
+ timeline,
104
+ cssSheetBuild,
105
+ };
96
106
  };
97
107
  /**
98
108
  * Flattens resolved options into the JSON-serializable shape the panel's
@@ -101,24 +111,25 @@ export const resolveOptions = (options, env) => {
101
111
  * are applied here so the panel shows the effective values the runtime uses.
102
112
  */
103
113
  const toFeatureSettings = (r) => {
104
- const so = r.sourceOverlay;
105
- const editor = so === false ? undefined : so.editor;
106
- return {
107
- hmr: {
108
- enabled: r.hmrEnabled,
109
- reconnect: r.reconnect,
110
- onIncompatible: r.onIncompatible,
111
- indicatorEnabled: r.indicator !== false,
112
- indicatorCount: r.indicator !== false && r.indicator.count,
113
- },
114
- sourceOverlay: {
115
- enabled: so !== false,
116
- key: (so === false ? undefined : so.key) ?? 's',
117
- editor: typeof editor === 'string' ? editor : editor ? 'custom' : 'vscode',
118
- throttleMs: (so === false ? undefined : so.throttleMs) ?? 50,
119
- },
120
- timeline: r.timeline,
121
- };
114
+ const so = r.sourceOverlay;
115
+ const editor = so === false ? undefined : so.editor;
116
+ return {
117
+ hmr: {
118
+ enabled: r.hmrEnabled,
119
+ reconnect: r.reconnect,
120
+ onIncompatible: r.onIncompatible,
121
+ indicatorEnabled: r.indicator !== false,
122
+ indicatorCount: r.indicator !== false && r.indicator.count,
123
+ },
124
+ sourceOverlay: {
125
+ enabled: so !== false,
126
+ key: (so === false ? undefined : so.key) ?? 's',
127
+ editor:
128
+ typeof editor === 'string' ? editor : editor ? 'custom' : 'vscode',
129
+ throttleMs: (so === false ? undefined : so.throttleMs) ?? 50,
130
+ },
131
+ timeline: r.timeline,
132
+ };
122
133
  };
123
134
  /**
124
135
  * Resolves a runtime module to an absolute fs path (served via `/@fs/`), so
@@ -126,100 +137,96 @@ const toFeatureSettings = (r) => {
126
137
  * the `.ts` source when running un-built (e.g. under vitest).
127
138
  */
128
139
  const resolveRuntimeModule = (name) => {
129
- for (const ext of ['js', 'ts']) {
130
- const url = new URL(`./runtime/${name}.${ext}`, import.meta.url);
131
- if (existsSync(url)) {
132
- return fileURLToPath(url).replace(/\\/g, '/');
133
- }
140
+ for (const ext of ['js', 'ts']) {
141
+ const url = new URL(`./runtime/${name}.${ext}`, import.meta.url);
142
+ if (existsSync(url)) {
143
+ return fileURLToPath(url).replace(/\\/g, '/');
134
144
  }
135
- throw new Error(`[lit-plugin] runtime module not found: ${name}`);
145
+ }
146
+ throw new Error(`[lit-plugin] runtime module not found: ${name}`);
136
147
  };
137
148
  const JS_FILE_RE = /\.[cm]?[jt]sx?$/;
138
149
  const OPEN_IN_EDITOR_PATH = '/__lit-open-in-editor';
139
- /** Reject requests whose `Origin` is a different host than the dev server, so
140
- * a page the developer happens to visit can't drive these local-only
141
- * endpoints. Same-origin requests (no `Origin`, or matching `Host`) pass. */
142
- const isSameOrigin = (headers) => {
143
- const { origin, host } = headers;
144
- if (origin === undefined || origin === 'null')
145
- return true;
146
- try {
147
- return new URL(origin).host === host;
150
+ export const createOpenInEditorMiddleware = (allowedRoots) => {
151
+ // First entry is the primary root: relative `file` params resolve against
152
+ // it. Every entry grants open access to files beneath it.
153
+ const roots = allowedRoots.map((r) => resolvePath(r));
154
+ return (req, res, next) => {
155
+ if (req.url === undefined) {
156
+ next();
157
+ return;
148
158
  }
149
- catch {
150
- return false;
159
+ if (req.method !== undefined && req.method !== 'GET') {
160
+ res.statusCode = 405;
161
+ res.end('method not allowed');
162
+ return;
151
163
  }
152
- };
153
- export const createOpenInEditorMiddleware = (allowedRoots) => {
154
- // First entry is the primary root: relative `file` params resolve against
155
- // it. Every entry grants open access to files beneath it.
156
- const roots = allowedRoots.map((r) => resolvePath(r));
157
- return (req, res, next) => {
158
- if (req.url === undefined) {
159
- next();
160
- return;
161
- }
162
- if (!isSameOrigin(req.headers ?? {})) {
163
- res.statusCode = 403;
164
- res.end('forbidden');
165
- return;
166
- }
167
- const query = req.url.includes('?')
168
- ? req.url.slice(req.url.indexOf('?') + 1)
169
- : '';
170
- const params = new URLSearchParams(query);
171
- const file = params.get('file');
172
- if (file === null) {
173
- res.statusCode = 400;
174
- res.end('missing file parameter');
175
- return;
176
- }
177
- // Confine the open to the allowed roots: resolve the requested path and
178
- // reject anything that escapes all of them (path traversal, absolute
179
- // paths to arbitrary files). `launch-editor` spawns the user's editor on
180
- // this path, so an unvalidated `file` is a local-file-open /
181
- // arg-injection vector.
182
- const resolved = resolvePath(roots[0] ?? process.cwd(), file);
183
- const allowed = roots.some((root) => resolved === root || resolved.startsWith(root + sep));
184
- if (!allowed) {
185
- res.statusCode = 403;
186
- res.end('file outside allowed roots');
187
- return;
188
- }
189
- if (!existsSync(resolved)) {
190
- res.statusCode = 404;
191
- res.end('file not found');
192
- return;
193
- }
194
- // Coerce to integers so a crafted `line`/`column` can't smuggle extra
195
- // shell-visible content through the `file:line:column` ref.
196
- const line = String(Math.max(1, Number.parseInt(params.get('line') ?? '1', 10) || 1));
197
- const column = String(Math.max(1, Number.parseInt(params.get('column') ?? '1', 10) || 1));
198
- const fileRef = `${resolved}:${line}:${column}`;
199
- import('launch-editor')
200
- .then((mod) => {
201
- const launch = (mod.default ?? mod);
202
- // `launch-editor` invokes the callback only on failure — synchronously
203
- // when it can't guess an editor, later if the spawn errors. Reply `ok`
204
- // right after launching; the guard keeps a sync failure's 500 first
205
- // and drops a late async failure (the response is long gone).
206
- let finished = false;
207
- const finish = (statusCode, msg) => {
208
- if (finished)
209
- return;
210
- finished = true;
211
- res.statusCode = statusCode;
212
- res.end(msg);
213
- };
214
- launch(fileRef, (_fileName, errorMessage) => {
215
- if (errorMessage !== null) {
216
- finish(500, errorMessage);
217
- }
218
- });
219
- finish(200, 'ok');
220
- })
221
- .catch(next);
222
- };
164
+ if (!isTrustedRequest(req.headers ?? {})) {
165
+ res.statusCode = 403;
166
+ res.end('forbidden');
167
+ return;
168
+ }
169
+ const query = req.url.includes('?')
170
+ ? req.url.slice(req.url.indexOf('?') + 1)
171
+ : '';
172
+ const params = new URLSearchParams(query);
173
+ const file = params.get('file');
174
+ if (file === null) {
175
+ res.statusCode = 400;
176
+ res.end('missing file parameter');
177
+ return;
178
+ }
179
+ // Confine the open to the allowed roots: resolve the requested path and
180
+ // reject anything that escapes all of them (path traversal, absolute
181
+ // paths to arbitrary files). `launch-editor` spawns the user's editor on
182
+ // this path, so an unvalidated `file` is a local-file-open /
183
+ // arg-injection vector.
184
+ const resolved = resolvePath(roots[0] ?? process.cwd(), file);
185
+ const allowed = roots.some(
186
+ (root) => resolved === root || resolved.startsWith(root + sep)
187
+ );
188
+ if (!allowed) {
189
+ res.statusCode = 403;
190
+ res.end('file outside allowed roots');
191
+ return;
192
+ }
193
+ if (!existsSync(resolved)) {
194
+ res.statusCode = 404;
195
+ res.end('file not found');
196
+ return;
197
+ }
198
+ // Coerce to integers so a crafted `line`/`column` can't smuggle extra
199
+ // shell-visible content through the `file:line:column` ref.
200
+ const line = String(
201
+ Math.max(1, Number.parseInt(params.get('line') ?? '1', 10) || 1)
202
+ );
203
+ const column = String(
204
+ Math.max(1, Number.parseInt(params.get('column') ?? '1', 10) || 1)
205
+ );
206
+ const fileRef = `${resolved}:${line}:${column}`;
207
+ import('launch-editor')
208
+ .then((mod) => {
209
+ const launch = mod.default ?? mod;
210
+ // `launch-editor` invokes the callback only on failure — synchronously
211
+ // when it can't guess an editor, later if the spawn errors. Reply `ok`
212
+ // right after launching; the guard keeps a sync failure's 500 first
213
+ // and drops a late async failure (the response is long gone).
214
+ let finished = false;
215
+ const finish = (statusCode, msg) => {
216
+ if (finished) return;
217
+ finished = true;
218
+ res.statusCode = statusCode;
219
+ res.end(msg);
220
+ };
221
+ launch(fileRef, (_fileName, errorMessage) => {
222
+ if (errorMessage !== null) {
223
+ finish(500, errorMessage);
224
+ }
225
+ });
226
+ finish(200, 'ok');
227
+ })
228
+ .catch(next);
229
+ };
223
230
  };
224
231
  /**
225
232
  * `import href from './x.css?hmr-url'` — `?url` semantics with working HMR.
@@ -265,83 +272,139 @@ const CSS_VIRTUAL_SUFFIX = '.js';
265
272
  * default.
266
273
  */
267
274
  export const litCssQueries = (getCssSheetBuild = () => 'auto') => {
268
- let isBuild = false;
269
- let isLib = false;
270
- return {
271
- name: 'lit-css-query',
272
- // Vite's core resolver claims `./x.css?hmr-url` for the CSS pipeline
273
- // before normal plugins get a look, so resolve ahead of it.
274
- enforce: 'pre',
275
- configResolved(config) {
276
- isBuild = config.command === 'build';
277
- isLib = config.build.lib !== false && config.build.lib !== undefined;
278
- },
279
- async resolveId(id, importer) {
280
- const prefix = CSS_URL_QUERY_RE.test(id)
281
- ? CSS_URL_VIRTUAL_PREFIX
282
- : CSS_SHEET_QUERY_RE.test(id)
283
- ? CSS_SHEET_VIRTUAL_PREFIX
284
- : null;
285
- if (prefix === null) {
286
- return null;
287
- }
288
- const file = id.slice(0, id.indexOf('?'));
289
- const resolved = await this.resolve(file, importer);
290
- if (resolved === null) {
291
- return null;
292
- }
293
- return prefix + resolved.id + CSS_VIRTUAL_SUFFIX;
294
- },
295
- load(id) {
296
- const helperPath = resolveRuntimeModule('css');
297
- if (id.startsWith(CSS_URL_VIRTUAL_PREFIX)) {
298
- const file = id.slice(CSS_URL_VIRTUAL_PREFIX.length, -CSS_VIRTUAL_SUFFIX.length);
299
- return {
300
- code: `import url from ${JSON.stringify(`${file}?url`)};\n` +
301
- `import {devCacheBust} from ${JSON.stringify(helperPath)};\n` +
302
- `export default devCacheBust(url);\n`,
303
- moduleType: 'js',
304
- };
305
- }
306
- if (id.startsWith(CSS_SHEET_VIRTUAL_PREFIX)) {
307
- const file = id.slice(CSS_SHEET_VIRTUAL_PREFIX.length, -CSS_VIRTUAL_SUFFIX.length);
308
- const mode = getCssSheetBuild();
309
- const inlineQuery = !isBuild || mode === 'url' || (mode === 'auto' && !isLib)
310
- ? null
311
- : mode === 'inline-raw'
312
- ? '?raw'
313
- : '?inline';
314
- if (inlineQuery !== null) {
315
- // Build only, so no `import.meta.hot` block. The sheet is still
316
- // constructed once per virtual module, so adopters share it exactly
317
- // as they do in the fetch-backed form.
318
- return {
319
- code: `import css from ${JSON.stringify(`${file}${inlineQuery}`)};\n` +
320
- `const sheet = new CSSStyleSheet();\n` +
321
- `sheet.replaceSync(css);\n` +
322
- `export default sheet;\n`,
323
- moduleType: 'js',
324
- };
325
- }
326
- // The accept specifier must be byte-identical to the import above —
327
- // Vite resolves accepted HMR deps by static analysis. Generating both
328
- // here is exactly what frees the caller from writing it. The swap is
329
- // self-accepted at this boundary, so it never propagates to adopters.
330
- const urlSpecifier = JSON.stringify(`${file}?url`);
331
- return {
332
- code: `import url from ${urlSpecifier};\n` +
333
- `import {urlSheet} from ${JSON.stringify(helperPath)};\n` +
334
- `const {sheet, onHotUpdate} = urlSheet(url);\n` +
335
- `export default sheet;\n` +
336
- `if (import.meta.hot) {\n` +
337
- ` import.meta.hot.accept(${urlSpecifier}, onHotUpdate);\n` +
338
- `}\n`,
339
- moduleType: 'js',
340
- };
341
- }
342
- return null;
343
- },
344
- };
275
+ let isBuild = false;
276
+ let isLib = false;
277
+ return {
278
+ name: 'lit-css-query',
279
+ // Vite's core resolver claims `./x.css?hmr-url` for the CSS pipeline
280
+ // before normal plugins get a look, so resolve ahead of it.
281
+ enforce: 'pre',
282
+ configResolved(config) {
283
+ isBuild = config.command === 'build';
284
+ isLib = config.build.lib !== false && config.build.lib !== undefined;
285
+ },
286
+ async resolveId(id, importer) {
287
+ const prefix = CSS_URL_QUERY_RE.test(id)
288
+ ? CSS_URL_VIRTUAL_PREFIX
289
+ : CSS_SHEET_QUERY_RE.test(id)
290
+ ? CSS_SHEET_VIRTUAL_PREFIX
291
+ : null;
292
+ if (prefix === null) {
293
+ return null;
294
+ }
295
+ const file = id.slice(0, id.indexOf('?'));
296
+ const resolved = await this.resolve(file, importer);
297
+ if (resolved === null) {
298
+ return null;
299
+ }
300
+ return prefix + resolved.id + CSS_VIRTUAL_SUFFIX;
301
+ },
302
+ load(id) {
303
+ const helperPath = resolveRuntimeModule('css');
304
+ if (id.startsWith(CSS_URL_VIRTUAL_PREFIX)) {
305
+ const file = id.slice(
306
+ CSS_URL_VIRTUAL_PREFIX.length,
307
+ -CSS_VIRTUAL_SUFFIX.length
308
+ );
309
+ return {
310
+ code:
311
+ `import url from ${JSON.stringify(`${file}?url`)};\n` +
312
+ `import {devCacheBust} from ${JSON.stringify(helperPath)};\n` +
313
+ `export default devCacheBust(url);\n`,
314
+ moduleType: 'js',
315
+ };
316
+ }
317
+ if (id.startsWith(CSS_SHEET_VIRTUAL_PREFIX)) {
318
+ const file = id.slice(
319
+ CSS_SHEET_VIRTUAL_PREFIX.length,
320
+ -CSS_VIRTUAL_SUFFIX.length
321
+ );
322
+ const mode = getCssSheetBuild();
323
+ const inlineQuery =
324
+ !isBuild || mode === 'url' || (mode === 'auto' && !isLib)
325
+ ? null
326
+ : mode === 'inline-raw'
327
+ ? '?raw'
328
+ : '?inline';
329
+ if (inlineQuery !== null) {
330
+ // Build only, so no `import.meta.hot` block. The sheet is still
331
+ // constructed once per virtual module, so adopters share it exactly
332
+ // as they do in the fetch-backed form.
333
+ return {
334
+ code:
335
+ `import css from ${JSON.stringify(`${file}${inlineQuery}`)};\n` +
336
+ `const sheet = new CSSStyleSheet();\n` +
337
+ `sheet.replaceSync(css);\n` +
338
+ `export default sheet;\n`,
339
+ moduleType: 'js',
340
+ };
341
+ }
342
+ // The accept specifier must be byte-identical to the import above —
343
+ // Vite resolves accepted HMR deps by static analysis. Generating both
344
+ // here is exactly what frees the caller from writing it. The swap is
345
+ // self-accepted at this boundary, so it never propagates to adopters.
346
+ const urlSpecifier = JSON.stringify(`${file}?url`);
347
+ return {
348
+ code:
349
+ `import url from ${urlSpecifier};\n` +
350
+ `import {urlSheet} from ${JSON.stringify(helperPath)};\n` +
351
+ `const {sheet, onHotUpdate} = urlSheet(url);\n` +
352
+ `export default sheet;\n` +
353
+ `if (import.meta.hot) {\n` +
354
+ ` import.meta.hot.accept(${urlSpecifier}, onHotUpdate);\n` +
355
+ `}\n`,
356
+ moduleType: 'js',
357
+ };
358
+ }
359
+ return null;
360
+ },
361
+ };
362
+ };
363
+ const TIMELINE_VIRTUAL_ID = 'virtual:lit-plugin/timeline';
364
+ const TIMELINE_RESOLVED_ID = '\0virtual:lit-plugin/timeline';
365
+ const TIMELINE_STUB =
366
+ 'export const addTimelineEvent = () => {};\n' +
367
+ 'export const addTimelineLayer = () => {};\n';
368
+ /**
369
+ * The public timeline API virtual module, served in dev and build alike:
370
+ * app code importing it must keep building under `vite build`, where the HMR
371
+ * plugin doesn't apply. While these hooks lived on that serve-only plugin,
372
+ * nothing claimed the specifier during a build and the bundler failed to
373
+ * resolve it.
374
+ *
375
+ * `getTimeline` is a getter, not a value: `litPlugin()` re-resolves its
376
+ * options against the loaded env in a `config` hook, which runs after the
377
+ * plugin array is built.
378
+ */
379
+ export const litTimelineVirtual = (getTimeline = () => false) => {
380
+ let isBuild = false;
381
+ return {
382
+ name: 'lit-timeline-virtual',
383
+ configResolved(config) {
384
+ isBuild = config.command === 'build';
385
+ },
386
+ resolveId(id) {
387
+ return id === TIMELINE_VIRTUAL_ID ? TIMELINE_RESOLVED_ID : null;
388
+ },
389
+ load(id) {
390
+ if (id !== TIMELINE_RESOLVED_ID) {
391
+ return null;
392
+ }
393
+ // Under build the stub is unconditional, even with `timeline: true`.
394
+ // The runtime delivers only over `import.meta.hot`, so in a production
395
+ // bundle it is a queue that never drains; the stub keeps the transport
396
+ // out of the bundle and makes the documented "no-op in production"
397
+ // behavior true.
398
+ if (isBuild || !getTimeline()) {
399
+ return {code: TIMELINE_STUB, moduleType: 'js'};
400
+ }
401
+ const apiPath = resolveRuntimeModule('timeline/public-api');
402
+ return {
403
+ code: `export * from ${JSON.stringify(apiPath)};\n`,
404
+ moduleType: 'js',
405
+ };
406
+ },
407
+ };
345
408
  };
346
409
  /**
347
410
  * A `css` tagged template literal with no interpolations and no escape
@@ -358,333 +421,336 @@ const CSS_LITERAL_RE = /(?<![\w$.])css`((?:[^`\\$]|\$(?!\{))*)`/g;
358
421
  * treatment (targets, drafts, …) as `.css` files. Applies in dev and build.
359
422
  */
360
423
  const litCssLiterals = () => {
361
- let lightningcss = null;
362
- let options;
363
- let minify = false;
364
- return {
365
- name: 'lit-css-literals',
366
- async configResolved(config) {
367
- if (config.css.transformer !== 'lightningcss') {
368
- return;
369
- }
370
- // The transformer setting guarantees the dependency: Vite itself
371
- // can't process .css files without it.
372
- lightningcss = await import('lightningcss');
373
- // cssModules makes no sense for a literal; everything else carries
374
- // over.
375
- const { cssModules: _cssModules, ...rest } = config.css.lightningcss ?? {};
376
- options = rest;
377
- minify = config.command === 'build';
378
- },
379
- transform(code, id) {
380
- if (lightningcss === null) {
381
- return null;
382
- }
383
- if (id.startsWith('\0') || id.includes('/node_modules/')) {
384
- return null;
385
- }
386
- const [file] = id.split('?', 2);
387
- if (!JS_FILE_RE.test(file) || !code.includes('css`')) {
388
- return null;
389
- }
390
- const ms = new MagicString(code);
391
- let changed = false;
392
- for (const m of code.matchAll(CSS_LITERAL_RE)) {
393
- const literal = m[1];
394
- if (literal.trim() === '') {
395
- continue;
396
- }
397
- let out;
398
- try {
399
- const result = lightningcss.transform({
400
- ...options,
401
- filename: file,
402
- code: Buffer.from(literal),
403
- minify,
404
- });
405
- out = Buffer.from(result.code).toString();
406
- }
407
- catch (e) {
408
- this.warn(`[lit-plugin] skipping css literal Lightning CSS couldn't parse: ${e.message}`);
409
- continue;
410
- }
411
- // Re-escape for the template literal the output goes back into.
412
- out = out.replace(/[\\`$]/g, '\\$&');
413
- if (out !== literal) {
414
- const start = m.index + 'css`'.length;
415
- ms.overwrite(start, start + literal.length, out);
416
- changed = true;
417
- }
418
- }
419
- if (!changed) {
420
- return null;
421
- }
422
- return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
423
- },
424
- };
424
+ let lightningcss = null;
425
+ let options;
426
+ let minify = false;
427
+ return {
428
+ name: 'lit-css-literals',
429
+ async configResolved(config) {
430
+ if (config.css.transformer !== 'lightningcss') {
431
+ return;
432
+ }
433
+ // The transformer setting guarantees the dependency: Vite itself
434
+ // can't process .css files without it.
435
+ lightningcss = await import('lightningcss');
436
+ // cssModules makes no sense for a literal; everything else carries
437
+ // over.
438
+ const {cssModules: _cssModules, ...rest} = config.css.lightningcss ?? {};
439
+ options = rest;
440
+ minify = config.command === 'build';
441
+ },
442
+ transform(code, id) {
443
+ if (lightningcss === null) {
444
+ return null;
445
+ }
446
+ if (id.startsWith('\0') || id.includes('/node_modules/')) {
447
+ return null;
448
+ }
449
+ const [file] = id.split('?', 2);
450
+ if (!JS_FILE_RE.test(file) || !code.includes('css`')) {
451
+ return null;
452
+ }
453
+ const ms = new MagicString(code);
454
+ let changed = false;
455
+ for (const m of code.matchAll(CSS_LITERAL_RE)) {
456
+ const literal = m[1];
457
+ if (literal.trim() === '') {
458
+ continue;
459
+ }
460
+ let out;
461
+ try {
462
+ const result = lightningcss.transform({
463
+ ...options,
464
+ filename: file,
465
+ code: Buffer.from(literal),
466
+ minify,
467
+ });
468
+ out = Buffer.from(result.code).toString();
469
+ } catch (e) {
470
+ this.warn(
471
+ `[lit-plugin] skipping css literal Lightning CSS couldn't parse: ${e.message}`
472
+ );
473
+ continue;
474
+ }
475
+ // Re-escape for the template literal the output goes back into.
476
+ out = out.replace(/[\\`$]/g, '\\$&');
477
+ if (out !== literal) {
478
+ const start = m.index + 'css`'.length;
479
+ ms.overwrite(start, start + literal.length, out);
480
+ changed = true;
481
+ }
482
+ }
483
+ if (!changed) {
484
+ return null;
485
+ }
486
+ return {code: ms.toString(), map: ms.generateMap({hires: true})};
487
+ },
488
+ };
425
489
  };
426
490
  /**
427
491
  * Vite plugin set providing HMR, CSS helpers, and Lightning CSS processing
428
492
  * for Lit projects.
429
493
  */
430
494
  export const litPlugin = (options = {}) => {
431
- // Resolved from explicit options first, env vars second, defaults last. The
432
- // `lit-plugin-options` `config` hook below re-resolves with the loaded env
433
- // once Vite hands us the mode; this initial pass covers code paths that run
434
- // before (or without) it.
435
- let resolved = resolveOptions(options, {});
436
- let root = '';
437
- // Env resolution lives in its own always-applied plugin: `?css-sheet` is a
438
- // build-time feature too, and the HMR plugin (`apply: 'serve'`) never gets a
439
- // `config` hook under `vite build`. `enforce: 'pre'` and first position put
440
- // it ahead of every other hook that reads `resolved`.
441
- const optionsPlugin = {
442
- name: 'lit-plugin-options',
443
- enforce: 'pre',
444
- config(viteConfig, { mode }) {
445
- // `loadEnv` reads `.env*` files from the env dir (root by default) and
446
- // merges in matching `process.env` keys, filtered to the `LIT_PLUGIN`
447
- // prefix. Explicit options still win (handled in resolveOptions).
448
- const envDir = viteConfig.envDir
449
- ? resolvePath(viteConfig.envDir)
450
- : viteConfig.root
451
- ? resolvePath(viteConfig.root)
452
- : process.cwd();
453
- resolved = resolveOptions(options, loadEnv(mode, envDir, ENV_PREFIX));
454
- },
455
- };
456
- const sourceOverlayPlugin = {
457
- name: 'lit-source-overlay',
458
- apply: 'serve',
459
- // Run before Vite's esbuild TS transform so source-meta line numbers are
460
- // measured against the author's raw source (and the `@customElement … class`
461
- // forms are still intact), not against transpiled output.
462
- enforce: 'pre',
463
- configResolved(config) {
464
- root = config.root;
495
+ // Resolved from explicit options first, env vars second, defaults last. The
496
+ // `lit-plugin-options` `config` hook below re-resolves with the loaded env
497
+ // once Vite hands us the mode; this initial pass covers code paths that run
498
+ // before (or without) it.
499
+ let resolved = resolveOptions(options, {});
500
+ let root = '';
501
+ // Env resolution lives in its own always-applied plugin: `?css-sheet` is a
502
+ // build-time feature too, and the HMR plugin (`apply: 'serve'`) never gets a
503
+ // `config` hook under `vite build`. `enforce: 'pre'` and first position put
504
+ // it ahead of every other hook that reads `resolved`.
505
+ const optionsPlugin = {
506
+ name: 'lit-plugin-options',
507
+ enforce: 'pre',
508
+ config(viteConfig, {mode}) {
509
+ // `loadEnv` reads `.env*` files from the env dir (root by default) and
510
+ // merges in matching `process.env` keys, filtered to the `LIT_PLUGIN`
511
+ // prefix. Explicit options still win (handled in resolveOptions).
512
+ const envDir = viteConfig.envDir
513
+ ? resolvePath(viteConfig.envDir)
514
+ : viteConfig.root
515
+ ? resolvePath(viteConfig.root)
516
+ : process.cwd();
517
+ resolved = resolveOptions(options, loadEnv(mode, envDir, ENV_PREFIX));
518
+ },
519
+ };
520
+ const sourceOverlayPlugin = {
521
+ name: 'lit-source-overlay',
522
+ apply: 'serve',
523
+ // Run before Vite's esbuild TS transform so source-meta line numbers are
524
+ // measured against the author's raw source (and the `@customElement … class`
525
+ // forms are still intact), not against transpiled output.
526
+ enforce: 'pre',
527
+ configResolved(config) {
528
+ root = config.root;
529
+ },
530
+ resolveId(id) {
531
+ if (id === '@oddsquad/vite-plugin-lit/source-overlay.js') {
532
+ return resolveRuntimeModule('source-overlay');
533
+ }
534
+ return null;
535
+ },
536
+ configureServer(server) {
537
+ if (!resolved.sourceOverlay) return;
538
+ // Allow opens from everything vite itself is willing to serve
539
+ // (`server.fs.allow` defaults to the workspace root), not just
540
+ // `config.root` — in monorepos, component sources regularly live in
541
+ // sibling packages outside the served app's root.
542
+ const fsAllow = server.config.server.fs?.allow ?? [];
543
+ server.middlewares.use(
544
+ OPEN_IN_EDITOR_PATH,
545
+ createOpenInEditorMiddleware([root, ...fsAllow])
546
+ );
547
+ },
548
+ transform(code, id, transformOptions) {
549
+ if (!resolved.sourceOverlay) return null;
550
+ if (transformOptions?.ssr) return null;
551
+ if (
552
+ id.startsWith('\0') ||
553
+ id.includes('__x00__') ||
554
+ id.includes('lit-plugin:') ||
555
+ id.includes('/node_modules/')
556
+ ) {
557
+ return null;
558
+ }
559
+ const [file] = id.split('?', 2);
560
+ if (!JS_FILE_RE.test(file) && !id.includes('?html-proxy')) {
561
+ return null;
562
+ }
563
+ if (!code.includes('customElement') && !code.includes('customElements')) {
564
+ return null;
565
+ }
566
+ const ms = new MagicString(code);
567
+ const relativeFile = file.startsWith(root + '/')
568
+ ? file.slice(root.length + 1)
569
+ : file;
570
+ if (!injectSourceMeta(code, relativeFile, ms)) {
571
+ return null;
572
+ }
573
+ return {code: ms.toString(), map: ms.generateMap({hires: true})};
574
+ },
575
+ transformIndexHtml() {
576
+ if (!resolved.sourceOverlay) return;
577
+ const overlayUrl = `/@fs/${resolveRuntimeModule('source-overlay')}`;
578
+ const {
579
+ exclude: _exclude,
580
+ onSelect: _onSelect,
581
+ ...overlayInit
582
+ } = resolved.sourceOverlay;
583
+ const initConfig = {
584
+ ...overlayInit,
585
+ openInEditorPath: OPEN_IN_EDITOR_PATH,
586
+ };
587
+ return [
588
+ {
589
+ tag: 'script',
590
+ attrs: {type: 'module'},
591
+ children: `import {initSourceOverlay} from ${JSON.stringify(overlayUrl)};\ninitSourceOverlay(${JSON.stringify(initConfig)});\n`,
592
+ injectTo: 'body',
465
593
  },
466
- resolveId(id) {
467
- if (id === '@lit-labs/vite-plugin-lit/source-overlay.js') {
468
- return resolveRuntimeModule('source-overlay');
469
- }
470
- return null;
471
- },
472
- configureServer(server) {
473
- if (!resolved.sourceOverlay)
474
- return;
475
- // Allow opens from everything vite itself is willing to serve
476
- // (`server.fs.allow` defaults to the workspace root), not just
477
- // `config.root` — in monorepos, component sources regularly live in
478
- // sibling packages outside the served app's root.
479
- const fsAllow = server.config.server.fs?.allow ?? [];
480
- server.middlewares.use(OPEN_IN_EDITOR_PATH, createOpenInEditorMiddleware([root, ...fsAllow]));
481
- },
482
- transform(code, id, transformOptions) {
483
- if (!resolved.sourceOverlay)
484
- return null;
485
- if (transformOptions?.ssr)
486
- return null;
487
- if (id.startsWith('\0') ||
488
- id.includes('__x00__') ||
489
- id.includes('lit-plugin:') ||
490
- id.includes('/node_modules/')) {
491
- return null;
492
- }
493
- const [file] = id.split('?', 2);
494
- if (!JS_FILE_RE.test(file) && !id.includes('?html-proxy')) {
495
- return null;
496
- }
497
- if (!code.includes('customElement') && !code.includes('customElements')) {
498
- return null;
499
- }
500
- const ms = new MagicString(code);
501
- const relativeFile = file.startsWith(root + '/')
502
- ? file.slice(root.length + 1)
503
- : file;
504
- if (!injectSourceMeta(code, relativeFile, ms)) {
505
- return null;
506
- }
507
- return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
508
- },
509
- transformIndexHtml() {
510
- if (!resolved.sourceOverlay)
511
- return;
512
- const overlayUrl = `/@fs/${resolveRuntimeModule('source-overlay')}`;
513
- const { exclude: _exclude, onSelect: _onSelect, ...overlayInit } = resolved.sourceOverlay;
514
- const initConfig = {
515
- ...overlayInit,
516
- openInEditorPath: OPEN_IN_EDITOR_PATH,
517
- };
518
- return [
519
- {
520
- tag: 'script',
521
- attrs: { type: 'module' },
522
- children: `import {initSourceOverlay} from ${JSON.stringify(overlayUrl)};\ninitSourceOverlay(${JSON.stringify(initConfig)});\n`,
523
- injectTo: 'body',
524
- },
525
- ];
526
- },
527
- };
528
- const hmr = {
529
- name: 'lit-plugin',
530
- apply: 'serve',
531
- config: () => {
532
- // Options are already resolved against the loaded env by
533
- // `lit-plugin-options`, which runs first.
534
- if (!resolved.hmrEnabled) {
535
- return;
536
- }
537
- // The injected runtime imports are invisible to the dep scanner. The
538
- // lit family stays prebundle-eligible on purpose: the wrapper modules'
539
- // bare imports then resolve to the same URL every other importer gets —
540
- // single lit instance, single template cache.
541
- return { optimizeDeps: { exclude: ['@lit-labs/vite-plugin-lit'] } };
542
- },
543
- resolveId(id) {
544
- // Public timeline API virtual module.
545
- if (id === 'virtual:lit-plugin/timeline') {
546
- return '\0virtual:lit-plugin/timeline';
547
- }
548
- // Resolve the browser CSS helpers and indicator runtime to the copy
549
- // shipped next to this plugin, so they work even when the package
550
- // isn't reachable through node resolution from the served root (and
551
- // stay out of prebundling).
552
- if (id === '@lit-labs/vite-plugin-lit/css.js') {
553
- return resolveRuntimeModule('css');
554
- }
555
- if (id === '@lit-labs/vite-plugin-lit/indicator.js') {
556
- return resolveRuntimeModule('indicator');
557
- }
558
- if (id === '@lit-labs/vite-plugin-lit/source-overlay.js') {
559
- return resolveRuntimeModule('source-overlay');
560
- }
561
- if (resolved.hmrEnabled && id.startsWith(VIRTUAL_PREFIX)) {
562
- return id;
563
- }
564
- return null;
565
- },
566
- load(id) {
567
- // Public timeline API — re-export the runtime module when timeline is
568
- // enabled, otherwise a no-op stub so imports don't throw in prod builds.
569
- if (id === '\0virtual:lit-plugin/timeline') {
570
- if (!resolved.timeline) {
571
- return {
572
- code: 'export const addTimelineEvent = () => {};\n' +
573
- 'export const addTimelineLayer = () => {};\n',
574
- moduleType: 'js',
575
- };
576
- }
577
- const apiPath = resolveRuntimeModule('timeline/public-api');
578
- return {
579
- code: `export * from ${JSON.stringify(apiPath)};\n`,
580
- moduleType: 'js',
581
- };
582
- }
583
- if (!resolved.hmrEnabled || !id.startsWith(VIRTUAL_PREFIX)) {
584
- return null;
585
- }
586
- if (id === INSTALL_ID) {
587
- const patchPath = resolveRuntimeModule('patch');
588
- const runtimeOptions = {
589
- reconnect: resolved.reconnect,
590
- onIncompatible: resolved.onIncompatible,
591
- };
592
- return {
593
- code: `import {install} from ${JSON.stringify(patchPath)};\n` +
594
- `install(${JSON.stringify(runtimeOptions)});\n`,
595
- moduleType: 'js',
596
- };
597
- }
598
- const spec = id.slice(VIRTUAL_PREFIX.length);
599
- const wrappedTags = WRAP_TABLE.get(spec);
600
- if (wrappedTags === undefined) {
601
- return null;
602
- }
603
- const internPath = resolveRuntimeModule('intern');
604
- // Browser-native ESM: explicit local exports shadow `export *` names
605
- // (spec-guaranteed), so everything except the wrapped tags passes
606
- // through unchanged.
607
- const lines = [
608
- `import * as __lit from ${JSON.stringify(spec)};`,
609
- `import {wrapTag} from ${JSON.stringify(internPath)};`,
610
- `export * from ${JSON.stringify(spec)};`,
611
- ];
612
- for (const { exportName, ns } of wrappedTags) {
613
- lines.push(`export const ${exportName} = wrapTag(__lit.${exportName}, ${JSON.stringify(ns)});`);
614
- }
615
- return { code: lines.join('\n') + '\n', moduleType: 'js' };
616
- },
617
- async transform(code, id, transformOptions) {
618
- if (!resolved.hmrEnabled) {
619
- return null;
620
- }
621
- if (transformOptions?.ssr) {
622
- return null;
623
- }
624
- if (id.startsWith('\0') || id.includes('/node_modules/')) {
625
- return null;
626
- }
627
- const [file] = id.split('?', 2);
628
- // Allow inline scripts extracted from HTML (`?html-proxy`).
629
- if (!JS_FILE_RE.test(file) && !id.includes('?html-proxy')) {
630
- return null;
631
- }
632
- return transformLitModule(code);
633
- },
634
- transformIndexHtml() {
635
- const tags = [];
636
- const indicator = resolved.indicator;
637
- if (indicator) {
638
- const indicatorUrl = `/@fs/` + resolveRuntimeModule('indicator');
639
- tags.push({
640
- tag: 'script',
641
- attrs: { type: 'module', src: indicatorUrl },
642
- injectTo: 'body',
643
- }, {
644
- tag: 'lit-devtools-hmr-indicator',
645
- attrs: indicator.count ? { count: '' } : undefined,
646
- children: '',
647
- injectTo: 'body',
648
- });
649
- }
650
- if (resolved.timeline) {
651
- const installUrl = `/@fs/` + resolveRuntimeModule('timeline/install');
652
- tags.push({
653
- tag: 'script',
654
- attrs: { type: 'module', src: installUrl },
655
- injectTo: 'body',
656
- });
657
- // Components inspector runtime — answers the panel's tree/details
658
- // requests. Paired with the timeline panel, which hosts its tab.
659
- const inspectorUrl = `/@fs/` + resolveRuntimeModule('inspector/install');
660
- tags.push({
661
- tag: 'script',
662
- attrs: { type: 'module', src: inspectorUrl },
663
- injectTo: 'body',
664
- });
665
- }
666
- return tags.length > 0 ? tags : undefined;
667
- },
668
- };
669
- // The source-overlay plugin is always present; its hooks no-op when the
670
- // feature is disabled (which env may decide), so inclusion can't be gated
671
- // on the synchronously-known options here.
672
- const plugins = [
673
- optionsPlugin,
674
- // Getter, not a snapshot: `resolved` is re-resolved against the loaded env
675
- // in `optionsPlugin`'s `config` hook, which runs after this array is built.
676
- litCssQueries(() => resolved.cssSheetBuild),
677
- litCssLiterals(),
678
- sourceOverlayPlugin,
679
- hmr,
680
- ];
681
- if (resolved.timeline) {
682
- // Pass a getter, not a snapshot: `resolved` is re-resolved against the
683
- // loaded env in the `config` hook, which runs after this plugin array is
684
- // built. The settings endpoint reads it per-request, by which point env is
685
- // applied.
686
- plugins.push(litTimelinePlugin(() => toFeatureSettings(resolved)));
687
- }
688
- return plugins;
594
+ ];
595
+ },
596
+ };
597
+ const hmr = {
598
+ name: 'lit-plugin',
599
+ apply: 'serve',
600
+ config: () => {
601
+ // Options are already resolved against the loaded env by
602
+ // `lit-plugin-options`, which runs first.
603
+ if (!resolved.hmrEnabled) {
604
+ return;
605
+ }
606
+ // The injected runtime imports are invisible to the dep scanner. The
607
+ // lit family stays prebundle-eligible on purpose: the wrapper modules'
608
+ // bare imports then resolve to the same URL every other importer gets —
609
+ // single lit instance, single template cache.
610
+ return {optimizeDeps: {exclude: ['@oddsquad/vite-plugin-lit']}};
611
+ },
612
+ resolveId(id) {
613
+ // Resolve the browser CSS helpers and indicator runtime to the copy
614
+ // shipped next to this plugin, so they work even when the package
615
+ // isn't reachable through node resolution from the served root (and
616
+ // stay out of prebundling).
617
+ //
618
+ // These bare specifiers must match the package name in package.json —
619
+ // consumers import them by name (see README "Stylesheets"), and Vite
620
+ // only consults this hook for the exact string. A rename that misses
621
+ // them turns the fallback into silently dead code.
622
+ if (id === '@oddsquad/vite-plugin-lit/css.js') {
623
+ return resolveRuntimeModule('css');
624
+ }
625
+ if (id === '@oddsquad/vite-plugin-lit/indicator.js') {
626
+ return resolveRuntimeModule('indicator');
627
+ }
628
+ if (id === '@oddsquad/vite-plugin-lit/source-overlay.js') {
629
+ return resolveRuntimeModule('source-overlay');
630
+ }
631
+ if (resolved.hmrEnabled && id.startsWith(VIRTUAL_PREFIX)) {
632
+ return id;
633
+ }
634
+ return null;
635
+ },
636
+ load(id) {
637
+ if (!resolved.hmrEnabled || !id.startsWith(VIRTUAL_PREFIX)) {
638
+ return null;
639
+ }
640
+ if (id === INSTALL_ID) {
641
+ const patchPath = resolveRuntimeModule('patch');
642
+ const runtimeOptions = {
643
+ reconnect: resolved.reconnect,
644
+ onIncompatible: resolved.onIncompatible,
645
+ };
646
+ return {
647
+ code:
648
+ `import {install} from ${JSON.stringify(patchPath)};\n` +
649
+ `install(${JSON.stringify(runtimeOptions)});\n`,
650
+ moduleType: 'js',
651
+ };
652
+ }
653
+ const spec = id.slice(VIRTUAL_PREFIX.length);
654
+ const wrappedTags = WRAP_TABLE.get(spec);
655
+ if (wrappedTags === undefined) {
656
+ return null;
657
+ }
658
+ const internPath = resolveRuntimeModule('intern');
659
+ // Browser-native ESM: explicit local exports shadow `export *` names
660
+ // (spec-guaranteed), so everything except the wrapped tags passes
661
+ // through unchanged.
662
+ const lines = [
663
+ `import * as __lit from ${JSON.stringify(spec)};`,
664
+ `import {wrapTag} from ${JSON.stringify(internPath)};`,
665
+ `export * from ${JSON.stringify(spec)};`,
666
+ ];
667
+ for (const {exportName, ns} of wrappedTags) {
668
+ lines.push(
669
+ `export const ${exportName} = wrapTag(__lit.${exportName}, ${JSON.stringify(ns)});`
670
+ );
671
+ }
672
+ return {code: lines.join('\n') + '\n', moduleType: 'js'};
673
+ },
674
+ async transform(code, id, transformOptions) {
675
+ if (!resolved.hmrEnabled) {
676
+ return null;
677
+ }
678
+ if (transformOptions?.ssr) {
679
+ return null;
680
+ }
681
+ if (id.startsWith('\0') || id.includes('/node_modules/')) {
682
+ return null;
683
+ }
684
+ const [file] = id.split('?', 2);
685
+ // Allow inline scripts extracted from HTML (`?html-proxy`).
686
+ if (!JS_FILE_RE.test(file) && !id.includes('?html-proxy')) {
687
+ return null;
688
+ }
689
+ return transformLitModule(code);
690
+ },
691
+ transformIndexHtml() {
692
+ const tags = [];
693
+ const indicator = resolved.indicator;
694
+ if (indicator) {
695
+ const indicatorUrl = `/@fs/` + resolveRuntimeModule('indicator');
696
+ tags.push(
697
+ {
698
+ tag: 'script',
699
+ attrs: {type: 'module', src: indicatorUrl},
700
+ injectTo: 'body',
701
+ },
702
+ {
703
+ tag: 'lit-devtools-hmr-indicator',
704
+ attrs: indicator.count ? {count: ''} : undefined,
705
+ children: '',
706
+ injectTo: 'body',
707
+ }
708
+ );
709
+ }
710
+ if (resolved.timeline) {
711
+ const installUrl = `/@fs/` + resolveRuntimeModule('timeline/install');
712
+ tags.push({
713
+ tag: 'script',
714
+ attrs: {type: 'module', src: installUrl},
715
+ injectTo: 'body',
716
+ });
717
+ // Components inspector runtime — answers the panel's tree/details
718
+ // requests. Paired with the timeline panel, which hosts its tab.
719
+ const inspectorUrl =
720
+ `/@fs/` + resolveRuntimeModule('inspector/install');
721
+ tags.push({
722
+ tag: 'script',
723
+ attrs: {type: 'module', src: inspectorUrl},
724
+ injectTo: 'body',
725
+ });
726
+ }
727
+ return tags.length > 0 ? tags : undefined;
728
+ },
729
+ };
730
+ // The source-overlay plugin is always present; its hooks no-op when the
731
+ // feature is disabled (which env may decide), so inclusion can't be gated
732
+ // on the synchronously-known options here.
733
+ const plugins = [
734
+ optionsPlugin,
735
+ // Getter, not a snapshot: `resolved` is re-resolved against the loaded env
736
+ // in `optionsPlugin`'s `config` hook, which runs after this array is built.
737
+ litCssQueries(() => resolved.cssSheetBuild),
738
+ litTimelineVirtual(() => resolved.timeline),
739
+ litCssLiterals(),
740
+ sourceOverlayPlugin,
741
+ hmr,
742
+ ];
743
+ if (resolved.timeline) {
744
+ // `features` is a getter, not a snapshot: `resolved` is re-resolved
745
+ // against the loaded env in `optionsPlugin`'s `config` hook, which runs
746
+ // after this array is built.
747
+ plugins.push(
748
+ createLitDevframePlugin({
749
+ version: PACKAGE_VERSION,
750
+ features: () => toFeatureSettings(resolved),
751
+ })
752
+ );
753
+ }
754
+ return plugins;
689
755
  };
690
- //# sourceMappingURL=plugin.js.map
756
+ //# sourceMappingURL=plugin.js.map