@megabudino/stack-utils 1.5.0 → 2.0.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.
@@ -1,39 +1,110 @@
1
- /** Declarative DOM transformations applied to proxied HTML documents. */
2
- export interface DomAction {
3
- /** CSS selector used to match elements in the proxied HTML document. */
4
- selector: string;
5
- /** Set or replace attributes on every matched element. */
6
- setAttributes?: Record<string, string>;
7
- /** Remove attributes from every matched element. */
8
- removeAttributes?: string[];
9
- /** Add CSS classes to every matched element. */
10
- addClass?: string[];
11
- /** Remove CSS classes from every matched element. */
12
- removeClass?: string[];
13
- /** Replace the text content of every matched element. */
14
- setText?: string;
15
- /** Replace the inner HTML of every matched element. */
16
- setHtml?: string;
17
- /**
18
- * Insert HTML inside every matched element before its existing children.
19
- *
20
- * When combined with `setText` or `setHtml`, the content replacement runs
21
- * first and `prependHtml` is inserted into the resulting element contents.
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ import type { Cheerio, CheerioAPI } from 'cheerio';
3
+ /**
4
+ * Activation mode for a Svelte island.
5
+ *
6
+ * - `serverOnly`: render the component server-side and freeze it as HTML.
7
+ * No client runtime is involved; props can be anything.
8
+ * - `mount`: render server-side for first paint, then mount the component on
9
+ * the client by replacing the SSR markup. Props must be JSON-serializable.
10
+ * - `hydrate`: render server-side and hydrate the existing markup in place
11
+ * on the client. Requires that the server output and client component are
12
+ * the exact same compiled Svelte 5 component and that props match.
13
+ */
14
+ export type IslandMode = 'serverOnly' | 'mount' | 'hydrate';
15
+ /**
16
+ * A registered Svelte component. Typed permissively so consumers can pass
17
+ * either a Svelte 5 function component or a legacy class component without
18
+ * coupling proxy types to Svelte type internals.
19
+ *
20
+ * Use `import Foo from './Foo.svelte'` and pass `Foo` directly.
21
+ */
22
+ export type IslandComponent = any;
23
+ /** Reference to a registered island for a `DomAction`. */
24
+ export interface IslandSpec {
25
+ /** Registry key. Must exist in `ProxyConfig.islands.components`. */
26
+ name: string;
27
+ /**
28
+ * Props passed to the component. For `mount` and `hydrate` modes the props
29
+ * are JSON-serialized into the page so the client can rehydrate with the
30
+ * same input, so they must be JSON-safe.
31
+ */
32
+ props?: Record<string, unknown>;
33
+ /** Activation mode. Defaults to `'serverOnly'`. */
34
+ mode?: IslandMode;
35
+ }
36
+ /** Island registry and client bootstrap configuration. */
37
+ export interface ProxyIslandsConfig {
38
+ /**
39
+ * Registry of components keyed by name. Names referenced by `DomAction.island`
40
+ * must exist in this registry or `createProxyHandle` throws.
41
+ */
42
+ components: Record<string, IslandComponent>;
43
+ /**
44
+ * URL of a client bootstrap script. When set, the proxy injects
45
+ * `<script type="module" src="${clientEntry}"></script>` at the end of the
46
+ * `<body>` whenever at least one `mount` or `hydrate` island is rendered on
47
+ * the page. Leave undefined to wire the script tag manually with
48
+ * `domActions.appendHtml`.
22
49
  */
23
- prependHtml?: string;
50
+ clientEntry?: string;
24
51
  /**
25
- * Insert HTML inside every matched element after its existing children.
52
+ * Optional stylesheet URLs that should be injected into the document head
53
+ * whenever at least one client island (`mount` or `hydrate`) is rendered.
26
54
  *
27
- * When combined with `setText` or `setHtml`, the content replacement runs
28
- * first and `appendHtml` is inserted into the resulting element contents.
55
+ * This is mainly useful for advanced/manual setups. When using the
56
+ * `proxyIslands()` Vite plugin, these URLs are auto-generated from the
57
+ * emitted client chunk CSS and should not need to be set by hand.
29
58
  */
30
- appendHtml?: string;
59
+ clientStylesheets?: string[];
60
+ }
61
+ /**
62
+ * Cheerio wrapper around the elements passed to `TransformDomContext.renderIsland`.
63
+ *
64
+ * Loosely typed (`Cheerio<any>`) so consumers can pass whatever shape `$(selector)`
65
+ * returns without coupling to cheerio's element internals. Cheerio infers a
66
+ * narrower element type at the call site for any usage outside of this helper.
67
+ */
68
+ export type TransformDomTarget = Cheerio<any>;
69
+ /**
70
+ * Context object passed to a `transformDom` hook.
71
+ *
72
+ * Carries the SvelteKit request currently being proxied so the hook can make
73
+ * per-request decisions (e.g., based on URL, route id, cookies) and exposes a
74
+ * `renderIsland` helper that mirrors `domActions[i].island`: it renders a
75
+ * registered Svelte component into the matched elements, tags them so the
76
+ * client runtime can pick them up, and registers head fragments and the
77
+ * client bootstrap exactly once for the document.
78
+ */
79
+ export interface TransformDomContext {
80
+ /** The SvelteKit request event for the proxied request. */
81
+ event: RequestEvent;
82
+ /** Shortcut for `event.url`. */
83
+ url: URL;
31
84
  /**
32
- * Remove every matched element.
33
- * When true, removal takes precedence over all other fields in the same action.
85
+ * Render a registered Svelte island into every element in `target`.
86
+ *
87
+ * Throws when `islands.components` is not configured on `ProxyConfig`,
88
+ * when the island name is unknown, or when props cannot be serialized for
89
+ * `mount` / `hydrate` modes. Server-rendered head fragments are deduped by
90
+ * exact string match and the client bootstrap is injected at most once per
91
+ * document.
34
92
  */
35
- remove?: boolean;
93
+ renderIsland(target: TransformDomTarget, spec: IslandSpec): Promise<void>;
36
94
  }
95
+ /**
96
+ * Arbitrary asynchronous transformation applied to proxied HTML documents.
97
+ *
98
+ * Runs after the standard proxy URL rewrites and after the legacy `domActions`
99
+ * pass (so it sees the same DOM the actions produced), and before
100
+ * `textReplacements` (which remain the last low-level override).
101
+ *
102
+ * The hook mutates the document via the cheerio root `$`. Returning a value is
103
+ * not supported — mutate `$` in place. The same `$` instance is used by
104
+ * `domActions` and the island bootstrap finalization, so all changes compose
105
+ * into a single rendered document.
106
+ */
107
+ export type TransformDomHook = ($: CheerioAPI, ctx: TransformDomContext) => void | Promise<void>;
37
108
  type DeprecatedProxyOption<Message extends string> = {
38
109
  readonly __deprecatedProxyOption: Message;
39
110
  };
@@ -96,9 +167,26 @@ export interface ProxyConfig {
96
167
  */
97
168
  textReplacements?: Record<string, string>;
98
169
  /**
99
- * Declarative DOM transformations applied to HTML responses after the
100
- * standard proxy rewrites and before `textReplacements`.
170
+ * @deprecated Removed in v2. Express every DOM mutation directly inside
171
+ * `transformDom`, which receives the cheerio root and `ctx.renderIsland`.
172
+ * Passing this field is rejected at startup by `createProxyHandle`.
173
+ */
174
+ domActions?: DeprecatedProxyOption<'`domActions` was removed in v2. Express mutations with `transformDom` (cheerio root) and call `ctx.renderIsland(...)` for islands.'>;
175
+ /**
176
+ * Arbitrary asynchronous transformation applied to HTML responses.
177
+ *
178
+ * Receives the cheerio root `$` for the proxied document plus a context
179
+ * with the request `event`, the `url`, and a `renderIsland` helper that
180
+ * wires registered Svelte components and the client bootstrap.
181
+ *
182
+ * Runs after the standard URL rewrites and before `textReplacements`.
183
+ * Non-HTML responses are not affected.
184
+ */
185
+ transformDom?: TransformDomHook;
186
+ /**
187
+ * Svelte component registry and client bootstrap configuration for the
188
+ * islands feature. Required when `transformDom` calls `ctx.renderIsland(...)`.
101
189
  */
102
- domActions?: DomAction[];
190
+ islands?: ProxyIslandsConfig;
103
191
  }
104
192
  export {};
@@ -0,0 +1,57 @@
1
+ export interface ProxyIslandComponentImport {
2
+ key: string;
3
+ localName: string;
4
+ source: string;
5
+ importKind: 'default' | 'named' | 'namespace';
6
+ importedName?: string;
7
+ }
8
+ export interface ProxyIslandsDeclaration {
9
+ variableName: string;
10
+ objectLiteralStart: number;
11
+ objectLiteralEnd: number;
12
+ hasClientEntry: boolean;
13
+ hasClientStylesheets: boolean;
14
+ }
15
+ export interface ExtractedProxyIslandsMetadata {
16
+ declarations: ProxyIslandsDeclaration[];
17
+ components: ProxyIslandComponentImport[];
18
+ }
19
+ export declare function needsAutoWiredAssets(metadata: ExtractedProxyIslandsMetadata): boolean;
20
+ export declare function extractProxyIslandsMetadata(code: string, filePath: string): ExtractedProxyIslandsMetadata | null;
21
+ export declare function buildProxyIslandsClientEntryModule(metadata: ExtractedProxyIslandsMetadata, hooksFilePath: string, generatedFilePath: string): string;
22
+ export declare function injectAutoWiredIslandsConfig(code: string, metadata: ExtractedProxyIslandsMetadata, values: {
23
+ clientEntryExpression: string;
24
+ clientStylesheetsExpression: string;
25
+ }): string;
26
+ /**
27
+ * Build the public URL we inject into proxied HTML for a generated client
28
+ * bundle file or its associated CSS asset.
29
+ *
30
+ * The islands runtime sticks these URLs into the `<head>` (and end of `<body>`)
31
+ * of pages whose pathname has nothing to do with the bundle layout. Relative
32
+ * URLs like `./_app/...` only resolve correctly when the page itself sits at
33
+ * the site root, which makes the assets unreachable as soon as the proxy
34
+ * serves a nested URL. SvelteKit defaults `paths.relative` to true, so Vite's
35
+ * `base` resolves to `./` for the SvelteKit-rendered pages — fine for
36
+ * SvelteKit itself, useless for us. We therefore coerce to an absolute path
37
+ * starting with `/`, and only honour `base` when the consumer explicitly
38
+ * configured one (a base that starts with `/`).
39
+ */
40
+ export declare function toPublicAssetUrl(fileName: string, base: string | undefined): string;
41
+ /**
42
+ * In dev, `vite-plugin-svelte` serves a `<Component>.svelte?svelte&type=style&lang.css`
43
+ * module as a JS wrapper that imports `__vite__updateStyle` and calls it with
44
+ * the CSS literal:
45
+ *
46
+ * const __vite__css = "<escaped css>"
47
+ * __vite__updateStyle(__vite__id, __vite__css)
48
+ *
49
+ * To stop the islands runtime from triggering a flash, we need to ship a
50
+ * single real stylesheet up front (a `<link rel="stylesheet">` blocks render
51
+ * the way JS-injected `<style>` tags cannot). This pulls the raw CSS string
52
+ * out of that wrapper so the dev middleware can concatenate it across every
53
+ * island module.
54
+ */
55
+ export declare function extractViteCssFromJs(jsCode: string): string;
56
+ /** Match every module id we want to treat as a stylesheet for dev-time extraction. */
57
+ export declare function isCssLikeModuleId(id: string | null | undefined): boolean;
@@ -0,0 +1,288 @@
1
+ import path from 'node:path';
2
+ import * as ts from 'typescript';
3
+ export function needsAutoWiredAssets(metadata) {
4
+ return metadata.declarations.some((declaration) => !declaration.hasClientEntry || !declaration.hasClientStylesheets);
5
+ }
6
+ export function extractProxyIslandsMetadata(code, filePath) {
7
+ const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true, getScriptKind(filePath));
8
+ const importsByLocalName = collectImports(sourceFile);
9
+ const declarations = [];
10
+ const components = new Map();
11
+ function visit(node) {
12
+ if (ts.isVariableDeclaration(node) &&
13
+ ts.isIdentifier(node.name) &&
14
+ node.initializer &&
15
+ ts.isCallExpression(node.initializer) &&
16
+ isDefineProxyIslandsCall(node.initializer)) {
17
+ const objectLiteral = node.initializer.arguments[0];
18
+ if (!objectLiteral || !ts.isObjectLiteralExpression(objectLiteral)) {
19
+ throw new Error('[stack-utils proxy] `defineProxyIslands(...)` must receive a direct object literal.');
20
+ }
21
+ const componentsProperty = findObjectProperty(objectLiteral, 'components');
22
+ if (!componentsProperty || !ts.isObjectLiteralExpression(componentsProperty.initializer)) {
23
+ throw new Error('[stack-utils proxy] `defineProxyIslands(...)` requires `components: { ... }` as a direct object literal.');
24
+ }
25
+ declarations.push({
26
+ variableName: node.name.text,
27
+ objectLiteralStart: objectLiteral.getStart(sourceFile),
28
+ objectLiteralEnd: objectLiteral.getEnd(),
29
+ hasClientEntry: Boolean(findObjectProperty(objectLiteral, 'clientEntry')),
30
+ hasClientStylesheets: Boolean(findObjectProperty(objectLiteral, 'clientStylesheets'))
31
+ });
32
+ for (const property of componentsProperty.initializer.properties) {
33
+ if (ts.isSpreadAssignment(property)) {
34
+ throw new Error('[stack-utils proxy] `defineProxyIslands({ components })` does not support spread syntax. List components explicitly.');
35
+ }
36
+ if (ts.isShorthandPropertyAssignment(property)) {
37
+ const localName = property.name.text;
38
+ const binding = importsByLocalName.get(localName);
39
+ if (!binding) {
40
+ throw new Error(`[stack-utils proxy] component "${localName}" must be imported directly in hooks.server.ts to enable auto-wiring.`);
41
+ }
42
+ registerComponent(components, {
43
+ key: localName,
44
+ localName,
45
+ ...binding
46
+ });
47
+ continue;
48
+ }
49
+ if (ts.isPropertyAssignment(property)) {
50
+ const key = readPropertyName(property.name);
51
+ if (!key) {
52
+ throw new Error('[stack-utils proxy] component keys in `defineProxyIslands({ components })` must be plain identifiers or string literals.');
53
+ }
54
+ if (!ts.isIdentifier(property.initializer)) {
55
+ throw new Error(`[stack-utils proxy] component "${key}" must point directly to an imported identifier.`);
56
+ }
57
+ const localName = property.initializer.text;
58
+ const binding = importsByLocalName.get(localName);
59
+ if (!binding) {
60
+ throw new Error(`[stack-utils proxy] component "${key}" must reference an imported identifier to enable auto-wiring.`);
61
+ }
62
+ registerComponent(components, {
63
+ key,
64
+ localName,
65
+ ...binding
66
+ });
67
+ continue;
68
+ }
69
+ throw new Error('[stack-utils proxy] unsupported `components` entry. Use `Foo` or `Alias: Foo`.');
70
+ }
71
+ }
72
+ ts.forEachChild(node, visit);
73
+ }
74
+ visit(sourceFile);
75
+ if (declarations.length === 0) {
76
+ return null;
77
+ }
78
+ return {
79
+ declarations,
80
+ components: [...components.values()]
81
+ };
82
+ }
83
+ export function buildProxyIslandsClientEntryModule(metadata, hooksFilePath, generatedFilePath) {
84
+ const importLines = [
85
+ `import { mountIslands } from '@megabudino/stack-utils/proxy/client';`,
86
+ ...metadata.components.map((component) => buildImportLine(component, hooksFilePath, generatedFilePath))
87
+ ];
88
+ const componentEntries = metadata.components.map((component) => component.key === component.localName
89
+ ? component.localName
90
+ : `${formatObjectKey(component.key)}: ${component.localName}`);
91
+ return [
92
+ '// Generated by @megabudino/stack-utils/proxy/vite.',
93
+ '// Do not edit by hand.',
94
+ ...importLines,
95
+ '',
96
+ 'mountIslands({',
97
+ '\tcomponents: {',
98
+ ...componentEntries.map((entry) => `\t\t${entry},`),
99
+ '\t}',
100
+ '});',
101
+ ''
102
+ ].join('\n');
103
+ }
104
+ export function injectAutoWiredIslandsConfig(code, metadata, values) {
105
+ let transformed = code;
106
+ for (const declaration of [...metadata.declarations].sort((left, right) => right.objectLiteralStart - left.objectLiteralStart)) {
107
+ const objectLiteralSource = transformed.slice(declaration.objectLiteralStart, declaration.objectLiteralEnd);
108
+ const nextLiteral = appendMissingObjectProperties(objectLiteralSource, [
109
+ ...(!declaration.hasClientEntry
110
+ ? [`clientEntry: ${values.clientEntryExpression}`]
111
+ : []),
112
+ ...(!declaration.hasClientStylesheets
113
+ ? [`clientStylesheets: ${values.clientStylesheetsExpression}`]
114
+ : [])
115
+ ]);
116
+ transformed =
117
+ transformed.slice(0, declaration.objectLiteralStart) +
118
+ nextLiteral +
119
+ transformed.slice(declaration.objectLiteralEnd);
120
+ }
121
+ return transformed;
122
+ }
123
+ function collectImports(sourceFile) {
124
+ const importsByLocalName = new Map();
125
+ for (const statement of sourceFile.statements) {
126
+ if (!ts.isImportDeclaration(statement) || !statement.importClause) {
127
+ continue;
128
+ }
129
+ const source = statement.moduleSpecifier;
130
+ if (!ts.isStringLiteral(source)) {
131
+ continue;
132
+ }
133
+ const sourceText = source.text;
134
+ const clause = statement.importClause;
135
+ if (clause.name) {
136
+ importsByLocalName.set(clause.name.text, {
137
+ source: sourceText,
138
+ importKind: 'default'
139
+ });
140
+ }
141
+ const bindings = clause.namedBindings;
142
+ if (!bindings) {
143
+ continue;
144
+ }
145
+ if (ts.isNamespaceImport(bindings)) {
146
+ importsByLocalName.set(bindings.name.text, {
147
+ source: sourceText,
148
+ importKind: 'namespace'
149
+ });
150
+ continue;
151
+ }
152
+ for (const element of bindings.elements) {
153
+ importsByLocalName.set(element.name.text, {
154
+ source: sourceText,
155
+ importKind: 'named',
156
+ importedName: element.propertyName?.text ?? element.name.text
157
+ });
158
+ }
159
+ }
160
+ return importsByLocalName;
161
+ }
162
+ function isDefineProxyIslandsCall(node) {
163
+ return ts.isIdentifier(node.expression) && node.expression.text === 'defineProxyIslands';
164
+ }
165
+ function findObjectProperty(objectLiteral, name) {
166
+ return objectLiteral.properties.find((property) => ts.isPropertyAssignment(property) && readPropertyName(property.name) === name);
167
+ }
168
+ function readPropertyName(name) {
169
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
170
+ return name.text;
171
+ }
172
+ return null;
173
+ }
174
+ function registerComponent(components, component) {
175
+ const existing = components.get(component.key);
176
+ if (!existing) {
177
+ components.set(component.key, component);
178
+ return;
179
+ }
180
+ const sameBinding = existing.localName === component.localName &&
181
+ existing.source === component.source &&
182
+ existing.importKind === component.importKind &&
183
+ existing.importedName === component.importedName;
184
+ if (!sameBinding) {
185
+ throw new Error(`[stack-utils proxy] island registry key "${component.key}" is declared more than once with different imports.`);
186
+ }
187
+ }
188
+ function buildImportLine(component, hooksFilePath, generatedFilePath) {
189
+ const source = resolveComponentImportSource(component.source, hooksFilePath, generatedFilePath);
190
+ if (component.importKind === 'default') {
191
+ return `import ${component.localName} from ${JSON.stringify(source)};`;
192
+ }
193
+ if (component.importKind === 'namespace') {
194
+ return `import * as ${component.localName} from ${JSON.stringify(source)};`;
195
+ }
196
+ if (component.importedName && component.importedName !== component.localName) {
197
+ return `import { ${component.importedName} as ${component.localName} } from ${JSON.stringify(source)};`;
198
+ }
199
+ return `import { ${component.localName} } from ${JSON.stringify(source)};`;
200
+ }
201
+ function resolveComponentImportSource(source, hooksFilePath, generatedFilePath) {
202
+ if (!source.startsWith('.')) {
203
+ return source;
204
+ }
205
+ const resolvedSourcePath = path.resolve(path.dirname(hooksFilePath), source);
206
+ const relativeSourcePath = path.relative(path.dirname(generatedFilePath), resolvedSourcePath);
207
+ const normalized = relativeSourcePath.split(path.sep).join('/');
208
+ return normalized.startsWith('.') ? normalized : `./${normalized}`;
209
+ }
210
+ function appendMissingObjectProperties(objectLiteralSource, properties) {
211
+ if (properties.length === 0) {
212
+ return objectLiteralSource;
213
+ }
214
+ const inner = objectLiteralSource.slice(1, -1);
215
+ const trimmedInner = inner.trim();
216
+ if (trimmedInner.length === 0) {
217
+ return `{\n\t${properties.join(',\n\t')}\n}`;
218
+ }
219
+ const separator = /,\s*$/.test(inner) ? '' : ',';
220
+ return `{${inner}${separator}\n\t${properties.join(',\n\t')}\n}`;
221
+ }
222
+ function formatObjectKey(key) {
223
+ return /^[$A-Z_a-z][$\w]*$/.test(key) ? key : JSON.stringify(key);
224
+ }
225
+ function getScriptKind(filePath) {
226
+ if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
227
+ return ts.ScriptKind.JS;
228
+ }
229
+ return ts.ScriptKind.TS;
230
+ }
231
+ /**
232
+ * Build the public URL we inject into proxied HTML for a generated client
233
+ * bundle file or its associated CSS asset.
234
+ *
235
+ * The islands runtime sticks these URLs into the `<head>` (and end of `<body>`)
236
+ * of pages whose pathname has nothing to do with the bundle layout. Relative
237
+ * URLs like `./_app/...` only resolve correctly when the page itself sits at
238
+ * the site root, which makes the assets unreachable as soon as the proxy
239
+ * serves a nested URL. SvelteKit defaults `paths.relative` to true, so Vite's
240
+ * `base` resolves to `./` for the SvelteKit-rendered pages — fine for
241
+ * SvelteKit itself, useless for us. We therefore coerce to an absolute path
242
+ * starting with `/`, and only honour `base` when the consumer explicitly
243
+ * configured one (a base that starts with `/`).
244
+ */
245
+ export function toPublicAssetUrl(fileName, base) {
246
+ let prefix = '/';
247
+ if (base && base.startsWith('/')) {
248
+ prefix = base.endsWith('/') ? base : `${base}/`;
249
+ }
250
+ return `${prefix}${fileName}`.replace(/\/{2,}/g, '/');
251
+ }
252
+ /**
253
+ * In dev, `vite-plugin-svelte` serves a `<Component>.svelte?svelte&type=style&lang.css`
254
+ * module as a JS wrapper that imports `__vite__updateStyle` and calls it with
255
+ * the CSS literal:
256
+ *
257
+ * const __vite__css = "<escaped css>"
258
+ * __vite__updateStyle(__vite__id, __vite__css)
259
+ *
260
+ * To stop the islands runtime from triggering a flash, we need to ship a
261
+ * single real stylesheet up front (a `<link rel="stylesheet">` blocks render
262
+ * the way JS-injected `<style>` tags cannot). This pulls the raw CSS string
263
+ * out of that wrapper so the dev middleware can concatenate it across every
264
+ * island module.
265
+ */
266
+ export function extractViteCssFromJs(jsCode) {
267
+ const match = jsCode.match(/const\s+__vite__css\s*=\s*("(?:\\.|[^"\\])*")/);
268
+ if (!match)
269
+ return '';
270
+ try {
271
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
272
+ const value = new Function(`return ${match[1]};`)();
273
+ return typeof value === 'string' ? value : '';
274
+ }
275
+ catch {
276
+ return '';
277
+ }
278
+ }
279
+ /** Match every module id we want to treat as a stylesheet for dev-time extraction. */
280
+ export function isCssLikeModuleId(id) {
281
+ if (!id)
282
+ return false;
283
+ if (id.includes('?svelte&type=style'))
284
+ return true;
285
+ if (/\.(css|scss|sass|less|styl|stylus|postcss|pcss)(\?|$)/i.test(id))
286
+ return true;
287
+ return false;
288
+ }
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function proxyIslands(): Plugin;