雖然當前前端項目多以單頁面爲主,但多頁面也並不是一無可取,在一些狀況下也是有用武之地的,好比:javascript
這裏就第4點作一些解釋,也對多頁面的應用場景作一個我認爲有價值的思路,在組內的一個項目中,由於項目日益膨脹,拆分系統有必定困難,項目頁面達到200+個以上, 所以構建速度十分緩慢,部署時間也很長,常常由於文案的更改及一些簡單的bug修復就要進行從新構建,若是採用單頁面一方面構建部署時間會隨着體量增大,另外一方面在工程上很差進行拆分。這時候多頁面就存在一種優點,咱們能夠在前端作一個空框架只包含菜單,內容區域採用多頁面結構,當咱們部署上線時能夠只針對單個頁面進行上線,速度大幅度提高(單頁面內部能夠集成前端路由),這樣業務模塊間也可平滑解耦。css
vue + typescript + webpack4 vue項目,並無使用vue-cli,緣由是對於開發人員來講,瞭解構建的詳細流程很重要,vue-cli這類工具的目的是快速實現項目的搭建,讓開發人員快速接手,快速進入 業務代碼編寫,所以隱含的爲咱們作了不少事,不少構建及本地開發的優化等等,但對於開發人員來講了解每一個步驟,每一個細節是作什麼的對自身成長頗有幫助(尤爲是組裏的不少程序員都不愛使用高度封裝的東西)。html
對於多頁面來講,與單頁面對比無非就是如下幾個問題:前端
- entry入口文件爲多個,須要考慮頁面多須要自動生成,少的話提早預置幾個就能夠。
- htmlWebpackPlugin使用時也須要相應的添加多個。
- 公共靜態資源提取的問題,splitchunkplugin是否須要使用的問題。
- 最後就是支持項目的部分構建的功能實現
爲達到咱們的終極目標,也就是可以部分代碼進行構建,咱們將一個項目從業務角度進行一個劃分,兩個層級,模塊和頁面,模塊表明一個具體業務場景,頁面表明這個業務場景的各個頁面,咱們將支持進行單/多模塊和單/多頁面的打包。vue
首先先看一下咱們的項目目錄結構java
├── build_tasks // 構建腳本webpack
├── config // 配置文件git
├── src // 源碼路徑程序員
└── static //build後文件路徑github
src目錄:
src
├── global.js // 項目全局工具
├── modules // 模塊 │
├── Layout.vue │
├── moduleA // 具體模塊名 │
│ └── pageA // 具體頁面名稱 │
├── xx.js │
├── index.vue
因爲咱們的頁面很是之多,所以咱們確定是須要自動生成entry文件的,而且這一步是須要在進入webpack構建流程以前就要作好的。咱們建立一個build_entries.ts的文件,用於編寫建立entry流程,這裏放一些核心代碼
const getTemplate = pagePath => { return ( ` import App from '${pagePath}'; import Vue from 'vue'; new Vue({ render: function (h) { return h(App); } }).$mount('#app');`); } const scriptReg = /<script([\s\S]*?)>/; /** * 判斷文件應該採用的後綴 */ const getSuffix = (source: string): string => { const matchArr = source.match(scriptReg) || []; if(matchArr[1].includes('ts')){ return '.ts' } return '.js'; }; const generateEntries = () => { const entries = {}; /***一些前置代碼拿到pages*/ if (!pages.length) return entries; // 清除entries rimraf.sync(entryPath+'/*.*'); pages.forEach(page => { const relativePage = path.relative(vueRoot, page); const source = fs.readFileSync(page, 'utf8'); const suffix = getSuffix(source); const pageEntry = path.resolve(entryPath, relativePage.replace(/\/index\.vue$/, '').replace(/\//g, '.')) + suffix; const entryName = path.basename(pageEntry, suffix); entries[entryName] = pageEntry if (fs.existsSync(pageEntry)) return; const pagePath = path.resolve(vueRoot, relativePage); const template = getTemplate(pagePath); fs.writeFileSync(pageEntry, template, 'utf-8'); }); return entries } export const getEntriesInfos = ()=>{ return generateEntries(); }
大概解釋下思路,咱們規定項目目錄結構爲modules/xxmodle/xxpage,咱們以命名爲index.vue的頁面爲入口頁面,爲每一個index.vue建立入口的js模版(getTemplete方法),生成的entry名稱爲"模塊名.頁面名.js"。由於項目內須要支持ts,所以咱們還須要判斷vue內的script標籤的語言,以便建立ts格式的entry仍是js格式的entry。 咱們的webpack配置:
const entries = getEntriesInfos(); const common = { entry: entries, output: { filename: `[name]-[hash].bundle.js`, path: path.resolve(rootPath, 'static'), publicPath, },
由於咱們是多頁面,每一個頁面都須要加載核心的包(如vue,element-ui,lodash等等)而這類包咱們是不常變化的,所以咱們須要使用webpack的dllplugin來剝離他們出來,不參與構建,咱們的項目中也可能會有咱們本身的全局工具包,這部分代碼不適合提取,只須要在entry中再加入一個common的entry便可。對於單頁面內是否須要使用splitchunk,在個人實踐中是沒有使用的,可是這個看狀況,若是頁面引用的包確實比較大(畢竟vue這類框架包已經被提取出去了,這個機率不大)那麼可使用splitchunk來分離,我目前的實踐是合併到一個頁面的js內,單頁面js在gzip後在200k之內均可忍受。 下面放一下dll的配置 webpack.dll.config.ts
const commonLibs = ['vue','element-ui','moment', 'lodash'] export default { mode: 'production', entry: { commonLibs }, output: { path: path.join(__dirname, 'dll_libs'), filename: 'dll.[name].[hash:8].min.js', library: '[name]', // publicPath: '/static/' }, plugins: [ new CleanWebpackPlugin(), new webpack.DllPlugin({ context: __dirname, path: path.join(__dirname, 'dll_libs/', '[name]-manifest.json'), name: '[name]' }), new assetsWebpackPlugin({ filename: 'dll_assets.json', path: path.join(__dirname,'assets/') }) ] } as webpack.Configuration;
如代碼所示咱們將'vue','element-ui','moment', 'lodash'這幾個組件提取打成一個公共包命名爲commonLib,這裏使用了assetsWebpackPlugin用於生成一個json文件,記錄每次dll構建的文件名(由於每次構建hash是不同的),爲的是在使用webpackhtmlplugin的時候拿到這個結果注入到模版頁面中去。 生成的json記錄相似:
{"commonLibs":{"js":"dll.commonLibs.51be3e86.min.js"}}
這樣咱們就能夠在webpack配置文件中取到這個名字:
const dllJson = require('./assets/dll_assets.json'); for(let entryKey in entries){ if(entryKey!== 'global'){ common.plugins.push( new HtmlWebpackPlugin({ title: allConfiguration[entryKey].title, isDebug: process.env.DEBUG, filename: `${entryKey}.html`, template: 'index.html', chunks:['global', entryKey, ], chunksSortMode: 'manual', dll_common_assets: process.env.NODE_ENV !== 'production'?'./dll_libs/' + dllJson.commonLibs.js : publicPath + 'dll_libs/' + dllJson.commonLibs.js, }), ) } }
由於是多頁面,所以咱們webpackhtml使用時也是要添加多個的,這裏根據生成的json拿到dll的文件名注入到模版頁面中。
接下來咱們要支持進行按需構建打包,支持單/多模塊以及單/多頁面的打包,這裏怎麼作呢,能夠在構建時傳入環境變量,而後在build_entry中判斷環境變量進行局部打包,由於打包的入口是entry的數量決定的。 命令能夠這樣構成:
MODULES=xxx,xxx PAGES=sss,sss npm run build
build_entry相關代碼,在generateEntries方法中
const entries = {}; const buildModules = process.env.MODULES || '*'; const buildPages = process.env.PAGES || '*'; const filePaths = `${!buildModules.includes(',') ? buildModules : '{'+buildModules+'}'}/${!buildPages.includes(',') ? buildPages : '{'+buildPages+'}'}/*.vue` const pages = glob.sync(path.resolve(vueRoot, filePaths)).filter(file =>{ return /index\.vue$/.test(file) || []; }) if (!pages.length) return entries;
上面的方法根據傳入的環境變量拼對應的頁面及模塊路徑,經過glob的支持生成對應的entyr進行構建。
多頁面構建完成以後就是發佈流程,發佈流程其實也會變的簡單,若是是單頁面每次構建完成都要總體替換靜態文件(js,css),多頁面模式下,咱們只須要替換對應頁面的文件便可,通常的思路是頁面文件能夠上傳到部署的服務器,而後靜態js,css等文件直接扔到CDN上便可,發佈不會影響到其餘頁面,即使出錯也不會影響項目,並且效率極高,這部分代碼就不展現了,只是提供思路,畢竟每一個項目發佈流程都不太同樣。
以上是我對多頁面應用場景的一個思路,它是有必定的適用場景的,比較適合大而全並且模塊劃分清晰的系統。
原文連接:https://zhangzippo.github.io/posts/2019/05/12/_20xx-05-10mutilpage.html