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/utils/walker.ts DELETED
@@ -1,523 +0,0 @@
1
- import generate from '@babel/generator'
2
- import MagicString from 'magic-string'
3
- import { babelParse, walkAST } from 'ast-kit'
4
- import { pageContext } from '../src/context'
5
- import { virtualFileId } from './constant'
6
- import type {
7
- WithStatement,
8
- BlockStatement,
9
- Statement,
10
- FunctionExpression,
11
- Node,
12
- ObjectExpression,
13
- ObjectMethod,
14
- ObjectProperty,
15
- } from '@babel/types'
16
-
17
- const pageContainerComp =
18
- ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n'
19
-
20
- function isArrowFunction(func: Function) {
21
- if (typeof func !== 'function') return false
22
- return !func.hasOwnProperty('prototype') && func.toString().includes('=>')
23
- }
24
-
25
- function compositionWalk(context: pageContext, code: string, sfc: any, id: string) {
26
- const codeMs = new MagicString(code)
27
- const setupAst = babelParse(sfc.scriptSetup!.loc.source, sfc.scriptSetup!.lang)
28
-
29
- let pageInfo = {
30
- hasPageBack: false,
31
- pageBackFnName: 'onPageBack',
32
- hasImportRef: false,
33
- backConfig: { ...context.config },
34
- onPageBackBodyAst: [] as Statement[],
35
- onPageBackCallNodeToRemove: null as Node | null,
36
- activeFnName: 'activeMpBack',
37
- inActiveFnName: 'inactiveMpBack',
38
- }
39
-
40
- const activeFnCallsToModify: any[] = []
41
- const inActiveFnCallsToModify: any[] = []
42
-
43
- if (setupAst) {
44
- walkAST<Node>(setupAst, {
45
- enter(node) {
46
- if (node.type === 'ImportDeclaration') {
47
- if (node.source.value.includes(virtualFileId)) {
48
- const importDefaultSpecifiers = node.specifiers.filter(
49
- (i) => i.type === 'ImportDefaultSpecifier'
50
- )
51
- const importDefaultSpecifier = importDefaultSpecifiers[0]
52
- pageInfo.hasPageBack = true
53
- pageInfo.pageBackFnName = importDefaultSpecifier.local.name
54
-
55
- const importSpecifiers = node.specifiers.filter((i) => i.type === 'ImportSpecifier')
56
- importSpecifiers.map((specifiers) => {
57
- if (
58
- specifiers.imported.type === 'Identifier' &&
59
- specifiers.imported.name === 'activeMpBack'
60
- ) {
61
- pageInfo.activeFnName = specifiers.local.name
62
- }
63
- if (
64
- specifiers.imported.type === 'Identifier' &&
65
- specifiers.imported.name === 'inactiveMpBack'
66
- ) {
67
- pageInfo.inActiveFnName = specifiers.local.name
68
- }
69
- })
70
- }
71
- if (node.source.value === 'vue') {
72
- node.specifiers.some((specifier) => {
73
- if (specifier.local.name === 'ref') {
74
- pageInfo.hasImportRef = true
75
- return true
76
- }
77
- return false
78
- })
79
- }
80
- }
81
-
82
- if (
83
- node.type === 'ExpressionStatement' &&
84
- node.expression.type === 'CallExpression' &&
85
- node.expression.callee.loc?.identifierName === pageInfo.pageBackFnName
86
- ) {
87
- // 记录下整个 onPageBack(...) 语句节点,以便后续移除
88
- pageInfo.onPageBackCallNodeToRemove = node
89
- const callback = node.expression.arguments[0]
90
- const backArguments = node.expression.arguments[1]
91
-
92
- if (backArguments?.type === 'ObjectExpression') {
93
- const config = new Function(
94
- // @ts-ignore
95
- `return (${(generate.default ? generate.default : generate)(backArguments).code});`
96
- )()
97
- Object.assign(pageInfo.backConfig, config)
98
- }
99
-
100
- if (
101
- callback &&
102
- (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
103
- ) {
104
- pageInfo.onPageBackBodyAst = callback.body.body
105
- }
106
-
107
- // 跳过此节点的子节点遍历,因为我们将手动处理其内部逻辑
108
- return
109
- }
110
-
111
- if (
112
- node.type === 'ExpressionStatement' &&
113
- node.expression.type === 'CallExpression' &&
114
- node.expression.callee.loc?.identifierName === pageInfo.activeFnName
115
- ) {
116
- activeFnCallsToModify.push({
117
- start: node.expression.start,
118
- end: node.expression.end,
119
- original: sfc.scriptSetup!.loc.source.substring(
120
- node.expression.start,
121
- node.expression.end
122
- ),
123
- name: pageInfo.activeFnName,
124
- })
125
- }
126
-
127
- if (
128
- node.type === 'ExpressionStatement' &&
129
- node.expression.type === 'CallExpression' &&
130
- node.expression.callee.loc?.identifierName === pageInfo.inActiveFnName
131
- ) {
132
- inActiveFnCallsToModify.push({
133
- start: node.expression.start,
134
- end: node.expression.end,
135
- original: sfc.scriptSetup!.loc.source.substring(
136
- node.expression.start,
137
- node.expression.end
138
- ),
139
- name: pageInfo.inActiveFnName,
140
- })
141
- }
142
- },
143
- })
144
- }
145
-
146
- // 没有引入mp-weixin-back-helper
147
- if (!pageInfo.hasPageBack) return code
148
-
149
- if (pageInfo.onPageBackCallNodeToRemove) {
150
- const scriptSetupOffset = sfc.scriptSetup!.loc.start.offset
151
- const nodeToRemove = pageInfo.onPageBackCallNodeToRemove as any
152
- const globalStart = scriptSetupOffset + nodeToRemove.start
153
- const globalEnd = scriptSetupOffset + nodeToRemove.end
154
- codeMs.remove(globalStart, globalEnd)
155
- }
156
-
157
- let callbackCode = ''
158
- if (pageInfo.onPageBackBodyAst.length > 0) {
159
- // 包装成一个临时的 AST 根节点以供遍历
160
- const tempAstRoot = {
161
- type: 'BlockStatement',
162
- body: pageInfo.onPageBackBodyAst,
163
- } as WithStatement
164
-
165
- walkAST(tempAstRoot, {
166
- enter(node: any) {
167
- if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
168
- const createIdentifier = (name: string) => ({ type: 'Identifier', name })
169
-
170
- if (node.callee.name === pageInfo.activeFnName) {
171
- node.arguments.unshift(createIdentifier('__MP_WEIXIN_ACTIVEBACK__'))
172
- } else if (node.callee.name === pageInfo.inActiveFnName) {
173
- node.arguments.unshift(createIdentifier('__MP_WEIXIN_INACTIVEBACK__'))
174
- }
175
- }
176
- },
177
- })
178
-
179
- callbackCode = pageInfo.onPageBackBodyAst
180
- // @ts-ignore
181
- .map((statement) => (generate.default ? generate.default : generate)(statement).code)
182
- .join('\n')
183
- }
184
-
185
- if (code.includes('<page-container')) {
186
- context.log.debugLog(`${context.getPageById(id)}页面已有page-container组件,注入失败`)
187
- return code
188
- }
189
-
190
- if (!pageInfo.backConfig.preventDefault) {
191
- callbackCode += 'uni.navigateBack({ delta: 1 });'
192
- }
193
-
194
- const importUseMpWeixinBack = `import { useMpWeixinBack } from '${virtualFileId}'`
195
- const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : ''
196
- const stateFrequency = 'let __MP_BACK_FREQUENCY__ = 1;'
197
-
198
- const statePageContainerVar = `
199
- const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${pageInfo.backConfig.initialValue})
200
- `
201
-
202
- // 获取传入插件的统一方法
203
- const configBack = (() => {
204
- const onPageBack = pageInfo.backConfig.onPageBack
205
- if (!onPageBack) return ''
206
- if (typeof onPageBack !== 'function') {
207
- throw new Error('`onPageBack` must be a function')
208
- }
209
- const params = JSON.stringify({ page: context.getPageById(id) })
210
- if (isArrowFunction(onPageBack) || onPageBack.toString().includes('function')) {
211
- return `(${onPageBack})(${params});`
212
- }
213
- return `(function ${onPageBack})()`
214
- })()
215
-
216
- const stateBeforeLeave = `
217
- const onBeforeLeave = () => {
218
- if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) {
219
- return
220
- }
221
- if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
222
- __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
223
- setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
224
- __MP_BACK_FREQUENCY__++
225
- }
226
- ${configBack}
227
- ${callbackCode}
228
- };
229
- `
230
- const { template, scriptSetup } = sfc
231
-
232
- // template标签中插入page-container组件
233
- const tempOffsets = {
234
- start: template.loc.start.offset,
235
- end: template.loc.end.offset,
236
- content: template.content,
237
- }
238
- const templateMagicString = new MagicString(tempOffsets.content)
239
- templateMagicString.append(pageContainerComp)
240
- codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString())
241
-
242
- // script标签中插入声明的变量和方法
243
- const scriptOffsets = {
244
- start: scriptSetup.loc.start.offset,
245
- end: scriptSetup.loc.end.offset,
246
- content: scriptSetup.content || '',
247
- }
248
- const scriptMagicString = new MagicString(scriptOffsets.content)
249
- scriptMagicString.prepend(
250
- ` ${importRefFromVue}
251
- ${importUseMpWeixinBack}
252
- ${stateFrequency}
253
- ${statePageContainerVar}
254
- ${stateBeforeLeave} `
255
- )
256
-
257
- // 应用 activeMpBack 调用的修改
258
- activeFnCallsToModify.forEach((call) => {
259
- // 使用正则匹配函数调用结构,确保我们只修改括号内的内容
260
- const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, 'g')
261
- const newCall = call.original.replace(fnCallRegex, (_match: any, args: string) => {
262
- // 如果原调用没有参数
263
- if (!args.trim()) {
264
- return `${call.name}(__MP_WEIXIN_ACTIVEBACK__)`
265
- }
266
- // 如果有参数,添加新参数
267
- return `${call.name}(__MP_WEIXIN_ACTIVEBACK__, ${args})`
268
- })
269
-
270
- scriptMagicString.overwrite(call.start, call.end, newCall)
271
- })
272
-
273
- inActiveFnCallsToModify.forEach((call) => {
274
- // 使用正则匹配函数调用结构,确保我们只修改括号内的内容
275
- const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, 'g')
276
- const newCall = call.original.replace(fnCallRegex, (_match: any, args: string) => {
277
- // 如果原调用没有参数
278
- if (!args.trim()) {
279
- return `${call.name}(__MP_WEIXIN_INACTIVEBACK__)`
280
- }
281
- // 如果有参数,添加新参数
282
- return `${call.name}(__MP_WEIXIN_INACTIVEBACK__, ${args})`
283
- })
284
-
285
- scriptMagicString.overwrite(call.start, call.end, newCall)
286
- })
287
-
288
- codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString())
289
-
290
- return codeMs.toString()
291
- }
292
-
293
- function optionsWalk(context: pageContext, code: string, sfc: any, id: string) {
294
- const codeMs = new MagicString(code)
295
- const ast = babelParse(sfc.script.loc.source, sfc.script.lang)
296
-
297
- let pageInfo = {
298
- hasPageBack: false,
299
- pageBackFnName: 'onPageBack',
300
- backConfig: { ...context.config },
301
- }
302
-
303
- let exportDefaultNode: ObjectExpression | null = null
304
- let dataMethodNode: BlockStatement | null = null
305
- let methodsNode: ObjectExpression | null = null
306
- let onPageBackNodeMethod: ObjectMethod | null = null
307
- let onPageBackNodeProperty: ObjectProperty | null = null
308
-
309
- if (ast) {
310
- walkAST<Node>(ast, {
311
- enter(node) {
312
- // todo需要判断使用默认选项式还是使用了setup
313
- if (
314
- node.type === 'ExportDefaultDeclaration' &&
315
- node.declaration.type === 'ObjectExpression'
316
- ) {
317
- exportDefaultNode = node.declaration
318
- const properties = node.declaration.properties
319
-
320
- for (let i = 0; i < properties.length; i++) {
321
- const element = properties[i]
322
- // export default 的 data()
323
- if (
324
- element.type === 'ObjectMethod' &&
325
- element.key.type === 'Identifier' &&
326
- element.key.name === 'data' &&
327
- element.body.type === 'BlockStatement'
328
- ) {
329
- dataMethodNode = element.body
330
- }
331
- // export default 的 methods
332
- if (
333
- element.type === 'ObjectProperty' &&
334
- element.key.type === 'Identifier' &&
335
- element.key.name === 'methods'
336
- ) {
337
- methodsNode = element.value as ObjectExpression
338
- }
339
-
340
- // 获取export default 的 onPackBack
341
- const blockStatementCondition =
342
- element.type === 'ObjectMethod' &&
343
- element.key.type === 'Identifier' &&
344
- element.key.name === pageInfo.pageBackFnName &&
345
- element.body.type === 'BlockStatement'
346
-
347
- const functionExpressionCondition =
348
- element.type === 'ObjectProperty' &&
349
- element.key.type === 'Identifier' &&
350
- element.key.name === pageInfo.pageBackFnName &&
351
- element.value.type === 'FunctionExpression'
352
-
353
- if (blockStatementCondition) {
354
- pageInfo.hasPageBack = true
355
- onPageBackNodeMethod = element
356
- }
357
-
358
- if (functionExpressionCondition) {
359
- pageInfo.hasPageBack = true
360
- onPageBackNodeProperty = element
361
- }
362
- }
363
- }
364
- },
365
- })
366
- }
367
-
368
- if (!pageInfo.hasPageBack) return
369
-
370
- const newDataProperty = [
371
- {
372
- type: 'ObjectProperty',
373
- key: { type: 'Identifier', name: '__MP_BACK_SHOW_PAGE_CONTAINER__' },
374
- value: { type: 'BooleanLiteral', value: true },
375
- computed: false,
376
- shorthand: false,
377
- },
378
- {
379
- type: 'ObjectProperty',
380
- key: { type: 'Identifier', name: '__MP_BACK_FREQUENCY__' },
381
- value: { type: 'NumericLiteral', value: 1 },
382
- computed: false,
383
- shorthand: false,
384
- },
385
- ] as ObjectProperty[]
386
- if (dataMethodNode) {
387
- const returnStatement = (dataMethodNode as BlockStatement).body.find(
388
- (node) => node.type === 'ReturnStatement'
389
- )
390
- if (
391
- returnStatement &&
392
- returnStatement.argument &&
393
- returnStatement.argument.type === 'ObjectExpression'
394
- ) {
395
- // 添加新的属性
396
- returnStatement.argument.properties.push(...newDataProperty)
397
- }
398
- } else if (exportDefaultNode) {
399
- const addData: ObjectMethod = {
400
- type: 'ObjectMethod',
401
- key: { type: 'Identifier', name: 'data' },
402
- kind: 'method',
403
- params: [],
404
- async: false,
405
- generator: false,
406
- computed: false,
407
- body: {
408
- type: 'BlockStatement',
409
- directives: [],
410
- body: [
411
- {
412
- type: 'ReturnStatement',
413
- argument: {
414
- type: 'ObjectExpression',
415
- properties: newDataProperty,
416
- },
417
- },
418
- ],
419
- },
420
- }
421
- ;(exportDefaultNode as ObjectExpression).properties.push(addData)
422
- }
423
-
424
- // 获取传入插件的统一方法
425
- const configBack = (() => {
426
- const onPageBack = pageInfo.backConfig.onPageBack
427
- if (!onPageBack) return ''
428
- if (typeof onPageBack !== 'function') {
429
- throw new Error('`onPageBack` must be a function')
430
- }
431
- const params = JSON.stringify({ page: context.getPageById(id) })
432
- if (isArrowFunction(onPageBack) || onPageBack.toString().includes('function')) {
433
- return `(${onPageBack})(${params});`
434
- }
435
- return `(function ${onPageBack})()`
436
- })()
437
-
438
- const stateBeforeLeave = `
439
- function onBeforeLeave() {
440
- if (this.__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
441
- this.__MP_BACK_SHOW_PAGE_CONTAINER__ = false
442
- setTimeout(() => { this.__MP_BACK_SHOW_PAGE_CONTAINER__ = true }, 0);
443
- this.__MP_BACK_FREQUENCY__++
444
- }
445
- ${configBack}
446
- ${!pageInfo.backConfig.preventDefault ? 'uni.navigateBack({ delta: 1 });' : ''}
447
- };
448
- `
449
- const stateBeforeLeaveAst = babelParse(stateBeforeLeave)
450
- const stateBeforeLeaveNode = stateBeforeLeaveAst.body.find(
451
- (node) => node.type === 'FunctionDeclaration'
452
- )
453
- const newMethodsProperty = {
454
- type: 'ObjectMethod',
455
- key: {
456
- type: 'Identifier',
457
- name: 'onBeforeLeave',
458
- },
459
- kind: 'method',
460
- generator: false,
461
- async: false,
462
- params: [],
463
- computed: false,
464
- body: {
465
- type: 'BlockStatement',
466
- directives: [],
467
- body: [
468
- ...(onPageBackNodeMethod ? (onPageBackNodeMethod as ObjectMethod)!.body.body : []),
469
- ...(onPageBackNodeProperty
470
- ? ((onPageBackNodeProperty as ObjectProperty)!.value as FunctionExpression).body.body
471
- : []),
472
- ...stateBeforeLeaveNode!.body.body,
473
- ],
474
- },
475
- } as ObjectMethod
476
- if (methodsNode) {
477
- ;(methodsNode as ObjectExpression).properties.push(newMethodsProperty)
478
- } else if (exportDefaultNode) {
479
- const addMethods: ObjectProperty = {
480
- type: 'ObjectProperty',
481
- computed: false,
482
- shorthand: false,
483
- key: {
484
- type: 'Identifier',
485
- name: 'methods',
486
- },
487
- value: {
488
- type: 'ObjectExpression',
489
- properties: [newMethodsProperty],
490
- },
491
- }
492
- ;(exportDefaultNode as ObjectExpression).properties.push(addMethods)
493
- }
494
-
495
- const { template, script } = sfc
496
-
497
- // template标签中插入page-container组件
498
- const tempOffsets = {
499
- start: template.loc.start.offset,
500
- end: template.loc.end.offset,
501
- content: template.content,
502
- }
503
- const templateMagicString = new MagicString(tempOffsets.content)
504
- templateMagicString.append(pageContainerComp)
505
- codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString())
506
-
507
- // script标签中插入声明的变量和方法
508
- const scriptOffsets = {
509
- start: script.loc.start.offset,
510
- end: script.loc.end.offset,
511
- }
512
-
513
- // @ts-ignore
514
- const newScriptContent = (generate.default ? generate.default : generate)(ast).code
515
- codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, newScriptContent)
516
-
517
- return codeMs.toString()
518
- }
519
-
520
- export const vueWalker = {
521
- compositionWalk,
522
- optionsWalk,
523
- }
package/vite.config.ts DELETED
@@ -1,19 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import vue from '@vitejs/plugin-vue'
3
- import mpBack from './dist/index.mjs'
4
-
5
- export default defineConfig({
6
- plugins: [
7
- mpBack(),
8
- vue({
9
- template: {
10
- compilerOptions: {
11
- isCustomElement: (tag) => tag.startsWith('page-') || tag.startsWith('mp-'),
12
- },
13
- },
14
- }),
15
- ],
16
- test: {
17
- environment: 'happy-dom',
18
- },
19
- })