├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .npmrc ├── .stylelintignore ├── .yarnrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── core ├── ast.js ├── files.js ├── index.js ├── invoke.js └── utils.js ├── demos ├── .invoke │ └── router.js ├── App.vue ├── apis │ └── index.js ├── index.html ├── index.js ├── src │ ├── Complex │ │ ├── Home │ │ │ ├── Account │ │ │ │ ├── Account.vue │ │ │ │ ├── Chunk │ │ │ │ │ └── Index.vue │ │ │ │ ├── Inner │ │ │ │ │ └── Index.vue │ │ │ │ └── _Dynamic │ │ │ │ │ └── Index.vue │ │ │ ├── Details │ │ │ │ ├── Details.vue │ │ │ │ ├── Infor │ │ │ │ │ └── Index.vue │ │ │ │ ├── Intro │ │ │ │ │ └── Index.vue │ │ │ │ └── meta.yml │ │ │ └── Home.vue │ │ ├── Index.vue │ │ └── Login │ │ │ ├── Images │ │ │ └── test.png │ │ │ └── Index.vue │ ├── Dynamic │ │ ├── Index.vue │ │ └── _UserForm │ │ │ ├── Index.vue │ │ │ └── meta.yml │ ├── Index.vue │ ├── Nest │ │ ├── Home │ │ │ ├── Account │ │ │ │ ├── Index.vue │ │ │ │ ├── _Id │ │ │ │ │ └── Index.vue │ │ │ │ └── meta.yml │ │ │ ├── Home.vue │ │ │ ├── Infor │ │ │ │ └── Index.vue │ │ │ ├── Test │ │ │ │ └── Index.vue │ │ │ └── meta.yml │ │ ├── Index.vue │ │ ├── index.scss │ │ ├── meta.yml │ │ └── test │ │ │ └── index.vue │ ├── NotFound.vue │ ├── Single │ │ ├── Index.vue │ │ └── User-Name │ │ │ ├── Index.vue │ │ │ └── meta.yml │ └── Template.vue └── webpack.config.js ├── docs ├── images │ ├── index_cn.png │ ├── index_en.png │ ├── name.png │ └── notice.png └── zh_CN │ └── README.md ├── jest.config.js ├── package.json ├── prettier.config.js ├── scripts ├── publish.js └── publish.sh ├── stylelint.config.js ├── tests ├── dynamic.spec.js ├── ignore │ ├── Components │ │ └── Index.vue │ ├── Images │ │ └── Index.vue │ └── Login │ │ └── Index.vue ├── nest.spec.js ├── option.spec.js ├── single.spec.js ├── single │ ├── Login │ │ └── Index.vue │ └── User │ │ └── Index.vue └── utils │ └── index.js ├── types ├── index.d.ts └── options.d.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@vue/app"], 3 | "env": { 4 | "test": { 5 | "presets": [ 6 | [ 7 | "@vue/app", 8 | { "modules": "commonjs", "targets": { "node": "current" } } 9 | ] 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.15.1 6 | working_directory: ~/app 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: vue-router-invoke-webpack-plugin-{{ .Branch }}-{{ checksum "yarn.lock" }} 11 | - run: yarn 12 | - save_cache: 13 | paths: 14 | - node_modules 15 | key: vue-router-invoke-webpack-plugin-{{ .Branch }}-{{ checksum "yarn.lock" }} 16 | - run: npm run lint 17 | - run: npm run test:single 18 | - run: npm run test:ci 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | demos/dist 2 | tests/single 3 | coverage -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "standard", 8 | "plugin:prettier/recommended", 9 | "plugin:vue/essential", 10 | "plugin:vue/strongly-recommended", 11 | "plugin:vue/recommended" 12 | ], 13 | "rules": { 14 | "prettier/prettier": "error", 15 | "quotes": ["error", "single", { "allowTemplateLiterals": true }], 16 | "space-before-function-paren": ["error", "never"], 17 | "prefer-promise-reject-errors": "off", 18 | "no-new": "off", 19 | "no-console": "error", 20 | "no-debugger": "error", 21 | "brace-style": [0], 22 | "vue/html-self-closing": [ 23 | "error", 24 | { 25 | "html": { 26 | "void": "any" 27 | } 28 | } 29 | ], 30 | "vue/html-closing-bracket-spacing": [0], 31 | "vue/max-attributes-per-line": [0], 32 | "vue/attribute-hyphenation": ["error", "never"], 33 | "vue/order-in-components": [ 34 | "error", 35 | { 36 | "order": [ 37 | "el", 38 | "name", 39 | "parent", 40 | "functional", 41 | ["delimiters", "comments"], 42 | ["components", "directives", "filters"], 43 | "extends", 44 | "mixins", 45 | "inheritAttrs", 46 | "model", 47 | "data", 48 | ["props", "propsData"], 49 | "computed", 50 | "watch", 51 | "LIFECYCLE_HOOKS", 52 | "methods", 53 | ["template", "render"], 54 | "renderError" 55 | ] 56 | } 57 | ], 58 | "vue/no-v-html": [0] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: Qymh 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Version** 14 | `vue-router-invoke-webpack-plugin`: 15 | `webpack`: 16 | `node`: 17 | `system`: [windows or mac] 18 | 19 | ** The tree of files or Screenshots** 20 | 21 | [example of tree] 22 | 23 | ``` 24 | src 25 | ├── views 26 | │ ├── Login 27 | │ │ └── Index.vue 28 | │ └── User 29 | │ ├── Account 30 | │ │ └── Index.vue 31 | │ ├── Home 32 | │ │ └── Index.vue 33 | │ └── Index.vue 34 | ``` 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | 4 | demos/dist 5 | .DS_Store 6 | coverage 7 | 8 | tests/.invoke 9 | .vscode -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .gitignore 4 | .DS_Store 5 | .invoke 6 | .vscode 7 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | demos/dist -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org/" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.4.4](https://github.com/Qymh/vue-router-invoke-webpack-plugin/compare/v0.4.3...v0.4.4) (2020-02-28) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * 🐛 wrong generation when multistage nested routes exist ([#27](https://github.com/Qymh/vue-router-invoke-webpack-plugin/issues/27)) ([6977b0f](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/6977b0fb31f013b0225a1d750676f1d408a09401)) 7 | 8 | 9 | 10 | ## [0.4.3](https://github.com/Qymh/vue-router-invoke-webpack-plugin/compare/v0.4.2...v0.4.3) (2020-01-10) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * 🐛 wrong test in circleci ([4dc5542](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/4dc5542ff45377e159c14468a3cf850296df26ac)) 16 | 17 | 18 | ### Features 19 | 20 | * 🎸 add codecov ([675c5e5](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/675c5e5c06030fe09b1658fe60fa8454d397db41)) 21 | * 🎸 add regexp to set ignore options ([#25](https://github.com/Qymh/vue-router-invoke-webpack-plugin/issues/25)) ([189a5a4](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/189a5a46d4e3f7bf5143426ffb388c5e37760056)) 22 | 23 | 24 | 25 | ## [0.4.2](https://github.com/Qymh/vue-router-invoke-webpack-plugin/compare/v0.4.1...v0.4.2) (2020-01-09) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * 🐛 wrong dependency for js-beautify ([#24](https://github.com/Qymh/vue-router-invoke-webpack-plugin/issues/24)) ([d9ed248](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/d9ed248d26f001c3cbc592d88f1affd12bf71d72)) 31 | 32 | 33 | 34 | ## [0.4.1](https://github.com/Qymh/vue-router-invoke-webpack-plugin/compare/v0.4.0...v0.4.1) (2019-12-16) 35 | 36 | 37 | ### Features 38 | 39 | * 🎸 improve frame work ([228d5bf](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/228d5bfab55c8b26c4cde91918f18574816b3729)) 40 | * 🎸 meta supports boolean and plain object ([#23](https://github.com/Qymh/vue-router-invoke-webpack-plugin/issues/23)) ([82a86d7](https://github.com/Qymh/vue-router-invoke-webpack-plugin/commit/82a86d7481eaf65eabac0074fcefbf4c90921bce)) 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Qymh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-router-invoke-webpack-plugin 2 | 3 | ![](https://img.shields.io/codecov/c/github/qymh/vue-router-invoke-webpack-plugin) 4 | ![](https://img.shields.io/npm/dm/vue-router-invoke-webpack-plugin) 5 | ![](https://img.shields.io/npm/v/vue-router-invoke-webpack-plugin) 6 | ![](https://img.shields.io/npm/l/vue-router-invoke-webpack-plugin) 7 | 8 | > a new version has been rewritten by `typescript` [vue-router-invoke-next-webpack-plugin](https://github.com/Qymh/vue-router-invoke-next-webpack-plugin) both supported vue2.x and vue3.x 9 | 10 | [CHANGELOG](https://github.com/Qymh/vue-router-invoke-webpack-plugin/blob/dev/CHANGELOG.md) 11 | 12 | [中文版本](https://github.com/Qymh/vue-router-invoke-webpack-plugin/blob/dev/docs/zh_CN/README.md) 13 | 14 | Automatic generate the routes of `vue-router` based on the file directory. 15 | 16 | ## Install 17 | 18 | ### npm 19 | 20 | ```javascript 21 | npm install vue-router-invoke-webpack-plugin -D 22 | ``` 23 | 24 | ### cnpm 25 | 26 | ```javascript 27 | cnpm install vue-router-invoke-webpack-plugin -D 28 | ``` 29 | 30 | ### yarn 31 | 32 | ```javascript 33 | yarn add vue-router-invoke-webpack-plugin -D 34 | ``` 35 | 36 | ## What is Automatic Generate Routes 37 | 38 | Routing automatic injection refers to according to the format of the file directory to automatically generate the corresponding `router.js`, every time without the need to create a module to reference manual 39 | 40 | ## Usage 41 | 42 | ### Webpack 43 | 44 | - We need know whether the environment is `development` or `production`.So you should set `process.env.NODE_ENV` which is equal to `development` in the development environment and is equal to `production` in the production environment.There are many plugins can do that. We recommend [cross-env](https://github.com/kentcdodds/cross-env) 45 | - If there are many people working together,we can't import route by the absolute address,so you should set a [alias](https://webpack.js.org/configuration/resolve/#resolvealias) for the watching `dir`. 46 | - the generated route will be lazyload. So make sure you have add [@babel/plugin-syntax-dynamic-import](https://babeljs.io/docs/en/next/babel-plugin-syntax-dynamic-import.html) 47 | 48 | ```javascript 49 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 50 | const path = require('path') 51 | 52 | // omit some other option... 53 | 54 | resolve: { 55 | alias: { 56 | '@': path.resolve(process.cwd(), 'demos') 57 | } 58 | } 59 | 60 | plugins: [ 61 | new VueRouterInvokeWebpackPlugin( 62 | dir: 'demos/src', 63 | alias: '@/src' 64 | ) 65 | ]; 66 | ``` 67 | 68 | ### VueCli3 69 | 70 | vueCli3 will be easier than webpack 71 | 72 | `vue.config.js` 73 | 74 | ```javascript 75 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 76 | 77 | module.exports = { 78 | // omit other options... 79 | configureWebpack(config) { 80 | config.plugins.push( 81 | new VueRouterInvokeWebpackPlugin({ 82 | dir: 'src/views', 83 | // must set the alias for the dir option which you have set 84 | alias: '@/views' 85 | }) 86 | ); 87 | } 88 | }; 89 | 90 | // or another way.. 91 | 92 | module.exports = { 93 | // omit other options... 94 | configureWebpack: { 95 | plugins: [ 96 | new VueRouterInvokeWebpackPlugin({ 97 | dir: 'src/views', 98 | // must set the alias for the dir option which you have set 99 | alias: '@/views' 100 | }) 101 | ] 102 | } 103 | }; 104 | ``` 105 | 106 | ### Start 107 | 108 | After configure the options you can use `npm run serve` or some other scripts that you defined to activate the plugin in the development environment. When first generated or the file which in the `dir` option's direction changes.`router.js` will be automatic generated. 109 | 110 | And you can use `npm run build` or some other scripts that you defined to activate the plugin in the production environment. `router.js` will be automatic generated. 111 | 112 | ## Options 113 | 114 | | Prop | Type | Required | Default | Description | 115 | | -------------- | :------: | :------: | :----------: | ---------------------------------------: | 116 | | dir | String | true | '' | vue file directory | 117 | | alias | String | true | '' | the option `dir`'s alias | 118 | | notFound | String | false | '' | the alias address of notFound chunk | 119 | | mode | String | false | history | hash or history | 120 | | meta | String | false | meta | the yml file's name | 121 | | routerDir | String | false | ROOT | generated router.js file | 122 | | language | String | false | javascript | javascript or typescript | 123 | | ignore | Array | false | ['.dsstore'] | files or directions will not be resolved | 124 | | redirect | Array | false | [] | redirect route | 125 | | modules | Array | false | [] | the import modules | 126 | | scrollBehavior | Function | false | '' | same as scrollBehavior | 127 | | beforeEach | Function | false | '' | router.beforeEach | 128 | | beforeResolve | Function | false | '' | router.beforeResolve | 129 | | afterEach | Function | false | '' | router.afterEach | 130 | 131 | ## How To Automatical Invoke 132 | 133 | The following example depends on VueCli3. I believe that if you know how to use in VueCli3,the using of webpack is easy for you. 134 | 135 | `vue.config.js` 136 | 137 | ```javascript 138 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 139 | 140 | module.exports = { 141 | // omit other options... 142 | configureWebpack(config) { 143 | config.plugins.push( 144 | new VueRouterInvokeWebpackPlugin({ 145 | dir: 'src/views', 146 | alias: '@/views' 147 | }) 148 | ); 149 | } 150 | }; 151 | ``` 152 | 153 | And import `router.js` in your entry file `src/main.js` 154 | 155 | The default location of `router.js` is under the invoke folder in the root directory,You can change the location anywhere by setting the `routerDir` option 156 | 157 | The address of `routerDir` is relative to `ROOT`, Pay attention to that it is not a absolute address 158 | 159 | And I recommoned that `router.js` may put into `.gitignore` or `.eslintignore`. Everyone's branch can be independent because `router.js` will be automatic generated 160 | 161 | ```javascript 162 | import Vue from 'vue'; 163 | import App from './App.vue'; 164 | import router from '../.invoke/router'; 165 | 166 | export default new Vue({ 167 | el: '#app', 168 | router, 169 | render: h => h(App) 170 | }); 171 | ``` 172 | 173 | ### SingleRoute 174 | 175 | Please pay attention to that there is a direcotry which wrapping the `Index.vue`,Do not name `vue` directly.It maybe not quite in the usual way 176 | 177 | The same, do not name the directory with `Index`, it may have diffrent sense on `Nested Route` 178 | 179 | > version 0.2.7, The plugin will throw an error when the wrong naming of the directory in production environment and will show you a danger notice in development environment 180 | 181 | So if you see that 182 | 183 | ![image](https://github.com/Qymh/vue-router-invoke-webpack-plugin/blob/master/docs/images/notice.png) 184 | 185 | The rule of naming about your directory maybe wrong 186 | 187 | If your directory just like this 188 | 189 | ``` 190 | src 191 | ├── views 192 | │ ├── Login 193 | │ │ └── Index.vue 194 | │ └── User 195 | │ ├── Account 196 | │ │ └── Index.vue 197 | │ ├── Home 198 | │ │ └── Index.vue 199 | │ └── Index.vue 200 | ``` 201 | 202 | automatical generated route will be this 203 | 204 | ```javascript 205 | { 206 | component: () => 207 | import('@/views/Login/Index.vue'), 208 | name: 'login', 209 | path: '/login' 210 | }, 211 | { 212 | component: () => 213 | import('@/views/User/Index.vue'), 214 | name: 'user', 215 | path: '/user' 216 | }, 217 | { 218 | component: () => 219 | import('@/views/User/Account/Index.vue'), 220 | name: 'user-account', 221 | path: '/user/account' 222 | }, 223 | { 224 | component: () => 225 | import('@/views/User/Home/Index.vue'), 226 | name: 'user-home', 227 | path: '/user/home' 228 | } 229 | ``` 230 | 231 | ### HomePage 232 | 233 | We make a special treatment for HomePage which route is `/` 234 | 235 | HomePage we named `Index.vue` and is a unique route 236 | 237 | If your directory just like this 238 | 239 | ``` 240 | src 241 | ├── views 242 | │ ├── Login 243 | │ │ └── Index.vue 244 | │ └── Index.vue 245 | ``` 246 | 247 | automatical generated route will be this 248 | 249 | ```javascript 250 | { 251 | component: () => 252 | import('@/views/Index.vue'), 253 | name: 'index', 254 | path: '/' 255 | }, 256 | { 257 | component: () => 258 | import('@/views/Login/Index.vue'), 259 | name: 'login', 260 | path: '/login' 261 | } 262 | ``` 263 | 264 | ### Dynamic Route 265 | 266 | If your directory just like this 267 | 268 | ``` 269 | src 270 | ├── views 271 | │ ├── Login 272 | │ │ └── Index.vue 273 | │ └── User 274 | │ ├── _Home 275 | │ │ └── Index.vue 276 | │ └── Index.vue 277 | ``` 278 | 279 | automatical generated route will be this 280 | 281 | ```javascript 282 | { 283 | component: () => 284 | import('@/views/Login/Index.vue'), 285 | name: 'login', 286 | path: '/login' 287 | }, 288 | { 289 | component: () => 290 | import('@/views/User/Index.vue'), 291 | name: 'user', 292 | path: '/user' 293 | }, 294 | { 295 | component: () => 296 | import('@/views/User/_Home/Index.vue'), 297 | name: 'user-home', 298 | path: '/user/:home' 299 | } 300 | ``` 301 | 302 | ### Nested Route 303 | 304 | If your directory just like this 305 | 306 | ``` 307 | src 308 | ├── views 309 | │ ├── Login 310 | │ │ └── Index.vue 311 | │ └── User 312 | │ ├── Chart 313 | │ │ └── Index.vue 314 | │ ├── Home 315 | │ │ └── Index.vue 316 | │ └── User.vue 317 | ``` 318 | 319 | automatical generated route will be this 320 | 321 | ```javascript 322 | { 323 | component: () => 324 | import('@/views/Login/Index.vue'), 325 | name: 'login', 326 | path: '/login' 327 | }, 328 | { 329 | component: () => 330 | import('@/views/User/User.vue'), 331 | name: 'user', 332 | path: '/user', 333 | children: [ 334 | { 335 | component: () => 336 | import('@/views/User/Chart/Index.vue'), 337 | name: 'user-chart', 338 | path: 'chart' 339 | }, 340 | { 341 | component: () => 342 | import('@/views/User/Home/Index.vue'), 343 | name: 'user-home', 344 | path: 'home' 345 | } 346 | ] 347 | } 348 | ``` 349 | 350 | ### Dymaic and Nested Route 351 | 352 | If your directory just like this 353 | 354 | ``` 355 | src 356 | ├── views 357 | │ ├── Login 358 | │ │ └── Index.vue 359 | │ └── User 360 | │ ├── _Category 361 | │ │ ├── _Category.vue 362 | │ │ └── Infor 363 | │ │ └── Index.vue 364 | │ └── Index.vue 365 | ``` 366 | 367 | automatical generated route will be this 368 | 369 | ```javascript 370 | { 371 | component: () => 372 | import('@/views/Login/Index.vue'), 373 | name: 'login', 374 | path: '/login' 375 | }, 376 | { 377 | component: () => 378 | import('@/views/User/Index.vue'), 379 | name: 'user', 380 | path: '/user' 381 | }, 382 | { 383 | component: () => 384 | import('@/views/User/_Category/_Category.vue'), 385 | name: 'user-category', 386 | path: '/user/:category', 387 | children: [ 388 | { 389 | component: () => 390 | import('@/views/User/_Category/Infor/Index.vue'), 391 | name: 'user-category-infor', 392 | path: 'infor' 393 | } 394 | ] 395 | } 396 | ``` 397 | 398 | ## Correct the name 399 | 400 | We will transform diffetent rule of naming into `upperCamelCase` naming 401 | 402 | For Example 403 | 404 | ``` 405 | src 406 | ├── views 407 | │ ├── LoginPage 408 | │ │ └── index.vue 409 | │ └── User-home 410 | │ ├── account 411 | │ │ └── Index.vue 412 | │ ├── Home-details 413 | │ │ └── Index.vue 414 | │ └── Index.vue 415 | ``` 416 | 417 | automatical generated route will be this 418 | 419 | ```javascript 420 | { 421 | component: () => import('@/views/LoginPage/index.vue'), 422 | name: 'loginPage', 423 | path: '/loginPage' 424 | }, 425 | { 426 | component: () => import('@/views/User-home/Index.vue'), 427 | name: 'userHome', 428 | path: '/userHome' 429 | }, 430 | { 431 | component: () => import('@/views/User-home/Home-details/Index.vue'), 432 | name: 'userHome-homeDetails', 433 | path: '/userHome/homeDetails' 434 | }, 435 | { 436 | component: () => import('@/views/User-home/account/Index.vue'), 437 | name: 'userHome-account', 438 | path: '/userHome/account' 439 | }, 440 | ``` 441 | 442 | ## Meta Succedaneum 443 | 444 | The `meta` option in `vue-router` can resolve many questions.Just like define the title of a page or define a page is necessary to login or not. 445 | 446 | Some of the questions just like define the page title can be resolved by [vue-meta](https://github.com/nuxt/vue-meta).That is a fantastic repository. 447 | 448 | But if you really need define the plain `meta` option of `vue-router` .You should make a `yml` file. 449 | 450 | For example 451 | 452 | ```javascript 453 | src/views 454 | ├── Single 455 | │ ├── Index.vue 456 | │ └── User 457 | │ ├── Index.vue 458 | │ └── meta.yml 459 | ``` 460 | 461 | `meta.yml` 462 | 463 | ```yml 464 | meta: 465 | - name: user 466 | ``` 467 | 468 | automatical generated route will be this 469 | 470 | ```javascript 471 | { 472 | component: () => import('@/views/Single/Index.vue'), 473 | name: 'single', 474 | path: 'single' 475 | }, 476 | { 477 | component: () => import('@/views/Single/User/Index.vue'), 478 | name: 'single-user', 479 | meta: { name: user }, 480 | path: 'single/user' 481 | } 482 | ``` 483 | 484 | > Version greater than 0.4.1, meta's type supports `boolean` `string` `array` `plain object`, but it doesn't support `Symbol` `function` `undefined` `circled object` that can't be translated by `JSON.stringify` 485 | 486 | ## Special Options 487 | 488 | ### NotFound 489 | 490 | If your set options like this 491 | 492 | ```javascript 493 | plugins: [ 494 | new VueRouterInvokeWebpackPlugin({ 495 | dir: 'src/views', 496 | alias: '@/views', 497 | // muse set ignore for notFound chunk 498 | ignore: ['NotFound.vue'], 499 | notFound: '@/views/NotFound.vue' 500 | }) 501 | ]; 502 | ``` 503 | 504 | the directory 505 | 506 | ``` 507 | src 508 | ├── views 509 | │ ├── Login 510 | │ │ └── Index.vue 511 | │ └── Index.vue 512 | │ └── NotFound.vue 513 | 514 | ``` 515 | 516 | automatical generated route will be this 517 | 518 | ```javascript 519 | { 520 | component: () => 521 | import('@/views/Index.vue'), 522 | name: 'index', 523 | path: '/' 524 | }, 525 | { 526 | component: () => 527 | import('@/views/NotFound.vue'), 528 | name: 'notFound', 529 | path: '*' 530 | }, 531 | { 532 | component: () => 533 | import('@/views/Login/Index.vue'), 534 | name: 'login', 535 | path: '/login' 536 | } 537 | ``` 538 | 539 | ### Ignore 540 | 541 | If your set options like this 542 | 543 | `images` `components` `template.vue` will not be resolved by the plugin 544 | 545 | Above Version `0.4.3` you can use `RegExp` to ignore files 546 | 547 | And the value ignore case 548 | 549 | ```javascript 550 | plugins: [ 551 | new VueRouterInvokeWebpackPlugin({ 552 | dir: 'src/views', 553 | alias: '@/views', 554 | language: 'javascript', 555 | ignore: ['images', 'components', 'template.vue', /\.scss$/] 556 | }) 557 | ]; 558 | ``` 559 | 560 | the directory 561 | 562 | ``` 563 | src 564 | ├── views 565 | │ ├── Login 566 | │ │ └── Index.vue 567 | │ │ └── Index.scss 568 | │ ├── Template.vue 569 | │ └── User 570 | │ ├── Components 571 | │ ├── Images 572 | │ └── Index.vue 573 | ``` 574 | 575 | automatical generated route will be this 576 | 577 | ```javascript 578 | { 579 | component: () => 580 | import('@/views/Login/Index.vue'), 581 | name: 'login', 582 | path: '/login' 583 | }, 584 | { 585 | component: () => 586 | import('@/views/User/Index.vue'), 587 | name: 'user', 588 | path: '/user' 589 | } 590 | ``` 591 | 592 | Obviously The plugin ignores the files 593 | 594 | ### Redirect 595 | 596 | If your set options like this 597 | 598 | ```javascript 599 | plugins: [ 600 | new VueRouterInvokeWebpackPlugin({ 601 | dir: 'src/views', 602 | alias: '@/views', 603 | language: 'javascript', 604 | redirect: [ 605 | { 606 | redirect: '/', 607 | path: '/home' 608 | }, 609 | { 610 | redirect: '/test', 611 | path: '/demo' 612 | } 613 | ] 614 | }) 615 | ]; 616 | ``` 617 | 618 | automatical generated route will be this 619 | 620 | ```javascript 621 | { 622 | path: '/home', 623 | redirect: '/' 624 | }, 625 | { 626 | path: '/demo', 627 | redirect: '/test' 628 | } 629 | ``` 630 | 631 | #### Redirect In yml 632 | 633 | > Feature In `0.4.0` 634 | 635 | you can add redirect path by using `yml` 636 | 637 | For example 638 | 639 | ```javascript 640 | src/views 641 | ├── Single 642 | │ ├── Index.vue 643 | │ └── User 644 | │ ├── Index.vue 645 | │ └── meta.yml 646 | ``` 647 | 648 | `meta.yml` 649 | 650 | ```yml 651 | redirect: 652 | - path: /test 653 | ``` 654 | 655 | automatical generated route will be this 656 | 657 | ```javascript 658 | { 659 | component: () => import('@/views/Single/Index.vue'), 660 | name: 'single', 661 | path: 'single' 662 | }, 663 | { 664 | component: () => import('@/views/Single/User/Index.vue'), 665 | name: 'single-user', 666 | path: 'single/user', 667 | redirect: { 668 | path: '/test' 669 | }, 670 | } 671 | ``` 672 | 673 | ### Modules 674 | 675 | The generated `router.js` has Two modules 676 | 677 | ```javascript 678 | import Vue from 'vue'; 679 | import Router from 'vue-router'; 680 | ``` 681 | 682 | If you need some other module which would use in `beforeEach` or some other place you can define it by using `modules`. For example 683 | 684 | ```javascript 685 | new VueRouterInvokeWebpackPlugin({ 686 | dir: 'src/views', 687 | alias: '@/views', 688 | modules: [ 689 | { 690 | name: 'diyName', 691 | package: 'some-packages' 692 | } 693 | ] 694 | }); 695 | ``` 696 | 697 | automatical generated route will be this 698 | 699 | ```javascript 700 | // omit other options 701 | import diyName from 'some-packages'; 702 | ``` 703 | 704 | ### VueRouter Guards 705 | 706 | we have supported VueRouter's Guards `beforeEach` `beforeResolve` `afterEach` 707 | 708 | If your set options like this 709 | 710 | ```javascript 711 | new VueRouterInvokeWebpackPlugin({ 712 | dir: 'src/views', 713 | alias: '@/views', 714 | language: 'javascript', 715 | beforeEach: (to, from, next) => { 716 | next(); 717 | }, 718 | beforeResolve: (to, from, next) => { 719 | next(); 720 | }, 721 | afterEach: (to, from) => {} 722 | }); 723 | ``` 724 | 725 | automatical generated route will be this 726 | 727 | ```javascript 728 | // omit others ... 729 | const router = new Router({ mode: 'history', routes }); 730 | router.beforeEach((to, from, next) => { 731 | next(); 732 | }); 733 | 734 | router.beforeResolve((to, from, next) => { 735 | next(); 736 | }); 737 | 738 | router.afterEach((to, from) => {}); 739 | export default router; 740 | ``` 741 | 742 | ### ScrollBehavior 743 | 744 | If your set options like this 745 | 746 | ```javascript 747 | new VueRouterInvokeWebpackPlugin({ 748 | dir: 'src/views', 749 | alias: '@/views', 750 | language: 'javascript', 751 | scrollBehavior: (to, from, savedPosition) => { 752 | if (savedPosition) { 753 | return savedPosition; 754 | } else { 755 | return { x: 0, y: 0 }; 756 | } 757 | } 758 | }); 759 | ``` 760 | 761 | automatical generated route will be this 762 | 763 | ```javascript 764 | // omit others... 765 | const router = new Router({ 766 | mode: 'history', 767 | routes, 768 | scrollBehavior: (to, from, savedPosition) => { 769 | if (savedPosition) { 770 | return savedPosition; 771 | } else { 772 | return { x: 0, y: 0 }; 773 | } 774 | } 775 | }); 776 | ``` 777 | 778 | ## Demos 779 | 780 | The detailed usage you can `git clone` our project and run `npm run build:demos` or you can just watch our [demos](https://github.com/Qymh/vue-router-invoke-webpack-plugin/tree/master/demos) directly.The demos dont't have substantial content,the more we focus is on the generation of directory,you can get how `router.js` generated in the demos. 781 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /core/ast.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const yamljs = require('js-yaml'); 5 | const { 6 | warn, 7 | tips, 8 | camelize, 9 | replaceVue, 10 | firstLowerCase, 11 | replaceAlias, 12 | replaceArtificialDynamic, 13 | makeMap, 14 | diff 15 | } = require('./utils'); 16 | const { 17 | isFile, 18 | root, 19 | getRouterDir, 20 | getWatchDir, 21 | generateIgnoreFiles, 22 | generateModules 23 | } = require('./files'); 24 | const routeStringPreJs = modules => 25 | `import Vue from 'vue';import Router from 'vue-router';${modules};Vue.use(Router);export const routes = [`; 26 | const routeStringPreTs = modules => 27 | `import Vue from 'vue';import Router, { RouteConfig } from 'vue-router';${modules};Vue.use(Router);export const routes: RouteConfig[] = [`; 28 | const routeStringPostFn = (mode, behavior) => 29 | `];const router = new Router({mode: '${mode}',routes,${behavior && 30 | 'scrollBehavior:' + behavior}});`; 31 | const routeStringExport = 'export default router;'; 32 | 33 | const modeMap = makeMap('hash,history'); 34 | const languageMap = makeMap('javascript,typescript'); 35 | 36 | function sortByIsFile(arr) { 37 | return arr.sort((a, b) => Number(b.isFile) - Number(a.isFile)); 38 | } 39 | 40 | function generateYmlReg(meta) { 41 | this.metaYmlReg = new RegExp(`^${meta}\\.yml$`, 'i'); 42 | } 43 | 44 | let nestCollections = {}; 45 | 46 | /** 47 | * @param {Object} options 48 | */ 49 | function init(options) { 50 | const mode = options.mode || 'history'; 51 | const language = options.language || 'javascript'; 52 | const meta = options.meta || 'meta'; 53 | if (!modeMap(mode)) { 54 | warn( 55 | `the mode can only be hash or history, make sure you have set the value correctly` 56 | ); 57 | } 58 | if (!languageMap(language)) { 59 | warn( 60 | `the language can only be javascript or typescript, make sure you have set the value correctly` 61 | ); 62 | } 63 | if (!options.dir) { 64 | warn(`the dir option is required please set the main files of vue`); 65 | } 66 | if (!options.alias) { 67 | warn( 68 | `the alias option is required, make sure you have set the alias of the dir option: ${options.dir} ` 69 | ); 70 | } 71 | let behavior = ''; 72 | if (options.scrollBehavior) { 73 | behavior = options.scrollBehavior.toString(); 74 | } 75 | const modules = generateModules(options); 76 | this.isFirst = this.isFirst !== false; 77 | this.metaYmlReg = ''; 78 | this.routerDir = ''; 79 | this.watchDir = ''; 80 | this.routeString = ''; 81 | this.ignoreRegExp = ''; 82 | this.nestArr = []; 83 | this.routeStringPre = 84 | language === 'javascript' 85 | ? routeStringPreJs(modules) 86 | : routeStringPreTs(modules); 87 | this.routeStringPost = routeStringPostFn(mode, behavior); 88 | this.routeStringExport = routeStringExport; 89 | this.alias = options.alias; 90 | this.dir = options.dir; 91 | generateYmlReg.call(this, meta); 92 | getRouterDir.call(this, options); 93 | generateIgnoreFiles.call(this, options); 94 | getWatchDir.call(this, options); 95 | this.routeString += this.routeStringPre; 96 | this.filesAst = []; 97 | } 98 | 99 | exports.init = init; 100 | 101 | /** 102 | * 103 | * @param {String} dir 104 | * @param {Array} filesAst 105 | * @param {Object} parent 106 | */ 107 | function generateFilesAst(dir, filesAst, parent) { 108 | const files = fs.readdirSync(dir); 109 | if (!files.length && !parent) { 110 | warn( 111 | `the directory ${dir} is empty, make sure you have set the directory correctly` 112 | ); 113 | } 114 | for (const file of files) { 115 | const curAst = {}; 116 | const fileLowerCase = firstLowerCase(file); 117 | const curDir = `${root}/${dir}/${file}`; 118 | if (this.metaYmlReg.test(file)) { 119 | const ymlStr = fs.readFileSync(curDir, 'utf8'); 120 | let ymlObj; 121 | try { 122 | ymlObj = yamljs.load(ymlStr); 123 | } catch (error) { 124 | tips(error.message); 125 | ymlObj = undefined; 126 | } 127 | parent.children.map(v => { 128 | if (!this.metaYmlReg.test(v.file) && v.isFile) { 129 | v.meta = ymlObj && ymlObj.meta; 130 | v.redirect = ymlObj && ymlObj.redirect; 131 | } 132 | }); 133 | } 134 | curAst.dir = curDir; 135 | curAst.alias = `${this.alias}${replaceAlias(dir, this.dir)}/${file}`; 136 | curAst.isVue = /\.vue$/.test(fileLowerCase); 137 | curAst.file = camelize(replaceVue(fileLowerCase)); 138 | curAst.isFile = isFile(curDir); 139 | if (parent) { 140 | curAst.isNest = curAst.file.trim() === camelize(parent.file).trim(); 141 | curAst.parentName = parent.parentName.concat(parent.file); 142 | } else { 143 | curAst.parentName = []; 144 | } 145 | filesAst.push(curAst); 146 | 147 | if (this.ignoreRegExp.test(curAst.alias)) { 148 | curAst.ignore = true; 149 | } 150 | 151 | let multipleError; 152 | 153 | // fix empty vue 154 | if ( 155 | (curAst.isFile && 156 | !( 157 | curAst.file === parent.file || 158 | (curAst.file && curAst.file.toLowerCase() === 'index') || 159 | (this.metaYmlReg && this.metaYmlReg.test(curAst.file)) 160 | )) || 161 | (multipleError = 162 | parent.children && parent.children.filter(v => v.isVue).length === 2) 163 | ) { 164 | if (!this.ignoreRegExp.test(curAst.alias)) { 165 | curAst.ignore = true; 166 | tips( 167 | `\n'${curAst.alias}' ${ 168 | multipleError 169 | ? 'is mixed with nested and single route' 170 | : 'is not in accordance with the rules \n you can not name it directly without a file wraps it ' 171 | }\n you may check the correct use in documentation https://github.com/Qymh/vue-router-invoke-webpack-plugin#singleroute\n or you should make sure you have set it in the ignore option` 172 | ); 173 | if (this.isFirst) { 174 | warn( 175 | `\n'${curAst.alias}' ${ 176 | multipleError 177 | ? 'is mixed by nested and single route' 178 | : 'is not in accordance with the rules \n you can not name it directly without a file wraps it ' 179 | }\n you may check the correct use in documentation https://github.com/Qymh/vue-router-invoke-webpack-plugin#singleroute\n or you should make sure you have set it in the ignore option` 180 | ); 181 | } 182 | } 183 | } 184 | 185 | if (!curAst.isFile) { 186 | curAst.children = []; 187 | generateFilesAst.call(this, `${dir}/${file}`, curAst.children, curAst); 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * isFile:true will in front of isFile:false 194 | * @param {Array} filesAst 195 | */ 196 | function sortFilesAst(filesAst) { 197 | sortByIsFile(filesAst); 198 | for (const item of filesAst) { 199 | if (item.children) { 200 | sortFilesAst.call(this, item.children); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * keep original value 207 | * @param {string} key 208 | * @param {any} value 209 | */ 210 | function handleKeyValueType(key, value) { 211 | const type = typeof value; 212 | switch (type) { 213 | case 'object': 214 | return `${key}: ${JSON.stringify(value)},`; 215 | case 'string': 216 | return `${key}: '${value}',`; 217 | default: 218 | return `${key}: ${value},`; 219 | } 220 | } 221 | 222 | /** 223 | * 224 | * @param {Array} filesAst 225 | * @param {Object} pre 226 | */ 227 | function generateRouteString(filesAst, pre) { 228 | if (!pre) { 229 | nestCollections = {}; 230 | } 231 | for (const item of filesAst) { 232 | // fix when non-compliance file 233 | if (item.ignore) { 234 | } else { 235 | if (!item.isFile) { 236 | generateRouteString.call(this, item.children, item); 237 | } else { 238 | if (this.metaYmlReg.test(item.file)) { 239 | if (nestCollections[item.parentName.join('-')]) { 240 | nestCollections[item.parentName.join('-')]--; 241 | } 242 | } else { 243 | this.routeString += ` 244 | { 245 | component: () => import('${item.alias}'), 246 | name:'${ 247 | item.parentName.length 248 | ? item.parentName 249 | .map(v => replaceArtificialDynamic(v)) 250 | .join('-') 251 | : 'index' 252 | }', 253 | `; 254 | if (item.meta) { 255 | this.routeString += `meta:{`; 256 | for (const meta of item.meta) { 257 | for (const key in meta) { 258 | this.routeString += handleKeyValueType(key, meta[key]); 259 | } 260 | } 261 | this.routeString += `},`; 262 | } 263 | if (item.redirect) { 264 | this.routeString += `redirect:{`; 265 | for (const redirect of item.redirect) { 266 | for (const key in redirect) { 267 | this.routeString += `${key}:'${redirect[key]}',`; 268 | } 269 | } 270 | this.routeString += `},`; 271 | } 272 | if (Object.keys(nestCollections).length) { 273 | const curNest = this.nestArr[this.nestArr.length - 1].split('-'); 274 | const res = diff(curNest, item.parentName); 275 | this.routeString += `path:'${res.join('/')}',`; 276 | } else { 277 | this.routeString += `path:'/${item.parentName.join('/')}',`; 278 | } 279 | if (item.isNest) { 280 | this.nestArr.push(item.parentName.join('-')); 281 | // fix when directory is empty and non-compliance file 282 | pre.children = pre.children.filter(v => { 283 | return (v.children && v.children.length) !== 0 && !v.ignore; 284 | }); 285 | nestCollections[item.parentName.join('-')] = 286 | pre.children.length - 1; 287 | this.routeString += `children:[`; 288 | if (pre.children.length - 1 === 0) { 289 | this.routeString += '],},'; 290 | nestCollections[item.parentName.join('-')]--; 291 | } 292 | } else { 293 | this.routeString += '},'; 294 | } 295 | const isNestChild = this.nestArr.some(v => 296 | pre.parentName.join('-').includes(v) 297 | ); 298 | if (isNestChild) { 299 | this.nestArr.forEach(v => { 300 | if (pre.parentName.join('-').includes(v)) { 301 | nestCollections[v]--; 302 | } 303 | }); 304 | // fix when meta.yml is empty 305 | if (item.meta !== undefined) { 306 | nestCollections[pre.parentName.join('-')] -= 1; 307 | } 308 | // fix when nested route which has more than two childish routes 309 | if (pre.children.length >= 2) { 310 | nestCollections[pre.parentName.join('-')] += 311 | pre.children.length - 1; 312 | } 313 | for (const key in nestCollections) { 314 | const val = nestCollections[key]; 315 | if (val === 0) { 316 | delete nestCollections[key]; 317 | this.routeString += '],},'; 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | } 325 | } 326 | 327 | exports.generateFilesAst = generateFilesAst; 328 | exports.sortFilesAst = sortFilesAst; 329 | exports.generateRouteString = generateRouteString; 330 | -------------------------------------------------------------------------------- /core/files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const fse = require('fs-extra'); 5 | const chokidar = require('chokidar'); 6 | const beautify = require('js-beautify').js; 7 | const { toPlain } = require('./utils'); 8 | const isFile = dir => fs.statSync(dir).isFile(); 9 | 10 | let isRunning = false; 11 | 12 | exports.isFile = isFile; 13 | 14 | const root = process.cwd(); 15 | exports.root = root; 16 | 17 | function writeFile(options) { 18 | if (!fs.existsSync(this.routerDir)) { 19 | if (options.routerDir) { 20 | fse.ensureDirSync(`${root}/${options.routerDir}/.invoke`); 21 | } else { 22 | fse.ensureDirSync(`${root}/.invoke`); 23 | } 24 | fs.writeFileSync( 25 | this.routerDir, 26 | beautify(this.routeString, { indent_size: 2, space_in_empty_paren: true }) 27 | ); 28 | isRunning = false; 29 | this.isFirst = false; 30 | } else { 31 | fs.writeFileSync( 32 | this.routerDir, 33 | beautify(this.routeString, { indent_size: 2, space_in_empty_paren: true }) 34 | ); 35 | isRunning = false; 36 | this.isFirst = false; 37 | } 38 | } 39 | 40 | function watchFile(options, start) { 41 | writeFile.call(this, options); 42 | const watcher = chokidar.watch(this.watchDir, { persistent: true }); 43 | watcher.on('raw', (event, path) => { 44 | if ((event === 'modified' && !/\.yml$/.test(path)) || isRunning) { 45 | return; 46 | } 47 | isRunning = true; 48 | start.call(this, options); 49 | writeFile.call(this, options); 50 | }); 51 | } 52 | 53 | exports.writeOrWatchFile = function(options, start) { 54 | const isDev = process.env.NODE_ENV === 'development'; 55 | isDev ? watchFile.call(this, options, start) : writeFile.call(this, options); 56 | }; 57 | 58 | exports.getRouterDir = function(options) { 59 | const routerDir = options.routerDir; 60 | const ext = options.language 61 | ? options.language === 'javascript' 62 | ? '.js' 63 | : '.ts' 64 | : '.js'; 65 | if (routerDir) { 66 | this.routerDir = `${root}/${routerDir}/.invoke/router${ext}`; 67 | } else { 68 | this.routerDir = `${root}/.invoke/router${ext}`; 69 | } 70 | }; 71 | 72 | exports.getWatchDir = function(options) { 73 | this.watchDir = `${root}/${options.dir}`; 74 | }; 75 | 76 | exports.generateIgnoreFiles = function(options) { 77 | options.ignore = options.ignore 78 | ? [...options.ignore, '.dsstore'] 79 | : ['.dsstore']; 80 | options.ignore = options.ignore.map(v => { 81 | if (toPlain(v) === 'RegExp') { 82 | return v.toString().replace(/(\/)\B/g, ''); 83 | } else if (v) { 84 | return v; 85 | } 86 | }); 87 | const reg = new RegExp(`(${options.ignore.join('|')})`, 'i'); 88 | this.ignoreRegExp = reg; 89 | }; 90 | 91 | exports.generateRedirectRoute = function(options) { 92 | const { redirect } = options; 93 | if (!redirect) { 94 | return; 95 | } 96 | for (const item of redirect) { 97 | this.routeString += ` 98 | { 99 | path:'${item.path}', 100 | redirect:'${item.redirect}' 101 | }, 102 | `; 103 | } 104 | }; 105 | 106 | exports.generateGuards = function(options) { 107 | if (options.beforeEach) { 108 | const str = options.beforeEach.toString(); 109 | this.routeString += ` 110 | router.beforeEach(${str}); 111 | `; 112 | } 113 | if (options.beforeResolve) { 114 | const str = options.beforeResolve.toString(); 115 | this.routeString += ` 116 | router.beforeResolve(${str}); 117 | `; 118 | } 119 | if (options.afterEach) { 120 | const str = options.afterEach.toString(); 121 | this.routeString += ` 122 | router.afterEach(${str}); 123 | `; 124 | } 125 | }; 126 | 127 | exports.generateModules = function(options) { 128 | let str = ''; 129 | if (options.modules) { 130 | for (const module of options.modules) { 131 | str += `import ${module.name} from '${module.package}';`; 132 | } 133 | } 134 | return str; 135 | }; 136 | 137 | exports.generateNotFound = function(options) { 138 | if (options.notFound) { 139 | this.routeString += ` 140 | { 141 | name:'notFound', 142 | path:'*', 143 | component: () => import('${options.notFound}') 144 | }, 145 | `; 146 | } 147 | }; 148 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { invoke } = require('./invoke'); 4 | class VueRouterInvokeWebpackPlugin { 5 | constructor(options) { 6 | this.$options = options; 7 | this.routerDir = ''; 8 | } 9 | 10 | apply(compiler) { 11 | // webpack4 12 | if (compiler && compiler.hooks && compiler.hooks.entryOption) { 13 | compiler.hooks.entryOption.tap('invoke', () => { 14 | invoke.call(this, this.$options); 15 | }); 16 | } 17 | // webpack3 18 | else { 19 | compiler.plugin('entry-option', () => { 20 | invoke.call(this, this.$options); 21 | }); 22 | } 23 | } 24 | } 25 | 26 | module.exports = VueRouterInvokeWebpackPlugin; 27 | -------------------------------------------------------------------------------- /core/invoke.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | init, 5 | generateRouteString, 6 | sortFilesAst, 7 | generateFilesAst 8 | } = require('./ast'); 9 | const { 10 | generateRedirectRoute, 11 | generateGuards, 12 | writeOrWatchFile, 13 | generateNotFound 14 | } = require('./files'); 15 | 16 | function start(options) { 17 | init.call(this, options); 18 | generateFilesAst.call(this, options.dir, this.filesAst, ''); 19 | sortFilesAst.call(this, this.filesAst); 20 | // console.dir(this.filesAst, { depth: null }); 21 | generateRouteString.call(this, this.filesAst); 22 | generateRedirectRoute.call(this, options); 23 | generateNotFound.call(this, options); 24 | this.routeString += this.routeStringPost; 25 | generateGuards.call(this, options); 26 | this.routeString += this.routeStringExport; 27 | } 28 | 29 | exports.start = start; 30 | 31 | exports.invoke = function(options) { 32 | start.call(this, options); 33 | writeOrWatchFile.call(this, options, start); 34 | }; 35 | -------------------------------------------------------------------------------- /core/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | exports.warn = msg => { 6 | assert.fail(`\n\n\x1B[31mvue-router-invoke-webpack-plugin:${msg} \x1b[39m\n`); 7 | }; 8 | 9 | exports.tips = msg => { 10 | // eslint-disable-next-line 11 | console.log(`\n\n\x1B[31mvue-router-invoke-webpack-plugin:${msg} \x1b[39m\n`); 12 | }; 13 | 14 | exports.firstLowerCase = ([first, ...rest]) => { 15 | if (first === '_') { 16 | return first + rest.shift().toLowerCase() + rest.join(''); 17 | } else { 18 | return first.toLowerCase() + rest.join(''); 19 | } 20 | }; 21 | 22 | exports.replaceAlias = (str, dir) => { 23 | return str.replace(new RegExp(dir, 'i'), ''); 24 | }; 25 | 26 | exports.replaceVue = str => str.replace(/\.vue/g, ''); 27 | 28 | exports.camelize = str => 29 | str.replace(/[-_](\w)/g, (_, c, i) => { 30 | return i === 0 ? `:${c}` : c.toUpperCase(); 31 | }); 32 | 33 | exports.makeMap = str => { 34 | const map = Object.create(null); 35 | const list = str.split(','); 36 | for (let i = 0; i < list.length; i++) { 37 | map[list[i]] = true; 38 | } 39 | return val => map[val]; 40 | }; 41 | 42 | exports.replaceArtificialDynamic = str => str.replace(/:/g, ''); 43 | 44 | exports.diff = (a, b) => { 45 | const aSet = new Set(a); 46 | return b.filter(v => !aSet.has(v)); 47 | }; 48 | 49 | exports.toPlain = value => Object.prototype.toString.call(value).slice(8, -1); 50 | -------------------------------------------------------------------------------- /demos/.invoke/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import apis from '@/apis';; 4 | Vue.use(Router); 5 | export const routes = [{ 6 | component: () => import('@/src/Index.vue'), 7 | name: 'index', 8 | path: '/', 9 | }, 10 | { 11 | component: () => import('@/src/Complex/Index.vue'), 12 | name: 'complex', 13 | path: '/complex', 14 | }, 15 | { 16 | component: () => import('@/src/Complex/Home/Home.vue'), 17 | name: 'complex-home', 18 | path: '/complex/home', 19 | children: [{ 20 | component: () => import('@/src/Complex/Home/Account/Account.vue'), 21 | name: 'complex-home-account', 22 | path: 'account', 23 | children: [{ 24 | component: () => import('@/src/Complex/Home/Account/Chunk/Index.vue'), 25 | name: 'complex-home-account-chunk', 26 | path: 'chunk', 27 | }, 28 | { 29 | component: () => import('@/src/Complex/Home/Account/Inner/Index.vue'), 30 | name: 'complex-home-account-inner', 31 | path: 'inner', 32 | }, 33 | { 34 | component: () => import('@/src/Complex/Home/Account/_Dynamic/Index.vue'), 35 | name: 'complex-home-account-dynamic', 36 | path: ':dynamic', 37 | }, 38 | ], 39 | }, 40 | { 41 | component: () => import('@/src/Complex/Home/Details/Details.vue'), 42 | name: 'complex-home-details', 43 | meta: { 44 | name: 'details', 45 | }, 46 | path: 'details', 47 | children: [{ 48 | component: () => import('@/src/Complex/Home/Details/Infor/Index.vue'), 49 | name: 'complex-home-details-infor', 50 | path: 'infor', 51 | }, 52 | { 53 | component: () => import('@/src/Complex/Home/Details/Intro/Index.vue'), 54 | name: 'complex-home-details-intro', 55 | path: 'intro', 56 | }, 57 | ], 58 | }, 59 | ], 60 | }, 61 | { 62 | component: () => import('@/src/Complex/Login/Index.vue'), 63 | name: 'complex-login', 64 | path: '/complex/login', 65 | }, 66 | { 67 | component: () => import('@/src/Dynamic/Index.vue'), 68 | name: 'dynamic', 69 | path: '/dynamic', 70 | }, 71 | { 72 | component: () => import('@/src/Dynamic/_UserForm/Index.vue'), 73 | name: 'dynamic-userForm', 74 | meta: { 75 | name: 'user', 76 | }, 77 | path: '/dynamic/:userForm', 78 | }, 79 | { 80 | component: () => import('@/src/Nest/Index.vue'), 81 | name: 'nest', 82 | meta: { 83 | name: 'nest', 84 | bool: true, 85 | }, 86 | path: '/nest', 87 | }, 88 | { 89 | component: () => import('@/src/Nest/Home/Home.vue'), 90 | name: 'nest-home', 91 | path: '/nest/home', 92 | children: [{ 93 | component: () => import('@/src/Nest/Home/Account/Index.vue'), 94 | name: 'nest-home-account', 95 | meta: { 96 | name: 'account', 97 | }, 98 | path: 'account', 99 | }, 100 | { 101 | component: () => import('@/src/Nest/Home/Account/_Id/Index.vue'), 102 | name: 'nest-home-account-id', 103 | path: 'account/:id', 104 | }, 105 | { 106 | component: () => import('@/src/Nest/Home/Infor/Index.vue'), 107 | name: 'nest-home-infor', 108 | path: 'infor', 109 | }, 110 | { 111 | component: () => import('@/src/Nest/Home/Test/Index.vue'), 112 | name: 'nest-home-test', 113 | path: 'test', 114 | }, 115 | ], 116 | }, 117 | { 118 | component: () => import('@/src/Nest/test/index.vue'), 119 | name: 'nest-test', 120 | path: '/nest/test', 121 | }, 122 | { 123 | component: () => import('@/src/Single/Index.vue'), 124 | name: 'single', 125 | path: '/single', 126 | }, 127 | { 128 | component: () => import('@/src/Single/User-Name/Index.vue'), 129 | name: 'single-userName', 130 | meta: { 131 | name: 'user', 132 | }, 133 | redirect: { 134 | path: '/another', 135 | }, 136 | path: '/single/userName', 137 | }, 138 | { 139 | name: 'notFound', 140 | path: '*', 141 | component: () => import('@/src/NotFound.vue') 142 | }, 143 | ]; 144 | const router = new Router({ 145 | mode: 'history', 146 | routes, 147 | scrollBehavior: (to, from, savedPosition) => { 148 | if (savedPosition) { 149 | return savedPosition; 150 | } else { 151 | return { 152 | x: 0, 153 | y: 0 154 | }; 155 | } 156 | } 157 | }); 158 | router.beforeEach(async (to, from, next) => { 159 | if (!Vue._cachedForbiddenRoute) { 160 | Vue._cachedForbiddenRoute = []; 161 | await apis.getForbiddenRoute().then(res => { 162 | Vue._cachedForbiddenRoute = res; 163 | }); 164 | } 165 | if (Vue._cachedForbiddenRoute.includes(to.path)) { 166 | next({ 167 | name: 'notFound' 168 | }); 169 | } else { 170 | next(); 171 | } 172 | }); 173 | 174 | router.beforeResolve((to, from, next) => { 175 | next(); 176 | }); 177 | 178 | router.afterEach((to, from) => {}); 179 | export default router; -------------------------------------------------------------------------------- /demos/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /demos/apis/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async getForbiddenRoute() { 3 | return ['/single/user']; 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /demos/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './.invoke/router.js'; 4 | 5 | export default new Vue({ 6 | el: '#app', 7 | router, 8 | render: h => h(App) 9 | }); 10 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Account/Account.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Account/Chunk/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Account/Inner/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Account/_Dynamic/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Details/Details.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Details/Infor/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /demos/src/Complex/Home/Details/Intro/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Complex/Home/Details/Intro/Index.vue -------------------------------------------------------------------------------- /demos/src/Complex/Home/Details/meta.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | - name: details -------------------------------------------------------------------------------- /demos/src/Complex/Home/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /demos/src/Complex/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Complex/Login/Images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Complex/Login/Images/test.png -------------------------------------------------------------------------------- /demos/src/Complex/Login/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Dynamic/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Dynamic/_UserForm/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Dynamic/_UserForm/meta.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | - name: user -------------------------------------------------------------------------------- /demos/src/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Nest/Home/Account/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Nest/Home/Account/_Id/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Nest/Home/Account/meta.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | - name: account 3 | -------------------------------------------------------------------------------- /demos/src/Nest/Home/Home.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /demos/src/Nest/Home/Infor/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Nest/Home/Infor/Index.vue -------------------------------------------------------------------------------- /demos/src/Nest/Home/Test/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Nest/Home/Test/Index.vue -------------------------------------------------------------------------------- /demos/src/Nest/Home/meta.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Nest/Home/meta.yml -------------------------------------------------------------------------------- /demos/src/Nest/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Nest/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Nest/index.scss -------------------------------------------------------------------------------- /demos/src/Nest/meta.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | - name: nest 3 | - bool: true 4 | -------------------------------------------------------------------------------- /demos/src/Nest/test/index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Nest/test/index.vue -------------------------------------------------------------------------------- /demos/src/NotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Single/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Single/User-Name/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /demos/src/Single/User-Name/meta.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | - name: user 3 | redirect: 4 | - path: '/another' 5 | -------------------------------------------------------------------------------- /demos/src/Template.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/demos/src/Template.vue -------------------------------------------------------------------------------- /demos/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 4 | const Progress = require('progress-bar-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const VueRouterInvokePlugin = require('../core/index'); 7 | const isDev = process.env.NODE_ENV === 'development'; 8 | 9 | const base = { 10 | mode: process.env.NODE_ENV, 11 | entry: { 12 | app: path.resolve(__dirname, './index.js') 13 | }, 14 | output: { 15 | filename: '[name].js', 16 | path: path.resolve(__dirname, './dist'), 17 | publicPath: '/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | loader: 'babel-loader', 24 | exclude: /node_modules/ 25 | }, 26 | { 27 | test: /\.vue$/, 28 | loader: 'vue-loader' 29 | } 30 | ] 31 | }, 32 | resolve: { 33 | extensions: ['.js', '.vue'], 34 | alias: { 35 | '@': path.resolve(process.cwd(), 'demos') 36 | } 37 | }, 38 | plugins: [ 39 | new VueRouterInvokePlugin({ 40 | dir: 'demos/src', 41 | alias: '@/src', 42 | language: 'javascript', 43 | routerDir: 'demos', 44 | ignore: [ 45 | 'images', 46 | 'template.vue', 47 | 'components', 48 | 'notfound.vue', 49 | /\.scss$/ 50 | ], 51 | notFound: '@/src/NotFound.vue', 52 | modules: [ 53 | { 54 | name: 'apis', 55 | package: '@/apis' 56 | } 57 | ], 58 | scrollBehavior: (to, from, savedPosition) => { 59 | if (savedPosition) { 60 | return savedPosition; 61 | } else { 62 | return { x: 0, y: 0 }; 63 | } 64 | }, 65 | /* eslint-disable */ 66 | beforeEach: async (to, from, next) => { 67 | if (!Vue._cachedForbiddenRoute) { 68 | Vue._cachedForbiddenRoute = []; 69 | await apis.getForbiddenRoute().then(res => { 70 | Vue._cachedForbiddenRoute = res; 71 | }); 72 | } 73 | if (Vue._cachedForbiddenRoute.includes(to.path)) { 74 | next({ 75 | name: 'notFound' 76 | }); 77 | } else { 78 | next(); 79 | } 80 | }, 81 | /* eslint-enable */ 82 | beforeResolve: (to, from, next) => { 83 | next(); 84 | }, 85 | afterEach: (to, from) => {} 86 | }), 87 | new VueLoaderPlugin(), 88 | new HtmlWebpackPlugin({ 89 | filename: 'index.html', 90 | template: path.resolve(__dirname, './index.html') 91 | }), 92 | new webpack.NoEmitOnErrorsPlugin() 93 | ], 94 | stats: { 95 | colors: true, 96 | modules: false, 97 | children: false, 98 | chunks: false, 99 | chunkModules: false 100 | } 101 | }; 102 | 103 | if (isDev) { 104 | base.devServer = { 105 | port: '8089', 106 | publicPath: '/', 107 | inline: true, 108 | quiet: true, 109 | open: false, 110 | clientLogLevel: 'warning', 111 | historyApiFallback: true 112 | }; 113 | 114 | base.plugins.push( 115 | new Progress(), 116 | new webpack.DefinePlugin({ 117 | process: { 118 | env: { 119 | NODE_ENV: '"development"' 120 | } 121 | } 122 | }) 123 | ); 124 | } 125 | 126 | if (!isDev) { 127 | base.plugins.push( 128 | new webpack.DefinePlugin({ 129 | process: { 130 | env: { 131 | NODE_ENV: '"production"' 132 | } 133 | } 134 | }) 135 | ); 136 | } 137 | 138 | module.exports = base; 139 | -------------------------------------------------------------------------------- /docs/images/index_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/docs/images/index_cn.png -------------------------------------------------------------------------------- /docs/images/index_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/docs/images/index_en.png -------------------------------------------------------------------------------- /docs/images/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/docs/images/name.png -------------------------------------------------------------------------------- /docs/images/notice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/docs/images/notice.png -------------------------------------------------------------------------------- /docs/zh_CN/README.md: -------------------------------------------------------------------------------- 1 | # vue-router-invoke-webpack-plugin 2 | 3 | ![](https://img.shields.io/codecov/c/github/qymh/vue-router-invoke-webpack-plugin) 4 | ![](https://img.shields.io/npm/dm/vue-router-invoke-webpack-plugin) 5 | ![](https://img.shields.io/npm/v/vue-router-invoke-webpack-plugin) 6 | ![](https://img.shields.io/npm/l/vue-router-invoke-webpack-plugin) 7 | 8 | > `typescript`重写且同时支持 vue2.x 和 vue3.x 的新版本 [vue-router-invoke-next-webpack-plugin](https://github.com/Qymh/vue-router-invoke-next-webpack-plugin) 9 | 10 | 根据文件格式自动生成`vue-router`的路由 11 | 12 | ## 下载 13 | 14 | ### npm 15 | 16 | ```javascript 17 | npm install vue-router-invoke-webpack-plugin -D 18 | ``` 19 | 20 | ### cnpm 21 | 22 | ```javascript 23 | cnpm install vue-router-invoke-webpack-plugin -D 24 | ``` 25 | 26 | ### yarn 27 | 28 | ```javascript 29 | yarn add vue-router-invoke-webpack-plugin -D 30 | ``` 31 | 32 | ## 什么是路由自动注入 33 | 34 | 路由自动注入是指根据文件目录的格式自动生成对应的`router.js`, 而不需要每次创建模块都去手动引用 35 | 36 | ## 用法 37 | 38 | ### Webpack 39 | 40 | - 我们需要确定当前环境处于开发(`development`)还是生产(`production`)环境,所以你需要设置`process.env.NODE_ENV`在开发环境下为`development`,在生产环境下为`production`.不考虑跨平台的话环境变量是可以直接设置的,但也有很多插件实现了跨平台设置,我们推荐[cross-env](https://github.com/kentcdodds/cross-env) 41 | - 考虑到多人开发,所以引用路由的时候不能通过你的本机绝对路由引入,所以你需要给观察的目录`dir`设置一个别名[alias](https://webpack.js.org/configuration/resolve/#resolvealias) 42 | - 自动构建的路由是懒加载的,所以你需要引用一个 babel 插件[@babel/plugin-syntax-dynamic-import](https://babeljs.io/docs/en/next/babel-plugin-syntax-dynamic-import.html) 43 | 44 | ```javascript 45 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 46 | const path = require('path') 47 | 48 | // 省略掉其他配置... 49 | 50 | resolve: { 51 | alias: { 52 | '@': path.resolve(process.cwd(), 'demos') 53 | } 54 | } 55 | 56 | plugins: [ 57 | new VueRouterInvokeWebpackPlugin( 58 | dir: 'demos/src', 59 | alias: '@/src' 60 | ) 61 | ]; 62 | ``` 63 | 64 | ### VueCli3 65 | 66 | vuecli3 会比 webpack 配置容易点 67 | 68 | `vue.config.js` 69 | 70 | ```javascript 71 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 72 | 73 | module.exports = { 74 | // 省略掉其他配置... 75 | configureWebpack(config) { 76 | config.plugins.push( 77 | new VueRouterInvokeWebpackPlugin({ 78 | dir: 'src/views', 79 | // 必须设置dir配置的别名 80 | alias: '@/views' 81 | }) 82 | ); 83 | } 84 | }; 85 | 86 | // 或者采用另外一个方法 87 | 88 | module.exports = { 89 | // 省略掉其他配置... 90 | configureWebpack: { 91 | plugins: [ 92 | new VueRouterInvokeWebpackPlugin({ 93 | dir: 'src/views', 94 | // 必须设置dir配置的别名 95 | alias: '@/views' 96 | }) 97 | ] 98 | } 99 | }; 100 | ``` 101 | 102 | ### Start 103 | 104 | 在配置好后,你可以通过`npm run serve`或者你定义的其他命令在开发模式下激活插件,当第一次运行或`dir`观察的目录发生变化时,`router.js`会被自动构建 105 | 106 | 同样的在生产环境下,你可以通过`npm run build`或者你定义的其他命令激活插件,`router.js`会被自动构建和打包 107 | 108 | ### 配置 109 | 110 | | Prop | Type | Required | Default | Description | 111 | | -------------- | :------: | :------: | :----------: | -------------------------: | 112 | | dir | String | true | '' | 观察的 vue 文件目录 | 113 | | alias | String | true | '' | 观察的 vue 文件目录的别名 | 114 | | notFound | String | false | '' | 404 路由 | 115 | | mode | String | false | history | hash 或者 history | 116 | | meta | String | false | meta | 定义 meta 的 yml 文件名 | 117 | | routerDir | String | false | ROOT | 构建后的 router.js 的位置 | 118 | | language | String | false | javascript | javascript 或者 typescript | 119 | | ignore | Array | false | ['.dsstore'] | 忽略的文件或者文件夹 | 120 | | redirect | Array | false | [] | 重定向路由 | 121 | | modules | Array | false | [] | 导入的其他模块 | 122 | | scrollBehavior | Function | false | '' | 同 scrollBehavior | 123 | | beforeEach | Function | false | '' | router.beforeEach | 124 | | beforeResolve | Function | false | '' | router.beforeResolve | 125 | | afterEach | Function | false | '' | router.afterEach | 126 | 127 | ## 如何使用自动注入 128 | 129 | 下面将会使用 vuecli3 作为列子 130 | 131 | `vue.config.js` 132 | 133 | ```javascript 134 | const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin'); 135 | 136 | module.exports = { 137 | // 省略其他配置 138 | configureWebpack(config) { 139 | config.plugins.push( 140 | new VueRouterInvokeWebpackPlugin({ 141 | dir: 'src/views', 142 | alias: '@/views' 143 | }) 144 | ); 145 | } 146 | }; 147 | ``` 148 | 149 | 然后在入口文件`src/main.js`中引用构建好的`router.js` 150 | 151 | 默认生成的`router.js`的位置在项目根路由的`.invoke`文件夹中,你可以通过`routerDir`这个配置更改默认位置 152 | 153 | 要注意的是`routerDir`是相对于根路由的地址,而不是绝对地址哦 154 | 155 | 我们建议把生成的`router.js`放在`.gitignore`和`.eslintignore`中,它没有必要被版本控制或者 eslint 校验,因为它是自动生成的 156 | 157 | ```javascript 158 | import Vue from 'vue'; 159 | import App from './App.vue'; 160 | import router from '../.invoke/router'; 161 | 162 | export default new Vue({ 163 | el: '#app', 164 | router, 165 | render: h => h(App) 166 | }); 167 | ``` 168 | 169 | ### 单路由 170 | 171 | 请注意,文件格式是有一层文件夹放在了外面,vue 命名为`Index.vue`而不是直接命名 vue 文件,这一点可能和平常的认知不太一样 172 | 173 | 同样的,也不要去命名文件夹名字为`Index`这会与嵌套路由的判断产生歧义 174 | 175 | > 0.2.7 的版本我们引入了暴力提醒 第一次打包的时候如果命名规则和期望值不同,插件会直接报错,在开发环境下插件不会中断程序运行,但会高亮报错信息,比如这样 176 | 177 | ![image](https://github.com/Qymh/vue-router-invoke-webpack-plugin/blob/master/docs/images/notice.png) 178 | 179 | 如果你发现此类的提醒,可以检查下文件格式是否符合规则 180 | 181 | 如果你的文件是这样的 182 | 183 | ``` 184 | src 185 | ├── views 186 | │ ├── Login 187 | │ │ └── Index.vue 188 | │ └── User 189 | │ ├── Account 190 | │ │ └── Index.vue 191 | │ ├── Home 192 | │ │ └── Index.vue 193 | │ └── Index.vue 194 | ``` 195 | 196 | 那么自动生成的路由会是这样 197 | 198 | ```javascript 199 | { 200 | component: () => 201 | import('@/views/Login/Index.vue'), 202 | name: 'login', 203 | path: '/login' 204 | }, 205 | { 206 | component: () => 207 | import('@/views/User/Index.vue'), 208 | name: 'user', 209 | path: '/user' 210 | }, 211 | { 212 | component: () => 213 | import('@/views/User/Account/Index.vue'), 214 | name: 'user-account', 215 | path: '/user/account' 216 | }, 217 | { 218 | component: () => 219 | import('@/views/User/Home/Index.vue'), 220 | name: 'user-home', 221 | path: '/user/home' 222 | } 223 | ``` 224 | 225 | ### 首页 226 | 227 | 我们对首页做了特殊处理,首页直接用`Index.vue`表示 228 | 229 | 如果你的文件是这样的 230 | 231 | ``` 232 | src 233 | ├── views 234 | │ ├── Login 235 | │ │ └── Index.vue 236 | │ └── Index.vue 237 | ``` 238 | 239 | 那么自动生成的路由会是这样 240 | 241 | ```javascript 242 | { 243 | component: () => 244 | import('@/views/Index.vue'), 245 | name: 'index', 246 | path: '/' 247 | }, 248 | { 249 | component: () => 250 | import('@/views/Login/Index.vue'), 251 | name: 'login', 252 | path: '/login' 253 | } 254 | ``` 255 | 256 | ### 动态路由 257 | 258 | 如果你的文件是这样的 259 | 260 | ``` 261 | src 262 | ├── views 263 | │ ├── Login 264 | │ │ └── Index.vue 265 | │ └── User 266 | │ ├── _Home 267 | │ │ └── Index.vue 268 | │ └── Index.vue 269 | ``` 270 | 271 | 那么自动生成的路由会是这样 272 | 273 | ```javascript 274 | { 275 | component: () => 276 | import('@/views/Login/Index.vue'), 277 | name: 'login', 278 | path: '/login' 279 | }, 280 | { 281 | component: () => 282 | import('@/views/User/Index.vue'), 283 | name: 'user', 284 | path: '/user' 285 | }, 286 | { 287 | component: () => 288 | import('@/views/User/_Home/Index.vue'), 289 | name: 'user-home', 290 | path: '/user/:home' 291 | } 292 | ``` 293 | 294 | ### 嵌套路由 295 | 296 | 如果你的文件是这样的 297 | 298 | ``` 299 | src 300 | ├── views 301 | │ ├── Login 302 | │ │ └── Index.vue 303 | │ └── User 304 | │ ├── Chart 305 | │ │ └── Index.vue 306 | │ ├── Home 307 | │ │ └── Index.vue 308 | │ └── User.vue 309 | ``` 310 | 311 | 那么自动生成的路由会是这样 312 | 313 | ```javascript 314 | { 315 | component: () => 316 | import('@/views/Login/Index.vue'), 317 | name: 'login', 318 | path: '/login' 319 | }, 320 | { 321 | component: () => 322 | import('@/views/User/User.vue'), 323 | name: 'user', 324 | path: '/user', 325 | children: [ 326 | { 327 | component: () => 328 | import('@/views/User/Chart/Index.vue'), 329 | name: 'user-chart', 330 | path: 'chart' 331 | }, 332 | { 333 | component: () => 334 | import('@/views/User/Home/Index.vue'), 335 | name: 'user-home', 336 | path: 'home' 337 | } 338 | ] 339 | } 340 | ``` 341 | 342 | ### 动态嵌套路由 343 | 344 | 如果你的文件长这样 345 | 346 | ``` 347 | src 348 | ├── views 349 | │ ├── Login 350 | │ │ └── Index.vue 351 | │ └── User 352 | │ ├── _Category 353 | │ │ ├── _Category.vue 354 | │ │ └── Infor 355 | │ │ └── Index.vue 356 | │ └── Index.vue 357 | ``` 358 | 359 | 那么自动生成的路由会是这样 360 | 361 | ```javascript 362 | { 363 | component: () => 364 | import('@/views/Login/Index.vue'), 365 | name: 'login', 366 | path: '/login' 367 | }, 368 | { 369 | component: () => 370 | import('@/views/User/Index.vue'), 371 | name: 'user', 372 | path: '/user' 373 | }, 374 | { 375 | component: () => 376 | import('@/views/User/_Category/_Category.vue'), 377 | name: 'user-category', 378 | path: '/user/:category', 379 | children: [ 380 | { 381 | component: () => 382 | import('@/views/User/_Category/Infor/Index.vue'), 383 | name: 'user-category-infor', 384 | path: 'infor' 385 | } 386 | ] 387 | } 388 | ``` 389 | 390 | ## 命名矫正 391 | 392 | 在为什么使用`vue-router-invoke-webpack-plugin`中提到过命名不统一的问题,我们会将所有不同的命名转化为驼峰命名,这只是一个小的命名矫正,根本的方法应该是统一命名规则 393 | 394 | 举个列子 395 | 396 | ``` 397 | src 398 | ├── views 399 | │ ├── LoginPage 400 | │ │ └── index.vue 401 | │ └── User-home 402 | │ ├── account 403 | │ │ └── Index.vue 404 | │ ├── Home-details 405 | │ │ └── Index.vue 406 | │ └── Index.vue 407 | ``` 408 | 409 | 矫正命名后的路由会是这样 410 | 411 | ```javascript 412 | { 413 | component: () => import('@/views/LoginPage/index.vue'), 414 | name: 'loginPage', 415 | path: '/loginPage' 416 | }, 417 | { 418 | component: () => import('@/views/User-home/Index.vue'), 419 | name: 'userHome', 420 | path: '/userHome' 421 | }, 422 | { 423 | component: () => import('@/views/User-home/Home-details/Index.vue'), 424 | name: 'userHome-homeDetails', 425 | path: '/userHome/homeDetails' 426 | }, 427 | { 428 | component: () => import('@/views/User-home/account/Index.vue'), 429 | name: 'userHome-account', 430 | path: '/userHome/account' 431 | }, 432 | ``` 433 | 434 | ## meta 替代品 435 | 436 | `meta`属性可以解决很多问题,比如定义当前页面标题或者给一个字段判断当前页面是否需要登录. 437 | 438 | 定义标题之类的可以通过[vue-meta](https://github.com/nuxt/vue-meta)解决 439 | 440 | 但如果你需要定义`meta`属性的话,需要写一个`yml`文件 441 | 442 | 举个列子 443 | 444 | ```javascript 445 | src/views 446 | ├── Single 447 | │ ├── Index.vue 448 | │ └── User 449 | │ ├── Index.vue 450 | │ └── meta.yml 451 | ``` 452 | 453 | `meta.yml` 454 | 455 | ```yml 456 | meta: 457 | - name: user 458 | ``` 459 | 460 | 自动构建后的路由会是这样 461 | 462 | ```javascript 463 | { 464 | component: () => import('@/views/Single/Index.vue'), 465 | name: 'single', 466 | path: 'single' 467 | }, 468 | { 469 | component: () => import('@/views/Single/User/Index.vue'), 470 | name: 'single-user', 471 | meta: { name: user }, 472 | path: 'single/user' 473 | } 474 | ``` 475 | 476 | > 在版本 0.4.1 后,meta 的类型支持了布尔、字符串、数组、原生对象,但是类型并不支持`JSON.stringify`无法转移的值比如`Symbol`、函数、`undefined`、循环引用的对象 477 | 478 | ## 特殊的配置 479 | 480 | ### 404 路由 481 | 482 | 举个列子 483 | 484 | ```javascript 485 | plugins: [ 486 | new VueRouterInvokeWebpackPlugin({ 487 | dir: 'src/views', 488 | alias: '@/views', 489 | // muse set ignore for notFound chunk 490 | ignore: ['NotFound.vue'], 491 | notFound: '@/views/NotFound.vue' 492 | }) 493 | ]; 494 | ``` 495 | 496 | ``` 497 | src 498 | ├── views 499 | │ ├── Login 500 | │ │ └── Index.vue 501 | │ └── Index.vue 502 | │ └── NotFound.vue 503 | 504 | ``` 505 | 506 | 自动构建后的路由会是这样 507 | 508 | ```javascript 509 | { 510 | component: () => 511 | import('@/views/Index.vue'), 512 | name: 'index', 513 | path: '/' 514 | }, 515 | { 516 | component: () => 517 | import('@/views/NotFound.vue'), 518 | name: 'notFound', 519 | path: '*' 520 | }, 521 | { 522 | component: () => 523 | import('@/views/Login/Index.vue'), 524 | name: 'login', 525 | path: '/login' 526 | } 527 | ``` 528 | 529 | ### 忽略文件和文件夹 530 | 531 | ```javascript 532 | plugins: [ 533 | new VueRouterInvokeWebpackPlugin({ 534 | dir: 'src/views', 535 | alias: '@/views', 536 | language: 'javascript', 537 | ignore: ['images', 'components', 'template.vue'] 538 | }) 539 | ]; 540 | ``` 541 | 542 | 自动构建的路由将会不区分大小写的忽略掉`images` `components` `template.vue` 543 | 544 | 在版本`0.4.3` 之上 你也可以在ignore中定义正则来匹配文件 545 | 546 | ```javascript 547 | plugins: [ 548 | new VueRouterInvokeWebpackPlugin({ 549 | dir: 'src/views', 550 | alias: '@/views', 551 | language: 'javascript', 552 | ignore: ['images', 'components', 'template.vue', /\.scss$/] 553 | }) 554 | ]; 555 | ``` 556 | 557 | the directory 558 | 559 | ``` 560 | src 561 | ├── views 562 | │ ├── Login 563 | │ │ └── Index.vue 564 | │ │ └── Index.scss 565 | │ ├── Template.vue 566 | │ └── User 567 | │ ├── Components 568 | │ ├── Images 569 | │ └── Index.vue 570 | ``` 571 | 572 | ```javascript 573 | { 574 | component: () => 575 | import('@/views/Login/Index.vue'), 576 | name: 'login', 577 | path: '/login' 578 | }, 579 | { 580 | component: () => 581 | import('@/views/User/Index.vue'), 582 | name: 'user', 583 | path: '/user' 584 | } 585 | ``` 586 | 587 | ### 重定向 588 | 589 | 假设你的配置如下 590 | 591 | ```javascript 592 | plugins: [ 593 | new VueRouterInvokeWebpackPlugin({ 594 | dir: 'src/views', 595 | alias: '@/views', 596 | language: 'javascript', 597 | redirect: [ 598 | { 599 | redirect: '/', 600 | path: '/home' 601 | }, 602 | { 603 | redirect: '/test', 604 | path: '/demo' 605 | } 606 | ] 607 | }) 608 | ]; 609 | ``` 610 | 611 | 自动构建的路由会长这样 612 | 613 | ```javascript 614 | { 615 | path: '/home', 616 | redirect: '/' 617 | }, 618 | { 619 | path: '/demo', 620 | redirect: '/test' 621 | } 622 | ``` 623 | 624 | #### 重定向 yml 配置 625 | 626 | > '0.4.0'版本之后 你可以在 yml 中配置重定向 627 | 628 | 举个列子 629 | 630 | ```javascript 631 | src/views 632 | ├── Single 633 | │ ├── Index.vue 634 | │ └── User 635 | │ ├── Index.vue 636 | │ └── meta.yml 637 | ``` 638 | 639 | `meta.yml` 640 | 641 | ```yml 642 | redirect: 643 | - path: /test 644 | ``` 645 | 646 | 自动生成的路由会变成这样 647 | 648 | ```javascript 649 | { 650 | component: () => import('@/views/Single/Index.vue'), 651 | name: 'single', 652 | path: 'single' 653 | }, 654 | { 655 | component: () => import('@/views/Single/User/Index.vue'), 656 | name: 'single-user', 657 | path: 'single/user', 658 | redirect: { 659 | path: '/test' 660 | }, 661 | } 662 | ``` 663 | 664 | ### 模块 665 | 666 | 自动生成的`router.js`有两个模块 `vue` `vue-router` 667 | 668 | ```javascript 669 | import Vue from 'vue'; 670 | import Router from 'vue-router'; 671 | ``` 672 | 673 | 如果你需要其他模块用在`beforeEach`之类的方法中,你需要手动加上,举个列子 674 | 675 | ```javascript 676 | new VueRouterInvokeWebpackPlugin({ 677 | dir: 'src/views', 678 | alias: '@/views', 679 | modules: [ 680 | { 681 | name: 'diyName', 682 | package: 'some-packages' 683 | } 684 | ] 685 | }); 686 | ``` 687 | 688 | 自动生成的路由会长这样 689 | 690 | ```javascript 691 | // 省略其它配置 692 | import diyName from 'some-packages'; 693 | ``` 694 | 695 | ### vue 路由全局守卫 696 | 697 | 如果你的配置如下 698 | 699 | ```javascript 700 | new VueRouterInvokeWebpackPlugin({ 701 | dir: 'src/views', 702 | alias: '@/views', 703 | language: 'javascript', 704 | beforeEach: (to, from, next) => { 705 | next(); 706 | }, 707 | beforeResolve: (to, from, next) => { 708 | next(); 709 | }, 710 | afterEach: (to, from) => {} 711 | }); 712 | ``` 713 | 714 | 自动生成的路由会是这样 715 | 716 | ```javascript 717 | // 省略其它配置 718 | const router = new Router({ mode: 'history', routes }); 719 | router.beforeEach((to, from, next) => { 720 | next(); 721 | }); 722 | 723 | router.beforeResolve((to, from, next) => { 724 | next(); 725 | }); 726 | 727 | router.afterEach((to, from) => {}); 728 | export default router; 729 | ``` 730 | 731 | ### ScrollBehavior 732 | 733 | 如果你的配置如下 734 | 735 | ```javascript 736 | new VueRouterInvokeWebpackPlugin({ 737 | dir: 'src/views', 738 | alias: '@/views', 739 | language: 'javascript', 740 | scrollBehavior: (to, from, savedPosition) => { 741 | if (savedPosition) { 742 | return savedPosition; 743 | } else { 744 | return { x: 0, y: 0 }; 745 | } 746 | } 747 | }); 748 | ``` 749 | 750 | 自动生成的路由会是这样 751 | 752 | ```javascript 753 | // 省略其他配置 754 | const router = new Router({ 755 | mode: 'history', 756 | routes, 757 | scrollBehavior: (to, from, savedPosition) => { 758 | if (savedPosition) { 759 | return savedPosition; 760 | } else { 761 | return { x: 0, y: 0 }; 762 | } 763 | } 764 | }); 765 | ``` 766 | 767 | ## Demos 768 | 769 | 详细的演示你可以`git clone`克隆我们的项目然后执行`npm run build:demos`,`demo`没有什么内容它的着重点在于`router.js`的构建,你可以看到不同文件目录对应的不同格式 770 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js'], 3 | transform: { 4 | '^.+\\.jsx?$': 'babel-jest' 5 | }, 6 | transformIgnorePatterns: ['/node_modules/'], 7 | testMatch: ['**/tests/**/*.spec.js'], 8 | collectCoverageFrom: ['core/**/*.js'], 9 | coverageDirectory: 'coverage', 10 | testEnvironment: 'node' 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-router-invoke-webpack-plugin", 3 | "version": "0.4.4", 4 | "description": "automatic generate your vue-router path and stronger normalize your file directory", 5 | "main": "core/index.js", 6 | "author": { 7 | "name": "Qymh", 8 | "email": "bowei.zhang@ankerbox.com" 9 | }, 10 | "keywords": [ 11 | "vue", 12 | "vue-router", 13 | "webpack" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Qymh/vue-router-invoke-webpack-plugin.git" 18 | }, 19 | "engines": { 20 | "node": ">=8.15.1" 21 | }, 22 | "typings": "types/index.d.ts", 23 | "license": "MIT", 24 | "scripts": { 25 | "check": "yarn upgrade-interactive --latest", 26 | "lint:corejs": "eslint \"core/**/*.js\"", 27 | "lint:demosjs": "eslint \"demos/**/*.{js,vue} \"", 28 | "lint:js": "npm run lint:corejs && npm run lint:demosjs", 29 | "lint:style": "stylelint \"demos/**/*.{js,vue}\"", 30 | "lint": "npm run lint:js && npm run lint:style", 31 | "dev:demos": "cross-env NODE_ENV=development webpack-dev-server --config demos/webpack.config.js", 32 | "build:demos": "cross-env NODE_ENV=production webpack --config demos/webpack.config.js", 33 | "test": "jest --coverage --watchAll", 34 | "test:single": "jest --coverage", 35 | "test:ci": "jest && codecov", 36 | "pub": "node scripts/publish", 37 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1", 38 | "commit": "git cz" 39 | }, 40 | "dependencies": { 41 | "js-beautify": "^1.10.2", 42 | "js-yaml": "^3.13.1", 43 | "yamljs": "^0.3.0" 44 | }, 45 | "devDependencies": { 46 | "@commitlint/cli": "^8.2.0", 47 | "@commitlint/config-conventional": "^8.2.0", 48 | "@types/jest": "^24.0.23", 49 | "@vue/babel-preset-app": "^4.1.1", 50 | "babel-jest": "^24.9.0", 51 | "babel-loader": "^8.0.6", 52 | "chokidar": "^3.3.0", 53 | "codecov": "^3.6.1", 54 | "conventional-changelog-cli": "^2.0.31", 55 | "core-js": "^3.6.2", 56 | "cross-env": "^6.0.3", 57 | "eslint": "^6.7.2", 58 | "eslint-config-prettier": "^6.7.0", 59 | "eslint-config-standard": "^14.1.0", 60 | "eslint-plugin-import": "^2.18.2", 61 | "eslint-plugin-node": "^10.0.0", 62 | "eslint-plugin-prettier": "^3.1.1", 63 | "eslint-plugin-promise": "^4.2.1", 64 | "eslint-plugin-standard": "^4.0.1", 65 | "eslint-plugin-vue": "^6.0.1", 66 | "execa": "^3.4.0", 67 | "fs-extra": "^8.1.0", 68 | "git-cz": "^3.3.0", 69 | "html-webpack-plugin": "^3.2.0", 70 | "husky": "^3.1.0", 71 | "inquirer": "^7.0.1", 72 | "jest": "^24.9.0", 73 | "lint-staged": "^9.5.0", 74 | "progress-bar-webpack-plugin": "^1.12.1", 75 | "rimraf": "^3.0.0", 76 | "semver": "^7.0.0", 77 | "stylelint": "^12.0.0", 78 | "stylelint-config-standard": "^19.0.0", 79 | "vue": "^2.6.10", 80 | "vue-loader": "^15.7.2", 81 | "vue-router": "^3.1.3", 82 | "vue-template-compiler": "^2.6.10", 83 | "webpack": "^4.41.2", 84 | "webpack-cli": "^3.3.10", 85 | "webpack-dev-server": "^3.9.0" 86 | }, 87 | "husky": { 88 | "hooks": { 89 | "pre-commit": "lint-staged" 90 | } 91 | }, 92 | "lint-staged": { 93 | "*.css": [ 94 | "stylelint --fix", 95 | "git add" 96 | ], 97 | "*.js": [ 98 | "eslint --fix", 99 | "git add" 100 | ], 101 | "*.vue": [ 102 | "stylelint --fix", 103 | "eslint --fix", 104 | "git add" 105 | ] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa'); 2 | const semver = require('semver'); 3 | const inquirer = require('inquirer'); 4 | 5 | const curVersion = require('../package').version; 6 | const bumps = ['patch', 'minor', 'major', 'prerelease', 'premajor']; 7 | 8 | const redTips = msg => 9 | // eslint-disable-next-line 10 | console.log('\x1b[1;31m' + msg + '\x1b[0m'); 11 | 12 | const versions = bumps.reduce((acc, val) => { 13 | acc[val] = semver.inc(curVersion, val); 14 | return acc; 15 | }, {}); 16 | 17 | const bumpChoices = bumps.reduce((acc, val) => { 18 | acc.push({ 19 | name: `${val} (${versions[val]})`, 20 | value: `${val}` 21 | }); 22 | return acc; 23 | }, []); 24 | 25 | async function release() { 26 | const { bump, customVersion, npmTag } = await inquirer.prompt([ 27 | { 28 | name: 'bump', 29 | message: 'Please choose the release type:', 30 | type: 'list', 31 | choices: [...bumpChoices, { name: 'custom', value: 'custom' }] 32 | }, 33 | { 34 | name: 'customVersion', 35 | message: 'Please input the version:', 36 | type: 'input', 37 | when: answers => answers.bump === 'custom' 38 | } 39 | ]); 40 | 41 | const curVersion = customVersion || versions[bump]; 42 | 43 | const { confirm } = await inquirer.prompt({ 44 | name: 'confirm', 45 | message: `Please confirm the version ${curVersion}`, 46 | type: 'list', 47 | choices: ['Y', 'N'] 48 | }); 49 | 50 | if (confirm === 'N') { 51 | redTips('exit publish'); 52 | return; 53 | } 54 | 55 | const promise = execa('bash', ['scripts/publish.sh', curVersion, npmTag]); 56 | promise.stdout.pipe(process.stdout); 57 | promise.stderr.pipe(process.stderr); 58 | // eslint-disable-next-line 59 | (async () => { 60 | try { 61 | const { stdout, stderr } = await promise; 62 | stdout && redTips(stdout); 63 | stderr && redTips(stderr); 64 | } catch (error) { 65 | redTips(error.stderr); 66 | } 67 | })(); 68 | } 69 | 70 | release(); 71 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # 带时间的日志输出 5 | log() { 6 | echo -e "\033[31m [$(date '+%Y-%m-%d %H:%M:%S')] $1 \033[0m" 7 | } 8 | 9 | version=$1 10 | 11 | log "cur version: $version" 12 | 13 | log "code review" 14 | yarn lint 15 | 16 | git add . 17 | 18 | if [[ `git status -s | grep -o -E ".*"` ]] 19 | then 20 | git commit -m "chore: 🤖 $version code" 21 | fi 22 | 23 | log "write version" 24 | npm version $version --message "$version" 25 | 26 | git tag -d "v${version}" 27 | 28 | log "changelog" 29 | yarn changelog 30 | git add . 31 | git commit -m "chore: 🤖 $version changelog" 32 | 33 | git tag "v${version}" 34 | 35 | log "publishing" 36 | npm publish 37 | git push 38 | git push origin v$version -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard'], 3 | rules: { 4 | 'rule-empty-line-before': 'never-multi-line' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /tests/dynamic.spec.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const VueRouterInvokeWebpackPlugin = require('../core'); 5 | const { makeFile, removeFile } = require('./utils'); 6 | function testPlugin(options, expectVal, notExpectVal) { 7 | webpack({ 8 | resolve: { 9 | alias: { 10 | '@': path.resolve(process.cwd(), 'tests') 11 | } 12 | }, 13 | plugins: [ 14 | new VueRouterInvokeWebpackPlugin( 15 | Object.assign({ routerDir: 'tests/dynamicT' }, options) 16 | ) 17 | ] 18 | }); 19 | if (expectVal || notExpectVal) { 20 | const isTs = options.language === 'typescript'; 21 | let file = fs.readFileSync( 22 | `tests/dynamicT/.invoke/router.${isTs ? 'ts' : 'js'}`, 23 | 'utf-8' 24 | ); 25 | file = file.replace(/\s/g, ''); 26 | if (expectVal) { 27 | expect(new RegExp(expectVal, 'i').test(file)).toBeTruthy(); 28 | } else { 29 | expect(new RegExp(notExpectVal, 'i').test(file)).toBeFalsy(); 30 | } 31 | } 32 | } 33 | 34 | describe('dynamicRoute', () => { 35 | it('hump name', () => { 36 | makeFile('dynamicT/_Dynamic/Index.vue'); 37 | testPlugin( 38 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 39 | `name\\:\\'dynamic\\',path\\:\\'\\/\\:dynamic\\'` 40 | ); 41 | removeFile('dynamicT'); 42 | }); 43 | 44 | it('yakitori name', () => { 45 | makeFile('dynamicT/_Dynamic-Name/Index.vue'); 46 | testPlugin( 47 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 48 | `name\\:\\'dynamicName\\',path\\:\\'\\/\\:dynamicName\\'` 49 | ); 50 | removeFile('dynamicT'); 51 | }); 52 | 53 | it('underlinename', () => { 54 | makeFile('dynamicT/_Dynamic_Name/Index.vue'); 55 | testPlugin( 56 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 57 | `name\\:\\'dynamicName\\',path\\:\\'\\/\\:dynamicName\\'` 58 | ); 59 | removeFile('dynamicT'); 60 | }); 61 | 62 | it('lowercase name', () => { 63 | makeFile('dynamicT/_dynamic_name/index.vue'); 64 | testPlugin( 65 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 66 | `name\\:\\'dynamicName\\',path\\:\\'\\/\\:dynamicName\\'` 67 | ); 68 | removeFile('dynamicT'); 69 | }); 70 | 71 | it('uppercase name', () => { 72 | makeFile('dynamicT/_DYNAMIC_NAME/INDEX.vue'); 73 | testPlugin( 74 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 75 | `name\\:\\'dynamicName\\',path\\:\\'\\/\\:dynamicName\\'` 76 | ); 77 | removeFile('dynamicT'); 78 | }); 79 | 80 | it('multiple', () => { 81 | makeFile('dynamicT/_dynamic_name/index.vue'); 82 | makeFile('dynamicT/_dynamic_name/_dynamic_inner/index.vue'); 83 | testPlugin( 84 | { dir: 'tests/dynamicT', alias: '@/dynamicT' }, 85 | `(name\\:\\'dynamicName\\',path\\:\\'\\/\\:dynamicName\\'|name\\:\\'dynamicName-dynamicInner\\',path\\:\\'\\/\\:dynamicName\\/\\:dynamicInner\\')` 86 | ); 87 | removeFile('dynamicT'); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /tests/ignore/Components/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/tests/ignore/Components/Index.vue -------------------------------------------------------------------------------- /tests/ignore/Images/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/tests/ignore/Images/Index.vue -------------------------------------------------------------------------------- /tests/ignore/Login/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/tests/ignore/Login/Index.vue -------------------------------------------------------------------------------- /tests/nest.spec.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const VueRouterInvokeWebpackPlugin = require('../core'); 5 | const { makeFile, removeFile } = require('./utils'); 6 | function testPlugin(options, expectVal, notExpectVal) { 7 | webpack({ 8 | resolve: { 9 | alias: { 10 | '@': path.resolve(process.cwd(), 'tests') 11 | } 12 | }, 13 | plugins: [ 14 | new VueRouterInvokeWebpackPlugin( 15 | Object.assign({ routerDir: 'tests/nestT' }, options) 16 | ) 17 | ] 18 | }); 19 | if (expectVal || notExpectVal) { 20 | const isTs = options.language === 'typescript'; 21 | let file = fs.readFileSync( 22 | `tests/nestT/.invoke/router.${isTs ? 'ts' : 'js'}`, 23 | 'utf-8' 24 | ); 25 | file = file.replace(/\s/g, ''); 26 | if (expectVal) { 27 | expect(new RegExp(expectVal, 'i').test(file)).toBeTruthy(); 28 | } else { 29 | expect(new RegExp(notExpectVal, 'i').test(file)).toBeFalsy(); 30 | } 31 | } 32 | } 33 | 34 | describe('nestRoute', () => { 35 | it('hump name', () => { 36 | makeFile('nestT/Parent/Parent.vue'); 37 | testPlugin( 38 | { dir: 'tests/nestT', alias: '@/nestT' }, 39 | `name\\:\\'parent\\',path\\:\\'\\/parent\\',children\\:\\[\\]` 40 | ); 41 | removeFile('nestT'); 42 | }); 43 | 44 | it('yakitori name', () => { 45 | makeFile('nestT/_Parent/_Parent.vue'); 46 | testPlugin( 47 | { dir: 'tests/nestT', alias: '@/nestT' }, 48 | `name\\:\\'parent\\',path\\:\\'\\/\\:parent\\',children\\:\\[\\]` 49 | ); 50 | removeFile('nestT'); 51 | }); 52 | 53 | it('underlinename', () => { 54 | makeFile('nestT/_Parent_Name/_Parent_Name.vue'); 55 | testPlugin( 56 | { dir: 'tests/nestT', alias: '@/nestT' }, 57 | `name\\:\\'parentName\\',path\\:\\'\\/\\:parentName\\',children\\:\\[\\]` 58 | ); 59 | removeFile('nestT'); 60 | }); 61 | 62 | it('lowercase name', () => { 63 | makeFile('nestT/_parent_name/_parent_name.vue'); 64 | testPlugin( 65 | { dir: 'tests/nestT', alias: '@/nestT' }, 66 | `name\\:\\'parentName\\',path\\:\\'\\/\\:parentName\\',children\\:\\[\\]` 67 | ); 68 | removeFile('nestT'); 69 | }); 70 | 71 | it('uppercase name', () => { 72 | makeFile('nestT/_PARENT_NAME/_PARENT_NAME.vue'); 73 | testPlugin( 74 | { dir: 'tests/nestT', alias: '@/nestT' }, 75 | `name\\:\\'parentName\\',path\\:\\'\\/\\:parentName\\',children\\:\\[\\]` 76 | ); 77 | removeFile('nestT'); 78 | }); 79 | 80 | it('multiple', () => { 81 | makeFile('nestT/_parent_name/_parent_name.vue'); 82 | makeFile('nestT/_parent_name/Child/Index.vue'); 83 | testPlugin( 84 | { dir: 'tests/nestT', alias: '@/nestT' }, 85 | `(name\\:\\'parentName\\',path\\:\\'\\/\\:parentName\\',children\\:\\[\\]|name\\:\\'parentName-child\\',path\\:\\'child\\')` 86 | ); 87 | removeFile('nestT'); 88 | }); 89 | 90 | it('mixed single and nested route', () => { 91 | makeFile('nestT/test/index.vue'); 92 | makeFile('nestT/test/test.vue'); 93 | expect(() => { 94 | testPlugin({ dir: 'tests/nestT', alias: '@/nestT' }); 95 | }).toThrow(); 96 | removeFile('nestT'); 97 | removeFile('nestT'); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /tests/option.spec.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const rimraf = require('rimraf'); 5 | const VueRouterInvokeWebpackPlugin = require('../core'); 6 | const { makeFile, removeFile, writeFile } = require('./utils'); 7 | function testPlugin(options, expectVal, notExpectVal) { 8 | webpack({ 9 | resolve: { 10 | alias: { 11 | '@': path.resolve(process.cwd(), 'tests') 12 | } 13 | }, 14 | plugins: [ 15 | new VueRouterInvokeWebpackPlugin( 16 | Object.assign({ routerDir: 'tests' }, options) 17 | ) 18 | ] 19 | }); 20 | if (expectVal || notExpectVal) { 21 | const isTs = options.language === 'typescript'; 22 | let file = fs.readFileSync( 23 | `tests/.invoke/router.${isTs ? 'ts' : 'js'}`, 24 | 'utf-8' 25 | ); 26 | file = file.replace(/\s/g, ''); 27 | if (expectVal) { 28 | expect(new RegExp(expectVal, 'i').test(file)).toBeTruthy(); 29 | } else { 30 | expect(new RegExp(notExpectVal, 'i').test(file)).toBeFalsy(); 31 | } 32 | } 33 | } 34 | 35 | describe('option', () => { 36 | it('create', () => { 37 | testPlugin({ 38 | dir: 'tests/single', 39 | alias: '@/single' 40 | }); 41 | }); 42 | 43 | it('must have dir', () => { 44 | expect(() => { 45 | testPlugin(); 46 | }).toThrow(); 47 | }); 48 | 49 | it('wrong empty dir', () => { 50 | expect(() => { 51 | testPlugin({ dir: 'tests/empty', alias: '@/empty' }); 52 | }).toThrow(); 53 | }); 54 | 55 | it('must have alias', () => { 56 | expect(() => { 57 | testPlugin({ dir: 'tests/single' }); 58 | }).toThrow(); 59 | }); 60 | 61 | it('mode should be hash or history', () => { 62 | expect(() => { 63 | testPlugin({ 64 | dir: 'tests/single', 65 | alias: '@/single', 66 | mode: 'test' 67 | }); 68 | }).toThrow(); 69 | }); 70 | 71 | it('default routerDir', () => { 72 | rimraf.sync(path.resolve(process.cwd(), '.invoke')); 73 | webpack({ 74 | resolve: { 75 | alias: { 76 | '@': path.resolve(process.cwd(), 'demos') 77 | } 78 | }, 79 | plugins: [ 80 | new VueRouterInvokeWebpackPlugin({ 81 | dir: 'demos/src', 82 | alias: '@/src', 83 | ignore: 'notfound' 84 | }) 85 | ] 86 | }); 87 | rimraf.sync(path.resolve(process.cwd(), '.invoke')); 88 | }); 89 | 90 | it('language should be javascript or typescript', () => { 91 | expect(() => { 92 | testPlugin({ 93 | dir: 'tests/single', 94 | alias: '@/single', 95 | language: 'test' 96 | }); 97 | }).toThrow(); 98 | }); 99 | 100 | it('javascript', () => { 101 | expect(() => { 102 | testPlugin({ 103 | dir: 'tests/single', 104 | alias: '@/single', 105 | language: 'javascript' 106 | }); 107 | }).not.toThrow(); 108 | }); 109 | 110 | it('typescript', () => { 111 | testPlugin( 112 | { 113 | dir: 'tests/single', 114 | alias: '@/single', 115 | language: 'typescript' 116 | }, 117 | '{RouteConfig}' 118 | ); 119 | }); 120 | 121 | it('meta', () => { 122 | removeFile('metaTest'); 123 | makeFile('metaTest/login/Index.vue'); 124 | makeFile('metaTest/login/meta.yml'); 125 | writeFile( 126 | 'metaTest/login/meta.yml', 127 | ` 128 | meta: 129 | - name: metaTest 130 | ` 131 | ); 132 | testPlugin( 133 | { dir: 'tests/metaTest', alias: '@/metaTest' }, 134 | `meta\\:\\{name\\:\\'metaTest\\'` 135 | ); 136 | removeFile('metaTest'); 137 | }); 138 | 139 | it('boolean smeta', () => { 140 | removeFile('metaTest'); 141 | makeFile('metaTest/login/Index.vue'); 142 | makeFile('metaTest/login/meta.yml'); 143 | writeFile( 144 | 'metaTest/login/meta.yml', 145 | ` 146 | meta: 147 | - name: true 148 | ` 149 | ); 150 | testPlugin( 151 | { dir: 'tests/metaTest', alias: '@/metaTest' }, 152 | `meta\\:\\{name\\:true` 153 | ); 154 | removeFile('metaTest'); 155 | }); 156 | 157 | it('redirect', () => { 158 | removeFile('metaTest'); 159 | makeFile('metaTest/login/Index.vue'); 160 | makeFile('metaTest/login/meta.yml'); 161 | writeFile( 162 | 'metaTest/login/meta.yml', 163 | ` 164 | redirect: 165 | - path: /test 166 | ` 167 | ); 168 | testPlugin( 169 | { dir: 'tests/metaTest', alias: '@/metaTest' }, 170 | `redirect\\:\\{path\\:\\'/test\\'` 171 | ); 172 | removeFile('metaTest'); 173 | }); 174 | 175 | it('empty meta', () => { 176 | removeFile('metaTest'); 177 | makeFile('metaTest/home/home.vue'); 178 | makeFile('metaTest/home/login/index.vue'); 179 | makeFile('metaTest/home/login/meta.yml'); 180 | testPlugin( 181 | { dir: 'tests/metaTest', alias: '@/metaTest' }, 182 | '', 183 | `meta\\:\\{name\\:\\'metaTest\\'` 184 | ); 185 | removeFile('metaTest'); 186 | }); 187 | 188 | it('multiple meta', () => { 189 | removeFile('metaTest'); 190 | makeFile('metaTest/home/home.vue'); 191 | makeFile('metaTest/home/inner/inner.vue'); 192 | makeFile('metaTest/home/inner/test/index.vue'); 193 | makeFile('metaTest/home/inner/testt/index.vue'); 194 | makeFile('metaTest/home/inner/meta.yml'); 195 | testPlugin( 196 | { dir: 'tests/metaTest', alias: '@/metaTest' }, 197 | '', 198 | `meta\\:\\{name\\:\\'metaTest\\'` 199 | ); 200 | removeFile('metaTest'); 201 | }); 202 | 203 | it('ignore', () => { 204 | testPlugin( 205 | { 206 | dir: 'tests/ignore', 207 | alias: '@/ignore', 208 | ignore: ['images', 'components'] 209 | }, 210 | '', 211 | 'images|components' 212 | ); 213 | }); 214 | 215 | it('redirect', () => { 216 | testPlugin( 217 | { 218 | dir: 'tests/single', 219 | alias: '@/single', 220 | redirect: [ 221 | { 222 | path: '/', 223 | redirect: '/home' 224 | } 225 | ] 226 | }, 227 | `redirect\\:\\'\\/home\\'` 228 | ); 229 | }); 230 | 231 | it('notFound', () => { 232 | testPlugin( 233 | { 234 | dir: 'tests/single', 235 | alias: '@/single', 236 | redirect: [ 237 | { 238 | path: '/', 239 | redirect: '/home' 240 | } 241 | ], 242 | ignore: ['NotFound.vue'], 243 | notFound: '@/single/NotFound.vue' 244 | }, 245 | `name\\:\\'notFound\\',path\\:\\'\\*\\'` 246 | ); 247 | }); 248 | 249 | it('modules', () => { 250 | testPlugin( 251 | { 252 | dir: 'tests/single', 253 | alias: '@/single', 254 | modules: [ 255 | { 256 | name: 'some', 257 | package: 'Some' 258 | } 259 | ] 260 | }, 261 | `importsomefrom\\'Some\\'` 262 | ); 263 | }); 264 | 265 | it('scrollBehavior', () => { 266 | testPlugin( 267 | { 268 | dir: 'tests/single', 269 | alias: '@/single', 270 | scrollBehavior: (to, from, savedPosition) => { 271 | if (savedPosition) { 272 | return savedPosition; 273 | } else { 274 | return { x: 0, y: 0 }; 275 | } 276 | } 277 | }, 278 | 'scrollBehavior:\\(to,from,savedPosition\\)' 279 | ); 280 | }); 281 | 282 | it('beforeEach', () => { 283 | testPlugin( 284 | { 285 | dir: 'tests/single', 286 | alias: '@/single', 287 | beforeEach: (to, from, next) => { 288 | next(); 289 | } 290 | }, 291 | 'beforeEach\\(\\(to,from,next\\)' 292 | ); 293 | }); 294 | 295 | it('beforeResolve', () => { 296 | testPlugin( 297 | { 298 | dir: 'tests/single', 299 | alias: '@/single', 300 | beforeResolve: (to, from, next) => { 301 | next(); 302 | } 303 | }, 304 | 'beforeResolve\\(\\(to,from,next\\)' 305 | ); 306 | }); 307 | 308 | it('afterEach', () => { 309 | testPlugin( 310 | { 311 | dir: 'tests/single', 312 | alias: '@/single', 313 | afterEach: (to, from) => {} 314 | }, 315 | 'afterEach\\(\\(to,from\\)' 316 | ); 317 | }); 318 | 319 | it('watchFiles', done => { 320 | process.env.NODE_ENV = 'development'; 321 | testPlugin({ 322 | dir: 'tests/single', 323 | alias: '@/single' 324 | }); 325 | process.env.NODE_ENV = 'test'; 326 | removeFile('single/watch'); 327 | setTimeout(() => { 328 | makeFile('single/watch/Index.vue'); 329 | setTimeout(() => { 330 | let file = fs.readFileSync('tests/.invoke/router.js', 'utf-8'); 331 | file = file.replace(/\s/g, ''); 332 | expect( 333 | new RegExp(`name\\:\\'watch\\',path\\:\\'\\/watch\\'`).test(file) 334 | ); 335 | removeFile('single/watch'); 336 | done(); 337 | }, 1000); 338 | }, 100); 339 | }); 340 | }); 341 | -------------------------------------------------------------------------------- /tests/single.spec.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const VueRouterInvokeWebpackPlugin = require('../core'); 5 | const { makeFile, removeFile } = require('./utils'); 6 | function testPlugin(options, expectVal, notExpectVal) { 7 | webpack({ 8 | resolve: { 9 | alias: { 10 | '@': path.resolve(process.cwd(), 'tests') 11 | } 12 | }, 13 | plugins: [ 14 | new VueRouterInvokeWebpackPlugin( 15 | Object.assign({ routerDir: 'tests/singleT' }, options) 16 | ) 17 | ] 18 | }); 19 | if (expectVal || notExpectVal) { 20 | const isTs = options.language === 'typescript'; 21 | let file = fs.readFileSync( 22 | `tests/singleT/.invoke/router.${isTs ? 'ts' : 'js'}`, 23 | 'utf-8' 24 | ); 25 | file = file.replace(/\s/g, ''); 26 | if (expectVal) { 27 | expect(new RegExp(expectVal, 'i').test(file)).toBeTruthy(); 28 | } else { 29 | expect(new RegExp(notExpectVal, 'i').test(file)).toBeFalsy(); 30 | } 31 | } 32 | } 33 | 34 | describe('singleRoute', () => { 35 | it('root wrong name', () => { 36 | makeFile('singleT/Login.vue'); 37 | expect(() => { 38 | testPlugin({ dir: 'tests/singleT', alias: '@/singleT' }); 39 | }).toThrow(); 40 | removeFile('singleT/Login.vue'); 41 | }); 42 | 43 | it('child wrong name', () => { 44 | makeFile('singleT/Child/Login.vue'); 45 | expect(() => { 46 | testPlugin({ dir: 'tests/singleT', alias: '@/singleT' }); 47 | }).toThrow(); 48 | removeFile('singleT/Child/Login.vue'); 49 | }); 50 | 51 | it('hump name', () => { 52 | makeFile('singleT/Login/Index.vue'); 53 | testPlugin( 54 | { dir: 'tests/singleT', alias: '@/singleT' }, 55 | `name\\:\\'login\\',path\\:\\'\\/login\\'` 56 | ); 57 | removeFile('singleT'); 58 | }); 59 | 60 | it('homepage', () => { 61 | makeFile('singleT/Login/Index.vue'); 62 | makeFile('singleT/Index.vue'); 63 | testPlugin( 64 | { dir: 'tests/singleT', alias: '@/singleT' }, 65 | `name\\:\\'index\\',path\\:\\'\\/\\'` 66 | ); 67 | removeFile('singleT'); 68 | }); 69 | 70 | it('yakitori name', () => { 71 | makeFile('singleT/Login-Name/Index.vue'); 72 | testPlugin( 73 | { dir: 'tests/singleT', alias: '@/singleT' }, 74 | `name\\:\\'loginName\\',path\\:\\'\\/loginName\\'` 75 | ); 76 | removeFile('singleT'); 77 | }); 78 | 79 | it('underlinename', () => { 80 | makeFile('singleT/Login_Name/Index.vue'); 81 | testPlugin( 82 | { dir: 'tests/singleT', alias: '@/singleT' }, 83 | `name\\:\\'loginName\\',path\\:\\'\\/loginName\\'` 84 | ); 85 | removeFile('singleT'); 86 | }); 87 | 88 | it('lowercase name', () => { 89 | makeFile('singleT/login_name/index.vue'); 90 | testPlugin( 91 | { dir: 'tests/singleT', alias: '@/singleT' }, 92 | `name\\:\\'loginName\\',path\\:\\'\\/loginName\\'` 93 | ); 94 | removeFile('singleT'); 95 | }); 96 | 97 | it('uppercase name', () => { 98 | makeFile('singleT/LOGIN_NAME/INDEX.vue'); 99 | testPlugin( 100 | { dir: 'tests/singleT', alias: '@/singleT' }, 101 | `name\\:\\'loginName\\',path\\:\\'\\/loginName\\'` 102 | ); 103 | removeFile('singleT'); 104 | }); 105 | 106 | it('multiple', () => { 107 | makeFile('singleT/login_name/index.vue'); 108 | makeFile('singleT/login_name/login_inner/index.vue'); 109 | testPlugin( 110 | { dir: 'tests/singleT', alias: '@/singleT' }, 111 | `(name\\:\\'loginName\\',path\\:\\'\\/loginName\\'|name\\:\\'loginName-loginInner\\',path\\:\\'\\/loginName\\/loginInner\\')` 112 | ); 113 | removeFile('singleT'); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /tests/single/Login/Index.vue: -------------------------------------------------------------------------------- 1 | 456 -------------------------------------------------------------------------------- /tests/single/User/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qymh/vue-router-invoke-webpack-plugin/0faf50ee34364301591cab6532619f5041b4c252/tests/single/User/Index.vue -------------------------------------------------------------------------------- /tests/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fse = require('fs-extra'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const tests = path.resolve(process.cwd(), 'tests'); 6 | 7 | exports.makeFile = str => { 8 | str = str.split('/'); 9 | if (!str.length) { 10 | return; 11 | } 12 | const file = str.pop(); 13 | const dir = str.join('/'); 14 | fse.ensureDirSync(path.resolve(tests, dir)); 15 | fs.writeFileSync(path.resolve(tests, dir, file), ''); 16 | }; 17 | 18 | exports.writeFile = (file, str) => { 19 | fs.writeFileSync(path.resolve(tests, file), str); 20 | }; 21 | 22 | exports.removeFile = str => { 23 | fse.removeSync(path.resolve(tests, str)); 24 | }; 25 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { VueRouterInvokeWebpackPluginOptions } from './options'; 2 | 3 | declare class VueRouterInvokeWebpackPlugin { 4 | constructor(options: VueRouterInvokeWebpackPluginOptions); 5 | } 6 | 7 | export = VueRouterInvokeWebpackPlugin; 8 | -------------------------------------------------------------------------------- /types/options.d.ts: -------------------------------------------------------------------------------- 1 | import { Route, NavigationGuard } from 'vue-router'; 2 | import Vue from 'vue'; 3 | 4 | type mode = 'hash' | 'history'; 5 | type language = 'javascript' | 'typescript'; 6 | type Position = { x: number; y: number }; 7 | type PositionResult = Position | { selector: string; offset?: Position } | void; 8 | 9 | export interface VueRouterInvokeWebpackPluginOptions { 10 | dir: string; 11 | alias: string; 12 | notFound?: string; 13 | mode?: mode; 14 | meta?: string; 15 | routerDir?: string; 16 | language?: language; 17 | ignore?: string[]; 18 | redirect?: string[]; 19 | modules?: string[]; 20 | scrollBehavior?: ( 21 | to: Route, 22 | from: Route, 23 | savedPosition: Position | void 24 | ) => PositionResult | Promise; 25 | beforeEach?: (guard: NavigationGuard) => void; 26 | beforeResolve?: (guard: NavigationGuard) => void; 27 | afterEach?: (to: Route, from: Route) => void; 28 | } 29 | --------------------------------------------------------------------------------