mp-weixin-back 0.0.8 → 0.0.10

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
@@ -2,59 +2,49 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  import JSON5 from 'json5';
4
4
  import { white, red, green } from 'kolorist';
5
- import generate from '@babel/generator';
6
5
  import { parse } from '@vue/compiler-sfc';
7
- import { babelParse, walkAST } from 'ast-kit';
6
+ import generate from '@babel/generator';
8
7
  import MagicString from 'magic-string';
8
+ import { babelParse, walkAST } from 'ast-kit';
9
9
 
10
10
  const virtualFileId = "mp-weixin-back-helper";
11
11
 
12
+ const pageContainerComp = ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n';
12
13
  function isArrowFunction(func) {
13
14
  if (typeof func !== "function")
14
15
  return false;
15
16
  return !func.hasOwnProperty("prototype") && func.toString().includes("=>");
16
17
  }
17
- async function parseSFC(code) {
18
- try {
19
- return parse(code).descriptor;
20
- } catch (error) {
21
- throw new Error(`\u89E3\u6790vue\u6587\u4EF6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u6B63\u786E`);
22
- }
23
- }
24
- async function transformVueFile(code, id) {
25
- const sfc = await parseSFC(code);
26
- if (!sfc.template?.content) {
27
- return code;
28
- }
29
- const componentStr = ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n';
30
- let pageBackConfig = { ...this.config };
31
- let hasPageBack = false;
32
- let hasImportRef = false;
33
- let pageBackFnName = "onPageBack";
34
- let callbackCode = ``;
18
+ function compositionWalk(context, code, sfc, id) {
35
19
  const codeMs = new MagicString(code);
36
- const setupCode = sfc.scriptSetup?.loc.source || "";
37
- const setupAst = babelParse(setupCode, sfc.scriptSetup?.lang);
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
+ callbackCode: ""
27
+ };
38
28
  if (setupAst) {
39
29
  walkAST(setupAst, {
40
30
  enter(node) {
41
31
  if (node.type === "ImportDeclaration") {
42
32
  if (node.source.value.includes(virtualFileId)) {
43
33
  const importSpecifier = node.specifiers[0];
44
- hasPageBack = true;
45
- pageBackFnName = importSpecifier.local.name;
34
+ pageInfo.hasPageBack = true;
35
+ pageInfo.pageBackFnName = importSpecifier.local.name;
46
36
  }
47
37
  if (node.source.value === "vue") {
48
38
  node.specifiers.some((specifier) => {
49
39
  if (specifier.local.name === "ref") {
50
- hasImportRef = true;
40
+ pageInfo.hasImportRef = true;
51
41
  return true;
52
42
  }
53
43
  return false;
54
44
  });
55
45
  }
56
46
  }
57
- if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageBackFnName) {
47
+ if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.pageBackFnName) {
58
48
  const callback = node.expression.arguments[0];
59
49
  const backArguments = node.expression.arguments[1];
60
50
  if (backArguments?.type === "ObjectExpression") {
@@ -62,12 +52,12 @@ async function transformVueFile(code, id) {
62
52
  // @ts-ignore
63
53
  `return (${(generate.default ? generate.default : generate)(backArguments).code});`
64
54
  )();
65
- Object.assign(pageBackConfig, config);
55
+ Object.assign(pageInfo.backConfig, config);
66
56
  }
67
57
  if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
68
58
  const body = callback.body;
69
59
  if (body.type === "BlockStatement") {
70
- callbackCode += body.body.map(
60
+ pageInfo.callbackCode += body.body.map(
71
61
  // @ts-ignore
72
62
  (statement) => (generate.default ? generate.default : generate)(statement).code
73
63
  ).join("");
@@ -77,66 +67,265 @@ async function transformVueFile(code, id) {
77
67
  }
78
68
  });
79
69
  }
80
- if (!hasPageBack)
81
- return;
82
- this.log.devLog(`\u9875\u9762${this.getPageById(id)}\u6CE8\u5165mp-weixin-back`);
70
+ if (!pageInfo.hasPageBack)
71
+ return code;
83
72
  if (code.includes("<page-container")) {
84
- this.log.devLog(`${this.getPageById(id)}\u9875\u9762\u5DF2\u6709page-container\u7EC4\u4EF6\uFF0C\u6CE8\u5165\u5931\u8D25`);
73
+ context.log.debugLog(`${context.getPageById(id)}\u9875\u9762\u5DF2\u6709page-container\u7EC4\u4EF6\uFF0C\u6CE8\u5165\u5931\u8D25`);
85
74
  return code;
86
75
  }
87
- if (!pageBackConfig.preventDefault) {
88
- callbackCode += `uni.navigateBack({ delta: 1 });`;
76
+ if (!pageInfo.backConfig.preventDefault) {
77
+ pageInfo.callbackCode += "uni.navigateBack({ delta: 1 });";
89
78
  }
79
+ const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : "";
80
+ const stateFrequency = "let __MP_BACK_FREQUENCY__ = 1;";
81
+ const statePageContainerVar = "const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);";
90
82
  const configBack = (() => {
91
- const onPageBack = pageBackConfig.onPageBack;
83
+ const onPageBack = pageInfo.backConfig.onPageBack;
92
84
  if (!onPageBack)
93
85
  return "";
94
86
  if (typeof onPageBack !== "function") {
95
87
  throw new Error("`onPageBack` must be a function");
96
88
  }
97
- const params = JSON.stringify({ page: this.getPageById(id) });
89
+ const params = JSON.stringify({ page: context.getPageById(id) });
98
90
  if (isArrowFunction(onPageBack) || onPageBack.toString().includes("function")) {
99
91
  return `(${onPageBack})(${params});`;
100
92
  }
101
93
  return `(function ${onPageBack})()`;
102
94
  })();
103
- const beforeLeaveStr = `
104
- ${!hasImportRef ? "import { ref } from 'vue'" : ""}
105
- let __MP_BACK_FREQUENCY__ = 1
106
- const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);
95
+ const stateBeforeLeave = `
107
96
  const onBeforeLeave = () => {
108
- console.log("__MP_BACK_FREQUENCY__", __MP_BACK_FREQUENCY__, ${pageBackConfig.frequency})
109
- if (__MP_BACK_FREQUENCY__ < ${pageBackConfig.frequency}) {
97
+ if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
110
98
  __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
111
99
  setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
112
100
  __MP_BACK_FREQUENCY__++
113
101
  }
114
102
  ${configBack}
115
- ${callbackCode}
103
+ ${pageInfo.callbackCode}
116
104
  };
117
105
  `;
118
- const { template, script, scriptSetup } = sfc;
106
+ const { template, scriptSetup } = sfc;
119
107
  const tempOffsets = {
120
108
  start: template.loc.start.offset,
121
109
  end: template.loc.end.offset,
122
110
  content: template.content
123
111
  };
124
112
  const templateMagicString = new MagicString(tempOffsets.content);
125
- templateMagicString.append(componentStr);
113
+ templateMagicString.append(pageContainerComp);
126
114
  codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString());
127
- const scriptSfc = script || scriptSetup;
128
- if (!scriptSfc)
129
- return;
130
115
  const scriptOffsets = {
131
- start: scriptSfc.loc.start.offset,
132
- end: scriptSfc.loc.end.offset,
133
- content: scriptSfc.content || ""
116
+ start: scriptSetup.loc.start.offset,
117
+ end: scriptSetup.loc.end.offset,
118
+ content: scriptSetup.content || ""
134
119
  };
135
120
  const scriptMagicString = new MagicString(scriptOffsets.content);
136
- scriptMagicString.prepend(beforeLeaveStr);
121
+ scriptMagicString.prepend(
122
+ ` ${importRefFromVue}
123
+ ${stateFrequency}
124
+ ${statePageContainerVar}
125
+ ${stateBeforeLeave} `
126
+ );
137
127
  codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString());
138
128
  return codeMs.toString();
139
129
  }
130
+ function optionsWalk(context, code, sfc, id) {
131
+ const codeMs = new MagicString(code);
132
+ const ast = babelParse(sfc.script.loc.source, sfc.script.lang);
133
+ let pageInfo = {
134
+ hasPageBack: false,
135
+ pageBackFnName: "onPageBack",
136
+ backConfig: { ...context.config }
137
+ };
138
+ let exportDefaultNode = null;
139
+ let dataMethodNode = null;
140
+ let methodsNode = null;
141
+ let onPageBackNodeMethod = null;
142
+ let onPageBackNodeProperty = null;
143
+ if (ast) {
144
+ walkAST(ast, {
145
+ enter(node) {
146
+ if (node.type === "ExportDefaultDeclaration" && node.declaration.type === "ObjectExpression") {
147
+ exportDefaultNode = node.declaration;
148
+ const properties = node.declaration.properties;
149
+ for (let i = 0; i < properties.length; i++) {
150
+ const element = properties[i];
151
+ if (element.type === "ObjectMethod" && element.key.type === "Identifier" && element.key.name === "data" && element.body.type === "BlockStatement") {
152
+ dataMethodNode = element.body;
153
+ }
154
+ if (element.type === "ObjectProperty" && element.key.type === "Identifier" && element.key.name === "methods") {
155
+ methodsNode = element.value;
156
+ }
157
+ const blockStatementCondition = element.type === "ObjectMethod" && element.key.type === "Identifier" && element.key.name === pageInfo.pageBackFnName && element.body.type === "BlockStatement";
158
+ const functionExpressionCondition = element.type === "ObjectProperty" && element.key.type === "Identifier" && element.key.name === pageInfo.pageBackFnName && element.value.type === "FunctionExpression";
159
+ if (blockStatementCondition) {
160
+ pageInfo.hasPageBack = true;
161
+ onPageBackNodeMethod = element;
162
+ }
163
+ if (functionExpressionCondition) {
164
+ pageInfo.hasPageBack = true;
165
+ onPageBackNodeProperty = element;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ });
171
+ }
172
+ if (!pageInfo.hasPageBack)
173
+ return;
174
+ const newDataProperty = [
175
+ {
176
+ type: "ObjectProperty",
177
+ key: { type: "Identifier", name: "__MP_BACK_SHOW_PAGE_CONTAINER__" },
178
+ value: { type: "BooleanLiteral", value: true },
179
+ computed: false,
180
+ shorthand: false
181
+ },
182
+ {
183
+ type: "ObjectProperty",
184
+ key: { type: "Identifier", name: "__MP_BACK_FREQUENCY__" },
185
+ value: { type: "NumericLiteral", value: 1 },
186
+ computed: false,
187
+ shorthand: false
188
+ }
189
+ ];
190
+ if (dataMethodNode) {
191
+ const returnStatement = dataMethodNode.body.find(
192
+ (node) => node.type === "ReturnStatement"
193
+ );
194
+ if (returnStatement && returnStatement.argument && returnStatement.argument.type === "ObjectExpression") {
195
+ returnStatement.argument.properties.push(...newDataProperty);
196
+ }
197
+ } else if (exportDefaultNode) {
198
+ const addData = {
199
+ type: "ObjectMethod",
200
+ key: { type: "Identifier", name: "data" },
201
+ kind: "method",
202
+ params: [],
203
+ async: false,
204
+ generator: false,
205
+ computed: false,
206
+ body: {
207
+ type: "BlockStatement",
208
+ directives: [],
209
+ body: [
210
+ {
211
+ type: "ReturnStatement",
212
+ argument: {
213
+ type: "ObjectExpression",
214
+ properties: newDataProperty
215
+ }
216
+ }
217
+ ]
218
+ }
219
+ };
220
+ exportDefaultNode.properties.push(addData);
221
+ }
222
+ const configBack = (() => {
223
+ const onPageBack = pageInfo.backConfig.onPageBack;
224
+ if (!onPageBack)
225
+ return "";
226
+ if (typeof onPageBack !== "function") {
227
+ throw new Error("`onPageBack` must be a function");
228
+ }
229
+ const params = JSON.stringify({ page: context.getPageById(id) });
230
+ if (isArrowFunction(onPageBack) || onPageBack.toString().includes("function")) {
231
+ return `(${onPageBack})(${params});`;
232
+ }
233
+ return `(function ${onPageBack})()`;
234
+ })();
235
+ const stateBeforeLeave = `
236
+ function onBeforeLeave() {
237
+ if (this.__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
238
+ this.__MP_BACK_SHOW_PAGE_CONTAINER__ = false
239
+ setTimeout(() => { this.__MP_BACK_SHOW_PAGE_CONTAINER__ = true }, 0);
240
+ this.__MP_BACK_FREQUENCY__++
241
+ }
242
+ ${configBack}
243
+ ${!pageInfo.backConfig.preventDefault ? "uni.navigateBack({ delta: 1 });" : ""}
244
+ };
245
+ `;
246
+ const stateBeforeLeaveAst = babelParse(stateBeforeLeave);
247
+ const stateBeforeLeaveNode = stateBeforeLeaveAst.body.find(
248
+ (node) => node.type === "FunctionDeclaration"
249
+ );
250
+ const newMethodsProperty = {
251
+ type: "ObjectMethod",
252
+ key: {
253
+ type: "Identifier",
254
+ name: "onBeforeLeave"
255
+ },
256
+ kind: "method",
257
+ generator: false,
258
+ async: false,
259
+ params: [],
260
+ computed: false,
261
+ body: {
262
+ type: "BlockStatement",
263
+ directives: [],
264
+ body: [
265
+ ...onPageBackNodeMethod ? onPageBackNodeMethod.body.body : [],
266
+ ...onPageBackNodeProperty ? onPageBackNodeProperty.value.body.body : [],
267
+ ...stateBeforeLeaveNode.body.body
268
+ ]
269
+ }
270
+ };
271
+ if (methodsNode) {
272
+ methodsNode.properties.push(newMethodsProperty);
273
+ } else if (exportDefaultNode) {
274
+ const addMethods = {
275
+ type: "ObjectProperty",
276
+ computed: false,
277
+ shorthand: false,
278
+ key: {
279
+ type: "Identifier",
280
+ name: "methods"
281
+ },
282
+ value: {
283
+ type: "ObjectExpression",
284
+ properties: [newMethodsProperty]
285
+ }
286
+ };
287
+ exportDefaultNode.properties.push(addMethods);
288
+ }
289
+ const { template, script } = sfc;
290
+ const tempOffsets = {
291
+ start: template.loc.start.offset,
292
+ end: template.loc.end.offset,
293
+ content: template.content
294
+ };
295
+ const templateMagicString = new MagicString(tempOffsets.content);
296
+ templateMagicString.append(pageContainerComp);
297
+ codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString());
298
+ const scriptOffsets = {
299
+ start: script.loc.start.offset,
300
+ end: script.loc.end.offset
301
+ };
302
+ const newScriptContent = (generate.default ? generate.default : generate)(ast).code;
303
+ codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, newScriptContent);
304
+ return codeMs.toString();
305
+ }
306
+ const vueWalker = {
307
+ compositionWalk,
308
+ optionsWalk
309
+ };
310
+
311
+ async function transformVueFile(code, id) {
312
+ try {
313
+ const sfc = parse(code).descriptor;
314
+ const { template, script, scriptSetup } = sfc;
315
+ if (!template?.content) {
316
+ return code;
317
+ }
318
+ if (!script?.content && !scriptSetup?.content) {
319
+ return code;
320
+ }
321
+ const walker = scriptSetup ? "compositionWalk" : "optionsWalk";
322
+ return vueWalker[walker](this, code, sfc, id);
323
+ } catch (error) {
324
+ this.log.error("\u89E3\u6790vue\u6587\u4EF6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u6B63\u786E");
325
+ this.log.debugLog(String(error));
326
+ return code;
327
+ }
328
+ }
140
329
 
141
330
  var __defProp = Object.defineProperty;
142
331
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -146,18 +335,19 @@ var __publicField = (obj, key, value) => {
146
335
  };
147
336
  class pageContext {
148
337
  constructor(config) {
338
+ __publicField(this, "logPreText", "[mp-weixin-back] : ");
149
339
  __publicField(this, "config");
150
340
  __publicField(this, "pages", []);
151
341
  __publicField(this, "log", {
152
342
  info: (text) => {
153
- console.log(white(text));
343
+ console.log(white(this.logPreText + text));
154
344
  },
155
345
  error: (text) => {
156
- console.log(red(text));
346
+ console.log(red(this.logPreText + text));
157
347
  },
158
- devLog: (text) => {
348
+ debugLog: (text) => {
159
349
  if (this.config.mode === "development" && this.config.debug) {
160
- console.log(green(text));
350
+ console.log(green(this.logPreText + text));
161
351
  }
162
352
  }
163
353
  });
@@ -196,7 +386,7 @@ class pageContext {
196
386
  }
197
387
  } catch (error) {
198
388
  this.log.error("\u8BFB\u53D6pages.json\u6587\u4EF6\u5931\u8D25");
199
- this.log.devLog(String(error));
389
+ this.log.debugLog(String(error));
200
390
  }
201
391
  }
202
392
  // 获取指定id的page
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mp-weixin-back",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.0.10",
5
5
  "description": "监听微信小程序的手势返回和页面默认导航栏的返回",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -64,4 +64,4 @@
64
64
  "unbuild": "^2.0.0",
65
65
  "vue": "^3.5.13"
66
66
  }
67
- }
67
+ }
package/src/context.ts CHANGED
@@ -6,18 +6,19 @@ import { ContextConfig, PagesJson } from '../types'
6
6
  import { transformVueFile } from '../utils'
7
7
 
8
8
  export class pageContext {
9
+ private logPreText = '[mp-weixin-back] : '
9
10
  config: ContextConfig
10
11
  pages: string[] = []
11
12
  log = {
12
13
  info: (text: string) => {
13
- console.log(white(text))
14
+ console.log(white(this.logPreText + text))
14
15
  },
15
16
  error: (text: string) => {
16
- console.log(red(text))
17
+ console.log(red(this.logPreText + text))
17
18
  },
18
- devLog: (text: string) => {
19
+ debugLog: (text: string) => {
19
20
  if (this.config.mode === 'development' && this.config.debug) {
20
- console.log(green(text))
21
+ console.log(green(this.logPreText + text))
21
22
  }
22
23
  },
23
24
  }
@@ -57,7 +58,7 @@ export class pageContext {
57
58
  }
58
59
  } catch (error: unknown) {
59
60
  this.log.error('读取pages.json文件失败')
60
- this.log.devLog(String(error))
61
+ this.log.debugLog(String(error))
61
62
  }
62
63
  }
63
64
  // 获取指定id的page
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div>我是默认的界面</div>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ data() {
8
+ return {}
9
+ },
10
+ onPageBack() {
11
+ console.log('触发了手势返回')
12
+ return true
13
+ },
14
+ }
15
+ </script>
16
+
17
+ <style></style>
@@ -7,6 +7,7 @@ import onPageBack from 'mp-weixin-back-helper'
7
7
 
8
8
  onPageBack(() => {
9
9
  console.log('触发了手势返回')
10
+ return true
10
11
  })
11
12
  </script>
12
13
 
@@ -1,14 +1,31 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
3
  // @ts-ignore
4
- import Index from './data/index.vue'
4
+ import IndexSetup from './data/index-setup.vue'
5
+ // @ts-ignore
6
+ import IndexDefault from './data/index-default.vue'
5
7
 
6
8
  describe('generate page-container components', () => {
7
- it('page has template tag', async () => {
8
- const wrapper = mount(Index)
9
+ it('setup compisitionAPI', async () => {
10
+ const wrapper = mount(IndexSetup)
9
11
  await wrapper.vm.$nextTick()
10
12
 
11
13
  const pageContainerRef = wrapper.find('page-container')
12
14
  expect(pageContainerRef.exists()).toBe(true)
15
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
16
+ expect(wrapper.vm.onBeforeLeave()).toBe(true)
17
+ })
18
+
19
+ it('default optionsAPI', async () => {
20
+ const wrapper = mount(IndexDefault)
21
+ await wrapper.vm.$nextTick()
22
+ const pageContainerRef = wrapper.find('page-container')
23
+ expect(pageContainerRef.exists()).toBe(true)
24
+ // @ts-ignore
25
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
26
+ // @ts-ignore
27
+ expect(wrapper.vm.__MP_BACK_FREQUENCY__).toBe(1)
28
+ // @ts-ignore
29
+ expect(wrapper.vm.onBeforeLeave()).toBe(true)
13
30
  })
14
31
  })