@deot/docs-playground 1.1.2 → 1.1.3
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/README.md +2 -0
- package/dist/cdn-Czve5_9b.cjs +94 -0
- package/dist/cdn-DEvdGJu2.js +53 -0
- package/dist/compile-Bw7v7QE-.js +333 -0
- package/dist/compile-DSMuM8zl.cjs +333 -0
- package/dist/files-C5UMzYxA.js +178 -0
- package/dist/files-D5LNOtjp.cjs +192 -0
- package/dist/index.cjs +195 -1029
- package/dist/index.d.ts +4 -2
- package/dist/index.js +152 -965
- package/dist/index.style.css +1 -1
- package/dist/linker-B--sjERT.cjs +167 -0
- package/dist/linker-JHdz37DK.js +166 -0
- package/dist/local-sandbox-B8IJ5jWb.js +431 -0
- package/dist/local-sandbox-DnkKrBhf.cjs +428 -0
- package/dist/modules-CInmL-8h.cjs +217 -0
- package/dist/modules-DlLJF_vd.js +211 -0
- package/dist/remote-CKMzn1J3.cjs +106 -0
- package/dist/remote-DfXAsO9n.js +106 -0
- package/dist/rolldown-runtime-C0BPl7ul.cjs +43 -0
- package/dist/script-5Xzzx7u5.cjs +57 -0
- package/dist/script-xKUjE8pf.js +46 -0
- package/dist/scss-Co2it4vB.js +215 -0
- package/dist/scss-DaIqB_qT.cjs +233 -0
- package/dist/store-BF7z-oaO.cjs +964 -0
- package/dist/store-D8vwi-HV.js +767 -0
- package/package.json +20 -9
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const require_scss = require("./scss-DaIqB_qT.cjs");
|
|
2
|
+
const require_files = require("./files-D5LNOtjp.cjs");
|
|
3
|
+
const require_script = require("./script-5Xzzx7u5.cjs");
|
|
4
|
+
const require_local_sandbox = require("./local-sandbox-DnkKrBhf.cjs");
|
|
5
|
+
let sass = require("sass");
|
|
6
|
+
//#region packages/playground/src/core/runtime/local/compile/scss.ts
|
|
7
|
+
/**
|
|
8
|
+
* 直接渲染使用已安装的 sass,importer 与 iframe 预览共用,不注入额外 BEM。
|
|
9
|
+
*/
|
|
10
|
+
var compiler = { compileString(source, options) {
|
|
11
|
+
return { css: (0, sass.compileString)(source, options).css };
|
|
12
|
+
} };
|
|
13
|
+
var toScssFiles = (files) => {
|
|
14
|
+
const map = {};
|
|
15
|
+
Object.entries(files || {}).forEach(([filename, code]) => {
|
|
16
|
+
const normalized = filename.replace(/\\/gu, "/");
|
|
17
|
+
map[normalized] = code;
|
|
18
|
+
map[normalized.replace(/^\/+/u, "")] = code;
|
|
19
|
+
});
|
|
20
|
+
return map;
|
|
21
|
+
};
|
|
22
|
+
var compileLocalScss = (source, filename, files, lang) => {
|
|
23
|
+
try {
|
|
24
|
+
return require_scss.compileScssSource(source, filename.replace(/^\/+/u, ""), compiler, toScssFiles(files), lang);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
/* istanbul ignore next -- Sass 失败时 message 总是字符串 */
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
throw new Error(`[Playground] SCSS 编译失败 ${filename}: ${message}`, { cause: error });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region packages/playground/src/core/runtime/local/compile/sfc.ts
|
|
33
|
+
var compilerPromise = null;
|
|
34
|
+
/**
|
|
35
|
+
* 只加载浏览器构建,避免 `@vue/compiler-sfc` 的 Node 主入口碰文件系统。
|
|
36
|
+
* @returns 浏览器版 compiler-sfc。
|
|
37
|
+
*/
|
|
38
|
+
var loadCompiler = () => {
|
|
39
|
+
compilerPromise ??= import("@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js").then((mod) => {
|
|
40
|
+
return mod.default || mod;
|
|
41
|
+
});
|
|
42
|
+
return compilerPromise;
|
|
43
|
+
};
|
|
44
|
+
var hashId = (filename) => {
|
|
45
|
+
let hash = 0;
|
|
46
|
+
for (let i = 0; i < filename.length; i += 1) {
|
|
47
|
+
hash = (hash << 5) - hash + filename.charCodeAt(i);
|
|
48
|
+
hash |= 0;
|
|
49
|
+
}
|
|
50
|
+
return Math.abs(hash).toString(36).slice(0, 8);
|
|
51
|
+
};
|
|
52
|
+
var messageOf$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
53
|
+
var isScssLang = (lang) => lang === "scss" || lang === "sass";
|
|
54
|
+
var isSupportedStyleLang = (lang) => !lang || lang === "css" || isScssLang(lang);
|
|
55
|
+
var compileSfc = async (code, filename, files = {}) => {
|
|
56
|
+
const compiler = await loadCompiler();
|
|
57
|
+
const errors = [];
|
|
58
|
+
const id = hashId(filename);
|
|
59
|
+
const { descriptor, errors: parseErrors } = compiler.parse(code, {
|
|
60
|
+
filename,
|
|
61
|
+
sourceMap: false
|
|
62
|
+
});
|
|
63
|
+
parseErrors.forEach((error) => {
|
|
64
|
+
errors.push(messageOf$1(error));
|
|
65
|
+
});
|
|
66
|
+
if (errors.length) return {
|
|
67
|
+
js: "export default {}",
|
|
68
|
+
css: "",
|
|
69
|
+
errors
|
|
70
|
+
};
|
|
71
|
+
const srcBlock = [
|
|
72
|
+
descriptor.script,
|
|
73
|
+
descriptor.scriptSetup,
|
|
74
|
+
descriptor.template,
|
|
75
|
+
...descriptor.styles
|
|
76
|
+
].find((block) => block?.src);
|
|
77
|
+
if (srcBlock) {
|
|
78
|
+
errors.push(`[Playground] 不支持 <${srcBlock.type} src>,请把源码放进 files。文件: ${filename}`);
|
|
79
|
+
return {
|
|
80
|
+
js: "export default {}",
|
|
81
|
+
css: "",
|
|
82
|
+
errors
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const hasScript = !!(descriptor.script || descriptor.scriptSetup);
|
|
86
|
+
const isTS = !!(descriptor.script?.lang?.startsWith("ts") || descriptor.scriptSetup?.lang?.startsWith("ts"));
|
|
87
|
+
let clientCode;
|
|
88
|
+
let bindings;
|
|
89
|
+
if (hasScript) try {
|
|
90
|
+
const scriptResult = compiler.compileScript(descriptor, {
|
|
91
|
+
id,
|
|
92
|
+
inlineTemplate: true,
|
|
93
|
+
genDefaultAs: "__sfc__",
|
|
94
|
+
templateOptions: {
|
|
95
|
+
filename,
|
|
96
|
+
id,
|
|
97
|
+
scoped: descriptor.styles.some((style) => style.scoped)
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
clientCode = scriptResult.content;
|
|
101
|
+
bindings = scriptResult.bindings;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
errors.push(`[Playground] compileScript ${filename}: ${messageOf$1(error)}`);
|
|
104
|
+
return {
|
|
105
|
+
js: "export default {}",
|
|
106
|
+
css: "",
|
|
107
|
+
errors
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else clientCode = "const __sfc__ = {};";
|
|
111
|
+
const templateInlined = !!(descriptor.scriptSetup && hasScript);
|
|
112
|
+
if (descriptor.template && !templateInlined) try {
|
|
113
|
+
const templateResult = compiler.compileTemplate({
|
|
114
|
+
id,
|
|
115
|
+
filename,
|
|
116
|
+
source: descriptor.template.content,
|
|
117
|
+
scoped: descriptor.styles.some((style) => style.scoped),
|
|
118
|
+
compilerOptions: {
|
|
119
|
+
bindingMetadata: bindings,
|
|
120
|
+
isTS
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (templateResult.errors?.length) templateResult.errors.forEach((error) => {
|
|
124
|
+
errors.push(messageOf$1(error));
|
|
125
|
+
});
|
|
126
|
+
else {
|
|
127
|
+
clientCode += `\n${templateResult.code}`;
|
|
128
|
+
clientCode += "\n__sfc__.render = typeof render === \"function\" ? render : exports.render;";
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
errors.push(`[Playground] compileTemplate ${filename}: ${messageOf$1(error)}`);
|
|
132
|
+
}
|
|
133
|
+
if (descriptor.styles.some((style) => style.scoped)) clientCode += `\n__sfc__.__scopeId = ${JSON.stringify(`data-v-${id}`)};`;
|
|
134
|
+
clientCode += `\n__sfc__.__file = ${JSON.stringify(filename)};`;
|
|
135
|
+
clientCode += "\nexport default __sfc__;";
|
|
136
|
+
let css = "";
|
|
137
|
+
for (const style of descriptor.styles) {
|
|
138
|
+
if (!isSupportedStyleLang(style.lang)) {
|
|
139
|
+
errors.push(`[Playground] <style lang="${style.lang}"> 暂不支持(仅 CSS / SCSS / Sass)。文件: ${filename}`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
let source = style.content;
|
|
143
|
+
if (isScssLang(style.lang)) try {
|
|
144
|
+
source = compileLocalScss(source, filename, files, style.lang);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
errors.push(messageOf$1(error));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const styleResult = await compiler.compileStyleAsync({
|
|
151
|
+
id: style.scoped ? `data-v-${id}` : id,
|
|
152
|
+
filename,
|
|
153
|
+
source,
|
|
154
|
+
scoped: !!style.scoped
|
|
155
|
+
});
|
|
156
|
+
if (styleResult.errors?.length) styleResult.errors.forEach((error) => {
|
|
157
|
+
errors.push(messageOf$1(error));
|
|
158
|
+
});
|
|
159
|
+
else css += `${styleResult.code}\n`;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
/* istanbul ignore next -- 样式编译失败会走 errors 数组,这里只兜底抛错 */
|
|
162
|
+
errors.push(`[Playground] compileStyle ${filename}: ${messageOf$1(error)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
js: clientCode,
|
|
167
|
+
css,
|
|
168
|
+
errors
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region packages/playground/src/core/runtime/local/compile/index.ts
|
|
173
|
+
var JSON_IDENT = /^[$A-Z_][0-9A-Z_$]*$/iu;
|
|
174
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
175
|
+
/**
|
|
176
|
+
* Vite 语义:default 是整份对象,合法标识符键再挂成具名导出。
|
|
177
|
+
* @param parsed JSON.parse 的结果。
|
|
178
|
+
* @param filename 当前虚拟文件名,写入 sourceURL。
|
|
179
|
+
* @returns 可执行的 CJS 文本。
|
|
180
|
+
*/
|
|
181
|
+
var transformJson = (parsed, filename) => {
|
|
182
|
+
const lines = [`exports.default = ${JSON.stringify(parsed)};`];
|
|
183
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) Object.keys(parsed).forEach((key) => {
|
|
184
|
+
if (!JSON_IDENT.test(key) || key === "__esModule" || key === "default") return;
|
|
185
|
+
lines.push(`exports[${JSON.stringify(key)}] = exports.default[${JSON.stringify(key)}];`);
|
|
186
|
+
});
|
|
187
|
+
lines.push(`//# sourceURL=playground://${filename}`);
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
};
|
|
190
|
+
var compileOne = async (filename, code, files) => {
|
|
191
|
+
const ext = require_files.getExtension(filename);
|
|
192
|
+
const errors = [];
|
|
193
|
+
if (ext === ".vue") {
|
|
194
|
+
if (/lang\s*=\s*["']jsx["']/iu.test(code) || /lang\s*=\s*["']tsx["']/iu.test(code)) {
|
|
195
|
+
errors.push(`[Playground] JSX/TSX 暂不支持: ${filename}`);
|
|
196
|
+
return {
|
|
197
|
+
filename,
|
|
198
|
+
js: "exports.default = {}",
|
|
199
|
+
css: "",
|
|
200
|
+
errors
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const result = await compileSfc(code, filename, files);
|
|
204
|
+
errors.push(...result.errors);
|
|
205
|
+
return {
|
|
206
|
+
filename,
|
|
207
|
+
js: require_script.transformScript(result.js, filename, { typescript: true }),
|
|
208
|
+
css: result.css,
|
|
209
|
+
errors
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (ext === ".css") return {
|
|
213
|
+
filename,
|
|
214
|
+
js: `exports.default = {};\n//# sourceURL=playground://${filename}`,
|
|
215
|
+
css: code,
|
|
216
|
+
errors
|
|
217
|
+
};
|
|
218
|
+
if (ext === ".scss" || ext === ".sass") try {
|
|
219
|
+
const css = compileLocalScss(code, filename, files);
|
|
220
|
+
return {
|
|
221
|
+
filename,
|
|
222
|
+
js: `exports.default = {};\n//# sourceURL=playground://${filename}`,
|
|
223
|
+
css,
|
|
224
|
+
errors
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
errors.push(messageOf(error));
|
|
228
|
+
return {
|
|
229
|
+
filename,
|
|
230
|
+
js: "exports.default = {};",
|
|
231
|
+
css: "",
|
|
232
|
+
errors
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (ext === ".json") try {
|
|
236
|
+
return {
|
|
237
|
+
filename,
|
|
238
|
+
js: transformJson(JSON.parse(code), filename),
|
|
239
|
+
css: "",
|
|
240
|
+
errors
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
errors.push(`[Playground] JSON 解析失败 ${filename}: ${messageOf(error)}`);
|
|
244
|
+
return {
|
|
245
|
+
filename,
|
|
246
|
+
js: "exports.default = {};",
|
|
247
|
+
css: "",
|
|
248
|
+
errors
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (ext === ".js" || ext === ".ts" || ext === ".jsx" || ext === ".tsx") {
|
|
252
|
+
if (ext === ".jsx" || ext === ".tsx") {
|
|
253
|
+
errors.push(`[Playground] JSX/TSX 暂不支持: ${filename}`);
|
|
254
|
+
return {
|
|
255
|
+
filename,
|
|
256
|
+
js: "exports.default = {};",
|
|
257
|
+
css: "",
|
|
258
|
+
errors
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
return {
|
|
263
|
+
filename,
|
|
264
|
+
js: require_script.transformScript(code, filename, { typescript: ext === ".ts" }),
|
|
265
|
+
css: "",
|
|
266
|
+
errors
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
errors.push(messageOf(error));
|
|
270
|
+
return {
|
|
271
|
+
filename,
|
|
272
|
+
js: "exports.default = {};",
|
|
273
|
+
css: "",
|
|
274
|
+
errors
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
errors.push(`[Playground] 不支持的文件类型: ${filename}`);
|
|
279
|
+
return {
|
|
280
|
+
filename,
|
|
281
|
+
js: "exports.default = {};",
|
|
282
|
+
css: "",
|
|
283
|
+
errors
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* 浏览器内编译整份 files。入口和相对路径与 Playground 的 files 键一致。
|
|
288
|
+
* @param files 当前 Playground 文件表。
|
|
289
|
+
* @param entryOrOptions 入口文件名,或带入口的编译选项。
|
|
290
|
+
* @returns 编译出的模块、样式和错误。
|
|
291
|
+
*/
|
|
292
|
+
var compilePlayground = async (files, entryOrOptions) => {
|
|
293
|
+
const options = typeof entryOrOptions === "string" ? { entry: entryOrOptions } : entryOrOptions || {};
|
|
294
|
+
const errors = [];
|
|
295
|
+
let entry;
|
|
296
|
+
try {
|
|
297
|
+
entry = require_files.resolveEntry(files, options.entry);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
entry: "",
|
|
301
|
+
modules: {},
|
|
302
|
+
css: [],
|
|
303
|
+
errors: [messageOf(error)]
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const fileIndex = require_files.createFileIndex(files);
|
|
307
|
+
const reachable = require_files.collectReachableFilenames(files, entry);
|
|
308
|
+
const modules = {};
|
|
309
|
+
const cssChunks = [];
|
|
310
|
+
for (const filename of reachable) {
|
|
311
|
+
const code = fileIndex.get(filename);
|
|
312
|
+
if (typeof code !== "string") continue;
|
|
313
|
+
const compiled = await compileOne(filename, code, files);
|
|
314
|
+
errors.push(...compiled.errors);
|
|
315
|
+
modules[filename] = {
|
|
316
|
+
filename,
|
|
317
|
+
js: compiled.js,
|
|
318
|
+
css: compiled.css
|
|
319
|
+
};
|
|
320
|
+
cssChunks.push(...require_local_sandbox.collectCss([compiled.css]));
|
|
321
|
+
}
|
|
322
|
+
const normalizedEntry = require_files.normalizeVirtualPath(entry);
|
|
323
|
+
/* istanbul ignore if -- 可达编译总会留下入口模块 */
|
|
324
|
+
if (!modules[normalizedEntry]) errors.push(`[Playground] 入口编译结果缺失: ${normalizedEntry}`);
|
|
325
|
+
return {
|
|
326
|
+
entry: normalizedEntry,
|
|
327
|
+
modules,
|
|
328
|
+
css: cssChunks,
|
|
329
|
+
errors
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
//#endregion
|
|
333
|
+
exports.compilePlayground = compilePlayground;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//#region packages/playground/src/core/runtime/local/compile/types.ts
|
|
2
|
+
var IGNORED_FILENAMES = /* @__PURE__ */ new Set(["import-map.json", "tsconfig.json"]);
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region packages/playground/src/core/runtime/local/compile/files.ts
|
|
5
|
+
/**
|
|
6
|
+
* 统一成以 `/` 开头的 POSIX 绝对虚拟路径。
|
|
7
|
+
* `App.vue` 与 `/App.vue` 是同一文件,和 iframe 预览使用的 files 键一致。
|
|
8
|
+
* @param filename Playground files 的键或相对路径。
|
|
9
|
+
* @returns 以 `/` 开头的虚拟路径。
|
|
10
|
+
*/
|
|
11
|
+
var normalizeVirtualPath = (filename) => {
|
|
12
|
+
const raw = String(filename || "").replace(/\\/gu, "/").trim();
|
|
13
|
+
if (!raw) return "/";
|
|
14
|
+
const parts = (raw.startsWith("/") ? raw : `/${raw}`).split("/");
|
|
15
|
+
const stack = [];
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (!part || part === ".") continue;
|
|
18
|
+
if (part === "..") {
|
|
19
|
+
if (stack.length) stack.pop();
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
stack.push(part);
|
|
23
|
+
}
|
|
24
|
+
return `/${stack.join("/")}`;
|
|
25
|
+
};
|
|
26
|
+
var dirname = (filename) => {
|
|
27
|
+
const path = normalizeVirtualPath(filename);
|
|
28
|
+
const index = path.lastIndexOf("/");
|
|
29
|
+
if (index <= 0) return "/";
|
|
30
|
+
return path.slice(0, index) || "/";
|
|
31
|
+
};
|
|
32
|
+
var basename = (filename) => {
|
|
33
|
+
const path = normalizeVirtualPath(filename);
|
|
34
|
+
const index = path.lastIndexOf("/");
|
|
35
|
+
return index === -1 ? path : path.slice(index + 1);
|
|
36
|
+
};
|
|
37
|
+
var joinVirtualPath = (baseDir, relative) => {
|
|
38
|
+
const rel = String(relative || "").replace(/\\/gu, "/");
|
|
39
|
+
if (rel.startsWith("/")) return normalizeVirtualPath(rel);
|
|
40
|
+
const base = normalizeVirtualPath(baseDir);
|
|
41
|
+
return normalizeVirtualPath(`${base === "/" ? "" : base}/${rel}`);
|
|
42
|
+
};
|
|
43
|
+
var getExtension = (filename) => {
|
|
44
|
+
const name = basename(filename);
|
|
45
|
+
const index = name.lastIndexOf(".");
|
|
46
|
+
if (index <= 0) return "";
|
|
47
|
+
return name.slice(index).toLowerCase();
|
|
48
|
+
};
|
|
49
|
+
var isIgnoredFilename = (filename) => IGNORED_FILENAMES.has(basename(filename));
|
|
50
|
+
var createFileIndex = (files) => {
|
|
51
|
+
const index = /* @__PURE__ */ new Map();
|
|
52
|
+
Object.entries(files || {}).forEach(([filename, code]) => {
|
|
53
|
+
const normalized = normalizeVirtualPath(filename);
|
|
54
|
+
if (isIgnoredFilename(normalized)) return;
|
|
55
|
+
index.set(normalized, code);
|
|
56
|
+
});
|
|
57
|
+
return index;
|
|
58
|
+
};
|
|
59
|
+
var resolveEntry = (files, entry) => {
|
|
60
|
+
const index = createFileIndex(files);
|
|
61
|
+
if (!index.size) throw new Error("[Playground] files 为空,无法确定入口");
|
|
62
|
+
if (entry) {
|
|
63
|
+
const normalized = normalizeVirtualPath(entry);
|
|
64
|
+
if (isIgnoredFilename(normalized)) throw new Error(`[Playground] entry 不能是 ${basename(normalized)}`);
|
|
65
|
+
if (!index.has(normalized)) throw new Error(`[Playground] entry 不在 files 中: ${entry}`);
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
for (const key of Object.keys(files || {})) {
|
|
69
|
+
const normalized = normalizeVirtualPath(key);
|
|
70
|
+
if (!isIgnoredFilename(normalized) && index.has(normalized)) return normalized;
|
|
71
|
+
}
|
|
72
|
+
/* istanbul ignore next -- 忽略文件已在前面排除,正常 files 不会走到这里 */
|
|
73
|
+
throw new Error("[Playground] files 中没有可用的入口文件");
|
|
74
|
+
};
|
|
75
|
+
var CANDIDATE_EXTENSIONS = [
|
|
76
|
+
".vue",
|
|
77
|
+
".ts",
|
|
78
|
+
".js",
|
|
79
|
+
".tsx",
|
|
80
|
+
".jsx",
|
|
81
|
+
".json",
|
|
82
|
+
".css",
|
|
83
|
+
".scss",
|
|
84
|
+
".sass"
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* 相对 / 无扩展名解析,行为对齐 Vite:精确路径、补扩展名、`/index.*`。
|
|
88
|
+
* @param fromFilename 发起导入的文件。
|
|
89
|
+
* @param specifier 相对或绝对虚拟路径。
|
|
90
|
+
* @param fileIndex 虚拟路径到 files 键的索引。
|
|
91
|
+
* @returns 解析后的 files 键;找不到时返回 null。
|
|
92
|
+
*/
|
|
93
|
+
var resolveModulePath = (fromFilename, specifier, fileIndex) => {
|
|
94
|
+
if (!specifier || !(specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/"))) return null;
|
|
95
|
+
const target = joinVirtualPath(dirname(fromFilename), specifier);
|
|
96
|
+
if (fileIndex.has(target)) return target;
|
|
97
|
+
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
98
|
+
const withExt = `${target}${ext}`;
|
|
99
|
+
if (fileIndex.has(withExt)) return withExt;
|
|
100
|
+
}
|
|
101
|
+
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
102
|
+
const asIndex = `${target}/index${ext}`;
|
|
103
|
+
if (fileIndex.has(asIndex)) return asIndex;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
var getCommonRoot = (filenames) => {
|
|
108
|
+
const normalized = filenames.map(normalizeVirtualPath);
|
|
109
|
+
if (!normalized.length) return "/";
|
|
110
|
+
const split = normalized.map((path) => path.split("/").filter(Boolean));
|
|
111
|
+
const first = split[0];
|
|
112
|
+
let depth = first.length;
|
|
113
|
+
for (let i = 1; i < split.length; i += 1) {
|
|
114
|
+
let common = 0;
|
|
115
|
+
while (common < depth && common < split[i].length && first[common] === split[i][common]) common += 1;
|
|
116
|
+
depth = common;
|
|
117
|
+
}
|
|
118
|
+
if (!depth) return "/";
|
|
119
|
+
const maybeFile = `/${first.slice(0, depth).join("/")}`;
|
|
120
|
+
if (normalized.some((path) => path === maybeFile)) return dirname(maybeFile);
|
|
121
|
+
return maybeFile;
|
|
122
|
+
};
|
|
123
|
+
var isInsideRoot = (filename, root) => {
|
|
124
|
+
const path = normalizeVirtualPath(filename);
|
|
125
|
+
const base = normalizeVirtualPath(root);
|
|
126
|
+
if (base === "/") return true;
|
|
127
|
+
return path === base || path.startsWith(`${base}/`);
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* 静态 import / export-from / import() 的模块说明符。
|
|
131
|
+
* @param code 源码文本。
|
|
132
|
+
* @returns 按出现顺序收集到的说明符。
|
|
133
|
+
*/
|
|
134
|
+
var listImportSpecifiers = (code) => {
|
|
135
|
+
const result = [];
|
|
136
|
+
const source = String(code || "");
|
|
137
|
+
for (const match of source.matchAll(/(?:\bfrom\s+|import\s*\(\s*|import\s+)['"]([^'"]+)['"]/gu)) if (match[1]) result.push(match[1]);
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
var isRelativeSpecifier = (id) => id.startsWith("./") || id.startsWith("../") || id.startsWith("/");
|
|
141
|
+
/**
|
|
142
|
+
* 从入口出发只编译静态 import 能走到的文件。
|
|
143
|
+
* @param files 当前 Playground 文件表。
|
|
144
|
+
* @param entry 入口文件名。
|
|
145
|
+
* @returns 可达文件名,入口在最前。
|
|
146
|
+
*/
|
|
147
|
+
var collectReachableFilenames = (files, entry) => {
|
|
148
|
+
const index = createFileIndex(files);
|
|
149
|
+
const ordered = [];
|
|
150
|
+
const seen = /* @__PURE__ */ new Set();
|
|
151
|
+
const visit = (filename) => {
|
|
152
|
+
const key = normalizeVirtualPath(filename);
|
|
153
|
+
if (seen.has(key) || !index.has(key)) return;
|
|
154
|
+
seen.add(key);
|
|
155
|
+
ordered.push(key);
|
|
156
|
+
listImportSpecifiers(index.get(key) || "").forEach((specifier) => {
|
|
157
|
+
const resolved = resolveModulePath(key, specifier, index);
|
|
158
|
+
if (resolved) visit(resolved);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
visit(entry);
|
|
162
|
+
return ordered;
|
|
163
|
+
};
|
|
164
|
+
var collectBareSpecifiers = (files, filenames) => {
|
|
165
|
+
const index = createFileIndex(files);
|
|
166
|
+
const result = [];
|
|
167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
168
|
+
filenames.forEach((filename) => {
|
|
169
|
+
listImportSpecifiers(index.get(normalizeVirtualPath(filename)) || "").forEach((specifier) => {
|
|
170
|
+
if (!specifier || isRelativeSpecifier(specifier) || seen.has(specifier)) return;
|
|
171
|
+
seen.add(specifier);
|
|
172
|
+
result.push(specifier);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return result;
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
export { basename, collectBareSpecifiers, collectReachableFilenames, createFileIndex, dirname, getCommonRoot, getExtension, isIgnoredFilename, isInsideRoot, isRelativeSpecifier, joinVirtualPath, listImportSpecifiers, normalizeVirtualPath, resolveEntry, resolveModulePath };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
//#region packages/playground/src/core/runtime/local/compile/types.ts
|
|
2
|
+
var IGNORED_FILENAMES = /* @__PURE__ */ new Set(["import-map.json", "tsconfig.json"]);
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region packages/playground/src/core/runtime/local/compile/files.ts
|
|
5
|
+
/**
|
|
6
|
+
* 统一成以 `/` 开头的 POSIX 绝对虚拟路径。
|
|
7
|
+
* `App.vue` 与 `/App.vue` 是同一文件,和 iframe 预览使用的 files 键一致。
|
|
8
|
+
* @param filename Playground files 的键或相对路径。
|
|
9
|
+
* @returns 以 `/` 开头的虚拟路径。
|
|
10
|
+
*/
|
|
11
|
+
var normalizeVirtualPath = (filename) => {
|
|
12
|
+
const raw = String(filename || "").replace(/\\/gu, "/").trim();
|
|
13
|
+
if (!raw) return "/";
|
|
14
|
+
const parts = (raw.startsWith("/") ? raw : `/${raw}`).split("/");
|
|
15
|
+
const stack = [];
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (!part || part === ".") continue;
|
|
18
|
+
if (part === "..") {
|
|
19
|
+
if (stack.length) stack.pop();
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
stack.push(part);
|
|
23
|
+
}
|
|
24
|
+
return `/${stack.join("/")}`;
|
|
25
|
+
};
|
|
26
|
+
var dirname = (filename) => {
|
|
27
|
+
const path = normalizeVirtualPath(filename);
|
|
28
|
+
const index = path.lastIndexOf("/");
|
|
29
|
+
if (index <= 0) return "/";
|
|
30
|
+
return path.slice(0, index) || "/";
|
|
31
|
+
};
|
|
32
|
+
var basename = (filename) => {
|
|
33
|
+
const path = normalizeVirtualPath(filename);
|
|
34
|
+
const index = path.lastIndexOf("/");
|
|
35
|
+
return index === -1 ? path : path.slice(index + 1);
|
|
36
|
+
};
|
|
37
|
+
var joinVirtualPath = (baseDir, relative) => {
|
|
38
|
+
const rel = String(relative || "").replace(/\\/gu, "/");
|
|
39
|
+
if (rel.startsWith("/")) return normalizeVirtualPath(rel);
|
|
40
|
+
const base = normalizeVirtualPath(baseDir);
|
|
41
|
+
return normalizeVirtualPath(`${base === "/" ? "" : base}/${rel}`);
|
|
42
|
+
};
|
|
43
|
+
var getExtension = (filename) => {
|
|
44
|
+
const name = basename(filename);
|
|
45
|
+
const index = name.lastIndexOf(".");
|
|
46
|
+
if (index <= 0) return "";
|
|
47
|
+
return name.slice(index).toLowerCase();
|
|
48
|
+
};
|
|
49
|
+
var isIgnoredFilename = (filename) => IGNORED_FILENAMES.has(basename(filename));
|
|
50
|
+
var createFileIndex = (files) => {
|
|
51
|
+
const index = /* @__PURE__ */ new Map();
|
|
52
|
+
Object.entries(files || {}).forEach(([filename, code]) => {
|
|
53
|
+
const normalized = normalizeVirtualPath(filename);
|
|
54
|
+
if (isIgnoredFilename(normalized)) return;
|
|
55
|
+
index.set(normalized, code);
|
|
56
|
+
});
|
|
57
|
+
return index;
|
|
58
|
+
};
|
|
59
|
+
var resolveEntry = (files, entry) => {
|
|
60
|
+
const index = createFileIndex(files);
|
|
61
|
+
if (!index.size) throw new Error("[Playground] files 为空,无法确定入口");
|
|
62
|
+
if (entry) {
|
|
63
|
+
const normalized = normalizeVirtualPath(entry);
|
|
64
|
+
if (isIgnoredFilename(normalized)) throw new Error(`[Playground] entry 不能是 ${basename(normalized)}`);
|
|
65
|
+
if (!index.has(normalized)) throw new Error(`[Playground] entry 不在 files 中: ${entry}`);
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
for (const key of Object.keys(files || {})) {
|
|
69
|
+
const normalized = normalizeVirtualPath(key);
|
|
70
|
+
if (!isIgnoredFilename(normalized) && index.has(normalized)) return normalized;
|
|
71
|
+
}
|
|
72
|
+
/* istanbul ignore next -- 忽略文件已在前面排除,正常 files 不会走到这里 */
|
|
73
|
+
throw new Error("[Playground] files 中没有可用的入口文件");
|
|
74
|
+
};
|
|
75
|
+
var CANDIDATE_EXTENSIONS = [
|
|
76
|
+
".vue",
|
|
77
|
+
".ts",
|
|
78
|
+
".js",
|
|
79
|
+
".tsx",
|
|
80
|
+
".jsx",
|
|
81
|
+
".json",
|
|
82
|
+
".css",
|
|
83
|
+
".scss",
|
|
84
|
+
".sass"
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* 相对 / 无扩展名解析,行为对齐 Vite:精确路径、补扩展名、`/index.*`。
|
|
88
|
+
* @param fromFilename 发起导入的文件。
|
|
89
|
+
* @param specifier 相对或绝对虚拟路径。
|
|
90
|
+
* @param fileIndex 虚拟路径到 files 键的索引。
|
|
91
|
+
* @returns 解析后的 files 键;找不到时返回 null。
|
|
92
|
+
*/
|
|
93
|
+
var resolveModulePath = (fromFilename, specifier, fileIndex) => {
|
|
94
|
+
if (!specifier || !(specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/"))) return null;
|
|
95
|
+
const target = joinVirtualPath(dirname(fromFilename), specifier);
|
|
96
|
+
if (fileIndex.has(target)) return target;
|
|
97
|
+
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
98
|
+
const withExt = `${target}${ext}`;
|
|
99
|
+
if (fileIndex.has(withExt)) return withExt;
|
|
100
|
+
}
|
|
101
|
+
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
102
|
+
const asIndex = `${target}/index${ext}`;
|
|
103
|
+
if (fileIndex.has(asIndex)) return asIndex;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
var getCommonRoot = (filenames) => {
|
|
108
|
+
const normalized = filenames.map(normalizeVirtualPath);
|
|
109
|
+
if (!normalized.length) return "/";
|
|
110
|
+
const split = normalized.map((path) => path.split("/").filter(Boolean));
|
|
111
|
+
const first = split[0];
|
|
112
|
+
let depth = first.length;
|
|
113
|
+
for (let i = 1; i < split.length; i += 1) {
|
|
114
|
+
let common = 0;
|
|
115
|
+
while (common < depth && common < split[i].length && first[common] === split[i][common]) common += 1;
|
|
116
|
+
depth = common;
|
|
117
|
+
}
|
|
118
|
+
if (!depth) return "/";
|
|
119
|
+
const maybeFile = `/${first.slice(0, depth).join("/")}`;
|
|
120
|
+
if (normalized.some((path) => path === maybeFile)) return dirname(maybeFile);
|
|
121
|
+
return maybeFile;
|
|
122
|
+
};
|
|
123
|
+
var isInsideRoot = (filename, root) => {
|
|
124
|
+
const path = normalizeVirtualPath(filename);
|
|
125
|
+
const base = normalizeVirtualPath(root);
|
|
126
|
+
if (base === "/") return true;
|
|
127
|
+
return path === base || path.startsWith(`${base}/`);
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* 静态 import / export-from / import() 的模块说明符。
|
|
131
|
+
* @param code 源码文本。
|
|
132
|
+
* @returns 按出现顺序收集到的说明符。
|
|
133
|
+
*/
|
|
134
|
+
var listImportSpecifiers = (code) => {
|
|
135
|
+
const result = [];
|
|
136
|
+
const source = String(code || "");
|
|
137
|
+
for (const match of source.matchAll(/(?:\bfrom\s+|import\s*\(\s*|import\s+)['"]([^'"]+)['"]/gu)) if (match[1]) result.push(match[1]);
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
var isRelativeSpecifier = (id) => id.startsWith("./") || id.startsWith("../") || id.startsWith("/");
|
|
141
|
+
/**
|
|
142
|
+
* 从入口出发只编译静态 import 能走到的文件。
|
|
143
|
+
* @param files 当前 Playground 文件表。
|
|
144
|
+
* @param entry 入口文件名。
|
|
145
|
+
* @returns 可达文件名,入口在最前。
|
|
146
|
+
*/
|
|
147
|
+
var collectReachableFilenames = (files, entry) => {
|
|
148
|
+
const index = createFileIndex(files);
|
|
149
|
+
const ordered = [];
|
|
150
|
+
const seen = /* @__PURE__ */ new Set();
|
|
151
|
+
const visit = (filename) => {
|
|
152
|
+
const key = normalizeVirtualPath(filename);
|
|
153
|
+
if (seen.has(key) || !index.has(key)) return;
|
|
154
|
+
seen.add(key);
|
|
155
|
+
ordered.push(key);
|
|
156
|
+
listImportSpecifiers(index.get(key) || "").forEach((specifier) => {
|
|
157
|
+
const resolved = resolveModulePath(key, specifier, index);
|
|
158
|
+
if (resolved) visit(resolved);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
visit(entry);
|
|
162
|
+
return ordered;
|
|
163
|
+
};
|
|
164
|
+
var collectBareSpecifiers = (files, filenames) => {
|
|
165
|
+
const index = createFileIndex(files);
|
|
166
|
+
const result = [];
|
|
167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
168
|
+
filenames.forEach((filename) => {
|
|
169
|
+
listImportSpecifiers(index.get(normalizeVirtualPath(filename)) || "").forEach((specifier) => {
|
|
170
|
+
if (!specifier || isRelativeSpecifier(specifier) || seen.has(specifier)) return;
|
|
171
|
+
seen.add(specifier);
|
|
172
|
+
result.push(specifier);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return result;
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
exports.basename = basename;
|
|
179
|
+
exports.collectBareSpecifiers = collectBareSpecifiers;
|
|
180
|
+
exports.collectReachableFilenames = collectReachableFilenames;
|
|
181
|
+
exports.createFileIndex = createFileIndex;
|
|
182
|
+
exports.dirname = dirname;
|
|
183
|
+
exports.getCommonRoot = getCommonRoot;
|
|
184
|
+
exports.getExtension = getExtension;
|
|
185
|
+
exports.isIgnoredFilename = isIgnoredFilename;
|
|
186
|
+
exports.isInsideRoot = isInsideRoot;
|
|
187
|
+
exports.isRelativeSpecifier = isRelativeSpecifier;
|
|
188
|
+
exports.joinVirtualPath = joinVirtualPath;
|
|
189
|
+
exports.listImportSpecifiers = listImportSpecifiers;
|
|
190
|
+
exports.normalizeVirtualPath = normalizeVirtualPath;
|
|
191
|
+
exports.resolveEntry = resolveEntry;
|
|
192
|
+
exports.resolveModulePath = resolveModulePath;
|