├── .husky ├── .gitignore └── commit-msg ├── examples ├── react │ ├── src │ │ ├── app.css │ │ ├── pages │ │ │ ├── me │ │ │ │ ├── index.css │ │ │ │ ├── route.config.ts │ │ │ │ ├── index.config.ts │ │ │ │ └── index.tsx │ │ │ ├── login │ │ │ │ ├── index.css │ │ │ │ ├── index.config.ts │ │ │ │ └── index.tsx │ │ │ ├── class-demo │ │ │ │ ├── index.css │ │ │ │ ├── index.config.ts │ │ │ │ ├── route.config.ts │ │ │ │ └── index.tsx │ │ │ ├── page-data │ │ │ │ ├── index.css │ │ │ │ ├── index.config.ts │ │ │ │ ├── route.config.ts │ │ │ │ └── index.tsx │ │ │ ├── page-params │ │ │ │ ├── index.css │ │ │ │ ├── index.config.ts │ │ │ │ ├── route.config.ts │ │ │ │ └── index.tsx │ │ │ ├── page-data-params │ │ │ │ ├── index.css │ │ │ │ ├── index.config.ts │ │ │ │ ├── route.config.ts │ │ │ │ └── index.tsx │ │ │ ├── index │ │ │ │ ├── index.config.ts │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ └── sel-city │ │ │ │ ├── index.config.ts │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ ├── packageA │ │ │ └── pages │ │ │ │ ├── cat │ │ │ │ ├── index.config.ts │ │ │ │ ├── route.config.ts │ │ │ │ └── index.tsx │ │ │ │ └── dog │ │ │ │ ├── index.config.ts │ │ │ │ └── index.tsx │ │ ├── router │ │ │ ├── middleware │ │ │ │ ├── index.ts │ │ │ │ ├── fetch-info.ts │ │ │ │ └── auth-check.ts │ │ │ └── index.ts │ │ ├── store │ │ │ └── user.ts │ │ ├── utils │ │ │ └── index.ts │ │ ├── app.ts │ │ ├── app.config.ts │ │ └── index.html │ ├── .gitignore │ ├── .prettierrc │ ├── config │ │ ├── dev.js │ │ ├── prod.js │ │ └── index.js │ ├── .eslintrc │ ├── project.tt.json │ ├── .editorconfig │ ├── babel.config.js │ ├── project.config.json │ ├── global.d.ts │ ├── tsconfig.json │ └── package.json └── vue3 │ ├── src │ ├── app.css │ ├── app.scss │ ├── pages │ │ ├── me │ │ │ ├── index.scss │ │ │ ├── route.config.ts │ │ │ ├── index.config.ts │ │ │ └── index.vue │ │ ├── login │ │ │ ├── index.scss │ │ │ ├── index.config.ts │ │ │ └── index.vue │ │ ├── page-data-params │ │ │ ├── index.scss │ │ │ ├── index.config.ts │ │ │ ├── route.config.ts │ │ │ └── index.vue │ │ ├── index │ │ │ ├── index.config.ts │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── sel-city │ │ │ ├── index.config.ts │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── router │ │ ├── middleware │ │ │ ├── index.ts │ │ │ ├── fetch-info.ts │ │ │ └── auth-check.ts │ │ └── index.ts │ ├── store │ │ └── user.ts │ ├── utils │ │ └── index.ts │ ├── app.ts │ ├── app.config.ts │ └── index.html │ ├── .eslintrc │ ├── .gitignore │ ├── .prettierrc │ ├── config │ ├── dev.js │ ├── prod.js │ └── index.js │ ├── project.tt.json │ ├── .editorconfig │ ├── babel.config.js │ ├── project.config.json │ ├── global.d.ts │ ├── tsconfig.json │ └── package.json ├── .npmrc ├── packages ├── tarojs-router-next-plugin │ ├── .npmrc │ ├── .gitignore │ ├── index.js │ ├── src │ │ ├── index.ts │ │ ├── config.ts │ │ ├── entitys.ts │ │ ├── utils.ts │ │ ├── plugin.ts │ │ ├── generator.ts │ │ └── loader.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md └── tarojs-router-next │ ├── src │ ├── exception │ │ ├── index.ts │ │ └── no-page.ts │ ├── func │ │ ├── isNil.ts │ │ ├── index.ts │ │ ├── getCurrentRouteKey.ts │ │ └── formatPath.ts │ ├── constants.ts │ ├── router-back-listener │ │ ├── type.ts │ │ └── index.ts │ ├── index.ts │ ├── middleware │ │ ├── type.ts │ │ └── index.ts │ ├── router │ │ ├── type.ts │ │ └── index.ts │ ├── lib │ │ └── compose.ts │ └── page-data │ │ └── index.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── docs ├── guide │ ├── scenes │ │ ├── middleware-auth.md │ │ └── value-across-page.md │ ├── quike │ │ ├── router-back-listener.md │ │ ├── config.md │ │ ├── subpackage.md │ │ ├── sync-router.md │ │ ├── route-config.md │ │ ├── navigate.md │ │ ├── start.md │ │ ├── params.md │ │ ├── qa.md │ │ └── middleware.md │ └── index.md ├── api │ ├── register-middlewares.md │ ├── register-router-back-listener.md │ ├── other.md │ ├── register-middleware.md │ └── router.md └── index.md ├── pnpm-workspace.yaml ├── .commitlintrc.json ├── public ├── logo.png ├── favicon.ico └── images │ ├── koa.png │ ├── code1.png │ ├── code2.gif │ └── code3.png ├── .gitignore ├── .prettierrc ├── .dumi └── overrides.css ├── .eslintrc ├── tsconfig.json ├── .cz-config.js ├── LICENSE ├── .github └── workflows │ └── deploy-docs.yml ├── package.json ├── CHANGELOG.md ├── README.md └── .dumirc.ts /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /examples/react/src/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue3/src/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue3/src/app.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/pages/me/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/me/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/pages/login/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/login/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | git-checks=false -------------------------------------------------------------------------------- /examples/react/src/pages/class-demo/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-params/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data-params/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/page-data-params/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /docs/guide/scenes/middleware-auth.md: -------------------------------------------------------------------------------- 1 | # 路由权限中间件 2 | 3 | 暂未完善 4 | -------------------------------------------------------------------------------- /docs/guide/scenes/value-across-page.md: -------------------------------------------------------------------------------- 1 | # 跨页面取值 2 | 3 | 暂未完善 4 | -------------------------------------------------------------------------------- /examples/vue3/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["taro/vue3"] 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | - 'examples/**' -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } -------------------------------------------------------------------------------- /examples/react/src/pages/me/route.config.ts: -------------------------------------------------------------------------------- 1 | export const Ext = { mustLogin: true } 2 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/me/route.config.ts: -------------------------------------------------------------------------------- 1 | export const Ext = { mustLogin: true } 2 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/logo.png -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { NoPageException } from './no-page' 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /examples/react/src/pages/index/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '首页' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/login/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '登录' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/me/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '用户信息页' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/index/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '首页' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/login/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '登录' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/me/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '用户信息页' 3 | } 4 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .DS_Store 4 | dist 5 | yarn.lock -------------------------------------------------------------------------------- /public/images/koa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/images/koa.png -------------------------------------------------------------------------------- /examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | deploy_versions/ 3 | .temp/ 4 | .rn_temp/ 5 | node_modules/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /examples/react/src/pages/sel-city/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '选择城市' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | deploy_versions/ 3 | .temp/ 4 | .rn_temp/ 5 | node_modules/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/sel-city/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '选择城市' 3 | } 4 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .DS_Store 4 | dist 5 | yarn.lock -------------------------------------------------------------------------------- /public/images/code1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/images/code1.png -------------------------------------------------------------------------------- /public/images/code2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/images/code2.gif -------------------------------------------------------------------------------- /public/images/code3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/tarojs-router-next/HEAD/public/images/code3.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | yarn-error.log 4 | .umi 5 | .dumi/tmp 6 | .dumi/tmp-test 7 | .dumi/tmp-production -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /examples/react/src/pages/class-demo/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: 'Class示例' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '展示携带的数据' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/sel-city/index.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | padding: 32px; 3 | border-bottom: 1px solid #efefef; 4 | } -------------------------------------------------------------------------------- /examples/react/src/packageA/pages/cat/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '分包页面-cat' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/packageA/pages/dog/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '分包页面-dog' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-params/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '展示携带的数据' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-params/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Params = { 2 | id: number 3 | name?: string 4 | } 5 | -------------------------------------------------------------------------------- /examples/react/src/pages/sel-city/index.css: -------------------------------------------------------------------------------- 1 | .item { 2 | padding: 32px; 3 | border-bottom: 1px solid #efefef; 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/page-data-params/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '页面参数示例' 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue3/src/router/middleware/index.ts: -------------------------------------------------------------------------------- 1 | // 注意引入顺序,该顺序是中间件的执行顺序 2 | import './auth-check' 3 | import './fetch-info' 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data-params/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '展示携带的数据' 3 | } 4 | -------------------------------------------------------------------------------- /examples/react/src/router/middleware/index.ts: -------------------------------------------------------------------------------- 1 | // 注意引入顺序,该顺序是中间件的执行顺序 2 | import './auth-check' 3 | import './fetch-info' 4 | -------------------------------------------------------------------------------- /examples/react/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /examples/vue3/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/func/isNil.ts: -------------------------------------------------------------------------------- 1 | export function isNil(val: any) { 2 | return val === undefined || val === null 3 | } 4 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default; 2 | module.exports.default = module.exports; 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /examples/react/src/store/user.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Store { 4 | userinfo: { id: number, name: string } 5 | } 6 | 7 | export const UserStore = new Store() -------------------------------------------------------------------------------- /examples/vue3/src/store/user.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Store { 4 | userinfo: { id: number, name: string } 5 | } 6 | 7 | export const UserStore = new Store() -------------------------------------------------------------------------------- /examples/vue3/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms = 1000) { 2 | return new Promise((ok) => { 3 | setTimeout(ok, ms) 4 | }) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/func/index.ts: -------------------------------------------------------------------------------- 1 | export * from './formatPath' 2 | export * from './getCurrentRouteKey' 3 | export * from './isNil' 4 | 5 | -------------------------------------------------------------------------------- /examples/react/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms = 1000) { 2 | return new Promise((ok) => { 3 | setTimeout(ok, ms) 4 | }) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /examples/react/src/packageA/pages/cat/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Params = { 2 | id: number 3 | sex?: 'boy' | 'girl' 4 | name?: string 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ROUTE_KEY = 'route_key' 2 | export const isBrowser = typeof document !== 'undefined' && !!document.scripts -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/exception/no-page.ts: -------------------------------------------------------------------------------- 1 | export class NoPageException extends Error { 2 | constructor() { 3 | super('没有页面可以回退了') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/router-back-listener/type.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '..' 2 | 3 | export type RouterBackListener = (to: Route, from: Route) => any 4 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Data = { 2 | users: { 3 | id: number 4 | name: string 5 | sex: 'boy' | 'girl' 6 | }[] 7 | } 8 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/index/index.scss: -------------------------------------------------------------------------------- 1 | .index > button { 2 | margin-bottom: 16px; 3 | } 4 | view { 5 | text-align: center; 6 | padding: 32px; 7 | color: #999; 8 | } 9 | -------------------------------------------------------------------------------- /examples/react/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /examples/vue3/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["taro/react"], 3 | "rules": { 4 | "react/jsx-uses-react": "off", 5 | "react/react-in-jsx-scope": "off", 6 | "jsx-quotes": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/react/src/packageA/pages/cat/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import React from 'react' 3 | 4 | export default function Page() { 5 | return 分包页面cat 6 | } 7 | -------------------------------------------------------------------------------- /examples/react/src/packageA/pages/dog/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import React from 'react' 3 | 4 | export default function Page() { 5 | return 分包页面dog 6 | } 7 | -------------------------------------------------------------------------------- /examples/react/src/pages/index/index.css: -------------------------------------------------------------------------------- 1 | .index { 2 | } 3 | .index > button { 4 | margin-bottom: 16px; 5 | } 6 | view { 7 | text-align: center; 8 | padding: 32px; 9 | color: #999; 10 | } 11 | -------------------------------------------------------------------------------- /examples/vue3/project.tt.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "vue3", 4 | "appid": "touristappid", 5 | "setting": { 6 | "es6": false, 7 | "minified": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/project.tt.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "react", 4 | "appid": "touristappid", 5 | "setting": { 6 | "es6": false, 7 | "minified": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { registerRouterBackListener } from 'tarojs-router-next' 2 | import './middleware' 3 | 4 | registerRouterBackListener((to, from) => { 5 | console.log(`全局监听页面返回:从 ${from.url} 返回到 ${to.url}`) 6 | }) 7 | -------------------------------------------------------------------------------- /examples/vue3/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { registerRouterBackListener } from 'tarojs-router-next' 2 | import './middleware' 3 | 4 | registerRouterBackListener((to, from) => { 5 | console.log(`全局监听页面返回:从 ${from.url} 返回到 ${to.url}`) 6 | }) 7 | -------------------------------------------------------------------------------- /examples/vue3/src/app.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './app.css' 3 | import './router' 4 | 5 | const App = createApp({ 6 | onShow(options) {}, 7 | // 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖 8 | }) 9 | 10 | export default App 11 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/page-data-params/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Params = { 2 | id: number 3 | name?: string 4 | } 5 | 6 | export type Data = { 7 | users: { 8 | id: number 9 | name: string 10 | sex: 'boy' | 'girl' 11 | }[] 12 | } 13 | -------------------------------------------------------------------------------- /.dumi/overrides.css: -------------------------------------------------------------------------------- 1 | .dumi-default-hero { 2 | height: 800px; 3 | } 4 | 5 | .dumi-default-hero-title { 6 | font-size: 88px; 7 | } 8 | 9 | .dumi-default-features-item { 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data-params/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Params = { 2 | id: number 3 | name?: string 4 | } 5 | 6 | export type Data = { 7 | users: { 8 | id: number 9 | name: string 10 | sex: 'boy' | 'girl' 11 | }[] 12 | } 13 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { IPluginContext } from '@tarojs/service' 2 | import { Plugin } from './plugin' 3 | 4 | export default (ctx: IPluginContext, config: any) => { 5 | new Plugin(ctx, config).onBuildStart().registerCommand() 6 | } 7 | -------------------------------------------------------------------------------- /examples/react/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /examples/vue3/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /examples/vue3/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | ['taro', { 6 | framework: 'vue3', 7 | ts: true 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/react/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | ['taro', { 6 | framework: 'react', 7 | ts: true 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/react/src/pages/class-demo/route.config.ts: -------------------------------------------------------------------------------- 1 | export type Params = { 2 | id: number 3 | sex?: 'boy' | 'girl' 4 | name?: string 5 | } 6 | 7 | export type Data = { 8 | users: { 9 | id: number 10 | name: string 11 | sex: 'boy' | 'girl' 12 | }[] 13 | } 14 | 15 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/config.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigPackage { 2 | name: string 3 | pagePath: string 4 | } 5 | export interface IConfig { 6 | ignore: string[] 7 | packages: IConfigPackage[] 8 | } 9 | 10 | export const isDev = process.env.NODE_ENV === 'development' 11 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/func/getCurrentRouteKey.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { ROUTE_KEY } from '../constants' 3 | 4 | export function getCurrentRouteKey(): string { 5 | if (!Taro.Current.page) { 6 | return '' 7 | } 8 | return Taro.Current.page[ROUTE_KEY] 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./dist", 3 | "projectname": "react", 4 | "description": "tarojs-router-next-react", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "postcss": false, 10 | "minified": false 11 | }, 12 | "compileType": "miniprogram" 13 | } 14 | -------------------------------------------------------------------------------- /examples/vue3/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./dist", 3 | "projectname": "vue3", 4 | "description": "tarojs-router-next-vue3", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "postcss": false, 10 | "minified": false 11 | }, 12 | "compileType": "miniprogram" 13 | } 14 | -------------------------------------------------------------------------------- /docs/guide/quike/router-back-listener.md: -------------------------------------------------------------------------------- 1 | # 路由回退监听 2 | 3 | 通过 [registerRouterBackListener](/api/register-router-back-listener) 方法全局监听路由回退事件 4 | 5 | ```typescript 6 | import { registerRouterBackListener } from 'tarojs-router-next' 7 | 8 | registerRouterBackListener((to, from) => { 9 | console.log(`全局监听页面返回:从 ${from.url} 返回到 ${to.url}`) 10 | }) 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES5", 5 | "outDir": "dist", 6 | "rootDir": "./src", 7 | "baseUrl": ".", 8 | "removeComments": true, 9 | "downlevelIteration": true, 10 | "sourceMap": true 11 | }, 12 | "include": ["./src"] 13 | } 14 | -------------------------------------------------------------------------------- /docs/api/register-middlewares.md: -------------------------------------------------------------------------------- 1 | # registerMiddlewares 2 | 3 | 注册多个路由中间件,注册的中间件按照注册顺序执行 4 | 5 | 方法定义: 6 | 7 | `registerMiddlewares(middlewares: Middleware[], condition?: MiddlewareCondition): void` 8 | 9 | 参数: 10 | 11 | 1. `middlewares` 中间件,`Middleware` 类型 12 | 2. `condition` 注册条件,`(ctx: RouteContext): boolean` 类型 13 | 14 | 使用方法参考 [registerMiddleware](/api/register-middleware) 15 | -------------------------------------------------------------------------------- /examples/react/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import './app.css' 3 | import './router' 4 | 5 | class App extends Component { 6 | componentDidMount() {} 7 | 8 | componentDidShow() {} 9 | 10 | componentDidHide() {} 11 | 12 | componentDidCatchError() {} 13 | 14 | // this.props.children 是将要会渲染的页面 15 | render() { 16 | return this.props.children 17 | } 18 | } 19 | 20 | export default App 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["packages/*"], 3 | "overrides": [ 4 | { 5 | "files": "examples/react/**", 6 | "extends": ["taro/react"], 7 | "rules": { 8 | "react/jsx-uses-react": "off", 9 | "react/react-in-jsx-scope": "off", 10 | "jsx-quotes": 0 11 | } 12 | }, 13 | { 14 | "files": "examples/vue3/**", 15 | "extends": ["taro/vue3"] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/react/src/pages/me/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { View } from '@tarojs/components' 3 | import './index.css' 4 | import { UserStore } from '../../store/user' 5 | 6 | const Index: FC = () => { 7 | return ( 8 | 9 | 该页面必须要登录,进得来说明已经登陆了 10 | 我的信息:{JSON.stringify(UserStore.userinfo)} 11 | 12 | ) 13 | } 14 | 15 | export default Index 16 | -------------------------------------------------------------------------------- /examples/vue3/src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | pages: [ 3 | 'pages/index/index', 4 | 'pages/login/index', 5 | 'pages/me/index', 6 | 'pages/page-data-params/index', 7 | 'pages/sel-city/index', 8 | ], 9 | window: { 10 | backgroundTextStyle: 'light', 11 | navigationBarBackgroundColor: '#fff', 12 | navigationBarTitleText: 'WeChat', 13 | navigationBarTextStyle: 'black' 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/me/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | -------------------------------------------------------------------------------- /examples/react/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: { 9 | /** 10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 11 | * 参考代码如下: 12 | * webpackChain (chain) { 13 | * chain.plugin('analyzer') 14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 15 | * } 16 | */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/vue3/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: { 9 | /** 10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 11 | * 参考代码如下: 12 | * webpackChain (chain) { 13 | * chain.plugin('analyzer') 14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 15 | * } 16 | */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/api/register-router-back-listener.md: -------------------------------------------------------------------------------- 1 | # registerRouterBackListener 2 | 3 | 注册全局路由返回监听 4 | 5 | 方法定义: 6 | 7 | `registerRouterBackListener(listener: RouterBackListener): void` 8 | 9 | 参数: 10 | 11 | 1. `listener` 监听函数 12 | 13 | ## 注册全局路由返回监听 14 | 15 | ```typescript 16 | import { registerRouterBackListener } from 'tarojs-router-next' 17 | 18 | registerRouterBackListener((to, from) => { 19 | console.log(`全局监听页面返回:从 ${from.url} 返回到 ${to.url}`) 20 | }) 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-params/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import React, { FC } from 'react' 3 | import { Router } from 'tarojs-router-next' 4 | import './index.css' 5 | 6 | const Index: FC = () => { 7 | const params = Router.getParams() 8 | return ( 9 | 10 | 上一个页面带来的参数: 11 | {JSON.stringify(params)} 12 | 13 | ) 14 | } 15 | 16 | export default Index 17 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { View, Text, Button } from '@tarojs/components' 3 | import { Router } from 'tarojs-router-next' 4 | import './index.css' 5 | 6 | const Index: FC = () => { 7 | const data = Router.getData() 8 | return ( 9 | 10 | 上一个页面带来的数据: 11 | {JSON.stringify(data)} 12 | 13 | ) 14 | } 15 | 16 | export default Index 17 | -------------------------------------------------------------------------------- /docs/guide/quike/config.md: -------------------------------------------------------------------------------- 1 | # 插件配置 2 | 3 | `tarojs-router-next-plugin` 插件接收两个配置项: 4 | 5 | - `ignore`:要忽略的文件夹 6 | - `packages`:分包配置 7 | 8 | ## ignore 9 | 10 | 该项配置可以用于忽略一些不是页面的文件夹,比如 `src/pages` 目录下有一个 `api` 文件夹不是页面,那么像如下配置后就不会生成 `Router.toApi` 方法了: 11 | 12 | ```javascript 13 | plugins: [ 14 | [ 15 | 'tarojs-router-next-plugin', 16 | { 17 | ignore: ['api'] 18 | } 19 | ] 20 | ] 21 | ``` 22 | 23 | ## packages 24 | 25 | 具体使用参见 [分包支持](/guide/quike/subpackage) 26 | -------------------------------------------------------------------------------- /examples/react/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.gif'; 3 | declare module '*.jpg'; 4 | declare module '*.jpeg'; 5 | declare module '*.svg'; 6 | declare module '*.css'; 7 | declare module '*.less'; 8 | declare module '*.scss'; 9 | declare module '*.sass'; 10 | declare module '*.styl'; 11 | 12 | declare namespace NodeJS { 13 | interface ProcessEnv { 14 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/vue3/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.gif'; 3 | declare module '*.jpg'; 4 | declare module '*.jpeg'; 5 | declare module '*.svg'; 6 | declare module '*.css'; 7 | declare module '*.less'; 8 | declare module '*.scss'; 9 | declare module '*.sass'; 10 | declare module '*.styl'; 11 | 12 | declare namespace NodeJS { 13 | interface ProcessEnv { 14 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/index.ts: -------------------------------------------------------------------------------- 1 | export { NoPageException } from './exception' 2 | export { 3 | execMiddlewares, getMiddlewares, Middleware, 4 | MiddlewareCondition, 5 | registerMiddleware, 6 | registerMiddlewares, RouteContext 7 | } from './middleware' 8 | export { NavigateOptions, NavigateType, Route, Router } from './router' 9 | export { registerRouterBackListener, RouterBackListener } from './router-back-listener' 10 | import { Router } from './router' 11 | export default Router 12 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/middleware/type.ts: -------------------------------------------------------------------------------- 1 | import { NavigateType, Route } from '../router/type' 2 | 3 | export interface RouteContext { 4 | /** 目标路由 */ 5 | route: Route 6 | /** 路由参数 */ 7 | params: any 8 | /** 跳转类型 */ 9 | type: NavigateType 10 | /** 携带数据 */ 11 | data?: any 12 | } 13 | 14 | export type Middleware = (ctx: RouteContext, next: () => Promise) => Promise 15 | 16 | export type MiddlewareCondition = (ctx: RouteContext) => boolean 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: tarojs-router-next 3 | order: 1 4 | hero: 5 | title: tarojs-router-next 6 | description: 可能是最好的 Taro 小程序路由库 7 | actions: 8 | - text: 快速开始 9 | link: /guide 10 | features: 11 | - emoji: 😆 12 | title: 自动生成 13 | description: 自动为页面生成带类型提示的路由方法 14 | - emoji: 🥰 15 | title: 同步路由 16 | description: 同步的路由方法调用,更清晰的代码逻辑 17 | - emoji: 🤪 18 | title: 路由中间件 19 | description: 轻松实现路由鉴权、提前获取数据等需求 20 | footer: Open-source MIT Licensed | Copyright © 2021
Powered by [dumi](https://d.umijs.org) 21 | --- 22 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/entitys.ts: -------------------------------------------------------------------------------- 1 | export class Page { 2 | dirName: string 3 | dirPath: string 4 | path: string 5 | fullPath: string 6 | packageName: string 7 | routeConfig?: RouteConfig 8 | method?: PageMethod 9 | } 10 | 11 | export class PageMethod { 12 | name: string 13 | type: string 14 | value: string 15 | } 16 | 17 | export class RouteConfig { 18 | params?: string 19 | data?: string 20 | backData?: string 21 | ext?: string 22 | } 23 | 24 | export class ConfigPage { 25 | packageRoot: string 26 | path: string 27 | fullPath: string 28 | } 29 | -------------------------------------------------------------------------------- /examples/react/src/pages/page-data-params/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@tarojs/components' 2 | import React, { FC } from 'react' 3 | import { Router } from 'tarojs-router-next' 4 | import './index.css' 5 | 6 | const Index: FC = () => { 7 | const params = Router.getParams() 8 | const data = Router.getData() 9 | 10 | return ( 11 | 12 | 上一个页面带来的参数: 13 | {JSON.stringify(params)} 14 | 15 | 上一个页面带来的数据: 16 | {JSON.stringify(data)} 17 | 18 | ) 19 | } 20 | 21 | export default Index 22 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/func/formatPath.ts: -------------------------------------------------------------------------------- 1 | import QueryString from 'query-string' 2 | import { Route } from '../router/type' 3 | 4 | export function formatPath(route: Route, params: object) { 5 | let url = route.url 6 | const urlSplit = url.split('?') 7 | if (urlSplit.length > 1 && urlSplit[1]) { 8 | const urlParams = QueryString.parse(url.split('?')[1]) 9 | params = Object.assign(urlParams, params) 10 | url = urlSplit[0] 11 | } 12 | 13 | let paramsStr = QueryString.stringify(params, { encode: false }) 14 | url = `${url}?${paramsStr}` 15 | 16 | return url 17 | } 18 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/page-data-params/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/login/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/router-back-listener/index.ts: -------------------------------------------------------------------------------- 1 | import { Current } from '@tarojs/taro' 2 | import { Route } from '..' 3 | import { RouterBackListener } from './type' 4 | 5 | export { RouterBackListener } 6 | 7 | export const routerBackListenerCollection: RouterBackListener[] = [] 8 | 9 | /** 注册全局路由返回监听 */ 10 | export function registerRouterBackListener(listener: RouterBackListener) { 11 | routerBackListenerCollection.push(listener) 12 | } 13 | 14 | export function execRouterBackListener(from: Route) { 15 | const to = { 16 | url: Current.router?.path || '', 17 | } 18 | for (const listener of routerBackListenerCollection) { 19 | listener(to, from) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/react/src/router/middleware/fetch-info.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { registerMiddleware } from 'tarojs-router-next' 3 | import { UserStore } from '../../store/user' 4 | import { sleep } from '../../utils' 5 | 6 | registerMiddleware( 7 | async (_, next) => { 8 | // 请求用户信息 9 | Taro.showLoading({ title: '请求用户信息中' }) 10 | await sleep() 11 | UserStore.userinfo = { 12 | id: 11, 13 | name: 'lblblong', 14 | } 15 | Taro.hideLoading() 16 | await next() 17 | }, 18 | // 中间件注册条件 19 | (_) => { 20 | // 仅当用户已登录且用户信息为空时才获取用户信息 21 | const token = Taro.getStorageSync('token') 22 | return token && !UserStore.userinfo 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /examples/vue3/src/router/middleware/fetch-info.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { registerMiddleware } from 'tarojs-router-next' 3 | import { UserStore } from '../../store/user' 4 | import { sleep } from '../../utils' 5 | 6 | registerMiddleware( 7 | async (_, next) => { 8 | // 请求用户信息 9 | Taro.showLoading({ title: '请求用户信息中' }) 10 | await sleep() 11 | UserStore.userinfo = { 12 | id: 11, 13 | name: 'lblblong', 14 | } 15 | Taro.hideLoading() 16 | await next() 17 | }, 18 | // 中间件注册条件 19 | (_) => { 20 | // 仅当用户已登录且用户信息为空时才获取用户信息 21 | const token = Taro.getStorageSync('token') 22 | return token && !UserStore.userinfo 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "removeComments": false, 5 | "preserveConstEnums": false, 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "noImplicitAny": false, 9 | "allowSyntheticDefaultImports": true, 10 | "esModuleInterop": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "strictNullChecks": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "skipLibCheck": true, 17 | "target": "ES5", 18 | "outDir": "dist", 19 | "rootDir": "./src", 20 | "declaration": true 21 | }, 22 | "compileOnSave": false 23 | } -------------------------------------------------------------------------------- /examples/react/src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | pages: [ 3 | 'pages/index/index', 4 | 'pages/page-data/index', 5 | 'pages/page-params/index', 6 | 'pages/page-data-params/index', 7 | 'pages/sel-city/index', 8 | 'pages/me/index', 9 | 'pages/login/index', 10 | 'pages/class-demo/index' 11 | ], 12 | subpackages: [ 13 | { 14 | name: 'packageA', 15 | root: 'packageA', 16 | pages: ['pages/cat/index', 'pages/dog/index'] 17 | } 18 | ], 19 | window: { 20 | backgroundTextStyle: 'light', 21 | navigationBarBackgroundColor: '#fff', 22 | navigationBarTitleText: 'WeChat', 23 | navigationBarTextStyle: 'black' 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /examples/react/src/router/middleware/auth-check.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { registerMiddleware, RouteContext, Router } from 'tarojs-router-next' 3 | 4 | registerMiddleware( 5 | async (_, next) => { 6 | const token = Taro.getStorageSync('token') 7 | if (!token) { 8 | const { confirm } = await Taro.showModal({ 9 | title: '提示', 10 | content: '请先登录', 11 | }) 12 | 13 | if (confirm) Router.toLogin() 14 | // 直接返回,不执行 next 即可打断中间件向下执行 15 | return 16 | } 17 | await next() 18 | }, 19 | // 中间件注册条件 20 | (ctx: RouteContext<{ mustLogin: boolean }>) => { 21 | // 仅当页面需要登录时才注册该中间件 22 | return ctx.route.ext?.mustLogin === true 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /examples/vue3/src/router/middleware/auth-check.ts: -------------------------------------------------------------------------------- 1 | import Taro from '@tarojs/taro' 2 | import { registerMiddleware, RouteContext, Router } from 'tarojs-router-next' 3 | 4 | registerMiddleware( 5 | async (_, next) => { 6 | const token = Taro.getStorageSync('token') 7 | if (!token) { 8 | const { confirm } = await Taro.showModal({ 9 | title: '提示', 10 | content: '请先登录', 11 | }) 12 | 13 | if (confirm) Router.toLogin() 14 | // 直接返回,不执行 next 即可打断中间件向下执行 15 | return 16 | } 17 | await next() 18 | }, 19 | // 中间件注册条件 20 | (ctx: RouteContext<{ mustLogin: boolean }>) => { 21 | // 仅当页面需要登录时才注册该中间件 22 | return ctx.route.ext?.mustLogin === true 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /examples/vue3/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | taro-vue 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/react/src/pages/login/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, View } from '@tarojs/components' 2 | import Taro from '@tarojs/taro' 3 | import React, { FC } from 'react' 4 | import { Router } from 'tarojs-router-next' 5 | import { sleep } from '../../utils' 6 | import './index.css' 7 | 8 | const Index: FC = () => { 9 | return ( 10 | 11 | 22 | 23 | ) 24 | } 25 | 26 | export default Index 27 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tarojs-router-next", 3 | "version": "3.4.6", 4 | "main": "./dist/index.js", 5 | "types": "./dist/index.d.ts", 6 | "homepage": "https://github.com/lblblong/tarojs-router-next", 7 | "scripts": { 8 | "build": "npm run clean && npm run prod", 9 | "dev": "tsc -w", 10 | "prod": "tsc", 11 | "clean": "rimraf dist", 12 | "prepublishOnly": "npm run clean && npm run prod" 13 | }, 14 | "files": [ 15 | "dist", 16 | "src", 17 | "package.json", 18 | "tsconfig.json" 19 | ], 20 | "author": "lblblong", 21 | "license": "MIT", 22 | "dependencies": { 23 | "query-string": "^7.0.0" 24 | }, 25 | "keywords": [ 26 | "taro", 27 | "weapp", 28 | "router" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/vue3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "preserve", 19 | "allowJs": true, 20 | "resolveJsonModule": true, 21 | "typeRoots": [ 22 | "node_modules/@types" 23 | ] 24 | }, 25 | "include": ["./src", "./types"], 26 | "compileOnSave": false 27 | } 28 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { VariableDeclaration } from 'ts-morph' 2 | 3 | export function extractValue(options: { name: string; declaration: VariableDeclaration }) { 4 | const { name, declaration } = options 5 | 6 | if (!VariableDeclaration.is(declaration.getKind())) throw Error(`${name} 应该导出变量类型`) 7 | 8 | const text = declaration.getFullText() 9 | 10 | return text.split('=', 2)[1].trim() 11 | } 12 | 13 | export function formatPageDir(dirName: string) { 14 | return dirName 15 | .replace(/\-/g, '_') 16 | .replace(/\_(\w)/g, (all, letter) => { 17 | return letter.toUpperCase() 18 | }) 19 | .replace(/^\S/, s => s.toUpperCase()) 20 | } 21 | 22 | export function isNil(val: any) { 23 | return val === undefined || val === null 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "removeComments": false, 5 | "preserveConstEnums": false, 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "noImplicitAny": false, 9 | "allowSyntheticDefaultImports": true, 10 | "esModuleInterop": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "strictNullChecks": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "skipLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@@/*": [".dumi/tmp/*"] 20 | } 21 | }, 22 | "exclude": [ 23 | "__tests__", 24 | "node_modules", 25 | "dist", 26 | "tests", 27 | "jest", 28 | "**/*.test.ts", 29 | "**/*.spec.ts" 30 | ], 31 | "compileOnSave": false 32 | } -------------------------------------------------------------------------------- /docs/api/other.md: -------------------------------------------------------------------------------- 1 | # 其他 2 | 3 | ## NavigateType 4 | 5 | 路由跳转类型方式 6 | 7 | | 字段 | 描述 | 8 | | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | 9 | | navigateTo | 保留当前页面,跳转到应用内的某个页面。但是不能跳到 `tabbar` 页面。使用 `Router.back` 可以返回到原页面。小程序中页面栈最多十层。 | 10 | | redirectTo | 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 `tabbar` 页面。 | 11 | | reLaunch | 关闭所有页面,打开到应用内的某个页面 | 12 | | switchTab | 跳转到 `tabBar` 页面,并关闭其他所有非 `tabBar` 页面 | 13 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "react-jsx", 19 | "allowJs": true, 20 | "resolveJsonModule": true, 21 | "typeRoots": [ 22 | "node_modules/@types", 23 | "global.d.ts" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "dist" 29 | ], 30 | "compileOnSave": false 31 | } 32 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tarojs-router-next-plugin", 3 | "version": "3.4.6", 4 | "main": "index.js", 5 | "homepage": "https://github.com/lblblong/tarojs-router-next", 6 | "scripts": { 7 | "build": "npm run clean && npm run prod", 8 | "dev": "tsc -w", 9 | "prod": "tsc", 10 | "clean": "rimraf dist", 11 | "prepublishOnly": "npm run clean && npm run prod" 12 | }, 13 | "dependencies": { 14 | "normalize-path": "^3.0.0", 15 | "ts-morph": "^21.0.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^18.7.12", 19 | "@types/normalize-path": "^3.0.0" 20 | }, 21 | "files": [ 22 | "dist", 23 | "index.js", 24 | "package.json" 25 | ], 26 | "author": "lblblong", 27 | "license": "MIT", 28 | "keywords": [ 29 | "taro", 30 | "weapp", 31 | "router" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | { 4 | value: 'feat', 5 | name: '✨ 新功能', 6 | }, 7 | { 8 | value: 'fix', 9 | name: '🐛 bug修复', 10 | }, 11 | { 12 | value: 'refactor', 13 | name: '🎨 重构代码', 14 | }, 15 | { 16 | value: 'perf', 17 | name: '👌 性能优化', 18 | }, 19 | { 20 | value: 'build', 21 | name: '📦 构建过程修改', 22 | }, 23 | { 24 | value: 'ci', 25 | name: '📦 CI修改', 26 | }, 27 | { 28 | value: 'docs', 29 | name: '📖 文档更新', 30 | }, 31 | { 32 | value: 'chore', 33 | name: '🙈 其他修改', 34 | }, 35 | ], 36 | 37 | scopes: [], 38 | 39 | messages: { 40 | type: '提交类型:', 41 | subject: '简短说明:', 42 | confirmCommit: '确认提交?', 43 | }, 44 | 45 | allowCustomScopes: true, 46 | allowBreakingChanges: ['feat', 'fix'], 47 | skipQuestions: ['scope', 'body', 'breaking', 'footer'], 48 | } 49 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/sel-city/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 42 | -------------------------------------------------------------------------------- /docs/guide/quike/subpackage.md: -------------------------------------------------------------------------------- 1 | # 分包支持 2 | 3 | `tarojs-router-next-plugin` 默认只会为 `src/pages` 目录下的页面生成路由方法,当使用分包时需要进行额外的配置。 4 | 5 | 比如下面的代码中在 `app.config.ts` 配置的分包 `root` 为 `packageA`,页面都在 `src/packageA/pages` 目录下,则需要在 `config/index.js` 中为插件传入分包配置: 6 | 7 | [![vRiXKx.png](https://s1.ax1x.com/2022/08/26/vRiXKx.png)](https://imgse.com/i/vRiXKx) 8 | 9 | 最右侧 `config/index.js` 红框中的 `packages` 则是分包配置,其中 `pagePath` 字段声明了分包的页面所在的文件夹路径,`name` 声明了分包的名字,最终会生成如下的分包路由方法: 10 | 11 | - Router.packageA.toCat 12 | - Router.packageA.toDot 13 | 14 | > 注意: 右侧 `config/index.js` 分包配置中的 `name` 并非是要对应 `app.config.ts` 中配置的 `root` 或者 `name`,而是用于生成路由方法的(`Router.[name].toXX`),如果将 `name` 改为 `pkgb` 则会生成 `Router.pkgb.toCat` 和 `Router.pkgb.toDog`,**所以 `name` 的命名请不要包含除下划线以外的其他符号** 15 | 16 | ## 主包页面不在 `src/pages` 目录下 17 | 18 | 有时我们不想使用推荐的目录结构,比如我想把主包的页面放在 `src/views` 下,那么则需要进行如下的配置: 19 | 20 | [![vRm6c4.png](https://s1.ax1x.com/2022/08/26/vRm6c4.png)](https://imgse.com/i/vRm6c4) 21 | 22 | 请注意,主包的 `name` 字段必须为 `main`,这样才会将主包路由方法直接挂载在 Router 类上。 23 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/router/type.ts: -------------------------------------------------------------------------------- 1 | export interface Route { 2 | /** 页面 url,与配置在 app.config.ts 中的一致 */ 3 | url: string 4 | /** 附加数据 */ 5 | ext?: E 6 | } 7 | 8 | export enum NavigateType { 9 | /** 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 Router.back 可以返回到原页面。小程序中页面栈最多十层。 */ 10 | navigateTo = 'navigateTo', 11 | /** 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。 */ 12 | redirectTo = 'redirectTo', 13 | /** 关闭所有页面,打开到应用内的某个页面 */ 14 | reLaunch = 'reLaunch', 15 | /** 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 */ 16 | switchTab = 'switchTab', 17 | } 18 | 19 | export interface NavigateOptions { 20 | /** 跳转类型 */ 21 | type?: NavigateType 22 | /** 跳转页面携带的数据,可以是任何类型 */ 23 | data?: unknown 24 | /** 路由参数,将拼接在 url 后面,不适合携带大量数据,携带大量数据请使用 data */ 25 | params?: Record 26 | /** 跳转完成的回调方法 */ 27 | complete?: (res: TaroGeneral.CallbackResult) => void 28 | /** 跳转失败的回调方法 */ 29 | fail?: (res: TaroGeneral.CallbackResult) => void 30 | /** 跳转成功的回调方法 */ 31 | success?: (res: TaroGeneral.CallbackResult) => void 32 | } 33 | -------------------------------------------------------------------------------- /examples/react/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Weng Weiyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/guide/quike/sync-router.md: -------------------------------------------------------------------------------- 1 | # 同步的路由方法 2 | 3 | 我们经常需要在返回到当前页面时做一些操作,思考一下以下场景在 Taro 中如何实现: 4 | 5 | - 在编辑页跳转到选择城市页面选择一个城市,然后返回赋值给编辑页的表单项 6 | - 在文章列表页点击其中一项进入到编辑页,编辑完成后返回数据给上一个页面局部更新,取消编辑则不做操作 7 | 8 | ## 之前的实现方式:EventChannel 9 | 10 | 通过 [EventChannel](https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.navigateTo.html#%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81) 建立页面通讯,在选择城市页面先获取 `eventChannel`,然后通过 `emit` 方法发送数据到到上一个页面注册的事件回调方法中,然后再调用 `Taro. navigateBack` 返回编辑页 11 | 12 | **_不好的地方:事件的回调方法可读性差、耦合度高、只能在回调内部处理异常,并且需要目标页面配合写 event_** 13 | 14 | ## 好的实现方式:同步的路由方法调用 15 | 16 | 在 tarojs-router-next 中,所有路由跳转都变成了同步方法,比如: 17 | 18 | ```tsx | pure 19 | // page/edit/index 20 | try { 21 | // 跳转页面选择城市 22 | const cityData = await Router.toSelectCity() 23 | if (!cityData) return 24 | // 赋值给表单项 25 | this.form.city = cityData 26 | } catch (err) { 27 | console.log(err.message) 28 | } 29 | 30 | // page/select-city/index 31 | Router.back() // 返回上一个页面,此时上一个页面拿到的是 null 32 | Router.back({ id: 1, name: '深圳' }) // 返回上一个页面并返回城市数据 33 | Router.back(new Error('用户取消选择')) // 返回上一个页面并抛出异常 34 | ``` 35 | 36 | taro-router-next 的路由跳转方法会返回一个 `Promise`,该 `Promise` 对象会同时传递给跳转的目标页面, 使用 **Router.back** 即可通过 `Promise` 返回或抛出异常给上一个页面 37 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: 构建文档 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - 'docs/**' 8 | - 'public/**' 9 | - '.umirc.ts' 10 | - '.github/workflows/**' 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | persist-credentials: false 20 | 21 | - name: Cache pnpm modules 22 | uses: actions/cache@v2 23 | env: 24 | cache-name: cache-pnpm-modules 25 | with: 26 | path: ~/.pnpm-store 27 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }} 28 | restore-keys: | 29 | ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}- 30 | 31 | - uses: pnpm/action-setup@v2.0.1 32 | with: 33 | version: 8.2.0 34 | run_install: true 35 | 36 | - run: pnpm run doc:build 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | publish_dir: ./dist -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from '../lib/compose' 2 | import { Middleware, MiddlewareCondition, RouteContext } from './type' 3 | 4 | export { Middleware, RouteContext, MiddlewareCondition } 5 | 6 | export const middlewareCollection: { 7 | middlewares: Middleware[] 8 | condition?: MiddlewareCondition 9 | }[] = [] 10 | 11 | export function registerMiddleware(middleware: Middleware, condition?: MiddlewareCondition) { 12 | middlewareCollection.push({ 13 | middlewares: [middleware], 14 | condition 15 | }) 16 | } 17 | 18 | export function registerMiddlewares(middlewares: Middleware[], condition?: MiddlewareCondition) { 19 | middlewareCollection.push({ 20 | middlewares, 21 | condition 22 | }) 23 | } 24 | 25 | export function getMiddlewares(ctx: RouteContext) { 26 | return middlewareCollection 27 | .filter(mc => { 28 | if (!mc.condition) return true 29 | else return mc.condition(ctx) 30 | }) 31 | .map(mc => mc.middlewares) 32 | .reduce((pre, cur) => { 33 | return [...pre, ...cur] 34 | }, []) 35 | } 36 | 37 | export async function execMiddlewares(middlewares: Middleware[], ctx: RouteContext) { 38 | const fn = compose(middlewares) 39 | return await fn(ctx) 40 | } 41 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/lib/compose.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Compose `middleware` returning 3 | * a fully valid middleware comprised 4 | * of all those which are passed. 5 | * 6 | * @param {Array} middleware 7 | * @return {Function} 8 | */ 9 | export function compose(middleware: Function[]) { 10 | if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') 11 | for (const fn of middleware) { 12 | if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') 13 | } 14 | 15 | /** 16 | * @param {Object} context 17 | * @return {Promise} 18 | * @api public 19 | */ 20 | return function (context: any, next?: Function) { 21 | // last called middleware # 22 | let index = -1 23 | return dispatch(0) 24 | function dispatch(i: number) { 25 | if (i <= index) return Promise.reject(new Error('next() called multiple times')) 26 | index = i 27 | let fn: Function | undefined = middleware[i] 28 | if (i === middleware.length) fn = next 29 | if (!fn) return Promise.resolve() 30 | try { 31 | return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) 32 | } catch (err) { 33 | return Promise.reject(err) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/react/src/pages/class-demo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, View } from '@tarojs/components' 2 | import React, { Component } from 'react' 3 | import { Router } from 'tarojs-router-next' 4 | import './index.css' 5 | 6 | export default class App extends Component { 7 | state = { 8 | params: null, 9 | data: null, 10 | } 11 | 12 | componentDidShow() { 13 | this.setState({ 14 | params: Router.getParams(), 15 | data: Router.getData(), 16 | }) 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 | 上一个页面带来的参数: 23 | {JSON.stringify(this.state.params)} 24 | 25 | 上一个页面带来的数据: 26 | {JSON.stringify(this.state.data)} 27 | 28 | 35 | 42 | 49 | 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/guide/quike/route-config.md: -------------------------------------------------------------------------------- 1 | # 路由配置 2 | 3 | 每一个页面都可以通过文件夹下的 `route.config.ts` 文件: 4 | 5 | - 定义进入该页面的 `Router.to**` 方法的参数 (`params`)、数据 (`data`) 的类型 6 | - 定义页面返回的数据的类型 `BackData` 7 | - 定义提供给路由中间件的 [附加数据](/guide/quike/middleware#路由附加数据) 8 | 9 | 通过 `route.config.ts` 导出类型定义后,可让 `Router.to**` 方法获得完备的类型提示 10 | 11 | ## route.config.ts 12 | 13 | 该文件添加在页面文件夹下,比如原文件夹结构: 14 | 15 | - src/pages/index 16 | - index.config.ts 17 | - index.css 18 | - index.tsx 19 | 20 | 增加 route.config.ts 后: 21 | 22 | - src/pages/index 23 | - index.config.ts 24 | - index.css 25 | - index.tsx 26 | - `route.config.ts` 27 | 28 | ## 定义进入该页面需要传入的 params 参数的类型 29 | 30 | 在 `route.config.ts` 中导出类型定义 `Params` 31 | 32 | 由于 params 会展开附加到 url 后面,请不要定义层级一层以上的类型 33 | 34 | ```typescript 35 | export type Params = { 36 | id: number 37 | title: string 38 | } 39 | ``` 40 | 41 | ## 定义进入该页面需要传入的 data 数据的类型 42 | 43 | 在 `route.config.ts` 中导出类型定义 `Data` 44 | 45 | ```typescript 46 | export type Data = { 47 | users: { 48 | id: number 49 | name: string 50 | sex: 'boy' | 'girl' 51 | }[] 52 | } 53 | ``` 54 | 55 | ## 导出附加数据 Ext 56 | 57 | 附加数据是传递给中间件使用的 58 | 59 | ```typescript 60 | export const Ext = { 61 | mustLogin: true, 62 | role: [1, 2, 3], 63 | } 64 | ``` 65 | 66 | ## 定义该页面返回的数据的类型 67 | 68 | 在 `route.config.ts` 中导出类型定义 `BackData` 69 | 70 | ```typescript 71 | export type BackData = { 72 | id: number 73 | name: string 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /examples/vue3/src/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 67 | -------------------------------------------------------------------------------- /examples/react/src/pages/sel-city/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, View } from '@tarojs/components' 2 | import Taro from '@tarojs/taro' 3 | import React, { FC } from 'react' 4 | import { Router } from 'tarojs-router-next' 5 | import './index.css' 6 | 7 | const Index: FC = () => { 8 | const cityList = [ 9 | { 10 | id: 1, 11 | name: '深圳', 12 | }, 13 | { 14 | id: 2, 15 | name: '广州', 16 | }, 17 | ] 18 | 19 | const onSel = (index: number) => { 20 | Router.back(cityList[index]) 21 | } 22 | 23 | return ( 24 | 25 | 26 | {cityList.map((it, index) => { 27 | return ( 28 | onSel(index)} className="item" key={it.id}> 29 | {it.name} 30 | 31 | ) 32 | })} 33 | 34 | 35 | 42 | 43 | 62 | 63 | ) 64 | } 65 | 66 | export default Index 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "author": "lblblong", 5 | "homepage": "https://github.com/lblblong/tarojs-router-next", 6 | "scripts": { 7 | "build": "pnpm --filter 'tarojs-router-next*' build", 8 | "clean": "rm -rf node_modules ./**/**/node_modules", 9 | "doc:serve": "dumi dev", 10 | "doc:build": "dumi build", 11 | "commit": "git-cz", 12 | "prepare": "husky install" 13 | }, 14 | "devDependencies": { 15 | "@commitlint/cli": "^12.1.4", 16 | "@commitlint/config-conventional": "^12.1.4", 17 | "@commitlint/prompt-cli": "^12.1.4", 18 | "@tarojs/service": "^3.6.16", 19 | "@tarojs/taro": "^3.6.16", 20 | "@types/fs-extra": "^9.0.9", 21 | "@types/glob": "^7.1.3", 22 | "@types/mkdirp": "^1.0.1", 23 | "@types/node": "^14.14.37", 24 | "@types/react": "^17.0.3", 25 | "@types/webpack": "^4.41.26", 26 | "@types/webpack-dev-server": "^3.11.3", 27 | "@umijs/plugin-analytics": "^0.2.2", 28 | "color-string": "^1.5.5", 29 | "commitizen": "^4.2.3", 30 | "cz-conventional-changelog": "^3.3.0", 31 | "cz-customizable": "^6.3.0", 32 | "dumi": "^2.2.7", 33 | "eslint": "^7.23.0", 34 | "eslint-config-taro": "3.6.16", 35 | "eslint-plugin-import": "^2.12.0", 36 | "eslint-plugin-react": "^7.8.2", 37 | "eslint-plugin-react-hooks": "^1.6.1", 38 | "husky": "^6.0.0", 39 | "rimraf": "^3.0.2", 40 | "typescript": "^4.2.3" 41 | }, 42 | "config": { 43 | "commitizen": { 44 | "path": "node_modules/cz-customizable" 45 | } 46 | }, 47 | "version": "0.0.0" 48 | } 49 | -------------------------------------------------------------------------------- /docs/api/register-middleware.md: -------------------------------------------------------------------------------- 1 | # registerMiddleware 2 | 3 | 注册路由中间件,注册的中间件按照注册顺序执行 4 | 5 | 方法定义: 6 | 7 | `registerMiddleware(middleware: Middleware, condition?: MiddlewareCondition): void` 8 | 9 | 参数: 10 | 11 | 1. `middleware` 中间件,`Middleware` 类型 12 | 2. `condition` 注册条件,`(ctx: RouteContext): boolean` 类型 13 | 14 | ## 注册路由中间件 15 | 16 | ```typescript 17 | import Taro from '@tarojs/taro' 18 | import { Middleware, registerMiddleware } from 'tarojs-router-next' 19 | 20 | // 定义路由中间件 21 | export const Logger: Middleware = async (ctx, next) => { 22 | console.log('中间件开始执行,当前进入的路由:', ctx.route.url) 23 | await next() // 执行下一个中间件 24 | console.log(' 中间件结束执行 ') 25 | } 26 | 27 | // 注册路由中间件 28 | registerMiddleware(Logger) 29 | ``` 30 | 31 | ## 动态注册路由中间件 32 | 33 | ```typescript 34 | // 仅为 me 和 home 页面注册该路由中间件 35 | registerMiddleware(Logger, (ctx) => { 36 | return ['/pages/me/index', '/pages/home/index'].indexOf(ctx.route.url) !== -1 37 | }) 38 | ``` 39 | 40 | ## 获取路由的附带数据 41 | 42 | 路由的附加数据可以在 [route.config.ts](/guide/quike/route-config) 配置,当直接使用 [Router.navigate](/api/router#navigate-route-options-) 方法时则通过参数 [route.ext](/guide/quike/route-config#导出附加数据-ext) 传递给中间件 43 | 44 | 一个检查必须登录的路由中间件示例: 45 | 46 | ```typescript 47 | import Taro from '@tarojs/taro' 48 | import { hasLogin, login } from '@/store/user' 49 | import { Middleware, registerMiddleware } from 'tarojs-router-next' 50 | 51 | export const LoginCheckMiddleware: Middleware<{ mustLogin: string }> = async (ctx, next) => { 52 | const { mustLogin } = ctx.route.ext // 附加数据 53 | if (mustLogin && !hasLogin) { 54 | await login() 55 | } 56 | await next() 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/guide/quike/navigate.md: -------------------------------------------------------------------------------- 1 | # 路由跳转 2 | 3 | 在项目启动后,tarojs-router-next 会自动监听项目下 `src/pages` 的变动,为 [Router](/api/router) 类生成对应的路由方法,路由方法名字以 [to](/api/router#to-options-) 起头。 4 | 5 | 如以下左边页面结构会为 [Router](/api/router) 生成的路由方法: 6 | 7 | ![](/tarojs-router-next/images/code1.png) 8 | 9 | ```typescript 10 | import { Router, NavigateType } from 'tarojs-router-next' 11 | 12 | Router.toLogin() // 不带参跳转 13 | Router.toLogin({ params: { username: 'router' }) // 带参跳转 14 | Router.toLogin({ type: NavigateType.redirectTo }) // 关闭当前页面,重定向到 login 页面 15 | 16 | ``` 17 | 18 | 以上是自动生成的 [Router.to\*\*](/api/router#to-options-) 方法,具体参考 API 文档:[Router.to\*\*(options)](/api/router#to-options-) 19 | 20 | 如果没有使用路由方法自动生成插件,还可以通过 [Router.navigate](/api/router#navigate-route-options-) 方法进行路由跳转 21 | 22 | ```typescript 23 | import { Router, NavigateType } from 'tarojs-router-next' 24 | 25 | // 不带参跳转 26 | Router.navigate({ url: '/pages/login/index' }) 27 | // 带参跳转 28 | Router.navigate({ url: '/pages/login/index' }, { params: { username: 'router' } }) 29 | // 关闭当前页面,重定向到 login 页面 30 | Router.navigate({ url: '/pages/login/index' }, { type: NavigateType.redirectTo }) 31 | ``` 32 | 33 | 跳转类型参考:[NavigateType](/api/other#navigatetype) 34 | 35 | ## 页面返回 36 | 37 | tarojs-router-next 提供了 [Router.back](/api/router#back-result-) 方法以供页面返回 38 | 39 | 该方法可以返回数据到前一个页面,也可抛出异常到前一个页面 40 | 41 | ```typescript 42 | Router.back() // 返回上一个页面,此时上一个页面拿到的是 null 43 | Router.back({ id: 1, name: '深圳' }) // 返回上一个页面并返回城市数据 44 | Router.back(new Error('用户取消选择')) // 返回上一个页面并抛出异常 45 | ``` 46 | 47 | 而在上一个页面获取返回的数据只需要 `await` 即可 48 | 49 | ```typescript 50 | try { 51 | const result = await Router.navigate({ url: '/pages/sel-city/index' }) 52 | console.log('选择城市:', result) 53 | } catch (err) { 54 | console.log(err.message) 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/page-data/index.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentRouteKey } from '../func' 2 | 3 | export class PageData { 4 | private static pageData: Map = new Map() 5 | private static pagePromise: Map< 6 | string, 7 | { 8 | res: (val: any) => void 9 | rej: (err: any) => void 10 | } 11 | > = new Map() 12 | 13 | private static backResult: Map = new Map() 14 | 15 | static getPageData(default_value?: T): T { 16 | let route_key = getCurrentRouteKey() 17 | let result = PageData.pageData.get(route_key) || default_value 18 | return result 19 | } 20 | 21 | private static delPageData(route_key: string) { 22 | PageData.pageData.delete(route_key) 23 | } 24 | 25 | private static delPagePromise(route_key: string) { 26 | PageData.pagePromise.delete(route_key) 27 | } 28 | 29 | static setPageData(route_key: string, data: any) { 30 | this.pageData.set(route_key, data) 31 | } 32 | 33 | static setPagePromise( 34 | route_key: string, 35 | options: { 36 | res: (val: any) => void 37 | rej: (err: any) => void 38 | } 39 | ) { 40 | this.pagePromise.set(route_key, options) 41 | } 42 | 43 | static emitBack(route_key: string) { 44 | const pme = PageData.pagePromise.get(route_key) 45 | if (!pme) return 46 | let result = PageData.backResult.get(route_key) 47 | 48 | PageData.delPageData(route_key) 49 | PageData.delPagePromise(route_key) 50 | 51 | if (result) { 52 | PageData.backResult.delete(route_key) 53 | if (result instanceof Error) { 54 | pme.rej(result) 55 | } else { 56 | pme.res(result) 57 | } 58 | } else { 59 | pme.res(null) 60 | } 61 | } 62 | 63 | static setBackResult(result: any) { 64 | const route_key = getCurrentRouteKey() 65 | PageData.backResult.set(route_key, result) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | [![](https://img.shields.io/npm/v/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 4 | [![](https://img.shields.io/npm/l/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 5 | [![](https://img.shields.io/npm/dt/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 6 | 7 | 它是一个小巧的 [Taro(小程序)](https://taro-docs.jd.com/taro/docs/README/index.html) 路由库,为你提供以下特性: 8 | 9 | - 自动生成带参数类型提示的路由方法 10 | - 允许传递任意类型、任意大小的参数数据 11 | - 同步的路由方法调用 12 | - koa 体验一致的路由中间件 13 | 14 | ## 解决什么问题 15 | 16 | 1. 路由跳转的页面 url 没有类型提示容易输错 17 | 2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据 18 | 3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常 19 | 4. 路由跳转的鉴权等实现起来比较麻烦 20 | 21 | ## 如何解决 22 | 23 | **1. 路由跳转的页面 url 没有类型提示容易输错** 24 | 25 | tarojs-router-next 不需要使用者手写页面 url,它会监听项目 `src/pages` 内容变化,自动为使用者生成对应的路由方法并附加到 [Router](/api/router) 类上,比如以下列子: 26 | 27 | 左边的页面结构会生成右边的 [Router.to\*\*](/api/router#to-options-) 系列方法,全都挂在 [Router](/api/router) 类上 28 | 29 | ![](/tarojs-router-next/images/code1.png) 30 | 31 | **2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据** 32 | 33 | tarojs-router-next 允许直接传递一个对象给 `params`,它会把 `params` 展开拼接到 `url` 后面。并且还可以接收一个 `data` 参数,`data` 可以传递任意类型、任意大小的数据。 34 | 35 | ![](/tarojs-router-next/images/code2.gif) 36 | 37 | **3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常** 38 | 39 | tarojs-router-next 的路由跳转会返回一个 `Promise`,可以用 `async/await` 写出同步代码,详细参考 [同步的路由方法](/guide/quike/sync-router) 40 | 41 | **4. 路由跳转的鉴权等实现起来比较麻烦** 42 | 43 | 自己实现路由的鉴权是比较麻烦的事情,而 tarojs-router-next 提供非常易于理解的路由中间件功能,详细参考 [路由中间件](/guide/quike/middleware) 44 | 45 | ## 平台与框架支持 46 | 47 | #### 框架支持 48 | 49 | 支持所有 `Taro` 可支持的框架(`React`、`Vue`、`Vue3`、`Nerv`) 50 | 51 | #### 小程序支持 52 | 53 | 理论上支持所有 `Taro` 可支持的小程序平台,目前已在 `微信小程序`、`QQ小程序`、`支付宝小程序` 测试通过 54 | 55 | #### H5 支持 56 | 57 | 支持 58 | 59 | #### React Native 支持 60 | 61 | 暂不支持 62 | -------------------------------------------------------------------------------- /docs/guide/quike/start.md: -------------------------------------------------------------------------------- 1 | # 安装及使用 2 | 3 | ## 安装核心依赖 4 | 5 | ```shell 6 | $ npm install --save tarojs-router-next 7 | ``` 8 | 9 | ## 安装路由方法自动生成插件 10 | 11 | ```shell 12 | $ npm install --dev tarojs-router-next-plugin 13 | ``` 14 | 15 | 在 [编译配置(config/index.js)](https://taro-docs.jd.com/docs/config-detail#plugins) 的 plugins 字段中引入插件: 16 | 17 | ```typescript 18 | const config = { 19 | plugins: ['tarojs-router-next-plugin'] 20 | } 21 | ``` 22 | 23 | 如果要关闭自动生成 [Router.to\*\*](/api/router#to-options-) 相关的路由方法,需要修改配置项 `watch` 为 `false`,请参考 [关闭自动生成 Router.to\*\*](/guide/quike/config#关闭自动生成-routerto) 24 | 25 | 请注意,如果使用 Taro 3.5 及以上版本,请在 [编译配置(config/index.js)](https://taro-docs.jd.com/docs/config-detail#compilerprebundleexclude) 中将 tarojs-router-next 从预编译中排除: 26 | 27 | ```typescript 28 | const config = { 29 | compiler: { 30 | prebundle: { 31 | exclude: ['tarojs-router-next'] 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ## 开始使用 38 | 39 | #### 路由跳转 40 | 41 | 在启动项目后,tarojs-router-next 会自动监听项目下 `src/pages` 的变动,自动为 [Router](/api/router) 类生成对应的路由方法,路由跳转方法名字以 [to](/api/router#to-options-) 起头。如以下左边页面结构会为 [Router](/api/router) 生成的路由方法: 42 | 43 | ![](/tarojs-router-next/images/code1.png) 44 | 45 | 如果关闭了路由方法的自动生成,还可以通过 [Router.navigate](/api/router#navigate-route-options-) 方法进行路由跳转 46 | 47 | 查看关于路由跳转的更多信息:[路由跳转](/guide/quike/navigate) 48 | 49 | #### 页面传参 50 | 51 | 可以通过方法的 `params` 和 `data` 选项传递数据: 52 | 53 | ```typescript 54 | // 传递参数,params 会展开拼接在 url 后面 55 | Router.toDetail({ params: { id: 1 } }) 56 | Router.toDetail({ params: { id: 1, name: 'lbl' } }) 57 | 58 | Router.navigate({ url: '/pages/detail/index' }, { params: { id: 1 } }) 59 | Router.navigate({ url: '/pages/detail/index' }, { params: { id: 1, name: 'lbl' } }) 60 | 61 | // 传递数据,可传递任意类型和大小的数据 62 | Router.toDetail({ data: { name: 'taro', role: [1, 2, 3] } }) 63 | Router.toDetail({ data: 123 }) 64 | Router.toDetail({ data: true }) 65 | 66 | // 同时传递 67 | Router.toDetail({ params: { id: 1 }, data: 123 }) 68 | ``` 69 | 70 | 查看关于路由传参的更多信息:[路由传参](/guide/quike/params) 71 | -------------------------------------------------------------------------------- /docs/guide/quike/params.md: -------------------------------------------------------------------------------- 1 | # 路由传参 2 | 3 | 区别于小程序的路由传参方式,tarojs-router-next 不需要在 `url` 后面手动拼接路由参数,并且还可以传递任意类型、任意大小的数据。 4 | 5 | ## 使用示例 6 | 7 | #### 传递 params 8 | 9 | `params` 会由 tarojs-router-next 展开并拼接在 `url` 后面,所以只能传递少量并且是 `Record` 类型的数据 10 | 11 | 请不要通过 `params` 传递层级大于一层的数据,层级大于一层的数据请使用 [data](/guide/quike/params#传递-data) 12 | 13 | ```typescript 14 | // 可以传递 15 | Router.toDetail({ params: { id: 1 } }) 16 | Router.toDetail({ params: { id: 1, name: 'lbl' } }) 17 | 18 | Router.navigate({ url: '/pages/detail/index' }, { params: { id: 1 } }) 19 | Router.navigate({ url: '/pages/detail/index' }, { params: { id: 1, name: 'lbl' } }) 20 | 21 | // 以下是错误使用方式 22 | Router.toDetail({ params: { obj: { id: 1 } } }) // 不可以传递层级大于 1 的数据 23 | Router.toDetail({ params: 1 }) // 必须是 Record 类型的数据 24 | ``` 25 | 26 | #### 传递 data 27 | 28 | `data` 可以传递任意类型任意大小的数据 29 | 30 | ```typescript 31 | Router.toDetail({ data: { name: 'taro', role: [1, 2, 3] } }) 32 | Router.toDetail({ data: 123 }) 33 | Router.toDetail({ data: true }) 34 | 35 | Router.toDetail({ url: '/pages/detail/index' }, { data: { name: 'taro', role: [1, 2, 3] } }) 36 | Router.toDetail({ url: '/pages/detail/index' }, { data: 123 }) 37 | Router.toDetail({ url: '/pages/detail/index' }, { data: true }) 38 | ``` 39 | 40 | ## 类型提示 41 | 42 | 页面的入参可能会经常变动,如果没有类型提示,我们经常会忘了哪里的传参没有同步修改 43 | 44 | 使用 tarojs-router-next 可以通过页面下的 [route.config.ts](/guide/quike/route-config) 定义 [params](/guide/quike/route-config#定义进入该页面需要传入的-params-参数的类型) 和 [data](/guide/quike/route-config#定义进入该页面需要传入的-data-数据的类型) 的类型: 45 | 46 | 在页面文件夹下创建 `route.config.ts` 文件 47 | 48 | - src/pages/detail 49 | - index.config.ts 50 | - index.tsx 51 | - `route.config.ts` 52 | 53 | 导出 `Data` 和 `Params` 的类型: 54 | 55 | ```typescript 56 | // 导出 params 的类型,名字必须是 Params 57 | export type Params = { 58 | id: number 59 | name: string 60 | } 61 | 62 | // 导出 data 的类型,名字必须是 Data 63 | export type Data = { 64 | users: { 65 | id: number 66 | name: string 67 | }[] 68 | } 69 | ``` 70 | 71 | 然后 tarojs-router-next 就会生成带有类型提示的跳转方法 72 | -------------------------------------------------------------------------------- /examples/vue3/config/index.js: -------------------------------------------------------------------------------- 1 | const isWsl = require('is-wsl') 2 | 3 | const plugins = ['tarojs-router-next-plugin'] 4 | 5 | if (isWsl) { 6 | plugins.push([ 7 | 'taro-plugin-sync-in-wsl', 8 | { 9 | weapp: [ 10 | { 11 | sourcePath: `dist`, 12 | outputPath: `/mnt/c/code/taro/tarojs-router-next-vue3`, 13 | }, 14 | ], 15 | }, 16 | ]) 17 | } 18 | 19 | const config = { 20 | projectName: 'vue3', 21 | date: '2021-4-20', 22 | designWidth: 750, 23 | deviceRatio: { 24 | 640: 2.34 / 2, 25 | 750: 1, 26 | 828: 1.81 / 2, 27 | }, 28 | sourceRoot: 'src', 29 | outputRoot: 'dist', 30 | plugins, 31 | defineConstants: {}, 32 | copy: { 33 | patterns: [], 34 | options: {}, 35 | }, 36 | framework: 'vue3', 37 | compiler: 'webpack5', 38 | cache: { 39 | enable: true // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache 40 | }, 41 | mini: { 42 | postcss: { 43 | pxtransform: { 44 | enable: true, 45 | config: {}, 46 | }, 47 | url: { 48 | enable: true, 49 | config: { 50 | limit: 1024, // 设定转换尺寸上限 51 | }, 52 | }, 53 | cssModules: { 54 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 55 | config: { 56 | namingPattern: 'module', // 转换模式,取值为 global/module 57 | generateScopedName: '[name]__[local]___[hash:base64:5]', 58 | }, 59 | }, 60 | }, 61 | }, 62 | h5: { 63 | publicPath: '/', 64 | staticDirectory: 'static', 65 | postcss: { 66 | autoprefixer: { 67 | enable: true, 68 | config: {}, 69 | }, 70 | cssModules: { 71 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 72 | config: { 73 | namingPattern: 'module', // 转换模式,取值为 global/module 74 | generateScopedName: '[name]__[local]___[hash:base64:5]', 75 | }, 76 | }, 77 | }, 78 | }, 79 | } 80 | 81 | module.exports = function (merge) { 82 | if (process.env.NODE_ENV === 'development') { 83 | return merge({}, config, require('./dev')) 84 | } 85 | return merge({}, config, require('./prod')) 86 | } 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v3.4.0 (2023-09-14) 2 | 3 | - 支持在 route.config.ts 中配置 BackData 来指定页面返回数据的类型 4 | - 为 Router.getParams 方法添加泛型支持 5 | 6 | ## v3.3.2 (2023-09-13) 7 | 8 | - 修复分包在 build 时提示需要 name 的问题 9 | 10 | ## v3.3.1 (2023-09-13) 11 | 12 | - 修复 win 下路径导致生成 toRoute 路由方法的问题 13 | 14 | ## v3.3.0 (2023-09-13) 15 | 16 | - 修复 win 下路径导致生成的方法没有参数提示的问题 17 | - 增加 complete、fail、success 回调 18 | 19 | ## v3.2.0 (2022-10-27) 20 | 21 | - 向外抛出异常 [#57](https://github.com/lblblong/tarojs-router-next/issues/57) 22 | 23 | ## v3.1.1 (2022-09-28) 24 | 25 | - 修复引用路径错误 26 | 27 | ## v3.1.0 (2022-09-28) 28 | 29 | - 导出内部 getMiddlewares 和 execMiddlewares 给外部使用 30 | 31 | ## v3.0.1 (2022-09-05) 32 | 33 | - 修复 win 平台下路径导致的问题 34 | 35 | ## v3.0.0 (2022-08-26) 36 | 37 | - 大幅提升编译速度,优化分包支持 38 | 39 | ## v2.9.0 (2022-08-23) 40 | 41 | - 优化路由方法的类型生成,提升生成速度 42 | 43 | ## v2.8.1 (2022-04-12) 44 | 45 | - Router.back 方法允许多层页面回退 46 | 47 | ## v2.7.2 (2022-02-10) 48 | 49 | - 更新 taro 依赖版本至 3.4.1 50 | - 切换包管理为 pnpm 51 | 52 | ## v2.7.1 (2021-10-26) 53 | 54 | - 修复 [setBackResult](https://lblblong.github.io/tarojs-router-next/api/router#setbackresult-result-any-) 非静态方法问题 55 | 56 | ## v2.7.0 (2021-10-25) 57 | 58 | - 新增设置页面返回数据方法:[setBackResult](https://lblblong.github.io/tarojs-router-next/api/router#setbackresult-result-any-) 59 | 60 | ## v2.6.0 (2021-10-16) 61 | 62 | - 新增全局监听路由返回功能:registerRouterBackListener 63 | 64 | ## v2.5.3 (2021-9-6) 65 | 66 | - 修复 to 方法返回值类型问题 67 | 68 | ## v2.5.2 (2021-8-31) 69 | 70 | - 更新文档 71 | 72 | ## v2.5.1 (2021-8-31) 73 | 74 | - 为此前中间件访问 type 和 data 功能添加类型提示 75 | 76 | ## v2.5.0 (2021-8-31) 77 | 78 | - 中间件逻辑修改为和 koa 完全一致 79 | 80 | ## v2.4.0 (2021-8-9) 81 | 82 | - 添加中间件中访问跳转路由 type 和 data 的功能 83 | 84 | ## v2.3.1 (2021-6-25) 85 | 86 | - 修复 h5 端第一次页面跳转第一个页面不隐藏问题 87 | 88 | ## v2.3.0 (2021-6-21) 89 | 90 | - route_key 从 query 中隐藏 91 | - 添加对 h5 自定义路由路径项目的支持 92 | 93 | ## v2.2.6 (2021-6-18) 94 | 95 | - 修复 router-gen 不输出日志问题 96 | 97 | ## v2.2.5 (2021-6-3) 98 | 99 | - 修复 Mac 环境下 .DS_Store 文件导致的问题 100 | 101 | ## v2.2.3 (2021-5-28) 102 | 103 | - 优化代码生成速度 104 | 105 | ## v2.2.2 (2021-5-13) 106 | 107 | - 修复默认 tarojs-router-next-plugin 默认非 watch 模式问题 108 | 109 | ## v2.2.1 (2021-5-13) 110 | 111 | - 增加配置项 [watch](https://lblblong.github.io/tarojs-router-next/guide/quike/config#%E5%85%B3%E9%97%AD%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90-routerto) 可关闭自动生成 112 | 113 | ## v2.2.0 (2021-5-13) 114 | 115 | - 增加分包支持 116 | - 构建模式仅为已在 app.config.ts 注册的页面生成路由方法 117 | -------------------------------------------------------------------------------- /docs/guide/quike/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 没有生成路由方法? 4 | 5 | **是否正确安装?** 6 | 7 | 请参考 [安装及使用](/guide/quike/start) 8 | 9 | **主包页面是否在 `src/pages` 目录下?** 10 | 11 | 如果不在 `src/pages` 下则需要按照 [主包页面不在 src/pages 目录下](/guide/quike/subpackage#主包页面不在-srcpages-目录下) 进行配置 12 | 13 | **是否配置分包** 14 | 15 | 请参考 [分包支持](/guide/quike/subpackage) 16 | 17 | ## 生成的路由方法跳转的路径是错误的 18 | 19 | 请检查页面文件名是否是 `index`,比如: `index.tsx / index.jsx / index.vue` 20 | 21 | ```typescript 22 | export default { 23 | pages: [ 24 | 'pages/user/index', // 支持 25 | 'pages/user/setting/index', // 不支持,非 pages 一级目录下 26 | 'pages/user/user', // 不支持,页面文件名不是 index 27 | ], 28 | subpackages: [ 29 | { 30 | root: 'packageA', 31 | pages: [ 32 | 'pages/cat/index', // 支持 33 | 'pages/animal/dog/index' // 不支持,非 packageA/pages 一级目录下 34 | 'pages/cat/cat' // 不支持,页面文件名不是 index 35 | ], 36 | } 37 | ], 38 | } 39 | 40 | ``` 41 | 42 | ## 开发模式下提示 toXXX 方法不存在 43 | 44 | Taro 3.5 及以上版本需要将 tarojs-router-next 从预编译中移除,否则后续的代码生成不会热更新到运行中的代码里。 45 | 46 | 请在 [编译配置(config/index.js)](https://taro-docs.jd.com/docs/config-detail#compilerprebundleexclude) 中将 tarojs-router-next 从预编译中排除: 47 | 48 | ```typescript 49 | const config = { 50 | compiler: { 51 | prebundle: { 52 | exclude: ['tarojs-router-next'], 53 | }, 54 | }, 55 | } 56 | ``` 57 | 58 | ## 小程序启动进入的第一个页面没有走路由中间件? 59 | 60 | 只有 Router.toXX 和 Router.navigate 方法会走路由中间件。 61 | 62 | 因此,一种最佳实践是:只提供一个入口页面,再在该入口页面通过入参判断引导到其他页面。 63 | 64 | 比如只提供一个入口页面为 pages/launch/index,当需要分享内容出去的时候: 65 | 66 | ```typescript 67 | // 分享用户详情页 68 | useShareAppMessage(() => ({ 69 | title: `${user.nickname}`, 70 | path: `pages/launch/index?type=user&id=${user.id}`, 71 | })) 72 | 73 | // 分享文章 74 | useShareAppMessage(() => ({ 75 | title: `${article.title}`, 76 | path: `pages/launch/index?type=article&id=${article.id}`, 77 | })) 78 | ``` 79 | 80 | 然后再在 launch 页通过 type 进行页面重定向: 81 | 82 | ```typescript 83 | const params = Router.getParams() 84 | 85 | if (params.type === 'user') { 86 | Router.toUser({ params, type: NavigateType.redirectTo }) 87 | } else if (params.type === 'article') { 88 | Router.toArticle({ params, type: NavigateType.redirectTo }) 89 | } else { 90 | Router.toHome({ type: NavigateType.redirectTo }) 91 | } 92 | ``` 93 | 94 | ## 关于路由方法生成 95 | 96 | 开发模式会为主包 `src/pages` 下的页面和各分包配置的 `pagePath` 下的页面生成路由方法,而生产模式仅为 `app.config.ts/js` 中注册的页面生成路由方法 97 | 98 | ## pages/xxx/index 页面不存在 99 | 100 | 出现这种情况一般是因为页面文件名非 `index.tsx / index.jsx / index.vue`,建议修改页面文件名为 `index` 101 | -------------------------------------------------------------------------------- /examples/react/config/index.js: -------------------------------------------------------------------------------- 1 | const isWsl = require('is-wsl') 2 | const path = require('path') 3 | 4 | const plugins = [ 5 | [ 6 | 'tarojs-router-next-plugin', 7 | { 8 | packages: [ 9 | // 可选,当主包页面路径不在 src/pages 下时通过以下方式配置 10 | // { 11 | // name: 'main', 12 | // pagePath: path.resolve(__dirname, '../src/pages'), 13 | // }, 14 | { 15 | name: 'packageA', 16 | pagePath: path.resolve(__dirname, '../src/packageA/pages'), 17 | }, 18 | ], 19 | }, 20 | ], 21 | ] 22 | 23 | if (isWsl) { 24 | plugins.push([ 25 | 'taro-plugin-sync-in-wsl', 26 | { 27 | weapp: [ 28 | { 29 | sourcePath: `dist`, 30 | outputPath: `/mnt/c/code/taro/tarojs-router-next-react`, 31 | }, 32 | ], 33 | }, 34 | ]) 35 | } 36 | 37 | const config = { 38 | projectName: 'react', 39 | date: '2021-4-20', 40 | designWidth: 750, 41 | deviceRatio: { 42 | 640: 2.34 / 2, 43 | 750: 1, 44 | 828: 1.81 / 2, 45 | }, 46 | sourceRoot: 'src', 47 | outputRoot: 'dist', 48 | plugins, 49 | defineConstants: {}, 50 | copy: { 51 | patterns: [], 52 | options: {}, 53 | }, 54 | framework: 'react', 55 | compiler: 'webpack5', 56 | cache: { 57 | enable: true, // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache 58 | }, 59 | mini: { 60 | postcss: { 61 | pxtransform: { 62 | enable: true, 63 | config: {}, 64 | }, 65 | url: { 66 | enable: true, 67 | config: { 68 | limit: 1024, // 设定转换尺寸上限 69 | }, 70 | }, 71 | cssModules: { 72 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 73 | config: { 74 | namingPattern: 'module', // 转换模式,取值为 global/module 75 | generateScopedName: '[name]__[local]___[hash:base64:5]', 76 | }, 77 | }, 78 | }, 79 | miniCssExtractPluginOption: { 80 | ignoreOrder: true, 81 | }, 82 | }, 83 | h5: { 84 | publicPath: '/', 85 | staticDirectory: 'static', 86 | postcss: { 87 | autoprefixer: { 88 | enable: true, 89 | config: {}, 90 | }, 91 | cssModules: { 92 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 93 | config: { 94 | namingPattern: 'module', // 转换模式,取值为 global/module 95 | generateScopedName: '[name]__[local]___[hash:base64:5]', 96 | }, 97 | }, 98 | }, 99 | }, 100 | } 101 | 102 | module.exports = function (merge) { 103 | if (process.env.NODE_ENV === 'development') { 104 | return merge({}, config, require('./dev')) 105 | } 106 | return merge({}, config, require('./prod')) 107 | } 108 | -------------------------------------------------------------------------------- /examples/vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "tarojs-router-next-vue3", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": true, 9 | "css": "none" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:jd": "taro build --type jd", 20 | "build:quickapp": "taro build --type quickapp", 21 | "dev:weapp": "npm run build:weapp -- --watch", 22 | "dev:swan": "npm run build:swan -- --watch", 23 | "dev:alipay": "npm run build:alipay -- --watch", 24 | "dev:tt": "npm run build:tt -- --watch", 25 | "dev:h5": "npm run build:h5 -- --watch", 26 | "dev:rn": "npm run build:rn -- --watch", 27 | "dev:qq": "npm run build:qq -- --watch", 28 | "dev:jd": "npm run build:jd -- --watch", 29 | "dev:quickapp": "npm run build:quickapp -- --watch", 30 | "router-gen": "taro router-gen --watch" 31 | }, 32 | "browserslist": [ 33 | "last 3 versions", 34 | "Android >= 4.1", 35 | "ios >= 8" 36 | ], 37 | "author": "", 38 | "dependencies": { 39 | "@babel/runtime": "^7.7.7", 40 | "@tarojs/components": "3.6.16", 41 | "@tarojs/helper": "3.6.16", 42 | "@tarojs/plugin-platform-weapp": "3.6.16", 43 | "@tarojs/plugin-platform-alipay": "3.6.16", 44 | "@tarojs/plugin-platform-tt": "3.6.16", 45 | "@tarojs/plugin-platform-swan": "3.6.16", 46 | "@tarojs/plugin-platform-jd": "3.6.16", 47 | "@tarojs/plugin-platform-qq": "3.6.16", 48 | "@tarojs/router": "3.6.16", 49 | "@tarojs/runtime": "3.6.16", 50 | "@tarojs/shared": "3.6.16", 51 | "@tarojs/taro": "3.6.16", 52 | "@tarojs/taro-h5": "3.6.16", 53 | "@tarojs/plugin-framework-vue3": "3.6.16", 54 | "tarojs-router-next": "workspace:*", 55 | "vue": "^3.0.0" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.8.0", 59 | "@tarojs/cli": "3.6.16", 60 | "@types/webpack-env": "^1.13.6", 61 | "webpack": "5.69.0", 62 | "@tarojs/webpack5-runner": "3.6.16", 63 | "babel-preset-taro": "3.6.16", 64 | "css-loader": "3.4.2", 65 | "style-loader": "1.3.0", 66 | "@vue/babel-plugin-jsx": "^1.0.6", 67 | "@vue/compiler-sfc": "^3.0.0", 68 | "vue-loader": "^16.0.0-beta.8", 69 | "eslint-plugin-vue": "^8.0.0", 70 | "eslint-config-taro": "3.6.16", 71 | "eslint": "^8.12.0", 72 | "is-wsl": "^2.2.0", 73 | "stylelint": "^14.4.0", 74 | "taro-plugin-sync-in-wsl": "^2.0.2", 75 | "@typescript-eslint/parser": "^5.20.0", 76 | "@typescript-eslint/eslint-plugin": "^5.20.0", 77 | "tarojs-router-next-plugin": "workspace:*", 78 | "typescript": "^4.1.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/README.md: -------------------------------------------------------------------------------- 1 | # tarojs-router-next 2 | 3 | 它是一个小巧的 [Taro(小程序)](https://github.com/nervjs/taro) 路由库,为你提供以下特性: 4 | 5 | - 自动生成带参数类型提示的路由方法 6 | - 允许传递任意类型、任意大小的参数数据 7 | - 同步的路由方法调用 8 | - koa 体验一致的路由中间件 9 | 10 | ## 快速开始 11 | 12 | [![](https://img.shields.io/npm/v/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 13 | [![](https://img.shields.io/npm/l/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 14 | [![](https://img.shields.io/npm/dt/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 15 | 16 | [使用文档](https://lblblong.github.io/tarojs-router-next/guide/quike/start),[API 文档](https://lblblong.github.io/tarojs-router-next/api/router) 17 | 18 | [Demo(代码)](https://github.com/lblblong/tarojs-router-next/tree/master/examples),[Demo(微信开发者工具打开)](https://developers.weixin.qq.com/s/2CcFkJmo7Dpb) 19 | 20 | #### 安装核心依赖 21 | 22 | ```shell 23 | $ npm install --save tarojs-router-next 24 | ``` 25 | 26 | #### 安装路由方法自动生成插件 27 | 28 | ```shell 29 | $ npm install --dev tarojs-router-next-plugin 30 | ``` 31 | 32 | 在 [编译配置(/config/index.js)](https://taro-docs.jd.com/docs/config-detail#plugins) 的 plugins 字段中引入插件: 33 | 34 | ```typescript 35 | const config = { 36 | plugins: ['tarojs-router-next-plugin'], 37 | } 38 | ``` 39 | 40 | ## 解决什么问题 41 | 42 | 1. 路由跳转的页面 url 没有类型提示容易输错 43 | 2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据 44 | 3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常 45 | 4. 路由跳转的鉴权等实现起来比较麻烦 46 | 47 | ## 如何解决 48 | 49 | **1. 路由跳转的页面 url 没有类型提示容易输错** 50 | 51 | tarojs-router-next 不需要使用者手写页面 url,它会监听项目 `src/pages` 内容变化,自动为使用者生成对应的路由方法并附加到 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上,比如以下列子: 52 | 53 | 左边的页面结构会生成右边的 [Router.to\*\*](https://lblblong.github.io/tarojs-router-next/api/router#to-options-) 系列方法,全都挂在 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上 54 | 55 | ![](https://lblblong.github.io/tarojs-router-next/images/code1.png) 56 | 57 | **2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据** 58 | 59 | tarojs-router-next 允许直接传递一个对象给 `params`,它会把 `params` 展开拼接到 `url` 后面。并且还可以接收一个 `data` 参数,`data` 可以传递任意类型、任意大小的数据。 60 | 61 | ![](https://lblblong.github.io/tarojs-router-next/images/code2.gif) 62 | 63 | **3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常** 64 | 65 | tarojs-router-next 的路由跳转会返回一个 `Promise`,可以用 `async/await` 写出同步代码,详细参考 [同步的路由方法](https://lblblong.github.io/tarojs-router-next/guide/quike/sync-router) 66 | 67 | **4. 路由跳转的鉴权等实现起来比较麻烦** 68 | 69 | 自己实现路由的鉴权是比较麻烦的事情,而 tarojs-router-next 提供非常易于理解的路由中间件功能,详细参考 [路由中间件](https://lblblong.github.io/tarojs-router-next/guide/quike/middleware) 70 | 71 | ## 平台与框架支持 72 | 73 | #### 框架 74 | 75 | 支持所有 `Taro` 可支持的框架(`React`、`Vue`、`Vue3`、`Nerv`) 76 | 77 | #### 小程序 78 | 79 | 理论上支持所有 `Taro` 可支持的小程序平台,目前已在 `微信小程序`、`QQ小程序`、`支付宝小程序` 测试通过 80 | 81 | #### H5 82 | 83 | 支持 84 | 85 | #### React Native 86 | 87 | 暂不支持 88 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/README.md: -------------------------------------------------------------------------------- 1 | # tarojs-router-next 2 | 3 | 它是一个小巧的 [Taro(小程序)](https://github.com/nervjs/taro) 路由库,为你提供以下特性: 4 | 5 | - 自动生成带参数类型提示的路由方法 6 | - 允许传递任意类型、任意大小的参数数据 7 | - 同步的路由方法调用 8 | - koa 体验一致的路由中间件 9 | 10 | ## 快速开始 11 | 12 | [![](https://img.shields.io/npm/v/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 13 | [![](https://img.shields.io/npm/l/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 14 | [![](https://img.shields.io/npm/dt/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 15 | 16 | [使用文档](https://lblblong.github.io/tarojs-router-next/guide/quike/start),[API 文档](https://lblblong.github.io/tarojs-router-next/api/router) 17 | 18 | [Demo(代码)](https://github.com/lblblong/tarojs-router-next/tree/master/examples),[Demo(微信开发者工具打开)](https://developers.weixin.qq.com/s/2CcFkJmo7Dpb) 19 | 20 | #### 安装核心依赖 21 | 22 | ```shell 23 | $ npm install --save tarojs-router-next 24 | ``` 25 | 26 | #### 安装路由方法自动生成插件 27 | 28 | ```shell 29 | $ npm install --dev tarojs-router-next-plugin 30 | ``` 31 | 32 | 在 [编译配置(/config/index.js)](https://taro-docs.jd.com/docs/config-detail#plugins) 的 plugins 字段中引入插件: 33 | 34 | ```typescript 35 | const config = { 36 | plugins: ['tarojs-router-next-plugin'], 37 | } 38 | ``` 39 | 40 | ## 解决什么问题 41 | 42 | 1. 路由跳转的页面 url 没有类型提示容易输错 43 | 2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据 44 | 3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常 45 | 4. 路由跳转的鉴权等实现起来比较麻烦 46 | 47 | ## 如何解决 48 | 49 | **1. 路由跳转的页面 url 没有类型提示容易输错** 50 | 51 | tarojs-router-next 不需要使用者手写页面 url,它会监听项目 `src/pages` 内容变化,自动为使用者生成对应的路由方法并附加到 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上,比如以下列子: 52 | 53 | 左边的页面结构会生成右边的 [Router.to\*\*](https://lblblong.github.io/tarojs-router-next/api/router#to-options-) 系列方法,全都挂在 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上 54 | 55 | ![](https://lblblong.github.io/tarojs-router-next/images/code1.png) 56 | 57 | **2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据** 58 | 59 | tarojs-router-next 允许直接传递一个对象给 `params`,它会把 `params` 展开拼接到 `url` 后面。并且还可以接收一个 `data` 参数,`data` 可以传递任意类型、任意大小的数据。 60 | 61 | ![](https://lblblong.github.io/tarojs-router-next/images/code2.gif) 62 | 63 | **3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常** 64 | 65 | tarojs-router-next 的路由跳转会返回一个 `Promise`,可以用 `async/await` 写出同步代码,详细参考 [同步的路由方法](https://lblblong.github.io/tarojs-router-next/guide/quike/sync-router) 66 | 67 | **4. 路由跳转的鉴权等实现起来比较麻烦** 68 | 69 | 自己实现路由的鉴权是比较麻烦的事情,而 tarojs-router-next 提供非常易于理解的路由中间件功能,详细参考 [路由中间件](https://lblblong.github.io/tarojs-router-next/guide/quike/middleware) 70 | 71 | ## 平台与框架支持 72 | 73 | #### 框架 74 | 75 | 支持所有 `Taro` 可支持的框架(`React`、`Vue`、`Vue3`、`Nerv`) 76 | 77 | #### 小程序 78 | 79 | 理论上支持所有 `Taro` 可支持的小程序平台,目前已在 `微信小程序`、`QQ小程序`、`支付宝小程序` 测试通过 80 | 81 | #### H5 82 | 83 | 支持 84 | 85 | #### React Native 86 | 87 | 暂不支持 88 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "tarojs-router-next-react", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": true, 9 | "css": "none" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:jd": "taro build --type jd", 20 | "build:quickapp": "taro build --type quickapp", 21 | "dev:weapp": "npm run build:weapp -- --watch", 22 | "dev:swan": "npm run build:swan -- --watch", 23 | "dev:alipay": "npm run build:alipay -- --watch", 24 | "dev:tt": "npm run build:tt -- --watch", 25 | "dev:h5": "npm run build:h5 -- --watch", 26 | "dev:rn": "npm run build:rn -- --watch", 27 | "dev:qq": "npm run build:qq -- --watch", 28 | "dev:jd": "npm run build:jd -- --watch", 29 | "dev:quickapp": "npm run build:quickapp -- --watch", 30 | "router-gen": "taro router-gen" 31 | }, 32 | "browserslist": [ 33 | "last 3 versions", 34 | "Android >= 4.1", 35 | "ios >= 8" 36 | ], 37 | "author": "", 38 | "dependencies": { 39 | "@babel/runtime": "^7.7.7", 40 | "@tarojs/components": "3.6.16", 41 | "@tarojs/helper": "3.6.16", 42 | "@tarojs/plugin-framework-react": "3.6.16", 43 | "@tarojs/plugin-platform-alipay": "3.6.16", 44 | "@tarojs/plugin-platform-jd": "3.6.16", 45 | "@tarojs/plugin-platform-qq": "3.6.16", 46 | "@tarojs/plugin-platform-swan": "3.6.16", 47 | "@tarojs/plugin-platform-tt": "3.6.16", 48 | "@tarojs/plugin-platform-weapp": "3.6.16", 49 | "@tarojs/react": "3.6.16", 50 | "@tarojs/router": "3.6.16", 51 | "@tarojs/runtime": "3.6.16", 52 | "@tarojs/shared": "3.6.16", 53 | "@tarojs/taro": "3.6.16", 54 | "@tarojs/taro-h5": "3.6.16", 55 | "react": "^17.0.0", 56 | "react-dom": "^17.0.0", 57 | "tarojs-router-next": "workspace:*" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.8.0", 61 | "@pmmmwh/react-refresh-webpack-plugin": "0.5.5", 62 | "@tarojs/cli": "3.6.16", 63 | "@tarojs/webpack5-runner": "3.6.16", 64 | "@types/react": "^17.0.2", 65 | "@types/webpack-env": "^1.13.6", 66 | "@typescript-eslint/eslint-plugin": "^4.15.1", 67 | "@typescript-eslint/parser": "^4.15.1", 68 | "babel-preset-taro": "3.6.16", 69 | "eslint": "^6.8.0", 70 | "eslint-config-taro": "3.6.16", 71 | "eslint-plugin-import": "^2.12.0", 72 | "eslint-plugin-react": "^7.8.2", 73 | "eslint-plugin-react-hooks": "^4.2.0", 74 | "is-wsl": "^2.2.0", 75 | "stylelint": "9.3.0", 76 | "taro-plugin-sync-in-wsl": "^2.0.2", 77 | "tarojs-router-next-plugin": "workspace:*", 78 | "typescript": "^4.5.4", 79 | "webpack": "5.69.0", 80 | "webpack-bundle-analyzer": "^4.5.0" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tarojs-router-next 2 | 3 | [![](https://img.shields.io/npm/v/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 4 | [![](https://img.shields.io/npm/l/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 5 | [![](https://img.shields.io/npm/dt/tarojs-router-next.svg?style=flat-square)](https://www.npmjs.com/package/tarojs-router-next) 6 | 7 | 它是一个小巧的 [Taro(小程序)](https://github.com/nervjs/taro) 路由库,为你提供以下特性: 8 | 9 | - 自动生成带参数类型提示的路由方法 10 | - 允许传递任意类型、任意大小的参数数据 11 | - 同步的路由方法调用 12 | - koa 体验一致的路由中间件 13 | 14 | ## 快速开始 15 | 16 | [使用文档](https://lblblong.github.io/tarojs-router-next/guide/quike/start),[API 文档](https://lblblong.github.io/tarojs-router-next/api/router) 17 | 18 | [Demo(代码)](https://github.com/lblblong/tarojs-router-next/tree/master/examples),[Demo(微信开发者工具打开)](https://developers.weixin.qq.com/s/2CcFkJmo7Dpb) 19 | 20 | #### 安装核心依赖 21 | 22 | ```shell 23 | $ npm install --save tarojs-router-next 24 | ``` 25 | 26 | #### 安装路由方法自动生成插件 27 | 28 | ```shell 29 | $ npm install --dev tarojs-router-next-plugin 30 | ``` 31 | 32 | 在 [编译配置(/config/index.js)](https://taro-docs.jd.com/docs/config-detail#plugins) 的 plugins 字段中引入插件: 33 | 34 | ```typescript 35 | const config = { 36 | plugins: ['tarojs-router-next-plugin'], 37 | } 38 | ``` 39 | 40 | ## 解决什么问题 41 | 42 | 1. 路由跳转的页面 url 没有类型提示容易输错 43 | 2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据 44 | 3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常 45 | 4. 路由跳转的鉴权等实现起来比较麻烦 46 | 47 | ## 如何解决 48 | 49 | **1. 路由跳转的页面 url 没有类型提示容易输错** 50 | 51 | tarojs-router-next 不需要使用者手写页面 url,它会监听项目 `src/pages` 内容变化,自动为使用者生成对应的路由方法并附加到 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上,比如以下列子: 52 | 53 | 左边的页面结构会生成右边的 [Router.to\*\*](https://lblblong.github.io/tarojs-router-next/api/router#to-options-) 系列方法,全都挂在 [Router](https://lblblong.github.io/tarojs-router-next/api/router) 类上 54 | 55 | ![](https://lblblong.github.io/tarojs-router-next/images/code1.png) 56 | 57 | **2. 路由传参需要手动拼接参数、无法携带任意类型、任意大小的数据** 58 | 59 | tarojs-router-next 允许直接传递一个对象给 `params`,它会把 `params` 展开拼接到 `url` 后面。并且还可以接收一个 `data` 参数,`data` 可以传递任意类型、任意大小的数据。 60 | 61 | ![](https://lblblong.github.io/tarojs-router-next/images/code2.gif) 62 | 63 | **3. 路由方法是异步的,页面通过 `EventChannel` 通信,事件的回调方法可读性差、耦合度高、只能在回调内部处理异常** 64 | 65 | tarojs-router-next 的路由跳转会返回一个 `Promise`,可以用 `async/await` 写出同步代码,详细参考 [同步的路由方法](https://lblblong.github.io/tarojs-router-next/guide/quike/sync-router) 66 | 67 | **4. 路由跳转的鉴权等实现起来比较麻烦** 68 | 69 | 自己实现路由的鉴权是比较麻烦的事情,而 tarojs-router-next 提供非常易于理解的路由中间件功能,详细参考 [路由中间件](https://lblblong.github.io/tarojs-router-next/guide/quike/middleware) 70 | 71 | ## 平台与框架支持 72 | 73 | #### 框架 74 | 75 | 支持所有 `Taro` 可支持的框架(`React`、`Vue`、`Vue3`、`Nerv`) 76 | 77 | #### 小程序 78 | 79 | 理论上支持所有 `Taro` 可支持的小程序平台,目前已在 `微信小程序`、`QQ小程序`、`支付宝小程序` 测试通过 80 | 81 | #### H5 82 | 83 | 支持 84 | 85 | #### React Native 86 | 87 | 暂不支持 88 | 89 | ## 友情推荐 90 | 91 | #### Taroify 92 | 93 | 地址:https://github.com/mallfoundry/taroify 94 | 95 | Taroify 是移动端组件库 Vant 的 Taro 版本,两者基于相同的视觉规范,提供一致的 API 接口,助力开发者快速搭建小程序应用。 96 | 97 | -------------------------------------------------------------------------------- /examples/react/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, View } from '@tarojs/components' 2 | import Taro from '@tarojs/taro' 3 | import React, { FC, useEffect } from 'react' 4 | import { Router } from 'tarojs-router-next' 5 | import './index.css' 6 | 7 | const data = { 8 | users: [ 9 | { 10 | id: 1, 11 | name: '灰灰', 12 | sex: 'boy' as const, 13 | }, 14 | { 15 | id: 2, 16 | name: '白白', 17 | sex: 'girl' as const, 18 | }, 19 | ], 20 | } 21 | 22 | const parmas = { 23 | id: 1, 24 | name: '白白', 25 | sex: 'boy' as const, 26 | } 27 | 28 | const Index: FC = () => { 29 | useEffect(() => { 30 | console.log('使用文档:https://lblblong.github.io/tarojs-router-next/guide') 31 | console.log('API文档:https://lblblong.github.io/tarojs-router-next/api/class/router') 32 | console.log('Demo 源代码:https://github.com/lblblong/tarojs-router-next/tree/master/examples') 33 | }, []) 34 | 35 | const onSelCity = async () => { 36 | try { 37 | const res = await Router.toSelCity() 38 | Taro.showModal({ 39 | title: '数据', 40 | content: JSON.stringify(res), 41 | }) 42 | } catch (err) { 43 | console.log(err) 44 | Taro.showModal({ 45 | title: '提示', 46 | content: '用户取消选择', 47 | }) 48 | } 49 | } 50 | 51 | const onClassDemo = async () => { 52 | try { 53 | const res = await Router.toClassDemo({ 54 | params: parmas, 55 | data: data, 56 | }) 57 | Taro.showModal({ 58 | title: '数据', 59 | content: JSON.stringify(res), 60 | }) 61 | } catch (err) { 62 | Taro.showModal({ 63 | title: '提示', 64 | content: '抛出了异常', 65 | }) 66 | } 67 | } 68 | 69 | return ( 70 | 71 | 页面跳转传参、数据 72 | 75 | 85 | 95 | 106 | 107 | 路由中间件 108 | 116 | Class页面 117 | 120 | 121 | 分包路由 122 | 132 | 133 | ) 134 | } 135 | 136 | export default Index 137 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { processTypeEnum } from '@tarojs/helper' 2 | import { IPluginContext } from '@tarojs/service' 3 | import { IPaths } from '@tarojs/service/src/utils/types' 4 | import path from 'path' 5 | import { IConfig, IConfigPackage } from './config' 6 | import { Page } from './entitys' 7 | import { Generator } from './generator' 8 | import { Loader } from './loader' 9 | 10 | export class Plugin { 11 | loader: Loader 12 | generator: Generator 13 | isWatch: boolean 14 | paths: IPaths 15 | helper: typeof import('@tarojs/helper') 16 | 17 | pages: Page[] = [] 18 | 19 | constructor(public readonly ctx: IPluginContext, public config: IConfig) { 20 | this.helper = this.ctx.helper 21 | this.paths = this.ctx.paths 22 | 23 | this.config.packages = this.config.packages ?? [] 24 | if (this.config.packages.findIndex(pkg => pkg.name === 'main') === -1) { 25 | this.config.packages.push({ 26 | name: 'main', 27 | pagePath: path.resolve(this.paths.sourcePath, 'pages') 28 | }) 29 | } 30 | this.config.ignore = this.config.ignore ?? ['.DS_Store'] 31 | 32 | this.isWatch = !!(this.ctx.runOpts.options.isWatch || this.ctx.runOpts.options.watch) 33 | this.loader = new Loader(this) 34 | this.generator = new Generator(this) 35 | } 36 | 37 | onBuildStart() { 38 | this.ctx.onBuildStart(() => this.start()) 39 | return this 40 | } 41 | 42 | registerCommand() { 43 | const { ctx } = this 44 | ctx.registerCommand({ 45 | name: 'router-gen', 46 | optionsMap: { 47 | '--watch': '监听页面信息变化自动生成 Router' 48 | }, 49 | synopsisList: ['taro router-gen 生成 Router', 'taro router-gen --watch 监听页面信息变化自动生成 Router'], 50 | fn: () => this.start() 51 | }) 52 | return this 53 | } 54 | 55 | watch() { 56 | const { ctx } = this 57 | this.log(processTypeEnum.REMIND, '正在监听页面变化自动生成 Router.to...') 58 | const loadPge = (pageDirPath: string, pkg: IConfigPackage) => { 59 | if (this.loader.loadPage(pageDirPath, pkg)) this.generator.emit() 60 | } 61 | 62 | for (const pkg of this.config.packages) { 63 | const onChange = (value: string) => { 64 | if (value.endsWith('route.config.ts')) value = value.replace(`${path.sep}route.config.ts`, '') 65 | loadPge(value, pkg) 66 | } 67 | 68 | ctx.helper.chokidar 69 | .watch(pkg.pagePath, { ignoreInitial: true, depth: 0 }) 70 | .on('addDir', onChange) 71 | .on('unlinkDir', onChange) 72 | 73 | ctx.helper.chokidar 74 | .watch(path.resolve(pkg.pagePath, '**/route.config.ts'), { 75 | ignoreInitial: true, 76 | depth: 1 77 | }) 78 | .on('add', onChange) 79 | .on('change', onChange) 80 | .on('unlink', onChange) 81 | } 82 | } 83 | 84 | start() { 85 | try { 86 | this.log(processTypeEnum.START, '正在生成路由方法...') 87 | this.loader.loadPages() 88 | this.generator.emit(true) 89 | if (this.isWatch) this.watch() 90 | } catch (err) { 91 | this.log( 92 | processTypeEnum.ERROR, 93 | '路由方法生成失败,请将以下错误反馈给我:https://github.com/lblblong/tarojs-router-next/issues' 94 | ) 95 | console.log(err) 96 | } 97 | } 98 | 99 | log(type: processTypeEnum, text: string) { 100 | this.ctx.helper.printLog(type, text) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /docs/api/router.md: -------------------------------------------------------------------------------- 1 | # Router 2 | 3 | `Router` 类是 tarojs-router-next 的核心,该类提供各种导航的静态方法,以及获取路由参数的方法。 4 | 5 | 默认情况下 tarojs-router-next 会监听扫描 `src/pages` 下的文件变化自动为 `Router` 类生成 `to` 开头的静态路由方法,避免使用者手动编写导航方法。 6 | 7 | ## getParams( ) 8 | 9 | 获取上一个页面传递过来的路由参数 10 | 11 | ## getData( default_value? ) 12 | 13 | 获取上一个页面传递过来的数据 14 | 15 | 参数: 16 | 17 | 1. `default_value` 默认值,`any` 类型,当没有传入数据时返回这个默认值 18 | 19 | ## to\*\*( options ) 20 | 21 | 以 to 开头的方法都是根据项目的 `src/pages` 下的文件夹和 [route.config.ts](/guide/quike/route-config) 文件自动生成的,比如 `src/pages/me` 文件夹会为 `Router` 类生成静态方法 `Router.toMe` 22 | 23 | ```typescript 24 | import { Router, NavigateType } from 'tarojs-router-next' 25 | 26 | // 跳转到 /pages/me/index 页面 27 | Router.toMe() 28 | // 重定向到 /pages/me/index 页面 29 | Router.toMe({ type: NavigateType.redirectTo }) 30 | // 跳转到 /pages/login/index 页面并传递参数 31 | Router.toLogin({ params: { username: 'taro' } }) 32 | // 跳转到 /pages/article-edit/index 页面并传递数据 33 | Router.toArticleEdit({ data: { title: 'taro', content: '小程序框架' } }) 34 | ``` 35 | 36 | 参数: 37 | 38 | 1. `options` 跳转选项 39 | - `options.params` 传递参数,默认 `{ [key: string]: string | number | boolean | undefined }` 类型,可以通过页面下 [route.config.ts](/guide/quike/route-config) 配置类型 40 | - `options.data` 传递数据,默认 `any` 类型,可以通过页面下 [route.config.ts](/guide/quike/route-config) 配置类型 41 | - `options.type` 跳转类型,[NavigateType](/api/other#navigatetype) 类型 42 | 43 | ## navigate( route, options? ) 44 | 45 | 导航到应用内的某个页面,一般不会直接使用,因为 tarojs-router-next 默认会为应用的每个页面生成对应的 to\*\* 的跳转方法。 46 | 47 | ```typescript 48 | import { Router, NavigateType } from 'tarojs-router-next' 49 | 50 | // 不带参跳转 51 | Router.navigate({ url: '/pages/login/index' }) 52 | // 带参跳转 53 | Router.navigate({ url: '/pages/login/index' }, { params: { username: 'router' } }) 54 | // 关闭当前页面,重定向到 login 页面 55 | Router.navigate({ url: '/pages/login/index' }, { type: NavigateType.redirectTo }) 56 | ``` 57 | 58 | 参数: 59 | 60 | 1. `route` 目标页面选项 61 | - `route.url` 目标页面的 url ,`string` 类型 62 | - `route.ext` 附加数据,该数据是给路由中间件访问的,类似 vue-router 的 [路由元信息](https://router.vuejs.org/zh/guide/advanced/meta.html) 63 | 2. `options` 跳转选项 64 | - `options.params` 传递参数,`{ [key: string]: string | number | boolean | undefined }` 类型 65 | - `options.data` 传递数据,`any` 类型 66 | - `options.type` 跳转类型,[NavigateType](/api/other#navigatetype) 类型 67 | 68 | ## back( result?, options? ) 69 | 70 | 返回到上一个页面 71 | 72 | 该方法可以返回数据到前一个页面,也可抛出异常到前一个页面 73 | 74 | ```typescript 75 | Router.back() // 返回上一个页面,此时上一个页面拿到的是 null 76 | Router.back({ id: 1, name: '深圳' }) // 返回上一个页面并返回城市数据 77 | Router.back(new Error('用户取消选择')) // 返回上一个页面并抛出异常 78 | ``` 79 | 80 | 而在上一个页面获取返回的数据只需要 `await` 即可 81 | 82 | ```typescript 83 | try { 84 | const result = await Router.navigate({ url: '/pages/sel-city/index' }) 85 | console.log('选择城市:', result) 86 | } catch (err) { 87 | console.log(err.message) 88 | } 89 | ``` 90 | 91 | 返回多级页面: 92 | 93 | ```typescript 94 | Router.back(undefined, { delta: 3 }) // 向上返回 3 级页面 95 | Router.back({ id: 1, name: '深圳' }, { delta: 3 }) // 向上返回 3 级页面,注意,这里的 result 是返回到上一个页面的 96 | ``` 97 | 98 | 参数: 99 | 100 | 1. `result` 返回的数据,可以为任意类型,但当 `result` 是 `Error` 的实例时,则是往上一个页面抛出异常 101 | 2. `options.delta` 返回的页面数,如果 delta 大于现有页面数,则返回到首页 102 | 103 | ## setBackResult( result: any ) 104 | 105 | 设置要返回到上一个页面的数据,适用于非 `Router.back` 方法触发页面回退的场景,通过这个方法设置返回数据后,用户点击物理按键返回也会带数据到上一个页面 106 | 107 | ```typescript 108 | Router.setBackResult({ id: 1, name: '深圳' }) // 设置返回到上一个页面的数据 109 | Router.setBackResult(new Error('用户取消选择')) // 当数据是 Error 实例时,则是抛出一个异常到上一个页面 110 | ``` 111 | 112 | 该方法与 Router.back 方法混用时注意以下情况: 113 | 114 | ```typescript 115 | Router.setBackResult(123) 116 | Router.back() // 虽然 back 方法没有带参,但是也会返回 123 到上一个页面 117 | 118 | Router.back(234) // 带参之后则会覆盖上面设置的值,返回数据是 234 119 | ``` 120 | -------------------------------------------------------------------------------- /.dumirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi' 2 | 3 | export default defineConfig({ 4 | logo: '/tarojs-router-next/logo.png', 5 | favicons: ['/tarojs-router-next/favicon.ico'], 6 | exportStatic: {}, 7 | base: '/tarojs-router-next', 8 | publicPath: '/tarojs-router-next/', 9 | themeConfig: { 10 | sidebar: { 11 | '/guide': [ 12 | { 13 | title: '快速开始', 14 | children: [ 15 | { 16 | title: '介绍', 17 | link: '/guide', 18 | }, 19 | { 20 | title: '安装及使用', 21 | link: '/guide/quike/start', 22 | }, 23 | { 24 | title: '路由跳转', 25 | link: '/guide/quike/navigate', 26 | }, 27 | { 28 | title: '路由传参', 29 | link: '/guide/quike/params', 30 | }, 31 | { 32 | title: '同步的路由方法', 33 | link: '/guide/quike/sync-router', 34 | }, 35 | { 36 | title: '路由回退监听', 37 | link: '/guide/quike/router-back-listener', 38 | }, 39 | { 40 | title: '路由中间件', 41 | link: '/guide/quike/middleware', 42 | }, 43 | { 44 | title: '路由配置', 45 | link: '/guide/quike/route-config', 46 | }, 47 | { 48 | title: '分包支持', 49 | link: '/guide/quike/subpackage', 50 | }, 51 | { 52 | title: '常见问题', 53 | link: '/guide/quike/qa', 54 | }, 55 | { 56 | title: '插件配置', 57 | link: '/guide/quike/config', 58 | }, 59 | ], 60 | }, 61 | { 62 | title: '场景案例', 63 | children: [ 64 | { 65 | title: '路由权限中间件', 66 | link: '/guide/scenes/middleware-auth', 67 | }, 68 | { 69 | title: '跨页面取值', 70 | link: '/guide/scenes/value-across-page', 71 | }, 72 | ], 73 | }, 74 | ], 75 | '/api': [ 76 | { 77 | title: '类', 78 | children: [ 79 | { 80 | title: 'Router', 81 | link: '/api/router', 82 | }, 83 | ], 84 | }, 85 | { 86 | title: '方法', 87 | children: [ 88 | { 89 | title: 'registerMiddleware', 90 | link: '/api/register-middleware', 91 | }, 92 | { 93 | title: 'registerMiddlewares', 94 | link: '/api/register-middlewares', 95 | }, 96 | { 97 | title: 'registerRouterBackListener', 98 | link: '/api/register-router-back-listener', 99 | }, 100 | ], 101 | }, 102 | { 103 | title: '其他', 104 | children: [ 105 | { 106 | title: '类型', 107 | link: '/api/other', 108 | }, 109 | ], 110 | }, 111 | ], 112 | }, 113 | nav: [ 114 | { 115 | title: '使用指南', 116 | link: '/guide', 117 | }, 118 | { 119 | title: 'API文档', 120 | link: '/api/router', 121 | }, 122 | { 123 | title: '(示例项目)', 124 | children: [ 125 | { title: '查看代码', link: 'https://github.com/lblblong/tarojs-router-next/tree/master/examples' }, 126 | { title: '开发者工具打开', link: 'https://developers.weixin.qq.com/s/2CcFkJmo7Dpb' }, 127 | ], 128 | }, 129 | { 130 | title: 'GitHub', 131 | link: 'https://github.com/lblblong/tarojs-router-next', 132 | }, 133 | ], 134 | }, 135 | analytics: { 136 | baidu: '86dd7440f66d97b070760cdd2d2b1312', 137 | }, 138 | }) 139 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/generator.ts: -------------------------------------------------------------------------------- 1 | import { processTypeEnum } from '@tarojs/helper/dist/constants' 2 | import * as path from 'path' 3 | import { Project, SourceFile } from 'ts-morph' 4 | import { Page } from './entitys' 5 | import { Plugin } from './plugin' 6 | import * as fs from 'fs' 7 | 8 | export class Generator { 9 | project: Project 10 | routerSourceFile: SourceFile 11 | targetModulePath: string 12 | 13 | constructor(private readonly root: Plugin) { 14 | this.targetModulePath = path.resolve(this.root.ctx.paths.nodeModulesPath, 'tarojs-router-next') 15 | const tsConfigFilePath = path.resolve(this.targetModulePath, 'tsconfig.json') 16 | this.project = new Project({ 17 | tsConfigFilePath, 18 | }) 19 | this.routerSourceFile = this.project.addSourceFileAtPath( 20 | path.resolve(this.targetModulePath, './src/router/index.ts') 21 | ) 22 | } 23 | 24 | emitTimer: NodeJS.Timeout 25 | 26 | emit(force = false) { 27 | clearTimeout(this.emitTimer) 28 | const _emit = () => { 29 | fs.rmSync(path.resolve(this.targetModulePath, './dist/router/index.js'), { recursive: true, force: true }) 30 | fs.rmSync(path.resolve(this.targetModulePath, './dist/router/index.js.map'), { recursive: true, force: true }) 31 | fs.rmSync(path.resolve(this.targetModulePath, './dist/router/index.d.ts'), { recursive: true, force: true }) 32 | 33 | this.routerSourceFile.refreshFromFileSystemSync() 34 | 35 | const tempSourceFile = this.project.createSourceFile('temp.ts', (writer) => { 36 | writer.writeLine('type NoInfer = T extends infer U ? U : never;') 37 | writer.writeLine('type RequiredKeys = { [K in keyof T]-?: {} extends Pick ? never : K }[keyof T]') 38 | writer.writeLine('type Data = RequiredKeys extends never ? { data?: Q } : { data: Q }') 39 | writer.writeLine('type Params

= RequiredKeys

extends never ? { params?: P } : { params: P }') 40 | writer.writeLine('class Router {') 41 | 42 | writer.write(this.generateMethods()) 43 | 44 | writer.writeLine('}') 45 | }) 46 | 47 | const routerClass = this.routerSourceFile.getClass('Router')! 48 | const staticMembers = tempSourceFile.getClass('Router')!.getStaticMembers() 49 | this.routerSourceFile.addTypeAliases(tempSourceFile.getTypeAliases().map((m) => m.getStructure())) 50 | routerClass.addMembers(staticMembers.map((m) => m.getStructure() as any)) 51 | 52 | this.routerSourceFile.emitSync() 53 | tempSourceFile.delete() 54 | this.root.log(processTypeEnum.REMIND, '👋 已成功生成') 55 | } 56 | 57 | if (force) { 58 | _emit() 59 | } else { 60 | this.emitTimer = setTimeout(_emit, 300) 61 | } 62 | } 63 | 64 | generateMethods() { 65 | let methodText = '' 66 | let packages = this.root.pages.reduce((store, page) => { 67 | let pages = store.get(page.packageName) 68 | if (!pages) { 69 | pages = [] 70 | store.set(page.packageName, pages) 71 | } 72 | pages.push(page) 73 | return store 74 | }, new Map()) 75 | 76 | for (const packageName of packages.keys()) { 77 | const pages = packages.get(packageName) 78 | if (packageName === 'main') { 79 | methodText += pages 80 | ?.map((page) => { 81 | return `static ${page.method?.name}: ${page.method?.type} = ${page.method?.value}` 82 | }) 83 | .join('\n\n') 84 | } else { 85 | methodText += ` 86 | static ${packageName}: { 87 | ${pages 88 | ?.map((page) => { 89 | return `${page.method?.name}: ${page.method?.type}` 90 | }) 91 | .join(';\n')} 92 | } = { 93 | ${pages 94 | ?.map((page) => { 95 | return `${page.method?.name}: ${page.method?.value}` 96 | }) 97 | .join(',\n')} 98 | } 99 | ` 100 | } 101 | } 102 | 103 | return methodText 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /packages/tarojs-router-next/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Taro, { Current, getCurrentInstance } from '@tarojs/taro' 2 | import { ROUTE_KEY } from '../constants' 3 | import { NoPageException } from '../exception/no-page' 4 | import { formatPath, isNil } from '../func' 5 | import { execMiddlewares, getMiddlewares } from '../middleware' 6 | import { PageData } from '../page-data' 7 | import { execRouterBackListener } from '../router-back-listener' 8 | import { NavigateOptions, NavigateType, Route } from './type' 9 | 10 | export { NavigateOptions, NavigateType, Route } from './type' 11 | 12 | export class Router { 13 | /** 14 | * 页面跳转 15 | * @param route 目标路由对象 16 | * @param options 跳转选项 17 | */ 18 | static async navigate(route: Route, options?: NavigateOptions): Promise { 19 | options = { ...{ type: NavigateType.navigateTo, params: {} }, ...options } 20 | options.params = Object.assign({}, options.params) 21 | const route_key = Date.now() + '' 22 | 23 | Current['_page'] = Current.page 24 | Object.defineProperties(Current, { 25 | page: { 26 | set: function (page) { 27 | if (page === undefined || page === null) { 28 | this._page = page 29 | return 30 | } 31 | if (!page[ROUTE_KEY]) { 32 | const originOnUnload = page.onUnload 33 | page.onUnload = function () { 34 | originOnUnload && originOnUnload.apply(this) 35 | PageData.emitBack(route_key) 36 | setTimeout(() => execRouterBackListener(route)) 37 | } 38 | page[ROUTE_KEY] = route_key 39 | } 40 | this._page = page 41 | }, 42 | get: function () { 43 | return this._page 44 | }, 45 | }, 46 | }) 47 | 48 | if (options.data) { 49 | PageData.setPageData(route_key, options.data) 50 | } 51 | 52 | const context = { route, type: options.type!, params: options.params, data: options.data } 53 | 54 | const middlewares = getMiddlewares(context) 55 | const url = formatPath(route, options!.params!) 56 | middlewares.push(async (ctx, next) => { 57 | switch (options!.type) { 58 | case NavigateType.reLaunch: 59 | await Taro.reLaunch({ 60 | url, 61 | complete: options?.complete, 62 | fail: options?.fail, 63 | success: options?.success, 64 | }) 65 | break 66 | case NavigateType.redirectTo: 67 | await Taro.redirectTo({ 68 | url, 69 | complete: options?.complete, 70 | fail: options?.fail, 71 | success: options?.success, 72 | }) 73 | break 74 | case NavigateType.switchTab: 75 | await Taro.switchTab({ 76 | url, 77 | complete: options?.complete, 78 | fail: options?.fail, 79 | success: options?.success, 80 | }) 81 | break 82 | default: 83 | await Taro.navigateTo({ 84 | url, 85 | complete: options?.complete, 86 | fail: options?.fail, 87 | success: options?.success, 88 | }) 89 | break 90 | } 91 | next() 92 | }) 93 | 94 | return new Promise(async (res, rej) => { 95 | try { 96 | PageData.setPagePromise(route_key, { res, rej }) 97 | await execMiddlewares(middlewares, context) 98 | } catch (err) { 99 | rej(err) 100 | } 101 | }) 102 | } 103 | 104 | /** 105 | * 返回上一个页面 106 | * @param result 返回给上一个页面的数据,如果 result 是 Error 的实例,则是抛出异常给上一个页面 107 | * @param options 其他选项 108 | */ 109 | static back( 110 | result?: unknown, 111 | options?: { 112 | /** 返回的页面数,如果 delta 大于现有页面数,则返回到首页。 */ 113 | delta?: number 114 | } 115 | ) { 116 | if (!isNil(result)) { 117 | PageData.setBackResult(result) 118 | } 119 | 120 | const currentPages = Taro.getCurrentPages() 121 | if (currentPages.length > 1) { 122 | return Taro.navigateBack(options) 123 | } 124 | 125 | throw new NoPageException() 126 | } 127 | 128 | /** 129 | * 设置页面返回的数据 130 | * 当物理键返回和左上角返回也需要带数据时会使用到 131 | */ 132 | static setBackResult(result: any) { 133 | PageData.setBackResult(result) 134 | } 135 | 136 | /** 137 | * 获取上一个页面携带过来的数据 138 | * @param default_value 默认数据 139 | */ 140 | static getData(default_value?: T): T | undefined { 141 | return PageData.getPageData(default_value) 142 | } 143 | 144 | /** 获取上一个页面携带过来的参数 */ 145 | static getParams>>(): T { 146 | const instance = getCurrentInstance() 147 | return Object.assign({}, instance.router?.params) as T 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /docs/guide/quike/middleware.md: -------------------------------------------------------------------------------- 1 | # 路由中间件 2 | 3 | 路由中间件将在跳转到目标页面之前执行,中间件的执行流程参考 koa 的洋葱模型: 4 | 5 | ![](/tarojs-router-next/images/koa.png) 6 | 7 | koa 是一个后端框架,在 koa 中,用户发起一个 http 请求给 koa 启动的服务,请求一层层进入 koa 的中间件,最终进入到一段 `具体的逻辑`(数据库操作或其他),然后再原路返回一段响应给用户。 8 | 9 | 换到这里就是,用户发起一个请求(进入页面的请求,包含目标页面的 url,ext 数据),请求一层层进入注册的中间件,然后进入到最后一个中间件:`跳转到目标页面的中间件`。然后再原路返回。 10 | 11 | ## 跳转到目标页面的中间件 12 | 13 | 在 tarojs-router-next 的路由跳转中,有一个隐藏的中间件:`跳转到目标页面的中间件`,它会默认添加在当前路由要执行的中间件的最后一个 `[...你的中间件, 跳转到目标页面的中间件]`,它即代表了 `目标页面`。 14 | 15 | ## 通过一个示例理解 16 | 17 | 注册几个路由中间件 18 | 19 | ```typescript 20 | import Taro from '@tarojs/taro' 21 | import { Middleware, registerMiddlewares } from 'tarojs-router-next' 22 | 23 | export const M1: Middleware = async (ctx, next) => { 24 | console.log('第一个中间件执行:', ctx.route.url) 25 | await next() // 执行下一个中间件 26 | } 27 | 28 | export const M2: Middleware = async (ctx, next) => { 29 | console.log('第二个中间件执行:', ctx.route.url) 30 | await next() // 执行下一个中间件 31 | } 32 | 33 | export const M3: Middleware = async (ctx, next) => { 34 | console.log('第三个中间件执行:', ctx.route.url) 35 | await next() // 执行下一个中间件 36 | } 37 | 38 | // 注册路由中间件 39 | registerMiddlewares([M1, M2, M3]) 40 | 41 | // 其实会执行四个中间件 [M1, M2, M3, 跳转到目标页面的中间件] 42 | ``` 43 | 44 | 然后在 `/pages/home/index` 页进行跳转到 `/pages/me/index` 页面 45 | 46 | ```typescript 47 | // pages/home/index.tsx 48 | import { Router } from 'tarojs-router-next' 49 | Router.toMe() // 进行页面跳转 50 | ``` 51 | 52 | 在 `/pages/me/index` 页面打印内容 53 | 54 | ```typescript 55 | // pages/me/index.tsx 56 | export default function Page() { 57 | console.log('成功进入了页面:me') 58 | return 59 | } 60 | ``` 61 | 62 | 输出: 63 | 64 | ```shell 65 | 第一个中间件执行:/pages/me/index 66 | 第二个中间件执行:/pages/me/index 67 | 第三个中间件执行:/pages/me/index 68 | 成功进入了页面:me 69 | ``` 70 | 71 | 现在我们想要在第二个中间件中判断用户是否登录,如果未登录就不要进入 me 页面,则只需要进行一些判断即可: 72 | 73 | ```typescript 74 | export const M2: Middleware = async (ctx, next) => { 75 | console.log('第二个中间件执行:', ctx.route.url) 76 | if (hasLogin()) { 77 | await next() // 执行下一个中间件 78 | } else { 79 | // 只要不执行 next,不进入后面的中间件即可 80 | console.log('请登录') 81 | } 82 | } 83 | ``` 84 | 85 | ## 注册路由中间件 86 | 87 | 上面的例子中我们注册了三个中间件,用的是 [registerMiddlewares](/api/register-middlewares),注册单个中间件可以使用 [registerMiddleware](/api/register-middleware) 88 | 89 | ```typescript 90 | import Taro from '@tarojs/taro' 91 | import { Middleware, registerMiddleware } from 'tarojs-router-next' 92 | 93 | export const M1: Middleware = async (ctx, next) => { 94 | console.log('中间件执行:', ctx.route.url) 95 | await next() 96 | console.log('中间件执行结束') 97 | } 98 | 99 | registerMiddleware(M1) 100 | ``` 101 | 102 | ## 动态注册路由中间件 103 | 104 | 有的时候我们希望某个中间件只为特定的页面工作,这个需求可以在中间件中增加判断条件来实现,但在中间件中做这些判断会使中间件的职能不够专一,并且这些判断逻辑无法在多个中间件中复用 105 | 106 | 怎么解决呢,我们可以在注册中间件时传递一个方法,将本来要写到中间件中的判断逻辑抽取到该方法中。在路由进入时该方法会被调用并传入当前路由的上下文,若方法返回 `true` 则为当前路由执行这些中间件 107 | 108 | ```typescript 109 | // 仅为 me 和 home 页面注册该路由中间件 110 | registerMiddleware(Logger, (ctx) => { 111 | return ['/pages/me/index', '/pages/home/index'].indexOf(ctx.route.url) !== -1 112 | }) 113 | 114 | // 注册多个中间件 115 | registerMiddlewares([Logger, Auth], (ctx) => { 116 | return ['/pages/me/index', '/pages/home/index'].indexOf(ctx.route.url) !== -1 117 | }) 118 | ``` 119 | 120 | ## 路由附加数据 121 | 122 | vue 开发者一定知道,我们使用 vue-router 定义路由时可以通过 [路由元信息](https://router.vuejs.org/zh/guide/advanced/meta.html) 携带一些数据告知导航守卫对该路由做一些特殊的处理: 123 | 124 | 比如告诉导航守卫该页面是要登陆的 125 | 126 | ```typescript 127 | const router = new VueRouter({ 128 | routes: [ 129 | { 130 | path: '/me', 131 | ... 132 | meta: { mustLogin: true } 133 | } 134 | ] 135 | }) 136 | ``` 137 | 138 | 或者是某些权限才可以访问, 139 | 140 | ```typescript 141 | { 142 | ... 143 | meta: { roles: [1, 2, 3] } 144 | } 145 | ``` 146 | 147 | 然后我们就可以在导航守卫中获取到 `meta` 和 `route` 来进行一些判断和处理 148 | 149 | #### 在 tarojs-router-next 中这样实现: 150 | 151 | 首先,我们要定义附加数据,在页面文件夹下面新建 [route.config.ts](/guide/quike/route-config) 文件,然后导出 [Ext](/guide/quike/route-config#导出附加数据-ext): 152 | 153 | ![](/tarojs-router-next/images/code3.png) 154 | 155 | 然后就可以在中间件中访问并使用: 156 | 157 | ```typescript 158 | import Taro from '@tarojs/taro' 159 | import { Middleware, Router } from 'tarojs-router-next' 160 | 161 | export const AuthCheck: Middleware<{ mustLogin: boolean }> = async (ctx, next) => { 162 | if (ctx.route.ext?.mustLogin) { 163 | const token = Taro.getStorageSync('token') 164 | if (!token) { 165 | const { confirm } = await Taro.showModal({ 166 | title: '提示', 167 | content: '请先登录', 168 | }) 169 | 170 | if (confirm) Router.toLogin() 171 | 172 | // 直接返回,不执行 next 即可打断中间件向下执行 173 | return 174 | } 175 | } 176 | 177 | await next() 178 | } 179 | ``` 180 | 181 | 但是请注意的是,通过 [route.config.ts](/guide/quike/route-config) 这种方式定义的附加数据,只有通过 [Router.to\*\*](/api/router#to-options-) 跳转时才会携带,通过 [Router.navigate](/api/router#navigate-route-options-) 跳转时,请通过 route.ext 参数携带 182 | 183 | ```typescript 184 | import { Router } from 'tarojs-router-next' 185 | Router.navigate({ url: '/pages/article-detail/index', ext: { mustLogin: true } }) 186 | ``` 187 | -------------------------------------------------------------------------------- /packages/tarojs-router-next-plugin/src/loader.ts: -------------------------------------------------------------------------------- 1 | import { processTypeEnum } from '@tarojs/helper/dist/constants' 2 | import fs from 'fs' 3 | import normalize from 'normalize-path' 4 | import path from 'path' 5 | import { Project, SourceFile } from 'ts-morph' 6 | import { IConfigPackage } from './config' 7 | import { ConfigPage, Page, RouteConfig } from './entitys' 8 | import { Plugin } from './plugin' 9 | import { extractValue, formatPageDir, isNil } from './utils' 10 | 11 | export class Loader { 12 | project = new Project() 13 | configPages: ConfigPage[] = [] 14 | appConfigPath: string 15 | appConfig: { 16 | pages: string[] 17 | subpackages?: any[] 18 | subPackages?: any[] 19 | window: any 20 | } 21 | 22 | constructor(private readonly root: Plugin) { 23 | // 非开发模式则读取 app.config.ts 中的配置,用于过滤未配置的页面 24 | if (!this.root.isWatch) this.loadConfigPages() 25 | } 26 | 27 | loadConfigPages(dynamic = false) { 28 | if (!dynamic && this.appConfig) return 29 | 30 | this.appConfigPath = this.root.helper.resolveMainFilePath(path.resolve(this.root.paths.sourcePath, './app.config')) 31 | this.appConfig = this.root.helper.readConfig(this.appConfigPath) 32 | 33 | for (const page of this.appConfig.pages) { 34 | this.configPages.push({ 35 | path: page, 36 | packageRoot: '', 37 | fullPath: path.resolve(this.root.paths.sourcePath, page), 38 | }) 39 | } 40 | 41 | for (const pkg of this.appConfig.subpackages || this.appConfig.subPackages || []) { 42 | for (const page of pkg.pages) { 43 | this.configPages.push({ 44 | path: page, 45 | packageRoot: pkg.root, 46 | fullPath: path.resolve(this.root.paths.sourcePath, pkg.root, page), 47 | }) 48 | } 49 | } 50 | } 51 | 52 | loadPages() { 53 | this.root.pages = [] 54 | for (const pkg of this.root.config.packages) { 55 | const routeConfigSourceFiles = this.project.addSourceFilesAtPaths(pkg.pagePath + '/**/route.config.ts') 56 | 57 | fs.readdirSync(pkg.pagePath) 58 | // 过滤一些非页面文件夹 59 | .filter((pageDirName) => this.root.config.ignore.indexOf(pageDirName) === -1) 60 | .forEach((pageDirName) => { 61 | const fullPath = path.resolve(pkg.pagePath, pageDirName, 'index') 62 | if ( 63 | !this.root.isWatch && 64 | this.configPages.findIndex((configPage) => configPage.fullPath === fullPath) === -1 65 | ) { 66 | return 67 | } 68 | 69 | const page = new Page() 70 | page.packageName = pkg.name 71 | page.dirName = pageDirName 72 | page.dirPath = path.resolve(pkg.pagePath, pageDirName) 73 | // 生成跳转路径 pages/xx/xx 74 | page.path = normalize(path.join(pkg.pagePath.replace(this.root.paths.sourcePath, ''), pageDirName, 'index')) 75 | page.fullPath = fullPath 76 | 77 | const sourceFile = routeConfigSourceFiles.find((sourceFile) => { 78 | return path.normalize(sourceFile.compilerNode.fileName) === path.resolve(page.dirPath, 'route.config.ts') 79 | }) 80 | 81 | if (sourceFile) { 82 | this.loadRouteConfig(page, sourceFile) 83 | } 84 | 85 | this.loadMethod(page) 86 | 87 | this.root.pages.push(page) 88 | this.root.log( 89 | processTypeEnum.GENERATE, 90 | `Router.${page.packageName === 'main' ? '' : page.packageName + '.'}${page.method?.name}` 91 | ) 92 | }) 93 | } 94 | } 95 | 96 | loadPage(pageDirPath: string, pkg: IConfigPackage) { 97 | const index = this.root.pages.findIndex((page) => page.dirPath === pageDirPath) 98 | 99 | const isExist = fs.existsSync(pageDirPath) 100 | if (isExist) { 101 | if (index !== -1) { 102 | const page = this.root.pages[index] 103 | this.loadRouteConfig(page) 104 | this.loadMethod(page) 105 | this.root.log( 106 | processTypeEnum.MODIFY, 107 | `Router.${page.packageName === 'main' ? '' : page.packageName + '.'}${page.method?.name}` 108 | ) 109 | } else { 110 | const page = new Page() 111 | page.packageName = pkg.name 112 | page.dirName = path.parse(pageDirPath).name 113 | page.dirPath = pageDirPath 114 | page.path = path.resolve(pageDirPath.replace(this.root.paths.sourcePath, ''), 'index') 115 | page.fullPath = path.resolve(pageDirPath, 'index') 116 | this.loadRouteConfig(page) 117 | this.loadMethod(page) 118 | this.root.pages.push(page) 119 | this.root.log( 120 | processTypeEnum.GENERATE, 121 | `Router.${page.packageName === 'main' ? '' : page.packageName + '.'}${page.method?.name}` 122 | ) 123 | } 124 | return true 125 | } else { 126 | if (index !== -1) { 127 | const [page] = this.root.pages.splice(index, 1) 128 | this.root.log( 129 | processTypeEnum.UNLINK, 130 | `Router.${page.packageName === 'main' ? '' : page.packageName + '.'}${page.method?.name}` 131 | ) 132 | return true 133 | } else { 134 | return false 135 | } 136 | } 137 | } 138 | 139 | loadRouteConfig(page: Page, configSourceFile?: SourceFile) { 140 | page.routeConfig = undefined 141 | const routeConfig: RouteConfig = {} 142 | 143 | if (!configSourceFile) { 144 | const configPath = path.resolve(page.dirPath, 'route.config.ts') 145 | if (!fs.existsSync(configPath)) return 146 | configSourceFile = this.project.getSourceFile(configPath) 147 | if (configSourceFile) { 148 | configSourceFile.refreshFromFileSystemSync() 149 | } else { 150 | configSourceFile = this.project.addSourceFileAtPath(configPath) 151 | } 152 | } 153 | 154 | configSourceFile.getExportedDeclarations().forEach((declarations, name) => { 155 | if (declarations.length > 1) return 156 | const declaration = declarations[0] as any 157 | switch (name) { 158 | case 'Params': 159 | routeConfig.params = `import('${path.resolve(page.dirPath, 'route.config').replace(/\\/g, '/')}').Params` 160 | break 161 | case 'Data': 162 | routeConfig.data = `import('${path.resolve(page.dirPath, 'route.config').replace(/\\/g, '/')}').Data` 163 | break 164 | case 'BackData': 165 | routeConfig.backData = `import('${path.resolve(page.dirPath, 'route.config').replace(/\\/g, '/')}').BackData` 166 | break 167 | case 'Ext': 168 | routeConfig.ext = extractValue({ 169 | name, 170 | declaration, 171 | }) 172 | break 173 | } 174 | }) 175 | 176 | page.routeConfig = routeConfig 177 | } 178 | 179 | loadMethod(page: Page) { 180 | const { routeConfig, dirName } = page 181 | 182 | let methodName = 'to' + formatPageDir(dirName) 183 | const methodBody = `return Router.navigate({ url: "${page.path}"${ 184 | routeConfig?.ext ? ', ext: ' + routeConfig.ext : '' 185 | } }, options)` 186 | 187 | let method = `function (options) {${methodBody}}` 188 | 189 | let methodType: string 190 | 191 | let ReturnType = 'any' 192 | if (routeConfig?.backData) { 193 | ReturnType = routeConfig.backData 194 | } 195 | 196 | if (!routeConfig || (isNil(routeConfig.params) && isNil(routeConfig.data))) { 197 | methodType = `` + 198 | "(options?: NavigateOptions & Params> & Data>) => Promise"; 199 | page.method = { 200 | name: methodName, 201 | type: methodType, 202 | value: method, 203 | } 204 | return 205 | } 206 | 207 | methodType = `` + 208 | "(...options: RequiredKeys> & Data>> extends never " + 209 | "? [options?: NavigateOptions & Params> & Data>] : [options: NavigateOptions & Params> & Data>]) => Promise"; 210 | page.method = { 211 | name: methodName, 212 | type: methodType, 213 | value: method, 214 | } 215 | } 216 | } 217 | --------------------------------------------------------------------------------