@void/react 0.21.2 → 0.21.4
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,5 +1,6 @@
|
|
|
1
1
|
//#region src/runtime/deferred-shell.ts
|
|
2
2
|
const DEFERRED_SHELL_END_ID = "__VOID_DEFERRED_SHELL_END__";
|
|
3
3
|
const DEFERRED_SHELL_END_MARKER = `<template id="${DEFERRED_SHELL_END_ID}"></template>`;
|
|
4
|
+
const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
|
|
4
5
|
//#endregion
|
|
5
|
-
export {
|
|
6
|
+
export { DEFERRED_SHELL_END_ID as n, DEFERRED_SHELL_END_MARKER as r, DEFERRED_SHELL_CANCEL_REASON as t };
|
package/dist/plugin.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir
|
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import nodePath from "node:path";
|
|
5
5
|
import { normalizePath } from "vite";
|
|
6
|
-
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, extractCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
6
|
+
import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, editIslandSource, extractCssImports, findIslandImports, findStaticCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
|
|
7
7
|
//#region ../../node_modules/.pnpm/pathslash@0.1.0/node_modules/pathslash/dist/index.mjs
|
|
8
8
|
const BACKSLASH_RE = /\\/g;
|
|
9
9
|
const isWindows = process.platform === "win32";
|
|
@@ -50,23 +50,6 @@ const COMPONENT_EXTENSIONS = [
|
|
|
50
50
|
".ts",
|
|
51
51
|
".js"
|
|
52
52
|
];
|
|
53
|
-
const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
|
|
54
|
-
function findIslandImports(code) {
|
|
55
|
-
const imports = [];
|
|
56
|
-
let match;
|
|
57
|
-
ISLAND_IMPORT_RE.lastIndex = 0;
|
|
58
|
-
while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
|
|
59
|
-
const specifierRaw = match[2];
|
|
60
|
-
imports.push({
|
|
61
|
-
statement: match[0],
|
|
62
|
-
importName: match[1],
|
|
63
|
-
specifierRaw,
|
|
64
|
-
specifier: specifierRaw.slice(1, -1),
|
|
65
|
-
hydrate: match[3].slice(1, -1)
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return imports;
|
|
69
|
-
}
|
|
70
53
|
function islandsPlugin(pagesDir, options) {
|
|
71
54
|
let pageScan = null;
|
|
72
55
|
let isDev = false;
|
|
@@ -113,7 +96,7 @@ function islandsPlugin(pagesDir, options) {
|
|
|
113
96
|
const filePath = resolve(pagesDir, entry.filePath);
|
|
114
97
|
const code = readFileSync(filePath, "utf-8");
|
|
115
98
|
const importerDir = dirname(filePath);
|
|
116
|
-
for (const islandImport of findIslandImports(code)) {
|
|
99
|
+
for (const islandImport of findIslandImports(code, filePath)) {
|
|
117
100
|
const spec = islandImport.specifier;
|
|
118
101
|
const strategy = islandImport.hydrate;
|
|
119
102
|
const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
|
|
@@ -174,10 +157,10 @@ function islandsPlugin(pagesDir, options) {
|
|
|
174
157
|
handler(code, id) {
|
|
175
158
|
if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
|
|
176
159
|
const importerDir = dirname(id);
|
|
177
|
-
|
|
160
|
+
const edits = [];
|
|
178
161
|
const wrappers = [];
|
|
179
162
|
let idx = 0;
|
|
180
|
-
for (const islandImport of findIslandImports(code)) {
|
|
163
|
+
for (const islandImport of findIslandImports(code, id)) {
|
|
181
164
|
const name = islandImport.importName;
|
|
182
165
|
const specRaw = islandImport.specifierRaw;
|
|
183
166
|
const strategy = islandImport.hydrate;
|
|
@@ -194,16 +177,17 @@ function islandsPlugin(pagesDir, options) {
|
|
|
194
177
|
}
|
|
195
178
|
const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
|
|
196
179
|
const replacement = `import ${rawName} from ${specRaw}`;
|
|
197
|
-
|
|
180
|
+
edits.push({
|
|
181
|
+
start: islandImport.start,
|
|
182
|
+
end: islandImport.end,
|
|
183
|
+
text: replacement
|
|
184
|
+
});
|
|
198
185
|
wrappers.push(`var ${name} = function ${name}(props) { return _React_island.createElement("div", { "data-island": ${JSON.stringify(islandId)}, "data-props": JSON.stringify(props), "data-hydrate": ${JSON.stringify(strategy)} }, _React_island.createElement(${rawName}, props)); };`);
|
|
199
186
|
}
|
|
200
|
-
if (wrappers.length > 0) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
map: null
|
|
205
|
-
};
|
|
206
|
-
}
|
|
187
|
+
if (wrappers.length > 0) return editIslandSource(code, edits, {
|
|
188
|
+
prepend: "import _React_island from \"react\";\n",
|
|
189
|
+
append: "\n" + wrappers.join("\n")
|
|
190
|
+
});
|
|
207
191
|
}
|
|
208
192
|
},
|
|
209
193
|
config(_, env) {
|
|
@@ -292,14 +276,9 @@ function generateIslandClientEntry(manifest, cssFiles, isDev) {
|
|
|
292
276
|
for (const entry of entries) uniqueIslands.set(entry.islandId, entry.componentPath);
|
|
293
277
|
const lines = [];
|
|
294
278
|
lines.push("import { initIslands } from \"void/pages-client\";");
|
|
295
|
-
if (!isDev) {
|
|
296
|
-
const
|
|
297
|
-
for (const
|
|
298
|
-
const content = readFileSync(file, "utf-8");
|
|
299
|
-
cssImportRe.lastIndex = 0;
|
|
300
|
-
let match;
|
|
301
|
-
while ((match = cssImportRe.exec(content)) !== null) lines.push(rewriteCssImportForVirtualModule(match[0], match[1], file));
|
|
302
|
-
}
|
|
279
|
+
if (!isDev) for (const file of cssFiles) {
|
|
280
|
+
const content = readFileSync(file, "utf-8");
|
|
281
|
+
for (const item of findStaticCssImports(content, file)) lines.push(rewriteCssImportForVirtualModule(item.statement, item.specifier, file));
|
|
303
282
|
}
|
|
304
283
|
lines.push("const islands = {");
|
|
305
284
|
for (const [id, path] of uniqueIslands) lines.push(` ${JSON.stringify(id)}: () => import(${JSON.stringify(path)}),`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "../context-B0GL7p5m.mjs";
|
|
2
2
|
import { n as setActionRouter } from "../action-Bq1iSG6m.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import React, { Suspense, startTransition, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer,
|
|
3
|
+
import { n as DEFERRED_SHELL_END_ID } from "../deferred-shell-DOuP2YAo.mjs";
|
|
4
|
+
import React, { Suspense, startTransition, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer, useTransition } from "react";
|
|
5
5
|
import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, isCallSiteActionError } from "void/pages-client";
|
|
6
6
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
7
7
|
//#region src/runtime/pages-client.ts
|
|
@@ -174,7 +174,7 @@ function RouteResource({ resource, onCommit }) {
|
|
|
174
174
|
const Layout = result.layouts[i];
|
|
175
175
|
element = React.createElement(Layout, null, element);
|
|
176
176
|
}
|
|
177
|
-
return React.createElement(RouterContext.Provider, { value: routeRouter }, React.createElement(ParamsContext.Provider, { value: routeParams }, React.createElement(SharedContext.Provider, { value: pageData.shared }, React.createElement(ErrorsContext.Provider, { value: pageData.errors ?? {} }, element))));
|
|
177
|
+
return React.createElement(RouterContext.Provider, { value: routeRouter }, React.createElement(ParamsContext.Provider, { value: routeParams }, React.createElement(SharedContext.Provider, { value: pageData.shared ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageData.errors ?? {} }, element))));
|
|
178
178
|
}
|
|
179
179
|
function startViewTransition(resource, reveal, enabled) {
|
|
180
180
|
if (!enabled || typeof document.startViewTransition !== "function") {
|
|
@@ -193,7 +193,6 @@ function startViewTransition(resource, reveal, enabled) {
|
|
|
193
193
|
}
|
|
194
194
|
function App({ engine, router, viewTransitions, navigationFallback: NavigationFallback }) {
|
|
195
195
|
const initialPageData = engine.initialPageData;
|
|
196
|
-
const sharedStateRef = useRef(initialPageData.shared);
|
|
197
196
|
const initialResource = useMemo(() => ({
|
|
198
197
|
id: 0,
|
|
199
198
|
url: initialPageData.url || currentUrl(),
|
|
@@ -203,10 +202,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
203
202
|
options: {},
|
|
204
203
|
immediate: false,
|
|
205
204
|
shell: Promise.resolve({
|
|
206
|
-
pageData:
|
|
207
|
-
...initialPageData,
|
|
208
|
-
shared: initialPageData.shared
|
|
209
|
-
},
|
|
205
|
+
pageData: initialPageData,
|
|
210
206
|
component: engine.initialComponent,
|
|
211
207
|
layouts: engine.initialLayouts
|
|
212
208
|
}),
|
|
@@ -222,19 +218,6 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
222
218
|
navigation: idleNavigationState()
|
|
223
219
|
});
|
|
224
220
|
const [isTransitionPending, startTransition] = useTransition();
|
|
225
|
-
const mergeShared = useCallback((result) => {
|
|
226
|
-
if (!result) return result;
|
|
227
|
-
sharedStateRef.current = {
|
|
228
|
-
...sharedStateRef.current,
|
|
229
|
-
...result.pageData.shared || {}
|
|
230
|
-
};
|
|
231
|
-
const shared = sharedStateRef.current;
|
|
232
|
-
result.pageData = {
|
|
233
|
-
...result.pageData,
|
|
234
|
-
shared
|
|
235
|
-
};
|
|
236
|
-
return result;
|
|
237
|
-
}, []);
|
|
238
221
|
const finishNavigation = useCallback((resource, result) => {
|
|
239
222
|
if (committedResources.has(resource.id)) return;
|
|
240
223
|
committedResources.add(resource.id);
|
|
@@ -299,7 +282,7 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
299
282
|
});
|
|
300
283
|
const next = {
|
|
301
284
|
...prepared,
|
|
302
|
-
shell: prepared.shell.then(
|
|
285
|
+
shell: prepared.shell.then((result) => {
|
|
303
286
|
if (!result) {
|
|
304
287
|
const resolveViewTransition = viewTransitionWaiters.get(prepared.id);
|
|
305
288
|
if (resolveViewTransition) {
|
|
@@ -394,7 +377,6 @@ function App({ engine, router, viewTransitions, navigationFallback: NavigationFa
|
|
|
394
377
|
return pagePromise;
|
|
395
378
|
}, [
|
|
396
379
|
engine,
|
|
397
|
-
mergeShared,
|
|
398
380
|
NavigationFallback,
|
|
399
381
|
startTransition,
|
|
400
382
|
viewTransitions
|
|
@@ -1,12 +1,101 @@
|
|
|
1
1
|
import { a as SharedContext, i as RouterContext, n as NavigationContext, r as ParamsContext, t as ErrorsContext } from "../context-B0GL7p5m.mjs";
|
|
2
|
-
import { n as DEFERRED_SHELL_END_MARKER, t as
|
|
2
|
+
import { n as DEFERRED_SHELL_END_ID, r as DEFERRED_SHELL_END_MARKER, t as DEFERRED_SHELL_CANCEL_REASON } from "../deferred-shell-DOuP2YAo.mjs";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { createSsrRouter, idleNavigationState } from "void/pages-client";
|
|
5
5
|
import { renderToReadableStream } from "react-dom/server";
|
|
6
6
|
import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs } from "void/pages-head";
|
|
7
7
|
import { serializePageData } from "void/pages-server";
|
|
8
|
+
//#region src/runtime/page-streams.ts
|
|
9
|
+
function concatStreams(streams) {
|
|
10
|
+
let index = 0;
|
|
11
|
+
let reader;
|
|
12
|
+
let cancelled = false;
|
|
13
|
+
const cancelRemaining = async (reason) => {
|
|
14
|
+
const cancellations = [];
|
|
15
|
+
if (reader) cancellations.push(reader.cancel(reason));
|
|
16
|
+
for (let remaining = index + (reader ? 1 : 0); remaining < streams.length; remaining++) cancellations.push(streams[remaining].cancel(reason));
|
|
17
|
+
await Promise.allSettled(cancellations);
|
|
18
|
+
};
|
|
19
|
+
return new ReadableStream({
|
|
20
|
+
async pull(controller) {
|
|
21
|
+
try {
|
|
22
|
+
while (index < streams.length) {
|
|
23
|
+
reader ??= streams[index].getReader();
|
|
24
|
+
const { done, value } = await reader.read();
|
|
25
|
+
if (cancelled) return;
|
|
26
|
+
if (done) {
|
|
27
|
+
reader.releaseLock();
|
|
28
|
+
reader = void 0;
|
|
29
|
+
index++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
controller.enqueue(value);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
controller.close();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (!cancelled) {
|
|
38
|
+
controller.error(error);
|
|
39
|
+
await cancelRemaining(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async cancel(reason) {
|
|
44
|
+
cancelled = true;
|
|
45
|
+
await cancelRemaining(reason);
|
|
46
|
+
}
|
|
47
|
+
}, { highWaterMark: 0 });
|
|
48
|
+
}
|
|
49
|
+
function readDeferredShell(stream) {
|
|
50
|
+
const reader = stream.getReader();
|
|
51
|
+
const decoder = new TextDecoder();
|
|
52
|
+
const encoder = new TextEncoder();
|
|
53
|
+
const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
|
|
54
|
+
let buffer = "";
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
return new ReadableStream({
|
|
57
|
+
async pull(controller) {
|
|
58
|
+
try {
|
|
59
|
+
while (true) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (cancelled) return;
|
|
62
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
63
|
+
const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
|
|
64
|
+
if (markerIndex !== -1) {
|
|
65
|
+
const shell = buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length);
|
|
66
|
+
await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
|
|
67
|
+
if (!cancelled) {
|
|
68
|
+
controller.enqueue(encoder.encode(shell));
|
|
69
|
+
controller.close();
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (done) {
|
|
74
|
+
if (buffer) controller.enqueue(encoder.encode(buffer));
|
|
75
|
+
controller.close();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (buffer.length > keepLength) {
|
|
79
|
+
controller.enqueue(encoder.encode(buffer.slice(0, -keepLength)));
|
|
80
|
+
buffer = buffer.slice(-keepLength);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (!cancelled) {
|
|
86
|
+
controller.error(error);
|
|
87
|
+
await reader.cancel(error).catch(() => {});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async cancel(reason) {
|
|
92
|
+
cancelled = true;
|
|
93
|
+
await reader.cancel(reason);
|
|
94
|
+
}
|
|
95
|
+
}, { highWaterMark: 0 });
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
8
98
|
//#region src/runtime/pages-server.ts
|
|
9
|
-
const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
|
|
10
99
|
function createPendingDeferred() {
|
|
11
100
|
return new Promise(() => {});
|
|
12
101
|
}
|
|
@@ -23,59 +112,6 @@ function textStream(text) {
|
|
|
23
112
|
controller.close();
|
|
24
113
|
} });
|
|
25
114
|
}
|
|
26
|
-
function concatStreams(streams) {
|
|
27
|
-
return new ReadableStream({ async start(controller) {
|
|
28
|
-
try {
|
|
29
|
-
for (const stream of streams) {
|
|
30
|
-
const reader = stream.getReader();
|
|
31
|
-
while (true) {
|
|
32
|
-
const { done, value } = await reader.read();
|
|
33
|
-
if (done) break;
|
|
34
|
-
controller.enqueue(value);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
controller.close();
|
|
38
|
-
} catch (error) {
|
|
39
|
-
controller.error(error);
|
|
40
|
-
}
|
|
41
|
-
} });
|
|
42
|
-
}
|
|
43
|
-
function readDeferredShell(stream) {
|
|
44
|
-
const encoder = new TextEncoder();
|
|
45
|
-
return new ReadableStream({ async start(controller) {
|
|
46
|
-
const reader = stream.getReader();
|
|
47
|
-
const decoder = new TextDecoder();
|
|
48
|
-
let buffer = "";
|
|
49
|
-
const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
|
|
50
|
-
const flush = (text) => {
|
|
51
|
-
if (text) controller.enqueue(encoder.encode(text));
|
|
52
|
-
};
|
|
53
|
-
try {
|
|
54
|
-
while (true) {
|
|
55
|
-
const { done, value } = await reader.read();
|
|
56
|
-
if (done) {
|
|
57
|
-
buffer += decoder.decode();
|
|
58
|
-
flush(buffer);
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
buffer += decoder.decode(value, { stream: true });
|
|
62
|
-
const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
|
|
63
|
-
if (markerIndex !== -1) {
|
|
64
|
-
flush(buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length));
|
|
65
|
-
await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
if (buffer.length > keepLength) {
|
|
69
|
-
flush(buffer.slice(0, -keepLength));
|
|
70
|
-
buffer = buffer.slice(-keepLength);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
controller.close();
|
|
74
|
-
} catch (error) {
|
|
75
|
-
controller.error(error);
|
|
76
|
-
}
|
|
77
|
-
} });
|
|
78
|
-
}
|
|
79
115
|
async function renderReactPage({ pageObj, assetTags, components, layoutTree, preambleScript = "" }) {
|
|
80
116
|
const PageComponent = (await components[pageObj.component]()).default;
|
|
81
117
|
const layoutIds = layoutTree[pageObj.component] || [];
|
|
@@ -86,7 +122,7 @@ async function renderReactPage({ pageObj, assetTags, components, layoutTree, pre
|
|
|
86
122
|
element = React.createElement(RouterContext.Provider, { value: createSsrRouter(pageObj.url || "/", pageObj.params ?? {}) }, React.createElement(NavigationContext.Provider, { value: idleNavigationState() }, React.createElement(React.Suspense, { fallback: null }, React.createElement(SharedContext.Provider, { value: pageObj.shared || {} }, React.createElement(ParamsContext.Provider, { value: pageObj.params ?? {} }, React.createElement(ErrorsContext.Provider, { value: pageObj.errors || {} }, element))))));
|
|
87
123
|
const renderElement = pageObj.deferredKeys ? React.createElement(React.Fragment, null, element, React.createElement("template", { id: DEFERRED_SHELL_END_ID })) : element;
|
|
88
124
|
const appStream = await renderToReadableStream(renderElement, { onError(error) {
|
|
89
|
-
if (error ===
|
|
125
|
+
if (error === "Void deferred shell complete") return;
|
|
90
126
|
throw error;
|
|
91
127
|
} });
|
|
92
128
|
const renderedAppStream = pageObj.deferredKeys ? readDeferredShell(appStream) : appStream;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/react",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.4",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"react-dom": "^19.3.0",
|
|
44
44
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
|
|
45
45
|
"vite-plus": "0.3.1",
|
|
46
|
-
"void": "0.21.
|
|
46
|
+
"void": "0.21.4"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": "^19.0.0",
|
|
50
50
|
"react-dom": "^19.0.0",
|
|
51
51
|
"vite": "^8.0.0",
|
|
52
|
-
"void": "0.21.
|
|
52
|
+
"void": "0.21.4"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "vp pack",
|