mp-weixin-back 0.0.16 → 0.0.17
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/package.json +6 -1
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.editorconfig +0 -25
- package/.github/workflows/publish.yml +0 -38
- package/.prettierrc.cjs +0 -8
- package/build.config.ts +0 -12
- package/llms.txt +0 -154
- package/shims-vue.d.ts +0 -6
- package/src/context.ts +0 -78
- package/src/index.ts +0 -79
- package/test/data/index-default.vue +0 -17
- package/test/data/index-setup.vue +0 -14
- package/test/data/index-utils.vue +0 -29
- package/test/generate.spec.ts +0 -64
- package/tsconfig.json +0 -15
- package/types/index.ts +0 -80
- package/utils/constant.ts +0 -2
- package/utils/index.ts +0 -62
- package/utils/walker.ts +0 -523
- package/vite.config.ts +0 -19
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mp-weixin-back",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.17",
|
|
5
5
|
"description": "Vite plugin to intercept back navigation (gesture back & navbar back) in WeChat miniprogram (mp-weixin) built with uni-app + Vue 3. 监听微信小程序的手势返回和页面默认导航栏的返回",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"types": "./client.d.ts"
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"client.d.ts",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
41
46
|
"author": "DBAAZzz",
|
|
42
47
|
"license": "ISC",
|
|
43
48
|
"repository": {
|
package/.changeset/README.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Changesets
|
|
2
|
-
|
|
3
|
-
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
4
|
-
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
5
|
-
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
6
|
-
|
|
7
|
-
We have a quick list of common questions to get you started engaging with this project in
|
|
8
|
-
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
package/.changeset/config.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
|
|
3
|
-
"changelog": "@changesets/cli/changelog",
|
|
4
|
-
"commit": false,
|
|
5
|
-
"fixed": [],
|
|
6
|
-
"linked": [],
|
|
7
|
-
"access": "public",
|
|
8
|
-
"baseBranch": "main",
|
|
9
|
-
"updateInternalDependencies": "patch",
|
|
10
|
-
"ignore": []
|
|
11
|
-
}
|
package/.editorconfig
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
root = true
|
|
3
|
-
|
|
4
|
-
[*]
|
|
5
|
-
indent_style = space
|
|
6
|
-
# 缩进大小=2
|
|
7
|
-
indent_size = 2
|
|
8
|
-
|
|
9
|
-
end_of_line = lf
|
|
10
|
-
# 字符集=utf-8
|
|
11
|
-
charset = utf-8
|
|
12
|
-
# 删除行尾空格 = 是
|
|
13
|
-
trim_trailing_whitespace = true
|
|
14
|
-
# 插入最后一行=真
|
|
15
|
-
insert_final_newline = true
|
|
16
|
-
|
|
17
|
-
[*.md]
|
|
18
|
-
# 删除行尾空格 = 否
|
|
19
|
-
trim_trailing_whitespace = false
|
|
20
|
-
|
|
21
|
-
[package.json]
|
|
22
|
-
# 缩进样式=空格
|
|
23
|
-
indent_style = space
|
|
24
|
-
# 缩进大小=2
|
|
25
|
-
indent_size = 2
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
publish:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
contents: write
|
|
15
|
-
pull-requests: write
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- uses: pnpm/action-setup@v4
|
|
20
|
-
with:
|
|
21
|
-
version: latest
|
|
22
|
-
|
|
23
|
-
- uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: 20
|
|
26
|
-
registry-url: https://registry.npmjs.org
|
|
27
|
-
cache: pnpm
|
|
28
|
-
|
|
29
|
-
- name: Install dependencies
|
|
30
|
-
run: pnpm install --no-frozen-lockfile
|
|
31
|
-
|
|
32
|
-
- name: Create Release Pull Request or Publish
|
|
33
|
-
uses: changesets/action@v1
|
|
34
|
-
with:
|
|
35
|
-
publish: pnpm release
|
|
36
|
-
env:
|
|
37
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.prettierrc.cjs
DELETED
package/build.config.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineBuildConfig } from 'unbuild'
|
|
2
|
-
|
|
3
|
-
export default defineBuildConfig({
|
|
4
|
-
entries: ['src/index'],
|
|
5
|
-
declaration: true,
|
|
6
|
-
clean: true,
|
|
7
|
-
rollup: {
|
|
8
|
-
emitCJS: true,
|
|
9
|
-
},
|
|
10
|
-
failOnWarn: false,
|
|
11
|
-
externals: ['vite', 'vue', '@babel/generator'],
|
|
12
|
-
})
|
package/llms.txt
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
# mp-weixin-back
|
|
2
|
-
|
|
3
|
-
> Vite plugin for intercepting back navigation (gesture back + navbar back button) in WeChat miniprogram (mp-weixin) built with uni-app + Vue 3.
|
|
4
|
-
|
|
5
|
-
## When to use
|
|
6
|
-
|
|
7
|
-
Use this library when you need to:
|
|
8
|
-
- Intercept or customize the swipe-back gesture in a WeChat miniprogram page
|
|
9
|
-
- Intercept or customize the navbar back button behavior
|
|
10
|
-
- Show a confirmation dialog before the user leaves a page
|
|
11
|
-
- Prevent accidental back navigation (e.g., on a form page)
|
|
12
|
-
|
|
13
|
-
## Install
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install mp-weixin-back
|
|
17
|
-
# or
|
|
18
|
-
pnpm add mp-weixin-back
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Setup (vite.config.ts)
|
|
22
|
-
|
|
23
|
-
Register the Vite plugin once, globally:
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { defineConfig } from 'vite'
|
|
27
|
-
import mpBackPlugin from 'mp-weixin-back'
|
|
28
|
-
|
|
29
|
-
export default defineConfig({
|
|
30
|
-
plugins: [
|
|
31
|
-
mpBackPlugin({
|
|
32
|
-
preventDefault: false, // true = block all back navigation by default
|
|
33
|
-
frequency: 1, // how many times to intercept before allowing through
|
|
34
|
-
debug: false, // enable debug logging in development
|
|
35
|
-
onPageBack: ({ page }) => {
|
|
36
|
-
console.log('back triggered on page:', page)
|
|
37
|
-
},
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Usage in Vue SFC (Composition API)
|
|
44
|
-
|
|
45
|
-
Import from the virtual module `mp-weixin-back-helper`:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
// Basic usage — listen to back event
|
|
49
|
-
import onPageBack from 'mp-weixin-back-helper'
|
|
50
|
-
|
|
51
|
-
onPageBack(() => {
|
|
52
|
-
console.log('back navigation detected')
|
|
53
|
-
})
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
// Advanced — prevent back and show dialog
|
|
58
|
-
import onPageBack from 'mp-weixin-back-helper'
|
|
59
|
-
|
|
60
|
-
onPageBack(
|
|
61
|
-
() => {
|
|
62
|
-
showConfirmDialog() // your logic here
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
preventDefault: true, // block default back behavior
|
|
66
|
-
frequency: 2, // intercept 2 times
|
|
67
|
-
initialValue: true, // start listening immediately (default)
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
// Manually toggle the listener
|
|
74
|
-
import onPageBack, { activeMpBack, inactiveMpBack } from 'mp-weixin-back-helper'
|
|
75
|
-
|
|
76
|
-
onPageBack(() => { /* ... */ }, { initialValue: false }) // start disabled
|
|
77
|
-
|
|
78
|
-
activeMpBack() // enable listener (call inside <script setup>)
|
|
79
|
-
inactiveMpBack() // disable listener (call inside <script setup>)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## TypeScript support
|
|
83
|
-
|
|
84
|
-
Add to `tsconfig.json`:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"compilerOptions": {
|
|
89
|
-
"types": ["mp-weixin-back/client"]
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Or in `env.d.ts`:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
/// <reference types="mp-weixin-back/client" />
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## API Reference
|
|
101
|
-
|
|
102
|
-
### Plugin options (`mpBackPlugin(options)`)
|
|
103
|
-
|
|
104
|
-
| Option | Type | Default | Description |
|
|
105
|
-
| --------------- | --------------------------------- | ------- | -------------------------------------------------------- |
|
|
106
|
-
| `preventDefault`| `boolean` | `false` | Block default back behavior globally |
|
|
107
|
-
| `frequency` | `number` | `1` | Number of times to intercept before allowing through |
|
|
108
|
-
| `debug` | `boolean` | `false` | Log debug info in development mode |
|
|
109
|
-
| `onPageBack` | `(params: { page: string }) => void` | — | Global callback fired on every back event |
|
|
110
|
-
|
|
111
|
-
### `onPageBack(callback, options?)`
|
|
112
|
-
|
|
113
|
-
Listen to back events on the current page. Must be called in `<script setup>`.
|
|
114
|
-
|
|
115
|
-
| Param | Type | Required | Description |
|
|
116
|
-
| ---------- | --------------------- | -------- | ------------------------------- |
|
|
117
|
-
| `callback` | `() => void` | Yes | Function called when back fires |
|
|
118
|
-
| `options` | `OnPageBackOptions` | No | Per-page overrides |
|
|
119
|
-
|
|
120
|
-
#### `OnPageBackOptions`
|
|
121
|
-
|
|
122
|
-
| Option | Type | Default | Description |
|
|
123
|
-
| --------------- | --------- | ------- | ------------------------------------------------------------ |
|
|
124
|
-
| `preventDefault`| `boolean` | `false` | Block default back for this page |
|
|
125
|
-
| `frequency` | `number` | `1` | Intercept count for this page |
|
|
126
|
-
| `initialValue` | `boolean` | `true` | Start listening immediately; set `false` to enable manually |
|
|
127
|
-
|
|
128
|
-
### `activeMpBack()`
|
|
129
|
-
|
|
130
|
-
Enable the back listener. Call inside `<script setup>` when `initialValue: false`.
|
|
131
|
-
|
|
132
|
-
### `inactiveMpBack()`
|
|
133
|
-
|
|
134
|
-
Disable the back listener. Call inside `<script setup>`.
|
|
135
|
-
|
|
136
|
-
## How it works
|
|
137
|
-
|
|
138
|
-
This is a **Vite transform plugin**. At build time it:
|
|
139
|
-
1. Reads `src/pages.json` to identify miniprogram pages
|
|
140
|
-
2. For each `.vue` page file, injects a `<page-container>` component into the template (the WeChat API used to intercept back gestures)
|
|
141
|
-
3. Wires up the `onPageBack` composable to control `show` state of `<page-container>`
|
|
142
|
-
|
|
143
|
-
The virtual module `mp-weixin-back-helper` is resolved by the plugin and provides the runtime composable API.
|
|
144
|
-
|
|
145
|
-
## Constraints
|
|
146
|
-
|
|
147
|
-
- Requires **uni-app** project with `src/pages.json`
|
|
148
|
-
- Requires **Vue 3** + `<script setup>` (Composition API)
|
|
149
|
-
- Works with **Vite 3–6**
|
|
150
|
-
- Only applies to page-level `.vue` files, not components
|
|
151
|
-
|
|
152
|
-
## Repository
|
|
153
|
-
|
|
154
|
-
https://github.com/DBAAZzz/mp-weixin-back
|
package/shims-vue.d.ts
DELETED
package/src/context.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import JSON5 from 'json5'
|
|
4
|
-
import { red, white, green } from 'kolorist'
|
|
5
|
-
import { ContextConfig, PagesJson } from '../types'
|
|
6
|
-
import { transformVueFile } from '../utils'
|
|
7
|
-
|
|
8
|
-
export class pageContext {
|
|
9
|
-
private logPreText = '[mp-weixin-back] : '
|
|
10
|
-
config: ContextConfig
|
|
11
|
-
pages: string[] = []
|
|
12
|
-
log = {
|
|
13
|
-
info: (text: string) => {
|
|
14
|
-
console.log(white(this.logPreText + text))
|
|
15
|
-
},
|
|
16
|
-
error: (text: string) => {
|
|
17
|
-
console.log(red(this.logPreText + text))
|
|
18
|
-
},
|
|
19
|
-
debugLog: (text: string) => {
|
|
20
|
-
if (this.config.mode === 'development' && this.config.debug) {
|
|
21
|
-
console.log(green(this.logPreText + text))
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
constructor(config: ContextConfig) {
|
|
27
|
-
this.config = config
|
|
28
|
-
}
|
|
29
|
-
getPagesJsonPath() {
|
|
30
|
-
const pagesJsonPath = path.join(this.config.root, 'src/pages.json')
|
|
31
|
-
return pagesJsonPath
|
|
32
|
-
}
|
|
33
|
-
// 获取页面配置详情
|
|
34
|
-
async getPagesJsonInfo() {
|
|
35
|
-
const hasPagesJson = fs.existsSync(this.getPagesJsonPath())
|
|
36
|
-
if (!hasPagesJson) return
|
|
37
|
-
try {
|
|
38
|
-
const content = await fs.promises.readFile(this.getPagesJsonPath(), 'utf-8')
|
|
39
|
-
const pagesContent = JSON5.parse(content) as PagesJson
|
|
40
|
-
const { pages, subpackages } = pagesContent
|
|
41
|
-
if (pages) {
|
|
42
|
-
const mainPages = pages.reduce((acc: string[], current) => {
|
|
43
|
-
acc.push(current.path)
|
|
44
|
-
return acc
|
|
45
|
-
}, [])
|
|
46
|
-
this.pages.push(...mainPages)
|
|
47
|
-
}
|
|
48
|
-
if (subpackages) {
|
|
49
|
-
for (let i = 0; i < subpackages.length; i++) {
|
|
50
|
-
const element = subpackages[i]
|
|
51
|
-
const root = element.root
|
|
52
|
-
const subPages = element.pages.reduce((acc: string[], current) => {
|
|
53
|
-
acc.push(`${root}/${current.path}`.replace('//', '/'))
|
|
54
|
-
return acc
|
|
55
|
-
}, [])
|
|
56
|
-
this.pages.push(...subPages)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
} catch (error: unknown) {
|
|
60
|
-
this.log.error(
|
|
61
|
-
`Failed to read pages.json. Make sure src/pages.json exists and is valid JSON/JSON5.\n` +
|
|
62
|
-
` Path checked: ${this.getPagesJsonPath()}\n` +
|
|
63
|
-
` Docs: https://github.com/DBAAZzz/mp-weixin-back#%EF%B8%8F-vite-配置`
|
|
64
|
-
)
|
|
65
|
-
this.log.debugLog(String(error))
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// 获取指定id的page
|
|
69
|
-
getPageById(id: string) {
|
|
70
|
-
const path = (id.split('src/')[1] || '').replace('.vue', '')
|
|
71
|
-
// 页面级别
|
|
72
|
-
return this.pages.find((i) => i === path) || null
|
|
73
|
-
}
|
|
74
|
-
async transform(code: string, id: string) {
|
|
75
|
-
const result = await transformVueFile.call(this, code, id)
|
|
76
|
-
return result
|
|
77
|
-
}
|
|
78
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { pageContext } from './context'
|
|
2
|
-
import { virtualFileId } from '../utils/constant'
|
|
3
|
-
import type { Plugin } from 'vite'
|
|
4
|
-
import type { Config, UserOptions } from '../types'
|
|
5
|
-
|
|
6
|
-
function MpBackPlugin(userOptions: UserOptions = {}): Plugin {
|
|
7
|
-
let context: pageContext
|
|
8
|
-
|
|
9
|
-
const defaultOptions: Config = {
|
|
10
|
-
initialValue: true,
|
|
11
|
-
preventDefault: false,
|
|
12
|
-
frequency: 1,
|
|
13
|
-
debug: false,
|
|
14
|
-
}
|
|
15
|
-
const options = { ...defaultOptions, ...userOptions }
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
name: 'vite-plugin-mp-weixin-back',
|
|
19
|
-
enforce: 'pre',
|
|
20
|
-
configResolved(config) {
|
|
21
|
-
context = new pageContext({ ...options, mode: config.mode, root: config.root })
|
|
22
|
-
},
|
|
23
|
-
buildStart() {
|
|
24
|
-
context.getPagesJsonInfo()
|
|
25
|
-
},
|
|
26
|
-
resolveId(id) {
|
|
27
|
-
if (id === virtualFileId) {
|
|
28
|
-
return virtualFileId
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
load(id) {
|
|
32
|
-
if (id.includes('node_modules')) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
// 导出一个对象
|
|
36
|
-
if (id === virtualFileId) {
|
|
37
|
-
return `
|
|
38
|
-
import { ref } from 'vue'
|
|
39
|
-
export default function onPageBack() {}
|
|
40
|
-
export function activeMpBack(fn = null) {
|
|
41
|
-
fn?.()
|
|
42
|
-
}
|
|
43
|
-
export function inactiveMpBack(fn = null) {
|
|
44
|
-
fn?.()
|
|
45
|
-
}
|
|
46
|
-
export function useMpWeixinBack(initialValue = true) {
|
|
47
|
-
const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
|
|
48
|
-
|
|
49
|
-
const __MP_WEIXIN_ACTIVEBACK__ = () => {
|
|
50
|
-
__MP_BACK_SHOW_PAGE_CONTAINER__.value = true
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const __MP_WEIXIN_INACTIVEBACK__ = () => {
|
|
54
|
-
__MP_BACK_SHOW_PAGE_CONTAINER__.value = false
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
__MP_BACK_SHOW_PAGE_CONTAINER__,
|
|
59
|
-
__MP_WEIXIN_ACTIVEBACK__,
|
|
60
|
-
__MP_WEIXIN_INACTIVEBACK__
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
`
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
async transform(code, id) {
|
|
67
|
-
if (id.includes('node_modules') || !id.includes('.vue')) {
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
code: await context.transform(code, id),
|
|
73
|
-
map: null,
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
} as Plugin
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export default MpBackPlugin
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>我是默认的界面</div>
|
|
3
|
-
<button id="button" @click="disableMpBack"></button>
|
|
4
|
-
<button id="button2" @click="activeMpBack"></button>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup>
|
|
8
|
-
import onPageBack, { activeMpBack as mpppacitve, inactiveMpBack } from 'mp-weixin-back-helper'
|
|
9
|
-
|
|
10
|
-
onPageBack(
|
|
11
|
-
() => {
|
|
12
|
-
console.log('触发了手势返回')
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
initialValue: false,
|
|
16
|
-
}
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
const activeMpBack = () => {
|
|
20
|
-
console.log('执行了activeMpBack')
|
|
21
|
-
mpppacitve()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const disableMpBack = () => {
|
|
25
|
-
inactiveMpBack()
|
|
26
|
-
}
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<style></style>
|
package/test/generate.spec.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import IndexSetup from './data/index-setup.vue'
|
|
4
|
-
import IndexUtils from './data/index-utils.vue'
|
|
5
|
-
import IndexDefault from './data/index-default.vue'
|
|
6
|
-
|
|
7
|
-
describe('generate page-container components', () => {
|
|
8
|
-
let logSpy: ReturnType<typeof vi.spyOn>
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
logSpy.mockRestore()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('setup compositionAPI', () => {
|
|
19
|
-
it('default case', async () => {
|
|
20
|
-
const wrapper = mount(IndexSetup)
|
|
21
|
-
await wrapper.vm.$nextTick()
|
|
22
|
-
|
|
23
|
-
const pageContainerRef = wrapper.find('page-container')
|
|
24
|
-
expect(pageContainerRef.exists()).toBe(true)
|
|
25
|
-
expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
|
|
26
|
-
expect(typeof wrapper.vm.onBeforeLeave).toBe('function')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('utils case', async () => {
|
|
30
|
-
const wrapper = mount(IndexUtils)
|
|
31
|
-
await wrapper.vm.$nextTick()
|
|
32
|
-
|
|
33
|
-
const pageContainerRef = wrapper.find('page-container')
|
|
34
|
-
expect(pageContainerRef.exists()).toBe(true)
|
|
35
|
-
expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(false)
|
|
36
|
-
expect(typeof wrapper.vm.onBeforeLeave).toBe('function')
|
|
37
|
-
|
|
38
|
-
await wrapper.find('#button2').trigger('click')
|
|
39
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
40
|
-
|
|
41
|
-
expect(logSpy).toHaveBeenCalledWith('执行了activeMpBack')
|
|
42
|
-
|
|
43
|
-
expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
|
|
44
|
-
|
|
45
|
-
await wrapper.find('#button').trigger('click')
|
|
46
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
47
|
-
|
|
48
|
-
expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(false)
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe('optionsAPI', () => {
|
|
53
|
-
it('default case', async () => {
|
|
54
|
-
const wrapper = mount(IndexDefault)
|
|
55
|
-
await wrapper.vm.$nextTick()
|
|
56
|
-
|
|
57
|
-
const pageContainerRef = wrapper.find('page-container')
|
|
58
|
-
expect(pageContainerRef.exists()).toBe(true)
|
|
59
|
-
expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
|
|
60
|
-
expect(wrapper.vm.__MP_BACK_FREQUENCY__).toBe(1)
|
|
61
|
-
expect(wrapper.vm.onBeforeLeave()).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
})
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es2018",
|
|
4
|
-
"lib": ["esnext"],
|
|
5
|
-
"types": ["node"],
|
|
6
|
-
"module": "esnext",
|
|
7
|
-
"moduleResolution": "node",
|
|
8
|
-
"resolveJsonModule": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"strictNullChecks": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"skipDefaultLibCheck": true,
|
|
13
|
-
"skipLibCheck": true
|
|
14
|
-
}
|
|
15
|
-
}
|
package/types/index.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parameters passed to the onPageBack callback.
|
|
3
|
-
*/
|
|
4
|
-
export type BackParams = {
|
|
5
|
-
/**
|
|
6
|
-
* The path of the current page that triggered the back event.
|
|
7
|
-
* @example 'pages/index/index'
|
|
8
|
-
*/
|
|
9
|
-
page: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Per-page options for `onPageBack()`.
|
|
14
|
-
*/
|
|
15
|
-
export type OnPageBackOptions = {
|
|
16
|
-
/**
|
|
17
|
-
* Whether to block the default back navigation for this page.
|
|
18
|
-
* When `true`, the user stays on the current page and only the callback fires.
|
|
19
|
-
* @default false
|
|
20
|
-
* @example
|
|
21
|
-
* onPageBack(() => showDialog(), { preventDefault: true })
|
|
22
|
-
*/
|
|
23
|
-
preventDefault: boolean
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* How many times to intercept the back event before allowing through.
|
|
27
|
-
* Set to a large number (e.g. `9999`) for persistent interception.
|
|
28
|
-
* @default 1
|
|
29
|
-
* @example
|
|
30
|
-
* // Block 3 times, then allow back
|
|
31
|
-
* onPageBack(() => {}, { frequency: 3 })
|
|
32
|
-
*/
|
|
33
|
-
frequency: number
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Whether to start listening immediately when the page mounts.
|
|
37
|
-
* Set to `false` to start disabled and enable manually via `activeMpBack()`.
|
|
38
|
-
* @default true
|
|
39
|
-
* @example
|
|
40
|
-
* // Start disabled, enable after user action
|
|
41
|
-
* onPageBack(() => {}, { initialValue: false })
|
|
42
|
-
* activeMpBack()
|
|
43
|
-
*/
|
|
44
|
-
initialValue: boolean
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Global plugin options passed to `mpBackPlugin()` in `vite.config.ts`.
|
|
49
|
-
*/
|
|
50
|
-
export type Config = OnPageBackOptions & {
|
|
51
|
-
/**
|
|
52
|
-
* Enable debug logging in development mode.
|
|
53
|
-
* @default false
|
|
54
|
-
*/
|
|
55
|
-
debug: boolean
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Global callback fired every time a back event is detected on any page.
|
|
59
|
-
* Page-level callbacks registered via `onPageBack()` run in addition to this.
|
|
60
|
-
* @example
|
|
61
|
-
* mpBackPlugin({
|
|
62
|
-
* onPageBack: ({ page }) => console.log('back on:', page)
|
|
63
|
-
* })
|
|
64
|
-
*/
|
|
65
|
-
onPageBack?: (params: BackParams) => void
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type UserOptions = Partial<Config>
|
|
69
|
-
|
|
70
|
-
export type ContextConfig = Config & {
|
|
71
|
-
mode: string
|
|
72
|
-
root: string
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
type Pages = { path: string }[]
|
|
76
|
-
|
|
77
|
-
export type PagesJson = {
|
|
78
|
-
pages: Pages
|
|
79
|
-
subpackages: { root: string; pages: Pages }[]
|
|
80
|
-
}
|
package/utils/constant.ts
DELETED
package/utils/index.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module'
|
|
2
|
-
import { pageContext } from '../src/context'
|
|
3
|
-
import { vueWalker } from './walker'
|
|
4
|
-
|
|
5
|
-
let compilerPromise: Promise<typeof import('@vue/compiler-sfc')> | null = null
|
|
6
|
-
|
|
7
|
-
async function resolveCompiler(root: string): Promise<typeof import('@vue/compiler-sfc')> {
|
|
8
|
-
// 避免重复解析(防止并发调用时的竞态条件)
|
|
9
|
-
if (compilerPromise) {
|
|
10
|
-
return compilerPromise
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
compilerPromise = (async () => {
|
|
14
|
-
// 尝试加载用户项目中的 @vue/compiler-sfc
|
|
15
|
-
try {
|
|
16
|
-
const _require = createRequire(import.meta.url)
|
|
17
|
-
// 尝试从用户根目录解析
|
|
18
|
-
const compilerPath = _require.resolve('@vue/compiler-sfc', { paths: [root] })
|
|
19
|
-
return _require(compilerPath) as typeof import('@vue/compiler-sfc')
|
|
20
|
-
} catch (firstError) {
|
|
21
|
-
try {
|
|
22
|
-
// 降级尝试直接 import
|
|
23
|
-
return await import('@vue/compiler-sfc')
|
|
24
|
-
} catch (secondError) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`[mp-weixin-back] Cannot resolve @vue/compiler-sfc.\n` +
|
|
27
|
-
`This plugin requires @vue/compiler-sfc to be installed in your project.\n` +
|
|
28
|
-
`Fix: pnpm add -D @vue/compiler-sfc\n` +
|
|
29
|
-
`Docs: https://github.com/DBAAZzz/mp-weixin-back#%EF%B8%8F-vite-配置\n`
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
})()
|
|
34
|
-
|
|
35
|
-
return compilerPromise
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function transformVueFile(this: pageContext, code: string, id: string) {
|
|
39
|
-
try {
|
|
40
|
-
const sfcCompiler = await resolveCompiler(this.config.root)
|
|
41
|
-
const sfc = sfcCompiler.parse(code).descriptor
|
|
42
|
-
const { template, script, scriptSetup } = sfc
|
|
43
|
-
if (!template?.content) {
|
|
44
|
-
return code
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!script?.content && !scriptSetup?.content) {
|
|
48
|
-
return code
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 判断页面是否为组合式写法
|
|
52
|
-
const walker = scriptSetup ? 'compositionWalk' : 'optionsWalk'
|
|
53
|
-
return vueWalker[walker](this, code, sfc, id)
|
|
54
|
-
} catch (error) {
|
|
55
|
-
this.log.error(
|
|
56
|
-
`Failed to transform ${id}. Please check the file is a valid Vue SFC.\n` +
|
|
57
|
-
` Docs: https://github.com/DBAAZzz/mp-weixin-back#-快速开始`
|
|
58
|
-
)
|
|
59
|
-
this.log.error(String(error))
|
|
60
|
-
return code
|
|
61
|
-
}
|
|
62
|
-
}
|
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
|
-
})
|