Vue路由自動注入實踐

什麼是路由自動注入

路由自動注入概念學習自nuxt,咱們不須要在router.js中每次手動輸入代碼引入模塊而是自動根據文件目錄格式生成router.jsjavascript

咱們把這個功能獨立成一個webpack插件,並對相關功能進行了完善,並且實現了vue-router的全部核心功能前端

更詳細使用指南和文檔能夠查看咱們的github倉庫vue

舉一個簡單的列子,好比你的目錄長這樣java

src
├── views
│   ├── Login
│   │   └── Index.vue
│   └── User
│       ├── Account
│       │   └── Index.vue
│       ├── Home
│       │   └── Index.vue
│       └── Index.vue
複製代碼

規則很簡單,若是目錄的一層是Index.vue,則目錄名即是當前的路由名字,若是是子文件夾則是第二層路由,以後自動生成的router.js會長成這樣node

{
  component: () =>
    import('@/views/Login/Index.vue'),
  name: 'login',
  path: '/login'
},
{
  component: () =>
    import('@/views/User/Index.vue'),
  name: 'user',
  path: '/user'
},
{
  component: () =>
    import('@/views/User/Account/Index.vue'),
  name: 'user-account',
  path: '/user/account'
},
{
  component: () =>
    import('@/views/User/Home/Index.vue'),
  name: 'user-home',
  path: '/user/home'
}
複製代碼

這裏值得一提的是其實生成的router.js是沒有必要加入到版本控制當中的,由於不論在開發(development)仍是生產(production)第一次構建項目都會自動生成,好比你項目用到了giteslint,那麼應該把它放在.gitignore.eslintignorewebpack

爲何使用路由自動注入

  • 方便

不用每次去引用模塊,只用建立文件夾,router.js會自動生成git

  • 統一路由命名

若是有完整的code review這個問題是不會存在的,但咱們稍微作了一點簡便,只要code review文件夾的命名就行了,最終生成的路由path會以駝峯命名,生成的name會以駝峯命名而且以連字符-鏈接不一樣層級的路由github

  • 統一路由層級

如圖片中的列子,咱們沒法從文件的命名去判斷路由到底在幾級,並且常常寫的時候,明明是2級或3級路由卻和1級路由在一層路由下,這是很不規範並且與邏輯不符的web

對比一下使用自動注入劃分層級後的路由vue-router

src/views
├── Index.vue
├── NotFound.vue
├── Withdraw
    <!-- 第一級 -->
│   ├── Index.vue
│   └── Result
│       ├── Description
            <!-- 第三級 -->
│       │   └── Index.vue
        <!-- 第二級 -->
│       └── Index.vue
└── WithdrawHistory
    <!-- 第一級 -->
    └── Index.vue
複製代碼

能夠從目錄結構看出路由的層級

咱們再來看看生成的路由,不一樣層級的路由名字經過連字符-鏈接,層級很清晰

{
    component: () => import('@/views/Withdraw/Index.vue'),
    name: 'withdraw',
    path: '/withdraw'
},
{
    component: () => import('@/views/Withdraw/Result/Index.vue'),
    name: 'withdraw-result',
    path: '/withdraw/result'
},
{
    component: () => import('@/views/Withdraw/Result/Description/Index.vue'),
    name: 'withdraw-result-description',
    path: '/withdraw/result/description'
},
{
    component: () => import('@/views/WithdrawHistory/Index.vue'),
    name: 'withdrawHistory',
    path: '/withdrawHistory'
},
複製代碼

爲何選擇vue-router-invoke-webpack-plugin

  • 完善的單元測試

  • types支持

vue-router-invoke-webpack-plugin中獨特的路由劃分思惟

當咱們的頁面過多的時候,好比項目有60多個甚至70多個單頁面,文件不可能會放在一個目錄下,通常這種時候,咱們會按功能將類似功能的路由放在一個目錄下,咱們以前也是這麼作的,其實這麼作也是沒啥問題的,但在路由自動注入下,咱們提出了另一種思路按路由層級劃分

什麼是層級劃分呢,簡單的一句話就是根據頁面所在的相對url地址進行劃分,舉個列子,咱們的首頁以下

首頁的路由爲/,咱們把首頁看成根路由,那麼能夠進入的一級路由分別爲提現 提現記錄 分紅數據等,點擊提現後,咱們進入了提現路由/withdraw

進入提現頁面後,會有兩處可點擊,這兩處即是二級頁面,放在一級頁面的子文件夾中,按剛纔的說法,路由目錄(截取部分)即是這樣

src/views
├── Bank
    <!-- 銀行卡管理 -->
│   └── Index.vue
├── DivideData
    <!-- 分紅數據 -->
│   └── Index.vue
<!- 首頁 --->
├── Index.vue
<!-- 404路由 -->
├── NotFound.vue
├── Withdraw
│   ├── BankDetails
        <!-- 提現中查看銀行卡信息 -->
│   │   └── Index.vue
│   ├── Description
        <!-- 提現說明 -->
│   │   └── Index.vue
    <!-- 提現頁面 -->
│   └── Index.vue
└── WithdrawHistory
     <!--提現記錄 -->
    └── Index.vue
複製代碼

其實通常這麼分下來,類似功能的是會在一個文件夾下面的,也實現了按功能分路由的思路,並且這種層級劃分是一目瞭然的,很容易能夠看出路由的從屬關係

但有時候也會遇到一個麻煩,就是有些頁面可能出如今當前層級下面,也可能出如今另一個層級下面,按功能分的時候也有這種,就是功能可能存在於兩個功能點之間,這種狀況其實能夠考慮下在哪一個層級的權重重一點或者從用戶的點擊習慣考慮,哪一個位置進去會多一點就放在哪一個層級下面

vue-router-invoke-webpack-plugin中獨特的文件結構

也許你們會有疑問,爲啥非要寫成Index.vue並多加一層文件夾封裝,直接命名vue文件很差嗎,用過nuxt的同窗可能也會感受到這一點的區別,這也是咱們在nuxt的基礎上增長的一個feature,爲了更友好的封裝一個單頁面

舉個列子,若是你的項目沒有引用ui庫,不少業務組件須要本身寫,除了經常使用的組件會放在目錄最外面的components文件,其他的對應一個單頁面的業務組件你會放在哪裏呢,這就是咱們預留的位置,好比一個目錄結構以下

src/views
├── Audit
│   ├── Index.vue
│   ├── components
│   │   └── AuditItem.vue
│   └── images
│       └── AuditIntro.png
複製代碼

Audit是咱們的審批頁面,其中用到了一個只有當前頁面所用的AuditItem.vue組件,也引用了一個只有當前頁面所用到的圖片AuditIntro.png,獨特的文件結構就是爲了這種需求而生的,當前頁面的組件圖片放在一個文件夾中會更清晰,但值得一提的是,你也須要在插件中設置ignore去忽略掉不被咱們解析的目錄,好比這樣

plugins: [
  new VueRouterInvokeWebpackPlugin({
    dir: 'src/views',
    alias: '@/views',
    language: 'javascript',
    ignore: ['images', 'components', 'template.vue']
  })
];
複製代碼

那麼 images components template.vue 會被忽略不解析

聊一聊路由權限控制

關於前端控制路由權限,前段時間看到過一個文章,感受實現思路稍微複雜了點,其實有一個比較簡單的思路,就是後端給定當前用戶沒有權限的路由,而後前端在beforeEach鉤子中去匹配,若是匹配到沒有權限則直接跳404或者沒有權限的頁面就好了,若是用vue-router-invoke-webpack-plugin寫會這麼寫

apis.getForbiddenRoute

export default {
  // 請求當前沒有權限的路由列表
  async getForbiddenRoute() {
    return ['/single/user'];
  }
};
複製代碼
plugins: [
    new VueRouterInvokePlugin({
      // 觀察的目錄
      dir: 'demos/src',
      // 觀察目錄的別名
      alias: '@/src',
      // 當前語言
      language: 'javascript',
      // 生成router.js的位置
      routerDir: 'demos',
      // 忽略文件夾
      ignore: ['images', 'template.vue', 'components', 'notfound.vue'],
      // 404路由地址
      notFound: '@/src/NotFound.vue',
      // 引用的模塊
      modules: [
        {
          name: 'apis',
          package: '@/apis'
        }
      ],
      // 同scrollBehavior
      scrollBehavior: (to, from, savedPosition) => {
        if (savedPosition) {
          return savedPosition;
        } else {
          return { x: 0, y: 0 };
        }
      },
      <!-- 主要是這段代碼 -->
      /* eslint-disable */
      beforeEach: async (to, from, next) => {
        // 經過綁定在靜態屬性上的_cachedForbiddenRoute判斷是否請求過接口
        if (!Vue._cachedForbiddenRoute) {
          Vue._cachedForbiddenRoute = [];
          await apis.getForbiddenRoute().then(res => {
            Vue._cachedForbiddenRoute = res;
          });
        }
        // 噹噹前頁面的地址存在於禁止訪問的列表中,則直接跳轉到404頁面
        if (Vue._cachedForbiddenRoute.includes(to.path)) {
          next({
            name: 'notFound'
          });
        } else {
          next();
        }
      }
    }),
]
複製代碼

但話說回來,任何實現思路,前端獲取的接口數據想篡改仍是能繞過去的,因此仍是得後端再防一層

項目實現思路

項目實現不太複雜,但要照顧到的地方不少

  • 基本路由
  • 動態路由
  • 多層嵌套路由
  • 多層嵌套動態路由
  • meta替代品
  • 文件不符合規則的友好處理
  • 命名轉換統一
  • node中原生fs模塊十分不友好

要考慮的小細節還挺多的,特別是當路由過於複雜的狀況

但node的fs的坑點是我沒有想到的,特別是在跨平臺上,因此咱們捨棄了使用原生的fs模塊,用chokidarfs-extra替代了fs的部分功能

前段時間也在學習vue的ast語法樹,因此學習了下思路去嘗試構建一棵ast,不過方法仍是有區別的,vue構建語法樹是經過正則拆分了元素開始標籤 元素屬性 元素字符 元素結束標籤等而後拼接而成的,拼接的過程特別複雜,這個項目會簡單不少,直接經過文件讀取遞歸遍歷目錄就能夠生成一棵ast

而後經過語法樹去構建字符串的router.js,構建的過程還比較麻煩,最後將構建好的字符串寫入文件就大功告成了

項目還須要完善的地方

  • 單元測試

如今的單元測試覆蓋率已經100%了,但我以爲仍然有比較多稍微複雜的狀況沒有寫到,以後會不只看單元測試覆蓋率,而是按想到須要測試得功能點去補充完整

  • 測試環境

項目接入的是circleci,無法在windows下測試,日常用的開發環境也是mac,因此測試環境方面以後還要去研究研究其餘能夠支持windows的ci工具,並對不一樣node版本進行測試

其實如今在windows下也有一個bug,但我發現nuxt也有這個bug,因此感受可能這不是一個bug或許是一個feature,以後也會去提一個issue去請教一下,也不知道是否是我電腦的問題,簡單說就是fs.watch去監聽文件目錄的時候(但這裏其實用的是chokidar,不過都同樣)當去改變以前已有的文件目錄的名字是改不了的,windows下會提示你什麼當前文件被引用了,須要結束掉進程這個文件名才能被修改

  • 更友好的支持

項目目前支持的是node版本> 8.15.1,僅支持webpack4,以後會支持webpack3和即將到來的webpack5

2019-04-19 15:41:31 版本>0.2.5

已支持webpack3

更多的功能

除了剛纔提到的一個簡單路由的列子和設置忽略項,咱們還支持了vue-router的其餘核心功能,包括動態路由 嵌套路由 全局路由守衛 meta替代品 等其餘功能,相關功能點都寫在了咱們開源倉庫的文檔中,詳細的用法和注意事項,能夠訪問咱們的github倉庫,若是以爲項目還不錯的話,能夠給咱們點一顆小星星,固然若是你在使用中發現了和預期不太同樣的狀況或者bug能夠隨時給咱們提issue

相關文章
相關標籤/搜索