Skip to content

Commit 2421c1a

Browse files
committed
Update package version to 0.0.9, enhance README with new fenceEnd and globalsEnd options, and refactor inline Svelte plugin for improved clarity and functionality.
1 parent 9d594d1 commit 2421c1a

File tree

3 files changed

+91
-107
lines changed

3 files changed

+91
-107
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,9 @@ const Thing1 = html`
209209
| :------------------ | :--------- | :------------------- | :------------------------------------------------- |
210210
| `tags` | `string[]` | `["html", "svelte"]` | Tag names to be treated as inline Svelte markup. |
211211
| `fenceStart` | `string` | `/* svelte:imports` | The comment that starts a standard import fence. |
212+
| `fenceEnd` | `string` | `*/` | The comment that ends a standard import fence. |
212213
| `globalsFenceStart` | `string` | `/* svelte:globals` | The comment that starts a global components fence. |
213-
| `fenceEnd` | `string` | `*/` | The comment that ends any fence. |
214+
| `globalsEnd` | `string` | `*/` | The comment that ends a global components fence. |
214215

215216
---
216217

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.8",
3+
"version": "0.0.9",
44
"license": "MIT",
55
"author": "Haniel Ubogu <https://github.com/HanielU>",
66
"repository": {

src/lib/plugin/index.ts

Lines changed: 88 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,34 @@ import MagicString from "magic-string";
44
import type { Plugin } from "vite";
55

66
export interface InlineSvelteOptions {
7-
/** Template-tag names treated as Svelte markup. Default: `["html", "svelte"]` */
7+
/** Template-tag names treated as Svelte markup – default `["html", "svelte"]` */
88
tags?: string[];
9-
/** Comment that *starts* an import fence. Default: `/* svelte:imports` */
9+
/** Comment that *starts* an import fence – default `/* svelte:imports` */
1010
fenceStart?: string;
11-
/** Comment that *ends* an import fence. Default: `*\/` */
11+
/** Comment that *ends* an import fence – default `*\/` */
1212
fenceEnd?: string;
13-
/** Comment that *starts* a global components fence. Default: `/* svelte:globals` */
14-
globalsFenceStart?: string;
13+
/** Comment that *starts* a globals fence – default `/* svelte:globals` */
14+
globalsStart?: string;
15+
/** Comment that *ends* a globals fence – default `*\/` */
16+
globalsEnd?: string;
1517
}
1618

1719
/* ───────── helpers ───────── */
1820

1921
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2022
const isUserSource = (id: string) => !id.includes("/node_modules/") && /\.(c?[tj]sx?)$/.test(id);
2123

22-
/** Injects a string of script content into a Svelte component's markup. */
23-
function applyScriptContent(markup: string, scriptContent: string): string {
24-
if (!scriptContent) return markup;
24+
/** Inject shared imports without duplicating instance `<script>` blocks */
25+
function applyImports(markup: string, imports: string): string {
26+
if (!imports) return markup;
2527

2628
const scriptRE = /<script(?![^>]*context=["']module["'])[^>]*>/i;
27-
const match = scriptRE.exec(markup);
28-
29-
if (match) {
30-
const idx = match.index + match[0].length;
31-
return `${markup.slice(0, idx)}\n${scriptContent}\n${markup.slice(idx)}`;
32-
} else {
33-
return `<script>\n${scriptContent}\n</script>\n${markup}`;
29+
const m = scriptRE.exec(markup);
30+
if (m) {
31+
const idx = m.index + m[0].length;
32+
return markup.slice(0, idx) + "\n" + imports + "\n" + markup.slice(idx);
3433
}
34+
return `<script>\n${imports}\n</script>\n` + markup;
3535
}
3636

3737
/* ───────── plugin ───────── */
@@ -40,149 +40,132 @@ export default function inlineSveltePlugin({
4040
tags = ["html", "svelte"],
4141
fenceStart = "/* svelte:imports",
4242
fenceEnd = "*/",
43-
globalsFenceStart = "/* svelte:globals",
43+
globalsStart = "/* svelte:globals",
44+
globalsEnd = "*/",
4445
}: InlineSvelteOptions = {}): Plugin {
4546
const tagGroup = tags.map(esc).join("|");
46-
const tplRE = new RegExp(
47-
`(?:const|let|var)\\s+([a-zA-Z0-9_$]+)\\s*=\\s*(?:${tagGroup})\\s*\`([\\s\\S]*?)\``,
48-
"g"
49-
);
50-
const importsFenceRE = new RegExp(`${esc(fenceStart)}([\\s\\S]*?)${esc(fenceEnd)}`, "m");
51-
const globalsFenceRE = new RegExp(`${esc(globalsFenceStart)}([\\s\\S]*?)${esc(fenceEnd)}`, "m");
47+
const tplRE = new RegExp(`(?:${tagGroup})\\s*\`([\\s\\S]*?)\``, "g");
48+
const fenceRE = new RegExp(`${esc(fenceStart)}([\\s\\S]*?)${esc(fenceEnd)}`, "m");
49+
const globalsRE = new RegExp(`${esc(globalsStart)}([\\s\\S]*?)${esc(globalsEnd)}`, "m");
50+
const globalDefRE = /const\s+([a-zA-Z0-9_$]+)\s*=\s*(?:html|svelte)\s*\`([\s\S]*?)\`/g;
5251

53-
const VIRT_PREFIX = "virtual:inline-svelte/";
54-
const RSLV_PREFIX = `\0${VIRT_PREFIX}`;
52+
const VIRT = "virtual:inline-svelte/";
53+
const RSLV = "\0" + VIRT;
5554

55+
/** virtualId → full markup (with injected imports) */
5656
const cache = new Map<string, string>();
5757

5858
return {
59-
name: "@hvniel/vite-plugin-svelte-inline-component-v3",
59+
name: "@hvniel/vite-plugin-svelte-inline-component",
6060
enforce: "pre",
6161

6262
transform(code, id) {
63-
if (!isUserSource(id) || !tags.some(tag => code.includes(tag))) {
64-
return;
65-
}
63+
if (!isUserSource(id)) return;
6664

6765
const ms = new MagicString(code);
6866
let edited = false;
6967

70-
// === STAGE 1: Process `svelte:imports` fence ===
71-
const importsMatch = importsFenceRE.exec(code);
72-
const standardImports = (importsMatch?.[1] ?? "").trim();
68+
/* file‑level shared imports (may be empty) */
69+
const imports = (fenceRE.exec(code)?.[1] ?? "").trim();
7370

74-
// === STAGE 2: Process `svelte:globals` fence ===
75-
const globalsMatch = globalsFenceRE.exec(code);
76-
// This will hold the full script block (imports + consts) for global components
77-
let globalScriptBlock = "";
78-
const hashToLocal = new Map<string, string>();
71+
let importsToAdd = "";
72+
let constsToAdd = "";
73+
let globalImportsForTpl = "";
74+
const hashToLocal = new Map<string, string>(); // dedupe within THIS pass
7975

76+
/* 1. Process globals */
77+
const globalsMatch = globalsRE.exec(code);
8078
if (globalsMatch) {
81-
const globalsContent = globalsMatch[1];
82-
const globalsStartIndex = globalsMatch.index;
83-
let match: RegExpExecArray | null;
84-
85-
tplRE.lastIndex = 0; // Reset regex before scanning fence content
86-
while ((match = tplRE.exec(globalsContent))) {
87-
const [fullMatch, componentName, rawMarkup] = match;
88-
89-
const finalMarkup = applyScriptContent(rawMarkup, standardImports);
90-
const hash = createHash("sha1").update(finalMarkup).digest("hex").slice(0, 8);
79+
edited = true;
80+
const globalsContent = globalsMatch[1] ?? "";
81+
// Overwrite the entire globals block so it's removed from the final output
82+
ms.overwrite(globalsMatch.index, globalsMatch.index + globalsMatch[0].length, "");
83+
84+
let defMatch: RegExpExecArray | null;
85+
while ((defMatch = globalDefRE.exec(globalsContent))) {
86+
const compName = defMatch[1];
87+
const rawMarkup = defMatch[2];
88+
// Globals can also use file-level imports from the `svelte:imports` fence
89+
const markup = applyImports(rawMarkup, imports);
90+
const hash = createHash("sha1").update(markup).digest("hex").slice(0, 8);
9191

9292
let local = hashToLocal.get(hash);
9393
if (!local) {
94-
local = `_Inline_${hash}`;
94+
local = `Inline_${hash}`;
9595
hashToLocal.set(hash, local);
9696

97-
const virtId = `${VIRT_PREFIX}${hash}.js`;
98-
if (!cache.has(virtId)) cache.set(virtId, finalMarkup);
99-
100-
const ns = `_InlineNS_${hash}`;
101-
const importStatement = `import * as ${ns} from '${virtId}';`;
102-
const localDefStatement = `const ${local} = Object.assign(${ns}.default, ${ns});`;
97+
const virt = `${VIRT}${hash}.js`;
98+
if (!cache.has(virt)) cache.set(virt, markup);
10399

104-
// Prepend the import to the top of the file for resolution
105-
ms.prepend(`${importStatement}\n${localDefStatement}\n`);
100+
const ns = `__InlineNS_${hash}`;
101+
importsToAdd +=
102+
`import * as ${ns} from '${virt}';\n` +
103+
`const ${local}=Object.assign(${ns}.default, ${ns});\n`;
106104

107-
// **FIX**: Build a full script block to inject into local components
108-
// This includes the necessary import and the const declarations.
109-
const aliasStatement = `const ${componentName} = ${local};`;
110-
globalScriptBlock += `${importStatement}\n${localDefStatement}\n${aliasStatement}\n`;
111-
} else {
112-
// If the component was already processed, just add its alias declaration
113-
globalScriptBlock += `const ${componentName} = ${local};\n`;
105+
// This import will be injected into the <script> of other templates
106+
globalImportsForTpl += `import ${compName} from '${virt}';\n`;
114107
}
115-
116-
ms.overwrite(
117-
globalsStartIndex + match.index,
118-
globalsStartIndex + match.index + fullMatch.length,
119-
`const ${componentName} = ${local};`
120-
);
121-
edited = true;
108+
// Create the top-level constant for the global component
109+
constsToAdd += `const ${compName} = ${local};\n`;
122110
}
123-
ms.remove(globalsStartIndex, globalsStartIndex + globalsMatch[0].length);
124111
}
125112

126-
// === STAGE 3: Process remaining "local" inline components ===
127-
let match: RegExpExecArray | null;
128-
tplRE.lastIndex = 0;
129-
130-
while ((match = tplRE.exec(code))) {
113+
/* 2. Process all regular templates */
114+
let m: RegExpExecArray | null;
115+
tplRE.lastIndex = 0; // Reset regex state
116+
while ((m = tplRE.exec(code))) {
117+
// Skip any templates found inside the globals block, as they've already been processed.
131118
if (
132119
globalsMatch &&
133-
match.index >= globalsMatch.index &&
134-
match.index < globalsMatch.index + globalsMatch[0].length
120+
m.index >= globalsMatch.index &&
121+
m.index < globalsMatch.index + globalsMatch[0].length
135122
) {
136123
continue;
137124
}
138125

139-
const [fullMatch, componentName, rawMarkup] = match;
140-
141-
// Inject standard imports AND the full script block for global components
142-
const scriptToInject = `${standardImports}\n${globalScriptBlock}`;
143-
const finalMarkup = applyScriptContent(rawMarkup, scriptToInject);
144-
145-
const hash = createHash("sha1").update(finalMarkup).digest("hex").slice(0, 8);
126+
const rawMarkup = m[1];
127+
// Inject imports for global components first, then file-level imports
128+
const markupWithGlobals = applyImports(rawMarkup, globalImportsForTpl);
129+
const markup = applyImports(markupWithGlobals, imports);
130+
const hash = createHash("sha1").update(markup).digest("hex").slice(0, 8);
146131

147132
let local = hashToLocal.get(hash);
148133
if (!local) {
149-
local = `_Inline_${hash}`;
134+
local = `Inline_${hash}`;
150135
hashToLocal.set(hash, local);
151136

152-
const virtId = `${VIRT_PREFIX}${hash}.js`;
153-
if (!cache.has(virtId)) cache.set(virtId, finalMarkup);
137+
const virt = `${VIRT}${hash}.js`;
138+
if (!cache.has(virt)) cache.set(virt, markup);
154139

155-
const ns = `_InlineNS_${hash}`;
156-
ms.prepend(
157-
`import * as ${ns} from '${virtId}';\n` +
158-
`const ${local} = Object.assign(${ns}.default, ${ns});\n`
159-
);
140+
const ns = `__InlineNS_${hash}`;
141+
importsToAdd +=
142+
`import * as ${ns} from '${virt}';\n` +
143+
`const ${local}=Object.assign(${ns}.default, ${ns});\n`;
160144
}
161145

162-
ms.overwrite(
163-
match.index,
164-
match.index + fullMatch.length,
165-
`const ${componentName} = ${local};`
166-
);
146+
ms.overwrite(m.index, tplRE.lastIndex, local);
167147
edited = true;
168148
}
169149

170-
if (!edited) return null;
150+
/* 3. Prepend all generated code */
151+
if (edited) {
152+
// Prepend consts first, then imports, to respect declaration order.
153+
ms.prepend(constsToAdd);
154+
ms.prepend(importsToAdd);
155+
return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
156+
}
171157

172-
return {
173-
code: ms.toString(),
174-
map: ms.generateMap({ hires: true }),
175-
};
158+
return null;
176159
},
177160

178161
resolveId(id) {
179-
return id.startsWith(VIRT_PREFIX) ? RSLV_PREFIX + id.slice(VIRT_PREFIX.length) : undefined;
162+
return id.startsWith(VIRT) ? RSLV + id.slice(VIRT.length) : undefined;
180163
},
181164

182165
load(id) {
183-
if (!id.startsWith(RSLV_PREFIX)) return;
166+
if (!id.startsWith(RSLV)) return;
184167

185-
const markup = cache.get(VIRT_PREFIX + id.slice(RSLV_PREFIX.length))!;
168+
const markup = cache.get(VIRT + id.slice(RSLV.length))!;
186169
return compile(markup, {
187170
generate: "client",
188171
css: "injected",

0 commit comments

Comments
 (0)