@void/svelte 1.1.11 → 1.1.12
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.
- package/dist/plugin.mjs +151 -129
- package/package.json +3 -3
- package/src/runtime/App.svelte +5 -0
package/dist/plugin.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
4
4
|
import nodePath from "node:path";
|
|
5
5
|
import { preprocess } from "svelte/compiler";
|
|
6
6
|
import { normalizePath } from "vite";
|
|
7
|
-
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, findIslandImports, findStaticCssImports,
|
|
7
|
+
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, findIslandImports, findStaticCssImports, hasIslandPages, islandStylesPlugin, recordIslandStyles, recordedIslandStyles, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
8
8
|
//#region ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
9
9
|
const BACKSLASH_RE = /\\/g;
|
|
10
10
|
const isWindows = process.platform === "win32";
|
|
@@ -80,136 +80,146 @@ function islandsPlugin(pagesDir, options) {
|
|
|
80
80
|
islandId
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
return [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
name: "void-svelte:islands",
|
|
86
|
+
enforce: "pre",
|
|
87
|
+
configResolved(config) {
|
|
88
|
+
resolvedConfig = config;
|
|
89
|
+
isDev = config.command === "serve";
|
|
90
|
+
},
|
|
91
|
+
configureServer(server) {
|
|
92
|
+
watchPagesForRescan(server, pagesDir, root, (scan) => {
|
|
93
|
+
pageScan = scan;
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
async buildStart() {
|
|
97
|
+
if (existsSync(pagesDir)) {
|
|
98
|
+
pageScan = await scanPages(root, { pagesDir });
|
|
99
|
+
const filesToScan = [
|
|
100
|
+
...pageScan.pages.map((page) => ({
|
|
101
|
+
filePath: page.componentPath,
|
|
102
|
+
island: page.island
|
|
103
|
+
})),
|
|
104
|
+
...pageScan.layouts.map((layout) => ({
|
|
105
|
+
filePath: layout.filePath,
|
|
106
|
+
island: layout.island
|
|
107
|
+
})),
|
|
108
|
+
...pageScan.namedLayouts.map((namedLayout) => ({
|
|
109
|
+
filePath: namedLayout.filePath,
|
|
110
|
+
island: namedLayout.island
|
|
111
|
+
}))
|
|
112
|
+
];
|
|
113
|
+
for (const entry of filesToScan) {
|
|
114
|
+
if (!entry.island) continue;
|
|
115
|
+
const filePath = resolve(pagesDir, entry.filePath);
|
|
116
|
+
const code = readFileSync(filePath, "utf-8");
|
|
117
|
+
const importerDir = dirname(filePath);
|
|
118
|
+
for (const islandImport of findIslandImports(code, filePath)) registerIsland(islandImport.specifier, importerDir, islandImport.hydrate);
|
|
119
|
+
}
|
|
120
|
+
for (const page of pageScan.pages) {
|
|
121
|
+
if (!page.island) continue;
|
|
122
|
+
if (page.componentPath.endsWith(".svelte")) islandCssFiles.add(normalizePath(resolve(pagesDir, page.componentPath)));
|
|
123
|
+
const layoutChain = resolveEffectiveLayoutChain(page, pageScan.layouts, pageScan.namedLayouts);
|
|
124
|
+
for (const layout of layoutChain) islandCssFiles.add(normalizePath(resolve(pagesDir, layout.filePath)));
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
},
|
|
128
|
+
buildEnd() {
|
|
129
|
+
recordIslandStyles(this, pageScan, pagesDir);
|
|
130
|
+
},
|
|
131
|
+
resolveId: {
|
|
132
|
+
filter: { id: /^(virtual:void-islands-(renderer|client)|\/@void-island-wrap\/)/ },
|
|
133
|
+
handler(source) {
|
|
134
|
+
if (source === ISLANDS_RENDERER_ID || source === ISLANDS_CLIENT_ENTRY_ID) return "\0" + source;
|
|
135
|
+
if (source.startsWith(WRAPPER_PREFIX)) return source;
|
|
124
136
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
142
|
-
return generateIslandRenderer(pageScan, pagesDir, mdScriptIds, needsJS);
|
|
137
|
+
},
|
|
138
|
+
load: {
|
|
139
|
+
filter: { id: /^(\0virtual:void-islands-(renderer|client)|\/@void-island-wrap\/)/ },
|
|
140
|
+
handler(id) {
|
|
141
|
+
if (id === "\0" + ISLANDS_RENDERER_ID) {
|
|
142
|
+
if (!pageScan) return "export function renderIslandPage() { throw new Error('No pages found'); }";
|
|
143
|
+
const mdPlugin = resolvedConfig?.plugins?.find((plugin) => plugin.name === "void-md");
|
|
144
|
+
const mdScriptIds = new Set(mdPlugin?.api?.getClientScripts?.()?.keys() ?? []);
|
|
145
|
+
const needsJS = computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig);
|
|
146
|
+
return generateIslandRenderer(pageScan, pagesDir, mdScriptIds, needsJS);
|
|
147
|
+
}
|
|
148
|
+
if (id === "\0" + ISLANDS_CLIENT_ENTRY_ID) {
|
|
149
|
+
if (!pageScan) return "";
|
|
150
|
+
return generateIslandClientEntry(islandManifest, islandCssFiles, recordedIslandStyles(pagesDir), islandManifest.size > 0 || computePagesNeedingClientJS(pageScan, pagesDir, resolvedConfig).size > 0, isDev);
|
|
151
|
+
}
|
|
152
|
+
if (id.startsWith(WRAPPER_PREFIX)) return generateWrapperComponent(id);
|
|
143
153
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
},
|
|
155
|
+
transform: {
|
|
156
|
+
filter: { id: /\.island\.svelte$/ },
|
|
157
|
+
handler(code, id) {
|
|
158
|
+
if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
|
|
159
|
+
const importerDir = dirname(id);
|
|
160
|
+
const edits = [];
|
|
161
|
+
for (const islandImport of findIslandImports(code, id)) {
|
|
162
|
+
const name = islandImport.importName;
|
|
163
|
+
const spec = islandImport.specifier;
|
|
164
|
+
const strategy = islandImport.hydrate;
|
|
165
|
+
const { absPath, islandId } = registerIsland(spec, importerDir, strategy);
|
|
166
|
+
const wrapperUrl = WRAPPER_PREFIX + encodeURIComponent(islandId) + ".svelte?src=" + encodeURIComponent(absPath || spec) + "&island=" + encodeURIComponent(islandId) + "&hydrate=" + encodeURIComponent(strategy);
|
|
167
|
+
const replacement = `import ${name} from ${JSON.stringify(wrapperUrl)}`;
|
|
168
|
+
edits.push({
|
|
169
|
+
start: islandImport.start,
|
|
170
|
+
end: islandImport.end,
|
|
171
|
+
text: replacement
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (edits.length > 0) return editIslandSource(code, edits);
|
|
147
175
|
}
|
|
148
|
-
|
|
176
|
+
},
|
|
177
|
+
async config(_, env) {
|
|
178
|
+
if (!existsSync(pagesDir)) return;
|
|
179
|
+
if (env.command === "serve") return { optimizeDeps: { include: ["svelte"] } };
|
|
180
|
+
if (await hasIslandPages(root, pagesDir)) return { environments: { client: { build: {
|
|
181
|
+
manifest: true,
|
|
182
|
+
rollupOptions: { input: { "islands-client": ISLANDS_CLIENT_ENTRY_ID } }
|
|
183
|
+
} } } };
|
|
149
184
|
}
|
|
150
185
|
},
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const name = islandImport.importName;
|
|
159
|
-
const spec = islandImport.specifier;
|
|
160
|
-
const strategy = islandImport.hydrate;
|
|
161
|
-
const { absPath, islandId } = registerIsland(spec, importerDir, strategy);
|
|
162
|
-
const wrapperUrl = WRAPPER_PREFIX + encodeURIComponent(islandId) + ".svelte?src=" + encodeURIComponent(absPath || spec) + "&island=" + encodeURIComponent(islandId) + "&hydrate=" + encodeURIComponent(strategy);
|
|
163
|
-
const replacement = `import ${name} from ${JSON.stringify(wrapperUrl)}`;
|
|
164
|
-
edits.push({
|
|
165
|
-
start: islandImport.start,
|
|
166
|
-
end: islandImport.end,
|
|
167
|
-
text: replacement
|
|
168
|
-
});
|
|
186
|
+
{
|
|
187
|
+
name: "void-svelte:islands-style",
|
|
188
|
+
apply: "build",
|
|
189
|
+
resolveId: {
|
|
190
|
+
filter: { id: /\.svelte\.css\?island-style$/ },
|
|
191
|
+
handler(source) {
|
|
192
|
+
return source;
|
|
169
193
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const api = resolvedConfig?.plugins.find((plugin) => plugin.name === "vite-plugin-svelte:config")?.api;
|
|
195
|
-
if (typeof api?.compileSvelte !== "function" || typeof api.idParser !== "function") this.error("Svelte island page styles need the @sveltejs/vite-plugin-svelte version that @void/svelte depends on.");
|
|
196
|
-
this.addWatchFile(file);
|
|
197
|
-
let code = readFileSync(file, "utf-8");
|
|
198
|
-
let map;
|
|
199
|
-
if (api.options.preprocess) {
|
|
200
|
-
const processed = await preprocess(code, api.options.preprocess, { filename: file });
|
|
201
|
-
code = processed.code;
|
|
202
|
-
map = processed.map;
|
|
203
|
-
for (const dependency of processed.dependencies ?? []) this.addWatchFile(dependency);
|
|
194
|
+
},
|
|
195
|
+
load: {
|
|
196
|
+
filter: { id: [/\.svelte\.css\?island-style$/, /\.svelte\?svelte&type=style/] },
|
|
197
|
+
order: "pre",
|
|
198
|
+
async handler(id) {
|
|
199
|
+
const islandStyle = id.endsWith(".css?island-style");
|
|
200
|
+
if (!islandStyle && (this.environment.config.consumer !== "client" || !recordedIslandStyles(pagesDir)?.includes(id))) return;
|
|
201
|
+
const file = islandStyle ? id.slice(0, -17) : id.slice(0, id.indexOf("?"));
|
|
202
|
+
const api = resolvedConfig?.plugins.find((plugin) => plugin.name === "vite-plugin-svelte:config")?.api;
|
|
203
|
+
if (typeof api?.compileSvelte !== "function" || typeof api.idParser !== "function") this.error("Svelte island page styles need the @sveltejs/vite-plugin-svelte version that @void/svelte depends on.");
|
|
204
|
+
this.addWatchFile(file);
|
|
205
|
+
let code = readFileSync(file, "utf-8");
|
|
206
|
+
let map;
|
|
207
|
+
if (api.options.preprocess) {
|
|
208
|
+
const processed = await preprocess(code, api.options.preprocess, { filename: file });
|
|
209
|
+
code = processed.code;
|
|
210
|
+
map = processed.map;
|
|
211
|
+
for (const dependency of processed.dependencies ?? []) this.addWatchFile(dependency);
|
|
212
|
+
}
|
|
213
|
+
const { compiled } = await api.compileSvelte(api.idParser(file, false), code, api.options, this.environment, map);
|
|
214
|
+
return {
|
|
215
|
+
code: compiled.css?.code ?? "",
|
|
216
|
+
map: compiled.css?.map ?? null
|
|
217
|
+
};
|
|
204
218
|
}
|
|
205
|
-
const { compiled } = await api.compileSvelte(api.idParser(file, false), code, api.options, this.environment, map);
|
|
206
|
-
return {
|
|
207
|
-
code: compiled.css?.code ?? "",
|
|
208
|
-
map: compiled.css?.map ?? null
|
|
209
|
-
};
|
|
210
219
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
220
|
+
},
|
|
221
|
+
islandStylesPlugin(pagesDir)
|
|
222
|
+
];
|
|
213
223
|
}
|
|
214
224
|
function generateWrapperComponent(id) {
|
|
215
225
|
const url = new URL(id, "file://");
|
|
@@ -232,6 +242,7 @@ function generateIslandRenderer(scan, pagesDir, mdScriptIds, pagesNeedingClientJ
|
|
|
232
242
|
import { render } from "svelte/server";
|
|
233
243
|
import App from "@void/svelte/App.svelte";
|
|
234
244
|
import { renderHeadToString, renderHtmlAttrs, renderBodyAttrs } from "void/pages-head";
|
|
245
|
+
import { islandFrontmatterScript } from "void/pages-server";
|
|
235
246
|
|
|
236
247
|
${generateComponentManifest(scan, pagesDir)}
|
|
237
248
|
${mdScriptIdsCode}
|
|
@@ -242,7 +253,8 @@ function escapeAttr(s) {
|
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
export async function renderIslandPage(pageObj, assetTags) {
|
|
245
|
-
const
|
|
256
|
+
const __pageModule = await components[pageObj.component]();
|
|
257
|
+
const PageComponent = __pageModule.default;
|
|
246
258
|
const layoutIds = layoutTree[pageObj.component] || [];
|
|
247
259
|
const layoutComponents = await Promise.all(
|
|
248
260
|
layoutIds.map(async (id) => (await components[id]()).default)
|
|
@@ -255,11 +267,18 @@ export async function renderIslandPage(pageObj, assetTags) {
|
|
|
255
267
|
pageProps: pageObj.props,
|
|
256
268
|
shared: pageObj.shared || {},
|
|
257
269
|
params: pageObj.params || {},
|
|
270
|
+
// Set above the layouts so they can read the Markdown page's values.
|
|
271
|
+
frontmatter: __pageModule.frontmatter,
|
|
258
272
|
router: null,
|
|
259
273
|
stores: null,
|
|
260
274
|
},
|
|
261
275
|
});
|
|
262
276
|
|
|
277
|
+
// Islands hydrate as separate roots. Send the page frontmatter so their
|
|
278
|
+
// useFrontmatter() returns the same value in the browser.
|
|
279
|
+
const __frontmatterScript = __pagesNeedingClientJS.has(pageObj.component) && html.includes("data-island=")
|
|
280
|
+
? islandFrontmatterScript(__pageModule.frontmatter)
|
|
281
|
+
: "";
|
|
263
282
|
const __mdScriptAttr = __mdClientScriptIds.has(pageObj.component) ? \` data-md-script="\${escapeAttr(pageObj.component)}"\` : "";
|
|
264
283
|
const headHtml = pageObj.head ? renderHeadToString(pageObj.head) : "";
|
|
265
284
|
const htmlAttrs = pageObj.head ? renderHtmlAttrs(pageObj.head) : "";
|
|
@@ -270,31 +289,34 @@ export async function renderIslandPage(pageObj, assetTags) {
|
|
|
270
289
|
<head>\${headHtml}\${head}\${assetTags.css}\${__pagesNeedingClientJS.has(pageObj.component) ? assetTags.preloads : ""}</head>
|
|
271
290
|
<body\${bodyAttrs}>
|
|
272
291
|
<div id="app"\${__mdScriptAttr}>\${html}</div>
|
|
273
|
-
\${__pagesNeedingClientJS.has(pageObj.component) ? assetTags.body : ""}
|
|
292
|
+
\${__frontmatterScript}\${__pagesNeedingClientJS.has(pageObj.component) ? assetTags.body : ""}
|
|
274
293
|
</body>
|
|
275
294
|
</html>\`;
|
|
276
295
|
}
|
|
277
296
|
`;
|
|
278
297
|
}
|
|
279
|
-
function generateIslandClientEntry(manifest, cssFiles, isDev) {
|
|
298
|
+
function generateIslandClientEntry(manifest, cssFiles, styles, needsRuntime, isDev) {
|
|
280
299
|
const entries = [...manifest.values()];
|
|
281
300
|
const uniqueIslands = /* @__PURE__ */ new Map();
|
|
282
301
|
for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
|
|
283
302
|
const lines = [];
|
|
284
|
-
lines.push(
|
|
285
|
-
if (!isDev) for (const file of cssFiles) {
|
|
303
|
+
if (!isDev && styles) for (const id of styles) lines.push(`import ${JSON.stringify(id)};`);
|
|
304
|
+
else if (!isDev) for (const file of cssFiles) {
|
|
286
305
|
const content = readFileSync(file, "utf-8");
|
|
287
306
|
if (/<style\b/.test(content)) lines.push(`import ${JSON.stringify(file + ".css?island-style")};`);
|
|
288
307
|
for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
|
|
289
308
|
}
|
|
309
|
+
if (!needsRuntime && !isDev) return lines.join("\n");
|
|
310
|
+
lines.push("import { initIslands } from \"void/pages-client\";");
|
|
290
311
|
lines.push("const islands = {");
|
|
291
312
|
for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
|
|
292
313
|
lines.push("};");
|
|
293
314
|
lines.push(`
|
|
294
|
-
async function hydrateIsland(el, mod, props) {
|
|
315
|
+
async function hydrateIsland(el, mod, props, frontmatter) {
|
|
295
316
|
const { hydrate } = await import("svelte");
|
|
296
317
|
const Component = mod.default || mod;
|
|
297
|
-
|
|
318
|
+
const context = new Map([["__void_frontmatter", frontmatter]]);
|
|
319
|
+
hydrate(Component, { target: el, props, context });
|
|
298
320
|
el.setAttribute("data-hydrated", "true");
|
|
299
321
|
}
|
|
300
322
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/svelte",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"description": "Svelte pages, server rendering, forms, and navigation for Void.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"svelte": "^5.57.2",
|
|
44
44
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
|
|
45
45
|
"vite-plus": "1.2.0",
|
|
46
|
-
"void": "1.1.
|
|
46
|
+
"void": "1.1.12"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"svelte": "^5.0.0",
|
|
50
50
|
"vite": "^8.0.0",
|
|
51
|
-
"void": "1.1.
|
|
51
|
+
"void": "1.1.12"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "vp pack",
|
package/src/runtime/App.svelte
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
pageProps: props.pageProps,
|
|
14
14
|
shared: props.shared,
|
|
15
15
|
params: props.params,
|
|
16
|
+
frontmatter: props.frontmatter,
|
|
16
17
|
router: props.router,
|
|
17
18
|
routeUrl: props.routeUrl,
|
|
18
19
|
navigation: props.navigation,
|
|
@@ -51,6 +52,10 @@
|
|
|
51
52
|
setContext("__void_navigation", {
|
|
52
53
|
get value() { return navigation; },
|
|
53
54
|
});
|
|
55
|
+
// Island pages pass Markdown frontmatter so layouts can read it (useFrontmatter).
|
|
56
|
+
if (init.frontmatter) {
|
|
57
|
+
setContext("__void_frontmatter", init.frontmatter);
|
|
58
|
+
}
|
|
54
59
|
|
|
55
60
|
// Client mode: subscribe to stores for live updates after hydration.
|
|
56
61
|
// onMount only runs on the client, not during SSR.
|