├── .eslintignore ├── examples └── vue │ ├── src │ ├── views │ │ ├── about │ │ │ └── index.vue │ │ ├── demo │ │ │ └── index.vue │ │ ├── test │ │ │ └── index.vue │ │ ├── home │ │ │ ├── fifth │ │ │ │ └── index.vue │ │ │ ├── four │ │ │ │ └── index.vue │ │ │ ├── third │ │ │ │ └── index.vue │ │ │ ├── first-page │ │ │ │ └── index.vue │ │ │ ├── multi │ │ │ │ ├── first │ │ │ │ │ └── index.vue │ │ │ │ ├── second │ │ │ │ │ └── index.vue │ │ │ │ └── third │ │ │ │ │ ├── third-child1 │ │ │ │ │ └── index.vue │ │ │ │ │ ├── third-child2 │ │ │ │ │ └── index.vue │ │ │ │ │ └── third-child3 │ │ │ │ │ └── index.ts │ │ │ └── second-page │ │ │ │ └── index.vue │ │ ├── _builtin │ │ │ ├── 404 │ │ │ │ └── index.vue │ │ │ └── login │ │ │ │ ├── index.vue │ │ │ │ └── components │ │ │ │ └── demo-com │ │ │ │ └── index.vue │ │ ├── one │ │ │ └── two │ │ │ │ ├── three-1 │ │ │ │ └── index.vue │ │ │ │ ├── three │ │ │ │ └── index.vue │ │ │ │ └── three-ano │ │ │ │ └── index.vue │ │ ├── HomeView.vue │ │ ├── AboutView.vue │ │ └── index.ts │ ├── styles │ │ ├── scss │ │ │ ├── global.scss │ │ │ └── scrollbar.scss │ │ └── css │ │ │ ├── global.css │ │ │ ├── scrollbar.css │ │ │ ├── transition.css │ │ │ └── reset.css │ ├── plugins │ │ ├── index.ts │ │ └── assets.ts │ ├── router │ │ ├── modules │ │ │ ├── demo.ts │ │ │ ├── test.ts │ │ │ ├── about.ts │ │ │ ├── one.ts │ │ │ └── home.ts │ │ └── index.ts │ ├── stores │ │ ├── index.ts │ │ └── counter.ts │ ├── assets │ │ ├── logo.svg │ │ └── base.css │ ├── components │ │ ├── icons │ │ │ ├── IconSupport.vue │ │ │ ├── IconTooling.vue │ │ │ ├── IconCommunity.vue │ │ │ ├── IconDocumentation.vue │ │ │ └── IconEcosystem.vue │ │ ├── HelloWorld.vue │ │ ├── WelcomeItem.vue │ │ └── TheWelcome.vue │ ├── main.ts │ ├── App.vue │ └── typings │ │ ├── components.d.ts │ │ ├── page-route.d.ts │ │ └── route.d.ts │ ├── .eslintignore │ ├── commitlint.config.js │ ├── public │ └── favicon.ico │ ├── .npmrc │ ├── .editorconfig │ ├── README.md │ ├── .gitattributes │ ├── index.html │ ├── .gitignore │ ├── tsconfig.json │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── package.json │ ├── vite.config.ts │ ├── .cz-config.js │ ├── uno.config.ts │ └── .eslintrc.js ├── pnpm-workspace.yaml ├── .npmrc ├── src ├── debug.js ├── shared │ ├── index.ts │ ├── fs-extra.ts │ ├── eslint.ts │ ├── constant.ts │ ├── glob.ts │ ├── option.ts │ └── route.ts ├── index.ts ├── context │ ├── declaration.ts │ ├── module.ts │ ├── views.ts │ ├── index.ts │ └── fs.ts └── types │ └── index.ts ├── .eslintrc.js ├── .gitattributes ├── tsconfig.json ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── README.en_US.md ├── package.json └── CHANGELOG.md /.eslintignore: -------------------------------------------------------------------------------- 1 | components.d.ts 2 | 3 | -------------------------------------------------------------------------------- /examples/vue/src/views/about/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/demo/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/test/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/fifth/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/four/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/third/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/.eslintignore: -------------------------------------------------------------------------------- 1 | components.d.ts 2 | -------------------------------------------------------------------------------- /examples/vue/src/views/_builtin/404/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/_builtin/login/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/first-page/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/one/two/three-1/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/one/two/three/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/multi/first/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/multi/second/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/second-page/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/one/two/three-ano/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/multi/third/third-child1/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/multi/third/third-child2/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/home/multi/third/third-child3/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/views/_builtin/login/components/demo-com/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/styles/scss/global.scss: -------------------------------------------------------------------------------- 1 | @import "./scrollbar.scss"; 2 | -------------------------------------------------------------------------------- /examples/vue/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /examples/vue/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import setupAssets from './assets'; 2 | 3 | export { setupAssets }; 4 | -------------------------------------------------------------------------------- /examples/vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soybeanjs/vite-plugin-vue-page-route/HEAD/examples/vue/public/favicon.ico -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | auto-install-peers=true 5 | -------------------------------------------------------------------------------- /examples/vue/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | auto-install-peers=true 5 | -------------------------------------------------------------------------------- /examples/vue/src/styles/css/global.css: -------------------------------------------------------------------------------- 1 | @import "./transition.css"; 2 | @import "./reset.css"; 3 | 4 | html, 5 | body, 6 | #app { 7 | height: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /examples/vue/src/plugins/assets.ts: -------------------------------------------------------------------------------- 1 | import 'uno.css'; 2 | 3 | /** import assets, such as js, css files. */ 4 | export default function setupAssets() { 5 | // 6 | } 7 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | const { register } = require('esbuild-register/dist/node'); 2 | 3 | register(); 4 | 5 | const plugin = require('./index.ts'); 6 | 7 | module.exports = plugin.default; 8 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constant'; 2 | export * from './option'; 3 | export * from './glob'; 4 | export * from './route'; 5 | export * from './eslint'; 6 | export * from './fs-extra'; 7 | -------------------------------------------------------------------------------- /examples/vue/src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /src/shared/fs-extra.ts: -------------------------------------------------------------------------------- 1 | export async function useFsExtra() { 2 | const { remove, ensureFile, writeFile } = await import('fs-extra'); 3 | 4 | return { 5 | remove, 6 | ensureFile, 7 | writeFile 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['soybeanjs'], 3 | overrides: [ 4 | { 5 | files: ['./scripts/*.ts'], 6 | rules: { 7 | 'no-unused-expressions': 'off' 8 | } 9 | } 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /examples/vue/src/router/modules/demo.ts: -------------------------------------------------------------------------------- 1 | const demo: AuthRoute.Route = { 2 | name: 'demo', 3 | path: '/demo', 4 | component: 'self', 5 | meta: { title: 'demo', icon: 'mdi:menu', singleLayout: 'basic' } 6 | }; 7 | 8 | export default demo; 9 | -------------------------------------------------------------------------------- /examples/vue/src/router/modules/test.ts: -------------------------------------------------------------------------------- 1 | const test: AuthRoute.Route = { 2 | name: 'test', 3 | path: '/test', 4 | component: 'self', 5 | meta: { title: 'test', icon: 'mdi:menu', singleLayout: 'basic' } 6 | }; 7 | 8 | export default test; 9 | -------------------------------------------------------------------------------- /examples/vue/src/router/modules/about.ts: -------------------------------------------------------------------------------- 1 | const about: AuthRoute.Route = { 2 | name: 'about', 3 | path: '/about', 4 | component: 'self', 5 | meta: { title: 'about', icon: 'mdi:menu', singleLayout: 'basic' } 6 | }; 7 | 8 | export default about; 9 | -------------------------------------------------------------------------------- /examples/vue/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | export function setupStore(app: App) { 5 | const store = createPinia(); 6 | app.use(store); 7 | } 8 | 9 | export * from './counter'; 10 | -------------------------------------------------------------------------------- /examples/vue/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 2 9 | end_of_line = lf 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | # vite-vue3-ts-starter 2 | 3 | This template is based on the template created by create-vue, which add eslint、prettier、husky、commitlint to format code. 4 | 5 | on branch unit-test, which has unit test tool vitest. 6 | 7 | on branch unocss, which has atomic css tool. 8 | -------------------------------------------------------------------------------- /examples/vue/src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /examples/vue/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue/src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | "*.vue" eol=lf 2 | "*.js" eol=lf 3 | "*.ts" eol=lf 4 | "*.jsx" eol=lf 5 | "*.tsx" eol=lf 6 | "*.cjs" eol=lf 7 | "*.cts" eol=lf 8 | "*.mjs" eol=lf 9 | "*.mts" eol=lf 10 | "*.json" eol=lf 11 | "*.html" eol=lf 12 | "*.css" eol=lf 13 | "*.less" eol=lf 14 | "*.scss" eol=lf 15 | "*.sass" eol=lf 16 | "*.styl" eol=lf 17 | -------------------------------------------------------------------------------- /examples/vue/src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useCounterStore = defineStore({ 4 | id: 'counter', 5 | state: () => ({ 6 | counter: 0 7 | }), 8 | getters: { 9 | doubleCount: state => state.counter * 2 10 | }, 11 | actions: { 12 | increment() { 13 | this.counter += 1; 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /examples/vue/.gitattributes: -------------------------------------------------------------------------------- 1 | "*.vue" eol=lf 2 | "*.js" eol=lf 3 | "*.ts" eol=lf 4 | "*.jsx" eol=lf 5 | "*.tsx" eol=lf 6 | "*.cjs" eol=lf 7 | "*.cts" eol=lf 8 | "*.mjs" eol=lf 9 | "*.mts" eol=lf 10 | "*.json" eol=lf 11 | "*.html" eol=lf 12 | "*.css" eol=lf 13 | "*.less" eol=lf 14 | "*.scss" eol=lf 15 | "*.sass" eol=lf 16 | "*.styl" eol=lf 17 | -------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vite-vue3-ts-starter 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/shared/eslint.ts: -------------------------------------------------------------------------------- 1 | import { access } from 'fs/promises'; 2 | 3 | export async function handleEslintFormat(filePath: string) { 4 | const { execa } = await import('execa'); 5 | 6 | const eslintBinPath = `${process.cwd()}/node_modules/eslint/bin/eslint.js`; 7 | 8 | try { 9 | await access(eslintBinPath); 10 | await execa('node', [eslintBinPath, filePath, '--fix']); 11 | } catch {} 12 | } 13 | -------------------------------------------------------------------------------- /examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | !.vscode/settings.json 24 | .idea 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /examples/vue/src/styles/scss/scrollbar.scss: -------------------------------------------------------------------------------- 1 | @mixin scrollbar($size: 8px, $color: #d9d9d9) { 2 | &::-webkit-scrollbar-thumb { 3 | background-color: $color; 4 | border-radius: $size; 5 | } 6 | &::-webkit-scrollbar-thumb:hover { 7 | background-color: $color; 8 | border-radius: $size; 9 | } 10 | &::-webkit-scrollbar { 11 | width: $size; 12 | height: $size; 13 | } 14 | &::-webkit-scrollbar-track-piece { 15 | background-color: rgba(0, 0, 0, 0); 16 | border-radius: 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "lib": ["DOM", "ESNext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "types": ["vite/client", "node"] 17 | }, 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | stats.html 17 | 18 | /cypress/videos/ 19 | /cypress/screenshots/ 20 | 21 | # Editor directories and files 22 | .vscode/* 23 | !.vscode/launch.json 24 | !.vscode/extensions.json 25 | !.vscode/settings.json 26 | .idea 27 | *.suo 28 | *.ntvs* 29 | *.njsproj 30 | *.sln 31 | *.sw? 32 | 33 | /src/typings/components.d.ts 34 | package-lock.json 35 | yarn.lock 36 | -------------------------------------------------------------------------------- /examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { setupAssets } from './plugins'; 3 | import { setupStore } from './stores'; 4 | import { setupRouter } from './router'; 5 | import App from './App.vue'; 6 | 7 | async function setupApp() { 8 | // import assets: js、css 9 | setupAssets(); 10 | 11 | // create app instance 12 | const app = createApp(App); 13 | 14 | // install store plugin: pinia 15 | setupStore(app); 16 | 17 | // vue router 18 | await setupRouter(app); 19 | 20 | // mount app on the dom 21 | app.mount('#app'); 22 | } 23 | 24 | setupApp(); 25 | -------------------------------------------------------------------------------- /examples/vue/src/styles/css/scrollbar.css: -------------------------------------------------------------------------------- 1 | /*---滚动条默认显示样式--*/ 2 | ::-webkit-scrollbar-thumb { 3 | background-color: #e6e6e6; 4 | border-radius: 6px; 5 | } 6 | /*---鼠标点击滚动条显示样式--*/ 7 | ::-webkit-scrollbar-thumb:hover { 8 | background-color: #e6e6e6; 9 | border-radius: 6px; 10 | } 11 | /*---滚动条大小--*/ 12 | ::-webkit-scrollbar { 13 | width: 6px; 14 | height: 6px; 15 | } 16 | 17 | /*---滚动框背景样式--*/ 18 | ::-webkit-scrollbar-track-piece { 19 | background-color: rgba(0, 0, 0, 0); 20 | border-radius: 0; 21 | } 22 | 23 | html { 24 | scrollbar-width: thin; 25 | scrollbar-color: e6e6e6 transparent; 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite'; 2 | import Context from './context'; 3 | import type { PluginOption } from './types'; 4 | 5 | /** 6 | * A vite plugin for vue, auto generate route info by page 7 | * @param options plugin options 8 | */ 9 | function pageRoute(options?: Partial) { 10 | const context = new Context(options); 11 | 12 | const plugin: Plugin = { 13 | name: 'vite-plugin-vue-page-route', 14 | enforce: 'post', 15 | configureServer() { 16 | context.setupFileWatcher(); 17 | } 18 | }; 19 | 20 | return plugin; 21 | } 22 | 23 | export default pageRoute; 24 | 25 | export type { PluginOption }; 26 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "lib": ["DOM", "ESNext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "paths": { 17 | "~/*": ["./*"], 18 | "@/*": ["./src/*"] 19 | }, 20 | "types": ["node", "vite/client"] 21 | }, 22 | "exclude": ["node_modules", "dist"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/vue/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import HomeView from '../views/HomeView.vue'; 4 | 5 | const router = createRouter({ 6 | history: createWebHistory(import.meta.env.BASE_URL), 7 | routes: [ 8 | { 9 | path: '/', 10 | name: 'home', 11 | component: HomeView 12 | }, 13 | { 14 | path: '/about', 15 | name: 'about', 16 | component: () => import('../views/AboutView.vue') 17 | } 18 | ] 19 | }); 20 | 21 | export async function setupRouter(app: App) { 22 | app.use(router); 23 | // 路由守卫createRouterGuard(router); 24 | await router.isReady(); 25 | } 26 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/shared/constant.ts: -------------------------------------------------------------------------------- 1 | export const PAGE_DIR = 'src/views'; 2 | 3 | export const PAGE_GLOB = ['**/index.{vue,tsx,jsx}', '!**/components/**']; 4 | 5 | export const ROUTE_DTS = 'src/typings/page-route.d.ts'; 6 | 7 | export const ROUTE_MODULE_DIR = 'src/router/modules'; 8 | 9 | export const ROUTE_MODULE_EXT = 'ts'; 10 | 11 | export const ROUTE_MODULE_TYPE = 'AuthRoute.Route'; 12 | 13 | export const ROOT_ROUTE = 'root'; 14 | 15 | export const NOT_FOUND_ROUTE = 'not-found'; 16 | 17 | export const PAGE_DEGREE_SPLIT_MARK = '_'; 18 | 19 | export const SPLASH_MARK = '/'; 20 | 21 | export const ROUTE_NAME_REG = /^([a-zA-Z]|\$|_|\d|-)+$/; 22 | 23 | export const INVALID_ROUTE_NAME = 'invalid-route-name'; 24 | 25 | export const CAMEL_OR_PASCAL = /[A-Z]/; 26 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "afzalsayed96.icones", 4 | "antfu.iconify", 5 | "antfu.unocss", 6 | "christian-kohler.path-intellisense", 7 | "dbaeumer.vscode-eslint", 8 | "eamodio.gitlens", 9 | "editorconfig.editorconfig", 10 | "esbenp.prettier-vscode", 11 | "formulahendry.auto-complete-tag", 12 | "formulahendry.auto-close-tag", 13 | "formulahendry.auto-rename-tag", 14 | "kisstkondoros.vscode-gutter-preview", 15 | "lokalise.i18n-ally", 16 | "mhutchie.git-graph", 17 | "mikestead.dotenv", 18 | "naumovs.color-highlight", 19 | "pkief.material-icon-theme", 20 | "steoates.autoimport", 21 | "vue.volar", 22 | "whtouche.vscode-js-console-utils", 23 | "zhuangtongfa.material-theme" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/vue/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "afzalsayed96.icones", 4 | "antfu.iconify", 5 | "antfu.unocss", 6 | "christian-kohler.path-intellisense", 7 | "dbaeumer.vscode-eslint", 8 | "eamodio.gitlens", 9 | "editorconfig.editorconfig", 10 | "esbenp.prettier-vscode", 11 | "formulahendry.auto-complete-tag", 12 | "formulahendry.auto-close-tag", 13 | "formulahendry.auto-rename-tag", 14 | "kisstkondoros.vscode-gutter-preview", 15 | "lokalise.i18n-ally", 16 | "mhutchie.git-graph", 17 | "mikestead.dotenv", 18 | "naumovs.color-highlight", 19 | "pkief.material-icon-theme", 20 | "steoates.autoimport", 21 | "vue.volar", 22 | "whtouche.vscode-js-console-utils", 23 | "zhuangtongfa.material-theme" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/vue/src/router/modules/one.ts: -------------------------------------------------------------------------------- 1 | const one: AuthRoute.Route = { 2 | name: 'one', 3 | path: '/one', 4 | component: 'basic', 5 | meta: { title: 'one', icon: 'mdi:menu' }, 6 | children: [ 7 | { 8 | name: 'one_two', 9 | path: '/one/two', 10 | component: 'multi', 11 | meta: { title: 'one_two', icon: 'mdi:menu' }, 12 | children: [ 13 | { 14 | name: 'one_two_three-1', 15 | path: '/one/two/three-1', 16 | component: 'self', 17 | meta: { title: 'one_two_three-1', icon: 'mdi:menu' } 18 | }, 19 | { 20 | name: 'one_two_three-ano', 21 | path: '/one/two/three-ano', 22 | component: 'self', 23 | meta: { title: 'one_two_three-ano', icon: 'mdi:menu' } 24 | } 25 | ] 26 | } 27 | ] 28 | }; 29 | 30 | export default one; 31 | -------------------------------------------------------------------------------- /examples/vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 11 | 19 | 42 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-demo", 3 | "type": "module", 4 | "version": "0.0.6", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "npm run typecheck && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "pinia": "^2.1.3", 13 | "vue": "^3.3.4", 14 | "vue-router": "^4.2.2" 15 | }, 16 | "devDependencies": { 17 | "@soybeanjs/vite-plugin-vue-page-route": "workspace:*", 18 | "@types/node": "20.2.5", 19 | "@vitejs/plugin-vue": "^4.2.3", 20 | "@vitejs/plugin-vue-jsx": "^3.0.1", 21 | "eslint": "^8.42.0", 22 | "eslint-config-soybeanjs-vue": "^0.2.2", 23 | "sass": "^1.62.1", 24 | "typescript": "^5.1.3", 25 | "unocss": "^0.53.0", 26 | "unplugin-vue-components": "0.25.1", 27 | "vite": "^4.3.9", 28 | "vite-plugin-inspect": "^0.7.28", 29 | "vue-tsc": "^1.6.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/glob.ts: -------------------------------------------------------------------------------- 1 | import fastGlob from 'fast-glob'; 2 | import type { ContextOption } from '../types'; 3 | 4 | /** 5 | * get the glob of page file 6 | * @param pageGlobs glob to match page files, based on the pageDir 7 | */ 8 | export function getGlobsOfPage(pageGlobs: string[], pageDir: string) { 9 | const globs = fastGlob.sync(pageGlobs, { 10 | onlyFiles: true, 11 | cwd: pageDir 12 | }); 13 | 14 | return globs.sort(); 15 | } 16 | 17 | export function getRelativePathOfGlob(glob: string, pageDir: string) { 18 | return `${pageDir}/${glob}`; 19 | } 20 | 21 | export async function matchGlob(glob: string, options: ContextOption) { 22 | const { isMatch } = await import('micromatch'); 23 | 24 | const isFile = isMatch(glob, '**/*.*'); 25 | const { pageGlobs } = options; 26 | 27 | const patterns = isFile ? pageGlobs : pageGlobs.filter(pattern => !pattern.includes('.')); 28 | 29 | return patterns.every(pattern => isMatch(glob, pattern)); 30 | } 31 | -------------------------------------------------------------------------------- /examples/vue/src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /examples/vue/src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/shared/option.ts: -------------------------------------------------------------------------------- 1 | import { PAGE_DIR, PAGE_GLOB, ROUTE_DTS, ROUTE_MODULE_DIR, ROUTE_MODULE_EXT, ROUTE_MODULE_TYPE } from './constant'; 2 | import type { PluginOption, ContextOption } from '../types'; 3 | 4 | /** 5 | * create plugin options 6 | * @param options user custom options for plugin 7 | */ 8 | export function createPluginOptions(userOptions: Partial, rootDir: string) { 9 | const IGNORE_UNDERLINE_REG = /^_([a-zA-Z]|[0-9]|$)+_*/; 10 | 11 | const BUILTIN_ROUTE_MODULE = '_builtin'; 12 | 13 | const options: ContextOption = { 14 | pageDir: PAGE_DIR, 15 | pageGlobs: PAGE_GLOB, 16 | routeDts: ROUTE_DTS, 17 | routeModuleDir: ROUTE_MODULE_DIR, 18 | routeModuleExt: ROUTE_MODULE_EXT, 19 | routeModuleType: ROUTE_MODULE_TYPE, 20 | routeNameTansformer: name => name.replace(IGNORE_UNDERLINE_REG, ''), 21 | lazyImport: () => true, 22 | onRouteModuleGenerate: name => !name.includes(BUILTIN_ROUTE_MODULE), 23 | rootDir 24 | }; 25 | 26 | Object.assign>(options, userOptions); 27 | 28 | return options; 29 | } 30 | -------------------------------------------------------------------------------- /examples/vue/src/typings/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | HelloWorld: typeof import('./../components/HelloWorld.vue')['default'] 11 | IconCommunity: typeof import('./../components/icons/IconCommunity.vue')['default'] 12 | IconDocumentation: typeof import('./../components/icons/IconDocumentation.vue')['default'] 13 | IconEcosystem: typeof import('./../components/icons/IconEcosystem.vue')['default'] 14 | IconSupport: typeof import('./../components/icons/IconSupport.vue')['default'] 15 | IconTooling: typeof import('./../components/icons/IconTooling.vue')['default'] 16 | RouterLink: typeof import('vue-router')['RouterLink'] 17 | RouterView: typeof import('vue-router')['RouterView'] 18 | TheWelcome: typeof import('./../components/TheWelcome.vue')['default'] 19 | WelcomeItem: typeof import('./../components/WelcomeItem.vue')['default'] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/vue/src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /examples/vue/src/views/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteComponent } from 'vue-router'; 2 | 3 | export const views: Record< 4 | PageRoute.LastDegreeRouteKey, 5 | RouteComponent | (() => Promise<{ default: RouteComponent }>) 6 | > = { 7 | 404: () => import('./_builtin/404/index.vue'), 8 | login: () => import('./_builtin/login/index.vue'), 9 | about: () => import('./about/index.vue'), 10 | demo: () => import('./demo/index.vue'), 11 | home_fifth: () => import('./home/fifth/index.vue'), 12 | 'home_first-page': () => import('./home/first-page/index.vue'), 13 | home_four: () => import('./home/four/index.vue'), 14 | home_multi_first: () => import('./home/multi/first/index.vue'), 15 | home_multi_second: () => import('./home/multi/second/index.vue'), 16 | 'home_multi_third_third-child1': () => import('./home/multi/third/third-child1/index.vue'), 17 | 'home_multi_third_third-child2': () => import('./home/multi/third/third-child2/index.vue'), 18 | 'home_second-page': () => import('./home/second-page/index.vue'), 19 | home_third: () => import('./home/third/index.vue'), 20 | 'one_two_three-1': () => import('./one/two/three-1/index.vue'), 21 | 'one_two_three-ano': () => import('./one/two/three-ano/index.vue'), 22 | one_two_three: () => import('./one/two/three/index.vue'), 23 | test: () => import('./test/index.vue') 24 | }; 25 | -------------------------------------------------------------------------------- /examples/vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import vueJsx from '@vitejs/plugin-vue-jsx'; 5 | import Components from 'unplugin-vue-components/vite'; 6 | import unocss from 'unocss/vite'; 7 | import pageRoute from '@soybeanjs/vite-plugin-vue-page-route'; 8 | import Inspect from 'vite-plugin-inspect'; 9 | 10 | export default defineConfig(() => { 11 | const rootPath = path.resolve(process.cwd()); 12 | const srcPath = `${rootPath}/src`; 13 | 14 | return { 15 | resolve: { 16 | alias: { 17 | '~': rootPath, 18 | '@': srcPath 19 | } 20 | }, 21 | css: { 22 | preprocessorOptions: { 23 | scss: { 24 | additionalData: `@use "./src/styles/scss/global.scss" as *;` 25 | } 26 | } 27 | }, 28 | plugins: [ 29 | vue(), 30 | vueJsx(), 31 | Components({ 32 | dts: 'src/typings/components.d.ts', 33 | types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }] 34 | }), 35 | unocss(), 36 | pageRoute(), 37 | Inspect() 38 | ], 39 | server: { 40 | host: '0.0.0.0', 41 | port: 3300 42 | }, 43 | build: { 44 | brotliSize: false, 45 | sourcemap: false, 46 | commonjsOptions: { 47 | ignoreTryCatch: false 48 | } 49 | } 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @soybeanjs/vite-plugin-vue-page-route 2 | 3 | 中文 | [English](./README.en_US.md) 4 | 5 | Vite 插件,根据页面文件自动生成路由声明、路由组件的导入、路由模块的定义。 6 | 7 | ## 用法 8 | 9 | ````ts 10 | import { defineConfig } from "vite"; 11 | import pageRoute from "@soybeanjs/vite-plugin-vue-page-route"; 12 | 13 | export default defineConfig({ 14 | plugins: [ 15 | pageRoute({ 16 | pageDir: "src/views", // 默认 17 | pageGlobs: ["**/index.{vue,tsx,jsx}", "!**/components/**"], // 默认 18 | routeDts: "src/typings/page-route.d.ts", // 默认 19 | routeModuleDir: "src/router/modules", // 默认 20 | routeModuleExt: "ts", // 默认 21 | routeModuleType: "AuthRoute.Route", // 默认 22 | /** 23 | * @example _builtin_login => login 24 | */ 25 | routeNameTansformer: (name) => 26 | name.replace(/^_([a-zA-Z]|[0-9]|$)+_*/, ""), // 默认 27 | /** 28 | * 路由懒加载 29 | * @param name 路由名称 30 | * @example 31 | * - 直接导入 32 | * ```ts 33 | * import Home from './views/home/index.vue'; 34 | * ``` 35 | * - 懒加载导入 36 | * ```ts 37 | * const Home = import('./views/home/index.vue'); 38 | * ``` 39 | */ 40 | lazyImport: (_name) => true, // 默认 41 | /** 42 | * 是否生成路由模块 43 | * @param name 未转换过的路由名称(没有调用函数routeNameTansformer) 44 | * @returns 是否生成路由模块的代码 45 | */ 46 | onRouteModuleGenerate: (name) => !name.includes("_builtin"), // 对于系统内置路由不生成路由模块, 其他的都生成 47 | }), 48 | ], 49 | }); 50 | ```` 51 | -------------------------------------------------------------------------------- /src/context/declaration.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from 'fs/promises'; 2 | import { ROOT_ROUTE, NOT_FOUND_ROUTE, handleEslintFormat } from '../shared'; 3 | import type { ContextOption, RouteConfig } from '../types'; 4 | 5 | function getDeclarationCode(routeConfig: RouteConfig) { 6 | let code = `declare namespace PageRoute { 7 | /** 8 | * the root route key 9 | * @translate 根路由 10 | */ 11 | type RootRouteKey = '${ROOT_ROUTE}'; 12 | 13 | /** 14 | * the not found route, which catch the invalid route path 15 | * @translate 未找到路由(捕获无效路径的路由) 16 | */ 17 | type NotFoundRouteKey = '${NOT_FOUND_ROUTE}'; 18 | 19 | /** 20 | * the route key 21 | * @translate 页面路由 22 | */ 23 | type RouteKey =`; 24 | 25 | routeConfig.names.forEach(name => { 26 | code += `\n | '${name}'`; 27 | }); 28 | 29 | code += `; 30 | 31 | /** 32 | * last degree route key, which has the page file 33 | * @translate 最后一级路由(该级路有对应的页面文件) 34 | */ 35 | type LastDegreeRouteKey = Extract< 36 | RouteKey,`; 37 | 38 | routeConfig.files.forEach(item => { 39 | code += `\n | '${item.name}'`; 40 | }); 41 | 42 | code += ` 43 | >;\n}\n`; 44 | 45 | return code; 46 | } 47 | 48 | export async function generateDeclaration(routeConfig: RouteConfig, options: ContextOption) { 49 | const code = getDeclarationCode(routeConfig); 50 | 51 | const filePath = `${options.rootDir}/${options.routeDts}`; 52 | 53 | await writeFile(filePath, code, 'utf-8'); 54 | 55 | await handleEslintFormat(filePath); 56 | } 57 | -------------------------------------------------------------------------------- /src/context/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getRouteModuleFilePath, 3 | handleEslintFormat, 4 | transformModuleNameToVariable, 5 | INVALID_ROUTE_NAME, 6 | useFsExtra 7 | } from '../shared'; 8 | import type { ContextOption, RouteModule } from '../types'; 9 | 10 | export function isDeleteWholeModule(deletes: string[], files: string[], moduleName: string) { 11 | const remains = files.filter(item => !deletes.includes(item)); 12 | 13 | return remains.every(item => !item.includes(moduleName)); 14 | } 15 | 16 | export async function generateRouteModuleCode(moduleName: string, module: RouteModule, options: ContextOption) { 17 | if (moduleName === INVALID_ROUTE_NAME) return; 18 | 19 | const { ensureFile, writeFile } = await useFsExtra(); 20 | 21 | const filePath = getRouteModuleFilePath(moduleName, options); 22 | 23 | const variable = transformModuleNameToVariable(moduleName); 24 | 25 | const code = `const ${variable}: ${options.routeModuleType} = ${JSON.stringify( 26 | module 27 | )};\n\nexport default ${variable};`; 28 | 29 | await ensureFile(filePath); 30 | 31 | await writeFile(filePath, code, 'utf-8'); 32 | 33 | await handleEslintFormat(filePath); 34 | } 35 | 36 | export function removeRouteModule(routeName: string, children?: RouteModule[]) { 37 | if (!children || !children.length) return; 38 | const findIndex = children.findIndex(item => item.name === routeName); 39 | 40 | if (findIndex > -1) { 41 | children.splice(findIndex, 1); 42 | } else { 43 | children.forEach(item => { 44 | removeRouteModule(routeName, item.children); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/vue/src/typings/page-route.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace PageRoute { 2 | /** 3 | * the root route key 4 | * @translate 根路由 5 | */ 6 | type RootRouteKey = 'root'; 7 | 8 | /** 9 | * the not found route, which catch the invalid route path 10 | * @translate 未找到路由(捕获无效路径的路由) 11 | */ 12 | type NotFoundRouteKey = 'not-found'; 13 | 14 | /** 15 | * the route key 16 | * @translate 页面路由 17 | */ 18 | type RouteKey = 19 | | '404' 20 | | 'login' 21 | | 'about' 22 | | 'demo' 23 | | 'home' 24 | | 'home_fifth' 25 | | 'home_first-page' 26 | | 'home_four' 27 | | 'home_multi' 28 | | 'home_multi_first' 29 | | 'home_multi_second' 30 | | 'home_multi_third' 31 | | 'home_multi_third_third-child1' 32 | | 'home_multi_third_third-child2' 33 | | 'home_second-page' 34 | | 'home_third' 35 | | 'one' 36 | | 'one_two' 37 | | 'one_two_three-1' 38 | | 'one_two_three-ano' 39 | | 'one_two_three' 40 | | 'test'; 41 | 42 | /** 43 | * last degree route key, which has the page file 44 | * @translate 最后一级路由(该级路有对应的页面文件) 45 | */ 46 | type LastDegreeRouteKey = Extract< 47 | RouteKey, 48 | | '404' 49 | | 'login' 50 | | 'about' 51 | | 'demo' 52 | | 'home_fifth' 53 | | 'home_first-page' 54 | | 'home_four' 55 | | 'home_multi_first' 56 | | 'home_multi_second' 57 | | 'home_multi_third_third-child1' 58 | | 'home_multi_third_third-child2' 59 | | 'home_second-page' 60 | | 'home_third' 61 | | 'one_two_three-1' 62 | | 'one_two_three-ano' 63 | | 'one_two_three' 64 | | 'test' 65 | >; 66 | } 67 | -------------------------------------------------------------------------------- /examples/vue/src/styles/css/transition.css: -------------------------------------------------------------------------------- 1 | /* fade */ 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity 0.3s ease-in-out; 5 | } 6 | .fade-enter-from, 7 | .fade-leave-to { 8 | opacity: 0; 9 | } 10 | 11 | /* fade-slide */ 12 | .fade-slide-leave-active, 13 | .fade-slide-enter-active { 14 | transition: all 0.3s; 15 | } 16 | .fade-slide-enter-from { 17 | opacity: 0; 18 | transform: translateX(-30px); 19 | } 20 | .fade-slide-leave-to { 21 | opacity: 0; 22 | transform: translateX(30px); 23 | } 24 | 25 | /* fade-bottom */ 26 | .fade-bottom-enter-active, 27 | .fade-bottom-leave-active { 28 | transition: opacity 0.25s, transform 0.3s; 29 | } 30 | .fade-bottom-enter-from { 31 | opacity: 0; 32 | transform: translateY(-10%); 33 | } 34 | .fade-bottom-leave-to { 35 | opacity: 0; 36 | transform: translateY(10%); 37 | } 38 | 39 | /* fade-scale */ 40 | .fade-scale-leave-active, 41 | .fade-scale-enter-active { 42 | transition: all 0.28s; 43 | } 44 | .fade-scale-enter-from { 45 | opacity: 0; 46 | transform: scale(1.2); 47 | } 48 | .fade-scale-leave-to { 49 | opacity: 0; 50 | transform: scale(0.8); 51 | } 52 | 53 | /* zoom-fade */ 54 | .zoom-fade-enter-active, 55 | .zoom-fade-leave-active { 56 | transition: transform 0.2s, opacity 0.3s ease-out; 57 | } 58 | .zoom-fade-enter-from { 59 | opacity: 0; 60 | transform: scale(0.92); 61 | } 62 | .zoom-fade-leave-to { 63 | opacity: 0; 64 | transform: scale(1.06); 65 | } 66 | 67 | /* zoom-out */ 68 | .zoom-out-enter-active, 69 | .zoom-out-leave-active { 70 | transition: opacity 0.1s ease-in-out, transform 0.15s ease-out; 71 | } 72 | .zoom-out-enter-from, 73 | .zoom-out-leave-to { 74 | opacity: 0; 75 | transform: scale(0); 76 | } 77 | -------------------------------------------------------------------------------- /examples/vue/.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | { value: 'init', name: 'init: 项目初始化' }, 4 | { value: 'feat', name: 'feat: 添加新特性' }, 5 | { value: 'fix', name: 'fix: 修复bug' }, 6 | { value: 'docs', name: 'docs: 仅仅修改文档' }, 7 | { value: 'style', name: 'style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑' }, 8 | { value: 'refactor', name: 'refactor: 代码重构,没有加新功能或者修复bug' }, 9 | { value: 'perf', name: 'perf: 优化相关,比如提升性能、体验' }, 10 | { value: 'test', name: 'test: 添加测试用例' }, 11 | { value: 'build', name: 'build: 依赖相关的内容' }, 12 | { value: 'ci', name: 'ci: CI配置相关,例如对k8s,docker的配置文件的修改' }, 13 | { value: 'chore', name: 'chore: 改变构建流程、或者增加依赖库、工具等' }, 14 | { value: 'revert', name: 'revert: 回滚到上一个版本' } 15 | ], 16 | scopes: [ 17 | ['projects', '项目搭建'], 18 | ['components', '组件相关'], 19 | ['hooks', 'hook 相关'], 20 | ['utils', 'utils 相关'], 21 | ['types', 'ts类型相关'], 22 | ['styles', '样式相关'], 23 | ['deps', '项目依赖'], 24 | ['auth', '对 auth 修改'], 25 | ['other', '其他修改'], 26 | ['custom', '以上都不是?我要自定义'] 27 | ].map(([value, description]) => { 28 | return { 29 | value, 30 | name: `${value.padEnd(30)} (${description})` 31 | } 32 | }), 33 | messages: { 34 | type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:', 35 | scope: '\n选择一个 scope(可选):', 36 | customScope: '请输入自定义的 scope:', 37 | subject: '填写简短精炼的变更描述:\n', 38 | body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n', 39 | breaking: '列举非兼容性重大的变更(可选):\n', 40 | footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n', 41 | confirmCommit: '确认提交?' 42 | }, 43 | allowBreakingChanges: ['feat', 'fix'], 44 | subjectLimit: 100, 45 | breaklineChar: '|' 46 | } 47 | -------------------------------------------------------------------------------- /README.en_US.md: -------------------------------------------------------------------------------- 1 | # @soybeanjs/vite-plugin-vue-page-route 2 | 3 | A Vite plugin for vue, auto generate route info by page, include route declaration, route file import, route module const. 4 | 5 | ## Usage 6 | 7 | ````ts 8 | import { defineConfig } from "vite"; 9 | import pageRoute from "@soybeanjs/vite-plugin-vue-page-route"; 10 | 11 | export default defineConfig({ 12 | plugins: [ 13 | pageRoute({ 14 | pageDir: "src/views", // default 15 | pageGlobs: ["**/index.{vue,tsx,jsx}", "!**/components/**"], // default 16 | routeDts: "src/typings/page-route.d.ts", // default 17 | routeModuleDir: "src/router/modules", // default 18 | routeModuleExt: "ts", // default 19 | routeModuleType: "AuthRoute.Route", // default 20 | /** 21 | * @example _builtin_login => login 22 | */ 23 | routeNameTansformer: (name) => 24 | name.replace(/^_([a-zA-Z]|[0-9]|$)+_*/, ""), // default 25 | /** 26 | * whether the route is lazy import 27 | * @param name route name 28 | * @example 29 | * - the direct import 30 | * ```ts 31 | * import Home from './views/home/index.vue'; 32 | * ``` 33 | * - the lazy import 34 | * ```ts 35 | * const Home = import('./views/home/index.vue'); 36 | * ``` 37 | */ 38 | lazyImport: (_name) => true, // default 39 | /** 40 | * whether generate the route module 41 | * @param name the route name, which is not transformed(not execute function routeNameTansformer) 42 | * @returns boolean 43 | */ 44 | onRouteModuleGenerate: (name) => !name.includes("_builtin"), // not generate route module for the builtin routes, but other routes will 45 | }), 46 | ], 47 | }); 48 | ```` 49 | -------------------------------------------------------------------------------- /examples/vue/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 86 | -------------------------------------------------------------------------------- /examples/vue/uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetUno } from 'unocss'; 2 | 3 | export default defineConfig({ 4 | exclude: ['node_modules', '.git', '.husky', '.vscode', 'dist', 'public', 'build', 'mock', './stats.html'], 5 | presets: [presetUno({ dark: 'class' })], 6 | shortcuts: { 7 | 'wh-full': 'w-full h-full', 8 | 'flex-center': 'flex justify-center items-center', 9 | 'flex-col-center': 'flex-center flex-col', 10 | 'flex-x-center': 'flex justify-center', 11 | 'flex-y-center': 'flex items-center', 12 | 'i-flex-center': 'inline-flex justify-center items-center', 13 | 'i-flex-x-center': 'inline-flex justify-center', 14 | 'i-flex-y-center': 'inline-flex items-center', 15 | 'flex-col': 'flex flex-col', 16 | 'flex-col-stretch': 'flex-col items-stretch', 17 | 'i-flex-col': 'inline-flex flex-col', 18 | 'i-flex-col-stretch': 'i-flex-col items-stretch', 19 | 'flex-1-hidden': 'flex-1 overflow-hidden', 20 | 'absolute-lt': 'absolute left-0 top-0', 21 | 'absolute-lb': 'absolute left-0 bottom-0', 22 | 'absolute-rt': 'absolute right-0 top-0', 23 | 'absolute-rb': 'absolute right-0 bottom-0', 24 | 'absolute-tl': 'absolute-lt', 25 | 'absolute-tr': 'absolute-rt', 26 | 'absolute-bl': 'absolute-lb', 27 | 'absolute-br': 'absolute-rb', 28 | 'absolute-center': 'absolute-lt flex-center wh-full', 29 | 'fixed-lt': 'fixed left-0 top-0', 30 | 'fixed-lb': 'fixed left-0 bottom-0', 31 | 'fixed-rt': 'fixed right-0 top-0', 32 | 'fixed-rb': 'fixed right-0 bottom-0', 33 | 'fixed-tl': 'fixed-lt', 34 | 'fixed-tr': 'fixed-rt', 35 | 'fixed-bl': 'fixed-lb', 36 | 'fixed-br': 'fixed-rb', 37 | 'fixed-center': 'fixed-lt flex-center wh-full', 38 | 'nowrap-hidden': 'whitespace-nowrap overflow-hidden', 39 | 'ellipsis-text': 'nowrap-hidden overflow-ellipsis', 40 | 'transition-base': 'transition-all duration-300 ease-in-out' 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/context/views.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from 'fs/promises'; 2 | import { handleEslintFormat } from '../shared'; 3 | import type { ContextOption, RouteFile } from '../types'; 4 | 5 | function transformKey(key: string) { 6 | return key.includes('-') ? `'${key}'` : key; 7 | } 8 | 9 | function isPureNumberKey(key: string) { 10 | const NUM_REG = /^\d+$/; 11 | return NUM_REG.test(key); 12 | } 13 | 14 | function transformImportKey(key: string) { 15 | const tranform = isPureNumberKey(key) ? `_view_${key}` : key; 16 | 17 | return tranform; 18 | } 19 | 20 | function getViewsCode(routeFiles: RouteFile[], options: ContextOption) { 21 | let preCode = `import type { RouteComponent } from 'vue-router';\n`; 22 | let code = ` 23 | export const views: Record< 24 | PageRoute.LastDegreeRouteKey, 25 | RouteComponent | (() => Promise<{ default: RouteComponent }>) 26 | > = {`; 27 | 28 | routeFiles.forEach(({ name, path }, index) => { 29 | const isLazy = options.lazyImport(name); 30 | 31 | const key = transformKey(name); 32 | 33 | if (isLazy) { 34 | code += `\n ${key}: () => import('${path}')`; 35 | } else { 36 | const importKey = transformImportKey(name); 37 | 38 | preCode += `import ${importKey} from '${path}';\n`; 39 | 40 | if (key === path && !isPureNumberKey(name)) { 41 | code += `\n ${key}`; 42 | } else { 43 | code += `\n ${key}: ${importKey}`; 44 | } 45 | } 46 | 47 | if (index < routeFiles.length - 1) { 48 | code += ','; 49 | } 50 | }); 51 | 52 | code += '\n};\n'; 53 | 54 | return preCode + code; 55 | } 56 | 57 | export async function generateViews(routeFiles: RouteFile[], options: ContextOption) { 58 | const code = getViewsCode(routeFiles, options); 59 | 60 | const filePath = `${options.rootDir}/${options.pageDir}/index.ts`; 61 | 62 | await writeFile(filePath, code, 'utf-8'); 63 | 64 | await handleEslintFormat(filePath); 65 | } 66 | -------------------------------------------------------------------------------- /examples/vue/src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /examples/vue/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | position: relative; 59 | font-weight: normal; 60 | } 61 | 62 | body { 63 | min-height: 100vh; 64 | color: var(--color-text); 65 | background: var(--color-background); 66 | transition: color 0.5s, background-color 0.5s; 67 | line-height: 1.6; 68 | font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 69 | Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", 70 | sans-serif; 71 | font-size: 15px; 72 | text-rendering: optimizeLegibility; 73 | -webkit-font-smoothing: antialiased; 74 | -moz-osx-font-smoothing: grayscale; 75 | } 76 | -------------------------------------------------------------------------------- /examples/vue/src/router/modules/home.ts: -------------------------------------------------------------------------------- 1 | const home: AuthRoute.Route = { 2 | name: 'home', 3 | path: '/home', 4 | component: 'basic', 5 | meta: { title: 'home', icon: 'mdi:menu' }, 6 | children: [ 7 | { name: 'home_fifth', path: '/home/fifth', component: 'self', meta: { title: 'home_fifth', icon: 'mdi:menu' } }, 8 | { 9 | name: 'home_first-page', 10 | path: '/home/first-page', 11 | component: 'self', 12 | meta: { title: 'home_first-page', icon: 'mdi:menu' } 13 | }, 14 | { name: 'home_four', path: '/home/four', component: 'self', meta: { title: 'home_four', icon: 'mdi:menu' } }, 15 | { 16 | name: 'home_multi', 17 | path: '/home/multi', 18 | component: 'multi', 19 | meta: { title: 'home_multi', icon: 'mdi:menu' }, 20 | children: [ 21 | { 22 | name: 'home_multi_first', 23 | path: '/home/multi/first', 24 | component: 'self', 25 | meta: { title: 'home_multi_first', icon: 'mdi:menu' } 26 | }, 27 | { 28 | name: 'home_multi_second', 29 | path: '/home/multi/second', 30 | component: 'self', 31 | meta: { title: 'home_multi_second', icon: 'mdi:menu' } 32 | }, 33 | { 34 | name: 'home_multi_third', 35 | path: '/home/multi/third', 36 | component: 'multi', 37 | meta: { title: 'home_multi_third', icon: 'mdi:menu' }, 38 | children: [ 39 | { 40 | name: 'home_multi_third_third-child1', 41 | path: '/home/multi/third/third-child1', 42 | component: 'self', 43 | meta: { title: 'home_multi_third_third-child1', icon: 'mdi:menu' } 44 | }, 45 | { 46 | name: 'home_multi_third_third-child2', 47 | path: '/home/multi/third/third-child2', 48 | component: 'self', 49 | meta: { title: 'home_multi_third_third-child2', icon: 'mdi:menu' } 50 | } 51 | ] 52 | } 53 | ] 54 | }, 55 | { 56 | name: 'home_second-page', 57 | path: '/home/second-page', 58 | component: 'self', 59 | meta: { title: 'home_second-page', icon: 'mdi:menu' } 60 | }, 61 | { name: 'home_third', path: '/home/third', component: 'self', meta: { title: 'home_third', icon: 'mdi:menu' } } 62 | ] 63 | }; 64 | 65 | export default home; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@soybeanjs/vite-plugin-vue-page-route", 3 | "version": "0.0.6", 4 | "description": "A vite plugin for vue, auto generate route info by page", 5 | "author": { 6 | "name": "Soybean", 7 | "email": "honghuangdc@gmail.com", 8 | "url": "https://github.com/soybeanjs" 9 | }, 10 | "license": "MIT", 11 | "homepage": "https://github.com/soybeanjs/vite-plugin-vue-page-route", 12 | "repository": { 13 | "url": "https://github.com/soybeanjs/vite-plugin-vue-page-route.git" 14 | }, 15 | "keywords": [ 16 | "soybeanjs", 17 | "soybean", 18 | "vite", 19 | "vite-plugin", 20 | "vite-plugin-vue", 21 | "vite-plugin-vue-page-route", 22 | "page-route", 23 | "directory-route", 24 | "file-based-route" 25 | ], 26 | "publishConfig": { 27 | "registry": "https://registry.npmjs.org/" 28 | }, 29 | "exports": { 30 | "types": "./dist/index.d.ts", 31 | "require": "./dist/index.cjs", 32 | "import": "./dist/index.mjs" 33 | }, 34 | "main": "./dist/index.cjs", 35 | "module": "./dist/index.mjs", 36 | "types": "./dist/index.d.ts", 37 | "files": [ 38 | "dist" 39 | ], 40 | "scripts": { 41 | "build": "unbuild", 42 | "lint": "eslint . --fix", 43 | "format": "soy prettier-format", 44 | "commit": "soy git-commit", 45 | "cleanup": "soy cleanup", 46 | "update-pkg": "soy update-pkg", 47 | "publish-pkg": "pnpm -r publish --access public", 48 | "typecheck": "tsc --noEmit", 49 | "release": "soy release && pnpm build && pnpm publish-pkg", 50 | "prepare": "pnpm -r run stub" 51 | }, 52 | "dependencies": { 53 | "@swc/core": "1.3.62", 54 | "chokidar": "3.5.3", 55 | "execa": "7.1.1", 56 | "fast-glob": "3.2.12", 57 | "fs-extra": "11.1.1", 58 | "kolorist": "1.8.0", 59 | "magic-string": "0.30.0", 60 | "micromatch": "4.0.5" 61 | }, 62 | "devDependencies": { 63 | "@soybeanjs/cli": "0.5.1", 64 | "@types/fs-extra": "11.0.1", 65 | "@types/micromatch": "4.0.2", 66 | "@types/node": "20.2.5", 67 | "@types/throttle-debounce": "5.0.0", 68 | "esbuild-register": "3.4.2", 69 | "eslint": "8.42.0", 70 | "eslint-config-soybeanjs": "0.4.8", 71 | "simple-git-hooks": "2.8.1", 72 | "tsx": "3.12.7", 73 | "typescript": "5.1.3", 74 | "unbuild": "1.2.1", 75 | "vite": "4.3.9" 76 | }, 77 | "simple-git-hooks": { 78 | "commit-msg": "pnpm soybean git-commit-verify", 79 | "pre-commit": "pnpm lint-staged" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "editor.fontLigatures": true, 6 | "editor.formatOnSave": false, 7 | "editor.guides.bracketPairs": "active", 8 | "editor.quickSuggestions": { 9 | "strings": true 10 | }, 11 | "editor.tabSize": 2, 12 | "eslint.alwaysShowStatus": true, 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "json", 21 | "jsonc", 22 | "json5", 23 | "yaml", 24 | "yml", 25 | "markdown" 26 | ], 27 | "files.associations": { 28 | "*.env.*": "dotenv" 29 | }, 30 | "files.eol": "\n", 31 | "git.enableSmartCommit": true, 32 | "gutterpreview.paths": { 33 | "@": "/src", 34 | "~@": "/src" 35 | }, 36 | "material-icon-theme.activeIconPack": "angular", 37 | "material-icon-theme.files.associations": {}, 38 | "material-icon-theme.folders.associations": { 39 | "src-tauri": "src", 40 | "enum": "typescript", 41 | "enums": "typescript", 42 | "store": "context", 43 | "stores": "context", 44 | "composable": "hook", 45 | "composables": "hook", 46 | "directive": "tools", 47 | "directives": "tools", 48 | "business": "core", 49 | "request": "api", 50 | "adapter": "middleware" 51 | }, 52 | "path-intellisense.mappings": { 53 | "@": "${workspaceFolder}/src", 54 | "~@": "${workspaceFolder}/src" 55 | }, 56 | "terminal.integrated.cursorStyle": "line", 57 | "terminal.integrated.fontSize": 14, 58 | "terminal.integrated.fontWeight": 500, 59 | "terminal.integrated.tabs.enabled": true, 60 | "workbench.iconTheme": "material-icon-theme", 61 | "workbench.colorTheme": "One Dark Pro", 62 | "[html]": { 63 | "editor.defaultFormatter": "esbenp.prettier-vscode" 64 | }, 65 | "[json]": { 66 | "editor.defaultFormatter": "esbenp.prettier-vscode" 67 | }, 68 | "[jsonc]": { 69 | "editor.defaultFormatter": "esbenp.prettier-vscode" 70 | }, 71 | "[javascript]": { 72 | "editor.defaultFormatter": "esbenp.prettier-vscode" 73 | }, 74 | "[javascriptreact]": { 75 | "editor.defaultFormatter": "esbenp.prettier-vscode" 76 | }, 77 | "[markdown]": { 78 | "editor.defaultFormatter": "yzhang.markdown-all-in-one" 79 | }, 80 | "[typescript]": { 81 | "editor.defaultFormatter": "esbenp.prettier-vscode" 82 | }, 83 | "[typescriptreact]": { 84 | "editor.defaultFormatter": "esbenp.prettier-vscode" 85 | }, 86 | "[vue]": { 87 | "editor.defaultFormatter": "Vue.volar" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/vue/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "editor.fontLigatures": true, 6 | "editor.formatOnSave": false, 7 | "editor.guides.bracketPairs": "active", 8 | "editor.quickSuggestions": { 9 | "strings": true 10 | }, 11 | "editor.tabSize": 2, 12 | "eslint.alwaysShowStatus": true, 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "json", 21 | "jsonc", 22 | "json5", 23 | "yaml", 24 | "yml", 25 | "markdown" 26 | ], 27 | "files.associations": { 28 | "*.env.*": "dotenv" 29 | }, 30 | "files.eol": "\n", 31 | "git.enableSmartCommit": true, 32 | "gutterpreview.paths": { 33 | "@": "/src", 34 | "~@": "/src" 35 | }, 36 | "material-icon-theme.activeIconPack": "angular", 37 | "material-icon-theme.files.associations": {}, 38 | "material-icon-theme.folders.associations": { 39 | "src-tauri": "src", 40 | "enum": "typescript", 41 | "enums": "typescript", 42 | "store": "context", 43 | "stores": "context", 44 | "composable": "hook", 45 | "composables": "hook", 46 | "directive": "tools", 47 | "directives": "tools", 48 | "business": "core", 49 | "request": "api", 50 | "adapter": "middleware" 51 | }, 52 | "path-intellisense.mappings": { 53 | "@": "${workspaceFolder}/src", 54 | "~@": "${workspaceFolder}/src" 55 | }, 56 | "terminal.integrated.cursorStyle": "line", 57 | "terminal.integrated.fontSize": 14, 58 | "terminal.integrated.fontWeight": 500, 59 | "terminal.integrated.tabs.enabled": true, 60 | "workbench.iconTheme": "material-icon-theme", 61 | "workbench.colorTheme": "One Dark Pro", 62 | "[html]": { 63 | "editor.defaultFormatter": "esbenp.prettier-vscode" 64 | }, 65 | "[json]": { 66 | "editor.defaultFormatter": "esbenp.prettier-vscode" 67 | }, 68 | "[jsonc]": { 69 | "editor.defaultFormatter": "esbenp.prettier-vscode" 70 | }, 71 | "[javascript]": { 72 | "editor.defaultFormatter": "esbenp.prettier-vscode" 73 | }, 74 | "[javascriptreact]": { 75 | "editor.defaultFormatter": "esbenp.prettier-vscode" 76 | }, 77 | "[markdown]": { 78 | "editor.defaultFormatter": "yzhang.markdown-all-in-one" 79 | }, 80 | "[typescript]": { 81 | "editor.defaultFormatter": "esbenp.prettier-vscode" 82 | }, 83 | "[typescriptreact]": { 84 | "editor.defaultFormatter": "esbenp.prettier-vscode" 85 | }, 86 | "[vue]": { 87 | "editor.defaultFormatter": "Vue.volar" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/vue/src/components/TheWelcome.vue: -------------------------------------------------------------------------------- 1 | 72 | 80 | -------------------------------------------------------------------------------- /examples/vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['soybeanjs-vue'], 3 | settings: { 4 | 'import/core-modules': ['uno.css', '~icons/*', 'virtual:svg-icons-register'] 5 | }, 6 | rules: { 7 | 'import/order': [ 8 | 'error', 9 | { 10 | 'newlines-between': 'never', 11 | groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 12 | pathGroups: [ 13 | { 14 | pattern: 'vue', 15 | group: 'external', 16 | position: 'before' 17 | }, 18 | { 19 | pattern: 'vue-router', 20 | group: 'external', 21 | position: 'before' 22 | }, 23 | { 24 | pattern: 'pinia', 25 | group: 'external', 26 | position: 'before' 27 | }, 28 | { 29 | pattern: 'naive-ui', 30 | group: 'external', 31 | position: 'before' 32 | }, 33 | { 34 | pattern: '@/config', 35 | group: 'internal', 36 | position: 'before' 37 | }, 38 | { 39 | pattern: '@/settings', 40 | group: 'internal', 41 | position: 'before' 42 | }, 43 | { 44 | pattern: '@/enum', 45 | group: 'internal', 46 | position: 'before' 47 | }, 48 | { 49 | pattern: '@/plugins', 50 | group: 'internal', 51 | position: 'before' 52 | }, 53 | { 54 | pattern: '@/layouts', 55 | group: 'internal', 56 | position: 'before' 57 | }, 58 | { 59 | pattern: '@/views', 60 | group: 'internal', 61 | position: 'before' 62 | }, 63 | { 64 | pattern: '@/components', 65 | group: 'internal', 66 | position: 'before' 67 | }, 68 | { 69 | pattern: '@/router', 70 | group: 'internal', 71 | position: 'before' 72 | }, 73 | { 74 | pattern: '@/service', 75 | group: 'internal', 76 | position: 'before' 77 | }, 78 | { 79 | pattern: '@/store', 80 | group: 'internal', 81 | position: 'before' 82 | }, 83 | { 84 | pattern: '@/context', 85 | group: 'internal', 86 | position: 'before' 87 | }, 88 | { 89 | pattern: '@/composables', 90 | group: 'internal', 91 | position: 'before' 92 | }, 93 | { 94 | pattern: '@/hooks', 95 | group: 'internal', 96 | position: 'before' 97 | }, 98 | { 99 | pattern: '@/utils', 100 | group: 'internal', 101 | position: 'before' 102 | }, 103 | { 104 | pattern: '@/assets', 105 | group: 'internal', 106 | position: 'before' 107 | }, 108 | { 109 | pattern: '@/**', 110 | group: 'internal', 111 | position: 'before' 112 | } 113 | ], 114 | pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'pinia', 'naive-ui'] 115 | } 116 | ] 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /src/context/index.ts: -------------------------------------------------------------------------------- 1 | import chokidar from 'chokidar'; 2 | import { 3 | createPluginOptions, 4 | getGlobsOfPage, 5 | getRouteConfigByGlobs, 6 | matchGlob, 7 | getRouteNameByGlob, 8 | INVALID_ROUTE_NAME 9 | } from '../shared'; 10 | import { generateDeclaration } from './declaration'; 11 | import { generateViews } from './views'; 12 | import { fileWatcherHandler, createFWHooksOfGenDeclarationAndViews, createFWHooksOfGenModule } from './fs'; 13 | import type { 14 | ContextOption, 15 | PluginOption, 16 | RouteConfig, 17 | FileWatcherDispatch, 18 | FileWatcherHooks, 19 | FileWatcherEvent 20 | } from '../types'; 21 | 22 | export default class Context { 23 | options: ContextOption; 24 | 25 | routeConfig: RouteConfig; 26 | 27 | dispatchId: null | NodeJS.Timeout = null; 28 | 29 | dispatchStack: FileWatcherDispatch[] = []; 30 | 31 | constructor(options: Partial = {}) { 32 | const rootDir = process.cwd(); 33 | 34 | this.options = createPluginOptions(options, rootDir); 35 | 36 | const globs = getGlobsOfPage(this.options.pageGlobs, this.options.pageDir); 37 | 38 | this.routeConfig = getRouteConfigByGlobs(globs, this.options); 39 | 40 | this.generate(); 41 | } 42 | 43 | private async generate() { 44 | await generateDeclaration(this.routeConfig, this.options); 45 | await generateViews(this.routeConfig.files, this.options); 46 | } 47 | 48 | private createFileWatcherHooks(dispatchs: FileWatcherDispatch[]) { 49 | const declarationAndViewsHooks = createFWHooksOfGenDeclarationAndViews(dispatchs, this.routeConfig, this.options); 50 | 51 | const filteredDispatchs = dispatchs.filter(dispatch => { 52 | const isFile = dispatch.event === 'add' || dispatch.event === 'unlink'; 53 | if (!isFile) return true; 54 | 55 | const routeName = getRouteNameByGlob(dispatch.path, this.options.pageDir); 56 | const generateRouteModule = this.options.onRouteModuleGenerate(routeName); 57 | 58 | return generateRouteModule; 59 | }); 60 | 61 | const moduleHooks = createFWHooksOfGenModule(filteredDispatchs, this.routeConfig, this.options); 62 | 63 | const hooks: FileWatcherHooks = { 64 | async onRenameDirWithFile() { 65 | await declarationAndViewsHooks.onRenameDirWithFile(); 66 | await moduleHooks.onRenameDirWithFile(); 67 | }, 68 | async onDelDirWithFile() { 69 | await declarationAndViewsHooks.onDelDirWithFile(); 70 | await moduleHooks.onDelDirWithFile(); 71 | }, 72 | async onAddDirWithFile() { 73 | await declarationAndViewsHooks.onAddDirWithFile(); 74 | await moduleHooks.onAddDirWithFile(); 75 | }, 76 | async onDelFile() { 77 | await declarationAndViewsHooks.onDelFile(); 78 | await moduleHooks.onDelFile(); 79 | }, 80 | async onAddFile() { 81 | await declarationAndViewsHooks.onAddFile(); 82 | await moduleHooks.onAddFile(); 83 | } 84 | }; 85 | 86 | return hooks; 87 | } 88 | 89 | private async dispatchFileWatcher(glob: string, event: FileWatcherEvent) { 90 | const isMatch = await matchGlob(glob, this.options); 91 | if (!isMatch) return; 92 | 93 | const dispacth: FileWatcherDispatch = { 94 | event, 95 | path: glob 96 | }; 97 | 98 | if (this.checkDispatch(dispacth)) { 99 | this.dispatchStack.push(dispacth); 100 | } 101 | 102 | if (!this.dispatchId) { 103 | this.dispatchId = setTimeout(async () => { 104 | const hooks = this.createFileWatcherHooks(this.dispatchStack); 105 | 106 | await fileWatcherHandler(this.dispatchStack, hooks); 107 | 108 | this.generate(); 109 | 110 | this.dispatchStack = []; 111 | this.dispatchId = null; 112 | }, 100); 113 | } 114 | } 115 | 116 | private checkDispatch(dispatch: FileWatcherDispatch) { 117 | const isFile = dispatch.event === 'add' || dispatch.event === 'unlink'; 118 | 119 | if (!isFile) return true; 120 | 121 | const routeName = getRouteNameByGlob(dispatch.path, this.options.pageDir); 122 | 123 | return routeName !== INVALID_ROUTE_NAME; 124 | } 125 | 126 | setupFileWatcher() { 127 | const events: FileWatcherEvent[] = ['addDir', 'unlinkDir', 'add', 'unlink']; 128 | 129 | events.forEach(event => { 130 | chokidar 131 | .watch(['.'], { 132 | ignoreInitial: true, 133 | cwd: this.options.pageDir 134 | }) 135 | .on(event, path => { 136 | this.dispatchFileWatcher(path, event); 137 | }); 138 | }); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /examples/vue/src/typings/route.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace AuthRoute { 2 | /** 根路由路径 */ 3 | type RootRoutePath = '/'; 4 | 5 | /** 捕获无效路由的路由路径 */ 6 | type NotFoundRoutePath = '/:pathMatch(.*)*'; 7 | 8 | type RootRouteKey = PageRoute.RootRouteKey; 9 | 10 | type NotFoundRouteKey = PageRoute.NotFoundRouteKey; 11 | 12 | type RouteKey = PageRoute.RouteKey; 13 | 14 | type LastDegreeRouteKey = PageRoute.LastDegreeRouteKey; 15 | 16 | type AllRouteKey = RouteKey | RootRouteKey | NotFoundRouteKey; 17 | 18 | /** 路由路径 */ 19 | type RoutePath = AuthRouteUtils.GetRoutePath; 20 | 21 | /** 22 | * 路由的组件 23 | * - basic - 基础布局,具有公共部分的布局 24 | * - blank - 空白布局 25 | * - multi - 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局) 26 | * - self - 作为子路由,使用自身的布局(作为最后一级路由,没有子路由) 27 | */ 28 | type RouteComponentType = 'basic' | 'blank' | 'multi' | 'self'; 29 | 30 | /** 路由描述 */ 31 | interface RouteMeta { 32 | /** 路由标题(可用来作document.title或者菜单的名称) */ 33 | title: string; 34 | /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */ 35 | dynamicPath?: AuthRouteUtils.GetDynamicPath<'/login'>; 36 | /** 作为单级路由的父级路由布局组件 */ 37 | singleLayout?: Extract; 38 | /** 需要登录权限 */ 39 | requiresAuth?: boolean; 40 | /** 缓存页面 */ 41 | keepAlive?: boolean; 42 | /** 菜单和面包屑对应的图标 */ 43 | icon?: string; 44 | /** 使用本地svg作为的菜单和面包屑对应的图标(assets/svg-icon文件夹的的svg文件名) */ 45 | localIcon?: string; 46 | /** 是否在菜单中隐藏(一些列表、表格的详情页面需要通过参数跳转,所以不能显示在菜单中) */ 47 | hide?: boolean; 48 | /** 外链链接 */ 49 | href?: string; 50 | /** 是否支持多个tab页签(默认一个,即相同name的路由会被替换) */ 51 | multiTab?: boolean; 52 | /** 路由顺序,可用于菜单的排序 */ 53 | order?: number; 54 | /** 当前路由需要选中的菜单项(用于跳转至不在左侧菜单显示的路由且需要高亮某个菜单的情况) */ 55 | activeMenu?: RouteKey; 56 | /** 表示是否是多级路由的中间级路由(用于转换路由数据时筛选多级路由的标识,定义路由时不用填写) */ 57 | multi?: boolean; 58 | /** 是否固定在tab卡不可关闭 */ 59 | affix?: boolean; 60 | } 61 | 62 | type Route = K extends AllRouteKey 63 | ? { 64 | /** 路由名称(路由唯一标识) */ 65 | name: K; 66 | /** 路由路径 */ 67 | path: AuthRouteUtils.GetRoutePath; 68 | /** 路由重定向 */ 69 | redirect?: AuthRouteUtils.GetRoutePath; 70 | /** 71 | * 路由组件 72 | * - basic: 基础布局,具有公共部分的布局 73 | * - blank: 空白布局 74 | * - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局) 75 | * - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由) 76 | */ 77 | component?: RouteComponentType; 78 | /** 子路由 */ 79 | children?: Route[]; 80 | /** 路由描述 */ 81 | meta: RouteMeta; 82 | } & Omit 83 | : never; 84 | 85 | /** 前端导入的路由模块 */ 86 | type RouteModule = Record; 87 | } 88 | 89 | declare namespace AuthRouteUtils { 90 | /** 路由key层级分割符 */ 91 | type RouteKeySplitMark = '_'; 92 | 93 | /** 路由path层级分割符 */ 94 | type RoutePathSplitMark = '/'; 95 | 96 | /** 空白字符串 */ 97 | type BlankString = ''; 98 | 99 | /** key转换成path */ 100 | type KeyToPath = K extends `${infer _Left}${RouteKeySplitMark}${RouteKeySplitMark}${infer _Right}` 101 | ? never 102 | : K extends `${infer Left}${RouteKeySplitMark}${infer Right}` 103 | ? Left extends BlankString 104 | ? never 105 | : Right extends BlankString 106 | ? never 107 | : KeyToPath<`${Left}${RoutePathSplitMark}${Right}`> 108 | : `${RoutePathSplitMark}${K}`; 109 | 110 | /** 根据路由key获取路由路径 */ 111 | type GetRoutePath = K extends AuthRoute.AllRouteKey 112 | ? K extends AuthRoute.RootRouteKey 113 | ? AuthRoute.RootRoutePath 114 | : K extends AuthRoute.NotFoundRouteKey 115 | ? AuthRoute.NotFoundRoutePath 116 | : KeyToPath 117 | : never; 118 | 119 | /** 获取一级路由(有子路由的一级路由和没有子路由的路由) */ 120 | type GetFirstDegreeRouteKey = 121 | K extends `${infer _Left}${RouteKeySplitMark}${infer _Right}` ? never : K; 122 | 123 | /** 获取有子路由的一级路由 */ 124 | type GetFirstDegreeRouteKeyWithChildren = 125 | K extends `${infer Left}${RouteKeySplitMark}${infer _Right}` ? Left : never; 126 | 127 | /** 单级路由的key (单级路由需要添加一个父级路由用于应用布局组件) */ 128 | type SingleRouteKey = Exclude< 129 | GetFirstDegreeRouteKey, 130 | GetFirstDegreeRouteKeyWithChildren | AuthRoute.RootRouteKey | AuthRoute.NotFoundRouteKey 131 | >; 132 | 133 | /** 单独路由父级路由key */ 134 | type SingleRouteParentKey = `${SingleRouteKey}-parent`; 135 | 136 | /** 单独路由父级路由path */ 137 | type SingleRouteParentPath = KeyToPath; 138 | 139 | /** 获取路由动态路径 */ 140 | type GetDynamicPath

= 141 | | `${P}/:${string}` 142 | | `${P}/:${string}(${string})` 143 | | `${P}/:${string}(${string})?`; 144 | } 145 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * plugin options 3 | * @translate 插件配置 4 | */ 5 | export interface PluginOption { 6 | /** 7 | * relative path to the directory to search for page files 8 | * @default 'src/views' 9 | */ 10 | pageDir: string; 11 | /** 12 | * glob to match page files, based on the pageDir property 13 | * @default [ '** /index.{vue,tsx,jsx}', '!** /components/**' ] 14 | * - attention: there is no blank after '**' 15 | * @link the detail syntax: https://github.com/micromatch/micromatch 16 | */ 17 | pageGlobs: string[]; 18 | /** 19 | * relative path to the directory to generate the route declaration. 20 | * @default 'src/typings/page-route.d.ts' 21 | */ 22 | routeDts: string; 23 | /** 24 | * relative path to the directory to generate the code of route module const 25 | * @default src/router/modules 26 | */ 27 | routeModuleDir: string; 28 | /** 29 | * the extension to the directory to generate the code of route module const 30 | * @default ts 31 | */ 32 | routeModuleExt: string; 33 | /** 34 | * the type declaretion of the generated route module item 35 | * @default AuthRoute.Route 36 | * @example 37 | * ```ts 38 | * const route: AuthRoute.Route = { 39 | * name: 'home', 40 | * path: '/home', 41 | * component: 'self', 42 | * meta: { 43 | * title: 'home', 44 | * singleLayout: 'basic' 45 | * } 46 | * } 47 | * export default route; 48 | * ``` 49 | */ 50 | routeModuleType: string; 51 | /** 52 | * transform the route name 53 | * @param name 54 | */ 55 | routeNameTansformer(name: string): string; 56 | /** 57 | * whether the route is lazy import 58 | * @param name route name 59 | * @example 60 | * - the direct import 61 | * ```ts 62 | * import Home from './views/home/index.vue'; 63 | * ``` 64 | * - the lazy import 65 | * ```ts 66 | * const Home = import('./views/home/index.vue'); 67 | * ``` 68 | */ 69 | lazyImport(name: string): boolean; 70 | /** 71 | * the route name, which is not tranfromed 72 | * @param name 73 | * @returns whether generate the route module, default is true 74 | */ 75 | onRouteModuleGenerate(name: string): boolean; 76 | } 77 | 78 | export interface ContextOption extends PluginOption { 79 | rootDir: string; 80 | } 81 | 82 | /** 83 | * the route name and the import path 84 | */ 85 | export interface RouteFile { 86 | name: string; 87 | path: string; 88 | } 89 | 90 | /** 91 | * the route config 92 | */ 93 | export interface RouteConfig { 94 | names: string[]; 95 | files: RouteFile[]; 96 | } 97 | 98 | /** 99 | * the component type of route 100 | * - basic - the basic layout, has common part of page 101 | * - blank - the blank layout 102 | * - multi - the multi degree route layout, when the degree is more than 2, 103 | * exclude the first degree and the last degree, the type of other is multi 104 | * - self - use self layout, which is the last degree route 105 | */ 106 | export type RouteComponentType = 'basic' | 'blank' | 'multi' | 'self'; 107 | 108 | /** 109 | * the route module 110 | */ 111 | export interface RouteModule { 112 | name: string; 113 | path: string; 114 | redirect?: string; 115 | component: RouteComponentType; 116 | meta: { 117 | title: string; 118 | icon: string; 119 | singleLayout?: Extract; 120 | }; 121 | children?: RouteModule[]; 122 | } 123 | 124 | export type FileWatcherEvent = 'addDir' | 'unlinkDir' | 'add' | 'unlink'; 125 | 126 | export interface FileWatcherDispatch { 127 | event: FileWatcherEvent; 128 | path: string; 129 | } 130 | 131 | export interface FileWatcherHooks { 132 | /** 133 | * rename the directory, which includes page files 134 | * @example 135 | * ``` 136 | * example1: 137 | * home home-new 138 | * ├── first ├── first 139 | * │ └── index.vue ==> │ └── index.vue 140 | * └── second └── second 141 | * └── index.vue └── index.vue 142 | * example2: 143 | * home home 144 | * └── first ==> └── first-new 145 | * └── index.vue └── index.vue 146 | * ``` 147 | */ 148 | onRenameDirWithFile(): Promise; 149 | /** 150 | * delete the directory, which includes page files 151 | * * @example 152 | * ``` 153 | * example1: 154 | * home 155 | * ├── first 156 | * │ └── index.vue ==> (delete directory home) 157 | * └── second 158 | * └── index.vue 159 | * example2: 160 | * home home 161 | * ├── first ==> └── first 162 | * │ └── index.vue └── index.vue 163 | * └── second 164 | * └── index.vue 165 | * ``` 166 | */ 167 | onDelDirWithFile(): Promise; 168 | /** 169 | * add a directory, which includes page files, it may be a copy action 170 | * * @example 171 | * ``` 172 | * example1: 173 | * home 174 | * ├── first 175 | * (add directory home) ==> │ └── index.vue 176 | * └── second 177 | * └── index.vue 178 | * example2: 179 | * home home 180 | * └── second ├── first 181 | * └── index.vue ==> │ └── index.vue 182 | * └── second 183 | * └── index.vue 184 | * ``` 185 | */ 186 | onAddDirWithFile(): Promise; 187 | /** 188 | * delete a page file 189 | * @example 190 | * ``` 191 | * example1: 192 | * home ==> home 193 | * └── index.vue 194 | * example2: 195 | * home ==> home 196 | * └── first └── first 197 | * └── index.vue 198 | * ``` 199 | */ 200 | onDelFile(): Promise; 201 | /** 202 | * add a page file 203 | * @example 204 | * ``` 205 | * example1: 206 | * home ==> home 207 | * └── index.vue 208 | * example2: 209 | * home ==> home 210 | * └── first └── first 211 | * └── index.vue 212 | * ``` 213 | */ 214 | onAddFile(): Promise; 215 | } 216 | -------------------------------------------------------------------------------- /examples/vue/src/styles/css/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 3 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 4 | */ 5 | 6 | *, 7 | ::before, 8 | ::after { 9 | box-sizing: border-box; /* 1 */ 10 | border-width: 0; /* 2 */ 11 | border-style: solid; /* 2 */ 12 | border-color: currentColor; /* 2 */ 13 | } 14 | 15 | /* 16 | 1. Use a consistent sensible line-height in all browsers. 17 | 2. Prevent adjustments of font size after orientation changes in iOS. 18 | 3. Use a more readable tab size. 19 | 4. Use the user's configured `sans` font-family by default. 20 | */ 21 | 22 | html { 23 | line-height: 1.5; /* 1 */ 24 | -webkit-text-size-adjust: 100%; /* 2 */ 25 | -moz-tab-size: 4; /* 3 */ 26 | tab-size: 4; /* 3 */ 27 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 28 | "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, 29 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ 30 | } 31 | 32 | /* 33 | 1. Remove the margin in all browsers. 34 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 35 | */ 36 | 37 | body { 38 | margin: 0; /* 1 */ 39 | line-height: inherit; /* 2 */ 40 | } 41 | 42 | /* 43 | 1. Add the correct height in Firefox. 44 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 45 | 3. Ensure horizontal rules are visible by default. 46 | */ 47 | 48 | hr { 49 | height: 0; /* 1 */ 50 | color: inherit; /* 2 */ 51 | border-top-width: 1px; /* 3 */ 52 | } 53 | 54 | /* 55 | Add the correct text decoration in Chrome, Edge, and Safari. 56 | */ 57 | 58 | abbr:where([title]) { 59 | text-decoration: underline dotted; 60 | } 61 | 62 | /* 63 | Remove the default font size and weight for headings. 64 | */ 65 | 66 | h1, 67 | h2, 68 | h3, 69 | h4, 70 | h5, 71 | h6 { 72 | font-size: inherit; 73 | font-weight: inherit; 74 | } 75 | 76 | /* 77 | Reset links to optimize for opt-in styling instead of opt-out. 78 | */ 79 | 80 | a { 81 | color: inherit; 82 | text-decoration: inherit; 83 | } 84 | 85 | /* 86 | Add the correct font weight in Edge and Safari. 87 | */ 88 | 89 | b, 90 | strong { 91 | font-weight: bolder; 92 | } 93 | 94 | /* 95 | 1. Use the user's configured `mono` font family by default. 96 | 2. Correct the odd `em` font sizing in all browsers. 97 | */ 98 | 99 | code, 100 | kbd, 101 | samp, 102 | pre { 103 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 104 | "Liberation Mono", "Courier New", monospace; /* 1 */ 105 | font-size: 1em; /* 2 */ 106 | } 107 | 108 | /* 109 | Add the correct font size in all browsers. 110 | */ 111 | 112 | small { 113 | font-size: 80%; 114 | } 115 | 116 | /* 117 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 118 | */ 119 | 120 | sub, 121 | sup { 122 | font-size: 75%; 123 | line-height: 0; 124 | position: relative; 125 | vertical-align: baseline; 126 | } 127 | 128 | sub { 129 | bottom: -0.25em; 130 | } 131 | 132 | sup { 133 | top: -0.5em; 134 | } 135 | 136 | /* 137 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 138 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 139 | 3. Remove gaps between table borders by default. 140 | */ 141 | 142 | table { 143 | text-indent: 0; /* 1 */ 144 | border-color: inherit; /* 2 */ 145 | border-collapse: collapse; /* 3 */ 146 | } 147 | 148 | /* 149 | 1. Change the font styles in all browsers. 150 | 2. Remove the margin in Firefox and Safari. 151 | 3. Remove default padding in all browsers. 152 | */ 153 | 154 | button, 155 | input, 156 | optgroup, 157 | select, 158 | textarea { 159 | font-family: inherit; /* 1 */ 160 | font-size: 100%; /* 1 */ 161 | line-height: inherit; /* 1 */ 162 | color: inherit; /* 1 */ 163 | margin: 0; /* 2 */ 164 | padding: 0; /* 3 */ 165 | } 166 | 167 | /* 168 | Remove the inheritance of text transform in Edge and Firefox. 169 | */ 170 | 171 | button, 172 | select { 173 | text-transform: none; 174 | } 175 | 176 | /* 177 | 1. Correct the inability to style clickable types in iOS and Safari. 178 | 2. Remove default button styles. 179 | */ 180 | 181 | button, 182 | [type="button"], 183 | [type="reset"], 184 | [type="submit"] { 185 | -webkit-appearance: button; /* 1 */ 186 | /* background-color: transparent; 2 */ 187 | background-image: none; /* 2 */ 188 | } 189 | 190 | /* 191 | Use the modern Firefox focus style for all focusable elements. 192 | */ 193 | 194 | :-moz-focusring { 195 | outline: auto; 196 | } 197 | 198 | /* 199 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 200 | */ 201 | 202 | :-moz-ui-invalid { 203 | box-shadow: none; 204 | } 205 | 206 | /* 207 | Add the correct vertical alignment in Chrome and Firefox. 208 | */ 209 | 210 | progress { 211 | vertical-align: baseline; 212 | } 213 | 214 | /* 215 | Correct the cursor style of increment and decrement buttons in Safari. 216 | */ 217 | 218 | ::-webkit-inner-spin-button, 219 | ::-webkit-outer-spin-button { 220 | height: auto; 221 | } 222 | 223 | /* 224 | 1. Correct the odd appearance in Chrome and Safari. 225 | 2. Correct the outline style in Safari. 226 | */ 227 | 228 | [type="search"] { 229 | -webkit-appearance: textfield; /* 1 */ 230 | outline-offset: -2px; /* 2 */ 231 | } 232 | 233 | /* 234 | Remove the inner padding in Chrome and Safari on macOS. 235 | */ 236 | 237 | ::-webkit-search-decoration { 238 | -webkit-appearance: none; 239 | } 240 | 241 | /* 242 | 1. Correct the inability to style clickable types in iOS and Safari. 243 | 2. Change font properties to `inherit` in Safari. 244 | */ 245 | 246 | ::-webkit-file-upload-button { 247 | -webkit-appearance: button; /* 1 */ 248 | font: inherit; /* 2 */ 249 | } 250 | 251 | /* 252 | Add the correct display in Chrome and Safari. 253 | */ 254 | 255 | summary { 256 | display: list-item; 257 | } 258 | 259 | /* 260 | Removes the default spacing and border for appropriate elements. 261 | */ 262 | 263 | blockquote, 264 | dl, 265 | dd, 266 | h1, 267 | h2, 268 | h3, 269 | h4, 270 | h5, 271 | h6, 272 | hr, 273 | figure, 274 | p, 275 | pre { 276 | margin: 0; 277 | } 278 | 279 | fieldset { 280 | margin: 0; 281 | padding: 0; 282 | } 283 | 284 | legend { 285 | padding: 0; 286 | } 287 | 288 | ol, 289 | ul, 290 | menu { 291 | list-style: none; 292 | margin: 0; 293 | padding: 0; 294 | } 295 | 296 | /* 297 | Prevent resizing textareas horizontally by default. 298 | */ 299 | 300 | textarea { 301 | resize: vertical; 302 | } 303 | 304 | /* 305 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 306 | 2. Set the default placeholder color to the user's configured gray 400 color. 307 | */ 308 | 309 | input::placeholder, 310 | textarea::placeholder { 311 | opacity: 1; /* 1 */ 312 | color: #9ca3af; /* 2 */ 313 | } 314 | 315 | /* 316 | Set the default cursor for buttons. 317 | */ 318 | 319 | button, 320 | [role="button"] { 321 | cursor: pointer; 322 | } 323 | 324 | /* 325 | Make sure disabled buttons don't get the pointer cursor. 326 | */ 327 | :disabled { 328 | cursor: default; 329 | } 330 | 331 | /* 332 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 333 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 334 | This can trigger a poorly considered lint error in some tools but is included by design. 335 | */ 336 | 337 | img, 338 | svg, 339 | video, 340 | canvas, 341 | audio, 342 | iframe, 343 | embed, 344 | object { 345 | display: block; /* 1 */ 346 | vertical-align: middle; /* 2 */ 347 | } 348 | 349 | /* 350 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 351 | */ 352 | 353 | img, 354 | video { 355 | max-width: 100%; 356 | height: auto; 357 | } 358 | 359 | /* 360 | Ensure the default browser behavior of the `hidden` attribute. 361 | */ 362 | 363 | [hidden] { 364 | display: none; 365 | } 366 | 367 | .dark { 368 | color-scheme: dark; 369 | } 370 | -------------------------------------------------------------------------------- /src/context/fs.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getRenamedDirConfig, 3 | getDelDirConfig, 4 | getAddDirConfig, 5 | getDelFileConfig, 6 | getAddFileConfig, 7 | getRouteModuleNameByRouteName, 8 | getRoutePathFromName, 9 | getRouteModuleNameByGlob, 10 | getRouteModuleWhetherFileExist, 11 | getSingleRouteModulesFromGlob, 12 | mergeFirstDegreeRouteModule, 13 | getRouteNameByGlobWithTransformer, 14 | recurseRemoveModuleByNames, 15 | useFsExtra 16 | } from '../shared'; 17 | import type { 18 | ContextOption, 19 | RouteConfig, 20 | FileWatcherDispatch, 21 | FileWatcherHooks, 22 | FileWatcherEvent, 23 | RouteModule 24 | } from '../types'; 25 | import { generateRouteModuleCode } from './module'; 26 | 27 | export async function fileWatcherHandler(dispatchs: FileWatcherDispatch[], hooks: FileWatcherHooks) { 28 | const dispatchWithCategory: Record = { 29 | addDir: [], 30 | unlinkDir: [], 31 | add: [], 32 | unlink: [] 33 | }; 34 | 35 | dispatchs.forEach(item => { 36 | dispatchWithCategory[item.event].push(item.path); 37 | }); 38 | 39 | const hasAddDir = dispatchWithCategory.addDir.length > 0; 40 | const hasUnlinkDir = dispatchWithCategory.unlinkDir.length > 0; 41 | const hasAdd = dispatchWithCategory.add.length > 0; 42 | const hasUnlink = dispatchWithCategory.unlink.length > 0; 43 | 44 | const { onRenameDirWithFile, onDelDirWithFile, onAddDirWithFile, onDelFile, onAddFile } = hooks; 45 | 46 | const conditions: [boolean, () => Promise][] = [ 47 | [hasAddDir && hasUnlinkDir && hasAdd && hasUnlink, onRenameDirWithFile], 48 | [hasUnlinkDir && hasUnlink, onDelDirWithFile], 49 | [hasAddDir && hasAdd, onAddDirWithFile], 50 | [hasUnlink, onDelFile], 51 | [hasAdd, onAddFile] 52 | ]; 53 | 54 | const [, callback] = conditions.find(([condition]) => condition) || [true, async () => {}]; 55 | 56 | await callback(); 57 | } 58 | 59 | export function createFWHooksOfGenDeclarationAndViews( 60 | dispatchs: FileWatcherDispatch[], 61 | routeConfig: RouteConfig, 62 | options: ContextOption 63 | ) { 64 | const hooks: FileWatcherHooks = { 65 | async onRenameDirWithFile() { 66 | const { oldRouteName, newRouteName, oldRouteFilePath, newRouteFilePath } = getRenamedDirConfig( 67 | dispatchs, 68 | options 69 | ); 70 | 71 | routeConfig.names = routeConfig.names.map(name => name.replace(oldRouteName, newRouteName)); 72 | 73 | routeConfig.files = routeConfig.files.map(item => { 74 | const name = item.name.replace(oldRouteName, newRouteName); 75 | const path = item.path.replace(oldRouteFilePath, newRouteFilePath); 76 | 77 | return { 78 | name, 79 | path 80 | }; 81 | }); 82 | }, 83 | async onDelDirWithFile() { 84 | const { delRouteName } = getDelDirConfig(dispatchs, options); 85 | 86 | routeConfig.names = routeConfig.names.filter(name => !name.includes(delRouteName)); 87 | routeConfig.files = routeConfig.files.filter(item => !item.name.includes(delRouteName)); 88 | }, 89 | async onAddDirWithFile() { 90 | const config = getAddDirConfig(dispatchs, options); 91 | 92 | routeConfig.names = routeConfig.names.concat(config.names).sort(); 93 | routeConfig.files = routeConfig.files.concat(config.files).sort((a, b) => (a.name > b.name ? 1 : -1)); 94 | }, 95 | async onDelFile() { 96 | const { delRouteNames } = getDelFileConfig(dispatchs, options); 97 | 98 | routeConfig.names = routeConfig.names.filter(name => delRouteNames.every(item => !name.includes(item))); 99 | routeConfig.files = routeConfig.files.filter(item => delRouteNames.every(v => !item.name.includes(v))); 100 | }, 101 | async onAddFile() { 102 | const config = getAddFileConfig(dispatchs, options); 103 | 104 | routeConfig.names = routeConfig.names.concat(config.names).sort(); 105 | routeConfig.files = routeConfig.files.concat(config.files).sort((a, b) => (a.name > b.name ? 1 : -1)); 106 | } 107 | }; 108 | 109 | return hooks; 110 | } 111 | 112 | export function createFWHooksOfGenModule( 113 | dispatchs: FileWatcherDispatch[], 114 | routeConfig: RouteConfig, 115 | options: ContextOption 116 | ) { 117 | async function getRouteModule( 118 | moduleName: string, 119 | existModuleName: string, 120 | existCallback: (module: RouteModule, filePath: string) => Promise 121 | ) { 122 | return getRouteModuleWhetherFileExist({ moduleName, existModuleName, routeConfig, options, existCallback }); 123 | } 124 | 125 | const hooks: FileWatcherHooks = { 126 | async onRenameDirWithFile() { 127 | const { oldRouteName, newRouteName } = getRenamedDirConfig(dispatchs, options); 128 | if (!oldRouteName || !newRouteName) return; 129 | const { remove } = await useFsExtra(); 130 | 131 | const oldRoutePath = getRoutePathFromName(oldRouteName); 132 | const newRoutePath = getRoutePathFromName(newRouteName); 133 | const oldModuleName = getRouteModuleNameByRouteName(oldRouteName); 134 | const newModuleName = getRouteModuleNameByRouteName(newRouteName); 135 | 136 | const module = await getRouteModule(newModuleName, oldModuleName, async (routeModule, filePath) => { 137 | const moduleJson = JSON.stringify(routeModule); 138 | const updateModuleJson = moduleJson 139 | .replace(new RegExp(`"${oldRouteName}`, 'g'), `"${newRouteName}`) 140 | .replace(new RegExp(`${oldRoutePath}`, 'g'), newRoutePath); 141 | 142 | const existModule = JSON.parse(updateModuleJson) as RouteModule; 143 | 144 | await remove(filePath); 145 | 146 | return existModule; 147 | }); 148 | 149 | if (module) { 150 | await generateRouteModuleCode(newModuleName, module, options); 151 | } 152 | }, 153 | async onDelDirWithFile() { 154 | const { remove } = await useFsExtra(); 155 | const { delRouteName } = getDelDirConfig(dispatchs, options); 156 | const moduleName = getRouteModuleNameByRouteName(delRouteName); 157 | 158 | const globs = dispatchs.filter(dispatch => dispatch.event === 'unlink').map(dispatch => dispatch.path); 159 | const routeNames = globs.map(glob => getRouteNameByGlobWithTransformer(glob, options)); 160 | 161 | const module = await getRouteModule(moduleName, moduleName, async (routeModule, filePath) => { 162 | if (delRouteName === moduleName) { 163 | await remove(filePath); 164 | 165 | return null; 166 | } 167 | 168 | recurseRemoveModuleByNames(routeModule, routeNames); 169 | 170 | return routeModule; 171 | }); 172 | 173 | if (module) { 174 | await generateRouteModuleCode(moduleName, module, options); 175 | } 176 | }, 177 | async onAddDirWithFile() { 178 | const globs = dispatchs.filter(dispatch => dispatch.event === 'add').map(dispatch => dispatch.path); 179 | 180 | const moduleName = getRouteModuleNameByGlob(globs[0], options); 181 | 182 | const module = await getRouteModule(moduleName, moduleName, async routeModule => { 183 | globs.forEach(glob => { 184 | const modules = getSingleRouteModulesFromGlob(glob, options); 185 | mergeFirstDegreeRouteModule(routeModule, modules); 186 | }); 187 | 188 | return routeModule; 189 | }); 190 | 191 | if (module) { 192 | await generateRouteModuleCode(moduleName, module, options); 193 | } 194 | }, 195 | async onDelFile() { 196 | const { remove } = await useFsExtra(); 197 | const { delRouteNames } = getDelFileConfig(dispatchs, options); 198 | 199 | const globs = dispatchs.filter(dispatch => dispatch.event === 'unlink').map(dispatch => dispatch.path); 200 | 201 | delRouteNames.forEach(async delRouteName => { 202 | const moduleName = getRouteModuleNameByRouteName(delRouteName); 203 | 204 | const routeNames = globs.map(glob => getRouteNameByGlobWithTransformer(glob, options)); 205 | 206 | const module = await getRouteModule(moduleName, moduleName, async (routeModule, filePath) => { 207 | if (delRouteName === moduleName) { 208 | await remove(filePath); 209 | 210 | return null; 211 | } 212 | 213 | recurseRemoveModuleByNames(routeModule, routeNames); 214 | 215 | return routeModule; 216 | }); 217 | 218 | if (module) { 219 | await generateRouteModuleCode(moduleName, module, options); 220 | } 221 | }); 222 | }, 223 | async onAddFile() { 224 | await this.onAddDirWithFile(); 225 | } 226 | }; 227 | 228 | return hooks; 229 | } 230 | -------------------------------------------------------------------------------- /src/shared/route.ts: -------------------------------------------------------------------------------- 1 | import { access } from 'fs/promises'; 2 | import { red, bgRed, green, bgYellow, yellow } from 'kolorist'; 3 | import { transformFile } from '@swc/core'; 4 | import { SPLASH_MARK, PAGE_DEGREE_SPLIT_MARK, ROUTE_NAME_REG, INVALID_ROUTE_NAME, CAMEL_OR_PASCAL } from './constant'; 5 | import { getRelativePathOfGlob } from './glob'; 6 | import { useFsExtra } from './fs-extra'; 7 | import type { 8 | ContextOption, 9 | RouteConfig, 10 | RouteModule, 11 | RouteComponentType, 12 | FileWatcherDispatch, 13 | RouteFile 14 | } from '../types'; 15 | 16 | // route config utils 17 | function transformRouteName(glob: string, routeName: string, pageDir: string) { 18 | let name = routeName; 19 | 20 | const filePath = getRelativePathOfGlob(glob, pageDir); 21 | 22 | if (CAMEL_OR_PASCAL.test(routeName)) { 23 | let warning = `${bgYellow('RECOMMEND')} `; 24 | warning += yellow(`the filePath: ${filePath}`); 25 | warning += green(`\n it's recommended to use kebab-case name style`); 26 | warning += green(`\n example: good: user-info bad: userInfo, UserInfo`); 27 | // eslint-disable-next-line no-console 28 | console.info(warning); 29 | } 30 | 31 | if (!ROUTE_NAME_REG.test(name)) { 32 | name = INVALID_ROUTE_NAME; 33 | 34 | let error = `${bgRed('ERROR')} `; 35 | error += red(`the path is invalid: ${filePath} !\n`); 36 | error += red(`routeName: ${routeName} !`); 37 | error += green( 38 | `\n the directory name and file name can only include letter[a-zA-Z], number[0-9], underline[_] and dollar[$]` 39 | ); 40 | // eslint-disable-next-line no-console 41 | console.error(error); 42 | } 43 | 44 | return name; 45 | } 46 | 47 | export function getRouteNameByGlob(glob: string, pageDir: string) { 48 | const globSplits = glob.split(SPLASH_MARK); 49 | 50 | const isFile = glob.includes('.'); 51 | const sliceLength = isFile ? globSplits.length - 1 : globSplits.length; 52 | 53 | const routeName = globSplits.splice(0, sliceLength).join(PAGE_DEGREE_SPLIT_MARK); 54 | 55 | return transformRouteName(glob, routeName, pageDir); 56 | } 57 | 58 | export function getAllRouteNames(routeName: string) { 59 | const names = routeName.split(PAGE_DEGREE_SPLIT_MARK); 60 | 61 | const namesWithParent: string[] = []; 62 | 63 | for (let i = 1; i <= names.length; i += 1) { 64 | const parentName = names.slice(0, i).reduce((pre, cur) => pre + PAGE_DEGREE_SPLIT_MARK + cur); 65 | namesWithParent.push(parentName); 66 | } 67 | 68 | return namesWithParent; 69 | } 70 | 71 | export function getRouteFilePathByGlob(glob: string) { 72 | return `./${glob}`; 73 | } 74 | 75 | export function getRouteNameByGlobWithTransformer(glob: string, options: ContextOption) { 76 | const routeName = getRouteNameByGlob(glob, options.pageDir); 77 | return options.routeNameTansformer(routeName); 78 | } 79 | 80 | export function getRouteConfigByGlobs(globs: string[], options: ContextOption) { 81 | const config: RouteConfig = { 82 | names: [], 83 | files: [] 84 | }; 85 | 86 | globs.sort().forEach(glob => { 87 | const routeName = getRouteNameByGlob(glob, options.pageDir); 88 | const names = getAllRouteNames(routeName); 89 | config.names.push(...names); 90 | 91 | const filePath = getRouteFilePathByGlob(glob); 92 | config.files.push({ name: routeName, path: filePath }); 93 | }); 94 | 95 | config.names = Array.from(new Set([...config.names])) 96 | .map(name => options.routeNameTansformer(name)) 97 | .filter(name => Boolean(name) && name !== INVALID_ROUTE_NAME); 98 | 99 | config.files = config.files 100 | .map(({ name, path }) => ({ name: options.routeNameTansformer(name), path })) 101 | .filter(item => item.name !== INVALID_ROUTE_NAME); 102 | 103 | return config; 104 | } 105 | 106 | // route module utils 107 | interface RouteModuleConfig { 108 | component: RouteComponentType; 109 | hasSingleLayout: boolean; 110 | } 111 | function getRouteModuleConfig(index: number, length: number) { 112 | const actions: [boolean, RouteModuleConfig][] = [ 113 | [length === 1, { component: 'self', hasSingleLayout: true }], 114 | [length === 2 && index === 0, { component: 'basic', hasSingleLayout: false }], 115 | [length === 2 && index === 1, { component: 'self', hasSingleLayout: false }], 116 | [length >= 3 && index === 0, { component: 'basic', hasSingleLayout: false }], 117 | [length >= 3 && index === length - 1, { component: 'self', hasSingleLayout: false }], 118 | [true, { component: 'multi', hasSingleLayout: false }] 119 | ]; 120 | 121 | const config: RouteModuleConfig = { 122 | component: 'self', 123 | hasSingleLayout: false 124 | }; 125 | 126 | const findItem = actions.find(([condition]) => condition); 127 | 128 | return findItem?.[1] || config; 129 | } 130 | 131 | export function getRoutePathFromName(routeName: string) { 132 | const PATH_SPLIT_MARK = '/'; 133 | 134 | return PATH_SPLIT_MARK + routeName.replace(new RegExp(`${PAGE_DEGREE_SPLIT_MARK}`, 'g'), PATH_SPLIT_MARK); 135 | } 136 | 137 | export function getRouteModuleNameByRouteName(routeName: string) { 138 | const routeNames = getAllRouteNames(routeName); 139 | 140 | if (!routeNames.length) { 141 | throw new Error(`路由名称不正确!`); 142 | } 143 | 144 | return routeNames[0]; 145 | } 146 | 147 | export function getRouteModuleNameByGlob(glob: string, options: ContextOption) { 148 | const routeName = getRouteNameByGlobWithTransformer(glob, options); 149 | 150 | const moduleName = getRouteModuleNameByRouteName(routeName); 151 | 152 | return moduleName; 153 | } 154 | 155 | export function checkIsValidRouteModule(data: any): data is RouteModule { 156 | const isObject = Object.prototype.toString.call(data) === '[object Object]'; 157 | 158 | return isObject && data.name && data.path && data.component && data.meta; 159 | } 160 | 161 | function getSingleRouteModulesFromRouteName(routeName: string) { 162 | const routeNames = getAllRouteNames(routeName); 163 | 164 | const modules: RouteModule[] = routeNames.map((item, index) => { 165 | const config = getRouteModuleConfig(index, routeNames.length); 166 | 167 | const module: RouteModule = { 168 | name: item, 169 | path: getRoutePathFromName(item), 170 | component: config.component, 171 | meta: { 172 | title: item, 173 | icon: 'mdi:menu' 174 | } 175 | }; 176 | 177 | if (config.hasSingleLayout) { 178 | module.meta.singleLayout = 'basic'; 179 | } 180 | 181 | return module; 182 | }); 183 | 184 | return modules; 185 | } 186 | 187 | export function getSingleRouteModulesFromGlob(glob: string, options: ContextOption) { 188 | const routeName = getRouteNameByGlobWithTransformer(glob, options); 189 | const modules = getSingleRouteModulesFromRouteName(routeName); 190 | 191 | return modules; 192 | } 193 | 194 | function getSingleRouteModulesWithChildren(singleModules: RouteModule[]): RouteModule | null { 195 | const reversedModules = [...singleModules].reverse(); 196 | 197 | reversedModules.forEach((module, index) => { 198 | if (index < reversedModules.length - 1) { 199 | reversedModules[index + 1].children = [module]; 200 | } 201 | }); 202 | 203 | return reversedModules[reversedModules.length - 1] || null; 204 | } 205 | 206 | function recurseMergeModule(modules: RouteModule[], singleModules: RouteModule[], singleRouteLevel: number) { 207 | if (!singleModules.length) return; 208 | 209 | const currentLevelRouteModule = singleModules[singleRouteLevel]; 210 | 211 | const findIndex = modules.findIndex(module => module.name === currentLevelRouteModule.name); 212 | 213 | if (findIndex > -1) { 214 | const findModule = modules[findIndex]; 215 | 216 | if (!findModule.children) { 217 | findModule.children = []; 218 | } 219 | 220 | recurseMergeModule(findModule.children!, singleModules, singleRouteLevel + 1); 221 | } else { 222 | const pushModule = getSingleRouteModulesWithChildren(singleModules.slice(singleRouteLevel)); 223 | if (pushModule) { 224 | modules.push(pushModule); 225 | } 226 | } 227 | } 228 | 229 | export function mergeFirstDegreeRouteModule(firstDegreeRouteModule: RouteModule, singleModules: RouteModule[]) { 230 | if (!firstDegreeRouteModule.children) { 231 | firstDegreeRouteModule.children = []; 232 | } 233 | 234 | recurseMergeModule(firstDegreeRouteModule.children!, singleModules, 1); 235 | } 236 | 237 | export function getTotalRouteModuleFromNames(routeNames: string[]) { 238 | let module: RouteModule; 239 | 240 | routeNames.forEach((routeName, index) => { 241 | const modules = getSingleRouteModulesFromRouteName(routeName); 242 | 243 | const [firstModule] = modules; 244 | 245 | if (index === 0) { 246 | module = firstModule; 247 | } 248 | 249 | if (firstModule.name === module.name && modules.length > 1) { 250 | mergeFirstDegreeRouteModule(module, modules); 251 | } 252 | }); 253 | 254 | return module!; 255 | } 256 | 257 | export function getRouteModuleFilePath(moduleName: string, options: ContextOption) { 258 | const { rootDir, routeModuleDir, routeModuleExt } = options; 259 | 260 | const filePath = `${rootDir}/${routeModuleDir}/${moduleName}.${routeModuleExt}`; 261 | 262 | return filePath; 263 | } 264 | 265 | export async function getIsRouteModuleFileExist(moduleName: string, options: ContextOption) { 266 | const filePath = getRouteModuleFilePath(moduleName, options); 267 | 268 | let exist = false; 269 | try { 270 | await access(filePath); 271 | exist = true; 272 | } catch {} 273 | 274 | return { 275 | exist, 276 | filePath 277 | }; 278 | } 279 | 280 | function getTheSmallLengthOfStrArr(arr: string[]) { 281 | let name: string = arr[0]; 282 | 283 | arr.forEach(item => { 284 | if (name === null) { 285 | name = item; 286 | } else { 287 | name = item.length < name.length ? item : name; 288 | } 289 | }); 290 | 291 | return name; 292 | } 293 | 294 | // eslint-disable-next-line max-params 295 | export async function getRouteModuleWhetherFileExist(params: { 296 | moduleName: string; 297 | existModuleName: string; 298 | routeConfig: RouteConfig; 299 | options: ContextOption; 300 | existCallback: (module: RouteModule, filePath: string) => Promise; 301 | }) { 302 | const { moduleName, existModuleName, routeConfig, options, existCallback } = params; 303 | 304 | const { exist, filePath } = await getIsRouteModuleFileExist(existModuleName, options); 305 | 306 | let module: RouteModule | null; 307 | 308 | try { 309 | if (exist) { 310 | const importModule = await getRouteModuleFromFile(filePath, existModuleName, options); 311 | 312 | if (checkIsValidRouteModule(importModule)) { 313 | module = await existCallback(importModule, filePath); 314 | } else { 315 | throw Error('invalid route module!'); 316 | } 317 | } else { 318 | throw Error('not exist module file!'); 319 | } 320 | } catch (error) { 321 | const routeNames = routeConfig.files.filter(item => item.name.includes(moduleName)).map(item => item.name); 322 | 323 | module = getTotalRouteModuleFromNames(routeNames); 324 | } 325 | 326 | return module; 327 | } 328 | 329 | export function recurseRemoveModuleByName(module: RouteModule, routeName: string) { 330 | if (!module.children) return; 331 | 332 | module.children = module.children.filter(item => item.name !== routeName); 333 | 334 | module.children.forEach(item => { 335 | if (routeName.includes(item.name)) { 336 | recurseRemoveModuleByName(item, routeName); 337 | } 338 | }); 339 | } 340 | 341 | export function recurseRemoveModuleByNames(module: RouteModule, routeNames: string[]) { 342 | if (!routeNames.length) return; 343 | 344 | routeNames.forEach(item => { 345 | recurseRemoveModuleByName(module, item); 346 | }); 347 | } 348 | 349 | // FSWatcher hooks utils 350 | 351 | export function getRenamedDirConfig(dispatchs: FileWatcherDispatch[], options: ContextOption) { 352 | const unlinkDirs: string[] = []; 353 | const addDirs: string[] = []; 354 | 355 | dispatchs.forEach(dispatch => { 356 | if (dispatch.event === 'unlinkDir') { 357 | unlinkDirs.push(dispatch.path); 358 | } 359 | if (dispatch.event === 'addDir') { 360 | addDirs.push(dispatch.path); 361 | } 362 | }); 363 | 364 | const oldDir = getTheSmallLengthOfStrArr(unlinkDirs); 365 | const newDir = getTheSmallLengthOfStrArr(addDirs); 366 | 367 | const oldRouteName = getRouteNameByGlobWithTransformer(oldDir, options); 368 | const oldRouteFilePath = getRouteFilePathByGlob(oldDir); 369 | 370 | const newRouteName = getRouteNameByGlobWithTransformer(newDir, options); 371 | const newRouteFilePath = getRouteFilePathByGlob(newDir); 372 | 373 | return { 374 | oldRouteName, 375 | newRouteName, 376 | oldRouteFilePath, 377 | newRouteFilePath 378 | }; 379 | } 380 | 381 | export function getDelDirConfig(dispatchs: FileWatcherDispatch[], options: ContextOption) { 382 | const unlinkDirs: string[] = []; 383 | 384 | dispatchs.forEach(dispatch => { 385 | if (dispatch.event === 'unlinkDir') { 386 | unlinkDirs.push(dispatch.path); 387 | } 388 | }); 389 | 390 | const delDir = getTheSmallLengthOfStrArr(unlinkDirs); 391 | 392 | const delRouteName = getRouteNameByGlobWithTransformer(delDir, options); 393 | 394 | return { 395 | delRouteName 396 | }; 397 | } 398 | 399 | export function getAddDirConfig(dispatchs: FileWatcherDispatch[], options: ContextOption) { 400 | const globs: string[] = []; 401 | 402 | dispatchs.forEach(dispatch => { 403 | if (dispatch.event === 'add') { 404 | globs.push(dispatch.path); 405 | } 406 | }); 407 | 408 | const config = getRouteConfigByGlobs(globs, options); 409 | 410 | return config; 411 | } 412 | 413 | export function getDelFileConfig(dispatchs: FileWatcherDispatch[], options: ContextOption) { 414 | const delRouteNames: string[] = []; 415 | 416 | dispatchs.forEach(dispatch => { 417 | if (dispatch.event === 'unlink') { 418 | const name = getRouteNameByGlobWithTransformer(dispatch.path, options); 419 | delRouteNames.push(name); 420 | } 421 | }); 422 | 423 | return { 424 | delRouteNames 425 | }; 426 | } 427 | 428 | export function getAddFileConfig(dispatchs: FileWatcherDispatch[], options: ContextOption) { 429 | const addRouteNames: string[] = []; 430 | const addRouteFiles: RouteFile[] = []; 431 | 432 | dispatchs.forEach(dispatch => { 433 | if (dispatch.event === 'add') { 434 | const name = getRouteNameByGlobWithTransformer(dispatch.path, options); 435 | addRouteNames.push(name); 436 | 437 | const path = getRouteFilePathByGlob(dispatch.path); 438 | addRouteFiles.push({ name, path }); 439 | } 440 | }); 441 | 442 | const config: RouteConfig = { 443 | names: addRouteNames, 444 | files: addRouteFiles 445 | }; 446 | 447 | return config; 448 | } 449 | 450 | async function getRouteModuleFromFile(filePath: string, moduleName: string, options: ContextOption) { 451 | const { writeFile, remove } = await useFsExtra(); 452 | 453 | const transformedFilePath = filePath.replace(`${moduleName}.${options.routeModuleExt}`, `${moduleName}-swc.js`); 454 | const { code } = await transformFile(filePath, { filename: transformedFilePath, module: { type: 'commonjs' } }); 455 | 456 | if (code) { 457 | await writeFile(transformedFilePath, code, 'utf-8'); 458 | } 459 | // eslint-disable-next-line @typescript-eslint/no-var-requires 460 | const { default: importModule } = require(transformedFilePath); 461 | 462 | await remove(transformedFilePath); 463 | 464 | return importModule as RouteModule; 465 | } 466 | 467 | export function transformModuleNameToVariable(name: string) { 468 | return name.replace(/-(\w)/g, (_, match: string) => match.toUpperCase()); 469 | } 470 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## [v0.0.6](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.5...main) (23-06-08) 5 | 6 | ###    🚀 Features 7 | 8 | - **projects**: Add route module code auto generate  -  by @soybeanjs [(e9edf)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e9edf14) 9 | - **projects**: Add eslint formate code after generate route module  -  by @soybeanjs [(6cb1d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/6cb1d08) 10 | - **projects**: Perf route generate  -  by @soybeanjs [(b4de1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/b4de144) 11 | - **projects**: Add generate route module on FileWatcher hooks  -  by @soybeanjs [(f46aa)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f46aad2) 12 | - **projects**: Complete the plugin refactor  -  by @soybeanjs [(04cd8)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/04cd838) 13 | - **projects**: Support ESM  -  by @soybeanjs [(72e7d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/72e7d42) 14 | 15 | ###    🐞 Bug Fixes 16 | 17 | - **projects**: Fix some bugs  -  by @soybeanjs [(e6c38)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e6c389d) 18 | - **projects**: Fix package.json  -  by @soybeanjs [(8b603)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/8b603b0) 19 | - **projects**: Add plugin fix  -  by @soybeanjs [(95907)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/959070e) 20 | - **projects**: Use unbuild replace tsup  -  by @soybeanjs [(cc929)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/cc929ac) 21 | 22 | ###    🔥 Performance 23 | 24 | - **projects**: Perf plugin  -  by @soybeanjs [(2669c)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/2669c53) 25 | - **projects**: Update plugin default option and update README  -  by @soybeanjs [(85ea1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/85ea171) 26 | 27 | ###    💅 Refactors 28 | 29 | - **projects**: Refactor plugin  -  by @soybeanjs [(ac725)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/ac725e2) 30 | - **projects**: Refactor get directory methord  -  by @soybeanjs [(a1490)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/a1490e5) 31 | - **projects**: Refactor plugin  -  by @soybeanjs [(d2853)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d285353) 32 | - **projects**: Refactor generate route module code  -  by @soybeanjs [(10691)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/106919e) 33 | - **projects**: Import chokidar to watch directory and file change  -  by @soybeanjs [(96e31)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/96e31c4) 34 | - **projects**: Add different situations of directory or file change  -  by @soybeanjs [(65996)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/659961a) 35 | - **projects**: Refactor plugin: update plugin options, and update the relative methods of glob  -  by @soybeanjs [(dab6a)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/dab6a35) 36 | - **projects**: Refactor plugin: add file watcher handler hooks  -  by @soybeanjs [(35695)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/35695c9) 37 | - **projects**: Refactor plugin: compelte generate declaration and views  -  by @soybeanjs [(14737)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/147377b) 38 | - **projects**: Update plugin  -  by @soybeanjs [(9bdda)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/9bddad1) 39 | 40 | ###    📖 Documentation 41 | 42 | - **projects**: Update README  -  by @soybeanjs [(19c28)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/19c289e) 43 | - **projects**: Update README  -  by @soybeanjs [(06150)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/0615012) 44 | - **projects**: Update README  -  by @soybeanjs [(25fba)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/25fbaae) 45 | - **projects**: Add CHANGELOG.md  -  by @soybeanjs [(d76b0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d76b0f1) 46 | 47 | ###    📦 Build 48 | 49 | - **projects**: Add czg  -  by @soybeanjs [(1b3f0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/1b3f03d) 50 | - **projects**: Remove husky  -  by @soybeanjs [(95ff0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/95ff0c5) 51 | - **projects**: Update deps and update package.json  -  by @soybeanjs [(3c39d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/3c39de2) 52 | 53 | ###    🏡 Chore 54 | 55 | - Release v0.0.3  -  by @soybeanjs [(fb987)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/fb98775) 56 | - Release v0.0.4  -  by @soybeanjs [(f4ecd)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f4ecd03) 57 | - Release v0.0.5  -  by @soybeanjs [(d6943)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d694304) 58 | - Release v1.0.4  -  by @soybeanjs [(e7d45)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e7d4598) 59 | - Release v1.0.5  -  by @soybeanjs [(92bd0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/92bd083) 60 | 61 | ###    ❤️ Contributors 62 | 63 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   64 | 65 | ## [v1.0.5](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.4...v1.0.5) (2023-06-04) 66 | 67 | ###    🏡 Chore 68 | 69 | - Release v1.0.5  -  by @soybeanjs [(92bd0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/92bd083) 70 | 71 | ###    ❤️ Contributors 72 | 73 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   74 | 75 | ## [v1.0.4](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v0.0.5...v1.0.4) (2023-06-02) 76 | 77 | ###    🚀 Features 78 | 79 | - **projects**: Add route module code auto generate  -  by @soybeanjs [(e9edf)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e9edf14) 80 | - **projects**: Add eslint formate code after generate route module  -  by @soybeanjs [(6cb1d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/6cb1d08) 81 | - **projects**: Perf route generate  -  by @soybeanjs [(b4de1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/b4de144) 82 | - **projects**: Add generate route module on FileWatcher hooks  -  by @soybeanjs [(f46aa)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f46aad2) 83 | - **projects**: Complete the plugin refactor  -  by @soybeanjs [(04cd8)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/04cd838) 84 | 85 | ###    🐞 Bug Fixes 86 | 87 | - **projects**: Fix some bugs  -  by @soybeanjs [(e6c38)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e6c389d) 88 | - **projects**: Fix package.json  -  by @soybeanjs [(8b603)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/8b603b0) 89 | - **projects**: Add plugin fix  -  by @soybeanjs [(95907)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/959070e) 90 | 91 | ###    🔥 Performance 92 | 93 | - **projects**: Perf plugin  -  by @soybeanjs [(2669c)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/2669c53) 94 | - **projects**: Update plugin default option and update README  -  by @soybeanjs [(85ea1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/85ea171) 95 | 96 | ###    💅 Refactors 97 | 98 | - **projects**: Refactor plugin  -  by @soybeanjs [(ac725)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/ac725e2) 99 | - **projects**: Refactor get directory methord  -  by @soybeanjs [(a1490)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/a1490e5) 100 | - **projects**: Refactor plugin  -  by @soybeanjs [(d2853)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d285353) 101 | - **projects**: Refactor generate route module code  -  by @soybeanjs [(10691)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/106919e) 102 | - **projects**: Import chokidar to watch directory and file change  -  by @soybeanjs [(96e31)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/96e31c4) 103 | - **projects**: Add different situations of directory or file change  -  by @soybeanjs [(65996)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/659961a) 104 | - **projects**: Refactor plugin: update plugin options, and update the relative methods of glob  -  by @soybeanjs [(dab6a)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/dab6a35) 105 | - **projects**: Refactor plugin: add file watcher handler hooks  -  by @soybeanjs [(35695)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/35695c9) 106 | - **projects**: Refactor plugin: compelte generate declaration and views  -  by @soybeanjs [(14737)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/147377b) 107 | - **projects**: Update plugin  -  by @soybeanjs [(9bdda)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/9bddad1) 108 | 109 | ###    📖 Documentation 110 | 111 | - **projects**: Update README  -  by @soybeanjs [(19c28)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/19c289e) 112 | - **projects**: Update README  -  by @soybeanjs [(06150)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/0615012) 113 | - **projects**: Update README  -  by @soybeanjs [(25fba)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/25fbaae) 114 | 115 | ###    📦 Build 116 | 117 | - **projects**: Add czg  -  by @soybeanjs [(1b3f0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/1b3f03d) 118 | - **projects**: Remove husky  -  by @soybeanjs [(95ff0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/95ff0c5) 119 | - **projects**: Update deps and update package.json  -  by @soybeanjs [(3c39d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/3c39de2) 120 | 121 | ###    🏡 Chore 122 | 123 | - Release v0.0.3  -  by @soybeanjs [(fb987)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/fb98775) 124 | - Release v0.0.4  -  by @soybeanjs [(f4ecd)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f4ecd03) 125 | - Release v0.0.5  -  by @soybeanjs [(d6943)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d694304) 126 | - Release v1.0.4  -  by @soybeanjs [(e7d45)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e7d4598) 127 | 128 | ###    ❤️ Contributors 129 | 130 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   131 | 132 | ## [v0.0.5](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v0.0.4...v0.0.5) (2023-01-15) 133 | 134 | ###    🐞 Bug Fixes 135 | 136 | - **projects**: Fix package.json  -  by @soybeanjs [(8b603)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/8b603b0) 137 | 138 | ###    🏡 Chore 139 | 140 | - Release v0.0.5  -  by @soybeanjs [(d6943)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d694304) 141 | 142 | ###    ❤️ Contributors 143 | 144 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   145 | 146 | ## [v0.0.4](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v0.03...v0.0.4) (2023-01-15) 147 | 148 | ###    🐞 Bug Fixes 149 | 150 | - **projects**: Fix some bugs  -  by @soybeanjs [(e6c38)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e6c389d) 151 | 152 | ###    🏡 Chore 153 | 154 | - Release v0.0.4  -  by @soybeanjs [(f4ecd)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f4ecd03) 155 | 156 | ###    ❤️ Contributors 157 | 158 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   159 | 160 | ## [v0.0.5](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v0.0.2...v0.03) (23-06-08) 161 | 162 | ###    🔥 Performance 163 | 164 | - **projects**: Update plugin default option and update README  -  by @soybeanjs [(85ea1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/85ea171) 165 | 166 | ###    🏡 Chore 167 | 168 | - Release v0.0.2  -  by @soybeanjs [(013b8)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/013b819) 169 | - Release v0.0.3  -  by @soybeanjs [(fb987)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/fb98775) 170 | 171 | ###    ❤️ Contributors 172 | 173 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   174 | 175 | ## [v0.0.2](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.3...v0.0.2) (2023-01-15) 176 | 177 | ###    🚀 Features 178 | 179 | - **projects**: Add route module code auto generate  -  by @soybeanjs [(e9edf)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e9edf14) 180 | - **projects**: Add eslint formate code after generate route module  -  by @soybeanjs [(6cb1d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/6cb1d08) 181 | - **projects**: Perf route generate  -  by @soybeanjs [(b4de1)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/b4de144) 182 | - **projects**: Add generate route module on FileWatcher hooks  -  by @soybeanjs [(f46aa)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/f46aad2) 183 | - **projects**: Complete the plugin refactor  -  by @soybeanjs [(04cd8)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/04cd838) 184 | 185 | ###    🔥 Performance 186 | 187 | - **projects**: Perf plugin  -  by @soybeanjs [(2669c)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/2669c53) 188 | 189 | ###    💅 Refactors 190 | 191 | - **projects**: Refactor plugin  -  by @soybeanjs [(ac725)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/ac725e2) 192 | - **projects**: Refactor get directory methord  -  by @soybeanjs [(a1490)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/a1490e5) 193 | - **projects**: Refactor plugin  -  by @soybeanjs [(d2853)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/d285353) 194 | - **projects**: Refactor generate route module code  -  by @soybeanjs [(10691)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/106919e) 195 | - **projects**: Import chokidar to watch directory and file change  -  by @soybeanjs [(96e31)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/96e31c4) 196 | - **projects**: Add different situations of directory or file change  -  by @soybeanjs [(65996)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/659961a) 197 | - **projects**: Refactor plugin: update plugin options, and update the relative methods of glob  -  by @soybeanjs [(dab6a)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/dab6a35) 198 | - **projects**: Refactor plugin: add file watcher handler hooks  -  by @soybeanjs [(35695)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/35695c9) 199 | - **projects**: Refactor plugin: compelte generate declaration and views  -  by @soybeanjs [(14737)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/147377b) 200 | - **projects**: Update plugin  -  by @soybeanjs [(9bdda)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/9bddad1) 201 | 202 | ###    📖 Documentation 203 | 204 | - **projects**: Update README  -  by @soybeanjs [(19c28)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/19c289e) 205 | - **projects**: Update README  -  by @soybeanjs [(06150)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/0615012) 206 | - **projects**: Update README  -  by @soybeanjs [(25fba)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/25fbaae) 207 | 208 | ###    📦 Build 209 | 210 | - **projects**: Add czg  -  by @soybeanjs [(1b3f0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/1b3f03d) 211 | - **projects**: Remove husky  -  by @soybeanjs [(95ff0)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/95ff0c5) 212 | - **projects**: Update deps and update package.json  -  by @soybeanjs [(3c39d)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/3c39de2) 213 | 214 | ###    🏡 Chore 215 | 216 | - Release v0.0.2  -  by @soybeanjs [(013b8)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/013b819) 217 | 218 | ###    ❤️ Contributors 219 | 220 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   221 | 222 | ## [v1.0.3](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.2...v1.0.3) (2022-11-07) 223 | 224 | ###    🐞 Bug Fixes 225 | 226 | - **projects**: Fix views order  -  by @soybeanjs [(b11bf)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/b11bf3a) 227 | 228 | ###    🏡 Chore 229 | 230 | - Release v1.0.3  -  by @soybeanjs [(4969f)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/4969f04) 231 | 232 | ###    ❤️ Contributors 233 | 234 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   235 | 236 | ## [v1.0.2](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.1...v1.0.2) (2022-11-07) 237 | 238 | ###    🐞 Bug Fixes 239 | 240 | - **projects**: Fix import key error  -  by @soybeanjs [(ed151)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/ed151b2) 241 | 242 | ###    🏡 Chore 243 | 244 | - Release v1.0.2  -  by @soybeanjs [(98120)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/981208a) 245 | 246 | ###    ❤️ Contributors 247 | 248 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   249 | 250 | ## [v1.0.1](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v1.0.0...v1.0.1) (2022-11-07) 251 | 252 | ###    🚀 Features 253 | 254 | - **projects**: Add lazy import or not lazy  -  by @soybeanjs [(9bc59)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/9bc59a9) 255 | 256 | ###    🏡 Chore 257 | 258 | - Release v1.0.1  -  by @soybeanjs [(971f7)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/971f728) 259 | 260 | ###    ❤️ Contributors 261 | 262 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   263 | 264 | ## [v1.0.0](https://github.com/soybeanjs/vite-plugin-vue-page-route/compare/v0.1.0...v1.0.0) (2022-11-07) 265 | 266 | ###    🚀 Features 267 | 268 | - **projects**: Add generate all views module import statement  -  by @soybeanjs [(46ec7)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/46ec7d5) 269 | 270 | ###    🐞 Bug Fixes 271 | 272 | - **projects**: Complete plugin  -  by @soybeanjs [(e35d5)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/e35d502) 273 | 274 | ###    🏡 Chore 275 | 276 | - Release v0.2.0  -  by @soybeanjs [(ab8d3)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/ab8d3d6) 277 | - Release v1.0.0  -  by @soybeanjs [(b2062)](https://github.com/soybeanjs/vite-plugin-vue-page-route/commit/b2062b2) 278 | 279 | ###    ❤️ Contributors 280 | 281 | [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   282 | --------------------------------------------------------------------------------