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/utils/index.ts CHANGED
@@ -1,167 +1,26 @@
1
- import generate from '@babel/generator'
2
1
  import { parse } from '@vue/compiler-sfc'
3
- import { babelParse, walkAST } from 'ast-kit'
4
2
  import { pageContext } from '../src/context'
5
- import MagicString from 'magic-string'
6
- import { virtualFileId } from './constant'
3
+ import { vueWalker } from './walker'
7
4
  import type { SFCDescriptor } from '@vue/compiler-sfc'
8
- import type { Node } from '@babel/types'
9
-
10
- function isArrowFunction(func: Function) {
11
- if (typeof func !== 'function') return false
12
- return !func.hasOwnProperty('prototype') && func.toString().includes('=>')
13
- }
14
-
15
- async function parseSFC(code: string): Promise<SFCDescriptor> {
16
- try {
17
- return parse(code).descriptor
18
- } catch (error) {
19
- throw new Error(`解析vue文件失败,请检查文件是否正确`)
20
- }
21
- }
22
5
 
23
6
  export async function transformVueFile(this: pageContext, code: string, id: string) {
24
- const sfc = await parseSFC(code)
25
- if (!sfc.template?.content) {
26
- return code
27
- }
28
-
29
- const componentStr =
30
- ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n'
31
-
32
- let pageBackConfig = { ...this.config }
33
- let hasPageBack = false
34
- let hasImportRef = false
35
- let pageBackFnName = 'onPageBack'
36
- let callbackCode = ``
37
-
38
- const codeMs = new MagicString(code)
39
- const setupCode = sfc.scriptSetup?.loc.source || ''
40
- const setupAst = babelParse(setupCode, sfc.scriptSetup?.lang)
41
-
42
- if (setupAst) {
43
- walkAST<Node>(setupAst, {
44
- enter(node) {
45
- if (node.type === 'ImportDeclaration') {
46
- if (node.source.value.includes(virtualFileId)) {
47
- const importSpecifier = node.specifiers[0]
48
- hasPageBack = true
49
- pageBackFnName = importSpecifier.local.name
50
- }
51
- if (node.source.value === 'vue') {
52
- node.specifiers.some((specifier) => {
53
- if (specifier.local.name === 'ref') {
54
- hasImportRef = true
55
- return true
56
- }
57
- return false
58
- })
59
- }
60
- }
61
-
62
- if (
63
- node.type === 'ExpressionStatement' &&
64
- node.expression.type === 'CallExpression' &&
65
- node.expression.callee.loc?.identifierName === pageBackFnName
66
- ) {
67
- const callback = node.expression.arguments[0]
68
- const backArguments = node.expression.arguments[1]
69
-
70
- if (backArguments?.type === 'ObjectExpression') {
71
- const config = new Function(
72
- // @ts-ignore
73
- `return (${(generate.default ? generate.default : generate)(backArguments).code});`
74
- )()
75
- Object.assign(pageBackConfig, config)
76
- }
77
-
78
- if (
79
- callback &&
80
- (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
81
- ) {
82
- const body = callback.body
83
- if (body.type === 'BlockStatement') {
84
- callbackCode += body.body
85
- .map(
86
- // @ts-ignore
87
- (statement) => (generate.default ? generate.default : generate)(statement).code
88
- )
89
- .join('')
90
- }
91
- }
92
- }
93
- },
94
- })
95
- }
96
-
97
- if (!hasPageBack) return
98
-
99
- this.log.devLog(`页面${this.getPageById(id)}注入mp-weixin-back`)
100
-
101
- if (code.includes('<page-container')) {
102
- this.log.devLog(`${this.getPageById(id)}页面已有page-container组件,注入失败`)
103
- return code
104
- }
105
-
106
- if (!pageBackConfig.preventDefault) {
107
- callbackCode += `uni.navigateBack({ delta: 1 });`
108
- }
109
-
110
- const configBack = (() => {
111
- const onPageBack = pageBackConfig.onPageBack
112
- if (!onPageBack) return ''
113
- if (typeof onPageBack !== 'function') {
114
- throw new Error('`onPageBack` must be a function')
7
+ try {
8
+ const sfc = parse(code).descriptor
9
+ const { template, script, scriptSetup } = sfc
10
+ if (!template?.content) {
11
+ return code
115
12
  }
116
13
 
117
- const params = JSON.stringify({ page: this.getPageById(id) })
118
-
119
- if (isArrowFunction(onPageBack) || onPageBack.toString().includes('function')) {
120
- return `(${onPageBack})(${params});`
14
+ if (!script?.content && !scriptSetup?.content) {
15
+ return code
121
16
  }
122
17
 
123
- return `(function ${onPageBack})()`
124
- })()
125
-
126
- const beforeLeaveStr = `
127
- ${!hasImportRef ? "import { ref } from 'vue'" : ''}
128
- let __MP_BACK_FREQUENCY__ = 1
129
- const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);
130
- const onBeforeLeave = () => {
131
- console.log("__MP_BACK_FREQUENCY__", __MP_BACK_FREQUENCY__, ${pageBackConfig.frequency})
132
- if (__MP_BACK_FREQUENCY__ < ${pageBackConfig.frequency}) {
133
- __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
134
- setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
135
- __MP_BACK_FREQUENCY__++
136
- }
137
- ${configBack}
138
- ${callbackCode}
139
- };
140
- `
141
-
142
- const { template, script, scriptSetup } = sfc
143
- const tempOffsets = {
144
- start: template.loc.start.offset,
145
- end: template.loc.end.offset,
146
- content: template.content,
147
- }
148
-
149
- const templateMagicString = new MagicString(tempOffsets.content)
150
- templateMagicString.append(componentStr)
151
- codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString())
152
-
153
- const scriptSfc = script || scriptSetup
154
- if (!scriptSfc) return
155
-
156
- const scriptOffsets = {
157
- start: scriptSfc.loc.start.offset,
158
- end: scriptSfc.loc.end.offset,
159
- content: scriptSfc.content || '',
18
+ // 判断页面是否为组合式写法
19
+ const walker = scriptSetup ? 'compositionWalk' : 'optionsWalk'
20
+ return vueWalker[walker](this, code, sfc, id)
21
+ } catch (error) {
22
+ this.log.error('解析vue文件失败,请检查文件是否正确')
23
+ this.log.debugLog(String(error))
24
+ return code
160
25
  }
161
-
162
- const scriptMagicString = new MagicString(scriptOffsets.content)
163
- scriptMagicString.prepend(beforeLeaveStr)
164
- codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString())
165
-
166
- return codeMs.toString()
167
26
  }
@@ -0,0 +1,389 @@
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
+ BlockStatement,
8
+ FunctionExpression,
9
+ Node,
10
+ ObjectExpression,
11
+ ObjectMethod,
12
+ ObjectProperty,
13
+ } from '@babel/types'
14
+
15
+ const pageContainerComp =
16
+ ' <page-container :show="__MP_BACK_SHOW_PAGE_CONTAINER__" :overlay="false" @beforeleave="onBeforeLeave" :z-index="1" :duration="false"></page-container>\n'
17
+
18
+ function isArrowFunction(func: Function) {
19
+ if (typeof func !== 'function') return false
20
+ return !func.hasOwnProperty('prototype') && func.toString().includes('=>')
21
+ }
22
+
23
+ function compositionWalk(context: pageContext, code: string, sfc: any, id: string) {
24
+ const codeMs = new MagicString(code)
25
+ const setupAst = babelParse(sfc.scriptSetup!.loc.source, sfc.scriptSetup!.lang)
26
+
27
+ let pageInfo = {
28
+ hasPageBack: false,
29
+ pageBackFnName: 'onPageBack',
30
+ hasImportRef: false,
31
+ backConfig: { ...context.config },
32
+ callbackCode: '',
33
+ }
34
+
35
+ if (setupAst) {
36
+ walkAST<Node>(setupAst, {
37
+ enter(node) {
38
+ if (node.type === 'ImportDeclaration') {
39
+ if (node.source.value.includes(virtualFileId)) {
40
+ const importSpecifier = node.specifiers[0]
41
+ pageInfo.hasPageBack = true
42
+ pageInfo.pageBackFnName = importSpecifier.local.name
43
+ }
44
+ if (node.source.value === 'vue') {
45
+ node.specifiers.some((specifier) => {
46
+ if (specifier.local.name === 'ref') {
47
+ pageInfo.hasImportRef = true
48
+ return true
49
+ }
50
+ return false
51
+ })
52
+ }
53
+ }
54
+
55
+ if (
56
+ node.type === 'ExpressionStatement' &&
57
+ node.expression.type === 'CallExpression' &&
58
+ node.expression.callee.loc?.identifierName === pageInfo.pageBackFnName
59
+ ) {
60
+ const callback = node.expression.arguments[0]
61
+ const backArguments = node.expression.arguments[1]
62
+
63
+ if (backArguments?.type === 'ObjectExpression') {
64
+ const config = new Function(
65
+ // @ts-ignore
66
+ `return (${(generate.default ? generate.default : generate)(backArguments).code});`
67
+ )()
68
+ Object.assign(pageInfo.backConfig, config)
69
+ }
70
+
71
+ if (
72
+ callback &&
73
+ (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
74
+ ) {
75
+ const body = callback.body
76
+ if (body.type === 'BlockStatement') {
77
+ pageInfo.callbackCode += body.body
78
+ .map(
79
+ // @ts-ignore
80
+ (statement) => (generate.default ? generate.default : generate)(statement).code
81
+ )
82
+ .join('')
83
+ }
84
+ }
85
+ }
86
+ },
87
+ })
88
+ }
89
+
90
+ // 没有引入mp-weixin-back-helper
91
+ if (!pageInfo.hasPageBack) return code
92
+
93
+ if (code.includes('<page-container')) {
94
+ context.log.debugLog(`${context.getPageById(id)}页面已有page-container组件,注入失败`)
95
+ return code
96
+ }
97
+
98
+ if (!pageInfo.backConfig.preventDefault) {
99
+ pageInfo.callbackCode += 'uni.navigateBack({ delta: 1 });'
100
+ }
101
+
102
+ const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : ''
103
+ const stateFrequency = 'let __MP_BACK_FREQUENCY__ = 1;'
104
+ const statePageContainerVar = 'const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);'
105
+ // 获取传入插件的统一方法
106
+ const configBack = (() => {
107
+ const onPageBack = pageInfo.backConfig.onPageBack
108
+ if (!onPageBack) return ''
109
+ if (typeof onPageBack !== 'function') {
110
+ throw new Error('`onPageBack` must be a function')
111
+ }
112
+ const params = JSON.stringify({ page: context.getPageById(id) })
113
+ if (isArrowFunction(onPageBack) || onPageBack.toString().includes('function')) {
114
+ return `(${onPageBack})(${params});`
115
+ }
116
+ return `(function ${onPageBack})()`
117
+ })()
118
+ const stateBeforeLeave = `
119
+ const onBeforeLeave = () => {
120
+ if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
121
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
122
+ setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
123
+ __MP_BACK_FREQUENCY__++
124
+ }
125
+ ${configBack}
126
+ ${pageInfo.callbackCode}
127
+ };
128
+ `
129
+ const { template, scriptSetup } = sfc
130
+
131
+ // template标签中插入page-container组件
132
+ const tempOffsets = {
133
+ start: template.loc.start.offset,
134
+ end: template.loc.end.offset,
135
+ content: template.content,
136
+ }
137
+ const templateMagicString = new MagicString(tempOffsets.content)
138
+ templateMagicString.append(pageContainerComp)
139
+ codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString())
140
+
141
+ // script标签中插入声明的变量和方法
142
+ const scriptOffsets = {
143
+ start: scriptSetup.loc.start.offset,
144
+ end: scriptSetup.loc.end.offset,
145
+ content: scriptSetup.content || '',
146
+ }
147
+ const scriptMagicString = new MagicString(scriptOffsets.content)
148
+ scriptMagicString.prepend(
149
+ ` ${importRefFromVue}
150
+ ${stateFrequency}
151
+ ${statePageContainerVar}
152
+ ${stateBeforeLeave} `
153
+ )
154
+ codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString())
155
+
156
+ return codeMs.toString()
157
+ }
158
+
159
+ function optionsWalk(context: pageContext, code: string, sfc: any, id: string) {
160
+ const codeMs = new MagicString(code)
161
+ const ast = babelParse(sfc.script.loc.source, sfc.script.lang)
162
+
163
+ let pageInfo = {
164
+ hasPageBack: false,
165
+ pageBackFnName: 'onPageBack',
166
+ backConfig: { ...context.config },
167
+ }
168
+
169
+ let exportDefaultNode: ObjectExpression | null = null
170
+ let dataMethodNode: BlockStatement | null = null
171
+ let methodsNode: ObjectExpression | null = null
172
+ let onPageBackNodeMethod: ObjectMethod | null = null
173
+ let onPageBackNodeProperty: ObjectProperty | null = null
174
+
175
+ if (ast) {
176
+ walkAST<Node>(ast, {
177
+ enter(node) {
178
+ // todo需要判断使用默认选项式还是使用了setup
179
+ if (
180
+ node.type === 'ExportDefaultDeclaration' &&
181
+ node.declaration.type === 'ObjectExpression'
182
+ ) {
183
+ exportDefaultNode = node.declaration
184
+ const properties = node.declaration.properties
185
+
186
+ for (let i = 0; i < properties.length; i++) {
187
+ const element = properties[i]
188
+ // export default 的 data()
189
+ if (
190
+ element.type === 'ObjectMethod' &&
191
+ element.key.type === 'Identifier' &&
192
+ element.key.name === 'data' &&
193
+ element.body.type === 'BlockStatement'
194
+ ) {
195
+ dataMethodNode = element.body
196
+ }
197
+ // export default 的 methods
198
+ if (
199
+ element.type === 'ObjectProperty' &&
200
+ element.key.type === 'Identifier' &&
201
+ element.key.name === 'methods'
202
+ ) {
203
+ methodsNode = element.value as ObjectExpression
204
+ }
205
+
206
+ // 获取export default 的 onPackBack
207
+ const blockStatementCondition =
208
+ element.type === 'ObjectMethod' &&
209
+ element.key.type === 'Identifier' &&
210
+ element.key.name === pageInfo.pageBackFnName &&
211
+ element.body.type === 'BlockStatement'
212
+
213
+ const functionExpressionCondition =
214
+ element.type === 'ObjectProperty' &&
215
+ element.key.type === 'Identifier' &&
216
+ element.key.name === pageInfo.pageBackFnName &&
217
+ element.value.type === 'FunctionExpression'
218
+
219
+ if (blockStatementCondition) {
220
+ pageInfo.hasPageBack = true
221
+ onPageBackNodeMethod = element
222
+ }
223
+
224
+ if (functionExpressionCondition) {
225
+ pageInfo.hasPageBack = true
226
+ onPageBackNodeProperty = element
227
+ }
228
+ }
229
+ }
230
+ },
231
+ })
232
+ }
233
+
234
+ if (!pageInfo.hasPageBack) return
235
+
236
+ const newDataProperty = [
237
+ {
238
+ type: 'ObjectProperty',
239
+ key: { type: 'Identifier', name: '__MP_BACK_SHOW_PAGE_CONTAINER__' },
240
+ value: { type: 'BooleanLiteral', value: true },
241
+ computed: false,
242
+ shorthand: false,
243
+ },
244
+ {
245
+ type: 'ObjectProperty',
246
+ key: { type: 'Identifier', name: '__MP_BACK_FREQUENCY__' },
247
+ value: { type: 'NumericLiteral', value: 1 },
248
+ computed: false,
249
+ shorthand: false,
250
+ },
251
+ ] as ObjectProperty[]
252
+ if (dataMethodNode) {
253
+ const returnStatement = (dataMethodNode as BlockStatement).body.find(
254
+ (node) => node.type === 'ReturnStatement'
255
+ )
256
+ if (
257
+ returnStatement &&
258
+ returnStatement.argument &&
259
+ returnStatement.argument.type === 'ObjectExpression'
260
+ ) {
261
+ // 添加新的属性
262
+ returnStatement.argument.properties.push(...newDataProperty)
263
+ }
264
+ } else if (exportDefaultNode) {
265
+ const addData: ObjectMethod = {
266
+ type: 'ObjectMethod',
267
+ key: { type: 'Identifier', name: 'data' },
268
+ kind: 'method',
269
+ params: [],
270
+ async: false,
271
+ generator: false,
272
+ computed: false,
273
+ body: {
274
+ type: 'BlockStatement',
275
+ directives: [],
276
+ body: [
277
+ {
278
+ type: 'ReturnStatement',
279
+ argument: {
280
+ type: 'ObjectExpression',
281
+ properties: newDataProperty,
282
+ },
283
+ },
284
+ ],
285
+ },
286
+ }
287
+ ; (exportDefaultNode as ObjectExpression).properties.push(addData)
288
+ }
289
+
290
+ // 获取传入插件的统一方法
291
+ const configBack = (() => {
292
+ const onPageBack = pageInfo.backConfig.onPageBack
293
+ if (!onPageBack) return ''
294
+ if (typeof onPageBack !== 'function') {
295
+ throw new Error('`onPageBack` must be a function')
296
+ }
297
+ const params = JSON.stringify({ page: context.getPageById(id) })
298
+ if (isArrowFunction(onPageBack) || onPageBack.toString().includes('function')) {
299
+ return `(${onPageBack})(${params});`
300
+ }
301
+ return `(function ${onPageBack})()`
302
+ })()
303
+
304
+ const stateBeforeLeave = `
305
+ function onBeforeLeave() {
306
+ if (this.__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
307
+ this.__MP_BACK_SHOW_PAGE_CONTAINER__ = false
308
+ setTimeout(() => { this.__MP_BACK_SHOW_PAGE_CONTAINER__ = true }, 0);
309
+ this.__MP_BACK_FREQUENCY__++
310
+ }
311
+ ${configBack}
312
+ ${!pageInfo.backConfig.preventDefault ? 'uni.navigateBack({ delta: 1 });' : ''}
313
+ };
314
+ `
315
+ const stateBeforeLeaveAst = babelParse(stateBeforeLeave)
316
+ const stateBeforeLeaveNode = stateBeforeLeaveAst.body.find(
317
+ (node) => node.type === 'FunctionDeclaration'
318
+ )
319
+ const newMethodsProperty = {
320
+ type: 'ObjectMethod',
321
+ key: {
322
+ type: 'Identifier',
323
+ name: 'onBeforeLeave',
324
+ },
325
+ kind: 'method',
326
+ generator: false,
327
+ async: false,
328
+ params: [],
329
+ computed: false,
330
+ body: {
331
+ type: 'BlockStatement',
332
+ directives: [],
333
+ body: [
334
+ ...(onPageBackNodeMethod ? (onPageBackNodeMethod as ObjectMethod)!.body.body : []),
335
+ ...(onPageBackNodeProperty
336
+ ? ((onPageBackNodeProperty as ObjectProperty)!.value as FunctionExpression).body.body
337
+ : []),
338
+ ...stateBeforeLeaveNode!.body.body,
339
+ ],
340
+ },
341
+ } as ObjectMethod
342
+ if (methodsNode) {
343
+ ; (methodsNode as ObjectExpression).properties.push(newMethodsProperty)
344
+ } else if (exportDefaultNode) {
345
+ const addMethods: ObjectProperty = {
346
+ type: 'ObjectProperty',
347
+ computed: false,
348
+ shorthand: false,
349
+ key: {
350
+ type: 'Identifier',
351
+ name: 'methods',
352
+ },
353
+ value: {
354
+ type: 'ObjectExpression',
355
+ properties: [newMethodsProperty],
356
+ },
357
+ }
358
+ ; (exportDefaultNode as ObjectExpression).properties.push(addMethods)
359
+ }
360
+
361
+ const { template, script } = sfc
362
+
363
+ // template标签中插入page-container组件
364
+ const tempOffsets = {
365
+ start: template.loc.start.offset,
366
+ end: template.loc.end.offset,
367
+ content: template.content,
368
+ }
369
+ const templateMagicString = new MagicString(tempOffsets.content)
370
+ templateMagicString.append(pageContainerComp)
371
+ codeMs.overwrite(tempOffsets.start, tempOffsets.end, templateMagicString.toString())
372
+
373
+ // script标签中插入声明的变量和方法
374
+ const scriptOffsets = {
375
+ start: script.loc.start.offset,
376
+ end: script.loc.end.offset,
377
+ }
378
+
379
+ // @ts-ignore
380
+ const newScriptContent = (generate.default ? generate.default : generate)(ast).code
381
+ codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, newScriptContent)
382
+
383
+ return codeMs.toString()
384
+ }
385
+
386
+ export const vueWalker = {
387
+ compositionWalk,
388
+ optionsWalk,
389
+ }