├── .prettierignore ├── babel.config.js ├── commitlint.config.js ├── .husky ├── commit-msg └── pre-commit ├── .eslintignore ├── .editorconfig ├── packages ├── read-pages │ ├── api-extractor.json │ ├── package.json │ ├── LICENSE │ ├── src │ │ └── index.ts │ └── README.md └── uni-native-router │ ├── api-extractor.json │ ├── package.json │ ├── src │ ├── utils.ts │ ├── types.ts │ ├── query.ts │ └── index.ts │ ├── LICENSE │ └── README.md ├── .prettierrc.js ├── .gitignore ├── .eslintrc.js ├── .vscode └── settings.json ├── tsconfig.json ├── LICENSE ├── api-extractor.json ├── CHANGELOG.md ├── scripts ├── build.mjs ├── release.mjs └── utils.mjs ├── package.json ├── rollup.config.js └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env'] 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged --allow-empty "$1" 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | assets 4 | locales 5 | public 6 | dist 7 | lib 8 | www 9 | *.md 10 | *.md 11 | *.d.ts 12 | # rollup.config.js -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /packages/read-pages/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../api-extractor.json", 3 | "mainEntryPointFilePath": "./dist/packages//src/index.d.ts", 4 | "dtsRollup": { 5 | "publicTrimmedFilePath": "./dist/.d.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/uni-native-router/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../api-extractor.json", 3 | "mainEntryPointFilePath": "./dist/packages//src/index.d.ts", 4 | "dtsRollup": { 5 | "publicTrimmedFilePath": "./dist/.d.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 200, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: false, 6 | singleQuote: true, 7 | proseWrap: 'preserve', 8 | arrowParens: 'always', 9 | jsxSingleQuote: false, 10 | htmlWhitespaceSensitivity: 'ignore', 11 | endOfLine: 'auto', 12 | trailingComma: 'none' 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node_modules 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .DS_Store 15 | .idea 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | *.lock 22 | 23 | # dist 24 | dist 25 | dist.zip 26 | 27 | # build 28 | build 29 | build.zip 30 | 31 | # temp 32 | temp -------------------------------------------------------------------------------- /packages/read-pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "read-pages", 3 | "version": "1.1.4", 4 | "description": "读取uni-app配置文件pages.json的工具包", 5 | "private": true, 6 | "main": "./index.js", 7 | "module": "./index.esm.js", 8 | "types": "./read-pages.d.ts", 9 | "keywords": [ 10 | "uni-app", 11 | "router", 12 | "uni-native-router", 13 | "pages.json", 14 | "pages", 15 | "read" 16 | ], 17 | "author": { 18 | "name": "Gertyxs", 19 | "email": "gertyxs@outlook.com", 20 | "url": "https://github.com/Gertyxs" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/Gertyxs/uni-native-router.git" 25 | }, 26 | "license": "MIT", 27 | "peerDependencies": {} 28 | } 29 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | es6: true, 7 | node: true 8 | }, 9 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | parser: '@typescript-eslint/parser', 13 | sourceType: 'module' 14 | }, 15 | plugins: ['prettier'], 16 | rules: { 17 | 'no-console': 'off', 18 | '@typescript-eslint/no-non-null-assertion': 'off', 19 | '@typescript-eslint/ban-ts-comment': 'off', 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | '@typescript-eslint/ban-types': 'off', 22 | '@typescript-eslint/no-var-requires': 'off' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/uni-native-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uni-native-router", 3 | "version": "1.1.4", 4 | "description": "一个封装uni-app原生路由API库,使用uni-app原生钩子实现和方法实现、hooks的使用方式适配vue3", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.esm.js", 7 | "types": "./dist/uni-native-router.d.ts", 8 | "files": [ 9 | "dist", 10 | "README.md", 11 | "LICENSE" 12 | ], 13 | "keywords": [ 14 | "uni", 15 | "uni-app", 16 | "router", 17 | "uni-native-router" 18 | ], 19 | "author": { 20 | "name": "Gertyxs", 21 | "email": "gertyxs@outlook.com", 22 | "url": "https://github.com/Gertyxs" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/Gertyxs/uni-native-router.git" 27 | }, 28 | "license": "MIT", 29 | "peerDependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "liveServer.settings.port": 5501, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[jsonc]": { 12 | "editor.defaultFormatter": "vscode.json-language-features" 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "vscode.typescript-language-features" 16 | }, 17 | "[javascriptreact]": { 18 | "editor.defaultFormatter": "vscode.typescript-language-features" 19 | }, 20 | "[html]": { 21 | "editor.defaultFormatter": "vscode.html-language-features" 22 | }, 23 | "[javascript]": { 24 | "editor.defaultFormatter": "vscode.typescript-language-features" 25 | } 26 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": ".", 5 | "rootDir": ".", 6 | "sourceMap": false, 7 | "noEmit": true, 8 | "target": "es5", 9 | "module": "es2015", 10 | "moduleResolution": "node", 11 | "outDir": "dist", 12 | "allowSyntheticDefaultImports": true, 13 | "allowJs": false, 14 | "skipLibCheck": true, 15 | "noUnusedLocals": true, 16 | "strictNullChecks": true, 17 | "noImplicitAny": false, 18 | "noImplicitThis": true, 19 | "noImplicitReturns": false, 20 | "isolatedModules": true, 21 | "experimentalDecorators": true, 22 | "resolveJsonModule": true, 23 | "esModuleInterop": true, 24 | "removeComments": false, 25 | "jsx": "preserve", 26 | "lib": [ 27 | "esnext", 28 | "dom", 29 | "dom.iterable" 30 | ] 31 | }, 32 | "include": [ 33 | "packages/*/src" 34 | ] 35 | } -------------------------------------------------------------------------------- /packages/uni-native-router/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 是否是对象 3 | * @param data 4 | * @returns Boolean 5 | */ 6 | export const isObject = (data: any) => { 7 | return Object.prototype.toString.call(data).includes('Object') 8 | } 9 | 10 | /** 11 | * 是否是数组 12 | * @param data 13 | * @returns Boolean 14 | */ 15 | export const isArray = (data: any) => { 16 | return Object.prototype.toString.call(data).includes('Array') 17 | } 18 | 19 | /** 20 | * 是否是字符串 21 | * @param data 22 | * @returns Boolean 23 | */ 24 | export const isString = (data: any) => { 25 | return Object.prototype.toString.call(data).includes('String') 26 | } 27 | 28 | /** 29 | * 是否是函数 30 | * @param data 31 | * @returns Boolean 32 | */ 33 | export const isFunction = (data: any) => { 34 | return Object.prototype.toString.call(data).includes('Function') 35 | } 36 | 37 | /** 38 | * 深拷贝 39 | * @param data 40 | * @returns 41 | */ 42 | export const deepClone = (data: any) => { 43 | if (typeof data !== 'object') return null 44 | return JSON.parse(JSON.stringify(data)) 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gert 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 | -------------------------------------------------------------------------------- /packages/read-pages/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gert 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 | -------------------------------------------------------------------------------- /packages/uni-native-router/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gert 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 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "apiReport": { 4 | "enabled": true, 5 | "reportFolder": "/temp/" 6 | }, 7 | "docModel": { 8 | "enabled": true 9 | }, 10 | "dtsRollup": { 11 | "enabled": true 12 | }, 13 | "tsdocMetadata": { 14 | "enabled": false 15 | }, 16 | "messages": { 17 | "compilerMessageReporting": { 18 | "default": { 19 | "logLevel": "warning" 20 | } 21 | }, 22 | "extractorMessageReporting": { 23 | "default": { 24 | "logLevel": "warning", 25 | "addToApiReportFile": true 26 | }, 27 | "ae-missing-release-tag": { 28 | "logLevel": "none" 29 | } 30 | }, 31 | "tsdocMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | }, 35 | "tsdoc-undefined-tag": { 36 | "logLevel": "none" 37 | }, 38 | "tsdoc-escape-greater-than": { 39 | "logLevel": "none" 40 | }, 41 | "tsdoc-malformed-inline-tag": { 42 | "logLevel": "none" 43 | }, 44 | "tsdoc-escape-right-brace": { 45 | "logLevel": "none" 46 | }, 47 | "tsdoc-unnecessary-backslash": { 48 | "logLevel": "none" 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /packages/uni-native-router/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Query 参数 3 | */ 4 | export interface Query { 5 | [key: string]: any 6 | } 7 | 8 | /** 9 | * 路由类型定义 10 | */ 11 | export interface Route { 12 | path?: string 13 | name?: string 14 | fullPath?: string 15 | query?: Query 16 | isLaunch?: boolean // 是否app运行初始化执行 17 | type?: string 18 | url?: string // 同path一样,兼容uni app字段 19 | [key: string]: any 20 | } 21 | 22 | /** 23 | * 分包的路由配置 24 | */ 25 | export interface SubPackages { 26 | root: string 27 | pages: Route 28 | [key: string]: any 29 | } 30 | 31 | /** 32 | * 初始化路由Options 33 | */ 34 | export interface CreateOptions { 35 | routes: Route[] // 路由配置 36 | subPackages?: SubPackages[] 37 | routeMethods?: string[] // 路由具有的方法 38 | } 39 | 40 | /** 41 | * 下一步回调函数 42 | */ 43 | export type Next = (value?: any) => void 44 | 45 | /** 46 | * 路由跳转前拦截函数 47 | */ 48 | export type BeforeEach = (to: Route, from: Route, next: Next) => void 49 | 50 | /** 51 | * 路由跳转后拦截函数 52 | */ 53 | export type AfterEach = (to: Route, from: Route) => void 54 | 55 | /** 56 | * 返回参数 57 | */ 58 | export interface BackParams { 59 | delta?: number // 返回的页面数 60 | [key: string]: any 61 | } 62 | 63 | /** 64 | * 路由元数据 65 | */ 66 | export interface RouteMeta { 67 | delta?: number // 返回的页面数 68 | [key: string]: any 69 | } 70 | 71 | /** 72 | * 路由实例 73 | */ 74 | export interface Router { 75 | navigateTo: (params: Route) => void 76 | switchTab: (params: Route) => void 77 | reLaunch: (params: Route) => void 78 | redirectTo: (params: Route) => void 79 | navigateBack: (params?: BackParams) => void 80 | beforeEach: (beforeEach: BeforeEach) => void 81 | afterEach: (afterEach: AfterEach) => void 82 | install(App: any): void 83 | routeMeta: RouteMeta 84 | } 85 | 86 | /** 87 | * global 88 | */ 89 | declare global { 90 | let uni: any 91 | let getCurrentPages: () => any 92 | } 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.4](https://github.com/Gertyxs/uni-native-router/compare/v1.1.3...v1.1.4) (2023-12-06) 2 | 3 | 4 | 5 | ## [1.1.3](https://github.com/Gertyxs/uni-native-router/compare/v1.1.2...v1.1.3) (2023-12-06) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * 修复版本号问题 ([0570ea8](https://github.com/Gertyxs/uni-native-router/commit/0570ea8c08b2097fb7faf0e754bff34518a606bc)) 11 | * 修复版本号问题 ([4b2a41f](https://github.com/Gertyxs/uni-native-router/commit/4b2a41f240a78b1cb928e261db1ac03a735e9f76)) 12 | * 解决uni方法被treeShaking掉 ([3c35d21](https://github.com/Gertyxs/uni-native-router/commit/3c35d21fca1df6bc5f11a77aa6d93a6b47a0f27a)) 13 | 14 | 15 | 16 | ## [1.1.2](https://github.com/Gertyxs/uni-native-router/compare/v1.1.1...v1.1.2) (2022-08-02) 17 | 18 | 19 | 20 | ## [1.1.1](https://github.com/Gertyxs/uni-native-router/compare/v1.1.0...v1.1.1) (2022-08-02) 21 | 22 | 23 | ### Features 24 | 25 | * 新增说明文档 ([bf7a9ea](https://github.com/Gertyxs/uni-native-router/commit/bf7a9ea55ac9be594fc35a39f5d1efb358375200)) 26 | 27 | 28 | 29 | # [1.1.0](https://github.com/Gertyxs/uni-native-router/compare/v1.0.9...v1.1.0) (2022-08-02) 30 | 31 | 32 | 33 | ## [1.0.9](https://github.com/Gertyxs/uni-native-router/compare/v1.0.8...v1.0.9) (2022-08-02) 34 | 35 | 36 | 37 | ## [1.0.8](https://github.com/Gertyxs/uni-native-router/compare/v1.0.7...v1.0.8) (2022-08-02) 38 | 39 | 40 | ### Features 41 | 42 | * update to v1.0.7 ([6cc105f](https://github.com/Gertyxs/uni-native-router/commit/6cc105fb2538e2f11cda86bd36ee8baef7ff60c3)) 43 | 44 | 45 | 46 | ## [1.0.7](https://github.com/Gertyxs/uni-native-router/compare/v1.0.6...v1.0.7) (2022-05-18) 47 | 48 | 49 | ### Features 50 | 51 | * 加入@microsoft/api-extractor分析ts和汇总ts ([42f2443](https://github.com/Gertyxs/uni-native-router/commit/42f2443e04de64bab42b41ec5487d9a56821a6fb)) 52 | 53 | 54 | 55 | ## [1.0.6](https://github.com/Gertyxs/uni-native-router/compare/v1.0.3...v1.0.6) (2022-05-17) 56 | 57 | 58 | 59 | ## 1.0.3 (2022-05-16) 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /packages/uni-native-router/src/query.ts: -------------------------------------------------------------------------------- 1 | import { Query } from './types' 2 | 3 | /** 4 | * 解析query 5 | * @param query 6 | * @returns {Object} 7 | */ 8 | export const parseQuery = (query: string) => { 9 | const result: Query = {} 10 | // 如果不是字符串返回空对象 11 | if (typeof query !== 'string') { 12 | return result 13 | } 14 | 15 | // 去掉字符串开头可能带的? 16 | if (query.charAt(0) === '?') { 17 | query = query.substring(1) 18 | } 19 | 20 | const pairs = query.split('&') 21 | 22 | for (let i = 0; i < pairs.length; i++) { 23 | const pair = pairs[i].split('=') 24 | // application/x-www-form-urlencoded编码会将' '转换为+ 25 | const key = decodeURIComponent(pair[0]).replace(/\+/g, ' ') 26 | const value = decodeURIComponent(pair[1]).replace(/\+/g, ' ') 27 | // 如果是新key,直接添加 28 | if (!(key in result)) { 29 | result[key] = value 30 | } else if (Array.isArray(result[key])) { 31 | // 如果key已经出现一次以上,直接向数组添加value 32 | result[key].push(value) 33 | } else { 34 | // key第二次出现,将结果改为数组 35 | const valueArr = [result[key]] 36 | valueArr.push(value) 37 | result[key] = valueArr 38 | } 39 | } 40 | return result 41 | } 42 | 43 | /** 44 | * 将对象分隔成 queryStr字符串 45 | * @param queryRaw 需要格式化的数据对象 46 | * @returns {String} 47 | */ 48 | export const stringifyQuery = (queryRaw: Query) => { 49 | let search = '' 50 | for (let key in queryRaw) { 51 | const value = queryRaw[key] 52 | key = encodeURIComponent(key) 53 | if (value == null) { 54 | if (value !== undefined) { 55 | search += (search.length ? '&' : '') + key 56 | } 57 | continue 58 | } 59 | const values: string[] = Array.isArray(value) ? value.map((v) => v && encodeURIComponent(v)) : [value && encodeURIComponent(value)] 60 | values.forEach((value) => { 61 | if (value !== undefined) { 62 | search += (search.length ? '&' : '') + key 63 | if (value != null) search += '=' + value 64 | } 65 | }) 66 | } 67 | return search 68 | } 69 | -------------------------------------------------------------------------------- /scripts/build.mjs: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs' 2 | import { resolve, step, run, packages } from './utils.mjs' 3 | import { Extractor, ExtractorConfig } from '@microsoft/api-extractor' 4 | import chalk from 'chalk' 5 | 6 | // main 7 | const main = async () => { 8 | shell.rm('-r', resolve(`./dist`)) 9 | shell.rm('-r', resolve('./node_modules/.rts2_cache')) 10 | 11 | packages.forEach((pkgDir) => { 12 | // remove 13 | step('\nRemove dist...') 14 | shell.rm('-r', resolve(`./packages/${pkgDir}/dist`)) 15 | 16 | // build 17 | shell.env.NODE_ENV = 'production' 18 | shell.env.TARGET = pkgDir 19 | step('\nBuild...') 20 | run('rollup -c ./rollup.config.js') 21 | 22 | // build types 23 | const extractorConfigPath = resolve(`./packages/${pkgDir}/api-extractor.json`) 24 | const extractorConfig = ExtractorConfig.loadFileAndPrepare(extractorConfigPath) 25 | const extractorResult = Extractor.invoke(extractorConfig, { 26 | localBuild: true, 27 | showVerboseMessages: true 28 | }) 29 | if (extractorResult.succeeded) { 30 | console.log(chalk.bold(chalk.green(`API Extractor completed successfully.`))) 31 | } 32 | 33 | // remove dist packages 34 | shell.rm('-r', resolve(`./packages/${pkgDir}/dist/packages`)) 35 | }) 36 | 37 | // copy package.json 38 | step('\nCopy package.json...') 39 | if (packages.includes('read-pages') && packages.includes('uni-native-router')) { 40 | shell.cp('-r', resolve('./packages/read-pages/package.json'), resolve('./packages/read-pages/dist/package.json')) 41 | shell.cp('-r', resolve('./packages/read-pages/dist/'), resolve('./packages/uni-native-router/dist/read-pages')) 42 | shell.rm('-r', resolve('./packages/read-pages/dist/')) 43 | } 44 | if (packages.includes('uni-native-router')) { 45 | shell.cp('-r', resolve('./README.md'), resolve('./packages/uni-native-router/README.md')) 46 | } 47 | 48 | step('\nBuild Successfully') 49 | } 50 | 51 | main().catch((err) => { 52 | console.error(err) 53 | }) 54 | -------------------------------------------------------------------------------- /packages/read-pages/src/index.ts: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const cwdPath = process.cwd() 4 | 5 | /** 6 | * 解析绝对路径 7 | * @param _path 8 | * @returns 9 | */ 10 | const resolvePath = (_path) => { 11 | return path.resolve(cwdPath, _path) 12 | } 13 | 14 | /** 15 | * 读取pages.json 路由配置文件 16 | */ 17 | const readPages = (options) => { 18 | options = options ? options : {} 19 | const input = options.input ? options.input : resolvePath('./src/pages.json') // pages.json路径 20 | const output = options.output ? options.output : resolvePath('./src/pages.js') // 输出路径 21 | const includes = options.includes ? options.includes : null // 需要获取包涵的字段 不输入包含所有 22 | const isCreate = options.isCreate ? options.isCreate : false // 是否生成文件 23 | const buffPrefix = Buffer.from('export default ') // 如果需要生成路由文件的导出语句 24 | let config: any = {} // 存储路由数据的对象 25 | 26 | // 同步读取文件 27 | const fileData = fs.readFileSync(input) 28 | 29 | // 解析存在异常的json数据 诸如 单引号、注释等 30 | config = new Function(`return ${fileData.toString()}`)() 31 | 32 | // 获取指定字段路由对象 33 | const getAssignFieldRoutes = (routes) => { 34 | return routes.map((page) => { 35 | const item = {} 36 | if (!includes) { 37 | return page 38 | } 39 | for (const key in page) { 40 | if (includes.includes(key) && page[key]) { 41 | item[key] = page[key] 42 | } 43 | } 44 | return item 45 | }) 46 | } 47 | 48 | // 遍历,读取指定的字段,减小文件体积 49 | config.pages = getAssignFieldRoutes(config.pages) 50 | 51 | // 如果有分包 52 | if (config.subPackages) { 53 | config.subPackages = getAssignFieldRoutes(config.subPackages) 54 | } 55 | 56 | // 合并路由和子路由 57 | if (config.subPackages) { 58 | config.routes = [...config.pages, config.subPackages] 59 | } else { 60 | config.routes = [...config.pages] 61 | } 62 | 63 | // 如果要生成路由文件 64 | if (isCreate) { 65 | fs.writeFileSync(output, `${buffPrefix}${Buffer.from(JSON.stringify(config.routes))}`) 66 | } 67 | 68 | return config 69 | } 70 | 71 | export default readPages 72 | -------------------------------------------------------------------------------- /scripts/release.mjs: -------------------------------------------------------------------------------- 1 | import { resolve, packages, getPackageInfo, args, isDryRun, run, runIfNotDry, step, generateVersion, publishPackage, updateVersions } from './utils.mjs' 2 | 3 | const pkg = getPackageInfo(resolve('./package.json')) 4 | const skipBuild = args.skipBuild 5 | // main 6 | const main = async () => { 7 | const currentVersion = pkg.version 8 | let targetVersion = args._[0] 9 | 10 | if (!targetVersion) { 11 | try { 12 | targetVersion = await generateVersion(targetVersion, currentVersion) 13 | if (!targetVersion) return 14 | } catch (error) { 15 | console.error(error.message) 16 | } 17 | } 18 | 19 | // update all package versions and inter-dependencies 20 | if (!isDryRun) { 21 | step('\nUpdating version...') 22 | updateVersions(targetVersion) 23 | } 24 | 25 | // build all packages with types 26 | step('\nBuilding all packages...') 27 | if (!skipBuild && !isDryRun) { 28 | run('npm run build') 29 | } else { 30 | console.log(`(skipped)`) 31 | } 32 | 33 | // generate changelog 34 | if (!isDryRun) { 35 | step('\nGenerating changelog...') 36 | run('npm run changelog') 37 | } 38 | 39 | const { stdout } = run('git diff') 40 | if (stdout && !isDryRun) { 41 | step('\nCommitting changes...') 42 | runIfNotDry('git add -A') 43 | runIfNotDry(`git commit -m 'release: v${targetVersion}'`) 44 | } else { 45 | console.log('No changes to commit.') 46 | } 47 | 48 | // publish packages 49 | if (!isDryRun) { 50 | step('\nPublishing packages...') 51 | for (const pkg of packages) { 52 | if (!['read-pages'].includes(pkg)) { 53 | await publishPackage(pkg, targetVersion) 54 | } 55 | } 56 | } 57 | 58 | // push to GitHub 59 | step('\nPushing to GitHub...') 60 | runIfNotDry(`git tag v${targetVersion}`) 61 | runIfNotDry(`git push origin refs/tags/v${targetVersion}`) 62 | runIfNotDry('git push origin master') 63 | 64 | if (isDryRun) { 65 | console.log(`\nDry run finished - run git diff to see package changes.`) 66 | } 67 | } 68 | 69 | main().catch((err) => { 70 | console.error(err) 71 | }) 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "1.1.4", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "prepare": "husky install", 10 | "build": "node scripts/build.mjs", 11 | "release": "node scripts/release.mjs", 12 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", 13 | "lint": "eslint --fix --ext \".js,.jsx,.tsx,.ts,.vue\" packages/** --no-error-on-unmatched-pattern --ignore-path .eslintignore" 14 | }, 15 | "lint-staged": { 16 | "*.{js,jsx,vue,ts,tsx}": [ 17 | "yarn run lint" 18 | ] 19 | }, 20 | "keywords": [ 21 | "uni-app", 22 | "router", 23 | "uni-native-router" 24 | ], 25 | "author": { 26 | "name": "Gertyxs", 27 | "email": "gertyxs@outlook.com", 28 | "url": "https://github.com/Gertyxs" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/Gertyxs/uni-native-router.git" 33 | }, 34 | "license": "MIT", 35 | "devDependencies": { 36 | "@babel/core": "^7.16.7", 37 | "@babel/preset-env": "^7.16.8", 38 | "@commitlint/cli": "^16.2.4", 39 | "@commitlint/config-conventional": "^16.2.4", 40 | "@microsoft/api-extractor": "^7.24.0", 41 | "@rollup/plugin-babel": "^5.3.0", 42 | "@rollup/plugin-commonjs": "^21.0.1", 43 | "@rollup/plugin-json": "^4.1.0", 44 | "@rollup/plugin-node-resolve": "^13.1.3", 45 | "@rollup/plugin-replace": "^3.0.1", 46 | "@types/node": "^14.14.25", 47 | "@typescript-eslint/eslint-plugin": "^5.23.0", 48 | "@typescript-eslint/parser": "^5.23.0", 49 | "chalk": "^5.0.1", 50 | "conventional-changelog-cli": "^2.2.2", 51 | "cross-env": "^7.0.3", 52 | "enquirer": "^2.3.6", 53 | "eslint": "^8.13.0", 54 | "eslint-config-prettier": "^8.5.0", 55 | "eslint-plugin-html": "^6.2.0", 56 | "eslint-plugin-prettier": "^4.0.0", 57 | "fs-extra": "^10.1.0", 58 | "husky": "^8.0.1", 59 | "lint-staged": "^12.4.1", 60 | "minimist": "^1.2.6", 61 | "prettier": "^2.6.2", 62 | "rimraf": "^3.0.2", 63 | "rollup": "^2.63.0", 64 | "rollup-plugin-filesize": "^9.1.2", 65 | "rollup-plugin-terser": "^7.0.2", 66 | "rollup-plugin-typescript2": "^0.31.2", 67 | "semver": "^7.3.7", 68 | "shelljs": "^0.8.5", 69 | "typescript": "^4.4.4" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/read-pages/README.md: -------------------------------------------------------------------------------- 1 | # read-pages 2 | 3 | > 读取 uni-app 配置文件 pages.json 的工具包 4 | 5 | ### vite 里面配置 6 | 7 | 建议直接使用`import pages from '@/pages.json'`导入 8 | 9 | `vite.config.ts` 10 | 11 | ```js 12 | import path from 'path' 13 | import { UserConfigExport, ConfigEnv, loadEnv } from 'vite' 14 | import eslintPlugin from 'vite-plugin-eslint' 15 | import uni from '@dcloudio/vite-plugin-uni' 16 | 17 | // 读取pages.json 18 | import readPages from 'uni-native-router/dist/read-pages' 19 | const pages = readPages({ input: './src/pages.json' }) 20 | 21 | export default ({ command, mode }: ConfigEnv): UserConfigExport => { 22 | const envConf = loadEnv(mode, root) 23 | return { 24 | root: root, 25 | plugins: [ 26 | uni() 27 | ], 28 | define: { 29 | ROUTES: JSON.stringify(pages.pages) 30 | } 31 | } 32 | } 33 | 34 | ``` 35 | 36 | > 在项目中拿到routes路由配置 37 | 38 | ```js 39 | import { createRouter } from 'uni-native-router' 40 | export { useRoute, useRouter } from 'uni-native-router' // 导出适配vue3的hooks获取路由钩子方法 41 | 42 | // 创建路由对象 43 | export let router = createRouter({ routes: ROUTES }) // ROUTES对应的是vite define配置里面的ROUTES 44 | 45 | // 设置路由器 46 | export function setupRouter(app: any) { 47 | // ... 48 | return router 49 | } 50 | ``` 51 | 52 | 53 | 54 | ### webpack 里面使用 55 | 56 | ```js 57 | import webpack from 'webpack' 58 | // 读取pages.json 59 | import readPages from 'uni-native-router/dist/read-pages' 60 | const pages = readPages({ input: './src/pages.json' }) 61 | 62 | module.exports = { 63 | configureWebpack: { 64 | plugins: [ 65 | new webpack.DefinePlugin({ 66 | ROUTES: webpack.DefinePlugin.runtimeValue(() => { 67 | return JSON.stringify(pages.pages) 68 | }, true) 69 | }) 70 | ] 71 | } 72 | } 73 | ``` 74 | 75 | > 在项目中拿到routes路由配置 76 | 77 | ```js 78 | import { createRouter } from 'uni-native-router' 79 | export { useRoute, useRouter } from 'uni-native-router' // 导出适配vue3的hooks获取路由钩子方法 80 | 81 | // 创建路由对象 82 | export let router = createRouter({ routes: ROUTES }) // ROUTES对应的是vite define配置里面的ROUTES 83 | 84 | // 设置路由器 85 | export function setupRouter(app: any) { 86 | // ... 87 | return router 88 | } 89 | ``` 90 | 91 | ### 92 | 93 | ## API 94 | 95 | ### readPages(OBJECT) 96 | 97 | `OBJECT.input `读取文件路径,默认为`./src/pages.json` 98 | 99 | `OBJECT.output `输出路径`isCreate:true`时生效,默认为`./src/pages.js` 100 | 101 | `OBJECT.includes `需要获取包涵的字段,不输入包含所有 102 | 103 | `OBJECT.isCreate` 是否生成文件,默认为`false` -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { terser } from 'rollup-plugin-terser' 3 | import nodeResolve from '@rollup/plugin-node-resolve' 4 | import replace from '@rollup/plugin-replace' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import { babel } from '@rollup/plugin-babel' 7 | import ts from 'rollup-plugin-typescript2' 8 | import json from '@rollup/plugin-json' 9 | 10 | if (!process.env.TARGET) { 11 | throw new Error('TARGET package must be specified via --environment flag.') 12 | } 13 | 14 | // resolve 15 | const resolve = (filePath) => path.resolve(__dirname, filePath) 16 | const env = process.env.NODE_ENV 17 | const pkg = require('./package.json') 18 | const isProd = env === 'production' 19 | const packagesDir = resolve('packages') 20 | const packageDir = path.resolve(packagesDir, process.env.TARGET) 21 | const getTargePkgPath = (filePath) => path.resolve(packageDir, filePath) 22 | 23 | // get author 24 | const getAuthors = (pkg) => { 25 | const { contributors, author } = pkg 26 | const authors = new Set() 27 | if (contributors && contributors) 28 | contributors.forEach((contributor) => { 29 | authors.add(contributor.name) 30 | }) 31 | if (author) authors.add(author.name) 32 | return Array.from(authors).join(', ') 33 | } 34 | 35 | const banner = `/*! 36 | * ${pkg.name} v${pkg.version} 37 | * (c) ${new Date().getFullYear()} ${getAuthors(pkg)} 38 | * @license MIT 39 | */` 40 | 41 | // create output 42 | const createOutput = (outputMap) => { 43 | const result = [] 44 | const moduleMap = { 45 | main: { format: 'cjs' }, 46 | module: { format: 'es' }, 47 | umd: { format: 'umd' } 48 | } 49 | for (const key in outputMap) { 50 | if (moduleMap[key]) { 51 | moduleMap[key].file = getTargePkgPath(outputMap[key]) 52 | result.push(moduleMap[key]) 53 | } 54 | } 55 | return result 56 | } 57 | 58 | // create config 59 | const createConfig = (input, output = [], plugins = []) => { 60 | // Injection of information 61 | output = output.map((item) => { 62 | item.banner = banner 63 | item.name = pkg.name 64 | item.exports = 'auto' 65 | return item 66 | }) 67 | 68 | // Explain the globals configuration. This configuration means that I simply do not package external dependencies into bundles, but import them front-loaded or use them as dependencies installed 69 | const external = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})] 70 | 71 | const config = { 72 | input, 73 | output, 74 | external, 75 | plugins: [ 76 | // Parsing the typescript 77 | ts({ 78 | tsconfig: path.resolve(__dirname, 'tsconfig.json'), 79 | cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'), 80 | tsconfigOverride: { 81 | compilerOptions: { 82 | declaration: true, 83 | declarationMap: true 84 | }, 85 | exclude: ['**/__tests__', 'test-dts'] 86 | } 87 | }), 88 | // Parsing third-party modules 89 | nodeResolve(), 90 | // handling json imports 91 | json(), 92 | // replace target string in file 93 | replace({ 94 | preventAssignment: true, 95 | 'process.env.NODE_ENV': JSON.stringify(env) 96 | }), 97 | // handle babel 98 | babel({ 99 | exclude: '**/node_modules/**', 100 | babelHelpers: 'bundled' 101 | }), 102 | // convert CommonJS to ES2015 modules for Rollup to process 103 | commonjs(), 104 | // other plugin 105 | ...plugins 106 | ] 107 | } 108 | 109 | // is production terser code 110 | if (isProd) { 111 | config.plugins.push( 112 | terser({ 113 | compress: { 114 | pure_getters: true, 115 | unsafe: true, 116 | unsafe_comps: true, 117 | warnings: false 118 | } 119 | }) 120 | ) 121 | } 122 | return config 123 | } 124 | 125 | // create config 126 | const config = createConfig(getTargePkgPath('./src/index.ts'), createOutput({ main: './dist/index.js', module: './dist/index.esm.js', umd: './dist/index.umd.js' }), []) 127 | 128 | export default [config] 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uni-native-router 2 | 3 | [![](https://img.shields.io/badge/npm-v1.1.4-blue)](https://www.npmjs.com/package/uni-native-router) 4 | 5 | > 一个使用 typescript 封装 uniapp 原生路由 API 库,使用 uni-app 原生钩子实现和方法实现、hooks 的使用方式适配 vue3 6 | 7 | ## 介绍 8 | 9 | 1. 使用 uniapp 原生 api 封装,破坏性小,易于移植,使用上和原生差异小 10 | 2. 使用 Typescript 封装 11 | 3. 由于基于原生 API 封装,全平台兼容 12 | 4. 适配 vue3,可以使用类似[Composition API](https://v3.cn.vuejs.org/api/composition-api.html)开发 13 | 5. 封装了路由守卫功能 14 | 6. 可配置 404 页面拦截 15 | 16 | ## 安装 17 | 18 | ```shell 19 | npm install --save uni-native-router 20 | # or 21 | yarn add uni-native-router 22 | ``` 23 | 24 | ## 用法 25 | 26 | > 创建 router.ts 27 | 28 | ```js 29 | import { createRouter } from 'uni-native-router' 30 | export { useRoute, useRouter } from 'uni-native-router' // 导出适配vue3的hooks获取路由钩子方法 31 | import pages from '@/pages.json' // 导入路由配置文件 32 | import { App } from 'vue' 33 | 34 | // 创建路由对象 35 | export let router = createRouter({ routes: pages.pages }) 36 | 37 | // 设置路由器 38 | export const setupRouter = (app: App) => { 39 | app.use(router) 40 | } 41 | // 路由请求前拦截 42 | router.beforeEach(async (to: any, from: any, next: any) => { 43 | next() 44 | }) 45 | // 路由请求后拦截 46 | router.afterEach((to: any, from: any) => { 47 | // 逻辑代码 48 | }) 49 | ``` 50 | 51 | #### 注意 52 | 53 | 创建路由对象的时候需要传入路由配置对象(即 pages.json 里面的配置) 54 | 55 | 如果使用[vite](https://cn.vitejs.dev/guide/)构建,可以直接使用`import pages from '@/pages.json'`导入得到对应对象 56 | 57 | 如果使用[webpack](https://webpack.js.org/)构建在打包的时候可能会拿不到路由对象,在此,编写了读取 pages.json 的工具,可参考[read-pages](https://github.com/Gertyxs/uni-native-router/tree/master/packages/read-pages) 58 | 59 | > 在 main.ts 入口文件注册 60 | 61 | ```js 62 | import { createSSRApp } from 'vue' 63 | import App from '@/App.vue' 64 | import { setupRouter } from './router' 65 | 66 | export function createApp() { 67 | const app = createSSRApp(App) 68 | 69 | // 初始化路由 70 | setupRouter(app) 71 | 72 | return { 73 | app 74 | } 75 | } 76 | ``` 77 | 78 | > 在组件或页面里面使用 79 | 80 | ```html 81 | 86 | 87 | 101 | ``` 102 | 103 | ## 配置 404 页面 104 | 105 | > 如果在`pages.json`里面找不到对应的路由会尝试找到`pages.json`里面的`name`为`notfound`的页面不限大小写 106 | 107 | ```json 108 | { 109 | "path": "pages/not-found/index", 110 | "name": "notfound", 111 | "style": { 112 | "navigationBarTitleText": "NotFound" 113 | } 114 | } 115 | ``` 116 | 117 | ## 创建路由对象 118 | 119 | ### createRouter(CreateOptions) 120 | 121 | 此方法为创建路由对象,返回路由对象`router` 122 | 123 | ```typescript 124 | CreateOptions { 125 | routes: Route[] // 路由配置 126 | routeMethods?: string[] // 路由具有的方法 127 | } 128 | ``` 129 | 130 | ## API 131 | 132 | ### router.navigateTo(OBJECT) 133 | 134 | 此方法返回一个`Promise`对象 135 | 136 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#navigateto) 137 | 138 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 139 | 140 | ### router.redirectTo(OBJECT) 141 | 142 | 此方法返回一个`Promise`对象 143 | 144 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#redirectTo) 145 | 146 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 147 | 148 | ### router.relaunch(OBJECT) 149 | 150 | 此方法返回一个`Promise`对象 151 | 152 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#relaunch) 153 | 154 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 155 | 156 | ### router.switchtab(OBJECT) 157 | 158 | 此方法返回一个`Promise`对象 159 | 160 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#switchtab) 161 | 162 | ### router.navigateBack(OBJECT) 163 | 164 | 此方法返回一个`Promise`对象 165 | 166 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#navigateBack) 167 | 168 | ### router.beforeEach(cb) 169 | 170 | 路由前置守卫 171 | 172 | `cb`守卫回调函数会传`to、 from、 next` 参数进去,完成操作必须要调用`next`方法执行下一步 173 | 174 | ### router.afterEach(cb) 175 | 176 | 路由后置守卫 177 | 178 | `cb`守卫回调函数会传`to、 from 参数进去 179 | 180 | ## License 181 | 182 | [MIT](https://github.com/Gertyxs/vite-plugin-stylelint-serve/blob/master/LICENSE) 183 | -------------------------------------------------------------------------------- /packages/uni-native-router/README.md: -------------------------------------------------------------------------------- 1 | # uni-native-router 2 | 3 | [![](https://img.shields.io/badge/npm-v1.1.4-blue)](https://www.npmjs.com/package/uni-native-router) 4 | 5 | > 一个使用 typescript 封装 uniapp 原生路由 API 库,使用 uni-app 原生钩子实现和方法实现、hooks 的使用方式适配 vue3 6 | 7 | ## 介绍 8 | 9 | 1. 使用 uniapp 原生 api 封装,破坏性小,易于移植,使用上和原生差异小 10 | 2. 使用 Typescript 封装 11 | 3. 由于基于原生 API 封装,全平台兼容 12 | 4. 适配 vue3,可以使用类似[Composition API](https://v3.cn.vuejs.org/api/composition-api.html)开发 13 | 5. 封装了路由守卫功能 14 | 6. 可配置 404 页面拦截 15 | 16 | ## 安装 17 | 18 | ```shell 19 | npm install --save uni-native-router 20 | # or 21 | yarn add uni-native-router 22 | ``` 23 | 24 | ## 用法 25 | 26 | > 创建 router.ts 27 | 28 | ```js 29 | import { createRouter } from 'uni-native-router' 30 | export { useRoute, useRouter } from 'uni-native-router' // 导出适配vue3的hooks获取路由钩子方法 31 | import pages from '@/pages.json' // 导入路由配置文件 32 | import { App } from 'vue' 33 | 34 | // 创建路由对象 35 | export let router = createRouter({ routes: pages.pages }) 36 | 37 | // 设置路由器 38 | export const setupRouter = (app: App) => { 39 | app.use(router) 40 | } 41 | // 路由请求前拦截 42 | router.beforeEach(async (to: any, from: any, next: any) => { 43 | next() 44 | }) 45 | // 路由请求后拦截 46 | router.afterEach((to: any, from: any) => { 47 | // 逻辑代码 48 | }) 49 | ``` 50 | 51 | #### 注意 52 | 53 | 创建路由对象的时候需要传入路由配置对象(即 pages.json 里面的配置) 54 | 55 | 如果使用[vite](https://cn.vitejs.dev/guide/)构建,可以直接使用`import pages from '@/pages.json'`导入得到对应对象 56 | 57 | 如果使用[webpack](https://webpack.js.org/)构建在打包的时候可能会拿不到路由对象,在此,编写了读取 pages.json 的工具,可参考[read-pages](https://github.com/Gertyxs/uni-native-router/tree/master/packages/read-pages) 58 | 59 | > 在 main.ts 入口文件注册 60 | 61 | ```js 62 | import { createSSRApp } from 'vue' 63 | import App from '@/App.vue' 64 | import { setupRouter } from './router' 65 | 66 | export function createApp() { 67 | const app = createSSRApp(App) 68 | 69 | // 初始化路由 70 | setupRouter(app) 71 | 72 | return { 73 | app 74 | } 75 | } 76 | ``` 77 | 78 | > 在组件或页面里面使用 79 | 80 | ```html 81 | 86 | 87 | 101 | ``` 102 | 103 | ## 配置 404 页面 104 | 105 | > 如果在`pages.json`里面找不到对应的路由会尝试找到`pages.json`里面的`name`为`notfound`的页面不限大小写 106 | 107 | ```json 108 | { 109 | "path": "pages/not-found/index", 110 | "name": "notfound", 111 | "style": { 112 | "navigationBarTitleText": "NotFound" 113 | } 114 | } 115 | ``` 116 | 117 | ## 创建路由对象 118 | 119 | ### createRouter(CreateOptions) 120 | 121 | 此方法为创建路由对象,返回路由对象`router` 122 | 123 | ```typescript 124 | CreateOptions { 125 | routes: Route[] // 路由配置 126 | routeMethods?: string[] // 路由具有的方法 127 | } 128 | ``` 129 | 130 | ## API 131 | 132 | ### router.navigateTo(OBJECT) 133 | 134 | 此方法返回一个`Promise`对象 135 | 136 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#navigateto) 137 | 138 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 139 | 140 | ### router.redirectTo(OBJECT) 141 | 142 | 此方法返回一个`Promise`对象 143 | 144 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#redirectTo) 145 | 146 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 147 | 148 | ### router.relaunch(OBJECT) 149 | 150 | 此方法返回一个`Promise`对象 151 | 152 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#relaunch) 153 | 154 | 增加`query`参数对象,便于传参数,同时也兼容`'path?key=value&key2=value2'`写法 155 | 156 | ### router.switchtab(OBJECT) 157 | 158 | 此方法返回一个`Promise`对象 159 | 160 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#switchtab) 161 | 162 | ### router.navigateBack(OBJECT) 163 | 164 | 此方法返回一个`Promise`对象 165 | 166 | OBJECT 参数同[uniapp](https://uniapp.dcloud.net.cn/api/router.html#navigateBack) 167 | 168 | ### router.beforeEach(cb) 169 | 170 | 路由前置守卫 171 | 172 | `cb`守卫回调函数会传`to、 from、 next` 参数进去,完成操作必须要调用`next`方法执行下一步 173 | 174 | ### router.afterEach(cb) 175 | 176 | 路由后置守卫 177 | 178 | `cb`守卫回调函数会传`to、 from 参数进去 179 | 180 | ## License 181 | 182 | [MIT](https://github.com/Gertyxs/vite-plugin-stylelint-serve/blob/master/LICENSE) 183 | -------------------------------------------------------------------------------- /scripts/utils.mjs: -------------------------------------------------------------------------------- 1 | import minimist from 'minimist' 2 | import fs from 'fs-extra' 3 | import shell from 'shelljs' 4 | import chalk from 'chalk' 5 | import semver from 'semver' 6 | import path from 'path' 7 | import enquirer from 'enquirer' 8 | import { fileURLToPath } from 'url' 9 | const { prompt } = enquirer 10 | 11 | export const args = minimist(process.argv.slice(2)) 12 | export const isDryRun = !!args.dry 13 | 14 | export const __filename = fileURLToPath(import.meta.url) 15 | export const __dirname = path.dirname(__filename) 16 | export const cwdPath = process.cwd() 17 | export const inc = (currentVersion, releaseType, preId) => semver.inc(currentVersion, releaseType, preId) 18 | export const run = (command, options = { silent: false }, callback) => shell.exec(command, options, callback) 19 | export const dryRun = (command) => console.log(chalk.blue(`[dryrun] ${command}`)) 20 | export const runIfNotDry = isDryRun ? dryRun : run 21 | export const step = (msg) => console.log(chalk.cyan(msg)) 22 | export const resolve = (filePath) => path.resolve(cwdPath, filePath) 23 | export const getPkgRoot = (pkg) => path.resolve(cwdPath, './packages/' + pkg) 24 | export const packages = fs.readdirSync(resolve('./packages')).filter((p) => !p.endsWith('.ts') && !p.startsWith('.js') && !p.startsWith('.')) 25 | 26 | // 构建生成版本号 27 | export const generateVersion = async (targetVersion, currentVersion) => { 28 | const preId = args.preid || (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0]) // ['beta', 1] 29 | const versionIncrements = ['patch', 'minor', 'major', ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])] 30 | 31 | if (!targetVersion) { 32 | // no explicit version, offer suggestions 33 | const { release } = await prompt({ 34 | type: 'select', 35 | name: 'release', 36 | message: 'Select release type', 37 | choices: versionIncrements.map((i) => `${i} (${inc(currentVersion, i, preId)})`).concat(['custom', 'currentVersion']) 38 | }) 39 | 40 | if (release === 'custom') { 41 | targetVersion = ( 42 | await prompt({ 43 | type: 'input', 44 | name: 'version', 45 | message: 'Input custom version', 46 | initial: currentVersion 47 | }) 48 | ).version 49 | } else if (release === 'currentVersion') { 50 | targetVersion = currentVersion 51 | } else { 52 | targetVersion = release.match(/\((.*)\)/)[1] 53 | } 54 | } 55 | 56 | if (!semver.valid(targetVersion)) { 57 | throw new Error(`invalid target version: ${targetVersion}`) 58 | } 59 | 60 | const { yes } = await prompt({ 61 | type: 'confirm', 62 | name: 'yes', 63 | message: `Releasing v${targetVersion}. Confirm?` 64 | }) 65 | 66 | if (!yes) { 67 | return null 68 | } 69 | return targetVersion 70 | } 71 | 72 | // 发布包 73 | export const publishPackage = async (pkgName, version) => { 74 | const pkgRoot = getPkgRoot(pkgName) 75 | const pkgPath = path.resolve(pkgRoot, 'package.json') 76 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) 77 | if (pkg.private) { 78 | return 79 | } 80 | 81 | let releaseTag = null 82 | if (args.tag) { 83 | releaseTag = args.tag 84 | } else if (version.includes('alpha')) { 85 | releaseTag = 'alpha' 86 | } else if (version.includes('beta')) { 87 | releaseTag = 'beta' 88 | } else if (version.includes('rc')) { 89 | releaseTag = 'rc' 90 | } 91 | 92 | step(`Publishing ${pkgName}...`) 93 | try { 94 | let command = 'npm publish --registry=https://registry.npmjs.org' 95 | if (releaseTag) { 96 | command += ` --tag ${releaseTag}` 97 | } 98 | shell.cd(pkgRoot) // cd package dir 99 | runIfNotDry(command) 100 | console.log(chalk.green(`Successfully published ${pkgName}@${version}`)) 101 | } catch (e) { 102 | if (e.stderr.match(/previously published/)) { 103 | console.log(chalk.red(`Skipping already published: ${pkgName}`)) 104 | } else { 105 | throw e 106 | } 107 | } 108 | } 109 | 110 | // 更新package.json内容 111 | export const updatePackage = (pkgRoot, pkgObj) => { 112 | const pkgPath = path.resolve(pkgRoot, 'package.json') 113 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) 114 | for (const key in pkgObj) { 115 | pkg[key] = pkgObj[key] 116 | } 117 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n') 118 | } 119 | 120 | // 更新版本号 121 | export const updateVersions = (version) => { 122 | // 1. update root package.json 123 | updatePackage(resolve('.'), { version }) 124 | // 2. update all packages 125 | packages.forEach((p) => updatePackage(getPkgRoot(p), { version })) 126 | } 127 | 128 | // 获取package.json 内容 129 | export const getPackageInfo = (pkgPath) => { 130 | if (!fs.existsSync(pkgPath)) { 131 | throw new Error(`${pkgPath} not found`) 132 | } 133 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) 134 | return pkg 135 | } 136 | -------------------------------------------------------------------------------- /packages/uni-native-router/src/index.ts: -------------------------------------------------------------------------------- 1 | import { deepClone, isString, isObject } from './utils' 2 | import { parseQuery, stringifyQuery } from './query' 3 | import { CreateOptions, BackParams, Route, Router, BeforeEach, AfterEach, RouteMeta } from './types' 4 | export let router: Router 5 | 6 | /** 7 | * 创建路由 8 | * @param options 创建路由配置 9 | * @returns {Router} 10 | */ 11 | export const createRouter = (options: CreateOptions) => { 12 | const pagesRoutes = options.routes || [] 13 | let subPackagesRoutes = [] as Route[] 14 | options.subPackages?.length !== 0 && 15 | options.subPackages?.map((pkg) => { 16 | const pages = pkg.pages.map((page: Route) => Object.assign({}, page, { path: `${pkg.root}/${page.path}` })) 17 | subPackagesRoutes = subPackagesRoutes.concat(pages) 18 | }) 19 | const routes = pagesRoutes.concat(subPackagesRoutes) 20 | const routeMethods = options.routeMethods ? options.routeMethods : ['navigateTo', 'switchTab', 'reLaunch', 'redirectTo', 'navigateBack'] 21 | const routeMeta: RouteMeta = { to: {}, from: {} } // 路由元信息 22 | const beforeEach: BeforeEach[] = [] // 路由跳转前的拦截方法 23 | const afterEach: AfterEach[] = [] // 路由跳转后的拦截方法 24 | let isLaunch = false // 程序是否已经运行了 25 | let isOperateAPI = false // 是否操作API跳转 26 | 27 | /** 28 | * 获取当前页面 29 | * @returns 当前页面 30 | */ 31 | const getCurrentPage = () => { 32 | const pages = getCurrentPages() 33 | return pages.length > 0 ? pages[pages.length - 1] : undefined 34 | } 35 | 36 | /** 37 | * 获取页面路径 38 | * @param page 39 | */ 40 | const getPagePath = (page: any | undefined) => { 41 | page = page || {} 42 | return isString(page.route) ? page.route : (page as any).$page.route 43 | } 44 | 45 | /** 46 | * 运行带有拦截功能的函数队列 47 | * @param fnList 48 | * @param to 49 | * @param from 50 | * @returns {Promise<*>} 51 | */ 52 | const callWithNext = (fnList: any, to: Route, from: Route) => { 53 | const allWithNext = fnList.map((fn: any) => { 54 | return new Promise((resolve, reject) => { 55 | fn(to, from, (value?: Route | boolean | string) => { 56 | if (typeof value === 'undefined') { 57 | return resolve(true) 58 | } 59 | if (typeof value === 'boolean') { 60 | if (value) return resolve(true) 61 | if (!value) return reject('路由跳转失败') 62 | } 63 | if (isObject(value)) { 64 | return resolve({ type: 'navigateTo', ...(value as Route) }) 65 | } 66 | if (isString(value)) { 67 | return resolve({ path: value as string, type: 'navigateTo' }) 68 | } 69 | reject(false) 70 | }) 71 | }) 72 | }) 73 | return Promise.all(allWithNext) 74 | } 75 | 76 | /** 77 | * 运行带有拦截功能的函数队列 78 | * @param fnList 79 | * @param to 80 | * @param from 81 | * @returns {void} 82 | */ 83 | const callWithoutNext = (fnList: any, to: Route, from: Route) => { 84 | if (fnList && fnList.length > 0) { 85 | fnList.forEach((fn: any) => { 86 | fn(to, from) 87 | }) 88 | } 89 | } 90 | 91 | /** 92 | * 匹配路由可以通过name或者path匹配 93 | * @param route 94 | * @returns {Route} 95 | */ 96 | const matchRoute = (route: Route) => { 97 | // eslint-disable-next-line prefer-const 98 | let { path, name, query } = route 99 | const _route: Route = Object.create(null) 100 | _route.query = {} 101 | if (path) { 102 | _route.fullPath = path 103 | const pairs = path.split('?') 104 | path = pairs[0] 105 | if (pairs.length >= 2) { 106 | _route.query = parseQuery(pairs[1]) 107 | } 108 | } 109 | if (query) { 110 | _route.query = { ..._route.query, ...query } 111 | } 112 | let targetRoute = routes.find((r: Route) => { 113 | // 如果是首页 直接返回第一个路由配置 114 | if (path === '/' || path === '') { 115 | return true 116 | } 117 | if (name) { 118 | return r.name === name 119 | } 120 | return r.path === path!.replace(/^\//, '') 121 | }) 122 | if (targetRoute) { 123 | return { ...deepClone(targetRoute), ..._route } 124 | } else { 125 | // 如果匹配不到对应的路由 尝试匹配 404 页面 name 为 notfound 不限大小写 126 | targetRoute = routes.find((r: Route) => r.name && r.name.toLowerCase() === 'notfound') 127 | if (targetRoute) { 128 | return { ...deepClone(targetRoute), ..._route } 129 | } 130 | } 131 | return null 132 | } 133 | 134 | /** 135 | * 处理路由对象 136 | * @param route 路由参数 137 | * @returns {Route} 138 | */ 139 | const handleRoute = (route: Route | string) => { 140 | let result: Route = { type: 'navigateTo' } 141 | if (isString(route)) { 142 | result.path = route as string 143 | } 144 | if (isObject(route)) { 145 | route = route as Route 146 | // 兼容 url 和 path写法 147 | if (route.url && !route.path) { 148 | route.path = route.url 149 | } 150 | result = { ...result, ...route } 151 | } 152 | return result 153 | } 154 | 155 | /** 156 | * 匹配to路由 157 | * @param route 路由参数 158 | * @returns {Route} 159 | */ 160 | const matchToRoute = (route: Route) => { 161 | // eslint-disable-next-line prefer-const 162 | let { path, name, query, type } = route 163 | if (type && !routeMethods.includes(type)) { 164 | throw new Error(`type必须是以下的值${routeMethods}`) 165 | } 166 | // 返回操作比较特别 167 | if (type === 'navigateBack') { 168 | const { delta = 1 } = route 169 | const stackRoutes = getCurrentPages() // 当前已经入栈的路由 170 | if (stackRoutes.length >= 1) { 171 | const to = stackRoutes.length - 1 - delta >= 0 ? stackRoutes[stackRoutes.length - 1 - delta] : stackRoutes[0] 172 | path = getPagePath(to) 173 | } 174 | } 175 | // 如果 path 不是以 / 开头 176 | if (path && path.indexOf('/') !== 0) { 177 | path = '/' + path 178 | } 179 | // 匹配路由 180 | routeMeta.to = matchRoute({ path, name, query }) 181 | if (!routeMeta.to) { 182 | throw new Error('找不到对应的路由配置') 183 | } 184 | } 185 | 186 | /** 187 | * 匹配from路由 188 | * @param route 路由参数 189 | * @returns {Route} 190 | */ 191 | const matchFromRoute = () => { 192 | const stackRoutes = getCurrentPages() // 当前已经入栈的路由 193 | if (stackRoutes.length > 0) { 194 | const from = stackRoutes[stackRoutes.length - 1] // 上一个路由 195 | const path = getPagePath(from) 196 | routeMeta.from = matchRoute({ path }) 197 | } else { 198 | // 如果没有历史记录 取第一个作为from 199 | if (routes.length > 0) { 200 | routeMeta.from = deepClone(routes[0]) 201 | routeMeta.from!.fullPath = routeMeta.from!.path 202 | routeMeta.from!.query = {} 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * 调用下一步 209 | * @returns {Promise<*>} 210 | */ 211 | const next = () => { 212 | matchFromRoute() 213 | if (beforeEach && beforeEach.length > 0) { 214 | return callWithNext(beforeEach, routeMeta.to, routeMeta.from) 215 | } else { 216 | return Promise.resolve() 217 | } 218 | } 219 | 220 | /** 221 | * 路由跳转内部方法 222 | * @param route 223 | * @returns {void} 224 | */ 225 | const routeTo = (route: Route) => { 226 | return new Promise((resolve: any, reject) => { 227 | if (route.isLaunch) { 228 | return resolve() 229 | } 230 | // const jump = (uni as any)[type] // 这种使用会被vite treeShaking 掉uni里面的方法 231 | // 为了方法不被 vite treeShaking掉,使用显式赋值 232 | // eslint-disable-next-line prefer-const 233 | let { type, url, ...reset } = route 234 | let jump: any = uni.navigateTo 235 | if (type === 'navigateTo') jump = uni.navigateTo 236 | if (type === 'switchTab') jump = uni.switchTab 237 | if (type === 'reLaunch') jump = uni.reLaunch 238 | if (type === 'redirectTo') jump = uni.redirectTo 239 | if (type === 'navigateBack') jump = uni.navigateBack 240 | const queryStr = stringifyQuery(routeMeta.to!.query) 241 | url = `/${routeMeta.to!.path}` 242 | if (queryStr) { 243 | url += `?${queryStr}` 244 | } 245 | jump({ 246 | ...reset, 247 | url: url, 248 | success: resolve, 249 | fail: reject 250 | }) 251 | }) 252 | } 253 | 254 | /** 255 | * 路由跳转方法 256 | * @param route 257 | * @returns {Promise<*>} 258 | */ 259 | const push = (route: Route | string) => { 260 | return new Promise((resolve: any, reject: any) => { 261 | route = handleRoute(route) 262 | try { 263 | // 匹配路由 264 | matchToRoute(route) 265 | // 符合要求下一步 266 | next().then((nextRes: any) => { 267 | nextRes = nextRes || [] 268 | routeTo(route as Route).then(() => { 269 | resolve() 270 | callWithoutNext(afterEach, routeMeta.to, routeMeta.from) 271 | // 路由钩子是否存在重定向 272 | nextRes.forEach((r: Route) => { 273 | if (isObject(r)) { 274 | let _route = { type: 'navigateTo', ...(r as Route) } as Route 275 | _route = handleRoute(_route) 276 | matchToRoute(_route) 277 | isOperateAPI = true 278 | push(_route) 279 | } 280 | }) 281 | }) 282 | }) 283 | } catch (error) { 284 | reject(error) 285 | } 286 | }) 287 | } 288 | 289 | router = { 290 | navigateTo(route: Route) { 291 | isOperateAPI = true 292 | return push({ ...route, type: 'navigateTo' }) 293 | }, 294 | switchTab(route: Route) { 295 | isOperateAPI = true 296 | return push({ ...route, type: 'switchTab' }) 297 | }, 298 | reLaunch(route: Route) { 299 | isOperateAPI = true 300 | return push({ ...route, type: 'reLaunch' }) 301 | }, 302 | redirectTo(route: Route) { 303 | isOperateAPI = true 304 | return push({ ...route, type: 'redirectTo' }) 305 | }, 306 | navigateBack(route?: BackParams) { 307 | isOperateAPI = true 308 | return push({ ...route, type: 'navigateBack' }) 309 | }, 310 | beforeEach(fn: BeforeEach) { 311 | beforeEach.push(fn) 312 | }, 313 | afterEach(fn: AfterEach) { 314 | afterEach.push(fn) 315 | }, 316 | routeMeta, 317 | install(app: any) { 318 | app.mixin({ 319 | onLaunch(options: any) { 320 | router.routeMeta.to.query = options.query 321 | }, 322 | onShow() { 323 | if (this.$mpType === 'page') { 324 | const page = getCurrentPage() 325 | const path = (page as any).$page.fullPath 326 | if (!isLaunch && !isOperateAPI) { 327 | push({ path, type: 'redirectTo', isLaunch: true }) // isLaunch 设置为允许,直接不跳转 328 | } 329 | if (isLaunch && !isOperateAPI && routeMeta.to.path !== path) { 330 | push({ path, type: 'redirectTo', isLaunch: true }) // isLaunch 设置为允许,直接不跳转 331 | } 332 | isLaunch = true 333 | isOperateAPI = false 334 | } 335 | } 336 | }) 337 | } 338 | } 339 | return router 340 | } 341 | 342 | /** 343 | * 钩子函数 返回路由操作对象 344 | * @returns {Router} 345 | */ 346 | export const useRouter = (): Router => { 347 | if (!router) { 348 | throw new Error('路由还没初始化') 349 | } 350 | return router 351 | } 352 | 353 | /** 354 | * 钩子函数 返回当前路由对象 355 | * @returns {Route} 356 | */ 357 | export const useRoute = (): Route => { 358 | if (!router) { 359 | throw new Error('路由还没初始化') 360 | } 361 | if (router.routeMeta.to && Object.keys(router.routeMeta.to).length > 0) { 362 | return router.routeMeta.to 363 | } else { 364 | return { query: {}, path: '' } 365 | } 366 | } 367 | --------------------------------------------------------------------------------