mp-weixin-back 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,399 +3,10 @@ import fs from 'fs';
3
3
  import JSON5 from 'json5';
4
4
  import { white, red, green } from 'kolorist';
5
5
  import { createRequire } from 'module';
6
- import generate from '@babel/generator';
7
6
  import MagicString from 'magic-string';
8
- import { babelParse, walkAST } from 'ast-kit';
9
-
10
- const virtualFileId = "mp-weixin-back-helper";
11
-
12
- const pageContainerComp = ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n';
13
- function isArrowFunction(func) {
14
- if (typeof func !== "function")
15
- return false;
16
- return !func.hasOwnProperty("prototype") && func.toString().includes("=>");
17
- }
18
- function compositionWalk(context, code, sfc, id) {
19
- const codeMs = new MagicString(code);
20
- const setupAst = babelParse(sfc.scriptSetup.loc.source, sfc.scriptSetup.lang);
21
- let pageInfo = {
22
- hasPageBack: false,
23
- pageBackFnName: "onPageBack",
24
- hasImportRef: false,
25
- backConfig: { ...context.config },
26
- onPageBackBodyAst: [],
27
- onPageBackCallNodeToRemove: null,
28
- activeFnName: "activeMpBack",
29
- inActiveFnName: "inactiveMpBack"
30
- };
31
- const activeFnCallsToModify = [];
32
- const inActiveFnCallsToModify = [];
33
- if (setupAst) {
34
- walkAST(setupAst, {
35
- enter(node) {
36
- if (node.type === "ImportDeclaration") {
37
- if (node.source.value.includes(virtualFileId)) {
38
- const importDefaultSpecifiers = node.specifiers.filter(
39
- (i) => i.type === "ImportDefaultSpecifier"
40
- );
41
- const importDefaultSpecifier = importDefaultSpecifiers[0];
42
- pageInfo.hasPageBack = true;
43
- pageInfo.pageBackFnName = importDefaultSpecifier.local.name;
44
- const importSpecifiers = node.specifiers.filter((i) => i.type === "ImportSpecifier");
45
- importSpecifiers.map((specifiers) => {
46
- if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "activeMpBack") {
47
- pageInfo.activeFnName = specifiers.local.name;
48
- }
49
- if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "inactiveMpBack") {
50
- pageInfo.inActiveFnName = specifiers.local.name;
51
- }
52
- });
53
- }
54
- if (node.source.value === "vue") {
55
- node.specifiers.some((specifier) => {
56
- if (specifier.local.name === "ref") {
57
- pageInfo.hasImportRef = true;
58
- return true;
59
- }
60
- return false;
61
- });
62
- }
63
- }
64
- if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.pageBackFnName) {
65
- pageInfo.onPageBackCallNodeToRemove = node;
66
- const callback = node.expression.arguments[0];
67
- const backArguments = node.expression.arguments[1];
68
- if (backArguments?.type === "ObjectExpression") {
69
- const config = new Function(
70
- // @ts-ignore
71
- `return (${(generate.default ? generate.default : generate)(backArguments).code});`
72
- )();
73
- Object.assign(pageInfo.backConfig, config);
74
- }
75
- if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
76
- pageInfo.onPageBackBodyAst = callback.body.body;
77
- }
78
- return;
79
- }
80
- if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.activeFnName) {
81
- activeFnCallsToModify.push({
82
- start: node.expression.start,
83
- end: node.expression.end,
84
- original: sfc.scriptSetup.loc.source.substring(
85
- node.expression.start,
86
- node.expression.end
87
- ),
88
- name: pageInfo.activeFnName
89
- });
90
- }
91
- if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.inActiveFnName) {
92
- inActiveFnCallsToModify.push({
93
- start: node.expression.start,
94
- end: node.expression.end,
95
- original: sfc.scriptSetup.loc.source.substring(
96
- node.expression.start,
97
- node.expression.end
98
- ),
99
- name: pageInfo.inActiveFnName
100
- });
101
- }
102
- }
103
- });
104
- }
105
- if (!pageInfo.hasPageBack)
106
- return code;
107
- if (pageInfo.onPageBackCallNodeToRemove) {
108
- const scriptSetupOffset = sfc.scriptSetup.loc.start.offset;
109
- const nodeToRemove = pageInfo.onPageBackCallNodeToRemove;
110
- const globalStart = scriptSetupOffset + nodeToRemove.start;
111
- const globalEnd = scriptSetupOffset + nodeToRemove.end;
112
- codeMs.remove(globalStart, globalEnd);
113
- }
114
- let callbackCode = "";
115
- if (pageInfo.onPageBackBodyAst.length > 0) {
116
- const tempAstRoot = {
117
- type: "BlockStatement",
118
- body: pageInfo.onPageBackBodyAst
119
- };
120
- walkAST(tempAstRoot, {
121
- enter(node) {
122
- if (node.type === "CallExpression" && node.callee.type === "Identifier") {
123
- const createIdentifier = (name) => ({ type: "Identifier", name });
124
- if (node.callee.name === pageInfo.activeFnName) {
125
- node.arguments.unshift(createIdentifier("__MP_WEIXIN_ACTIVEBACK__"));
126
- } else if (node.callee.name === pageInfo.inActiveFnName) {
127
- node.arguments.unshift(createIdentifier("__MP_WEIXIN_INACTIVEBACK__"));
128
- }
129
- }
130
- }
131
- });
132
- callbackCode = pageInfo.onPageBackBodyAst.map((statement) => (generate.default ? generate.default : generate)(statement).code).join("\n");
133
- }
134
- if (code.includes("<page-container")) {
135
- context.log.debugLog(`${context.getPageById(id)}\u9875\u9762\u5DF2\u6709page-container\u7EC4\u4EF6\uFF0C\u6CE8\u5165\u5931\u8D25`);
136
- return code;
137
- }
138
- if (!pageInfo.backConfig.preventDefault) {
139
- callbackCode += "uni.navigateBack({ delta: 1 });";
140
- }
141
- const importUseMpWeixinBack = `import { useMpWeixinBack } from '${virtualFileId}'`;
142
- const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : "";
143
- const stateFrequency = "let __MP_BACK_FREQUENCY__ = 1;";
144
- const statePageContainerVar = `
145
- const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${pageInfo.backConfig.initialValue})
146
- `;
147
- const configBack = (() => {
148
- const onPageBack = pageInfo.backConfig.onPageBack;
149
- if (!onPageBack)
150
- return "";
151
- if (typeof onPageBack !== "function") {
152
- throw new Error("`onPageBack` must be a function");
153
- }
154
- const params = JSON.stringify({ page: context.getPageById(id) });
155
- if (isArrowFunction(onPageBack) || onPageBack.toString().includes("function")) {
156
- return `(${onPageBack})(${params});`;
157
- }
158
- return `(function ${onPageBack})()`;
159
- })();
160
- const stateBeforeLeave = `
161
- const onBeforeLeave = () => {
162
- if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) {
163
- return
164
- }
165
- if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
166
- __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
167
- setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
168
- __MP_BACK_FREQUENCY__++
169
- }
170
- ${configBack}
171
- ${callbackCode}
172
- };
173
- `;
174
- const { template, scriptSetup } = sfc;
175
- const tempOffsets = {
176
- start: template.loc.start.offset,
177
- end: template.loc.end.offset,
178
- content: template.content
179
- };
180
- const templateMagicString = new MagicString(tempOffsets.content);
181
- templateMagicString.append(pageContainerComp);
182
- codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString());
183
- const scriptOffsets = {
184
- start: scriptSetup.loc.start.offset,
185
- end: scriptSetup.loc.end.offset,
186
- content: scriptSetup.content || ""
187
- };
188
- const scriptMagicString = new MagicString(scriptOffsets.content);
189
- scriptMagicString.prepend(
190
- ` ${importRefFromVue}
191
- ${importUseMpWeixinBack}
192
- ${stateFrequency}
193
- ${statePageContainerVar}
194
- ${stateBeforeLeave} `
195
- );
196
- activeFnCallsToModify.forEach((call) => {
197
- const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
198
- const newCall = call.original.replace(fnCallRegex, (_match, args) => {
199
- if (!args.trim()) {
200
- return `${call.name}(__MP_WEIXIN_ACTIVEBACK__)`;
201
- }
202
- return `${call.name}(__MP_WEIXIN_ACTIVEBACK__, ${args})`;
203
- });
204
- scriptMagicString.overwrite(call.start, call.end, newCall);
205
- });
206
- inActiveFnCallsToModify.forEach((call) => {
207
- const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
208
- const newCall = call.original.replace(fnCallRegex, (_match, args) => {
209
- if (!args.trim()) {
210
- return `${call.name}(__MP_WEIXIN_INACTIVEBACK__)`;
211
- }
212
- return `${call.name}(__MP_WEIXIN_INACTIVEBACK__, ${args})`;
213
- });
214
- scriptMagicString.overwrite(call.start, call.end, newCall);
215
- });
216
- codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString());
217
- return codeMs.toString();
218
- }
219
- function optionsWalk(context, code, sfc, id) {
220
- const codeMs = new MagicString(code);
221
- const ast = babelParse(sfc.script.loc.source, sfc.script.lang);
222
- let pageInfo = {
223
- hasPageBack: false,
224
- pageBackFnName: "onPageBack",
225
- backConfig: { ...context.config }
226
- };
227
- let exportDefaultNode = null;
228
- let dataMethodNode = null;
229
- let methodsNode = null;
230
- let onPageBackNodeMethod = null;
231
- let onPageBackNodeProperty = null;
232
- if (ast) {
233
- walkAST(ast, {
234
- enter(node) {
235
- if (node.type === "ExportDefaultDeclaration" && node.declaration.type === "ObjectExpression") {
236
- exportDefaultNode = node.declaration;
237
- const properties = node.declaration.properties;
238
- for (let i = 0; i < properties.length; i++) {
239
- const element = properties[i];
240
- if (element.type === "ObjectMethod" && element.key.type === "Identifier" && element.key.name === "data" && element.body.type === "BlockStatement") {
241
- dataMethodNode = element.body;
242
- }
243
- if (element.type === "ObjectProperty" && element.key.type === "Identifier" && element.key.name === "methods") {
244
- methodsNode = element.value;
245
- }
246
- const blockStatementCondition = element.type === "ObjectMethod" && element.key.type === "Identifier" && element.key.name === pageInfo.pageBackFnName && element.body.type === "BlockStatement";
247
- const functionExpressionCondition = element.type === "ObjectProperty" && element.key.type === "Identifier" && element.key.name === pageInfo.pageBackFnName && element.value.type === "FunctionExpression";
248
- if (blockStatementCondition) {
249
- pageInfo.hasPageBack = true;
250
- onPageBackNodeMethod = element;
251
- }
252
- if (functionExpressionCondition) {
253
- pageInfo.hasPageBack = true;
254
- onPageBackNodeProperty = element;
255
- }
256
- }
257
- }
258
- }
259
- });
260
- }
261
- if (!pageInfo.hasPageBack)
262
- return;
263
- const newDataProperty = [
264
- {
265
- type: "ObjectProperty",
266
- key: { type: "Identifier", name: "__MP_BACK_SHOW_PAGE_CONTAINER__" },
267
- value: { type: "BooleanLiteral", value: true },
268
- computed: false,
269
- shorthand: false
270
- },
271
- {
272
- type: "ObjectProperty",
273
- key: { type: "Identifier", name: "__MP_BACK_FREQUENCY__" },
274
- value: { type: "NumericLiteral", value: 1 },
275
- computed: false,
276
- shorthand: false
277
- }
278
- ];
279
- if (dataMethodNode) {
280
- const returnStatement = dataMethodNode.body.find(
281
- (node) => node.type === "ReturnStatement"
282
- );
283
- if (returnStatement && returnStatement.argument && returnStatement.argument.type === "ObjectExpression") {
284
- returnStatement.argument.properties.push(...newDataProperty);
285
- }
286
- } else if (exportDefaultNode) {
287
- const addData = {
288
- type: "ObjectMethod",
289
- key: { type: "Identifier", name: "data" },
290
- kind: "method",
291
- params: [],
292
- async: false,
293
- generator: false,
294
- computed: false,
295
- body: {
296
- type: "BlockStatement",
297
- directives: [],
298
- body: [
299
- {
300
- type: "ReturnStatement",
301
- argument: {
302
- type: "ObjectExpression",
303
- properties: newDataProperty
304
- }
305
- }
306
- ]
307
- }
308
- };
309
- exportDefaultNode.properties.push(addData);
310
- }
311
- const configBack = (() => {
312
- const onPageBack = pageInfo.backConfig.onPageBack;
313
- if (!onPageBack)
314
- return "";
315
- if (typeof onPageBack !== "function") {
316
- throw new Error("`onPageBack` must be a function");
317
- }
318
- const params = JSON.stringify({ page: context.getPageById(id) });
319
- if (isArrowFunction(onPageBack) || onPageBack.toString().includes("function")) {
320
- return `(${onPageBack})(${params});`;
321
- }
322
- return `(function ${onPageBack})()`;
323
- })();
324
- const stateBeforeLeave = `
325
- function onBeforeLeave() {
326
- if (this.__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
327
- this.__MP_BACK_SHOW_PAGE_CONTAINER__ = false
328
- setTimeout(() => { this.__MP_BACK_SHOW_PAGE_CONTAINER__ = true }, 0);
329
- this.__MP_BACK_FREQUENCY__++
330
- }
331
- ${configBack}
332
- ${!pageInfo.backConfig.preventDefault ? "uni.navigateBack({ delta: 1 });" : ""}
333
- };
334
- `;
335
- const stateBeforeLeaveAst = babelParse(stateBeforeLeave);
336
- const stateBeforeLeaveNode = stateBeforeLeaveAst.body.find(
337
- (node) => node.type === "FunctionDeclaration"
338
- );
339
- const newMethodsProperty = {
340
- type: "ObjectMethod",
341
- key: {
342
- type: "Identifier",
343
- name: "onBeforeLeave"
344
- },
345
- kind: "method",
346
- generator: false,
347
- async: false,
348
- params: [],
349
- computed: false,
350
- body: {
351
- type: "BlockStatement",
352
- directives: [],
353
- body: [
354
- ...onPageBackNodeMethod ? onPageBackNodeMethod.body.body : [],
355
- ...onPageBackNodeProperty ? onPageBackNodeProperty.value.body.body : [],
356
- ...stateBeforeLeaveNode.body.body
357
- ]
358
- }
359
- };
360
- if (methodsNode) {
361
- methodsNode.properties.push(newMethodsProperty);
362
- } else if (exportDefaultNode) {
363
- const addMethods = {
364
- type: "ObjectProperty",
365
- computed: false,
366
- shorthand: false,
367
- key: {
368
- type: "Identifier",
369
- name: "methods"
370
- },
371
- value: {
372
- type: "ObjectExpression",
373
- properties: [newMethodsProperty]
374
- }
375
- };
376
- exportDefaultNode.properties.push(addMethods);
377
- }
378
- const { template, script } = sfc;
379
- const tempOffsets = {
380
- start: template.loc.start.offset,
381
- end: template.loc.end.offset,
382
- content: template.content
383
- };
384
- const templateMagicString = new MagicString(tempOffsets.content);
385
- templateMagicString.append(pageContainerComp);
386
- codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString());
387
- const scriptOffsets = {
388
- start: script.loc.start.offset,
389
- end: script.loc.end.offset
390
- };
391
- const newScriptContent = (generate.default ? generate.default : generate)(ast).code;
392
- codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, newScriptContent);
393
- return codeMs.toString();
394
- }
395
- const vueWalker = {
396
- compositionWalk,
397
- optionsWalk
398
- };
7
+ import { parse } from '@babel/parser';
8
+ import _traverse from '@babel/traverse';
9
+ import { babelParse } from 'ast-kit';
399
10
 
400
11
  let compilerPromise = null;
401
12
  async function resolveCompiler(root) {
@@ -407,10 +18,10 @@ async function resolveCompiler(root) {
407
18
  const _require = createRequire(import.meta.url);
408
19
  const compilerPath = _require.resolve("@vue/compiler-sfc", { paths: [root] });
409
20
  return _require(compilerPath);
410
- } catch (firstError) {
21
+ } catch {
411
22
  try {
412
23
  return await import('@vue/compiler-sfc');
413
- } catch (secondError) {
24
+ } catch {
414
25
  throw new Error(
415
26
  `[mp-weixin-back] Cannot resolve @vue/compiler-sfc.
416
27
  This plugin requires @vue/compiler-sfc to be installed in your project.
@@ -423,26 +34,489 @@ Docs: https://github.com/DBAAZzz/mp-weixin-back#%EF%B8%8F-vite-\u914D\u7F6E
423
34
  })();
424
35
  return compilerPromise;
425
36
  }
426
- async function transformVueFile(code, id) {
427
- try {
428
- const sfcCompiler = await resolveCompiler(this.config.root);
429
- const sfc = sfcCompiler.parse(code).descriptor;
430
- const { template, script, scriptSetup } = sfc;
431
- if (!template?.content) {
432
- return code;
37
+
38
+ const virtualFileId = "mp-weixin-back-helper";
39
+ const resolvedVirtualFileId = "\0" + virtualFileId;
40
+ const ON_PAGE_BACK = "onPageBack";
41
+ const BEFORE_LEAVE_HANDLER = "__MP_BACK_ON_BEFORE_LEAVE__";
42
+
43
+ class MpBackConfigError extends Error {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "MpBackConfigError";
47
+ }
48
+ }
49
+
50
+ const OPTION_TYPES = {
51
+ preventDefault: { nodeType: "BooleanLiteral", label: "\u5E03\u5C14" },
52
+ initialValue: { nodeType: "BooleanLiteral", label: "\u5E03\u5C14" },
53
+ frequency: { nodeType: "NumericLiteral", label: "\u6570\u5B57" }
54
+ };
55
+ function extractStaticOptions(node, where) {
56
+ const result = {};
57
+ for (const prop of node.properties) {
58
+ if (prop.type === "SpreadElement") {
59
+ throw new MpBackConfigError(`${where}\uFF1AonPageBack \u914D\u7F6E\u4E0D\u652F\u6301\u5C55\u5F00\u8FD0\u7B97\u7B26\uFF0C\u914D\u7F6E\u5728\u6784\u5EFA\u671F\u9759\u6001\u8BFB\u53D6`);
60
+ }
61
+ if (prop.type === "ObjectMethod")
62
+ continue;
63
+ if (prop.computed) {
64
+ throw new MpBackConfigError(`${where}\uFF1AonPageBack \u914D\u7F6E\u4E0D\u652F\u6301\u8BA1\u7B97\u5C5E\u6027\u540D\uFF0C\u914D\u7F6E\u5728\u6784\u5EFA\u671F\u9759\u6001\u8BFB\u53D6`);
65
+ }
66
+ const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
67
+ if (key === null) {
68
+ throw new MpBackConfigError(`${where}\uFF1AonPageBack \u914D\u7F6E\u7684\u5C5E\u6027\u540D\u5FC5\u987B\u662F\u6807\u8BC6\u7B26\u6216\u5B57\u7B26\u4E32\u5B57\u9762\u91CF`);
69
+ }
70
+ if (!(key in OPTION_TYPES))
71
+ continue;
72
+ const expected = OPTION_TYPES[key];
73
+ const value = prop.value;
74
+ if (value.type === expected.nodeType) {
75
+ result[key] = value.value;
76
+ } else {
77
+ throw new MpBackConfigError(
78
+ `${where}\uFF1AonPageBack \u914D\u7F6E\u9879 ${key} \u5FC5\u987B\u662F${expected.label}\u5B57\u9762\u91CF\uFF08\u8BE5\u503C\u5728\u6784\u5EFA\u671F\u8BFB\u53D6\uFF0C\u6536\u5230 ${value.type}\uFF09\u3002\u5982\u9700\u8FD0\u884C\u65F6\u63A7\u5236\uFF0C\u8BF7\u4F7F\u7528 activeMpBack()/inactiveMpBack()`
79
+ );
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+
85
+ const traverse$1 = _traverse.default ?? _traverse;
86
+ const DEFAULT_PAGE_CONTAINER = {
87
+ zIndex: 1,
88
+ overlay: false,
89
+ duration: false
90
+ };
91
+ function buildPageContainerTag(options = {}) {
92
+ const pc = { ...DEFAULT_PAGE_CONTAINER, ...options };
93
+ return `<page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="${pc.overlay}" @beforeleave="${BEFORE_LEAVE_HANDLER}" :z-index="${pc.zIndex}" :duration="${pc.duration}"></page-container>`;
94
+ }
95
+ function injectPageContainer(ms, template, tag) {
96
+ ms.appendLeft(template.loc.end.offset, `
97
+ ${tag}
98
+ `);
99
+ }
100
+ function buildCompositionBeforeLeave(cfg, globalHookCode) {
101
+ return `const ${BEFORE_LEAVE_HANDLER} = () => {
102
+ if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) return
103
+ if (__MP_BACK_FREQUENCY__ < ${cfg.frequency}) {
104
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
105
+ setTimeout(() => { __MP_BACK_SHOW_PAGE_CONTAINER__.value = true }, 0)
106
+ __MP_BACK_FREQUENCY__++
107
+ }
108
+ ${globalHookCode}
109
+ if (typeof __MP_BACK_CB__ === 'function') __MP_BACK_CB__()
110
+ ${cfg.preventDefault ? "" : "uni.navigateBack({ delta: 1 })"}
111
+ };`;
112
+ }
113
+ function buildOptionsBeforeLeaveMethod(cfg, globalHookCode) {
114
+ return `${BEFORE_LEAVE_HANDLER}() {
115
+ if (!this.__MP_BACK_SHOW_PAGE_CONTAINER__) return
116
+ if (this.__MP_BACK_FREQUENCY__ < ${cfg.frequency}) {
117
+ this.__MP_BACK_SHOW_PAGE_CONTAINER__ = false
118
+ setTimeout(() => { this.__MP_BACK_SHOW_PAGE_CONTAINER__ = true }, 0)
119
+ this.__MP_BACK_FREQUENCY__++
120
+ }
121
+ ${globalHookCode}
122
+ const __mpBackOption = this.$options.onPageBack
123
+ const __mpBackHandler = typeof __mpBackOption === 'function' ? __mpBackOption : __mpBackOption && __mpBackOption.handler
124
+ if (typeof __mpBackHandler === 'function') __mpBackHandler.call(this)
125
+ ${cfg.preventDefault ? "" : "uni.navigateBack({ delta: 1 })"}
126
+ }`;
127
+ }
128
+ const KNOWN_GLOBALS = /* @__PURE__ */ new Set([
129
+ "uni",
130
+ "wx",
131
+ "getApp",
132
+ "getCurrentPages",
133
+ "requirePlugin",
134
+ "console",
135
+ "JSON",
136
+ "Math",
137
+ "Date",
138
+ "Object",
139
+ "Array",
140
+ "String",
141
+ "Number",
142
+ "Boolean",
143
+ "Symbol",
144
+ "Promise",
145
+ "Reflect",
146
+ "RegExp",
147
+ "Map",
148
+ "Set",
149
+ "WeakMap",
150
+ "WeakSet",
151
+ "Error",
152
+ "TypeError",
153
+ "RangeError",
154
+ "parseInt",
155
+ "parseFloat",
156
+ "isNaN",
157
+ "isFinite",
158
+ "NaN",
159
+ "Infinity",
160
+ "undefined",
161
+ "globalThis",
162
+ "setTimeout",
163
+ "clearTimeout",
164
+ "setInterval",
165
+ "clearInterval",
166
+ "encodeURIComponent",
167
+ "decodeURIComponent",
168
+ "encodeURI",
169
+ "decodeURI",
170
+ "arguments"
171
+ ]);
172
+ function serializeGlobalHook(context, page) {
173
+ const hook = context.config.onPageBack;
174
+ if (!hook)
175
+ return "";
176
+ if (typeof hook !== "function") {
177
+ throw new MpBackConfigError("\u63D2\u4EF6\u914D\u7F6E\u9879 onPageBack \u5FC5\u987B\u662F\u51FD\u6570");
178
+ }
179
+ const source = hook.toString();
180
+ const { expression, ast } = normalizeFunctionSource(source);
181
+ warnOnClosureReferences(context, ast);
182
+ const params = JSON.stringify({ page });
183
+ return `;(${expression})(${params});`;
184
+ }
185
+ function normalizeFunctionSource(source) {
186
+ const candidates = [
187
+ source,
188
+ `function ${source}`,
189
+ source.startsWith("async ") ? `async function ${source.slice("async ".length)}` : null
190
+ ].filter((c) => c !== null);
191
+ for (const candidate of candidates) {
192
+ try {
193
+ const ast = parse(`(${candidate});`, { sourceType: "module" });
194
+ return { expression: candidate, ast };
195
+ } catch {
196
+ }
197
+ }
198
+ throw new MpBackConfigError(
199
+ "\u65E0\u6CD5\u89E3\u6790\u63D2\u4EF6\u914D\u7F6E\u7684 onPageBack \u51FD\u6570\u6E90\u7801\uFF0C\u8BF7\u4F7F\u7528\u7BAD\u5934\u51FD\u6570\u3001function \u8868\u8FBE\u5F0F\u6216\u5BF9\u8C61\u65B9\u6CD5\u7B80\u5199"
200
+ );
201
+ }
202
+ function warnOnClosureReferences(context, ast) {
203
+ const unresolved = /* @__PURE__ */ new Set();
204
+ traverse$1(ast, {
205
+ Identifier(p) {
206
+ if (!p.isReferencedIdentifier())
207
+ return;
208
+ const name = p.node.name;
209
+ if (KNOWN_GLOBALS.has(name))
210
+ return;
211
+ if (p.scope.hasBinding(name))
212
+ return;
213
+ unresolved.add(name);
214
+ }
215
+ });
216
+ if (unresolved.size > 0) {
217
+ context.log.error(
218
+ `\u63D2\u4EF6\u914D\u7F6E\u7684 onPageBack \u5F15\u7528\u4E86\u5916\u90E8\u53D8\u91CF\uFF1A${[...unresolved].join(", ")}\u3002\u8BE5\u51FD\u6570\u4F1A\u88AB\u5E8F\u5217\u5316\u6CE8\u5165\u9875\u9762\uFF0C\u95ED\u5305\u53D8\u91CF\u5728\u8FD0\u884C\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u8BA9\u51FD\u6570\u81EA\u5305\u542B\u3002`
219
+ );
220
+ }
221
+ }
222
+
223
+ const traverse = _traverse.default ?? _traverse;
224
+ function compositionTransform(context, code, template, scriptSetup, id) {
225
+ const base = scriptSetup.loc.start.offset;
226
+ const ast = parseScript(scriptSetup.content, scriptSetup.lang);
227
+ const hasHelperImport = ast.program.body.some(
228
+ (stmt) => stmt.type === "ImportDeclaration" && stmt.source.value === virtualFileId
229
+ );
230
+ if (!hasHelperImport)
231
+ return;
232
+ const registerCalls = [];
233
+ const activeCalls = [];
234
+ const inactiveCalls = [];
235
+ traverse(ast, {
236
+ CallExpression(path) {
237
+ const exportName = resolveHelperCallee(path);
238
+ if (exportName === "onPageBack")
239
+ registerCalls.push(path.node);
240
+ else if (exportName === "activeMpBack")
241
+ activeCalls.push(path.node);
242
+ else if (exportName === "inactiveMpBack")
243
+ inactiveCalls.push(path.node);
244
+ }
245
+ });
246
+ if (registerCalls.length === 0) {
247
+ context.log.debugLog(`${id} import \u4E86 mp-weixin-back-helper \u4F46\u672A\u8C03\u7528 onPageBack\uFF0C\u8DF3\u8FC7\u6CE8\u5165`);
248
+ return;
249
+ }
250
+ let staticOptions = {};
251
+ let optionsFound = false;
252
+ for (const call of registerCalls) {
253
+ const optionsArg = call.arguments[1];
254
+ if (!optionsArg)
255
+ continue;
256
+ if (optionsArg.type !== "ObjectExpression") {
257
+ throw new MpBackConfigError(
258
+ `${id}\uFF1AonPageBack \u7684\u7B2C\u4E8C\u4E2A\u53C2\u6570\u5FC5\u987B\u662F\u5185\u8054\u5BF9\u8C61\u5B57\u9762\u91CF\uFF08\u914D\u7F6E\u5728\u6784\u5EFA\u671F\u9759\u6001\u8BFB\u53D6\uFF0C\u6536\u5230 ${optionsArg.type}\uFF09\u3002\u8BF7\u5199\u6210 onPageBack(cb, { preventDefault: true }) \u7684\u5F62\u5F0F`
259
+ );
260
+ }
261
+ if (optionsFound) {
262
+ context.log.error(`${id}\uFF1AonPageBack \u88AB\u591A\u6B21\u4F20\u5165\u914D\u7F6E\uFF0C\u4EC5\u7B2C\u4E00\u5904\u751F\u6548`);
263
+ continue;
264
+ }
265
+ staticOptions = extractStaticOptions(optionsArg, id);
266
+ optionsFound = true;
267
+ }
268
+ const cfg = {
269
+ preventDefault: context.config.preventDefault,
270
+ frequency: context.config.frequency,
271
+ initialValue: context.config.initialValue,
272
+ ...staticOptions
273
+ };
274
+ const ms = new MagicString(code);
275
+ injectPageContainer(ms, template, buildPageContainerTag(context.config.pageContainer));
276
+ for (const call of registerCalls) {
277
+ ms.overwrite(base + call.callee.start, base + call.callee.end, "__MP_BACK_REGISTER__");
278
+ }
279
+ for (const call of activeCalls) {
280
+ insertFirstArgument(ms, base, call, "__MP_WEIXIN_ACTIVEBACK__");
281
+ }
282
+ for (const call of inactiveCalls) {
283
+ insertFirstArgument(ms, base, call, "__MP_WEIXIN_INACTIVEBACK__");
284
+ }
285
+ const globalHookCode = serializeGlobalHook(context, context.getPageById(id));
286
+ const injected = `
287
+ import { useMpWeixinBack } from '${virtualFileId}';
288
+ let __MP_BACK_FREQUENCY__ = 1;
289
+ let __MP_BACK_CB__ = null;
290
+ const __MP_BACK_REGISTER__ = (cb) => { __MP_BACK_CB__ = cb };
291
+ const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${cfg.initialValue});
292
+ ${buildCompositionBeforeLeave(cfg, globalHookCode)}
293
+ `;
294
+ ms.appendRight(base, injected);
295
+ return {
296
+ code: ms.toString(),
297
+ map: ms.generateMap({ hires: true, source: id, includeContent: true })
298
+ };
299
+ }
300
+ function parseScript(content, lang) {
301
+ const plugins = [];
302
+ if (lang === "ts" || lang === "tsx")
303
+ plugins.push("typescript");
304
+ if (lang === "jsx" || lang === "tsx")
305
+ plugins.push("jsx");
306
+ return parse(content, { sourceType: "module", plugins });
307
+ }
308
+ function resolveHelperCallee(path) {
309
+ const callee = path.node.callee;
310
+ if (callee.type !== "Identifier")
311
+ return null;
312
+ const binding = path.scope.getBinding(callee.name);
313
+ if (!binding)
314
+ return null;
315
+ const declaration = binding.path;
316
+ const parent = declaration.parent;
317
+ if (parent?.type !== "ImportDeclaration" || parent.source.value !== virtualFileId)
318
+ return null;
319
+ if (declaration.isImportDefaultSpecifier())
320
+ return "onPageBack";
321
+ if (declaration.isImportSpecifier()) {
322
+ const imported = declaration.node.imported;
323
+ if (imported.type === "Identifier") {
324
+ if (imported.name === "activeMpBack")
325
+ return "activeMpBack";
326
+ if (imported.name === "inactiveMpBack")
327
+ return "inactiveMpBack";
433
328
  }
434
- if (!script?.content && !scriptSetup?.content) {
435
- return code;
329
+ }
330
+ return null;
331
+ }
332
+ function insertFirstArgument(ms, base, call, name) {
333
+ const firstArg = call.arguments[0];
334
+ if (firstArg) {
335
+ ms.appendLeft(base + firstArg.start, `${name}, `);
336
+ } else {
337
+ ms.appendLeft(base + call.end - 1, name);
338
+ }
339
+ }
340
+
341
+ function optionsTransform(context, code, template, script, id) {
342
+ const base = script.loc.start.offset;
343
+ const ast = babelParse(script.content, script.lang);
344
+ let componentObject = null;
345
+ for (const stmt of ast.body) {
346
+ if (stmt.type !== "ExportDefaultDeclaration")
347
+ continue;
348
+ if (stmt.declaration.type === "ObjectExpression") {
349
+ componentObject = stmt.declaration;
350
+ } else if (stmt.declaration.type === "CallExpression" && stmt.declaration.arguments[0]?.type === "ObjectExpression") {
351
+ componentObject = stmt.declaration.arguments[0];
436
352
  }
437
- const walker = scriptSetup ? "compositionWalk" : "optionsWalk";
438
- return vueWalker[walker](this, code, sfc, id);
439
- } catch (error) {
440
- this.log.error(
441
- `Failed to transform ${id}. Please check the file is a valid Vue SFC.
442
- Docs: https://github.com/DBAAZzz/mp-weixin-back#-\u5FEB\u901F\u5F00\u59CB`
353
+ }
354
+ if (!componentObject)
355
+ return;
356
+ let dataOption = null;
357
+ let methodsOption = null;
358
+ let onPageBackOption = null;
359
+ let dataIndex = -1;
360
+ let methodsIndex = -1;
361
+ let onPageBackIndex = -1;
362
+ let lastSpreadIndex = -1;
363
+ for (let index = 0; index < componentObject.properties.length; index++) {
364
+ const prop = componentObject.properties[index];
365
+ if (prop.type === "SpreadElement") {
366
+ lastSpreadIndex = index;
367
+ continue;
368
+ }
369
+ const name = propertyName(prop);
370
+ if (name === "data") {
371
+ dataOption = prop;
372
+ dataIndex = index;
373
+ }
374
+ if (name === "methods") {
375
+ methodsOption = prop;
376
+ methodsIndex = index;
377
+ }
378
+ if (name === ON_PAGE_BACK) {
379
+ onPageBackOption = prop;
380
+ onPageBackIndex = index;
381
+ }
382
+ }
383
+ if (!onPageBackOption)
384
+ return;
385
+ if (lastSpreadIndex > onPageBackIndex) {
386
+ throw new MpBackConfigError(
387
+ `${id}\uFF1AonPageBack \u4E4B\u540E\u5B58\u5728\u5BF9\u8C61\u5C55\u5F00\uFF08...\uFF09\uFF0C\u8FD0\u884C\u65F6\u5B9E\u9645\u751F\u6548\u7684\u56DE\u8C03\u53EF\u80FD\u4E0E\u6784\u5EFA\u671F\u8BFB\u53D6\u7684\u914D\u7F6E\u4E0D\u4E00\u81F4\uFF1B\u8BF7\u5C06\u5C55\u5F00\u79FB\u5230 onPageBack \u4E4B\u524D`
443
388
  );
444
- this.log.error(String(error));
445
- return code;
389
+ }
390
+ let staticOptions = {};
391
+ if (onPageBackOption.type === "ObjectProperty" && onPageBackOption.value.type === "ObjectExpression") {
392
+ staticOptions = extractStaticOptions(onPageBackOption.value, id);
393
+ }
394
+ const cfg = {
395
+ preventDefault: context.config.preventDefault,
396
+ frequency: context.config.frequency,
397
+ initialValue: context.config.initialValue,
398
+ ...staticOptions
399
+ };
400
+ const ms = new MagicString(code);
401
+ injectPageContainer(ms, template, buildPageContainerTag(context.config.pageContainer));
402
+ const stateProps = `__MP_BACK_SHOW_PAGE_CONTAINER__: ${cfg.initialValue}, __MP_BACK_FREQUENCY__: 1,`;
403
+ if (dataOption) {
404
+ if (lastSpreadIndex > dataIndex) {
405
+ throw new MpBackConfigError(
406
+ `${id}\uFF1Adata \u4E4B\u540E\u5B58\u5728\u5BF9\u8C61\u5C55\u5F00\uFF08...\uFF09\uFF0C\u6CE8\u5165\u7684\u62E6\u622A\u72B6\u6001\u53EF\u80FD\u5728\u8FD0\u884C\u65F6\u88AB\u8986\u76D6\uFF1B\u8BF7\u5C06\u5C55\u5F00\u79FB\u5230 data \u4E4B\u524D`
407
+ );
408
+ }
409
+ const dataReturn = resolveDataReturnObject(dataOption);
410
+ if (!dataReturn) {
411
+ throw new MpBackConfigError(
412
+ `${id}\uFF1Adata \u5FC5\u987B\u662F\u65B9\u6CD5\u6216\u8FD4\u56DE\u5BF9\u8C61\u5B57\u9762\u91CF\u7684\u51FD\u6570\uFF08\u652F\u6301 data() { return {...} }\u3001data: () => ({...})\u3001data: function () { return {...} }\uFF09\uFF0C\u5426\u5219\u65E0\u6CD5\u6CE8\u5165\u62E6\u622A\u72B6\u6001`
413
+ );
414
+ }
415
+ ms.appendRight(base + dataReturn.start + 1, `
416
+ ${stateProps}`);
417
+ } else {
418
+ if (lastSpreadIndex >= 0) {
419
+ throw new MpBackConfigError(
420
+ `${id}\uFF1A\u7EC4\u4EF6\u9009\u9879\u4F7F\u7528\u4E86\u5BF9\u8C61\u5C55\u5F00\uFF08...\uFF09\u4E14\u672A\u663E\u5F0F\u58F0\u660E data\uFF0C\u63D2\u4EF6\u65E0\u6CD5\u5B89\u5168\u6CE8\u5165\u62E6\u622A\u72B6\u6001\uFF1B\u8BF7\u663E\u5F0F\u58F0\u660E data() { return {} }`
421
+ );
422
+ }
423
+ ms.appendRight(
424
+ base + componentObject.start + 1,
425
+ `
426
+ data() {
427
+ return { ${stateProps} }
428
+ },`
429
+ );
430
+ }
431
+ const globalHookCode = serializeGlobalHook(context, context.getPageById(id));
432
+ const method = buildOptionsBeforeLeaveMethod(cfg, globalHookCode);
433
+ if (methodsOption) {
434
+ if (lastSpreadIndex > methodsIndex) {
435
+ throw new MpBackConfigError(
436
+ `${id}\uFF1Amethods \u4E4B\u540E\u5B58\u5728\u5BF9\u8C61\u5C55\u5F00\uFF08...\uFF09\uFF0C\u6CE8\u5165\u7684\u5904\u7406\u65B9\u6CD5\u53EF\u80FD\u5728\u8FD0\u884C\u65F6\u88AB\u8986\u76D6\uFF1B\u8BF7\u5C06\u5C55\u5F00\u79FB\u5230 methods \u4E4B\u524D`
437
+ );
438
+ }
439
+ const methodsObject = methodsOption.type === "ObjectProperty" && methodsOption.value.type === "ObjectExpression" ? methodsOption.value : null;
440
+ if (!methodsObject) {
441
+ throw new MpBackConfigError(
442
+ `${id}\uFF1Amethods \u5FC5\u987B\u662F\u5BF9\u8C61\u5B57\u9762\u91CF\uFF0C\u5426\u5219\u65E0\u6CD5\u6CE8\u5165\u8FD4\u56DE\u62E6\u622A\u7684\u5904\u7406\u65B9\u6CD5`
443
+ );
444
+ }
445
+ ms.appendRight(base + methodsObject.start + 1, `
446
+ ${method},`);
447
+ } else {
448
+ if (lastSpreadIndex >= 0) {
449
+ throw new MpBackConfigError(
450
+ `${id}\uFF1A\u7EC4\u4EF6\u9009\u9879\u4F7F\u7528\u4E86\u5BF9\u8C61\u5C55\u5F00\uFF08...\uFF09\u4E14\u672A\u663E\u5F0F\u58F0\u660E methods\uFF0C\u63D2\u4EF6\u65E0\u6CD5\u5B89\u5168\u6CE8\u5165\u8FD4\u56DE\u62E6\u622A\u7684\u5904\u7406\u65B9\u6CD5\uFF1B\u8BF7\u663E\u5F0F\u58F0\u660E methods: {}`
451
+ );
452
+ }
453
+ ms.appendRight(base + componentObject.start + 1, `
454
+ methods: {
455
+ ${method},
456
+ },`);
457
+ }
458
+ return {
459
+ code: ms.toString(),
460
+ map: ms.generateMap({ hires: true, source: id, includeContent: true })
461
+ };
462
+ }
463
+ function propertyName(prop) {
464
+ if (prop.type === "SpreadElement" || prop.computed)
465
+ return null;
466
+ if (prop.key.type === "Identifier")
467
+ return prop.key.name;
468
+ if (prop.key.type === "StringLiteral")
469
+ return prop.key.value;
470
+ return null;
471
+ }
472
+ function resolveDataReturnObject(dataOption) {
473
+ if (dataOption.type === "ObjectMethod") {
474
+ return findReturnObject(dataOption.body);
475
+ }
476
+ const value = dataOption.value;
477
+ if (value.type === "FunctionExpression") {
478
+ return findReturnObject(value.body);
479
+ }
480
+ if (value.type === "ArrowFunctionExpression") {
481
+ if (value.body.type === "ObjectExpression")
482
+ return value.body;
483
+ if (value.body.type === "BlockStatement")
484
+ return findReturnObject(value.body);
485
+ }
486
+ return null;
487
+ }
488
+ function findReturnObject(block) {
489
+ for (const stmt of block.body) {
490
+ if (stmt.type === "ReturnStatement" && stmt.argument?.type === "ObjectExpression") {
491
+ return stmt.argument;
492
+ }
493
+ }
494
+ return null;
495
+ }
496
+
497
+ async function transformVueFile(context, code, id) {
498
+ if (context.hasPages && context.getPageById(id) === null) {
499
+ context.log.debugLog(`${id} \u4E0D\u662F pages.json \u4E2D\u6CE8\u518C\u7684\u9875\u9762\uFF0C\u8DF3\u8FC7\u6CE8\u5165`);
500
+ return;
501
+ }
502
+ const compiler = await resolveCompiler(context.config.root);
503
+ const { descriptor } = compiler.parse(code, { filename: id });
504
+ const template = descriptor.template;
505
+ const script = descriptor.script;
506
+ const scriptSetup = descriptor.scriptSetup;
507
+ if (!template?.content)
508
+ return;
509
+ if (template.content.includes("<page-container")) {
510
+ context.log.debugLog(`${id} \u9875\u9762\u5DF2\u6709 page-container \u7EC4\u4EF6\uFF0C\u8DF3\u8FC7\u6CE8\u5165`);
511
+ return;
512
+ }
513
+ if (scriptSetup?.content) {
514
+ const result = compositionTransform(context, code, template, scriptSetup, id);
515
+ if (result)
516
+ return result;
517
+ }
518
+ if (script?.content) {
519
+ return optionsTransform(context, code, template, script, id);
446
520
  }
447
521
  }
448
522
 
@@ -452,11 +526,15 @@ var __publicField = (obj, key, value) => {
452
526
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
453
527
  return value;
454
528
  };
455
- class pageContext {
529
+ class PageContext {
456
530
  constructor(config) {
457
531
  __publicField(this, "logPreText", "[mp-weixin-back] : ");
458
532
  __publicField(this, "config");
459
533
  __publicField(this, "pages", []);
534
+ /** pages.json 的绝对路径;找不到时为 null(此时禁用页面级过滤) */
535
+ __publicField(this, "pagesJsonPath");
536
+ /** 页面源码目录相对 root 的前缀:CLI 项目为 'src',HBuilderX 项目为 '' */
537
+ __publicField(this, "sourceBase", "src");
460
538
  __publicField(this, "log", {
461
539
  info: (text) => {
462
540
  console.log(white(this.logPreText + text));
@@ -471,60 +549,100 @@ class pageContext {
471
549
  }
472
550
  });
473
551
  this.config = config;
552
+ this.pagesJsonPath = this.resolvePagesJsonPath();
553
+ }
554
+ /** 依次探测 src/pages.json(CLI 项目)和 pages.json(HBuilderX 项目) */
555
+ resolvePagesJsonPath() {
556
+ for (const base of ["src", ""]) {
557
+ const candidate = path.join(this.config.root, base, "pages.json");
558
+ if (fs.existsSync(candidate)) {
559
+ this.sourceBase = base;
560
+ return candidate;
561
+ }
562
+ }
563
+ return null;
564
+ }
565
+ get hasPages() {
566
+ return this.pagesJsonPath !== null;
474
567
  }
475
- getPagesJsonPath() {
476
- const pagesJsonPath = path.join(this.config.root, "src/pages.json");
477
- return pagesJsonPath;
568
+ isPagesJson(file) {
569
+ return this.pagesJsonPath !== null && path.normalize(file) === path.normalize(this.pagesJsonPath);
478
570
  }
479
- // 获取页面配置详情
480
- async getPagesJsonInfo() {
481
- const hasPagesJson = fs.existsSync(this.getPagesJsonPath());
482
- if (!hasPagesJson)
571
+ /** 读取并解析 pages.json;dev 下 pages.json 变更时会被重新调用 */
572
+ async loadPages() {
573
+ if (!this.pagesJsonPath) {
574
+ this.log.debugLog("\u672A\u627E\u5230 src/pages.json \u6216 pages.json\uFF0C\u7981\u7528\u9875\u9762\u7EA7\u8FC7\u6EE4\uFF08\u6240\u6709\u4F7F\u7528 helper \u7684 .vue \u6587\u4EF6\u90FD\u4F1A\u88AB\u5904\u7406\uFF09");
483
575
  return;
576
+ }
484
577
  try {
485
- const content = await fs.promises.readFile(this.getPagesJsonPath(), "utf-8");
578
+ const content = await fs.promises.readFile(this.pagesJsonPath, "utf-8");
486
579
  const pagesContent = JSON5.parse(content);
487
- const { pages, subpackages } = pagesContent;
488
- if (pages) {
489
- const mainPages = pages.reduce((acc, current) => {
490
- acc.push(current.path);
491
- return acc;
492
- }, []);
493
- this.pages.push(...mainPages);
580
+ const next = [];
581
+ for (const page of pagesContent.pages ?? []) {
582
+ next.push(page.path);
494
583
  }
495
- if (subpackages) {
496
- for (let i = 0; i < subpackages.length; i++) {
497
- const element = subpackages[i];
498
- const root = element.root;
499
- const subPages = element.pages.reduce((acc, current) => {
500
- acc.push(`${root}/${current.path}`.replace("//", "/"));
501
- return acc;
502
- }, []);
503
- this.pages.push(...subPages);
584
+ const subpackages = [...pagesContent.subpackages ?? [], ...pagesContent.subPackages ?? []];
585
+ for (const sub of subpackages) {
586
+ for (const page of sub.pages ?? []) {
587
+ next.push(`${sub.root}/${page.path}`.replace("//", "/"));
504
588
  }
505
589
  }
590
+ this.pages = next;
591
+ this.log.debugLog(`\u5DF2\u52A0\u8F7D ${next.length} \u4E2A\u9875\u9762\uFF08${this.pagesJsonPath}\uFF09`);
506
592
  } catch (error) {
507
593
  this.log.error(
508
- `Failed to read pages.json. Make sure src/pages.json exists and is valid JSON/JSON5.
509
- Path checked: ${this.getPagesJsonPath()}
594
+ `Failed to read pages.json. Make sure it is valid JSON/JSON5.
595
+ Path checked: ${this.pagesJsonPath}
510
596
  Docs: https://github.com/DBAAZzz/mp-weixin-back#%EF%B8%8F-vite-\u914D\u7F6E`
511
597
  );
512
598
  this.log.debugLog(String(error));
513
599
  }
514
600
  }
515
- // 获取指定id的page
601
+ /** 获取指定 id 对应的页面路径;不是已注册页面(或无 pages.json)时返回 null */
516
602
  getPageById(id) {
517
- const path2 = (id.split("src/")[1] || "").replace(".vue", "");
518
- return this.pages.find((i) => i === path2) || null;
603
+ if (!this.hasPages)
604
+ return null;
605
+ const filename = id.split("?")[0];
606
+ const rel = path.relative(path.join(this.config.root, this.sourceBase), filename).split(path.sep).join("/");
607
+ if (rel.startsWith(".."))
608
+ return null;
609
+ const pagePath = rel.replace(/\.vue$/, "");
610
+ return this.pages.includes(pagePath) ? pagePath : null;
519
611
  }
520
612
  async transform(code, id) {
521
- const result = await transformVueFile.call(this, code, id);
522
- return result;
613
+ return await transformVueFile(this, code, id);
523
614
  }
524
615
  }
525
616
 
617
+ function virtualModuleCode(dev) {
618
+ return `
619
+ import { ref } from 'vue'
620
+ const __MP_BACK_DEV__ = ${dev}
621
+ function __mpBackWarn(name) {
622
+ if (__MP_BACK_DEV__) {
623
+ console.warn('[mp-weixin-back] ' + name + ' \u672A\u751F\u6548\uFF1A\u8BE5\u6587\u4EF6\u672A\u88AB\u63D2\u4EF6\u7F16\u8BD1\u5904\u7406\u3002\u8BF7\u786E\u8BA4\u5B83\u662F pages.json \u4E2D\u6CE8\u518C\u7684\u9875\u9762\uFF0C\u4E14\u5F53\u524D\u6784\u5EFA\u5E73\u53F0\u4E3A mp-weixin\u3002')
624
+ }
625
+ }
626
+ export default function onPageBack() { __mpBackWarn('onPageBack') }
627
+ export function activeMpBack(fn) {
628
+ if (typeof fn === 'function') fn()
629
+ else __mpBackWarn('activeMpBack')
630
+ }
631
+ export function inactiveMpBack(fn) {
632
+ if (typeof fn === 'function') fn()
633
+ else __mpBackWarn('inactiveMpBack')
634
+ }
635
+ export function useMpWeixinBack(initialValue = true) {
636
+ const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
637
+ const __MP_WEIXIN_ACTIVEBACK__ = () => { __MP_BACK_SHOW_PAGE_CONTAINER__.value = true }
638
+ const __MP_WEIXIN_INACTIVEBACK__ = () => { __MP_BACK_SHOW_PAGE_CONTAINER__.value = false }
639
+ return { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ }
640
+ }
641
+ `;
642
+ }
526
643
  function MpBackPlugin(userOptions = {}) {
527
644
  let context;
645
+ let enabled = true;
528
646
  const defaultOptions = {
529
647
  initialValue: true,
530
648
  preventDefault: false,
@@ -536,58 +654,57 @@ function MpBackPlugin(userOptions = {}) {
536
654
  name: "vite-plugin-mp-weixin-back",
537
655
  enforce: "pre",
538
656
  configResolved(config) {
539
- context = new pageContext({ ...options, mode: config.mode, root: config.root });
657
+ const platform = process.env.UNI_PLATFORM;
658
+ enabled = !platform || platform === "mp-weixin";
659
+ context = new PageContext({ ...options, mode: config.mode, root: config.root });
660
+ if (!enabled) {
661
+ context.log.info(`\u5F53\u524D\u6784\u5EFA\u5E73\u53F0\u4E3A ${platform}\uFF0C\u63D2\u4EF6\u5DF2\u7981\u7528\uFF08\u4FDD\u7559 no-op API \u4EE5\u4FDD\u8BC1\u4EE3\u7801\u53EF\u7F16\u8BD1\uFF09`);
662
+ }
540
663
  },
541
664
  buildStart() {
542
- context.getPagesJsonInfo();
665
+ if (context.pagesJsonPath) {
666
+ this.addWatchFile(context.pagesJsonPath);
667
+ }
668
+ return context.loadPages();
669
+ },
670
+ async watchChange(id) {
671
+ if (context.isPagesJson(id)) {
672
+ await context.loadPages();
673
+ }
543
674
  },
544
675
  resolveId(id) {
545
676
  if (id === virtualFileId) {
546
- return virtualFileId;
677
+ return resolvedVirtualFileId;
547
678
  }
548
679
  },
549
680
  load(id) {
550
- if (id.includes("node_modules")) {
551
- return;
552
- }
553
- if (id === virtualFileId) {
554
- return `
555
- import { ref } from 'vue'
556
- export default function onPageBack() {}
557
- export function activeMpBack(fn = null) {
558
- fn?.()
559
- }
560
- export function inactiveMpBack(fn = null) {
561
- fn?.()
562
- }
563
- export function useMpWeixinBack(initialValue = true) {
564
- const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
565
-
566
- const __MP_WEIXIN_ACTIVEBACK__ = () => {
567
- __MP_BACK_SHOW_PAGE_CONTAINER__.value = true
568
- }
569
-
570
- const __MP_WEIXIN_INACTIVEBACK__ = () => {
571
- __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
572
- }
573
-
574
- return {
575
- __MP_BACK_SHOW_PAGE_CONTAINER__,
576
- __MP_WEIXIN_ACTIVEBACK__,
577
- __MP_WEIXIN_INACTIVEBACK__
578
- }
579
- }
580
- `;
681
+ if (id === resolvedVirtualFileId) {
682
+ return virtualModuleCode(context.config.mode !== "production");
581
683
  }
582
684
  },
583
685
  async transform(code, id) {
584
- if (id.includes("node_modules") || !id.includes(".vue")) {
686
+ if (!enabled)
687
+ return;
688
+ const [filename, rawQuery] = id.split("?", 2);
689
+ if (filename.includes("node_modules"))
690
+ return;
691
+ if (!filename.endsWith(".vue"))
692
+ return;
693
+ if (rawQuery && new URLSearchParams(rawQuery).has("vue"))
694
+ return;
695
+ if (!code.includes(virtualFileId) && !code.includes(ON_PAGE_BACK))
696
+ return;
697
+ try {
698
+ return await context.transform(code, id);
699
+ } catch (error) {
700
+ if (error instanceof MpBackConfigError) {
701
+ this.error(`[mp-weixin-back] ${error.message}`);
702
+ }
703
+ const message = error instanceof Error ? error.message : String(error);
704
+ context.log.error(`Failed to transform ${id}: ${message}`);
705
+ this.warn(`[mp-weixin-back] \u8F6C\u6362 ${id} \u5931\u8D25\uFF0C\u8BE5\u9875\u9762\u7684\u8FD4\u56DE\u62E6\u622A\u672A\u751F\u6548\uFF1A${message}`);
585
706
  return;
586
707
  }
587
- return {
588
- code: await context.transform(code, id),
589
- map: null
590
- };
591
708
  }
592
709
  };
593
710
  }