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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mp-weixin-back",
3
3
  "type": "module",
4
- "version": "0.0.16",
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": {
@@ -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)
@@ -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
@@ -1,8 +0,0 @@
1
- // .prettierrc.js 文件
2
- module.exports = {
3
- printWidth: 100,
4
- tabWidth: 2,
5
- useTabs: false,
6
- semi: false,
7
- singleQuote: true,
8
- };
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
@@ -1,6 +0,0 @@
1
- // shims-vue.d.ts
2
- declare module '*.vue' {
3
- import { DefineComponent } from 'vue'
4
- const component: DefineComponent<{}, {}, any>
5
- export default component
6
- }
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,17 +0,0 @@
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>
@@ -1,14 +0,0 @@
1
- <template>
2
- <div>我是默认的界面</div>
3
- </template>
4
-
5
- <script setup>
6
- import onPageBack from 'mp-weixin-back-helper'
7
-
8
- onPageBack(() => {
9
- console.log('触发了手势返回')
10
- return true
11
- })
12
- </script>
13
-
14
- <style></style>
@@ -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>
@@ -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
@@ -1,2 +0,0 @@
1
- export const virtualFileId = 'mp-weixin-back-helper'
2
-
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
- })