@neuxnet/neux-cli 0.2.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 +438 -0
- package/README.zh-CN.md +554 -0
- package/assets/container/assets/index.css +1 -0
- package/assets/container/assets/index.js +137 -0
- package/assets/container/assets/pageFrame.css +1 -0
- package/assets/container/assets/pageFrame.js +56 -0
- package/assets/container/assets/service.js +6 -0
- package/assets/container/assets/vconsole.js +931 -0
- package/assets/container/favicon.ico +0 -0
- package/assets/container/images/icon-arrow.png +0 -0
- package/assets/container/images/mini-action-white.png +0 -0
- package/assets/container/images/mini-action.png +0 -0
- package/assets/container/images/mini-arrow-left-white.png +0 -0
- package/assets/container/images/mini-arrow-left.jpg +0 -0
- package/assets/container/images/mini-arrow-left.png +0 -0
- package/assets/container/images/mini-close-white.png +0 -0
- package/assets/container/images/mini-close.png +0 -0
- package/assets/container/images/more.png +0 -0
- package/assets/container/images/search.jpg +0 -0
- package/assets/container/index.html +1 -0
- package/assets/container/pageFrame.html +1 -0
- package/assets/init/tabbar/home-active.png +0 -0
- package/assets/init/tabbar/home.png +0 -0
- package/assets/init/tabbar/list-active.png +0 -0
- package/assets/init/tabbar/list.png +0 -0
- package/assets/init/types/neux-api.d.ts +1402 -0
- package/package.json +68 -0
- package/scripts/generate-init-types.js +210 -0
- package/scripts/sync-compiler.js +22 -0
- package/scripts/sync-web-container.js +34 -0
- package/src/bin/cli.js +646 -0
- package/src/core/brand.js +11 -0
- package/src/core/compiler.js +305 -0
- package/src/core/defaults.js +12 -0
- package/src/core/dev.js +15 -0
- package/src/core/errors.js +17 -0
- package/src/core/fs.js +66 -0
- package/src/core/i18n.js +37 -0
- package/src/core/init.js +866 -0
- package/src/core/lifecycle.js +32 -0
- package/src/core/manifest.js +72 -0
- package/src/core/pack.js +156 -0
- package/src/core/package-info.js +27 -0
- package/src/core/preview-server.js +123 -0
- package/src/core/project.js +101 -0
- package/src/core/prompts.js +71 -0
- package/src/core/proxy-security.js +141 -0
- package/src/core/proxy.js +156 -0
- package/src/core/qr.js +41 -0
- package/src/core/terminal-qr.js +72 -0
- package/src/core/update.js +278 -0
- package/src/core/watch.js +113 -0
- package/src/core/web.js +649 -0
- package/src/core/zip.js +120 -0
- package/src/index.js +20 -0
- package/src/providers/service-client.js +149 -0
- package/src/providers/service-config.js +264 -0
- package/src/providers/service.js +146 -0
- package/src/providers/upload.js +112 -0
- package/vendor/dimina-compiler/bin/index.cjs +265 -0
- package/vendor/dimina-compiler/bin/index.js +263 -0
- package/vendor/dimina-compiler/compatibility-B-DoZtUX.cjs +395 -0
- package/vendor/dimina-compiler/compatibility-Cl3-DO6V.js +366 -0
- package/vendor/dimina-compiler/core/logic-compiler.cjs +378 -0
- package/vendor/dimina-compiler/core/logic-compiler.js +374 -0
- package/vendor/dimina-compiler/core/style-compiler.cjs +392 -0
- package/vendor/dimina-compiler/core/style-compiler.js +377 -0
- package/vendor/dimina-compiler/core/view-compiler.cjs +1601 -0
- package/vendor/dimina-compiler/core/view-compiler.js +1582 -0
- package/vendor/dimina-compiler/index.cjs +762 -0
- package/vendor/dimina-compiler/index.js +751 -0
- package/vendor/dimina-compiler/sourcemap-BgtIgqkC.cjs +1377 -0
- package/vendor/dimina-compiler/sourcemap-CKjhV9h7.js +1135 -0
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
import { C as resetStoreInfo, D as getAbsolutePath, E as collectAssets, M as tagWhiteList, N as transformRpx, P as toMiniProgramModuleId, S as getWorkPath, _ as getTargetPath, b as getViewScriptExts, d as getDependencyGraph, i as remapSourcemap, j as resolveAssetSourcePath, l as getComponent, n as createLineSourcemap, o as getAppId, r as mergeSourcemap, t as concatSourcemap, u as getContentByPath, x as getViewScriptTags, y as getTemplateExts } from "../sourcemap-CKjhV9h7.js";
|
|
2
|
+
import { i as takeCompatibilityWarnings, n as getTemplateDirectiveName, t as checkTemplateCompatibility } from "../compatibility-Cl3-DO6V.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { isMainThread, parentPort } from "node:worker_threads";
|
|
6
|
+
import { parseSync } from "oxc-parser";
|
|
7
|
+
import { walk } from "oxc-walker";
|
|
8
|
+
import { compileTemplate } from "@vue/compiler-sfc";
|
|
9
|
+
import MagicString from "magic-string";
|
|
10
|
+
import * as cheerio from "cheerio";
|
|
11
|
+
import { transform } from "esbuild";
|
|
12
|
+
import * as htmlparser2 from "htmlparser2";
|
|
13
|
+
//#region src/common/expression-parser.js
|
|
14
|
+
/**
|
|
15
|
+
* 表达式解析器 - 使用 Oxc AST 解析器提取依赖
|
|
16
|
+
*/
|
|
17
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
18
|
+
"true",
|
|
19
|
+
"false",
|
|
20
|
+
"null",
|
|
21
|
+
"undefined",
|
|
22
|
+
"NaN",
|
|
23
|
+
"Infinity",
|
|
24
|
+
"this",
|
|
25
|
+
"arguments",
|
|
26
|
+
"Array",
|
|
27
|
+
"Object",
|
|
28
|
+
"String",
|
|
29
|
+
"Number",
|
|
30
|
+
"Boolean",
|
|
31
|
+
"Math",
|
|
32
|
+
"Date",
|
|
33
|
+
"RegExp",
|
|
34
|
+
"Error",
|
|
35
|
+
"JSON",
|
|
36
|
+
"console",
|
|
37
|
+
"window",
|
|
38
|
+
"document",
|
|
39
|
+
"parseInt",
|
|
40
|
+
"parseFloat",
|
|
41
|
+
"isNaN",
|
|
42
|
+
"isFinite",
|
|
43
|
+
"encodeURI",
|
|
44
|
+
"encodeURIComponent",
|
|
45
|
+
"decodeURI",
|
|
46
|
+
"decodeURIComponent"
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* 提取表达式中的所有变量依赖路径
|
|
50
|
+
* 使用 Oxc AST 解析器进行精确分析
|
|
51
|
+
* @param {string} expression - 表达式字符串,如 "count || defaultValue" 或 "item.name"
|
|
52
|
+
* @returns {Array<string>} - 依赖的变量名数组,如 ["count", "defaultValue"] 或 ["item"]
|
|
53
|
+
*/
|
|
54
|
+
function extractDependencies(expression) {
|
|
55
|
+
if (!expression || typeof expression !== "string") return [];
|
|
56
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
57
|
+
try {
|
|
58
|
+
const ast = parseSync("expression.js", `(${expression})`, {
|
|
59
|
+
sourceType: "module",
|
|
60
|
+
lang: "js"
|
|
61
|
+
}).program;
|
|
62
|
+
visitExpressionAst(ast, null, dependencies);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn("[expression-parser] AST 解析失败,表达式:", expression, "错误:", error.message);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
return Array.from(dependencies);
|
|
68
|
+
}
|
|
69
|
+
function visitExpressionAst(node, parent, dependencies) {
|
|
70
|
+
if (!node || typeof node !== "object") return;
|
|
71
|
+
if (node.type === "Identifier") {
|
|
72
|
+
collectIdentifier(node, parent, dependencies);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (node.type === "MemberExpression") {
|
|
76
|
+
let root = node.object;
|
|
77
|
+
while (root?.type === "MemberExpression" || root?.type === "ChainExpression") root = root.type === "ChainExpression" ? root.expression : root.object;
|
|
78
|
+
if (root?.type === "Identifier" && !KEYWORDS.has(root.name)) dependencies.add(root.name);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const [key, value] of Object.entries(node)) {
|
|
82
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
|
|
83
|
+
if (Array.isArray(value)) for (const child of value) visitExpressionAst(child, node, dependencies);
|
|
84
|
+
else visitExpressionAst(value, node, dependencies);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function collectIdentifier(node, parent, dependencies) {
|
|
88
|
+
const name = node.name;
|
|
89
|
+
if (KEYWORDS.has(name)) return;
|
|
90
|
+
if (parent?.type === "MemberExpression" && parent.property === node && !parent.computed) return;
|
|
91
|
+
if (parent?.type === "Property" && parent.key === node && !parent.computed && !parent.shorthand) return;
|
|
92
|
+
dependencies.add(name);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 解析表达式并生成依赖路径信息
|
|
96
|
+
* @param {string} expression - 表达式字符串
|
|
97
|
+
* @returns {Object} - { expression: 原始表达式, dependencies: 依赖数组, isSimple: 是否简单绑定 }
|
|
98
|
+
*/
|
|
99
|
+
function parseExpression(expression) {
|
|
100
|
+
if (!expression || typeof expression !== "string") return {
|
|
101
|
+
expression: "",
|
|
102
|
+
dependencies: [],
|
|
103
|
+
isSimple: true
|
|
104
|
+
};
|
|
105
|
+
const dependencies = extractDependencies(expression);
|
|
106
|
+
const isSimple = dependencies.length === 1 && expression.trim() === dependencies[0];
|
|
107
|
+
return {
|
|
108
|
+
expression: expression.trim(),
|
|
109
|
+
dependencies,
|
|
110
|
+
isSimple
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 批量解析多个属性绑定表达式
|
|
115
|
+
* @param {Object} bindings - 绑定对象,如 { count2: "count", value: "item.name" }
|
|
116
|
+
* @returns {Object} - 解析后的绑定信息
|
|
117
|
+
*/
|
|
118
|
+
function parseBindings(bindings) {
|
|
119
|
+
if (!bindings || typeof bindings !== "object") return {};
|
|
120
|
+
const parsed = {};
|
|
121
|
+
for (const [propName, expression] of Object.entries(bindings)) parsed[propName] = parseExpression(expression);
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/core/view-compiler.js
|
|
126
|
+
/**
|
|
127
|
+
* 根据扩展名列表生成匹配尾部扩展名的正则,如 ['.wxs', '.qds'] -> /(\.wxs|\.qds)$/
|
|
128
|
+
*/
|
|
129
|
+
function buildExtStripRegex(exts) {
|
|
130
|
+
const alt = exts.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
131
|
+
return new RegExp(`(${alt})$`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 移除视图脚本文件路径末尾的扩展名,支持 .wxs 和自定义扩展名
|
|
135
|
+
*/
|
|
136
|
+
function stripViewScriptExt(p) {
|
|
137
|
+
return p.replace(buildExtStripRegex(getViewScriptExts()), "");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 解析 JavaScript 代码
|
|
141
|
+
* @param {string} code
|
|
142
|
+
* @param {string} filename
|
|
143
|
+
* @param {'module'|'script'} sourceType
|
|
144
|
+
* @returns {*} Oxc Program AST
|
|
145
|
+
*/
|
|
146
|
+
function parseJs(code, filename = "view-compiler.js", sourceType = "module") {
|
|
147
|
+
return parseSync(filename, code, {
|
|
148
|
+
sourceType,
|
|
149
|
+
lang: "js"
|
|
150
|
+
}).program;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 如果顶层是单个表达式语句,则只返回表达式源码
|
|
154
|
+
* @param {string} code
|
|
155
|
+
* @param {*} ast - Oxc Program AST
|
|
156
|
+
* @returns {string} Program code or the source of the single top-level expression.
|
|
157
|
+
*/
|
|
158
|
+
function getProgramCode(code, ast) {
|
|
159
|
+
const statement = ast.body?.[0];
|
|
160
|
+
if (ast.body?.length === 1 && statement?.type === "ExpressionStatement") return code.slice(statement.expression.start, statement.expression.end);
|
|
161
|
+
return code;
|
|
162
|
+
}
|
|
163
|
+
function isStringLiteral(node) {
|
|
164
|
+
return node?.type === "StringLiteral" || node?.type === "Literal" && typeof node.value === "string";
|
|
165
|
+
}
|
|
166
|
+
function getStringLiteralRawValue(node) {
|
|
167
|
+
if (!isStringLiteral(node)) return "";
|
|
168
|
+
if (typeof node.raw === "string") return node.raw.slice(1, -1);
|
|
169
|
+
return String(node.value);
|
|
170
|
+
}
|
|
171
|
+
function getSource(code, node) {
|
|
172
|
+
return code.slice(node.start, node.end);
|
|
173
|
+
}
|
|
174
|
+
function applyCodeReplacements(source, replacements) {
|
|
175
|
+
if (replacements.length === 0) return source;
|
|
176
|
+
const selected = [];
|
|
177
|
+
const byLargestRange = [...replacements].sort((a, b) => {
|
|
178
|
+
return b.end - b.start - (a.end - a.start) || b.start - a.start;
|
|
179
|
+
});
|
|
180
|
+
for (const replacement of byLargestRange) if (!selected.some((item) => replacement.start < item.end && item.start < replacement.end)) selected.push(replacement);
|
|
181
|
+
const s = new MagicString(source);
|
|
182
|
+
for (const replacement of selected.sort((a, b) => b.start - a.start)) if (replacement.type === "insert") s.appendLeft(replacement.start, replacement.value);
|
|
183
|
+
else s.overwrite(replacement.start, replacement.end, replacement.value);
|
|
184
|
+
return s.toString();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 为模板表达式中的成员访问补充空值保护,避免生成的 render 函数直接访问 null/undefined 属性
|
|
188
|
+
* 例如: stickyProps.zIndex -> stickyProps?.zIndex
|
|
189
|
+
* @param {string} expression
|
|
190
|
+
* @returns {string} 添加了可选链操作符的表达式字符串
|
|
191
|
+
*/
|
|
192
|
+
function addOptionalChaining(expression) {
|
|
193
|
+
if (!expression || typeof expression !== "string") return expression;
|
|
194
|
+
try {
|
|
195
|
+
const code = `(${expression})`;
|
|
196
|
+
const ast = parseJs(code);
|
|
197
|
+
const insertions = [];
|
|
198
|
+
walk(ast, { enter(node) {
|
|
199
|
+
if (node.type !== "MemberExpression" || node.optional) return;
|
|
200
|
+
if (node.computed) {
|
|
201
|
+
const bracketIndex = code.lastIndexOf("[", node.property.start);
|
|
202
|
+
if (bracketIndex >= 0) insertions.push({
|
|
203
|
+
type: "insert",
|
|
204
|
+
start: bracketIndex,
|
|
205
|
+
end: bracketIndex,
|
|
206
|
+
value: "?."
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
const dotIndex = code.lastIndexOf(".", node.property.start);
|
|
210
|
+
if (dotIndex >= 0) insertions.push({
|
|
211
|
+
type: "insert",
|
|
212
|
+
start: dotIndex,
|
|
213
|
+
end: dotIndex,
|
|
214
|
+
value: "?"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
} });
|
|
218
|
+
return applyCodeReplacements(code, insertions).slice(1, -1);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return expression;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function parseSafeBraceExp(exp) {
|
|
224
|
+
return addOptionalChaining(parseBraceExp(exp));
|
|
225
|
+
}
|
|
226
|
+
function transformTextInterpolation(text) {
|
|
227
|
+
if (!text || typeof text !== "string" || !isWrappedByBraces(text)) return text;
|
|
228
|
+
return text.replace(braceRegex, (match, bracePart) => {
|
|
229
|
+
if (!bracePart) return match;
|
|
230
|
+
const matchResult = bracePart.match(noBraceRegex);
|
|
231
|
+
if (!matchResult) return match;
|
|
232
|
+
return `{{${addOptionalChaining(matchResult[1].trim())}}}`;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
var compileResCache = /* @__PURE__ */ new Map();
|
|
236
|
+
var wxsModuleRegistry = /* @__PURE__ */ new Set();
|
|
237
|
+
var wxsFilePathMap = /* @__PURE__ */ new Map();
|
|
238
|
+
var enableSourcemap = false;
|
|
239
|
+
function resetViewCompilerCache() {
|
|
240
|
+
compileResCache.clear();
|
|
241
|
+
wxsModuleRegistry.clear();
|
|
242
|
+
wxsFilePathMap.clear();
|
|
243
|
+
}
|
|
244
|
+
if (!isMainThread) parentPort.on("message", async ({ pages, storeInfo, sourcemap }) => {
|
|
245
|
+
try {
|
|
246
|
+
resetStoreInfo(storeInfo);
|
|
247
|
+
enableSourcemap = !!sourcemap;
|
|
248
|
+
const progress = {
|
|
249
|
+
_completedTasks: 0,
|
|
250
|
+
get completedTasks() {
|
|
251
|
+
return this._completedTasks;
|
|
252
|
+
},
|
|
253
|
+
set completedTasks(value) {
|
|
254
|
+
this._completedTasks = value;
|
|
255
|
+
parentPort.postMessage({ completedTasks: this._completedTasks });
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
await compileML(pages.mainPages, null, progress);
|
|
259
|
+
for (const [root, subPages] of Object.entries(pages.subPages)) await compileML(subPages.info, root, progress);
|
|
260
|
+
compileResCache.clear();
|
|
261
|
+
wxsModuleRegistry.clear();
|
|
262
|
+
wxsFilePathMap.clear();
|
|
263
|
+
parentPort.postMessage({
|
|
264
|
+
success: true,
|
|
265
|
+
compatibilityWarnings: takeCompatibilityWarnings(),
|
|
266
|
+
dependencyGraph: getDependencyGraph().toJSON()
|
|
267
|
+
});
|
|
268
|
+
} catch (error) {
|
|
269
|
+
compileResCache.clear();
|
|
270
|
+
wxsModuleRegistry.clear();
|
|
271
|
+
wxsFilePathMap.clear();
|
|
272
|
+
parentPort.postMessage({
|
|
273
|
+
success: false,
|
|
274
|
+
error: {
|
|
275
|
+
message: error.message,
|
|
276
|
+
stack: error.stack,
|
|
277
|
+
name: error.name
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
/**
|
|
283
|
+
* 编译页面视图文件
|
|
284
|
+
*/
|
|
285
|
+
async function compileML(pages, root, progress) {
|
|
286
|
+
initWxsFilePathMap(getWorkPath());
|
|
287
|
+
for (const page of pages) {
|
|
288
|
+
const scriptRes = /* @__PURE__ */ new Map();
|
|
289
|
+
const sourceMapRes = /* @__PURE__ */ new Map();
|
|
290
|
+
buildCompileView(page, false, scriptRes, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), sourceMapRes);
|
|
291
|
+
const filename = `${page.path.replace(/\//g, "_")}`;
|
|
292
|
+
const outputDir = root ? `${getTargetPath()}/${root}` : `${getTargetPath()}/main`;
|
|
293
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
294
|
+
if (enableSourcemap) {
|
|
295
|
+
const compileRes = [...scriptRes.entries()].map(([modulePath, code]) => ({
|
|
296
|
+
path: modulePath,
|
|
297
|
+
code,
|
|
298
|
+
map: sourceMapRes.get(modulePath)
|
|
299
|
+
}));
|
|
300
|
+
const sourcemapFileName = `${filename}.js.map`;
|
|
301
|
+
const { bundleCode, sourcemap } = mergeSourcemap(compileRes, `${filename}.js`);
|
|
302
|
+
fs.writeFileSync(`${outputDir}/${filename}.js`, `${bundleCode}//# sourceMappingURL=${sourcemapFileName}\n`);
|
|
303
|
+
fs.writeFileSync(`${outputDir}/${sourcemapFileName}`, sourcemap);
|
|
304
|
+
} else {
|
|
305
|
+
let mergeRender = "";
|
|
306
|
+
for (const [key, value] of scriptRes.entries()) {
|
|
307
|
+
const amdFormat = `modDefine('${key}', function(require, module, exports) {
|
|
308
|
+
${value}
|
|
309
|
+
});`;
|
|
310
|
+
try {
|
|
311
|
+
const { code: minifiedCode } = await transform(amdFormat, {
|
|
312
|
+
minify: true,
|
|
313
|
+
target: ["es2020"],
|
|
314
|
+
platform: "browser"
|
|
315
|
+
});
|
|
316
|
+
mergeRender += minifiedCode;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
const location = error.errors?.[0]?.location;
|
|
319
|
+
const sourceLines = amdFormat.split("\n");
|
|
320
|
+
const sourceHint = location?.line ? sourceLines.slice(Math.max(0, location.line - 3), location.line + 2).map((line, index) => `${Math.max(1, location.line - 2) + index}: ${line.trim()}`).join("\n") : "";
|
|
321
|
+
error.message = `视图模块 ${key} 转换失败: ${error.message}${sourceHint ? `\n${sourceHint}` : ""}`;
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
fs.writeFileSync(`${outputDir}/${filename}.js`, mergeRender);
|
|
326
|
+
}
|
|
327
|
+
scriptRes.clear();
|
|
328
|
+
sourceMapRes.clear();
|
|
329
|
+
progress.completedTasks++;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 初始化 wxs 文件路径映射
|
|
334
|
+
* @param {string} workPath - 工作路径
|
|
335
|
+
*/
|
|
336
|
+
function initWxsFilePathMap(workPath) {
|
|
337
|
+
wxsFilePathMap.clear();
|
|
338
|
+
const npmDir = path.join(workPath, "miniprogram_npm");
|
|
339
|
+
if (fs.existsSync(npmDir)) scanWxsFiles(npmDir, workPath);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 递归扫描目录下的所有 wxs 文件
|
|
343
|
+
* @param {string} dir - 目录路径
|
|
344
|
+
* @param {string} workPath - 工作路径
|
|
345
|
+
*/
|
|
346
|
+
function scanWxsFiles(dir, workPath) {
|
|
347
|
+
try {
|
|
348
|
+
const items = fs.readdirSync(dir);
|
|
349
|
+
for (const item of items) {
|
|
350
|
+
const fullPath = path.join(dir, item);
|
|
351
|
+
const stat = fs.statSync(fullPath);
|
|
352
|
+
if (stat.isDirectory()) scanWxsFiles(fullPath, workPath);
|
|
353
|
+
else if (stat.isFile() && getViewScriptExts().some((ext) => item.endsWith(ext))) {
|
|
354
|
+
const moduleName = stripViewScriptExt(fullPath.replace(workPath, "")).replace(/[\/\\@\-]/g, "_").replace(/^_/, "");
|
|
355
|
+
wxsFilePathMap.set(moduleName, fullPath);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 注册 wxs 模块
|
|
362
|
+
* @param {string} modulePath - 模块路径
|
|
363
|
+
*/
|
|
364
|
+
function registerWxsModule(modulePath) {
|
|
365
|
+
wxsModuleRegistry.add(modulePath);
|
|
366
|
+
}
|
|
367
|
+
function getTemplateCompilerOptions(scopeId) {
|
|
368
|
+
return {
|
|
369
|
+
prefixIdentifiers: true,
|
|
370
|
+
hoistStatic: false,
|
|
371
|
+
cacheHandlers: true,
|
|
372
|
+
scopeId,
|
|
373
|
+
mode: "function",
|
|
374
|
+
inline: true,
|
|
375
|
+
isCustomElement: (tag) => !tag.startsWith("dd-")
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* 检查是否为已注册的 wxs 模块
|
|
380
|
+
* @param {string} modulePath - 模块路径
|
|
381
|
+
* @returns {boolean} wxs 模块是否已注册
|
|
382
|
+
*/
|
|
383
|
+
function isRegisteredWxsModule(modulePath) {
|
|
384
|
+
return wxsModuleRegistry.has(modulePath);
|
|
385
|
+
}
|
|
386
|
+
function buildCompileView(module, isComponent = false, scriptRes, activePaths = /* @__PURE__ */ new Set(), inheritedTemplatePaths = /* @__PURE__ */ new Set(), sourceMapRes = /* @__PURE__ */ new Map()) {
|
|
387
|
+
const currentPath = module.path;
|
|
388
|
+
if (activePaths.has(currentPath)) return;
|
|
389
|
+
activePaths.add(currentPath);
|
|
390
|
+
const allScriptModules = [];
|
|
391
|
+
const currentInstruction = compileModule(module, isComponent, scriptRes, {
|
|
392
|
+
skipTemplatePaths: isComponent ? inheritedTemplatePaths : /* @__PURE__ */ new Set(),
|
|
393
|
+
sourceMapRes
|
|
394
|
+
});
|
|
395
|
+
if (currentInstruction && currentInstruction.scriptModule) allScriptModules.push(...currentInstruction.scriptModule);
|
|
396
|
+
const childInheritedTemplatePaths = new Set(inheritedTemplatePaths);
|
|
397
|
+
for (const tm of currentInstruction?.templateModule || []) childInheritedTemplatePaths.add(tm.path);
|
|
398
|
+
if (module.usingComponents) {
|
|
399
|
+
const graphDependencies = getDependencyGraph().getDirectDependencies(module.path, "component");
|
|
400
|
+
const componentDependencies = graphDependencies.length > 0 ? graphDependencies : Object.values(module.usingComponents);
|
|
401
|
+
for (const componentInfo of componentDependencies) {
|
|
402
|
+
const componentModule = getComponent(componentInfo);
|
|
403
|
+
if (!componentModule) continue;
|
|
404
|
+
if (componentModule.path === module.path) continue;
|
|
405
|
+
const componentInstruction = buildCompileView(componentModule, true, scriptRes, activePaths, childInheritedTemplatePaths, sourceMapRes);
|
|
406
|
+
if (componentInstruction && componentInstruction.scriptModule) {
|
|
407
|
+
for (const sm of componentInstruction.scriptModule) if (!allScriptModules.find((existing) => existing.path === sm.path)) allScriptModules.push(sm);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!isComponent && allScriptModules.length > 0) {
|
|
412
|
+
for (const sm of allScriptModules) if (!scriptRes.has(sm.path)) scriptRes.set(sm.path, sm.code);
|
|
413
|
+
compileModuleWithAllWxs(module, scriptRes, allScriptModules, sourceMapRes);
|
|
414
|
+
}
|
|
415
|
+
activePaths.delete(currentPath);
|
|
416
|
+
return {
|
|
417
|
+
scriptModule: allScriptModules,
|
|
418
|
+
templateModule: currentInstruction?.templateModule || []
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 编译页面及自定义组件,自定义组件可认为是特殊的页面
|
|
423
|
+
* https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/
|
|
424
|
+
* @param {*} module
|
|
425
|
+
*/
|
|
426
|
+
function compileModule(module, isComponent, scriptRes, options = {}) {
|
|
427
|
+
const skipTemplatePaths = options.skipTemplatePaths || /* @__PURE__ */ new Set();
|
|
428
|
+
const sourceMapRes = options.sourceMapRes || /* @__PURE__ */ new Map();
|
|
429
|
+
const { tpl, instruction, sourceInfo } = toCompileTemplate(isComponent, module.path, module.usingComponents, module.componentPlaceholder);
|
|
430
|
+
if (!tpl) return null;
|
|
431
|
+
const templateModule = instruction.templateModule || [];
|
|
432
|
+
const templateModuleForCompile = templateModule.filter((tm) => !skipTemplatePaths.has(tm.path));
|
|
433
|
+
const compileInstruction = {
|
|
434
|
+
...instruction,
|
|
435
|
+
templateModule: templateModuleForCompile
|
|
436
|
+
};
|
|
437
|
+
let useCache = false;
|
|
438
|
+
let cachedCode = null;
|
|
439
|
+
const canUseCache = skipTemplatePaths.size === 0;
|
|
440
|
+
if (canUseCache && !scriptRes.has(module.path) && compileResCache.has(module.path)) {
|
|
441
|
+
const cacheData = compileResCache.get(module.path);
|
|
442
|
+
if (cacheData && typeof cacheData === "object" && cacheData.code && cacheData.instruction) {
|
|
443
|
+
cachedCode = cacheData.code;
|
|
444
|
+
useCache = true;
|
|
445
|
+
for (const sm of cacheData.instruction.scriptModule) if (!scriptRes.has(sm.path)) scriptRes.set(sm.path, sm.code);
|
|
446
|
+
} else if (typeof cacheData === "string") {
|
|
447
|
+
cachedCode = cacheData;
|
|
448
|
+
useCache = true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (useCache && cachedCode) {
|
|
452
|
+
scriptRes.set(module.path, cachedCode);
|
|
453
|
+
const cachedMap = compileResCache.get(module.path)?.map;
|
|
454
|
+
if (enableSourcemap && cachedMap) sourceMapRes.set(module.path, cachedMap);
|
|
455
|
+
const allWxsModules = collectAllWxsModules(scriptRes, /* @__PURE__ */ new Set(), instruction.scriptModule || []);
|
|
456
|
+
if (allWxsModules.length > 0) {
|
|
457
|
+
const mergedModules = [...instruction.scriptModule || []];
|
|
458
|
+
for (const wxsModule of allWxsModules) if (!mergedModules.find((existing) => existing.path === wxsModule.path)) mergedModules.push(wxsModule);
|
|
459
|
+
instruction.scriptModule = mergedModules;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
...instruction,
|
|
463
|
+
scriptModule: allWxsModules
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const processedTpl = tpl.replace(/\bthis\./g, "_ctx.");
|
|
467
|
+
const tplCode = compileTemplate({
|
|
468
|
+
source: processedTpl,
|
|
469
|
+
filename: module.path,
|
|
470
|
+
id: `data-v-${module.id}`,
|
|
471
|
+
scoped: true,
|
|
472
|
+
inMap: enableSourcemap ? createLineSourcemap(processedTpl, sourceInfo.path, sourceInfo.content) : void 0,
|
|
473
|
+
compilerOptions: getTemplateCompilerOptions(`data-v-${module.id}`)
|
|
474
|
+
});
|
|
475
|
+
const templateResults = [];
|
|
476
|
+
for (const tm of compileInstruction.templateModule) {
|
|
477
|
+
const compiledTemplate = compileTemplate({
|
|
478
|
+
source: tm.tpl,
|
|
479
|
+
filename: tm.path,
|
|
480
|
+
id: `data-v-${module.id}`,
|
|
481
|
+
scoped: true,
|
|
482
|
+
inMap: enableSourcemap && tm.sourceInfo ? createLineSourcemap(tm.tpl, tm.sourceInfo.path, tm.sourceInfo.content, tm.sourceInfo.startLine) : void 0,
|
|
483
|
+
compilerOptions: getTemplateCompilerOptions(`data-v-${module.id}`)
|
|
484
|
+
});
|
|
485
|
+
templateResults.push({
|
|
486
|
+
path: tm.path,
|
|
487
|
+
...insertWxsToRenderResult(compiledTemplate.code, compileInstruction.scriptModule, scriptRes, tm.path, compiledTemplate.map)
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const renderResult = insertWxsToRenderResult(tplCode.code, compileInstruction.scriptModule, scriptRes, module.path, tplCode.map);
|
|
491
|
+
const moduleChunks = [
|
|
492
|
+
`Module({
|
|
493
|
+
path: '${module.path}',
|
|
494
|
+
id: '${module.id}',
|
|
495
|
+
appStyleScopeId: ${JSON.stringify(module.appStyleScopeId || null)},
|
|
496
|
+
sharedStyleScopeIds: ${JSON.stringify(module.sharedStyleScopeIds || [])},
|
|
497
|
+
styleIsolation: ${JSON.stringify(module.styleIsolation || "isolated")},
|
|
498
|
+
render: `,
|
|
499
|
+
renderResult,
|
|
500
|
+
`,
|
|
501
|
+
usingComponents: ${JSON.stringify(module.usingComponents)},
|
|
502
|
+
componentPlaceholder: ${JSON.stringify(module.componentPlaceholder || {})},
|
|
503
|
+
customTabBar: ${JSON.stringify(module.customTabBar || null)},
|
|
504
|
+
tplComponents: {`
|
|
505
|
+
];
|
|
506
|
+
for (const templateResult of templateResults) moduleChunks.push(`'${templateResult.path}':`, templateResult, ",");
|
|
507
|
+
moduleChunks.push("},\n });");
|
|
508
|
+
const { code, sourcemap: moduleMap } = concatSourcemap(moduleChunks, module.path);
|
|
509
|
+
const allWxsModules = collectAllWxsModules(scriptRes, /* @__PURE__ */ new Set(), compileInstruction.scriptModule || []);
|
|
510
|
+
if (allWxsModules.length > 0) {
|
|
511
|
+
const mergedModules = [...compileInstruction.scriptModule || []];
|
|
512
|
+
for (const wxsModule of allWxsModules) if (!mergedModules.find((existing) => existing.path === wxsModule.path)) mergedModules.push(wxsModule);
|
|
513
|
+
compileInstruction.scriptModule = mergedModules;
|
|
514
|
+
}
|
|
515
|
+
const cacheData = {
|
|
516
|
+
code,
|
|
517
|
+
instruction: compileInstruction,
|
|
518
|
+
map: enableSourcemap ? moduleMap : null
|
|
519
|
+
};
|
|
520
|
+
if (canUseCache) compileResCache.set(module.path, cacheData);
|
|
521
|
+
scriptRes.set(module.path, code);
|
|
522
|
+
if (enableSourcemap) sourceMapRes.set(module.path, moduleMap);
|
|
523
|
+
return {
|
|
524
|
+
...compileInstruction,
|
|
525
|
+
templateModule
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* 处理 wxs 内容,包括注入全局方法、转换 constructor、处理 require 等
|
|
530
|
+
* @param {string} wxsContent - wxs 代码内容
|
|
531
|
+
* @param {string} wxsFilePath - wxs 文件路径(用于处理 require)
|
|
532
|
+
* @param {Array} scriptModule - 脚本模块数组
|
|
533
|
+
* @param {string} workPath - 工作路径
|
|
534
|
+
* @param {string} filePath - 当前处理的文件路径
|
|
535
|
+
* @returns {string} 处理后的 wxs 代码
|
|
536
|
+
*/
|
|
537
|
+
function processWxsContent(wxsContent, wxsFilePath, scriptModule, workPath, filePath, graphOwnerPath = filePath) {
|
|
538
|
+
if (wxsFilePath && graphOwnerPath) getDependencyGraph().addFile(graphOwnerPath, wxsFilePath, "view");
|
|
539
|
+
let wxsAst;
|
|
540
|
+
try {
|
|
541
|
+
wxsAst = parseJs(wxsContent, wxsFilePath || "inline.wxs", "script");
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.error(`[view] 解析 wxs 文件失败: ${wxsFilePath}`, error.message);
|
|
544
|
+
return wxsContent;
|
|
545
|
+
}
|
|
546
|
+
const replacements = [];
|
|
547
|
+
walk(wxsAst, { enter(node) {
|
|
548
|
+
if (node.type === "CallExpression") {
|
|
549
|
+
const calleeName = node.callee?.name;
|
|
550
|
+
if (calleeName === "getRegExp") {
|
|
551
|
+
const args = node.arguments;
|
|
552
|
+
if (args.length > 0) if (isStringLiteral(args[0]) && (!args[1] || isStringLiteral(args[1]))) {
|
|
553
|
+
const pattern = getStringLiteralRawValue(args[0]);
|
|
554
|
+
const flags = args.length > 1 ? getStringLiteralRawValue(args[1]) : "";
|
|
555
|
+
replacements.push({
|
|
556
|
+
start: node.start,
|
|
557
|
+
end: node.end,
|
|
558
|
+
value: `/${pattern}/${flags}`
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
const newRegExpArgs = args.map((arg) => getSource(wxsContent, arg)).join(", ");
|
|
562
|
+
replacements.push({
|
|
563
|
+
start: node.start,
|
|
564
|
+
end: node.end,
|
|
565
|
+
value: `new RegExp(${newRegExpArgs})`
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
} else if (calleeName === "getDate") {
|
|
569
|
+
const args = node.arguments.map((arg) => getSource(wxsContent, arg)).join(", ");
|
|
570
|
+
replacements.push({
|
|
571
|
+
start: node.start,
|
|
572
|
+
end: node.end,
|
|
573
|
+
value: `new Date(${args})`
|
|
574
|
+
});
|
|
575
|
+
} else if (calleeName === "require" && node.arguments.length > 0 && wxsFilePath) {
|
|
576
|
+
const requirePath = node.arguments[0].value;
|
|
577
|
+
if (requirePath && typeof requirePath === "string") {
|
|
578
|
+
let resolvedWxsPath;
|
|
579
|
+
if (filePath && filePath.includes("/miniprogram_npm/")) {
|
|
580
|
+
const currentWxsDir = path.dirname(wxsFilePath);
|
|
581
|
+
resolvedWxsPath = path.resolve(currentWxsDir, requirePath);
|
|
582
|
+
const moduleName = stripViewScriptExt(resolvedWxsPath.replace(workPath, "")).replace(/[\/\\@\-]/g, "_").replace(/^_/, "");
|
|
583
|
+
processWxsDependency(resolvedWxsPath, moduleName, scriptModule, workPath, filePath, graphOwnerPath);
|
|
584
|
+
replacements.push({
|
|
585
|
+
start: node.arguments[0].start,
|
|
586
|
+
end: node.arguments[0].end,
|
|
587
|
+
value: JSON.stringify(moduleName)
|
|
588
|
+
});
|
|
589
|
+
} else {
|
|
590
|
+
const currentWxsDir = path.dirname(wxsFilePath);
|
|
591
|
+
resolvedWxsPath = path.resolve(currentWxsDir, requirePath);
|
|
592
|
+
const depModuleName = stripViewScriptExt(resolvedWxsPath.replace(workPath, "")).replace(/[\/\\@\-]/g, "_").replace(/^_/, "");
|
|
593
|
+
processWxsDependency(resolvedWxsPath, depModuleName, scriptModule, workPath, filePath, graphOwnerPath);
|
|
594
|
+
replacements.push({
|
|
595
|
+
start: node.arguments[0].start,
|
|
596
|
+
end: node.arguments[0].end,
|
|
597
|
+
value: JSON.stringify(depModuleName)
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (node.type === "MemberExpression") {
|
|
604
|
+
if (node.property?.name === "constructor" && !node.computed) {
|
|
605
|
+
const objectCode = getSource(wxsContent, node.object);
|
|
606
|
+
replacements.push({
|
|
607
|
+
start: node.start,
|
|
608
|
+
end: node.end,
|
|
609
|
+
value: `Object.prototype.toString.call(${objectCode}).slice(8, -1)`
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} });
|
|
614
|
+
return applyCodeReplacements(wxsContent, replacements);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* 通过代码内容判断是否为 wxs 模块
|
|
618
|
+
* @param {string} moduleCode - 模块代码
|
|
619
|
+
* @param {string} modulePath - 模块路径(可选,用于路径判断)
|
|
620
|
+
* @returns {boolean} 是否为 wxs 模块
|
|
621
|
+
*/
|
|
622
|
+
function isWxsModuleByContent(moduleCode, modulePath = "") {
|
|
623
|
+
if (!moduleCode || typeof moduleCode !== "string") return false;
|
|
624
|
+
if (modulePath && isRegisteredWxsModule(modulePath)) return true;
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
function processWxsDependency(wxsFilePath, moduleName, scriptModule, workPath, filePath, graphOwnerPath = filePath) {
|
|
628
|
+
if (!fs.existsSync(wxsFilePath)) {
|
|
629
|
+
console.warn(`[view] wxs 依赖文件不存在: ${wxsFilePath}`);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (scriptModule.find((sm) => sm.path === moduleName)) return;
|
|
633
|
+
const wxsContent = getContentByPath(wxsFilePath).trim();
|
|
634
|
+
if (!wxsContent) return;
|
|
635
|
+
const wxsCode = processWxsContent(wxsContent, wxsFilePath, scriptModule, workPath, filePath, graphOwnerPath);
|
|
636
|
+
registerWxsModule(moduleName);
|
|
637
|
+
scriptModule.push({
|
|
638
|
+
path: moduleName,
|
|
639
|
+
code: wxsCode
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* 重新编译模块,包含所有收集到的 wxs 模块
|
|
644
|
+
* @param {*} module
|
|
645
|
+
* @param {*} scriptRes
|
|
646
|
+
* @param {*} allScriptModules
|
|
647
|
+
*/
|
|
648
|
+
function compileModuleWithAllWxs(module, scriptRes, allScriptModules, sourceMapRes = /* @__PURE__ */ new Map()) {
|
|
649
|
+
const { tpl, instruction, sourceInfo } = toCompileTemplate(false, module.path, module.usingComponents, module.componentPlaceholder);
|
|
650
|
+
if (!tpl) return;
|
|
651
|
+
const mergedInstruction = {
|
|
652
|
+
...instruction,
|
|
653
|
+
scriptModule: allScriptModules
|
|
654
|
+
};
|
|
655
|
+
const processedTpl = tpl.replace(/\bthis\./g, "_ctx.");
|
|
656
|
+
const tplCode = compileTemplate({
|
|
657
|
+
source: processedTpl,
|
|
658
|
+
filename: module.path,
|
|
659
|
+
id: `data-v-${module.id}`,
|
|
660
|
+
scoped: true,
|
|
661
|
+
inMap: enableSourcemap ? createLineSourcemap(processedTpl, sourceInfo.path, sourceInfo.content) : void 0,
|
|
662
|
+
compilerOptions: getTemplateCompilerOptions(`data-v-${module.id}`)
|
|
663
|
+
});
|
|
664
|
+
const templateResults = [];
|
|
665
|
+
for (const tm of mergedInstruction.templateModule) {
|
|
666
|
+
const compiledTemplate = compileTemplate({
|
|
667
|
+
source: tm.tpl,
|
|
668
|
+
filename: tm.path,
|
|
669
|
+
id: `data-v-${module.id}`,
|
|
670
|
+
scoped: true,
|
|
671
|
+
inMap: enableSourcemap && tm.sourceInfo ? createLineSourcemap(tm.tpl, tm.sourceInfo.path, tm.sourceInfo.content, tm.sourceInfo.startLine) : void 0,
|
|
672
|
+
compilerOptions: getTemplateCompilerOptions(`data-v-${module.id}`)
|
|
673
|
+
});
|
|
674
|
+
templateResults.push({
|
|
675
|
+
path: tm.path,
|
|
676
|
+
...insertWxsToRenderResult(compiledTemplate.code, allScriptModules, scriptRes, tm.path, compiledTemplate.map)
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const renderResult = insertWxsToRenderResult(tplCode.code, allScriptModules, scriptRes, module.path, tplCode.map);
|
|
680
|
+
const moduleChunks = [
|
|
681
|
+
`Module({
|
|
682
|
+
path: '${module.path}',
|
|
683
|
+
id: '${module.id}',
|
|
684
|
+
appStyleScopeId: ${JSON.stringify(module.appStyleScopeId || null)},
|
|
685
|
+
sharedStyleScopeIds: ${JSON.stringify(module.sharedStyleScopeIds || [])},
|
|
686
|
+
styleIsolation: ${JSON.stringify(module.styleIsolation || "isolated")},
|
|
687
|
+
render: `,
|
|
688
|
+
renderResult,
|
|
689
|
+
`,
|
|
690
|
+
usingComponents: ${JSON.stringify(module.usingComponents)},
|
|
691
|
+
componentPlaceholder: ${JSON.stringify(module.componentPlaceholder || {})},
|
|
692
|
+
customTabBar: ${JSON.stringify(module.customTabBar || null)},
|
|
693
|
+
tplComponents: {`
|
|
694
|
+
];
|
|
695
|
+
for (const templateResult of templateResults) moduleChunks.push(`'${templateResult.path}':`, templateResult, ",");
|
|
696
|
+
moduleChunks.push("},\n });");
|
|
697
|
+
const { code, sourcemap: moduleMap } = concatSourcemap(moduleChunks, module.path);
|
|
698
|
+
const cacheData = {
|
|
699
|
+
code,
|
|
700
|
+
instruction: mergedInstruction,
|
|
701
|
+
map: enableSourcemap ? moduleMap : null
|
|
702
|
+
};
|
|
703
|
+
compileResCache.set(module.path, cacheData);
|
|
704
|
+
scriptRes.set(module.path, code);
|
|
705
|
+
if (enableSourcemap) sourceMapRes.set(module.path, moduleMap);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* 处理 include 节点的条件属性,并返回处理后的内容
|
|
709
|
+
* @param {Object} $ - cheerio 实例
|
|
710
|
+
* @param {Object} elem - include 元素
|
|
711
|
+
* @param {string} includeContent - 要包含的内容
|
|
712
|
+
* @returns {string} 处理后的 HTML 字符串
|
|
713
|
+
*/
|
|
714
|
+
function processIncludeConditionalAttrs($, elem, includeContent) {
|
|
715
|
+
const allAttrs = $(elem).attr();
|
|
716
|
+
const conditionAttrs = {};
|
|
717
|
+
let hasCondition = false;
|
|
718
|
+
for (const attrName in allAttrs) if ([
|
|
719
|
+
"if",
|
|
720
|
+
"elif",
|
|
721
|
+
"else"
|
|
722
|
+
].includes(getTemplateDirectiveName(attrName))) {
|
|
723
|
+
conditionAttrs[attrName] = allAttrs[attrName];
|
|
724
|
+
hasCondition = true;
|
|
725
|
+
}
|
|
726
|
+
if (hasCondition) {
|
|
727
|
+
let blockAttrs = "";
|
|
728
|
+
for (const attrName in conditionAttrs) {
|
|
729
|
+
const attrValue = conditionAttrs[attrName];
|
|
730
|
+
if (attrValue !== void 0 && attrValue !== "") blockAttrs += ` ${attrName}="${attrValue}"`;
|
|
731
|
+
else blockAttrs += ` ${attrName}`;
|
|
732
|
+
}
|
|
733
|
+
return `<block${blockAttrs}>${includeContent}</block>`;
|
|
734
|
+
} else return includeContent;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* 递归处理被引入文件中的组件 wxs 依赖
|
|
738
|
+
* @param {*} content 被引入文件的内容
|
|
739
|
+
* @param {*} includePath 被引入文件的路径
|
|
740
|
+
* @param {*} scriptModule 用于收集 wxs 模块的数组
|
|
741
|
+
* @param {*} components 当前可用的组件映射
|
|
742
|
+
* @param {Set} processedPaths 已处理的路径集合,防止循环引用和栈溢出
|
|
743
|
+
*/
|
|
744
|
+
function processIncludedFileWxsDependencies(content, includePath, scriptModule, components, processedPaths = /* @__PURE__ */ new Set()) {
|
|
745
|
+
if (processedPaths.has(includePath)) return;
|
|
746
|
+
processedPaths.add(includePath);
|
|
747
|
+
const $ = cheerio.load(content, {
|
|
748
|
+
xmlMode: true,
|
|
749
|
+
decodeEntities: false,
|
|
750
|
+
_useHtmlParser2: true,
|
|
751
|
+
lowerCaseTags: false,
|
|
752
|
+
lowerCaseAttributeNames: false
|
|
753
|
+
});
|
|
754
|
+
const componentTags = /* @__PURE__ */ new Set();
|
|
755
|
+
$("*").each((_, elem) => {
|
|
756
|
+
const tagName = elem.tagName;
|
|
757
|
+
if (components && components[tagName]) componentTags.add(tagName);
|
|
758
|
+
});
|
|
759
|
+
for (const tagName of componentTags) {
|
|
760
|
+
const componentPath = components[tagName];
|
|
761
|
+
const componentModule = getComponent(componentPath);
|
|
762
|
+
if (componentModule) {
|
|
763
|
+
if (processedPaths.has(componentModule.path)) continue;
|
|
764
|
+
const componentTemplate = toCompileTemplate(true, componentModule.path, componentModule.usingComponents, componentModule.componentPlaceholder, processedPaths);
|
|
765
|
+
if (componentTemplate && componentTemplate.instruction && componentTemplate.instruction.scriptModule) {
|
|
766
|
+
for (const sm of componentTemplate.instruction.scriptModule) if (!scriptModule.find((existing) => existing.path === sm.path)) scriptModule.push(sm);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* 转换成底层框架模板
|
|
773
|
+
* @param {*} isComponent
|
|
774
|
+
* @param {*} path
|
|
775
|
+
* @param {*} components
|
|
776
|
+
* @param {*} componentPlaceholder
|
|
777
|
+
* @param {Set} processedPaths 已处理的路径集合,防止循环引用和栈溢出
|
|
778
|
+
*/
|
|
779
|
+
function toCompileTemplate(isComponent, path, components, componentPlaceholder, processedPaths = /* @__PURE__ */ new Set()) {
|
|
780
|
+
const workPath = getWorkPath();
|
|
781
|
+
const fullPath = getViewPath(workPath, path);
|
|
782
|
+
if (!fullPath) return { tpl: void 0 };
|
|
783
|
+
getDependencyGraph().addFile(path, fullPath, "view");
|
|
784
|
+
const sourcePath = toMiniProgramModuleId(fullPath, workPath).replace(buildExtStripRegex(getTemplateExts()), "");
|
|
785
|
+
const diagnosticSource = fullPath.startsWith(workPath) ? fullPath.slice(workPath.length) : path;
|
|
786
|
+
const originalContent = getContentByPath(fullPath);
|
|
787
|
+
let content = originalContent;
|
|
788
|
+
if (!content.trim()) content = "<block></block>";
|
|
789
|
+
else {
|
|
790
|
+
checkTemplateCompatibility(content, diagnosticSource, components);
|
|
791
|
+
if (isComponent) content = `<component-host name="${path}">${content}</component-host>`;
|
|
792
|
+
else if (cheerio.load(content, {
|
|
793
|
+
xmlMode: true,
|
|
794
|
+
decodeEntities: false
|
|
795
|
+
}).root().children().toArray().filter((node) => node.type !== "comment").length > 1) content = `<view>${content}</view>`;
|
|
796
|
+
}
|
|
797
|
+
const templateModule = [];
|
|
798
|
+
const scriptModule = [];
|
|
799
|
+
const $ = cheerio.load(content, {
|
|
800
|
+
xmlMode: true,
|
|
801
|
+
decodeEntities: false,
|
|
802
|
+
_useHtmlParser2: true,
|
|
803
|
+
lowerCaseTags: false,
|
|
804
|
+
lowerCaseAttributeNames: false,
|
|
805
|
+
withStartIndices: true,
|
|
806
|
+
withEndIndices: true
|
|
807
|
+
});
|
|
808
|
+
$("include").each((_, elem) => {
|
|
809
|
+
const src = $(elem).attr("src");
|
|
810
|
+
if (src) {
|
|
811
|
+
const includeFullPath = resolveTemplateDependencyPath(workPath, sourcePath, src);
|
|
812
|
+
getDependencyGraph().addFile(path, includeFullPath, "view");
|
|
813
|
+
let includePath = includeFullPath.replace(workPath, "").replace(buildExtStripRegex(getTemplateExts()), "");
|
|
814
|
+
const includeDiagnosticSource = includeFullPath.startsWith(workPath) ? includeFullPath.slice(workPath.length) : includePath;
|
|
815
|
+
if (!includePath.startsWith("/")) includePath = "/" + includePath;
|
|
816
|
+
const includeContent = getContentByPath(includeFullPath);
|
|
817
|
+
if (includeContent.trim()) {
|
|
818
|
+
checkTemplateCompatibility(includeContent, includeDiagnosticSource, components);
|
|
819
|
+
const $includeContent = cheerio.load(includeContent, {
|
|
820
|
+
xmlMode: true,
|
|
821
|
+
decodeEntities: false,
|
|
822
|
+
_useHtmlParser2: true,
|
|
823
|
+
withStartIndices: true,
|
|
824
|
+
withEndIndices: true
|
|
825
|
+
});
|
|
826
|
+
transTagTemplate($includeContent, templateModule, includePath, components, componentPlaceholder, {
|
|
827
|
+
path: includeDiagnosticSource,
|
|
828
|
+
content: includeContent
|
|
829
|
+
}, path);
|
|
830
|
+
transTagWxs($includeContent, scriptModule, includePath, path);
|
|
831
|
+
processIncludedFileWxsDependencies(includeContent, includePath, scriptModule, components, processedPaths);
|
|
832
|
+
$includeContent("template").remove();
|
|
833
|
+
$includeContent(getViewScriptTags().join(",")).remove();
|
|
834
|
+
const processedContent = processIncludeConditionalAttrs($, elem, $includeContent.html());
|
|
835
|
+
$(elem).replaceWith(processedContent);
|
|
836
|
+
} else $(elem).remove();
|
|
837
|
+
} else $(elem).remove();
|
|
838
|
+
});
|
|
839
|
+
transTagTemplate($, templateModule, sourcePath, components, componentPlaceholder, {
|
|
840
|
+
path: diagnosticSource,
|
|
841
|
+
content: originalContent
|
|
842
|
+
}, path);
|
|
843
|
+
transTagWxs($, scriptModule, sourcePath, path);
|
|
844
|
+
const importNodes = $("import");
|
|
845
|
+
importNodes.each((_, elem) => {
|
|
846
|
+
const src = $(elem).attr("src");
|
|
847
|
+
if (src) {
|
|
848
|
+
const importFullPath = resolveTemplateDependencyPath(workPath, sourcePath, src);
|
|
849
|
+
getDependencyGraph().addFile(path, importFullPath, "view");
|
|
850
|
+
let importPath = importFullPath.replace(workPath, "").replace(buildExtStripRegex(getTemplateExts()), "");
|
|
851
|
+
const importDiagnosticSource = importFullPath.startsWith(workPath) ? importFullPath.slice(workPath.length) : importPath;
|
|
852
|
+
if (!importPath.startsWith("/")) importPath = "/" + importPath;
|
|
853
|
+
const importContent = getContentByPath(importFullPath);
|
|
854
|
+
if (importContent.trim()) {
|
|
855
|
+
checkTemplateCompatibility(importContent, importDiagnosticSource, components);
|
|
856
|
+
const $$ = cheerio.load(importContent, {
|
|
857
|
+
xmlMode: true,
|
|
858
|
+
decodeEntities: false,
|
|
859
|
+
_useHtmlParser2: true,
|
|
860
|
+
withStartIndices: true,
|
|
861
|
+
withEndIndices: true
|
|
862
|
+
});
|
|
863
|
+
transTagTemplate($$, templateModule, importPath, components, componentPlaceholder, {
|
|
864
|
+
path: importDiagnosticSource,
|
|
865
|
+
content: importContent
|
|
866
|
+
}, path);
|
|
867
|
+
transTagWxs($$, scriptModule, importPath, path);
|
|
868
|
+
processIncludedFileWxsDependencies(importContent, importPath, scriptModule, components, processedPaths);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
importNodes.remove();
|
|
873
|
+
transAsses($, $("image"), sourcePath, path);
|
|
874
|
+
const res = [];
|
|
875
|
+
transHtmlTag($.html(), res, components, componentPlaceholder);
|
|
876
|
+
return {
|
|
877
|
+
tpl: res.join(""),
|
|
878
|
+
sourceInfo: {
|
|
879
|
+
path: diagnosticSource,
|
|
880
|
+
content: originalContent
|
|
881
|
+
},
|
|
882
|
+
instruction: {
|
|
883
|
+
templateModule,
|
|
884
|
+
scriptModule
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function transTagTemplate($, templateModule, path, components, componentPlaceholder, sourceInfo, graphOwnerPath = path) {
|
|
889
|
+
const templateNodes = $("template[name]");
|
|
890
|
+
templateNodes.each((_, elem) => {
|
|
891
|
+
const name = $(elem).attr("name");
|
|
892
|
+
const templateContent = $(elem);
|
|
893
|
+
templateContent.find("import").remove();
|
|
894
|
+
templateContent.find("include").remove();
|
|
895
|
+
templateContent.find(getViewScriptTags().join(",")).remove();
|
|
896
|
+
transAsses($, templateContent.find("image"), path, graphOwnerPath);
|
|
897
|
+
const res = [];
|
|
898
|
+
transHtmlTag(templateContent.html(), res, components, componentPlaceholder);
|
|
899
|
+
templateModule.push({
|
|
900
|
+
path: `tpl-${name}`,
|
|
901
|
+
tpl: res.join(""),
|
|
902
|
+
sourceInfo: sourceInfo ? {
|
|
903
|
+
...sourceInfo,
|
|
904
|
+
startLine: getSourceLine(sourceInfo.content, elem.children?.[0]?.startIndex ?? elem.startIndex)
|
|
905
|
+
} : null
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
templateNodes.remove();
|
|
909
|
+
}
|
|
910
|
+
function getSourceLine(source, index = 0) {
|
|
911
|
+
return source.slice(0, Math.max(0, index)).split("\n").length;
|
|
912
|
+
}
|
|
913
|
+
function transAsses($, imageNodes, path, graphOwnerPath = path) {
|
|
914
|
+
imageNodes.each((_, elem) => {
|
|
915
|
+
const imgSrc = $(elem).attr("src").trim();
|
|
916
|
+
if (!imgSrc.startsWith("{{")) {
|
|
917
|
+
if (!imgSrc.startsWith("http") && !imgSrc.startsWith("//") && /\.(?:png|jpe?g|gif|svg)(?:\?.*)?$/.test(imgSrc)) getDependencyGraph().addFile(graphOwnerPath, resolveAssetSourcePath(getWorkPath(), path, imgSrc), "view");
|
|
918
|
+
$(elem).attr("src", collectAssets(getWorkPath(), path, imgSrc, getTargetPath(), getAppId()));
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
var DIMINA_SLOT_GROUP_TAG = "dimina-slot-group";
|
|
923
|
+
var DIMINA_FOR_SCOPE_TAG = "dimina-for-scope";
|
|
924
|
+
function getDirectiveAttributeNames(attrs, suffixes) {
|
|
925
|
+
const directiveNames = new Set(suffixes.map((suffix) => suffix.replace(/^:/, "")));
|
|
926
|
+
return Object.keys(attrs || {}).filter((name) => directiveNames.has(getTemplateDirectiveName(name)));
|
|
927
|
+
}
|
|
928
|
+
function hasForAndIf(attrs) {
|
|
929
|
+
return getDirectiveAttributeNames(attrs, [":for", ":for-items"]).length > 0 && getDirectiveAttributeNames(attrs, [":if"]).length > 0;
|
|
930
|
+
}
|
|
931
|
+
function groupDuplicateNamedSlots($, components) {
|
|
932
|
+
const slotHosts = $("*").toArray().filter((element) => {
|
|
933
|
+
const tag = element.tagName;
|
|
934
|
+
return tag === "component" || Boolean(components?.[tag]);
|
|
935
|
+
});
|
|
936
|
+
for (const host of slotHosts) {
|
|
937
|
+
const groups = /* @__PURE__ */ new Map();
|
|
938
|
+
for (const child of $(host).children().toArray()) {
|
|
939
|
+
const slotName = child.attribs?.slot;
|
|
940
|
+
if (!slotName) continue;
|
|
941
|
+
const group = groups.get(slotName) || [];
|
|
942
|
+
group.push(child);
|
|
943
|
+
groups.set(slotName, group);
|
|
944
|
+
}
|
|
945
|
+
for (const [slotName, nodes] of groups) {
|
|
946
|
+
if (nodes.length === 1 && !hasForAndIf(nodes[0].attribs)) continue;
|
|
947
|
+
const wrapper = $(`<${DIMINA_SLOT_GROUP_TAG}></${DIMINA_SLOT_GROUP_TAG}>`);
|
|
948
|
+
wrapper.attr("name", slotName);
|
|
949
|
+
$(nodes[0]).before(wrapper);
|
|
950
|
+
for (const node of nodes) {
|
|
951
|
+
$(node).removeAttr("slot");
|
|
952
|
+
wrapper.append(node);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function wrapForIfScopes($) {
|
|
958
|
+
const nodes = $("*").toArray();
|
|
959
|
+
for (const node of nodes) {
|
|
960
|
+
if (node.tagName === DIMINA_FOR_SCOPE_TAG || !hasForAndIf(node.attribs)) continue;
|
|
961
|
+
const attrsToMove = getDirectiveAttributeNames(node.attribs, [
|
|
962
|
+
":for",
|
|
963
|
+
":for-items",
|
|
964
|
+
":for-item",
|
|
965
|
+
":for-index",
|
|
966
|
+
":key"
|
|
967
|
+
]);
|
|
968
|
+
const wrapper = $(`<${DIMINA_FOR_SCOPE_TAG}></${DIMINA_FOR_SCOPE_TAG}>`);
|
|
969
|
+
for (const name of attrsToMove) {
|
|
970
|
+
wrapper.attr(name, node.attribs[name]);
|
|
971
|
+
$(node).removeAttr(name);
|
|
972
|
+
}
|
|
973
|
+
$(node).before(wrapper);
|
|
974
|
+
wrapper.append(node);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function normalizeTemplateSyntax(html, components) {
|
|
978
|
+
const $ = cheerio.load(html, {
|
|
979
|
+
xmlMode: true,
|
|
980
|
+
decodeEntities: false,
|
|
981
|
+
_useHtmlParser2: true,
|
|
982
|
+
lowerCaseTags: false,
|
|
983
|
+
lowerCaseAttributeNames: false
|
|
984
|
+
});
|
|
985
|
+
groupDuplicateNamedSlots($, components);
|
|
986
|
+
wrapForIfScopes($);
|
|
987
|
+
return $.html();
|
|
988
|
+
}
|
|
989
|
+
function transHtmlTag(html, res, components, componentPlaceholder) {
|
|
990
|
+
const attrsList = [];
|
|
991
|
+
const parser = new htmlparser2.Parser({
|
|
992
|
+
onopentag(tag, attrs) {
|
|
993
|
+
attrsList.push(attrs);
|
|
994
|
+
res.push(transTag({
|
|
995
|
+
isStart: true,
|
|
996
|
+
tag,
|
|
997
|
+
attrs,
|
|
998
|
+
components,
|
|
999
|
+
componentPlaceholder
|
|
1000
|
+
}));
|
|
1001
|
+
},
|
|
1002
|
+
ontext(text) {
|
|
1003
|
+
res.push(transformTextInterpolation(text));
|
|
1004
|
+
},
|
|
1005
|
+
onclosetag(tag) {
|
|
1006
|
+
res.push(transTag({
|
|
1007
|
+
tag,
|
|
1008
|
+
attrs: attrsList.pop(),
|
|
1009
|
+
components
|
|
1010
|
+
}));
|
|
1011
|
+
},
|
|
1012
|
+
onerror(error) {
|
|
1013
|
+
console.error(error);
|
|
1014
|
+
}
|
|
1015
|
+
}, { xmlMode: true });
|
|
1016
|
+
parser.write(normalizeTemplateSyntax(html, components));
|
|
1017
|
+
parser.end();
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* 处理组件标签
|
|
1021
|
+
* @param {*} opts
|
|
1022
|
+
*/
|
|
1023
|
+
function transTag(opts) {
|
|
1024
|
+
const { isStart, tag, attrs, components } = opts;
|
|
1025
|
+
let res;
|
|
1026
|
+
if (tag === DIMINA_SLOT_GROUP_TAG) {
|
|
1027
|
+
if (isStart) return `<template ${generateSlotDirective(attrs.name)}>`;
|
|
1028
|
+
return "</template>";
|
|
1029
|
+
} else if (tag === DIMINA_FOR_SCOPE_TAG) res = "template";
|
|
1030
|
+
else if (tag === "slot") res = tag;
|
|
1031
|
+
else if (components && components[tag]) res = `dd-${tag}`;
|
|
1032
|
+
else if (tag === "component" || tag === "canvas") res = tag;
|
|
1033
|
+
else if (tagWhiteList.includes(tag)) res = `dd-${tag}`;
|
|
1034
|
+
else res = tag;
|
|
1035
|
+
let tagRes;
|
|
1036
|
+
const propsAry = isStart ? getProps(attrs, tag, components) : [];
|
|
1037
|
+
const multipleSlots = attrs?.slot;
|
|
1038
|
+
if (attrs?.slot) {
|
|
1039
|
+
const isDynamicSlot = isWrappedByBraces(multipleSlots);
|
|
1040
|
+
if (isStart) {
|
|
1041
|
+
const withVIf = [];
|
|
1042
|
+
const withoutVIf = [];
|
|
1043
|
+
for (let i = 0; i < propsAry.length; i++) {
|
|
1044
|
+
const prop = propsAry[i];
|
|
1045
|
+
if (prop.includes("v-if") || prop.includes("v-else-if") || prop.includes("v-else")) withVIf.push(prop);
|
|
1046
|
+
else if (!prop.includes("slot")) withoutVIf.push(prop);
|
|
1047
|
+
}
|
|
1048
|
+
const vIfProps = withVIf.length > 0 ? `${withVIf.join(" ")} ` : "";
|
|
1049
|
+
const vOtherProps = withoutVIf.length > 0 ? ` ${withoutVIf.join(" ")}` : "";
|
|
1050
|
+
const templateContent = `<template ${vIfProps}${generateSlotDirective(multipleSlots)}><${res}${vOtherProps}>`;
|
|
1051
|
+
if (isDynamicSlot) tagRes = `<dd-block>${templateContent}`;
|
|
1052
|
+
else tagRes = templateContent;
|
|
1053
|
+
} else if (isDynamicSlot) tagRes = `</${res}></template></dd-block>`;
|
|
1054
|
+
else tagRes = `</${res}></template>`;
|
|
1055
|
+
} else if (isStart) {
|
|
1056
|
+
const props = propsAry.join(" ");
|
|
1057
|
+
tagRes = props ? `<${res} ${props}>` : `<${res}>`;
|
|
1058
|
+
} else tagRes = `</${res}>`;
|
|
1059
|
+
return tagRes;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* 处理动态slot指令生成,比如 slot="{{xxx}}"
|
|
1063
|
+
*
|
|
1064
|
+
* @param {string} slotValue - slot属性值
|
|
1065
|
+
* @returns {string} 生成的slot指令
|
|
1066
|
+
*/
|
|
1067
|
+
function generateSlotDirective(slotValue) {
|
|
1068
|
+
if (isWrappedByBraces(slotValue)) return `#[${parseBraceExp(slotValue)}]`;
|
|
1069
|
+
else return `#${slotValue}`;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* 转换语法
|
|
1073
|
+
* @param {*} attrs
|
|
1074
|
+
* @param {*} tag
|
|
1075
|
+
* @param {*} components - 组件映射,用于判断是否为自定义组件
|
|
1076
|
+
*/
|
|
1077
|
+
function getProps(attrs, tag, components) {
|
|
1078
|
+
const attrsList = [];
|
|
1079
|
+
const isCustomComponent = Boolean(components && components[tag]);
|
|
1080
|
+
const propBindings = {};
|
|
1081
|
+
const hasEventBindings = Object.keys(attrs).some((name) => /^(?:capture-)?(?:bind|catch)(?::)?.+/.test(name));
|
|
1082
|
+
if (tag === "page-meta") attrsList.push({
|
|
1083
|
+
name: "dimina-rpx-unit",
|
|
1084
|
+
value: "vw"
|
|
1085
|
+
});
|
|
1086
|
+
if (hasEventBindings) attrsList.push({
|
|
1087
|
+
name: "v-c-event-node",
|
|
1088
|
+
value: components && components[tag] ? "'component'" : "'node'"
|
|
1089
|
+
});
|
|
1090
|
+
Object.entries(attrs).forEach(([name, value]) => {
|
|
1091
|
+
const templateDirective = getTemplateDirectiveName(name);
|
|
1092
|
+
if (templateDirective === "if") attrsList.push({
|
|
1093
|
+
name: "v-if",
|
|
1094
|
+
value: parseSafeBraceExp(value)
|
|
1095
|
+
});
|
|
1096
|
+
else if (templateDirective === "elif") attrsList.push({
|
|
1097
|
+
name: "v-else-if",
|
|
1098
|
+
value: parseSafeBraceExp(value)
|
|
1099
|
+
});
|
|
1100
|
+
else if (templateDirective === "else") attrsList.push({
|
|
1101
|
+
name: "v-else",
|
|
1102
|
+
value: ""
|
|
1103
|
+
});
|
|
1104
|
+
else if (templateDirective === "for" || templateDirective === "for-items") attrsList.push({
|
|
1105
|
+
name: "v-for",
|
|
1106
|
+
value: parseForExp(value, attrs)
|
|
1107
|
+
});
|
|
1108
|
+
else if (templateDirective === "for-item" || templateDirective === "for-index") {} else if (templateDirective === "key") {
|
|
1109
|
+
const tranValue = parseKeyExpression(value, getForItemName(attrs), getForIndexName(attrs));
|
|
1110
|
+
attrsList.push({
|
|
1111
|
+
name: ":key",
|
|
1112
|
+
value: tranValue
|
|
1113
|
+
});
|
|
1114
|
+
} else if (name === "style") {
|
|
1115
|
+
const parsedStyle = parseSafeBraceExp(value);
|
|
1116
|
+
attrsList.push({
|
|
1117
|
+
name: "v-c-style",
|
|
1118
|
+
value: transformRpx(parsedStyle)
|
|
1119
|
+
});
|
|
1120
|
+
if (isCustomComponent) {
|
|
1121
|
+
attrsList.push({
|
|
1122
|
+
name: ":dimina-wxml-style",
|
|
1123
|
+
value: parsedStyle
|
|
1124
|
+
});
|
|
1125
|
+
if (isWrappedByBraces(value) && parsedStyle) propBindings.style = parsedStyle;
|
|
1126
|
+
}
|
|
1127
|
+
} else if (name === "class") {
|
|
1128
|
+
if (isWrappedByBraces(value)) attrsList.push({
|
|
1129
|
+
name: ":class",
|
|
1130
|
+
value: parseClassRules(value)
|
|
1131
|
+
});
|
|
1132
|
+
else attrsList.push({
|
|
1133
|
+
name: "class",
|
|
1134
|
+
value
|
|
1135
|
+
});
|
|
1136
|
+
attrsList.push({
|
|
1137
|
+
name: "v-c-class",
|
|
1138
|
+
value: ""
|
|
1139
|
+
});
|
|
1140
|
+
} else if (name === "is" && tag === "component") attrsList.push({
|
|
1141
|
+
name: ":is",
|
|
1142
|
+
value: `'dd-'+${parseSafeBraceExp(value)}`
|
|
1143
|
+
});
|
|
1144
|
+
else if (name === "animation" && tag !== "movable-view" && tagWhiteList.includes(tag)) attrsList.push({
|
|
1145
|
+
name: "v-c-animation",
|
|
1146
|
+
value: parseSafeBraceExp(value)
|
|
1147
|
+
});
|
|
1148
|
+
else if (name === "value" && (tag === "input" || tag === "textarea") || (name === "x" || name === "y") && tag === "movable-view") {
|
|
1149
|
+
const parsedValue = parseSafeBraceExp(value);
|
|
1150
|
+
const conditionExp = generateVModelTemplate(parsedValue);
|
|
1151
|
+
if (conditionExp) {
|
|
1152
|
+
attrsList.push({
|
|
1153
|
+
name: `:${name}`,
|
|
1154
|
+
value: parsedValue
|
|
1155
|
+
});
|
|
1156
|
+
attrsList.push({
|
|
1157
|
+
name: `update:${name}`,
|
|
1158
|
+
value: conditionExp
|
|
1159
|
+
});
|
|
1160
|
+
} else attrsList.push({
|
|
1161
|
+
name: `v-model:${name}`,
|
|
1162
|
+
value: parsedValue
|
|
1163
|
+
});
|
|
1164
|
+
} else if (name.startsWith("data-")) if (isWrappedByBraces(value)) {
|
|
1165
|
+
attrsList.push({
|
|
1166
|
+
name: "v-c-data",
|
|
1167
|
+
value: ""
|
|
1168
|
+
});
|
|
1169
|
+
attrsList.push({
|
|
1170
|
+
name: `:${name}`,
|
|
1171
|
+
value: parseSafeBraceExp(value)
|
|
1172
|
+
});
|
|
1173
|
+
} else attrsList.push({
|
|
1174
|
+
name,
|
|
1175
|
+
value
|
|
1176
|
+
});
|
|
1177
|
+
else if (isWrappedByBraces(value)) {
|
|
1178
|
+
const pVal = tag === "template" && name === "data" ? parseTemplateDataExp(value) : parseSafeBraceExp(value);
|
|
1179
|
+
if (isCustomComponent) {
|
|
1180
|
+
if (pVal && typeof pVal === "string") propBindings[name] = pVal;
|
|
1181
|
+
}
|
|
1182
|
+
attrsList.push({
|
|
1183
|
+
name: `:${name}`,
|
|
1184
|
+
value: pVal
|
|
1185
|
+
});
|
|
1186
|
+
} else if (name !== "slot") attrsList.push({
|
|
1187
|
+
name,
|
|
1188
|
+
value
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
const propsRes = [];
|
|
1192
|
+
attrsList.forEach((attr) => {
|
|
1193
|
+
const { name, value } = attr;
|
|
1194
|
+
if (value === "") propsRes.push(`${name}`);
|
|
1195
|
+
else if (/\$\{[^}]*\}/.test(value)) propsRes.push(`:${name}="\`${value}\`"`);
|
|
1196
|
+
else propsRes.push(`${name}="${escapeQuotes(value)}"`);
|
|
1197
|
+
});
|
|
1198
|
+
if (isCustomComponent && Object.keys(propBindings).length > 0) try {
|
|
1199
|
+
const validBindings = {};
|
|
1200
|
+
for (const [key, value] of Object.entries(propBindings)) if (value !== void 0 && value !== null && typeof value === "string") validBindings[key] = value;
|
|
1201
|
+
if (Object.keys(validBindings).length > 0) {
|
|
1202
|
+
const parsedBindings = parseBindings(validBindings);
|
|
1203
|
+
const escapedJson = JSON.stringify(parsedBindings).replace(/"/g, """);
|
|
1204
|
+
propsRes.push(`v-c-prop-bindings="${escapedJson}"`);
|
|
1205
|
+
}
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
console.warn("[compiler] 序列化 propBindings 失败:", error.message, "标签:", tag, "绑定数据:", propBindings);
|
|
1208
|
+
}
|
|
1209
|
+
return propsRes;
|
|
1210
|
+
}
|
|
1211
|
+
function generateVModelTemplate(expression) {
|
|
1212
|
+
let var1, var2, updateExpression;
|
|
1213
|
+
if (expression.includes("&&")) {
|
|
1214
|
+
[var1, var2] = expression.split("&&").map((v) => v.trim());
|
|
1215
|
+
updateExpression = `${var1} ? (${var2} = $event) : (${var1} = $event)`;
|
|
1216
|
+
} else if (expression.includes("||")) {
|
|
1217
|
+
[var1, var2] = expression.split("||").map((v) => v.trim());
|
|
1218
|
+
updateExpression = `${var1} ? (${var1} = $event) : (${var2} = $event)`;
|
|
1219
|
+
} else if (expression.includes("?")) {
|
|
1220
|
+
const parts = expression.split(/[?:]/).map((v) => v.trim());
|
|
1221
|
+
var1 = parts[0];
|
|
1222
|
+
var2 = parts[2];
|
|
1223
|
+
updateExpression = `${var1} ? (${var1} = $event) : (${var2} = $event)`;
|
|
1224
|
+
} else return false;
|
|
1225
|
+
return updateExpression;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* 兼容 :key="{{ index }}" 或 :key="{{ item.index }}"的情况
|
|
1229
|
+
*/
|
|
1230
|
+
function parseKeyExpression(exp, itemName = "item", indexName = "index") {
|
|
1231
|
+
exp = exp.trim();
|
|
1232
|
+
if (/\*this/.test(exp) || /\*item/.test(exp)) return `${itemName}.toString()`;
|
|
1233
|
+
if (!exp.includes("{{")) {
|
|
1234
|
+
if (/^-?\d+(\.\d+)?$/.test(exp)) return exp;
|
|
1235
|
+
if (exp === indexName) return indexName;
|
|
1236
|
+
return exp.startsWith(itemName) ? `${exp}` : `${itemName}.${exp}`;
|
|
1237
|
+
}
|
|
1238
|
+
if (exp.startsWith("{{") && exp.endsWith("}}")) {
|
|
1239
|
+
const content = exp.slice(2, -2).trim();
|
|
1240
|
+
if (content === "this") return `${itemName}.toString()`;
|
|
1241
|
+
else if (content === indexName) return indexName;
|
|
1242
|
+
else return content.startsWith(itemName) ? `${content}` : `${itemName}.${content}`;
|
|
1243
|
+
}
|
|
1244
|
+
const result = exp.split(/(\{\{.*?\}\})/).map((part) => {
|
|
1245
|
+
if (part.startsWith("{{") && part.endsWith("}}")) {
|
|
1246
|
+
const content = part.slice(2, -2).trim();
|
|
1247
|
+
if (content === indexName) return indexName;
|
|
1248
|
+
return content.startsWith(itemName) ? content : `${itemName}.${content}`;
|
|
1249
|
+
}
|
|
1250
|
+
return `'${part}'`;
|
|
1251
|
+
}).join("+");
|
|
1252
|
+
return result.endsWith("+''") ? result.slice(0, -3) : result;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* 根据工作目录获取 ml 文件绝对路径
|
|
1256
|
+
* @param {string} workPath
|
|
1257
|
+
* @param {string} src
|
|
1258
|
+
* @returns 返回绝对路径
|
|
1259
|
+
*/
|
|
1260
|
+
function getViewPath(workPath, src) {
|
|
1261
|
+
const aSrc = src.startsWith("/") ? src : `/${src}`;
|
|
1262
|
+
for (const mlType of getTemplateExts()) {
|
|
1263
|
+
const mlFullPath = `${workPath}${aSrc}${mlType}`;
|
|
1264
|
+
if (fs.existsSync(mlFullPath)) return mlFullPath;
|
|
1265
|
+
const indexMlFullPath = `${workPath}${aSrc}/index${mlType}`;
|
|
1266
|
+
if (fs.existsSync(indexMlFullPath)) return indexMlFullPath;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* 解析 import/include 的模板文件路径。
|
|
1271
|
+
* 微信允许省略 .wxml;显式扩展名保持原样,无扩展名时按当前模板类型优先级补全。
|
|
1272
|
+
*/
|
|
1273
|
+
function resolveTemplateDependencyPath(workPath, ownerPath, src) {
|
|
1274
|
+
const resolvedPath = getAbsolutePath(workPath, ownerPath, src);
|
|
1275
|
+
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) return resolvedPath;
|
|
1276
|
+
if (path.extname(resolvedPath)) return resolvedPath;
|
|
1277
|
+
for (const ext of getTemplateExts()) {
|
|
1278
|
+
const candidate = `${resolvedPath}${ext}`;
|
|
1279
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
|
|
1280
|
+
}
|
|
1281
|
+
return resolvedPath;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* 将字符串内部的双引号进行替换
|
|
1285
|
+
* @param {*} input
|
|
1286
|
+
*/
|
|
1287
|
+
function escapeQuotes(input) {
|
|
1288
|
+
return input.replace(/"/g, "'");
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* 判断字符串是不是被{{}}包裹
|
|
1292
|
+
* @param {*} str
|
|
1293
|
+
*/
|
|
1294
|
+
function isWrappedByBraces(str) {
|
|
1295
|
+
return /\{\{.*\}\}/.test(str);
|
|
1296
|
+
}
|
|
1297
|
+
function splitWithBraces(str) {
|
|
1298
|
+
const result = [];
|
|
1299
|
+
let temp = "";
|
|
1300
|
+
let inBraces = false;
|
|
1301
|
+
for (let i = 0; i < str.length; i++) {
|
|
1302
|
+
const char = str[i];
|
|
1303
|
+
if (char === "{" && i + 1 < str.length && str[i + 1] === "{") {
|
|
1304
|
+
inBraces = true;
|
|
1305
|
+
temp += "{{";
|
|
1306
|
+
i++;
|
|
1307
|
+
} else if (char === "}" && i + 1 < str.length && str[i + 1] === "}") {
|
|
1308
|
+
inBraces = false;
|
|
1309
|
+
temp += "}}";
|
|
1310
|
+
i++;
|
|
1311
|
+
} else if (!inBraces && char === " ") {
|
|
1312
|
+
if (temp) {
|
|
1313
|
+
result.push(temp);
|
|
1314
|
+
temp = "";
|
|
1315
|
+
}
|
|
1316
|
+
} else temp += char;
|
|
1317
|
+
}
|
|
1318
|
+
if (temp) result.push(temp);
|
|
1319
|
+
return result;
|
|
1320
|
+
}
|
|
1321
|
+
function parseClassRules(cssRule) {
|
|
1322
|
+
let list = splitWithBraces(cssRule);
|
|
1323
|
+
list = list.map((item) => {
|
|
1324
|
+
return parseSafeBraceExp(item);
|
|
1325
|
+
});
|
|
1326
|
+
if (list.length === 1) return list.pop();
|
|
1327
|
+
return `[${list.join(",")}]`;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html#wx-for
|
|
1331
|
+
* 使用 :for-item 可以指定数组当前元素的变量名
|
|
1332
|
+
* @param {*} attrs
|
|
1333
|
+
*/
|
|
1334
|
+
function getForItemName(attrs) {
|
|
1335
|
+
for (const key in attrs) if (getTemplateDirectiveName(key) === "for-item") return attrs[key];
|
|
1336
|
+
return "item";
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* 使用 :for-index 可以指定数组当前下标的变量名
|
|
1340
|
+
* @param {*} attrs
|
|
1341
|
+
*/
|
|
1342
|
+
function getForIndexName(attrs) {
|
|
1343
|
+
for (const key in attrs) if (getTemplateDirectiveName(key) === "for-index") return attrs[key];
|
|
1344
|
+
return "index";
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* 解析 for 表达式的值
|
|
1348
|
+
* @param {*} exp
|
|
1349
|
+
* @param {*} attrs
|
|
1350
|
+
*/
|
|
1351
|
+
function parseForExp(exp, attrs) {
|
|
1352
|
+
return `(${getForItemName(attrs)}, ${getForIndexName(attrs)}) in ${parseSafeBraceExp(exp)}`;
|
|
1353
|
+
}
|
|
1354
|
+
var braceRegex = /(\{\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}\})|([^{}]+)/g;
|
|
1355
|
+
var noBraceRegex = /\{\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}\}/;
|
|
1356
|
+
var ternaryRegex = /[^?]+\?.+:.+/;
|
|
1357
|
+
var RESERVED_TEMPLATE_CONTEXT_ALIASES = /* @__PURE__ */ new Map([["class", "__dimina_reserved_class"]]);
|
|
1358
|
+
var RESERVED_TEMPLATE_CONTEXT_NAMES = new Map([...RESERVED_TEMPLATE_CONTEXT_ALIASES].map(([name, alias]) => [alias, name]));
|
|
1359
|
+
function encodeReservedTemplateContextIdentifier(expression) {
|
|
1360
|
+
return RESERVED_TEMPLATE_CONTEXT_ALIASES.get(expression) || expression;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* 解析 {{}} 表达式的值
|
|
1364
|
+
* @param {*} exp
|
|
1365
|
+
*/
|
|
1366
|
+
function parseBraceExp(exp) {
|
|
1367
|
+
let result;
|
|
1368
|
+
const group = [];
|
|
1369
|
+
while (result = braceRegex.exec(exp)) {
|
|
1370
|
+
if (result[1]) {
|
|
1371
|
+
const matchResult = result[1].match(noBraceRegex);
|
|
1372
|
+
if (matchResult) {
|
|
1373
|
+
const statement = encodeReservedTemplateContextIdentifier(matchResult[1].trim());
|
|
1374
|
+
if (ternaryRegex.test(statement)) group.push(`(${statement})`);
|
|
1375
|
+
else group.push(statement);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (result[2]) group.push(`+'${result[2].replace(/'/g, "\\'")}'+`);
|
|
1379
|
+
}
|
|
1380
|
+
return group.join("").replace(/^\+|\+$/g, "");
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* 解析 template 的 data 对象表达式,保留对象字面量和内部三元表达式
|
|
1384
|
+
* @param {string} exp
|
|
1385
|
+
* @returns {string} 解析后的对象表达式字符串
|
|
1386
|
+
*/
|
|
1387
|
+
function parseTemplateDataExp(exp) {
|
|
1388
|
+
const matchResult = exp.trim().match(/^\{\{([\s\S]*)\}\}$/);
|
|
1389
|
+
if (matchResult) return addOptionalChaining(`{${matchResult[1].trim()}}`);
|
|
1390
|
+
return `{${parseSafeBraceExp(exp)}}`;
|
|
1391
|
+
}
|
|
1392
|
+
function transTagWxs($, scriptModule, filePath, graphOwnerPath = filePath) {
|
|
1393
|
+
const wxsNodes = $(getViewScriptTags().join(","));
|
|
1394
|
+
wxsNodes.each((_, elem) => {
|
|
1395
|
+
const smName = $(elem).attr("module");
|
|
1396
|
+
if (smName) {
|
|
1397
|
+
let wxsContent;
|
|
1398
|
+
let uniqueModuleName = smName;
|
|
1399
|
+
let cacheKey = smName;
|
|
1400
|
+
const src = $(elem).attr("src");
|
|
1401
|
+
let wxsFilePath = null;
|
|
1402
|
+
const workPath = getWorkPath();
|
|
1403
|
+
if (src) {
|
|
1404
|
+
if (filePath.includes("/miniprogram_npm/")) {
|
|
1405
|
+
const componentFullPath = workPath + filePath.split("/").slice(0, -1).join("/");
|
|
1406
|
+
wxsFilePath = path.resolve(componentFullPath, src);
|
|
1407
|
+
} else wxsFilePath = getAbsolutePath(workPath, filePath, src);
|
|
1408
|
+
if (wxsFilePath) {
|
|
1409
|
+
getDependencyGraph().addFile(graphOwnerPath, wxsFilePath, "view");
|
|
1410
|
+
uniqueModuleName = stripViewScriptExt(wxsFilePath.replace(workPath, "")).replace(/[\/\\@\-]/g, "_").replace(/^_/, "");
|
|
1411
|
+
cacheKey = wxsFilePath;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
if (compileResCache.has(cacheKey)) wxsContent = compileResCache.get(cacheKey);
|
|
1415
|
+
else {
|
|
1416
|
+
if (src && wxsFilePath) if (fs.existsSync(wxsFilePath)) wxsContent = getContentByPath(wxsFilePath).trim();
|
|
1417
|
+
else {
|
|
1418
|
+
console.warn(`[view] wxs 文件不存在: ${wxsFilePath}`);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
else wxsContent = $(elem).html();
|
|
1422
|
+
if (!wxsContent) return;
|
|
1423
|
+
wxsContent = processWxsContent(wxsContent, wxsFilePath, scriptModule, workPath, filePath, graphOwnerPath);
|
|
1424
|
+
compileResCache.set(cacheKey, wxsContent);
|
|
1425
|
+
}
|
|
1426
|
+
if (wxsContent) {
|
|
1427
|
+
registerWxsModule(uniqueModuleName);
|
|
1428
|
+
scriptModule.push({
|
|
1429
|
+
path: uniqueModuleName,
|
|
1430
|
+
code: wxsContent,
|
|
1431
|
+
originalName: smName
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
wxsNodes.remove();
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* 递归收集 wxs 模块的所有依赖
|
|
1440
|
+
* @param {Map} scriptRes - 脚本资源映射
|
|
1441
|
+
* @param {Set} collectedPaths - 已收集的路径集合,避免重复处理
|
|
1442
|
+
* @param {Array} scriptModule - 用于收集新加载的 wxs 模块
|
|
1443
|
+
* @returns {Array} 所有 wxs 模块的数组
|
|
1444
|
+
*/
|
|
1445
|
+
function collectAllWxsModules(scriptRes, collectedPaths = /* @__PURE__ */ new Set(), scriptModule = []) {
|
|
1446
|
+
const allWxsModules = [];
|
|
1447
|
+
const workPath = getWorkPath();
|
|
1448
|
+
for (const [modulePath, moduleCode] of scriptRes.entries()) {
|
|
1449
|
+
if (collectedPaths.has(modulePath)) continue;
|
|
1450
|
+
if (isWxsModuleByContent(moduleCode, modulePath)) {
|
|
1451
|
+
collectedPaths.add(modulePath);
|
|
1452
|
+
allWxsModules.push({
|
|
1453
|
+
path: modulePath,
|
|
1454
|
+
code: moduleCode
|
|
1455
|
+
});
|
|
1456
|
+
const dependencies = extractWxsDependencies(moduleCode);
|
|
1457
|
+
for (const depPath of dependencies) if (!collectedPaths.has(depPath)) if (scriptRes.has(depPath)) {
|
|
1458
|
+
const depModules = collectAllWxsModules(/* @__PURE__ */ new Map([[depPath, scriptRes.get(depPath)]]), collectedPaths, scriptModule);
|
|
1459
|
+
allWxsModules.push(...depModules);
|
|
1460
|
+
} else {
|
|
1461
|
+
const loaded = loadWxsModule(depPath, workPath, scriptModule);
|
|
1462
|
+
if (loaded) {
|
|
1463
|
+
scriptRes.set(depPath, loaded.code);
|
|
1464
|
+
allWxsModules.push(loaded);
|
|
1465
|
+
collectedPaths.add(depPath);
|
|
1466
|
+
const depModules = collectAllWxsModules(/* @__PURE__ */ new Map([[depPath, loaded.code]]), collectedPaths, scriptModule);
|
|
1467
|
+
allWxsModules.push(...depModules);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return allWxsModules;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* 尝试从文件系统加载 wxs 模块
|
|
1476
|
+
* @param {string} modulePath - 模块路径
|
|
1477
|
+
* @param {string} workPath - 工作路径
|
|
1478
|
+
* @param {Array} scriptModule - 脚本模块数组
|
|
1479
|
+
* @returns {Object|null} 加载的模块对象或 null
|
|
1480
|
+
*/
|
|
1481
|
+
function loadWxsModule(modulePath, workPath, scriptModule) {
|
|
1482
|
+
const wxsFilePath = wxsFilePathMap.get(modulePath);
|
|
1483
|
+
if (!wxsFilePath) return null;
|
|
1484
|
+
try {
|
|
1485
|
+
const wxsContent = getContentByPath(wxsFilePath).trim();
|
|
1486
|
+
if (!wxsContent) return null;
|
|
1487
|
+
const processedContent = processWxsContent(wxsContent, wxsFilePath, scriptModule, workPath, "");
|
|
1488
|
+
registerWxsModule(modulePath);
|
|
1489
|
+
return {
|
|
1490
|
+
path: modulePath,
|
|
1491
|
+
code: processedContent
|
|
1492
|
+
};
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
console.warn(`[view] 加载 wxs 模块失败: ${modulePath}`, error.message);
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* 从 wxs 模块代码中提取依赖的模块路径
|
|
1500
|
+
* @param {string} moduleCode - 模块代码
|
|
1501
|
+
* @returns {Array} 依赖的模块路径数组
|
|
1502
|
+
*/
|
|
1503
|
+
function extractWxsDependencies(moduleCode) {
|
|
1504
|
+
const dependencies = [];
|
|
1505
|
+
const requirePattern = /(?:require|_)\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
1506
|
+
let match;
|
|
1507
|
+
while ((match = requirePattern.exec(moduleCode)) !== null) {
|
|
1508
|
+
const depPath = match[1];
|
|
1509
|
+
if (depPath && !dependencies.includes(depPath)) dependencies.push(depPath);
|
|
1510
|
+
}
|
|
1511
|
+
return dependencies;
|
|
1512
|
+
}
|
|
1513
|
+
function insertWxsToRenderResult(code, scriptModule, scriptRes, filename = "render.js", inputMap = null) {
|
|
1514
|
+
const wxsBindings = [];
|
|
1515
|
+
const codeReplacements = [];
|
|
1516
|
+
const ast = parseJs(code, filename);
|
|
1517
|
+
const statement = ast.body?.[0];
|
|
1518
|
+
const renderBody = (statement?.type === "ExpressionStatement" ? statement.expression : null)?.body;
|
|
1519
|
+
const declarations = [];
|
|
1520
|
+
for (const [index, sm] of scriptModule.entries()) {
|
|
1521
|
+
if (!scriptRes.has(sm.path)) scriptRes.set(sm.path, sm.code);
|
|
1522
|
+
const templatePropertyName = sm.originalName || sm.path;
|
|
1523
|
+
const requireModuleName = sm.path;
|
|
1524
|
+
const localIdentifier = `__wxs_${index}`;
|
|
1525
|
+
wxsBindings.push({
|
|
1526
|
+
localIdentifier,
|
|
1527
|
+
templatePropertyName
|
|
1528
|
+
});
|
|
1529
|
+
declarations.push(`const ${localIdentifier} = require(${JSON.stringify(requireModuleName)});`);
|
|
1530
|
+
}
|
|
1531
|
+
if (wxsBindings.length > 0 && renderBody?.type === "BlockStatement") codeReplacements.push({
|
|
1532
|
+
type: "insert",
|
|
1533
|
+
start: renderBody.start + 1,
|
|
1534
|
+
end: renderBody.start + 1,
|
|
1535
|
+
value: `\n${declarations.join("\n")}`
|
|
1536
|
+
});
|
|
1537
|
+
walk(ast, { enter(node) {
|
|
1538
|
+
if (node.type === "MemberExpression" && node.object?.type === "Identifier" && node.object.name === "_ctx" && !node.computed && node.property?.type === "Identifier") {
|
|
1539
|
+
const reservedName = RESERVED_TEMPLATE_CONTEXT_NAMES.get(node.property.name);
|
|
1540
|
+
if (reservedName) {
|
|
1541
|
+
codeReplacements.push({
|
|
1542
|
+
start: node.property.start,
|
|
1543
|
+
end: node.property.end,
|
|
1544
|
+
value: reservedName
|
|
1545
|
+
});
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
const replacement = wxsBindings.find((item) => item.templatePropertyName === node.property.name);
|
|
1549
|
+
if (replacement) codeReplacements.push({
|
|
1550
|
+
start: node.start,
|
|
1551
|
+
end: node.end,
|
|
1552
|
+
value: replacement.localIdentifier
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
} });
|
|
1556
|
+
if (codeReplacements.length === 0) return {
|
|
1557
|
+
code: getProgramCode(code, ast),
|
|
1558
|
+
map: inputMap
|
|
1559
|
+
};
|
|
1560
|
+
const transformed = applyCodeReplacements(code, codeReplacements);
|
|
1561
|
+
let map = inputMap;
|
|
1562
|
+
if (enableSourcemap) {
|
|
1563
|
+
const generatedMap = new MagicString(code);
|
|
1564
|
+
const selected = [];
|
|
1565
|
+
for (const replacement of [...codeReplacements].sort((a, b) => b.end - b.start - (a.end - a.start))) if (!selected.some((item) => replacement.start < item.end && item.start < replacement.end)) selected.push(replacement);
|
|
1566
|
+
for (const replacement of selected.sort((a, b) => b.start - a.start)) if (replacement.type === "insert") generatedMap.appendLeft(replacement.start, replacement.value);
|
|
1567
|
+
else generatedMap.overwrite(replacement.start, replacement.end, replacement.value);
|
|
1568
|
+
const wxsTransformMap = generatedMap.generateMap({
|
|
1569
|
+
file: filename,
|
|
1570
|
+
source: filename,
|
|
1571
|
+
includeContent: true,
|
|
1572
|
+
hires: true
|
|
1573
|
+
}).toString();
|
|
1574
|
+
map = inputMap ? remapSourcemap(wxsTransformMap, inputMap) : wxsTransformMap;
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
code: getProgramCode(transformed, parseJs(transformed, filename)),
|
|
1578
|
+
map
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
//#endregion
|
|
1582
|
+
export { compileML, generateSlotDirective, generateVModelTemplate, initWxsFilePathMap, loadWxsModule, normalizeTemplateSyntax, parseBraceExp, parseClassRules, parseKeyExpression, parseTemplateDataExp, processIncludeConditionalAttrs, processWxsContent, resetViewCompilerCache, splitWithBraces };
|