@oddsquad/vite-plugin-lit 0.3.0 → 0.4.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 (70) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +10 -6
  3. package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js → browser-agent-rpc-BXhoSh1z-CdtPhBL-.js} +2 -2
  4. package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js.map → browser-agent-rpc-BXhoSh1z-CdtPhBL-.js.map} +1 -1
  5. package/dist/client/assets/flow-DK8vmxvN.js +745 -0
  6. package/dist/client/assets/flow-DK8vmxvN.js.map +1 -0
  7. package/dist/client/assets/{index-CFrVgNfS.js → index-DVe6M4HV.js} +976 -102
  8. package/dist/client/assets/index-DVe6M4HV.js.map +1 -0
  9. package/dist/client/index.html +1 -1
  10. package/lib/devframe/protocol.d.ts.map +1 -1
  11. package/lib/devframe/protocol.js +1 -0
  12. package/lib/devframe/protocol.js.map +1 -1
  13. package/lib/devframe/vite.d.ts.map +1 -1
  14. package/lib/devframe/vite.js +1 -0
  15. package/lib/devframe/vite.js.map +1 -1
  16. package/lib/options.d.ts +131 -0
  17. package/lib/options.d.ts.map +1 -0
  18. package/lib/options.js +128 -0
  19. package/lib/options.js.map +1 -0
  20. package/lib/plugin.d.ts +6 -148
  21. package/lib/plugin.d.ts.map +1 -1
  22. package/lib/plugin.js +14 -477
  23. package/lib/plugin.js.map +1 -1
  24. package/lib/plugins/css-literals.d.ts +15 -0
  25. package/lib/plugins/css-literals.d.ts.map +1 -0
  26. package/lib/plugins/css-literals.js +154 -0
  27. package/lib/plugins/css-literals.js.map +1 -0
  28. package/lib/plugins/css-queries.d.ts +20 -0
  29. package/lib/plugins/css-queries.d.ts.map +1 -0
  30. package/lib/plugins/css-queries.js +139 -0
  31. package/lib/plugins/css-queries.js.map +1 -0
  32. package/lib/plugins/open-in-editor.d.ts +16 -0
  33. package/lib/plugins/open-in-editor.d.ts.map +1 -0
  34. package/lib/plugins/open-in-editor.js +91 -0
  35. package/lib/plugins/open-in-editor.js.map +1 -0
  36. package/lib/plugins/shared.d.ts +13 -0
  37. package/lib/plugins/shared.d.ts.map +1 -0
  38. package/lib/plugins/shared.js +23 -0
  39. package/lib/plugins/shared.js.map +1 -0
  40. package/lib/plugins/timeline-virtual.d.ts +19 -0
  41. package/lib/plugins/timeline-virtual.d.ts.map +1 -0
  42. package/lib/plugins/timeline-virtual.js +53 -0
  43. package/lib/plugins/timeline-virtual.js.map +1 -0
  44. package/lib/runtime/css.d.ts.map +1 -1
  45. package/lib/runtime/css.js +9 -2
  46. package/lib/runtime/css.js.map +1 -1
  47. package/lib/runtime/inspector/install.js +61 -11
  48. package/lib/runtime/inspector/install.js.map +1 -1
  49. package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
  50. package/lib/runtime/source-overlay/overlay-element.js +19 -6
  51. package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
  52. package/lib/runtime/timeline/install.js +10 -5
  53. package/lib/runtime/timeline/install.js.map +1 -1
  54. package/lib/runtime/timeline/public-api.d.ts +2 -2
  55. package/lib/runtime/timeline/public-api.js +2 -2
  56. package/lib/runtime/timeline/public-api.js.map +1 -1
  57. package/lib/runtime/timeline/render.d.ts +15 -6
  58. package/lib/runtime/timeline/render.d.ts.map +1 -1
  59. package/lib/runtime/timeline/render.js +150 -11
  60. package/lib/runtime/timeline/render.js.map +1 -1
  61. package/package.json +4 -1
  62. package/types/timeline.d.ts +12 -1
  63. package/types/timeline.d.ts.map +1 -1
  64. package/types/timeline.js +2 -0
  65. package/types/timeline.js.map +1 -1
  66. package/dist/client/assets/index-CFrVgNfS.js.map +0 -1
  67. package/lib/timeline-plugin.d.ts +0 -72
  68. package/lib/timeline-plugin.d.ts.map +0 -1
  69. package/lib/timeline-plugin.js +0 -359
  70. package/lib/timeline-plugin.js.map +0 -1
package/lib/plugin.d.ts CHANGED
@@ -4,154 +4,12 @@
4
4
  * SPDX-License-Identifier: BSD-3-Clause
5
5
  */
6
6
  import { type Plugin } from 'vite';
7
- import { type TrustHeaders } from './http.js';
8
- import type { SourceOverlayOptions } from './types.js';
9
- /**
10
- * On-page HMR feedback: a small pulsing indicator in the corner of the host
11
- * page that briefly animates on each HMR update, for at-a-glance feedback
12
- * without watching the console.
13
- */
14
- export interface HmrIndicatorOptions {
15
- /** Inject the indicator element. Defaults to `true` (when HMR is enabled). */
16
- enabled?: boolean;
17
- /**
18
- * Show a cumulative update count next to the dot (idle opacity 0.5 instead
19
- * of fully transparent). Defaults to `false`.
20
- */
21
- count?: boolean;
22
- }
23
- /**
24
- * HMR for Lit component classes and its on-page feedback.
25
- */
26
- export interface HmrOptions {
27
- /**
28
- * Enable in-place HMR for Lit component classes. When `false`, the plugin
29
- * skips all HMR transforms and runtime injection. Defaults to `true`.
30
- */
31
- enabled?: boolean;
32
- /**
33
- * Cycle `disconnectedCallback()`/`connectedCallback()` on live instances
34
- * after a hot patch. Interning makes this mostly unnecessary, so it's
35
- * opt-in. Defaults to `false`.
36
- */
37
- reconnect?: boolean;
38
- /**
39
- * What to do when a component can't be hot-patched in place (e.g.
40
- * standard `accessor` decorators). Defaults to `'reload'`.
41
- */
42
- onIncompatible?: 'reload' | 'warn';
43
- /**
44
- * The on-page update indicator. `true` enables it without a count; an
45
- * object configures it. Forced off when HMR itself is disabled. Defaults
46
- * to enabled (without a count).
47
- */
48
- indicator?: boolean | HmrIndicatorOptions;
49
- }
50
- /**
51
- * What a `?css-sheet` import compiles to under `vite build`.
52
- *
53
- * - `'url'`: fetch-backed sheet over the emitted `.css` asset.
54
- * - `'inline'`: css text embedded in the JS chunk, processed by the css
55
- * pipeline (`?inline`).
56
- * - `'inline-raw'`: css text embedded verbatim, skipping the css pipeline
57
- * (`?raw`).
58
- * - `'auto'`: `'inline'` when `build.lib` is set, `'url'` otherwise.
59
- */
60
- export type CssSheetBuild = 'auto' | 'url' | 'inline' | 'inline-raw';
61
- /**
62
- * Options for the Lit Vite plugin.
63
- *
64
- * Every option also resolves from environment variables (and `.env` files)
65
- * with the `LIT_PLUGIN` prefix — e.g. `LIT_PLUGIN_HMR_INDICATOR=true`. Options
66
- * passed here take precedence over env vars, which take precedence over the
67
- * built-in defaults.
68
- */
69
- export interface LitPluginOptions {
70
- /**
71
- * HMR for Lit components and its on-page feedback. `true`/`false` toggles
72
- * the whole feature (patching and indicator); an object configures it.
73
- * Defaults to enabled.
74
- */
75
- hmr?: boolean | HmrOptions;
76
- /**
77
- * Dev-only click-to-open-in-IDE inspector for Lit custom elements.
78
- * Toggle with Ctrl+Shift+S (configurable via `key`). Defaults to `false`.
79
- */
80
- sourceOverlay?: boolean | SourceOverlayOptions;
81
- /**
82
- * Vite DevTools Timeline panel — a Vue DevTools–style layered event stream
83
- * for Lit lifecycle, render, mouse, and keyboard events.
84
- *
85
- * Requires `@vitejs/devtools` in the Vite config and the `@vitejs/devtools`
86
- * Vite plugin (`DevTools()`) to be active. Defaults to `false` while
87
- * experimental.
88
- *
89
- * `true` enables all built-in layers with defaults.
90
- */
91
- timeline?: boolean;
92
- /**
93
- * What a `?css-sheet` import compiles to under `vite build`.
94
- * - `'url'`: fetch-backed sheet over the emitted `.css` asset
95
- * - `'inline'`: css text embedded in the JS chunk, processed by the css
96
- * pipeline (`?inline`)
97
- * - `'inline-raw'`: css text embedded verbatim, skipping the css pipeline
98
- * (`?raw`)
99
- * - `'auto'`: `'inline'` when `build.lib` is set, `'url'` otherwise
100
- *
101
- * Dev is always the fetch-backed HMR form regardless of this setting.
102
- * Defaults to `'auto'`.
103
- */
104
- cssSheetBuild?: CssSheetBuild;
105
- }
106
- /** Plugin options after merging explicit options, env vars, and defaults. */
107
- export interface ResolvedOptions {
108
- hmrEnabled: boolean;
109
- reconnect: boolean;
110
- onIncompatible: 'reload' | 'warn';
111
- indicator: false | {
112
- count: boolean;
113
- };
114
- sourceOverlay: false | SourceOverlayOptions;
115
- timeline: boolean;
116
- cssSheetBuild: CssSheetBuild;
117
- }
118
- /**
119
- * Merge explicit options over env vars over defaults (in that precedence) into
120
- * the flat shape the plugin hooks consume.
121
- */
122
- export declare const resolveOptions: (options: LitPluginOptions, env: Record<string, string>) => ResolvedOptions;
123
- export declare const createOpenInEditorMiddleware: (allowedRoots: readonly string[]) => (req: {
124
- url?: string;
125
- method?: string;
126
- headers?: TrustHeaders;
127
- }, res: {
128
- statusCode: number;
129
- end: (msg: string) => void;
130
- }, next: (err?: unknown) => void) => void;
131
- /**
132
- * Import-query support, served in dev and build alike (source code using
133
- * `?hmr-url`/`?css-sheet` must keep working under `vite build`, where the HMR
134
- * plugin doesn't apply). Exported for the baseline e2e run, which needs the
135
- * queries working without the HMR plugin.
136
- *
137
- * `getCssSheetBuild` is a getter, not a value: `litPlugin()` re-resolves its
138
- * options against the loaded env in a `config` hook, which runs after the
139
- * plugin array is built. Standalone (no getter) it behaves like the `'auto'`
140
- * default.
141
- */
142
- export declare const litCssQueries: (getCssSheetBuild?: () => CssSheetBuild) => Plugin;
143
- /**
144
- * The public timeline API virtual module, served in dev and build alike:
145
- * app code importing it must keep building under `vite build`, where the HMR
146
- * plugin doesn't apply. While these hooks lived on that serve-only plugin,
147
- * nothing claimed the specifier during a build and the bundler failed to
148
- * resolve it.
149
- *
150
- * `getTimeline` is a getter, not a value: `litPlugin()` re-resolves its
151
- * options against the loaded env in a `config` hook, which runs after the
152
- * plugin array is built.
153
- */
154
- export declare const litTimelineVirtual: (getTimeline?: () => boolean) => Plugin;
7
+ import { type LitPluginOptions } from './options.js';
8
+ export { createOpenInEditorMiddleware } from './plugins/open-in-editor.js';
9
+ export { litCssQueries } from './plugins/css-queries.js';
10
+ export { litTimelineVirtual } from './plugins/timeline-virtual.js';
11
+ export type { HmrIndicatorOptions, HmrOptions, CssSheetBuild, LitPluginOptions, ResolvedOptions, } from './options.js';
12
+ export { resolveOptions } from './options.js';
155
13
  /**
156
14
  * Vite plugin set providing HMR, CSS helpers, and Lightning CSS processing
157
15
  * for Lit projects.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/lib/plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAA2B,KAAK,MAAM,EAAC,MAAM,MAAM,CAAC;AAE3D,OAAO,EAAmB,KAAK,YAAY,EAAC,MAAM,WAAW,CAAC;AAG9D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,YAAY,CAAC;AAMrD;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAEnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAC;CAC3C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AASrE;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IAE/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,QAAQ,GAAG,MAAM,CAAC;IAClC,SAAS,EAAE,KAAK,GAAG;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,CAAC;IACpC,aAAa,EAAE,KAAK,GAAG,oBAAoB,CAAC;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAuCD;;;GAGG;AACH,eAAO,MAAM,cAAc,YAChB,gBAAgB,OACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC1B,eA4DF,CAAC;AAiDF,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,SAuFhC,CAAC;AAoCF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,sBACN,MAAM,aAAa,KACpC,MAwFF,CAAC;AAQF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,iBAChB,MAAM,OAAO,KACzB,MA6BF,CAAC;AAuFF;;;GAGG;AACH,eAAO,MAAM,SAAS,aAAa,gBAAgB,KAAQ,MAAM,EA+QhE,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/lib/plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAU,KAAK,MAAM,EAAC,MAAM,MAAM,CAAC;AAO1C,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAC,4BAA4B,EAAC,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAC,kBAAkB,EAAC,MAAM,+BAA+B,CAAC;AACjE,YAAY,EACV,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,cAAc,EAAC,MAAM,cAAc,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,SAAS,aAAa,gBAAgB,KAAQ,MAAM,EA+QhE,CAAC"}
package/lib/plugin.js CHANGED
@@ -3,490 +3,27 @@
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';
6
+ import {resolve as resolvePath} from 'node:path';
9
7
  import {loadEnv} from 'vite';
10
8
  import MagicString from 'magic-string';
11
- import {isTrustedRequest} from './http.js';
12
9
  import {injectSourceMeta} from './source-meta.js';
13
10
  import {INSTALL_ID, VIRTUAL_PREFIX, transformLitModule} from './transform.js';
14
11
  import {WRAP_TABLE} from './wrap-table.js';
15
12
  import {createLitDevframePlugin} from './devframe/vite.js';
16
13
  import {PACKAGE_VERSION} from './devframe/paths.js';
17
- const CSS_SHEET_BUILD_MODES = ['auto', 'url', 'inline', 'inline-raw'];
18
- /** Env var prefix consumed at config time. */
19
- const ENV_PREFIX = 'LIT_PLUGIN';
20
- /** Parse a boolean-ish env string; `undefined` when unset/unrecognized. */
21
- const envBool = (v) =>
22
- v === 'true' || v === '1'
23
- ? true
24
- : v === 'false' || v === '0'
25
- ? false
26
- : undefined;
27
- /** Parse a numeric env string; `undefined` when unset/non-numeric. */
28
- const envNum = (v) => {
29
- if (v === undefined || v === '') return undefined;
30
- const n = Number(v);
31
- return Number.isNaN(n) ? undefined : n;
32
- };
33
- /**
34
- * Parse a string against a closed set of values; `undefined` (with a warning)
35
- * when set to something outside it, so a typo falls back to the default
36
- * instead of silently disabling a feature.
37
- */
38
- const envEnum = (v, allowed, source) => {
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;
46
- };
47
- /**
48
- * Merge explicit options over env vars over defaults (in that precedence) into
49
- * the flat shape the plugin hooks consume.
50
- */
51
- export const resolveOptions = (options, env) => {
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
- };
106
- };
107
- /**
108
- * Flattens resolved options into the JSON-serializable shape the panel's
109
- * Settings tab consumes (callback options like `exclude`/`onSelect` dropped;
110
- * a custom editor object reported as `"custom"`). Default key/editor/throttle
111
- * are applied here so the panel shows the effective values the runtime uses.
112
- */
113
- const toFeatureSettings = (r) => {
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
- };
133
- };
134
- /**
135
- * Resolves a runtime module to an absolute fs path (served via `/@fs/`), so
136
- * the plugin works from any served root. Falls back from the built `.js` to
137
- * the `.ts` source when running un-built (e.g. under vitest).
138
- */
139
- const resolveRuntimeModule = (name) => {
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, '/');
144
- }
145
- }
146
- throw new Error(`[lit-plugin] runtime module not found: ${name}`);
147
- };
148
- const JS_FILE_RE = /\.[cm]?[jt]sx?$/;
149
- const OPEN_IN_EDITOR_PATH = '/__lit-open-in-editor';
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;
158
- }
159
- if (req.method !== undefined && req.method !== 'GET') {
160
- res.statusCode = 405;
161
- res.end('method not allowed');
162
- return;
163
- }
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
- };
230
- };
231
- /**
232
- * `import href from './x.css?hmr-url'` — `?url` semantics with working HMR.
233
- * The string is a real stylesheet URL (dev-served CSS file; hashed `.css`
234
- * asset on build) for shadow-root `<link>` hrefs and `@import url()`s. In
235
- * dev, each HMR re-execution yields a freshly cache-busted href, so the
236
- * browser refetches the changed stylesheet; in build the `?url` asset URL
237
- * passes through unchanged.
238
- */
239
- const CSS_URL_QUERY_RE = /^([^?]+\.css)\?(?:[^&]*&)*hmr-url(?:&.*)?$/;
240
- const CSS_URL_VIRTUAL_PREFIX = '\0lit-plugin:hmr-url:';
241
- /**
242
- * `import sheet from './x.css?css-sheet'` — a constructed, shareable
243
- * `CSSStyleSheet` backed by the `.css` asset that hot-swaps in place. Adopt it
244
- * from any number of components (`static styles = [sheet]`); a source edit
245
- * re-fetches and `replaceSync()`s it, updating every shadow root that adopted
246
- * it without re-rendering a component or reloading the page.
247
- *
248
- * This is the `urlSheet()` helper plus its irreducible HMR wiring, lifted into
249
- * a plugin-generated module: the `import.meta.hot.accept` lives here (where
250
- * Vite's static analysis sees the literal specifier), so callers write a bare
251
- * import and never touch `import.meta.hot` themselves.
252
- *
253
- * Under `vite build` the generated module can instead inline the css text (see
254
- * `cssSheetBuild`) — a library's consumers bundle its JS only, so a
255
- * fetch-backed sheet would 404 on the `.css` asset that never reached their
256
- * build.
257
- */
258
- const CSS_SHEET_QUERY_RE = /^([^?]+\.css)\?(?:[^&]*&)*css-sheet(?:&.*)?$/;
259
- const CSS_SHEET_VIRTUAL_PREFIX = '\0lit-plugin:css-sheet:';
260
- // The virtual id must not end in `.css`, or Vite's CSS plugins (which match
261
- // the id's extension regardless of `\0`) would compile the wrapper as CSS.
262
- const CSS_VIRTUAL_SUFFIX = '.js';
263
- /**
264
- * Import-query support, served in dev and build alike (source code using
265
- * `?hmr-url`/`?css-sheet` must keep working under `vite build`, where the HMR
266
- * plugin doesn't apply). Exported for the baseline e2e run, which needs the
267
- * queries working without the HMR plugin.
268
- *
269
- * `getCssSheetBuild` is a getter, not a value: `litPlugin()` re-resolves its
270
- * options against the loaded env in a `config` hook, which runs after the
271
- * plugin array is built. Standalone (no getter) it behaves like the `'auto'`
272
- * default.
273
- */
274
- export const litCssQueries = (getCssSheetBuild = () => 'auto') => {
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
- };
408
- };
409
- /**
410
- * A `css` tagged template literal with no interpolations and no escape
411
- * sequences — the only kind we can hand to a CSS parser as-is. Literals
412
- * with `${…}` holes or backslashes simply don't match and stay untouched.
413
- * The lookbehind keeps `unsafeCSS`/`myCss`-style tags from matching.
414
- */
415
- const CSS_LITERAL_RE = /(?<![\w$.])css`((?:[^`\\$]|\$(?!\{))*)`/g;
416
- /**
417
- * Runs Vite's configured Lightning CSS over `css` tagged template literals
418
- * in user modules, which the CSS pipeline itself never sees (they're plain
419
- * JS strings to it). Inert unless `css.transformer` is `'lightningcss'`;
420
- * options come from `css.lightningcss`, so component styles get the same
421
- * treatment (targets, drafts, …) as `.css` files. Applies in dev and build.
422
- */
423
- const litCssLiterals = () => {
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
- };
489
- };
14
+ import {ENV_PREFIX, resolveOptions, toFeatureSettings} from './options.js';
15
+ import {
16
+ OPEN_IN_EDITOR_PATH,
17
+ createOpenInEditorMiddleware,
18
+ } from './plugins/open-in-editor.js';
19
+ import {litCssQueries} from './plugins/css-queries.js';
20
+ import {litTimelineVirtual} from './plugins/timeline-virtual.js';
21
+ import {litCssLiterals} from './plugins/css-literals.js';
22
+ import {resolveRuntimeModule, JS_FILE_RE} from './plugins/shared.js';
23
+ export {createOpenInEditorMiddleware} from './plugins/open-in-editor.js';
24
+ export {litCssQueries} from './plugins/css-queries.js';
25
+ export {litTimelineVirtual} from './plugins/timeline-virtual.js';
26
+ export {resolveOptions} from './options.js';
490
27
  /**
491
28
  * Vite plugin set providing HMR, CSS helpers, and Lightning CSS processing
492
29
  * for Lit projects.