@oddsquad/vite-plugin-lit 0.0.3 → 0.0.5

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 (175) hide show
  1. package/index.d.ts +3 -2
  2. package/index.d.ts.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/color-scheme.d.ts +30 -0
  6. package/lib/color-scheme.d.ts.map +1 -0
  7. package/lib/color-scheme.js +41 -0
  8. package/lib/color-scheme.js.map +1 -0
  9. package/lib/icons.d.ts +18 -0
  10. package/lib/icons.d.ts.map +1 -0
  11. package/lib/icons.js +23 -0
  12. package/lib/icons.js.map +1 -0
  13. package/lib/plugin.d.ts +71 -6
  14. package/lib/plugin.d.ts.map +1 -1
  15. package/lib/plugin.js +522 -21
  16. package/lib/plugin.js.map +1 -1
  17. package/lib/runtime/css.d.ts +94 -0
  18. package/lib/runtime/css.d.ts.map +1 -0
  19. package/lib/runtime/css.js +112 -0
  20. package/lib/runtime/css.js.map +1 -0
  21. package/lib/runtime/edge-panel.d.ts +18 -0
  22. package/lib/runtime/edge-panel.d.ts.map +1 -0
  23. package/lib/runtime/edge-panel.js +130 -0
  24. package/lib/runtime/edge-panel.js.map +1 -0
  25. package/lib/runtime/fonts.d.ts +18 -0
  26. package/lib/runtime/fonts.d.ts.map +1 -0
  27. package/lib/runtime/fonts.js +18 -0
  28. package/lib/runtime/fonts.js.map +1 -0
  29. package/lib/runtime/indicator.d.ts +7 -0
  30. package/lib/runtime/indicator.d.ts.map +1 -0
  31. package/lib/runtime/indicator.js +141 -0
  32. package/lib/runtime/indicator.js.map +1 -0
  33. package/lib/runtime/inspector/collect.d.ts +17 -0
  34. package/lib/runtime/inspector/collect.d.ts.map +1 -0
  35. package/lib/runtime/inspector/collect.js +132 -0
  36. package/lib/runtime/inspector/collect.js.map +1 -0
  37. package/lib/runtime/inspector/install.d.ts +7 -0
  38. package/lib/runtime/inspector/install.d.ts.map +1 -0
  39. package/lib/runtime/inspector/install.js +204 -0
  40. package/lib/runtime/inspector/install.js.map +1 -0
  41. package/lib/runtime/inspector/serialize.d.ts +10 -0
  42. package/lib/runtime/inspector/serialize.d.ts.map +1 -0
  43. package/lib/runtime/inspector/serialize.js +133 -0
  44. package/lib/runtime/inspector/serialize.js.map +1 -0
  45. package/lib/runtime/intern.d.ts.map +1 -1
  46. package/lib/runtime/intern.js +1 -1
  47. package/lib/runtime/intern.js.map +1 -1
  48. package/lib/runtime/overrides.d.ts +25 -0
  49. package/lib/runtime/overrides.d.ts.map +1 -0
  50. package/lib/runtime/overrides.js +33 -0
  51. package/lib/runtime/overrides.js.map +1 -0
  52. package/lib/runtime/patch.d.ts +6 -15
  53. package/lib/runtime/patch.d.ts.map +1 -1
  54. package/lib/runtime/patch.js +81 -28
  55. package/lib/runtime/patch.js.map +1 -1
  56. package/lib/runtime/source-meta.d.ts +16 -0
  57. package/lib/runtime/source-meta.d.ts.map +1 -0
  58. package/lib/runtime/source-meta.js +8 -0
  59. package/lib/runtime/source-meta.js.map +1 -0
  60. package/lib/runtime/source-overlay/editors.d.ts +9 -0
  61. package/lib/runtime/source-overlay/editors.d.ts.map +1 -0
  62. package/lib/runtime/source-overlay/editors.js +40 -0
  63. package/lib/runtime/source-overlay/editors.js.map +1 -0
  64. package/lib/runtime/source-overlay/mask-path.d.ts +7 -0
  65. package/lib/runtime/source-overlay/mask-path.d.ts.map +1 -0
  66. package/lib/runtime/source-overlay/mask-path.js +26 -0
  67. package/lib/runtime/source-overlay/mask-path.js.map +1 -0
  68. package/lib/runtime/source-overlay/overlay-element.d.ts +18 -0
  69. package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -0
  70. package/lib/runtime/source-overlay/overlay-element.js +381 -0
  71. package/lib/runtime/source-overlay/overlay-element.js.map +1 -0
  72. package/lib/runtime/source-overlay/source-host.d.ts +14 -0
  73. package/lib/runtime/source-overlay/source-host.d.ts.map +1 -0
  74. package/lib/runtime/source-overlay/source-host.js +94 -0
  75. package/lib/runtime/source-overlay/source-host.js.map +1 -0
  76. package/lib/runtime/source-overlay/template.d.ts +7 -0
  77. package/lib/runtime/source-overlay/template.d.ts.map +1 -0
  78. package/lib/runtime/source-overlay/template.js +125 -0
  79. package/lib/runtime/source-overlay/template.js.map +1 -0
  80. package/lib/runtime/source-overlay.d.ts +7 -0
  81. package/lib/runtime/source-overlay.d.ts.map +1 -0
  82. package/lib/runtime/source-overlay.js +11 -0
  83. package/lib/runtime/source-overlay.js.map +1 -0
  84. package/lib/runtime/timeline/clock.d.ts +10 -0
  85. package/lib/runtime/timeline/clock.d.ts.map +1 -0
  86. package/lib/runtime/timeline/clock.js +22 -0
  87. package/lib/runtime/timeline/clock.js.map +1 -0
  88. package/lib/runtime/timeline/identity.d.ts +37 -0
  89. package/lib/runtime/timeline/identity.d.ts.map +1 -0
  90. package/lib/runtime/timeline/identity.js +73 -0
  91. package/lib/runtime/timeline/identity.js.map +1 -0
  92. package/lib/runtime/timeline/input.d.ts +16 -0
  93. package/lib/runtime/timeline/input.d.ts.map +1 -0
  94. package/lib/runtime/timeline/input.js +87 -0
  95. package/lib/runtime/timeline/input.js.map +1 -0
  96. package/lib/runtime/timeline/install.d.ts +7 -0
  97. package/lib/runtime/timeline/install.d.ts.map +1 -0
  98. package/lib/runtime/timeline/install.js +69 -0
  99. package/lib/runtime/timeline/install.js.map +1 -0
  100. package/lib/runtime/timeline/lifecycle.d.ts +27 -0
  101. package/lib/runtime/timeline/lifecycle.d.ts.map +1 -0
  102. package/lib/runtime/timeline/lifecycle.js +225 -0
  103. package/lib/runtime/timeline/lifecycle.js.map +1 -0
  104. package/lib/runtime/timeline/public-api.d.ts +28 -0
  105. package/lib/runtime/timeline/public-api.d.ts.map +1 -0
  106. package/lib/runtime/timeline/public-api.js +44 -0
  107. package/lib/runtime/timeline/public-api.js.map +1 -0
  108. package/lib/runtime/timeline/render.d.ts +39 -0
  109. package/lib/runtime/timeline/render.d.ts.map +1 -0
  110. package/lib/runtime/timeline/render.js +109 -0
  111. package/lib/runtime/timeline/render.js.map +1 -0
  112. package/lib/runtime/timeline/transport.d.ts +42 -0
  113. package/lib/runtime/timeline/transport.d.ts.map +1 -0
  114. package/lib/runtime/timeline/transport.js +86 -0
  115. package/lib/runtime/timeline/transport.js.map +1 -0
  116. package/lib/segmented-tabs.d.ts +33 -0
  117. package/lib/segmented-tabs.d.ts.map +1 -0
  118. package/lib/segmented-tabs.js +129 -0
  119. package/lib/segmented-tabs.js.map +1 -0
  120. package/lib/source-meta.d.ts +26 -0
  121. package/lib/source-meta.d.ts.map +1 -0
  122. package/lib/source-meta.js +129 -0
  123. package/lib/source-meta.js.map +1 -0
  124. package/lib/timeline-plugin.d.ts +72 -0
  125. package/lib/timeline-plugin.d.ts.map +1 -0
  126. package/lib/timeline-plugin.js +376 -0
  127. package/lib/timeline-plugin.js.map +1 -0
  128. package/lib/tokens.d.ts +38 -0
  129. package/lib/tokens.d.ts.map +1 -0
  130. package/lib/tokens.js +263 -0
  131. package/lib/tokens.js.map +1 -0
  132. package/lib/transform.d.ts +5 -5
  133. package/lib/transform.d.ts.map +1 -1
  134. package/lib/transform.js +3 -3
  135. package/lib/transform.js.map +1 -1
  136. package/lib/types.d.ts +34 -0
  137. package/lib/types.d.ts.map +1 -0
  138. package/lib/types.js +7 -0
  139. package/lib/types.js.map +1 -0
  140. package/lib/wrap-table.d.ts.map +1 -1
  141. package/lib/wrap-table.js.map +1 -1
  142. package/package.json +18 -4
  143. package/panel/components-view.d.ts +60 -0
  144. package/panel/components-view.d.ts.map +1 -0
  145. package/panel/components-view.js +581 -0
  146. package/panel/components-view.js.map +1 -0
  147. package/panel/devtools-settings.d.ts +41 -0
  148. package/panel/devtools-settings.d.ts.map +1 -0
  149. package/panel/devtools-settings.js +463 -0
  150. package/panel/devtools-settings.js.map +1 -0
  151. package/panel/index.html +29 -0
  152. package/panel/lit-devtools-panel.d.ts +37 -0
  153. package/panel/lit-devtools-panel.d.ts.map +1 -0
  154. package/panel/lit-devtools-panel.js +183 -0
  155. package/panel/lit-devtools-panel.js.map +1 -0
  156. package/panel/timeline-event-list.d.ts +43 -0
  157. package/panel/timeline-event-list.d.ts.map +1 -0
  158. package/panel/timeline-event-list.js +416 -0
  159. package/panel/timeline-event-list.js.map +1 -0
  160. package/panel/timeline-layers.d.ts +25 -0
  161. package/panel/timeline-layers.d.ts.map +1 -0
  162. package/panel/timeline-layers.js +96 -0
  163. package/panel/timeline-layers.js.map +1 -0
  164. package/panel/timeline-view.d.ts +38 -0
  165. package/panel/timeline-view.d.ts.map +1 -0
  166. package/panel/timeline-view.js +234 -0
  167. package/panel/timeline-view.js.map +1 -0
  168. package/types/inspector.d.ts +130 -0
  169. package/types/inspector.d.ts.map +1 -0
  170. package/types/inspector.js +16 -0
  171. package/types/inspector.js.map +1 -0
  172. package/types/timeline.d.ts +98 -0
  173. package/types/timeline.d.ts.map +1 -0
  174. package/types/timeline.js +39 -0
  175. package/types/timeline.js.map +1 -0
package/lib/plugin.js CHANGED
@@ -4,9 +4,98 @@
4
4
  * SPDX-License-Identifier: BSD-3-Clause
5
5
  */
6
6
  import { existsSync } from 'node:fs';
7
+ import { resolve as resolvePath, sep } from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
9
+ import { loadEnv } from 'vite';
10
+ import MagicString from 'magic-string';
11
+ import { injectSourceMeta } from './source-meta.js';
8
12
  import { INSTALL_ID, VIRTUAL_PREFIX, transformLitModule } from './transform.js';
9
13
  import { WRAP_TABLE } from './wrap-table.js';
14
+ import { litTimelinePlugin } from './timeline-plugin.js';
15
+ /** Env var prefix consumed at config time. */
16
+ const ENV_PREFIX = 'LIT_PLUGIN';
17
+ /** Parse a boolean-ish env string; `undefined` when unset/unrecognized. */
18
+ const envBool = (v) => v === 'true' || v === '1'
19
+ ? true
20
+ : v === 'false' || v === '0'
21
+ ? false
22
+ : undefined;
23
+ /** Parse a numeric env string; `undefined` when unset/non-numeric. */
24
+ const envNum = (v) => {
25
+ if (v === undefined || v === '')
26
+ return undefined;
27
+ const n = Number(v);
28
+ return Number.isNaN(n) ? undefined : n;
29
+ };
30
+ /**
31
+ * Merge explicit options over env vars over defaults (in that precedence) into
32
+ * the flat shape the plugin hooks consume.
33
+ */
34
+ const resolveOptions = (options, env) => {
35
+ const hmr = options.hmr;
36
+ const hmrObj = typeof hmr === 'object' ? hmr : undefined;
37
+ const hmrEnabled = (typeof hmr === 'boolean' ? hmr : hmrObj?.enabled) ??
38
+ envBool(env[`${ENV_PREFIX}_HMR`]) ??
39
+ true;
40
+ const reconnect = hmrObj?.reconnect ?? envBool(env[`${ENV_PREFIX}_HMR_RECONNECT`]) ?? false;
41
+ const onIncompatible = hmrObj?.onIncompatible ??
42
+ env[`${ENV_PREFIX}_HMR_ON_INCOMPATIBLE`] ??
43
+ 'reload';
44
+ const ind = hmrObj?.indicator;
45
+ const indObj = typeof ind === 'object' ? ind : undefined;
46
+ const indEnabled = (typeof ind === 'boolean' ? ind : indObj?.enabled) ??
47
+ envBool(env[`${ENV_PREFIX}_HMR_INDICATOR`]) ??
48
+ true;
49
+ const indCount = indObj?.count ?? envBool(env[`${ENV_PREFIX}_HMR_INDICATOR_COUNT`]) ?? false;
50
+ // The indicator is meaningless without HMR, so it follows the master toggle.
51
+ const indicator = hmrEnabled && indEnabled ? { count: indCount } : false;
52
+ const so = options.sourceOverlay;
53
+ const soExplicit = typeof so === 'boolean' ? so : so === undefined ? undefined : true;
54
+ const soEnabled = soExplicit ?? envBool(env[`${ENV_PREFIX}_SOURCE_OVERLAY`]) ?? false;
55
+ let sourceOverlay = false;
56
+ if (soEnabled) {
57
+ const base = typeof so === 'object' ? { ...so } : {};
58
+ base.key ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_KEY`] || undefined;
59
+ base.editor ??= env[`${ENV_PREFIX}_SOURCE_OVERLAY_EDITOR`] || undefined;
60
+ base.throttleMs ??= envNum(env[`${ENV_PREFIX}_SOURCE_OVERLAY_THROTTLE_MS`]);
61
+ sourceOverlay = base;
62
+ }
63
+ const timeline = options.timeline ?? envBool(env[`${ENV_PREFIX}_TIMELINE`]) ?? false;
64
+ return {
65
+ hmrEnabled,
66
+ reconnect,
67
+ onIncompatible,
68
+ indicator,
69
+ sourceOverlay,
70
+ timeline,
71
+ };
72
+ };
73
+ /**
74
+ * Flattens resolved options into the JSON-serializable shape the panel's
75
+ * Settings tab consumes (callback options like `exclude`/`onSelect` dropped;
76
+ * a custom editor object reported as `"custom"`). Default key/editor/throttle
77
+ * are applied here so the panel shows the effective values the runtime uses.
78
+ */
79
+ const toFeatureSettings = (r) => {
80
+ const so = r.sourceOverlay;
81
+ const editor = so === false ? undefined : so.editor;
82
+ return {
83
+ hmr: {
84
+ enabled: r.hmrEnabled,
85
+ reconnect: r.reconnect,
86
+ onIncompatible: r.onIncompatible,
87
+ indicatorEnabled: r.indicator !== false,
88
+ indicatorCount: r.indicator !== false && r.indicator.count,
89
+ },
90
+ sourceOverlay: {
91
+ enabled: so !== false,
92
+ key: (so === false ? undefined : so.key) ?? 's',
93
+ editor: typeof editor === 'string' ? editor : editor ? 'custom' : 'vscode',
94
+ throttleMs: (so === false ? undefined : so.throttleMs) ?? 50,
95
+ },
96
+ timeline: r.timeline,
97
+ };
98
+ };
10
99
  /**
11
100
  * Resolves a runtime module to an absolute fs path (served via `/@fs/`), so
12
101
  * the plugin works from any served root. Falls back from the built `.js` to
@@ -19,42 +108,400 @@ const resolveRuntimeModule = (name) => {
19
108
  return fileURLToPath(url).replace(/\\/g, '/');
20
109
  }
21
110
  }
22
- throw new Error(`[lit-hmr] runtime module not found: ${name}`);
111
+ throw new Error(`[lit-plugin] runtime module not found: ${name}`);
23
112
  };
24
113
  const JS_FILE_RE = /\.[cm]?[jt]sx?$/;
114
+ const OPEN_IN_EDITOR_PATH = '/__lit-open-in-editor';
115
+ /** Reject requests whose `Origin` is a different host than the dev server, so
116
+ * a page the developer happens to visit can't drive these local-only
117
+ * endpoints. Same-origin requests (no `Origin`, or matching `Host`) pass. */
118
+ const isSameOrigin = (headers) => {
119
+ const { origin, host } = headers;
120
+ if (origin === undefined || origin === 'null')
121
+ return true;
122
+ try {
123
+ return new URL(origin).host === host;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ };
129
+ const createOpenInEditorMiddleware = (root) => {
130
+ return (req, res, next) => {
131
+ if (req.url === undefined) {
132
+ next();
133
+ return;
134
+ }
135
+ if (!isSameOrigin(req.headers ?? {})) {
136
+ res.statusCode = 403;
137
+ res.end('forbidden');
138
+ return;
139
+ }
140
+ const query = req.url.includes('?')
141
+ ? req.url.slice(req.url.indexOf('?') + 1)
142
+ : '';
143
+ const params = new URLSearchParams(query);
144
+ const file = params.get('file');
145
+ if (file === null) {
146
+ res.statusCode = 400;
147
+ res.end('missing file parameter');
148
+ return;
149
+ }
150
+ // Confine the open to the project root: resolve the requested path and
151
+ // reject anything that escapes `root` (path traversal, absolute paths to
152
+ // arbitrary files). `launch-editor` spawns the user's editor on this path,
153
+ // so an unvalidated `file` is a local-file-open / arg-injection vector.
154
+ const resolved = resolvePath(root, file);
155
+ if (resolved !== root && !resolved.startsWith(root + sep)) {
156
+ res.statusCode = 403;
157
+ res.end('file outside project root');
158
+ return;
159
+ }
160
+ if (!existsSync(resolved)) {
161
+ res.statusCode = 404;
162
+ res.end('file not found');
163
+ return;
164
+ }
165
+ // Coerce to integers so a crafted `line`/`column` can't smuggle extra
166
+ // shell-visible content through the `file:line:column` ref.
167
+ const line = String(Math.max(1, Number.parseInt(params.get('line') ?? '1', 10) || 1));
168
+ const column = String(Math.max(1, Number.parseInt(params.get('column') ?? '1', 10) || 1));
169
+ const fileRef = `${resolved}:${line}:${column}`;
170
+ import('launch-editor')
171
+ .then((mod) => {
172
+ const launch = (mod.default ?? mod);
173
+ launch(fileRef, (_fileName, errorMessage) => {
174
+ if (errorMessage !== null) {
175
+ res.statusCode = 500;
176
+ res.end(errorMessage);
177
+ return;
178
+ }
179
+ res.end('ok');
180
+ });
181
+ })
182
+ .catch(next);
183
+ };
184
+ };
25
185
  /**
26
- * Dev-only Vite plugin providing true HMR for Lit: template-strings
27
- * interning plus in-place custom element class patching.
186
+ * `import href from './x.css?hmr-url'` — `?url` semantics with working HMR.
187
+ * The string is a real stylesheet URL (dev-served CSS file; hashed `.css`
188
+ * asset on build) for shadow-root `<link>` hrefs and `@import url()`s. In
189
+ * dev, each HMR re-execution yields a freshly cache-busted href, so the
190
+ * browser refetches the changed stylesheet; in build the `?url` asset URL
191
+ * passes through unchanged.
28
192
  */
29
- export const litHmr = (options = {}) => {
30
- const runtimeOptions = {
31
- reconnect: options.reconnect ?? false,
32
- onIncompatible: options.onIncompatible ?? 'reload',
33
- };
193
+ const CSS_URL_QUERY_RE = /^([^?]+\.css)\?(?:[^&]*&)*hmr-url(?:&.*)?$/;
194
+ const CSS_URL_VIRTUAL_PREFIX = '\0lit-plugin:hmr-url:';
195
+ /**
196
+ * `import sheet from './x.css?css-sheet'` — a constructed, shareable
197
+ * `CSSStyleSheet` backed by the `.css` asset that hot-swaps in place. Adopt it
198
+ * from any number of components (`static styles = [sheet]`); a source edit
199
+ * re-fetches and `replaceSync()`s it, updating every shadow root that adopted
200
+ * it without re-rendering a component or reloading the page.
201
+ *
202
+ * This is the `urlSheet()` helper plus its irreducible HMR wiring, lifted into
203
+ * a plugin-generated module: the `import.meta.hot.accept` lives here (where
204
+ * Vite's static analysis sees the literal specifier), so callers write a bare
205
+ * import and never touch `import.meta.hot` themselves.
206
+ */
207
+ const CSS_SHEET_QUERY_RE = /^([^?]+\.css)\?(?:[^&]*&)*css-sheet(?:&.*)?$/;
208
+ const CSS_SHEET_VIRTUAL_PREFIX = '\0lit-plugin:css-sheet:';
209
+ // The virtual id must not end in `.css`, or Vite's CSS plugins (which match
210
+ // the id's extension regardless of `\0`) would compile the wrapper as CSS.
211
+ const CSS_VIRTUAL_SUFFIX = '.js';
212
+ /**
213
+ * Import-query support, served in dev and build alike (source code using
214
+ * `?hmr-url`/`?css-sheet` must keep working under `vite build`, where the HMR
215
+ * plugin doesn't apply). Exported for the baseline e2e run, which needs the
216
+ * queries working without the HMR plugin.
217
+ */
218
+ export const litCssQueries = () => ({
219
+ name: 'lit-css-query',
220
+ // Vite's core resolver claims `./x.css?hmr-url` for the CSS pipeline
221
+ // before normal plugins get a look, so resolve ahead of it.
222
+ enforce: 'pre',
223
+ async resolveId(id, importer) {
224
+ const prefix = CSS_URL_QUERY_RE.test(id)
225
+ ? CSS_URL_VIRTUAL_PREFIX
226
+ : CSS_SHEET_QUERY_RE.test(id)
227
+ ? CSS_SHEET_VIRTUAL_PREFIX
228
+ : null;
229
+ if (prefix === null) {
230
+ return null;
231
+ }
232
+ const file = id.slice(0, id.indexOf('?'));
233
+ const resolved = await this.resolve(file, importer);
234
+ if (resolved === null) {
235
+ return null;
236
+ }
237
+ return prefix + resolved.id + CSS_VIRTUAL_SUFFIX;
238
+ },
239
+ load(id) {
240
+ const helperPath = resolveRuntimeModule('css');
241
+ if (id.startsWith(CSS_URL_VIRTUAL_PREFIX)) {
242
+ const file = id.slice(CSS_URL_VIRTUAL_PREFIX.length, -CSS_VIRTUAL_SUFFIX.length);
243
+ return {
244
+ code: `import url from ${JSON.stringify(`${file}?url`)};\n` +
245
+ `import {devCacheBust} from ${JSON.stringify(helperPath)};\n` +
246
+ `export default devCacheBust(url);\n`,
247
+ moduleType: 'js',
248
+ };
249
+ }
250
+ if (id.startsWith(CSS_SHEET_VIRTUAL_PREFIX)) {
251
+ const file = id.slice(CSS_SHEET_VIRTUAL_PREFIX.length, -CSS_VIRTUAL_SUFFIX.length);
252
+ // The accept specifier must be byte-identical to the import above —
253
+ // Vite resolves accepted HMR deps by static analysis. Generating both
254
+ // here is exactly what frees the caller from writing it. The swap is
255
+ // self-accepted at this boundary, so it never propagates to adopters.
256
+ const urlSpecifier = JSON.stringify(`${file}?url`);
257
+ return {
258
+ code: `import url from ${urlSpecifier};\n` +
259
+ `import {urlSheet} from ${JSON.stringify(helperPath)};\n` +
260
+ `const {sheet, onHotUpdate} = urlSheet(url);\n` +
261
+ `export default sheet;\n` +
262
+ `if (import.meta.hot) {\n` +
263
+ ` import.meta.hot.accept(${urlSpecifier}, onHotUpdate);\n` +
264
+ `}\n`,
265
+ moduleType: 'js',
266
+ };
267
+ }
268
+ return null;
269
+ },
270
+ });
271
+ /**
272
+ * A `css` tagged template literal with no interpolations and no escape
273
+ * sequences — the only kind we can hand to a CSS parser as-is. Literals
274
+ * with `${…}` holes or backslashes simply don't match and stay untouched.
275
+ * The lookbehind keeps `unsafeCSS`/`myCss`-style tags from matching.
276
+ */
277
+ const CSS_LITERAL_RE = /(?<![\w$.])css`((?:[^`\\$]|\$(?!\{))*)`/g;
278
+ /**
279
+ * Runs Vite's configured Lightning CSS over `css` tagged template literals
280
+ * in user modules, which the CSS pipeline itself never sees (they're plain
281
+ * JS strings to it). Inert unless `css.transformer` is `'lightningcss'`;
282
+ * options come from `css.lightningcss`, so component styles get the same
283
+ * treatment (targets, drafts, …) as `.css` files. Applies in dev and build.
284
+ */
285
+ const litCssLiterals = () => {
286
+ let lightningcss = null;
287
+ let options;
288
+ let minify = false;
34
289
  return {
35
- name: 'lit-hmr',
290
+ name: 'lit-css-literals',
291
+ async configResolved(config) {
292
+ if (config.css.transformer !== 'lightningcss') {
293
+ return;
294
+ }
295
+ // The transformer setting guarantees the dependency: Vite itself
296
+ // can't process .css files without it.
297
+ lightningcss = await import('lightningcss');
298
+ // cssModules makes no sense for a literal; everything else carries
299
+ // over.
300
+ const { cssModules: _cssModules, ...rest } = config.css.lightningcss ?? {};
301
+ options = rest;
302
+ minify = config.command === 'build';
303
+ },
304
+ transform(code, id) {
305
+ if (lightningcss === null) {
306
+ return null;
307
+ }
308
+ if (id.startsWith('\0') || id.includes('/node_modules/')) {
309
+ return null;
310
+ }
311
+ const [file] = id.split('?', 2);
312
+ if (!JS_FILE_RE.test(file) || !code.includes('css`')) {
313
+ return null;
314
+ }
315
+ const ms = new MagicString(code);
316
+ let changed = false;
317
+ for (const m of code.matchAll(CSS_LITERAL_RE)) {
318
+ const literal = m[1];
319
+ if (literal.trim() === '') {
320
+ continue;
321
+ }
322
+ let out;
323
+ try {
324
+ const result = lightningcss.transform({
325
+ ...options,
326
+ filename: file,
327
+ code: Buffer.from(literal),
328
+ minify,
329
+ });
330
+ out = Buffer.from(result.code).toString();
331
+ }
332
+ catch (e) {
333
+ this.warn(`[lit-plugin] skipping css literal Lightning CSS couldn't parse: ${e.message}`);
334
+ continue;
335
+ }
336
+ // Re-escape for the template literal the output goes back into.
337
+ out = out.replace(/[\\`$]/g, '\\$&');
338
+ if (out !== literal) {
339
+ const start = m.index + 'css`'.length;
340
+ ms.overwrite(start, start + literal.length, out);
341
+ changed = true;
342
+ }
343
+ }
344
+ if (!changed) {
345
+ return null;
346
+ }
347
+ return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
348
+ },
349
+ };
350
+ };
351
+ /**
352
+ * Vite plugin set providing HMR, CSS helpers, and Lightning CSS processing
353
+ * for Lit projects.
354
+ */
355
+ export const litPlugin = (options = {}) => {
356
+ // Resolved from explicit options first, env vars second, defaults last. The
357
+ // `config` hook re-resolves with the loaded env once Vite hands us the mode;
358
+ // this initial pass covers code paths that run before (or without) it.
359
+ let resolved = resolveOptions(options, {});
360
+ let root = '';
361
+ const sourceOverlayPlugin = {
362
+ name: 'lit-source-overlay',
363
+ apply: 'serve',
364
+ // Run before Vite's esbuild TS transform so source-meta line numbers are
365
+ // measured against the author's raw source (and the `@customElement … class`
366
+ // forms are still intact), not against transpiled output.
367
+ enforce: 'pre',
368
+ configResolved(config) {
369
+ root = config.root;
370
+ },
371
+ resolveId(id) {
372
+ if (id === '@lit-labs/vite-plugin-lit/source-overlay.js') {
373
+ return resolveRuntimeModule('source-overlay');
374
+ }
375
+ return null;
376
+ },
377
+ configureServer(server) {
378
+ if (!resolved.sourceOverlay)
379
+ return;
380
+ server.middlewares.use(OPEN_IN_EDITOR_PATH, createOpenInEditorMiddleware(root));
381
+ },
382
+ transform(code, id, transformOptions) {
383
+ if (!resolved.sourceOverlay)
384
+ return null;
385
+ if (transformOptions?.ssr)
386
+ return null;
387
+ if (id.startsWith('\0') ||
388
+ id.includes('__x00__') ||
389
+ id.includes('lit-plugin:') ||
390
+ id.includes('/node_modules/')) {
391
+ return null;
392
+ }
393
+ const [file] = id.split('?', 2);
394
+ if (!JS_FILE_RE.test(file) && !id.includes('?html-proxy')) {
395
+ return null;
396
+ }
397
+ if (!code.includes('customElement') && !code.includes('customElements')) {
398
+ return null;
399
+ }
400
+ const ms = new MagicString(code);
401
+ const relativeFile = file.startsWith(root + '/')
402
+ ? file.slice(root.length + 1)
403
+ : file;
404
+ if (!injectSourceMeta(code, relativeFile, ms)) {
405
+ return null;
406
+ }
407
+ return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
408
+ },
409
+ transformIndexHtml() {
410
+ if (!resolved.sourceOverlay)
411
+ return;
412
+ const overlayUrl = `/@fs/${resolveRuntimeModule('source-overlay')}`;
413
+ const { exclude: _exclude, onSelect: _onSelect, ...overlayInit } = resolved.sourceOverlay;
414
+ const initConfig = {
415
+ ...overlayInit,
416
+ openInEditorPath: OPEN_IN_EDITOR_PATH,
417
+ };
418
+ return [
419
+ {
420
+ tag: 'script',
421
+ attrs: { type: 'module' },
422
+ children: `import {initSourceOverlay} from ${JSON.stringify(overlayUrl)};\ninitSourceOverlay(${JSON.stringify(initConfig)});\n`,
423
+ injectTo: 'body',
424
+ },
425
+ ];
426
+ },
427
+ };
428
+ const hmr = {
429
+ name: 'lit-plugin',
36
430
  apply: 'serve',
37
- // The injected runtime imports are invisible to the dep scanner. The
38
- // lit family stays prebundle-eligible on purpose: the wrapper modules'
39
- // bare imports then resolve to the same URL every other importer gets —
40
- // single lit instance, single template cache.
41
- config: () => ({
42
- optimizeDeps: { exclude: ['@lit-labs/vite-hmr'] },
43
- }),
431
+ config: (viteConfig, { mode }) => {
432
+ // Resolve options against the loaded env now that Vite hands us the mode.
433
+ // `loadEnv` reads `.env*` files from the env dir (root by default) and
434
+ // merges in matching `process.env` keys, filtered to the `LIT_PLUGIN`
435
+ // prefix. Explicit options still win (handled in resolveOptions).
436
+ const envDir = viteConfig.envDir
437
+ ? resolvePath(viteConfig.envDir)
438
+ : viteConfig.root
439
+ ? resolvePath(viteConfig.root)
440
+ : process.cwd();
441
+ resolved = resolveOptions(options, loadEnv(mode, envDir, ENV_PREFIX));
442
+ if (!resolved.hmrEnabled) {
443
+ return;
444
+ }
445
+ // The injected runtime imports are invisible to the dep scanner. The
446
+ // lit family stays prebundle-eligible on purpose: the wrapper modules'
447
+ // bare imports then resolve to the same URL every other importer gets —
448
+ // single lit instance, single template cache.
449
+ return { optimizeDeps: { exclude: ['@lit-labs/vite-plugin-lit'] } };
450
+ },
44
451
  resolveId(id) {
45
- if (id.startsWith(VIRTUAL_PREFIX)) {
452
+ // Public timeline API virtual module.
453
+ if (id === 'virtual:lit-plugin/timeline') {
454
+ return '\0virtual:lit-plugin/timeline';
455
+ }
456
+ // Resolve the browser CSS helpers and indicator runtime to the copy
457
+ // shipped next to this plugin, so they work even when the package
458
+ // isn't reachable through node resolution from the served root (and
459
+ // stay out of prebundling).
460
+ if (id === '@lit-labs/vite-plugin-lit/css.js') {
461
+ return resolveRuntimeModule('css');
462
+ }
463
+ if (id === '@lit-labs/vite-plugin-lit/indicator.js') {
464
+ return resolveRuntimeModule('indicator');
465
+ }
466
+ if (id === '@lit-labs/vite-plugin-lit/source-overlay.js') {
467
+ return resolveRuntimeModule('source-overlay');
468
+ }
469
+ if (resolved.hmrEnabled && id.startsWith(VIRTUAL_PREFIX)) {
46
470
  return id;
47
471
  }
48
472
  return null;
49
473
  },
50
474
  load(id) {
51
- if (!id.startsWith(VIRTUAL_PREFIX)) {
475
+ // Public timeline API — re-export the runtime module when timeline is
476
+ // enabled, otherwise a no-op stub so imports don't throw in prod builds.
477
+ if (id === '\0virtual:lit-plugin/timeline') {
478
+ if (!resolved.timeline) {
479
+ return {
480
+ code: 'export const addTimelineEvent = () => {};\n' +
481
+ 'export const addTimelineLayer = () => {};\n',
482
+ moduleType: 'js',
483
+ };
484
+ }
485
+ const apiPath = resolveRuntimeModule('timeline/public-api');
486
+ return {
487
+ code: `export * from ${JSON.stringify(apiPath)};\n`,
488
+ moduleType: 'js',
489
+ };
490
+ }
491
+ if (!resolved.hmrEnabled || !id.startsWith(VIRTUAL_PREFIX)) {
52
492
  return null;
53
493
  }
54
494
  if (id === INSTALL_ID) {
55
495
  const patchPath = resolveRuntimeModule('patch');
56
- return (`import {install} from ${JSON.stringify(patchPath)};\n` +
57
- `install(${JSON.stringify(runtimeOptions)});\n`);
496
+ const runtimeOptions = {
497
+ reconnect: resolved.reconnect,
498
+ onIncompatible: resolved.onIncompatible,
499
+ };
500
+ return {
501
+ code: `import {install} from ${JSON.stringify(patchPath)};\n` +
502
+ `install(${JSON.stringify(runtimeOptions)});\n`,
503
+ moduleType: 'js',
504
+ };
58
505
  }
59
506
  const spec = id.slice(VIRTUAL_PREFIX.length);
60
507
  const wrappedTags = WRAP_TABLE.get(spec);
@@ -73,9 +520,12 @@ export const litHmr = (options = {}) => {
73
520
  for (const { exportName, ns } of wrappedTags) {
74
521
  lines.push(`export const ${exportName} = wrapTag(__lit.${exportName}, ${JSON.stringify(ns)});`);
75
522
  }
76
- return lines.join('\n') + '\n';
523
+ return { code: lines.join('\n') + '\n', moduleType: 'js' };
77
524
  },
78
525
  async transform(code, id, transformOptions) {
526
+ if (!resolved.hmrEnabled) {
527
+ return null;
528
+ }
79
529
  if (transformOptions?.ssr) {
80
530
  return null;
81
531
  }
@@ -89,6 +539,57 @@ export const litHmr = (options = {}) => {
89
539
  }
90
540
  return transformLitModule(code);
91
541
  },
542
+ transformIndexHtml() {
543
+ const tags = [];
544
+ const indicator = resolved.indicator;
545
+ if (indicator) {
546
+ const indicatorUrl = `/@fs/` + resolveRuntimeModule('indicator');
547
+ tags.push({
548
+ tag: 'script',
549
+ attrs: { type: 'module', src: indicatorUrl },
550
+ injectTo: 'body',
551
+ }, {
552
+ tag: 'lit-devtools-hmr-indicator',
553
+ attrs: indicator.count ? { count: '' } : undefined,
554
+ children: '',
555
+ injectTo: 'body',
556
+ });
557
+ }
558
+ if (resolved.timeline) {
559
+ const installUrl = `/@fs/` + resolveRuntimeModule('timeline/install');
560
+ tags.push({
561
+ tag: 'script',
562
+ attrs: { type: 'module', src: installUrl },
563
+ injectTo: 'body',
564
+ });
565
+ // Components inspector runtime — answers the panel's tree/details
566
+ // requests. Paired with the timeline panel, which hosts its tab.
567
+ const inspectorUrl = `/@fs/` + resolveRuntimeModule('inspector/install');
568
+ tags.push({
569
+ tag: 'script',
570
+ attrs: { type: 'module', src: inspectorUrl },
571
+ injectTo: 'body',
572
+ });
573
+ }
574
+ return tags.length > 0 ? tags : undefined;
575
+ },
92
576
  };
577
+ // The source-overlay plugin is always present; its hooks no-op when the
578
+ // feature is disabled (which env may decide), so inclusion can't be gated
579
+ // on the synchronously-known options here.
580
+ const plugins = [
581
+ litCssQueries(),
582
+ litCssLiterals(),
583
+ sourceOverlayPlugin,
584
+ hmr,
585
+ ];
586
+ if (resolved.timeline) {
587
+ // Pass a getter, not a snapshot: `resolved` is re-resolved against the
588
+ // loaded env in the `config` hook, which runs after this plugin array is
589
+ // built. The settings endpoint reads it per-request, by which point env is
590
+ // applied.
591
+ plugins.push(litTimelinePlugin(() => toFeatureSettings(resolved)));
592
+ }
593
+ return plugins;
93
594
  };
94
595
  //# sourceMappingURL=plugin.js.map