@superblocksteam/sdk 1.14.2 → 2.0.3-next.46
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/.mocharc.json +7 -0
- package/.prettierrc +18 -0
- package/dist/cli-replacement/dev.d.mts +19 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -0
- package/dist/cli-replacement/dev.mjs +122 -0
- package/dist/cli-replacement/dev.mjs.map +1 -0
- package/dist/cli-replacement/init.d.ts +14 -0
- package/dist/cli-replacement/init.d.ts.map +1 -0
- package/dist/cli-replacement/init.js +26 -0
- package/dist/cli-replacement/init.js.map +1 -0
- package/dist/client.d.ts +31 -17
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +137 -155
- package/dist/client.js.map +1 -0
- package/dist/dbfs/client.d.ts +6 -0
- package/dist/dbfs/client.d.ts.map +1 -0
- package/dist/dbfs/client.js +117 -0
- package/dist/dbfs/client.js.map +1 -0
- package/dist/dbfs/local.d.ts +15 -0
- package/dist/dbfs/local.d.ts.map +1 -0
- package/dist/dbfs/local.js +126 -0
- package/dist/dbfs/local.js.map +1 -0
- package/dist/dev-utils/custom-build.d.mts +4 -0
- package/dist/dev-utils/custom-build.d.mts.map +1 -0
- package/dist/dev-utils/custom-build.mjs +99 -0
- package/dist/dev-utils/custom-build.mjs.map +1 -0
- package/dist/dev-utils/custom-config.d.mts +2 -0
- package/dist/dev-utils/custom-config.d.mts.map +1 -0
- package/dist/dev-utils/custom-config.mjs +57 -0
- package/dist/dev-utils/custom-config.mjs.map +1 -0
- package/dist/dev-utils/dev-logger.d.mts +8 -0
- package/dist/dev-utils/dev-logger.d.mts.map +1 -0
- package/dist/dev-utils/dev-logger.mjs +25 -0
- package/dist/dev-utils/dev-logger.mjs.map +1 -0
- package/dist/dev-utils/dev-server.d.mts +18 -0
- package/dist/dev-utils/dev-server.d.mts.map +1 -0
- package/dist/dev-utils/dev-server.mjs +265 -0
- package/dist/dev-utils/dev-server.mjs.map +1 -0
- package/dist/dev-utils/dev-tracer.d.ts +3 -0
- package/dist/dev-utils/dev-tracer.d.ts.map +1 -0
- package/dist/dev-utils/dev-tracer.js +28 -0
- package/dist/dev-utils/dev-tracer.js.map +1 -0
- package/dist/dev-utils/vite-plugin-dd-rum.d.mts +10 -0
- package/dist/dev-utils/vite-plugin-dd-rum.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-dd-rum.mjs +34 -0
- package/dist/dev-utils/vite-plugin-dd-rum.mjs.map +1 -0
- package/dist/dev-utils/vite-plugin-react-transform.d.mts +7 -0
- package/dist/dev-utils/vite-plugin-react-transform.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-react-transform.mjs +110 -0
- package/dist/dev-utils/vite-plugin-react-transform.mjs.map +1 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.d.mts +34 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.mjs +720 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.mjs.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -9
- package/dist/errors.js.map +1 -0
- package/dist/flag.d.ts +2 -2
- package/dist/flag.d.ts.map +1 -0
- package/dist/flag.js +5 -9
- package/dist/flag.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -20
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +42 -18
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +47 -43
- package/dist/sdk.js.map +1 -0
- package/dist/socket/handlers.d.ts +3 -128
- package/dist/socket/handlers.d.ts.map +1 -0
- package/dist/socket/handlers.js +7 -9
- package/dist/socket/handlers.js.map +1 -0
- package/dist/socket/index.d.ts +4 -3
- package/dist/socket/index.d.ts.map +1 -0
- package/dist/socket/index.js +12 -21
- package/dist/socket/index.js.map +1 -0
- package/dist/socket/signing.d.ts +3 -1
- package/dist/socket/signing.d.ts.map +1 -0
- package/dist/socket/signing.js +8 -17
- package/dist/socket/signing.js.map +1 -0
- package/dist/socket/socket.d.ts +3 -2
- package/dist/socket/socket.d.ts.map +1 -0
- package/dist/socket/socket.js +9 -19
- package/dist/socket/socket.js.map +1 -0
- package/dist/types/common.d.ts +2 -103
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +8 -24
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -20
- package/dist/types/index.js.map +1 -0
- package/dist/types/plugin.d.ts +1 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +3 -5
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/signing.d.ts +2 -1
- package/dist/types/signing.d.ts.map +1 -0
- package/dist/types/signing.js +2 -2
- package/dist/types/signing.js.map +1 -0
- package/dist/types/socket.d.ts +1 -0
- package/dist/types/socket.d.ts.map +1 -0
- package/dist/types/socket.js +2 -5
- package/dist/types/socket.js.map +1 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +14 -23
- package/dist/utils.js.map +1 -0
- package/dist/version-control.d.mts +59 -0
- package/dist/version-control.d.mts.map +1 -0
- package/dist/version-control.mjs +899 -0
- package/dist/version-control.mjs.map +1 -0
- package/eslint.config.js +85 -0
- package/package.json +72 -32
- package/src/cli-replacement/dev.mts +182 -0
- package/src/cli-replacement/init.ts +47 -0
- package/src/client.ts +114 -38
- package/src/dbfs/client.ts +162 -0
- package/src/dbfs/local.ts +163 -0
- package/src/dev-utils/custom-build.mts +113 -0
- package/src/dev-utils/custom-config.mts +66 -0
- package/src/dev-utils/dev-logger.mts +39 -0
- package/src/dev-utils/dev-server.mts +342 -0
- package/src/dev-utils/dev-tracer.ts +31 -0
- package/src/dev-utils/vite-plugin-dd-rum.mts +47 -0
- package/src/dev-utils/vite-plugin-react-transform.mts +130 -0
- package/src/dev-utils/vite-plugin-sb-cdn.mts +988 -0
- package/src/flag.ts +2 -3
- package/src/index.ts +119 -4
- package/src/sdk.ts +91 -17
- package/src/socket/handlers.ts +9 -147
- package/src/socket/index.ts +6 -9
- package/src/socket/signing.ts +7 -8
- package/src/socket/socket.ts +8 -9
- package/src/types/common.ts +2 -119
- package/src/types/index.ts +4 -4
- package/src/types/signing.ts +1 -1
- package/src/types/socket.ts +1 -1
- package/src/utils.ts +5 -6
- package/src/version-control.mts +1351 -0
- package/test/dev-utils/fixture/index.html +12 -0
- package/test/dev-utils/fixture/main.jsx +22 -0
- package/test/dev-utils/fixture/package-lock.json +25 -0
- package/test/dev-utils/fixture/package.json +9 -0
- package/test/dev-utils/vite-plugin-sb-cdn.test.mts +74 -0
- package/test/tsconfig.json +9 -0
- package/test/version-control.test.mts +1412 -0
- package/tsconfig.json +15 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/.eslintrc.json +0 -55
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import { commaLists, stripIndent } from "common-tags";
|
|
2
|
+
import baseDebug from "debug";
|
|
3
|
+
import { parse, init } from "es-module-lexer";
|
|
4
|
+
import prettier from "prettier";
|
|
5
|
+
import { createIdResolver, } from "vite";
|
|
6
|
+
import { reactTransformPlugin } from "./vite-plugin-react-transform.mjs";
|
|
7
|
+
// Create and extend the debug logger
|
|
8
|
+
const debug = baseDebug("sb:sdk:vite-plugin-import-map");
|
|
9
|
+
/**
|
|
10
|
+
* Injects content into HTML after the charset meta tag, or falls back to before </head>
|
|
11
|
+
*
|
|
12
|
+
* @param html - The original HTML content
|
|
13
|
+
* @param contentToInject - The content to inject
|
|
14
|
+
* @param debugMessage - Optional debug message to log
|
|
15
|
+
* @returns The modified HTML with content injected
|
|
16
|
+
*/
|
|
17
|
+
function injectAfterCharset(html, contentToInject, debugMessage) {
|
|
18
|
+
if (!contentToInject)
|
|
19
|
+
return html;
|
|
20
|
+
if (debugMessage) {
|
|
21
|
+
debug(debugMessage);
|
|
22
|
+
}
|
|
23
|
+
// Try to find the charset meta tag
|
|
24
|
+
const charsetRegex = /<meta[^>]*charset[^>]*>/i;
|
|
25
|
+
const charsetMatch = html.match(charsetRegex);
|
|
26
|
+
if (charsetMatch && charsetMatch.index !== undefined) {
|
|
27
|
+
// Calculate position right after the charset tag
|
|
28
|
+
const insertPosition = charsetMatch.index + charsetMatch[0].length;
|
|
29
|
+
// Insert content after the charset tag
|
|
30
|
+
const result = html.substring(0, insertPosition) +
|
|
31
|
+
contentToInject +
|
|
32
|
+
html.substring(insertPosition);
|
|
33
|
+
debug("Inserted content after charset meta tag");
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Fallback: insert before closing head tag if no charset tag is found
|
|
38
|
+
debug("No charset meta tag found, inserting content before </head>");
|
|
39
|
+
return html.replace("</head>", `${contentToInject}</head>`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Calculates a SHA-384 integrity hash for a resource
|
|
44
|
+
*
|
|
45
|
+
* @param content - The content to hash (string, ArrayBuffer, or Uint8Array)
|
|
46
|
+
* @returns The base64-encoded SHA-384 hash prefixed with 'sha384-'
|
|
47
|
+
*/
|
|
48
|
+
async function calculateIntegrity(content) {
|
|
49
|
+
// Safely handle different content types
|
|
50
|
+
let buffer;
|
|
51
|
+
if (content instanceof ArrayBuffer) {
|
|
52
|
+
buffer = content;
|
|
53
|
+
}
|
|
54
|
+
else if (content instanceof Uint8Array) {
|
|
55
|
+
buffer = content.buffer;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Handle string or any other type by converting to string first
|
|
59
|
+
buffer = new TextEncoder().encode(String(content));
|
|
60
|
+
}
|
|
61
|
+
// Calculate SHA-384 hash
|
|
62
|
+
const hashBuffer = await crypto.subtle.digest("SHA-384", buffer);
|
|
63
|
+
// Convert to base64
|
|
64
|
+
const hashBase64 = btoa(Array.from(new Uint8Array(hashBuffer))
|
|
65
|
+
.map((byte) => String.fromCharCode(byte))
|
|
66
|
+
.join(""));
|
|
67
|
+
return `sha384-${hashBase64}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Extracts import specifiers from JavaScript/TypeScript code, separating absolute and relative imports
|
|
71
|
+
*
|
|
72
|
+
* @param code - The source code to analyze
|
|
73
|
+
* @returns Object containing absolute and relative imports found in the code
|
|
74
|
+
*/
|
|
75
|
+
async function extractImportsFromCode(code) {
|
|
76
|
+
try {
|
|
77
|
+
// Initialize the lexer if it hasn't been initialized yet
|
|
78
|
+
await init;
|
|
79
|
+
const [imports] = parse(code);
|
|
80
|
+
const allImports = [];
|
|
81
|
+
for (const imp of imports) {
|
|
82
|
+
// d === -1 means it's a static import (not dynamic, not import.meta)
|
|
83
|
+
if (imp.d === -1) {
|
|
84
|
+
// For static imports, use the name field when available
|
|
85
|
+
// or extract from the source string
|
|
86
|
+
const importSpecifier = imp.n || code.substring(imp.s, imp.e);
|
|
87
|
+
// Clean up the import string if needed (remove quotes)
|
|
88
|
+
const cleanedImport = typeof importSpecifier === "string"
|
|
89
|
+
? importSpecifier.replace(/['"\`]/g, "")
|
|
90
|
+
: "";
|
|
91
|
+
// Only include if it's not an absolute URL
|
|
92
|
+
if (cleanedImport && !cleanedImport.startsWith("http")) {
|
|
93
|
+
allImports.push(cleanedImport);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
debug("Processed imports:", allImports);
|
|
98
|
+
// Separate into absolute and relative imports
|
|
99
|
+
const absolute = [];
|
|
100
|
+
const relative = [];
|
|
101
|
+
for (const imp of allImports) {
|
|
102
|
+
if (imp.startsWith("/") ||
|
|
103
|
+
imp.startsWith("./") ||
|
|
104
|
+
imp.startsWith("../")) {
|
|
105
|
+
relative.push(imp);
|
|
106
|
+
}
|
|
107
|
+
else if (!imp.startsWith("/")) {
|
|
108
|
+
// Skip absolute file paths
|
|
109
|
+
absolute.push(imp);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { absolute, relative };
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.error("Failed to parse imports:", err);
|
|
116
|
+
return { absolute: [], relative: [] };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Fetches a module and recursively analyzes all its imports
|
|
121
|
+
*
|
|
122
|
+
* @param baseUrl - The base URL for resolving relative imports
|
|
123
|
+
* @param url - The URL of the module to analyze
|
|
124
|
+
* @param debug - Whether to output debug information
|
|
125
|
+
* @param visited - Set of already visited URLs to prevent cycles
|
|
126
|
+
* @param visitedModules - Map to track all visited modules with their content
|
|
127
|
+
* @returns Object containing all dependencies, content, binary data, and visited modules
|
|
128
|
+
*/
|
|
129
|
+
async function analyzeModuleGraph(baseUrl, url, visited = new Set(), visitedModules = new Map()) {
|
|
130
|
+
// Avoid circular dependencies
|
|
131
|
+
if (visited.has(url)) {
|
|
132
|
+
return {
|
|
133
|
+
dependencies: new Set(),
|
|
134
|
+
content: "",
|
|
135
|
+
binary: new ArrayBuffer(0),
|
|
136
|
+
visitedModules,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
visited.add(url);
|
|
140
|
+
try {
|
|
141
|
+
debug(`Fetching module: ${url}`);
|
|
142
|
+
// First fetch as ArrayBuffer for efficiency
|
|
143
|
+
const response = await fetch(url);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
146
|
+
}
|
|
147
|
+
// Get binary data once
|
|
148
|
+
const binary = await response.arrayBuffer();
|
|
149
|
+
// Convert to text for import analysis
|
|
150
|
+
const decoder = new TextDecoder();
|
|
151
|
+
const code = decoder.decode(binary);
|
|
152
|
+
// Store this module in the visitedModules map
|
|
153
|
+
visitedModules.set(url, { content: code, binary });
|
|
154
|
+
// Extract both absolute and relative imports
|
|
155
|
+
const { absolute, relative } = await extractImportsFromCode(code);
|
|
156
|
+
debug(`Found in ${url}:\n Absolute: ${absolute.join(", ")}\n Relative: ${relative.join(", ")}`);
|
|
157
|
+
// Initialize with the direct absolute dependencies
|
|
158
|
+
const dependencies = new Set(absolute);
|
|
159
|
+
// Recursively process relative imports
|
|
160
|
+
for (const relativeImport of relative) {
|
|
161
|
+
// Resolve relative path against the current URL
|
|
162
|
+
const resolvedUrl = new URL(relativeImport, url).href;
|
|
163
|
+
debug(`Resolving relative import ${relativeImport} from ${url} to ${resolvedUrl}`);
|
|
164
|
+
// Recursively analyze this dependency
|
|
165
|
+
const { dependencies: nestedDeps } = await analyzeModuleGraph(baseUrl, resolvedUrl, visited, visitedModules);
|
|
166
|
+
// Add all nested dependencies to our set
|
|
167
|
+
for (const dep of nestedDeps) {
|
|
168
|
+
dependencies.add(dep);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { dependencies, content: code, binary, visitedModules };
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
console.error(`Error analyzing ${url}:`, err);
|
|
175
|
+
return {
|
|
176
|
+
dependencies: new Set(),
|
|
177
|
+
content: "",
|
|
178
|
+
binary: new ArrayBuffer(0),
|
|
179
|
+
visitedModules,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fetches a remote module and extracts all its imports, including following relative imports
|
|
185
|
+
*
|
|
186
|
+
* @param url - The URL of the module to analyze
|
|
187
|
+
* @param debug - Whether to output debug information
|
|
188
|
+
* @returns Object containing all found dependencies, content, binary data, and all visited modules
|
|
189
|
+
*/
|
|
190
|
+
async function analyzeRemoteModule(url) {
|
|
191
|
+
try {
|
|
192
|
+
debug(`Analyzing remote module: ${url}`);
|
|
193
|
+
const baseUrl = new URL(url).href;
|
|
194
|
+
const { dependencies, content, binary, visitedModules } = await analyzeModuleGraph(baseUrl, url);
|
|
195
|
+
// First analyze the main module's needed exports
|
|
196
|
+
const mainModuleNeededExports = await extractNeededExports(url, content);
|
|
197
|
+
// Start with the main module's exports and merge in exports from dependencies
|
|
198
|
+
const allNeededExports = new Map(mainModuleNeededExports);
|
|
199
|
+
// Recursively analyze all related modules to find their imports
|
|
200
|
+
// This is critical for handling transitive dependencies
|
|
201
|
+
let analyzedModules = 0;
|
|
202
|
+
for (const [moduleUrl, moduleData] of visitedModules.entries()) {
|
|
203
|
+
if (moduleUrl === url)
|
|
204
|
+
continue; // Skip the main module we already analyzed
|
|
205
|
+
analyzedModules++;
|
|
206
|
+
const moduleNeededExports = await extractNeededExports(moduleUrl, moduleData.content);
|
|
207
|
+
// Merge all needed exports into our map
|
|
208
|
+
for (const [importedModule, exportNames,] of moduleNeededExports.entries()) {
|
|
209
|
+
if (!allNeededExports.has(importedModule)) {
|
|
210
|
+
allNeededExports.set(importedModule, new Set());
|
|
211
|
+
}
|
|
212
|
+
// Add all export names from this module
|
|
213
|
+
for (const name of exportNames) {
|
|
214
|
+
allNeededExports.get(importedModule)?.add(name);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
debug(`Analyzed ${analyzedModules} related modules for ${url}\n` +
|
|
219
|
+
`Found ${dependencies.size} dependencies: ${Array.from(dependencies).join(", ")}`);
|
|
220
|
+
// Log all needed exports for debugging
|
|
221
|
+
if (allNeededExports.size > 0) {
|
|
222
|
+
debug(`Needed exports from dependencies:`);
|
|
223
|
+
for (const [module, exports] of allNeededExports.entries()) {
|
|
224
|
+
debug(` ${module}: ${[...exports].join(", ")}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
debug(`No dependency exports needed by ${url}`);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
dependencies: Array.from(dependencies),
|
|
232
|
+
content,
|
|
233
|
+
binary,
|
|
234
|
+
allModules: visitedModules,
|
|
235
|
+
neededExports: allNeededExports,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
console.error(`Error analyzing remote module ${url}:`, err);
|
|
240
|
+
return {
|
|
241
|
+
dependencies: [],
|
|
242
|
+
content: "",
|
|
243
|
+
binary: new ArrayBuffer(0),
|
|
244
|
+
allModules: new Map(),
|
|
245
|
+
neededExports: new Map(),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Extracts export specifiers used by a module and its dependencies
|
|
251
|
+
* @param content - The source code to analyze
|
|
252
|
+
* @returns Map of module names to their needed exports
|
|
253
|
+
*/
|
|
254
|
+
async function extractNeededExports(moduleName, content) {
|
|
255
|
+
try {
|
|
256
|
+
// Initialize the lexer if it hasn't been initialized yet
|
|
257
|
+
await init;
|
|
258
|
+
const result = new Map();
|
|
259
|
+
const [imports] = parse(content);
|
|
260
|
+
for (const imp of imports) {
|
|
261
|
+
// Static imports
|
|
262
|
+
if (imp.d === -1 && imp.n) {
|
|
263
|
+
// Get the imported module name
|
|
264
|
+
const importedModule = imp.n;
|
|
265
|
+
// Check for namespace imports: import * as X from 'module'
|
|
266
|
+
if (imp.ss === -1) {
|
|
267
|
+
// This is a namespace import, meaning we need all exports
|
|
268
|
+
if (!result.has(importedModule)) {
|
|
269
|
+
result.set(importedModule, new Set(["*"]));
|
|
270
|
+
}
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
// Extract the specific imported names for named imports
|
|
274
|
+
const importCode = content.substring(imp.ss, imp.se);
|
|
275
|
+
const namedImportMatch = importCode.match(/\{([^}]*)\}/);
|
|
276
|
+
if (namedImportMatch) {
|
|
277
|
+
const namedImports = namedImportMatch[1]
|
|
278
|
+
.split(",")
|
|
279
|
+
.map((name) => name.trim().split(" as ")[0].trim())
|
|
280
|
+
.filter(Boolean);
|
|
281
|
+
if (!result.has(importedModule)) {
|
|
282
|
+
result.set(importedModule, new Set());
|
|
283
|
+
}
|
|
284
|
+
for (const name of namedImports) {
|
|
285
|
+
result.get(importedModule)?.add(name);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Default import: import X from 'module'
|
|
290
|
+
if (!result.has(importedModule)) {
|
|
291
|
+
result.set(importedModule, new Set(["default"]));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
result.get(importedModule)?.add("default");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
debug(`Extracted needed exports for ${moduleName}: %O`, Object.fromEntries(Array.from(result.entries()).map(([key, value]) => [
|
|
300
|
+
key,
|
|
301
|
+
Array.from(value),
|
|
302
|
+
])));
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
console.error(`Error extracting needed exports for ${moduleName}:`, err);
|
|
307
|
+
return new Map();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Creates a Vite plugin that injects an import map into the HTML.
|
|
312
|
+
* It analyzes remote modules to find their imports and maps them to local Vite modules.
|
|
313
|
+
*
|
|
314
|
+
* Features:
|
|
315
|
+
* - Automatically discovers and maps imports in remote modules
|
|
316
|
+
* - Efficiently fetches binary data once and reuses it for multiple purposes
|
|
317
|
+
* - In production mode, adds integrity hashes and modulepreload directives
|
|
318
|
+
* - In development mode, only adds the import map for faster iteration
|
|
319
|
+
* - Ensures proper ordering of elements in head for correct module resolution
|
|
320
|
+
*
|
|
321
|
+
* @param options - The import map configuration options
|
|
322
|
+
* @returns A Vite plugin that injects the import map into HTML
|
|
323
|
+
*/
|
|
324
|
+
export async function superblocksCdnPlugin(options) {
|
|
325
|
+
const { imports: initialImportMap = {}, scopes: additionalScopes = {}, cssImports = {}, } = options;
|
|
326
|
+
let mode;
|
|
327
|
+
let config;
|
|
328
|
+
// Get the keys from the imports object to use as the ignored imports
|
|
329
|
+
const imports = Object.keys(initialImportMap);
|
|
330
|
+
// tracks imports of modules so we can map them to chunks later
|
|
331
|
+
const moduleToImportsMap = new Map();
|
|
332
|
+
// maps imports to chunks so we can resolve them in the import map
|
|
333
|
+
const importToChunkMap = new Map();
|
|
334
|
+
// tracks the dependencies of modules in the import map
|
|
335
|
+
const importMapModulesDependencies = new Map();
|
|
336
|
+
// tracks remote modules to preload with their content and integrity
|
|
337
|
+
const modulesToPreload = new Map();
|
|
338
|
+
// a map of imports required by CDN bundles, grouped by their import specifier
|
|
339
|
+
const cdnDependencies = new Map();
|
|
340
|
+
// a map tracking the chunks that CDN dependencies end up in, created in buildStart()
|
|
341
|
+
const cdnDependencyChunks = new Map();
|
|
342
|
+
// For each import, download the file and analyze its imports
|
|
343
|
+
for (const [, importUrl] of Object.entries(initialImportMap)) {
|
|
344
|
+
// Skip non-HTTP imports
|
|
345
|
+
if (!importUrl.startsWith("http"))
|
|
346
|
+
continue;
|
|
347
|
+
// Add the main module to preload
|
|
348
|
+
modulesToPreload.set(importUrl, { url: importUrl });
|
|
349
|
+
// Extract the base URL (e.g., http://localhost:4173/)
|
|
350
|
+
const urlObj = new URL(importUrl);
|
|
351
|
+
const baseUrl = `${urlObj.protocol}//${urlObj.host}/`;
|
|
352
|
+
debug(`Extracted base URL from %s: %s`, importUrl, baseUrl);
|
|
353
|
+
// Analyze the remote module to find all dependencies (including recursive ones)
|
|
354
|
+
const { dependencies, content, binary, allModules, neededExports } = await analyzeRemoteModule(importUrl);
|
|
355
|
+
// Store the list of dependencies for this import
|
|
356
|
+
importMapModulesDependencies.set(baseUrl, [
|
|
357
|
+
...(importMapModulesDependencies.get(baseUrl) ?? []),
|
|
358
|
+
...dependencies,
|
|
359
|
+
]);
|
|
360
|
+
// Store the needed exports for each dependency
|
|
361
|
+
for (const [dependency, exportNames] of neededExports) {
|
|
362
|
+
if (dependency.startsWith("/") ||
|
|
363
|
+
dependency.startsWith("./") ||
|
|
364
|
+
dependency.startsWith("../")) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (!cdnDependencies.has(dependency)) {
|
|
368
|
+
cdnDependencies.set(dependency, new Set());
|
|
369
|
+
}
|
|
370
|
+
// Add all needed exports from this CDN module
|
|
371
|
+
for (const exportName of exportNames) {
|
|
372
|
+
cdnDependencies.get(dependency)?.add(exportName);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Add module info to preload map for main module
|
|
376
|
+
const modulePreloadData = modulesToPreload.get(importUrl);
|
|
377
|
+
if (modulePreloadData) {
|
|
378
|
+
modulePreloadData.content = content;
|
|
379
|
+
modulePreloadData.integrity = await calculateIntegrity(binary);
|
|
380
|
+
}
|
|
381
|
+
// Process all discovered modules and add them to the preload list
|
|
382
|
+
for (const [moduleUrl, moduleData] of allModules) {
|
|
383
|
+
// Don't add the main module again
|
|
384
|
+
if (moduleUrl === importUrl)
|
|
385
|
+
continue;
|
|
386
|
+
// Only add modules from the same origin (relative modules)
|
|
387
|
+
if (moduleUrl.startsWith(baseUrl)) {
|
|
388
|
+
// Add the module to preload if not already added
|
|
389
|
+
if (!modulesToPreload.has(moduleUrl)) {
|
|
390
|
+
// Calculate the integrity hash
|
|
391
|
+
const integrity = await calculateIntegrity(moduleData.binary);
|
|
392
|
+
modulesToPreload.set(moduleUrl, {
|
|
393
|
+
url: moduleUrl,
|
|
394
|
+
content: moduleData.content,
|
|
395
|
+
integrity,
|
|
396
|
+
});
|
|
397
|
+
debug(`Added relative module for preload: %s with integrity %s`, moduleUrl, integrity);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return [
|
|
403
|
+
reactTransformPlugin(),
|
|
404
|
+
/**
|
|
405
|
+
* When a CDN module imports symbols from a local package (e.g., h, Fragment
|
|
406
|
+
* from preact), Vite/Rollup may tree-shake these exports if they're not used locally.
|
|
407
|
+
* This causes runtime errors when the CDN module tries to use these missing exports.
|
|
408
|
+
*
|
|
409
|
+
* This plugin creates virtual entry chunks that import and re-export exactly
|
|
410
|
+
* the symbols needed by CDN modules. By using emitFile with preserveSignature: "strict",
|
|
411
|
+
* we ensure these specific exports are preserved without modifying the original chunks.
|
|
412
|
+
*/
|
|
413
|
+
{
|
|
414
|
+
name: "preserve-cdn-dependency-imports",
|
|
415
|
+
// When a module is parsed that matches one of the dependencies of our CDN bundle,
|
|
416
|
+
// create a virtual chunk for it to ensure its exports remain available
|
|
417
|
+
moduleParsed(moduleInfo) {
|
|
418
|
+
const match = [...cdnDependencyChunks.entries()].find(([, id]) => id === moduleInfo.id);
|
|
419
|
+
if (!match)
|
|
420
|
+
return;
|
|
421
|
+
const [specifier] = match;
|
|
422
|
+
const exportList = cdnDependencies.get(specifier);
|
|
423
|
+
debug(`Emitting virtual chunk for ${specifier} with exports: ${exportList ? [...exportList].join(", ") : "none"}`);
|
|
424
|
+
// create a virtual chunk that Rollup treats as an entry point,
|
|
425
|
+
// forcing exports to be preserved
|
|
426
|
+
this.emitFile({
|
|
427
|
+
type: "chunk",
|
|
428
|
+
id: `virtual:preserve-exports/${specifier}`,
|
|
429
|
+
// this option ensures that the modules' import/export signature remains unmodified
|
|
430
|
+
preserveSignature: "strict",
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
resolveId(id) {
|
|
434
|
+
if (id.startsWith("virtual:preserve-exports")) {
|
|
435
|
+
debug(`Resolving virtual export preservation module: ${id}`);
|
|
436
|
+
// return the id unchanged to handle it in the load hook
|
|
437
|
+
return id;
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
},
|
|
441
|
+
// Generate the content for our virtual export preservation modules
|
|
442
|
+
load(id) {
|
|
443
|
+
if (id.startsWith("virtual:preserve-exports/")) {
|
|
444
|
+
const parts = id.split("/");
|
|
445
|
+
const dependency = parts.slice(1).join("/");
|
|
446
|
+
const exports = cdnDependencies.get(dependency);
|
|
447
|
+
if (!exports || exports.size === 0) {
|
|
448
|
+
debug(`No exports to preserve for ${dependency}`);
|
|
449
|
+
return "";
|
|
450
|
+
}
|
|
451
|
+
const exportsList = Array.from(exports);
|
|
452
|
+
debug(`Generating preservation module for ${dependency} with exports: ${exportsList.join(", ")}`);
|
|
453
|
+
return stripIndent `
|
|
454
|
+
import { ${commaLists `${exportsList}`} } from "${dependency}";
|
|
455
|
+
export { ${commaLists `${exportsList}`} };
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "vite-plugin-superblocks-cdn",
|
|
463
|
+
// run before Vite's module resolution
|
|
464
|
+
enforce: "pre",
|
|
465
|
+
/**
|
|
466
|
+
* Configure Vite to properly handle CDN modules
|
|
467
|
+
* - Exclude CDN modules from optimization
|
|
468
|
+
* - Configure build for proper chunking and export preservation
|
|
469
|
+
*/
|
|
470
|
+
config(_config, { mode: configuredMode }) {
|
|
471
|
+
mode = configuredMode;
|
|
472
|
+
debug(`Plugin initializing in ${mode} mode`);
|
|
473
|
+
// Exclude CDN imports from pre-bundling
|
|
474
|
+
// This prevents Vite from trying to resolve and optimize modules
|
|
475
|
+
// that will actually be loaded from CDN at runtime
|
|
476
|
+
const optimizeDepsExclude = [
|
|
477
|
+
...(_config.optimizeDeps?.exclude || []),
|
|
478
|
+
...Object.keys(initialImportMap),
|
|
479
|
+
];
|
|
480
|
+
debug(`Excluding CDN modules from optimization: ${optimizeDepsExclude.join(", ")}`);
|
|
481
|
+
return {
|
|
482
|
+
..._config,
|
|
483
|
+
optimizeDeps: {
|
|
484
|
+
..._config.optimizeDeps,
|
|
485
|
+
exclude: optimizeDepsExclude,
|
|
486
|
+
},
|
|
487
|
+
build: {
|
|
488
|
+
..._config.build,
|
|
489
|
+
rollupOptions: {
|
|
490
|
+
..._config.build?.rollupOptions,
|
|
491
|
+
output: {
|
|
492
|
+
..._config.build?.rollupOptions?.output,
|
|
493
|
+
// Group all node_modules into a vendor chunk
|
|
494
|
+
manualChunks: (source) => source.includes("node_modules") ? "vendor" : null,
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
},
|
|
500
|
+
configResolved(_config) {
|
|
501
|
+
config = _config;
|
|
502
|
+
},
|
|
503
|
+
async buildStart() {
|
|
504
|
+
// create an internal resolver to avoid calling this.resolve(),
|
|
505
|
+
// which would trigger most plugin hooks.
|
|
506
|
+
const resolver = createIdResolver(config);
|
|
507
|
+
// resolve all CDN dependencies to their local modules
|
|
508
|
+
for (const [dep] of cdnDependencies) {
|
|
509
|
+
if (cdnDependencyChunks.has(dep))
|
|
510
|
+
continue;
|
|
511
|
+
const resolved = await resolver(this.environment, dep);
|
|
512
|
+
if (!resolved)
|
|
513
|
+
continue;
|
|
514
|
+
cdnDependencyChunks.set(dep, resolved);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
resolveId(id, importer) {
|
|
518
|
+
// Build a graph of module relationships for later chunk resolution
|
|
519
|
+
// This is needed for mapping imports in the scopes section of the import map
|
|
520
|
+
if (importer) {
|
|
521
|
+
if (!moduleToImportsMap.has(importer)) {
|
|
522
|
+
moduleToImportsMap.set(importer, new Set());
|
|
523
|
+
}
|
|
524
|
+
moduleToImportsMap.get(importer)?.add(id);
|
|
525
|
+
}
|
|
526
|
+
// Externalize modules specified in the import map
|
|
527
|
+
// The cdn: prefix ensures they're properly resolved to the CDN URL
|
|
528
|
+
if (imports.includes(id)) {
|
|
529
|
+
debug(`Externalizing module for CDN resolution: ${id}`);
|
|
530
|
+
return { id: `cdn:${id}`, external: true };
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
},
|
|
534
|
+
/**
|
|
535
|
+
* Handle loading of externalized CDN modules
|
|
536
|
+
* These modules will be loaded from CDN at runtime, so we return empty content
|
|
537
|
+
*/
|
|
538
|
+
async load(id) {
|
|
539
|
+
if (id.startsWith("cdn:")) {
|
|
540
|
+
debug(`Providing empty module for CDN import: ${id}`);
|
|
541
|
+
return "";
|
|
542
|
+
}
|
|
543
|
+
// returning null here will cause Vite to continue with default behavior
|
|
544
|
+
return null;
|
|
545
|
+
},
|
|
546
|
+
// track which modules are bundled in which runtime chunk
|
|
547
|
+
generateBundle(_, bundle) {
|
|
548
|
+
const moduleToChunkMap = new Map();
|
|
549
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
550
|
+
if (chunk.type === "chunk" && chunk.modules) {
|
|
551
|
+
for (const moduleId in chunk.modules) {
|
|
552
|
+
moduleToChunkMap.set(moduleId, fileName);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Map imports to chunks using our tracking data
|
|
557
|
+
for (const [moduleId, importIds] of moduleToImportsMap) {
|
|
558
|
+
const chunkName = moduleToChunkMap.get(moduleId);
|
|
559
|
+
if (chunkName) {
|
|
560
|
+
for (const importId of importIds) {
|
|
561
|
+
importToChunkMap.set(importId, chunkName);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
async transformIndexHtml(html, { server }) {
|
|
567
|
+
const isDevMode = !!server;
|
|
568
|
+
// Create the base import map with explicit ordering for proper resolution
|
|
569
|
+
// Important: Import maps require an "imports" object at the top level
|
|
570
|
+
const importMap = {
|
|
571
|
+
imports: {
|
|
572
|
+
...Object.fromEntries(Object.entries(initialImportMap).map(([module, url]) => [
|
|
573
|
+
`${isDevMode ? "/@id/" : ""}cdn:${module}`,
|
|
574
|
+
url,
|
|
575
|
+
])),
|
|
576
|
+
},
|
|
577
|
+
// Scopes apply to specific URL prefixes for more granular control
|
|
578
|
+
scopes: { ...additionalScopes },
|
|
579
|
+
};
|
|
580
|
+
for (const [baseUrl, dependencies] of importMapModulesDependencies) {
|
|
581
|
+
if (dependencies.length === 0)
|
|
582
|
+
continue;
|
|
583
|
+
// Create a scope for this remote URL if we found dependencies
|
|
584
|
+
importMap.scopes = importMap.scopes || {};
|
|
585
|
+
importMap.scopes[baseUrl] = importMap.scopes[baseUrl] || {};
|
|
586
|
+
debug(`Creating scope for %s with %s dependencies`, baseUrl, dependencies.length);
|
|
587
|
+
const base = config.base ?? "/";
|
|
588
|
+
for (const moduleName of dependencies) {
|
|
589
|
+
let dependencyChunkUrl = "";
|
|
590
|
+
try {
|
|
591
|
+
if (isDevMode) {
|
|
592
|
+
// this.resolve is not available in this plugin hook
|
|
593
|
+
const mod = await server.moduleGraph.ensureEntryFromUrl(moduleName);
|
|
594
|
+
if (mod) {
|
|
595
|
+
// Need to get a properly resolved path for the browser
|
|
596
|
+
// For import maps to work correctly, the URL must be resolvable by the browser
|
|
597
|
+
// First try to get the resolved URL from Vite
|
|
598
|
+
let resolvedChunkUrl = "";
|
|
599
|
+
// Use the module's resolved URL or its resolved ID
|
|
600
|
+
if (mod.id) {
|
|
601
|
+
// mod.id contains the full absolute path to the file, change to relative
|
|
602
|
+
resolvedChunkUrl = mod.id.replace(server.config.root, "");
|
|
603
|
+
}
|
|
604
|
+
// Get the asset URL as it would be served to the browser
|
|
605
|
+
// For Vite to correctly resolve this module in the browser
|
|
606
|
+
if (resolvedChunkUrl.startsWith("/")) {
|
|
607
|
+
// For paths starting with /, they're already root-relative
|
|
608
|
+
// Make sure we don't duplicate the base path
|
|
609
|
+
dependencyChunkUrl = resolvedChunkUrl.startsWith(base)
|
|
610
|
+
? resolvedChunkUrl
|
|
611
|
+
: `${base}${resolvedChunkUrl.slice(1)}`;
|
|
612
|
+
}
|
|
613
|
+
else if (resolvedChunkUrl.startsWith("http")) {
|
|
614
|
+
// For absolute URLs, use them directly
|
|
615
|
+
dependencyChunkUrl = resolvedChunkUrl;
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
// For other paths, prepend the base path
|
|
619
|
+
dependencyChunkUrl = `${base}${resolvedChunkUrl}`;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
// Production mode approach
|
|
625
|
+
const chunkPath = importToChunkMap.get(moduleName);
|
|
626
|
+
if (chunkPath) {
|
|
627
|
+
dependencyChunkUrl = `${base}${chunkPath}`;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (dependencyChunkUrl === "") {
|
|
631
|
+
throw new Error("No module found for " + moduleName);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
console.warn(`Failed to resolve module ${moduleName}: %O`, error);
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
if (dependencyChunkUrl) {
|
|
639
|
+
debug(`Resolved %s to %s`, moduleName, dependencyChunkUrl);
|
|
640
|
+
// Store in the import map scope
|
|
641
|
+
importMap.scopes[baseUrl][moduleName] = dependencyChunkUrl;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const importMapJson = JSON.stringify(importMap, null, 2);
|
|
647
|
+
debug(`Final generated import map:\n${importMapJson}`);
|
|
648
|
+
const script = `<script type="importmap">${importMapJson}</script>`;
|
|
649
|
+
// Generate preload links with integrity when available
|
|
650
|
+
const preloadLinks = Array.from(modulesToPreload.values())
|
|
651
|
+
.map((module) => {
|
|
652
|
+
const integrityAttr = module.integrity
|
|
653
|
+
? ` integrity="${module.integrity}" crossorigin="anonymous"`
|
|
654
|
+
: "";
|
|
655
|
+
return `<link rel="modulepreload" href="${module.url}"${integrityAttr}>`;
|
|
656
|
+
})
|
|
657
|
+
.join("\n");
|
|
658
|
+
// Import map must come before preload links for proper module resolution
|
|
659
|
+
const combinedContent = preloadLinks
|
|
660
|
+
? `${script}\n${preloadLinks}`
|
|
661
|
+
: script;
|
|
662
|
+
// Use the utility function to inject content after charset meta tag
|
|
663
|
+
const modifiedHtml = injectAfterCharset(html, combinedContent, "Injecting import map and preload links after charset meta tag");
|
|
664
|
+
// Format the HTML and return
|
|
665
|
+
return await prettier.format(modifiedHtml, {
|
|
666
|
+
parser: "html",
|
|
667
|
+
embeddedLanguageFormatting: "auto",
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
/**
|
|
672
|
+
* Injects CDN stylesheets into the HTML. CSS files are not supported in import maps,
|
|
673
|
+
* which is why we need a separate solution for it.
|
|
674
|
+
*/
|
|
675
|
+
{
|
|
676
|
+
name: "inject-cdn-stylesheets",
|
|
677
|
+
transform(code) {
|
|
678
|
+
// Create a regex pattern for each CSS import to remove
|
|
679
|
+
// This allows for multiple CSS files to be imported
|
|
680
|
+
const cssImportPatterns = Object.keys(cssImports).map((key) => {
|
|
681
|
+
// Create a regex that matches imports for this key
|
|
682
|
+
// Escaping slashes and dots in the import path
|
|
683
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
684
|
+
return new RegExp(`import ["'](\.\.\/)*(${escapedKey})["'];?`, "g");
|
|
685
|
+
});
|
|
686
|
+
// Apply all patterns to the code
|
|
687
|
+
let transformedCode = code;
|
|
688
|
+
for (const pattern of cssImportPatterns) {
|
|
689
|
+
transformedCode = transformedCode.replace(pattern, "");
|
|
690
|
+
}
|
|
691
|
+
if (transformedCode !== code) {
|
|
692
|
+
debug("Removed CSS imports from code");
|
|
693
|
+
return transformedCode;
|
|
694
|
+
}
|
|
695
|
+
return code;
|
|
696
|
+
},
|
|
697
|
+
async transformIndexHtml(html) {
|
|
698
|
+
// Get cssImports from options, with a default empty object
|
|
699
|
+
const cssImportsMap = options.cssImports || {};
|
|
700
|
+
// Generate link tags for each CSS import
|
|
701
|
+
const cssLinks = Object.values(cssImportsMap)
|
|
702
|
+
.map((url) => `<link rel="stylesheet" href="${url}">\n`)
|
|
703
|
+
.join("");
|
|
704
|
+
if (!cssLinks) {
|
|
705
|
+
return await prettier.format(html, {
|
|
706
|
+
parser: "html",
|
|
707
|
+
embeddedLanguageFormatting: "auto",
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
// Use the shared utility function to inject CSS links
|
|
711
|
+
const modifiedHtml = injectAfterCharset(html, cssLinks, `Injecting CSS links: ${cssLinks}`);
|
|
712
|
+
return await prettier.format(modifiedHtml, {
|
|
713
|
+
parser: "html",
|
|
714
|
+
embeddedLanguageFormatting: "auto",
|
|
715
|
+
});
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
//# sourceMappingURL=vite-plugin-sb-cdn.mjs.map
|