vue-router無法實現按需載入動態組件,有什麼替代方案可以解決這一需求呢?

用戶登錄以後,從後台獲取該用戶的菜單許可權,每個菜單項都對應一個頁面組件。現在想根據菜單樹動態生成路由,然後懶載入對應的組件。

非同步載入可以用webpack的require.ensure:

{
path: "/Test",
name: "Test",
component: resolve =&> {
require.ensure(["components/Test"], () =&> {
resolve(require("components/Test"))
})
}
}

但是這裡不允許出現變數,只能是靜態字元串,所以我甚至連將component這段冗長的代碼抽取成一個方法都做不到。

之前找了很多地方,好像vue-router作者也不建議這種實現方式,那麼這是否意味著這種需求不該並且無法實現?在這種前提下,我是不是應該斷了動態載入這份念想,只能老老實實在資料庫菜單表和router.js里同時維護一份菜單頁面組件列表?比如現在有100個菜單對應的頁面,我就需要將上面這段代碼重複100遍?

vue初入坑,求解答,十分感謝!


Webpack 有 context 這個功能,require.ensure 的路徑只要不是完全動態,比如有一個靜態的根目錄的話,它會自動處理該目錄下的所有文件。

具體實現可能還會有些細節,但理論上是可以做到的。


可以定義不同角色,每個角色菜單不一樣,

用路由對應,/rolea,/roleb,/rolec

分別載入3個組件。組件裡面再用路由,如/rolea/modela。


update

我使用的是vue1.2,今天剛好做了套組件測試,實現了按需載入組件
1.配置路由
"test/:id" : "test/Main"
test/Main.js是測試入口,後邊的參數是需要測試的組件
如:實際路由 /#!/test/common.formComponents.Form,說明需要動態載入common.formComponents.Form.js並掛載到界面
2.組件模板提供個掛載位

&123&

3.動態掛載

this.file : this.$route.params.id
require(["./"+this.file],function(Component){
new Component().$mount(this.$els.main)
}.bind(this))

==============================

你好,我處理過類似需求。具體工作是分幾步進行的。看你的描述應該是個後台管理應用,跟我的比較相似,我來介紹一下自己的實現方式。
1.首先將許可權從前端解耦出去。
我們的所有界面都是由組件嵌套起來的,組件的顯示取決於返回的數據。那麼,我只要要求後端自行處理好鑒權後的數據給我,我就不需要關心這個問題了。

2.簡化路由配置的語法
vue-router的語法比較啰嗦,我們完全可以將其簡化,再寫一個方法將我們的簡化版路由錶轉換為vue-router需要的格式
一般來講,我們是這樣寫路由表的

var routeMap = {
「/ 」 : "./home/Home",
"module1" : {
"url" : "module1/Home",
"children" : {
"/add/element/:id" : "module1/xxxxxx",
"/modify/element/:id" : "module1/xxxxxx"
}
}
}

然後寫一個方法來轉換一下

utils.convertRouteMap = function(map){
var route = {}
Object.keys(map).forEach(function(key){
var modules = map[key]
if(typeof modules === "string"){
route[key] = {component:getPromisedComponent(modules)}
return false
}
if(typeof modules !== "object")return false
route[key] = {
component : null,
subRoutes : {}
}
if(modules.url){
route[key].component = getPromisedComponent(modules.url)
route[key].subRoutes = utils.convertRouteMap(modules.children)
}else{
route[key].component = utils.component.CustomSubRouter
route[key].subRoutes = utils.convertRouteMap(modules)
}
})
function getPromisedComponent(url){
return function(resolve,reject){
require([url],resolve)
}
}
return route
}

最終啟動應用

function start(routeMap){
router.map(utils.convertRouteMap(routeMap))
router.redirect({
"*" : "/Error/404"
})
router.start(App,"#app")
}

3.動態註冊路由

我們的系統,由於基礎功能都比較相似,但是不同客戶之間業務差異較大,我們就把系統進行了拆分。將基礎功能打包為一個基礎版本,業務模塊以擴展包的形式注入。
我們用一個字典來維護擴展模塊,約定每個擴展模塊的根目錄下有一個package.json,內容為模塊的描述和路由。
在應用啟動前,我們請求一下字典,然後按之前約定的規則讀出所有package.json,將路由合併進來,轉換一下路徑,再轉換為vue-router格式,啟動應用。
總的來說,我們的路由還是在前端寫死,只是以模塊為單位組織,最終在應用啟動前合併起來。
具體代碼較多就不貼了。

4.菜單
我們的菜單是一個獨立模塊,獲取到的是後端經過許可權過濾後的菜單項,作用僅僅是將用戶導航到某一個具體業務去,而業務本身也是經過介面過濾的,只返回許可權允許的數據。所以即使你猜路徑也拿不到不該看的數據。

我對組件進行了一個簡單的分類
可以掛載到路由上的組件,我把它稱為頁面級組件,此類組件可以向根組件發一個通知,用來修改頁面標題等全局屬性
其他組件就是普通的vue組件了,用來抽象一些局部的交互

總體來講,就是:
1.嚴格隔離前後端職責,前端完全不應該關心用戶,許可權這類較敏感的信息
2.將業務拆分成模塊

簡單介紹一下我們的開發模式:
我們的業務,是向20多個省的客戶供應業務管理系統,這些客戶大體的業務流程都是相似的,但是又有不同。
我們的供應模式是先開發好基礎版本業務,再由各省本地的分公司按該省客戶要求定製業務。
由於我們本地分公司的開發人員普遍都是偏業務的,技術水準較低,我們要做到方便二次開發。所以我們使用新技術是比較謹慎的。
在以往,我們的前端都必須自己部署java環境,在java環境下做開發。經過多年努力,基本實現了全部介面json化。
剛好在前年時有移動端h5的研發需求,我就趁機來了個全套,先用nodejs開發了一套通用代理,實現帶鑒權跨域調用java介面,然後前端上了vue,將業務拆分為組件,組件內調用業務介面。
目前我們的前端開發,只需要部署我的nodejs環境,然後將svn的前端目錄導出到node環境下開發,發布時維護人員直接從svncheckout,就可以實現無縫過渡。
經過一個項目的實踐,我們發現我的方案不但開發效率提升了一個數量級,而且技術門檻相當的低。招進來新人,基本上培訓半小時他就可以寫東西了。但是由於考慮到我們的代碼交付後還要由現場人員維護,我們還是在es3的基礎上打了一些es5的polyfill,沒有上編譯系統。

開發效率真的是數量級的提升,比如我現在,我一個前端跟3個後端搭夥搞一套應用,我做的太快,等介面無聊,就寫了以上的東西
技術太低端,手動匿個名


最近正遇到同樣或者說類似的問題,正在做調研,還沒有解決方案。下面我會把我的一些階段性結論和思路說一下,如果題主 or 別人有興趣,我會在找到解決方案後更新。

零、背景:

我們的需求是這樣的,現在想用 vue 做基礎框架搭建一個完整的網站,複雜度可能如知乎。

我們做了前後端分離,因此前端會更像單頁應用(比如需要前端路由)。然而在網站具有一定複雜性的前提下,直接用單頁應用的方式打包成一個文件,會導致載入速度慘不忍睹。因此需要每個頁面分開打包。

此時就會遇到題主描述的問題,webpack 的 require.ensure 不支持動態,即使能夠打包一個目錄,也無法按照我的需求分開打包。我也不想維護一個巨長的目錄文件,怎麼辦呢?

一、階段性結論

在進行了一些嘗試和調研以後,目前認為 webpack require.ensure 的問題無法解決。這對於使用 vue-router 是一個大坑。

除此之外,router-link 自定義程度低、router 只能使用組件的方式不能使用實例的方式、許可權的限制(如有些頁面可以不登錄查看),等等問題都是一些小坑。

在大小坑都有的情況下,我目前不確定使用 vue-router 到底是不是一個好方案。

想起之前我用過的 angular 路由(太久遠記不清了,也可能是 ionic),是可以實現需要的文件分別請求的,雖然我也知道那個時候我沒有用到打包,但這也給了另一個思路:使用其它路由。

二、可能的方案

1. 預處理 json。

將目錄文件必要的部分提取成 json,然後在整個打包開始之前,利用 gulp/grunt 的自動化,將這一段 json 變成 webpack 能支持的 js 代碼。

這種方案的好處是跟官方文檔差異最小。壞處是源代碼與生成的代碼傻傻分不清楚。

2. jsonp。

使用 jsonp 或者別的方式,代替 webpack 的 require.ensure 來載入動態組件。

3. 使用其它路由。

這個就不好講了,因為我別的路由都沒怎麼用過,需要的調研成本很高。

4. 自己寫路由。

好處是輕鬆自定義,並且一定能實現,一定是適合自己產品的方案。壞處是不知道怎麼樣最好,以及需要造輪子,實現很多其他人已經實現過的功能。

三、最後

目前想到的就這些,我還在做調研。更不更新看情況。

另外,當然也希望 @尤雨溪 大大這種老司機能夠有成熟的方案提供給我們,我不是伸手黨啦,畢竟我也是可以自己寫一個以後也會開源的。但個人經驗確實有限,目前寫的整個框架都比較定製化,我們的產品使用還好,用到別人的產品可能就需要完全按照我們的思路來,配置很多東西。這樣能夠幫助到的人就是少數了,遠遠比不上一套成熟的方案要好。


沒有遇到過這種需求,以下只是一種思路:

1.將所有可能需要載入的組件配置好路由

2.運行時 路由 跳轉 如 go()方法等 裡面傳入參數 指定不同的name 或者path 來達到 非同步並且動態的結果


在編譯階段處理。做好約定。 通過node腳本自動生成router 配置或菜單配置。 也不失為一種解決方案。


目前自己用非同步載入,利用vuex保存數據,然後使用Vue-router 裡面的addRoutes 增加的路由,可以解決。還有require.ensure裡面的require不可以用變數,使用了拼接方法可以解決

childComponentHander (path) {
return resolve =&> {
require.ensure([], (require) =&> {
resolve(require("@/views/" + path + ".vue"))
})
}
}


直接用 webpack 的 import() 就可以了,也就是代碼分離。準確的說,題主說的是代碼分離的第二種形式,而 import() 是第一種形式,並且是推薦的。


這是我們項目中的寫法,題注參考一下:

不過這個是 Webpack 1 的,不知道 2 內是否生效.

// router.ts
import * as VueRouter from "vue-router"

const routerConfig = [
{ path: "/", alias: "/greeting", componentPath: "./components/greeting/index", component: null },
{ path: "/main", componentPath: "./components/main/index", component: null },
{...}
]

const router = new VueRouter({
mode: "history",
routes: routerConfig.map(item =&> {
if (item.componentPath) {
item.component = resolve =&> require([item.componentPath + ".vue"], resolve) // 請注意路徑的寫法.
}
return item
})
})

export default router


前端渲染的框架,不知道許可權管理怎樣做最合適?


這是前端webpack打包後代碼動態化的問題,記住,webpack是一個靜態打包工具,可以結合systemjs來做。先用webpack打包出特定的組件代碼,以libraryTarget輸出,然後在需要調用的地方用systemjs載入進來。


不請自來。

我最近也遇到了類似的需求,我的解決方案是:把所有的頁面的載入方法都放到一個文件里,或者一組文件里,然後根據定義一個命名規範,比如說 模塊名/頁面名 或者用多層對象嵌套,然後添加一個獲取頁面的方法,調用獲取頁面方法傳入的參數就是你的動態頁面地址。

至於許可權,可以在獲取頁面的方法里進行校驗,可以每次都校驗,也可以一次性載入所有有許可權的地址進行校驗。

至於請求的安全性,我是交給後台去做,規範化一個功能所有請求地址的格式,然後進行動態許可權載入,有需求的話可以配備緩存。

最終結果:最規矩的用戶,看不到401。稍微特殊一點的,會動態地址請求的,可能會看到401頁面。再特殊的,能看到頁面,無許可權提交數據。至於剩下的什麼會話攻擊之類的,就是純純的後端的事情了。

當然,也可以考慮最暴力的方案,直接重寫require


component: resolve =&> require(["../pages/user/customer.vue"], resolve) },


這個應該能滿足你的需求

使用bundle-loader做分塊動態載入 webpack-contrib/bundle-loader

const components = require.context("./components", true, /.vue$/)
const routeConfig = []
components.keys().forEach((n) =&> {
routeConfig.push(createRouteConfig(n.slice(1), "components" + n.slice(1)))
})
function createRouteConfig (path, view) {
return {
path,
component (resolve) {
const handler = require("bundle-loader!./" + view)
handler(module =&> resolve(module))
}
}
}
router.addRoutes(routeConfig)


如果本身在開發時,並不知道這個路由需要載入多少頁面的情況下,不可能寫require,因為具體要放多少個路由可能放在XML或是資料庫的配置文件裡面。

那麼怎麼辦呢,是不是有辦法動態執行:import ...


我是相同的界面,路由就一個入口,但是進來的人根據許可權require不同的js,從而控制顯示不同的界面。僅供參考,目前用得很好。我們的系統,總得來說,排除外圍的一些東西,就一個listview和一個editview。進到頁面了,再判斷許可權。這樣就不用做動態路由。


實現一組組件合併打包:

恰好前幾天寫了篇文章:Vue 項目中使用 webpack 將多個組件合併打包並實現按需載入

代碼見:KingMario/think-in-vue

效果:多組件合併打包按需載入,點擊鏈接之前打開「開發者工具」之 network 查看 js 載入過程

部分代碼摘錄如下:

const CommonComponents = require.context(".", true, /.vue$/)

CommonComponents.keys().forEach(key =&> {
exports[key.replace(/(.+/)([^/]+)(.vue)$/, "$2")] = CommonComponents(key)
})

注意約定好不同子目錄下的組件不要重名。

添加路由就使用 addRoute 好了。


我也碰到了這樣的問題。我是把需要的路由全列出來,登錄的時候把許可權狀態用vuex保存下來,然後動態載入的菜單。


這裡之前看到一種方法,用webpack給各個組件分別打包,然後載入打包之後的js,直接載入.vue文件貌似還真沒有找到好的辦法


`require.ensure`和`System.import`都是ES Module,為了達到靜態分析的目的,強制要求載入路徑必須是靜態字元串不能有變數。


首先我理解你這裡不同用戶隨許可權不同,可見的內容不同!那麼我是這樣做的: 按具有最高許可權用戶做出路由配置,然後在需要許可權才可見的路徑上 ,在beforeEnter 中根據許可權跳轉。對於菜單view也是根據許可權進行隱藏!

感覺差異在一定範圍內,這種方法還可以!要是太大就不太好。

我也是新手。


推薦閱讀:

在什麼場景下,選擇 AngularJS 比其他前端框架更好?
關於redux在項目運用中的一些問題?
Angular2 相比 React 技術棧有什麼具體的優勢?
前端框架有哪些典型問題?
很迷茫,不知道自己現在是要繼續學習 React.js 還是系統地學習 JS?

TAG:前端工程師 | 前端框架 | Vuejs | webpack | vue-router |