Skip to content

Commit 61d578c

Browse files
committed
Update package version to 0.0.14, add test for global components referencing each other, and refactor inline Svelte plugin for improved global component handling and dependency management.
1 parent 23b112f commit 61d578c

File tree

3 files changed

+113
-57
lines changed

3 files changed

+113
-57
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hvniel/vite-plugin-svelte-inline-component",
3-
"version": "0.0.13",
3+
"version": "0.0.14",
44
"license": "MIT",
55
"author": "Haniel Ubogu <https://github.com/HanielU>",
66
"repository": {

src/lib/plugin/index.ts

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export default function inlineSveltePlugin({
7777
const VIRT = "virtual:inline-svelte/";
7878
const RSLV = "\0" + VIRT;
7979

80-
/** virtualId → full markup (with injected imports) */
8180
const cache = new Map<string, string>();
8281

8382
return {
@@ -93,64 +92,37 @@ export default function inlineSveltePlugin({
9392
const imports = (fenceRE.exec(code)?.[1] ?? "").trim();
9493

9594
let importsToAdd = "";
96-
let globalImportsForTpl = "";
9795
let hoistedCode = "";
98-
const globalVarDefs = new Map<string, string>();
9996
const hashToLocal = new Map<string, string>();
10097
const globalComponentNames = new Set<string>();
98+
const globalVarDefs = new Map<string, string>();
99+
const depGraph = new Map<string, Set<string>>();
100+
let allGlobalNames: string[] = [];
101+
let globalImportsForTpl = "";
101102

102103
/* 1. Process globals */
103104
const globalsMatch = globalsRE.exec(code);
104105
if (globalsMatch) {
105106
edited = true;
106107
const globalsContent = globalsMatch[1] ?? "";
107108
ms.overwrite(globalsMatch.index, globalsMatch.index + globalsMatch[0].length, "");
108-
hoistedCode = globalsContent;
109-
110-
const replacements = [];
111-
let match;
112-
globalDefRE.lastIndex = 0;
113-
while ((match = globalDefRE.exec(globalsContent)) !== null) {
114-
const [fullMatch, compName, templateLiteralWithTicks] = match;
115-
globalComponentNames.add(compName);
116-
117-
const rawMarkup = templateLiteralWithTicks.slice(1, -1);
118-
const markup = applyImports(rawMarkup, imports);
119-
const hash = createHash("sha1").update(markup).digest("hex").slice(0, 8);
120-
121-
let local = hashToLocal.get(hash);
122-
if (!local) {
123-
local = `Inline_${hash}`;
124-
hashToLocal.set(hash, local);
125-
const virt = `${VIRT}${hash}.js`;
126-
if (!cache.has(virt)) cache.set(virt, markup);
127-
const ns = `__InlineNS_${hash}`;
128-
importsToAdd += `import * as ${ns} from '${virt}';\nconst ${local}=Object.assign(${ns}.default, ${ns});\n`;
129-
globalImportsForTpl += `import ${compName} from '${virt}';\n`;
130-
}
131109

132-
replacements.push({
133-
start: match.index,
134-
end: match.index + fullMatch.length,
135-
newText: `const ${compName} = ${local}`,
136-
});
137-
}
138-
139-
for (const rep of replacements.reverse()) {
140-
hoistedCode = hoistedCode.slice(0, rep.start) + rep.newText + hoistedCode.slice(rep.end);
141-
}
110+
const componentMatches = [...globalsContent.matchAll(globalDefRE)];
111+
componentMatches.forEach(match => globalComponentNames.add(match[1]));
142112

143113
const startOfDeclRegex = /^(?:const|let|var)\s+([a-zA-Z0-9_$]+)/gm;
144114
let declMatch;
145-
while ((declMatch = startOfDeclRegex.exec(hoistedCode)) !== null) {
115+
while ((declMatch = startOfDeclRegex.exec(globalsContent)) !== null) {
146116
const varName = declMatch[1];
117+
if (globalComponentNames.has(varName)) continue;
118+
147119
const startIndex = declMatch.index;
148120
let braceDepth = 0,
149121
bracketDepth = 0,
150122
parenDepth = 0,
151123
endIndex = -1;
152-
for (let i = startIndex; i < hoistedCode.length; i++) {
153-
const char = hoistedCode[i];
124+
for (let i = startIndex; i < globalsContent.length; i++) {
125+
const char = globalsContent[i];
154126
if (char === "{") braceDepth++;
155127
else if (char === "}") braceDepth--;
156128
else if (char === "[") bracketDepth++;
@@ -163,24 +135,91 @@ export default function inlineSveltePlugin({
163135
}
164136
}
165137
if (endIndex === -1) {
166-
const nextNewline = hoistedCode.indexOf("\n", startIndex);
167-
endIndex = nextNewline !== -1 ? nextNewline : hoistedCode.length;
138+
const nextNewline = globalsContent.indexOf("\n", startIndex);
139+
endIndex = nextNewline !== -1 ? nextNewline : globalsContent.length;
168140
}
169-
const definition = hoistedCode.substring(startIndex, endIndex).trim();
141+
const definition = globalsContent.substring(startIndex, endIndex).trim();
170142
if (definition) globalVarDefs.set(varName, definition);
171143
startOfDeclRegex.lastIndex = endIndex;
172144
}
173-
}
174145

175-
const depGraph = new Map<string, Set<string>>();
176-
const allGlobalNames = [...globalVarDefs.keys()];
177-
for (const [name, definition] of globalVarDefs.entries()) {
178-
const dependencies = new Set<string>();
179-
for (const depName of allGlobalNames) {
180-
if (name === depName) continue;
181-
if (new RegExp(`\\b${depName}\\b`).test(definition)) dependencies.add(depName);
146+
allGlobalNames = [...globalComponentNames, ...globalVarDefs.keys()];
147+
const nameToMarkup = new Map(componentMatches.map(m => [m[1], m[2].slice(1, -1)]));
148+
149+
for (const name of allGlobalNames) {
150+
const dependencies = new Set<string>();
151+
const content = globalComponentNames.has(name)
152+
? nameToMarkup.get(name)!
153+
: globalVarDefs.get(name)!;
154+
for (const depName of allGlobalNames) {
155+
if (name === depName) continue;
156+
if (new RegExp(`\\b${depName}\\b`).test(content)) dependencies.add(depName);
157+
}
158+
depGraph.set(name, dependencies);
159+
}
160+
161+
const sortedComponentNames = [...globalComponentNames].sort((a, b) => {
162+
const depsA = getTransitiveDependencies(new Set([a]), depGraph);
163+
if (depsA.has(b)) return 1;
164+
const depsB = getTransitiveDependencies(new Set([b]), depGraph);
165+
if (depsB.has(a)) return -1;
166+
return allGlobalNames.indexOf(a) - allGlobalNames.indexOf(b);
167+
});
168+
169+
const componentInfo = new Map<string, { local: string; virt: string }>();
170+
const replacements = [];
171+
172+
for (const compName of sortedComponentNames) {
173+
const match = componentMatches.find(m => m[1] === compName)!;
174+
const [, , templateLiteralWithTicks] = match;
175+
const rawMarkup = templateLiteralWithTicks.slice(1, -1);
176+
177+
const allDeps = getTransitiveDependencies(new Set([compName]), depGraph);
178+
allDeps.delete(compName);
179+
180+
let scriptToInject = "";
181+
const sortedDeps = [...allDeps].sort(
182+
(a, b) => allGlobalNames.indexOf(a) - allGlobalNames.indexOf(b)
183+
);
184+
for (const depName of sortedDeps) {
185+
if (globalComponentNames.has(depName)) {
186+
const info = componentInfo.get(depName);
187+
if (info) scriptToInject += `import ${depName} from '${info.virt}';\n`;
188+
} else {
189+
const definition = globalVarDefs.get(depName);
190+
if (definition) scriptToInject += `\n${definition}`;
191+
}
192+
}
193+
194+
const markupWithDeps = applyImports(rawMarkup, scriptToInject);
195+
const markup = applyImports(markupWithDeps, imports);
196+
const hash = createHash("sha1").update(markup).digest("hex").slice(0, 8);
197+
198+
let local = hashToLocal.get(hash);
199+
const virt = `${VIRT}${hash}.js`;
200+
if (!local) {
201+
local = `Inline_${hash}`;
202+
hashToLocal.set(hash, local);
203+
if (!cache.has(virt)) cache.set(virt, markup);
204+
const ns = `__InlineNS_${hash}`;
205+
importsToAdd += `import * as ${ns} from '${virt}';\nconst ${local}=Object.assign(${ns}.default, ${ns});\n`;
206+
}
207+
208+
componentInfo.set(compName, { local, virt });
209+
globalImportsForTpl += `import ${compName} from '${virt}';\n`;
210+
replacements.push({
211+
start: match.index,
212+
end: match.index + match[0].length,
213+
newText: `const ${compName} = ${local};`,
214+
});
215+
}
216+
217+
let tempHoistedCode = globalsContent;
218+
for (const rep of replacements.reverse()) {
219+
tempHoistedCode =
220+
tempHoistedCode.slice(0, rep.start) + rep.newText + tempHoistedCode.slice(rep.end);
182221
}
183-
depGraph.set(name, dependencies);
222+
hoistedCode = tempHoistedCode;
184223
}
185224

186225
/* 2. Process all regular templates */
@@ -209,10 +248,9 @@ export default function inlineSveltePlugin({
209248
for (const name of sortedDeps) {
210249
if (globalComponentNames.has(name)) continue;
211250

212-
// FIX: A more precise regex to check for actual declarations, not just usage.
213-
const isDeclaredInScript = new RegExp(
214-
`\\b(let|const|var)\\s+([^=;]*?)\\b${name}\\b`
215-
).test(existingScriptContent);
251+
const isDeclaredInScript = new RegExp(`\\b(let|const|var)\\s+[^=;]*?\\b${name}\\b`).test(
252+
existingScriptContent
253+
);
216254
if (isDeclaredInScript) continue;
217255

218256
const definition = globalVarDefs.get(name);

src/plugin.svelte.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { MemoryRouter, Routes, Route } from "@hvniel/svelte-router";
1111
// svelte:globals
1212
const Frank = html`<h1>Frank</h1>`;
1313
const James = html`<h1>James</h1>`;
14+
const James2 = html`<div><James /><Frank /></div>`;
1415
const count = $state(0);
1516
const dupes = [
1617
{
@@ -225,7 +226,7 @@ describe("Inline Svelte Components with sv", () => {
225226
});
226227

227228
it("supports multiple global components", () => {
228-
const renderer = render(html`<div><James name="John" /><James name="Jane" /></div>`);
229+
const renderer = render(html`<div><James /><James /></div>`);
229230

230231
expect(renderer.container.firstElementChild).toMatchInlineSnapshot(`
231232
<div>
@@ -270,4 +271,21 @@ describe("Inline Svelte Components with sv", () => {
270271
</div>
271272
`);
272273
});
274+
275+
it("allows global components to reference other global components", () => {
276+
const renderer = render(James2);
277+
278+
expect(renderer.container.firstElementChild).toMatchInlineSnapshot(`
279+
<div>
280+
<h1>
281+
James
282+
</h1>
283+
<!---->
284+
<h1>
285+
Frank
286+
</h1>
287+
<!---->
288+
</div>
289+
`);
290+
});
273291
});

0 commit comments

Comments
 (0)