webpack4 + vue多頁面項目精細(xì)構(gòu)建思路

webpack4 + vue多頁面項目精細(xì)構(gòu)建思路

原文鏈接:https://zhangzippo.github.io/posts/2019/05/12/_20xx-05-10mutilpage.html

雖然當(dāng)前前端項目多以單頁面為主,但多頁面也并非一無是處,在一些情況下也是有用武之地的,比如:

  1. 項目龐大,各個業(yè)務(wù)模塊需要解耦
  2. SEO更容易優(yōu)化
  3. 沒有復(fù)雜的狀態(tài)管理問題
  4. 可以實現(xiàn)頁面單獨上線

前言

這里就第4點做一些解釋,也對多頁面的應(yīng)用場景做一個我認(rèn)為有價值的思路,在組內(nèi)的一個項目中,因為項目日益膨脹,拆分系統(tǒng)有一定困難,項目頁面達(dá)到200+個以上, 因此構(gòu)建速度十分緩慢,部署時間也很長,經(jīng)常因為文案的更改及一些簡單的bug修復(fù)就要進(jìn)行重新構(gòu)建,如果采用單頁面一方面構(gòu)建部署時間會隨著體量增大,另一方面在工程上不好進(jìn)行拆分。這時候多頁面就存在一種優(yōu)勢,我們可以在前端做一個空框架只包含菜單,內(nèi)容區(qū)域采用多頁面結(jié)構(gòu),當(dāng)我們部署上線時可以只針對單個頁面進(jìn)行上線,速度大幅度提升(單頁面內(nèi)部可以集成前端路由),這樣業(yè)務(wù)模塊間也可平滑解耦。

項目架構(gòu)

vue + typescript + webpack4
vue項目,并沒有使用vue-cli,原因是對于開發(fā)人員來說,了解構(gòu)建的詳細(xì)流程很重要,vue-cli這類工具的目的是快速實現(xiàn)項目的搭建,讓開發(fā)人員快速接手,快速進(jìn)入 業(yè)務(wù)代碼編寫,因此隱含的為我們做了很多事,很多構(gòu)建及本地開發(fā)的優(yōu)化等等,但對于開發(fā)人員來說了解每個步驟,每個細(xì)節(jié)是做什么的對自身成長很有幫助(尤其是組里的很多程序員都不愛使用高度封裝的東西)。

思路

對于多頁面來說,與單頁面對比無非就是以下幾個問題:

  1. entry入口文件為多個,需要考慮頁面多需要自動生成,少的話提前預(yù)置幾個就可以。
  2. htmlWebpackPlugin使用時也需要相應(yīng)的添加多個。
  3. 公共靜態(tài)資源提取的問題,splitchunkplugin是否需要使用的問題。
  4. 最后就是支持項目的部分構(gòu)建的功能實現(xiàn)

為達(dá)到我們的終極目標(biāo),也就是能夠部分代碼進(jìn)行構(gòu)建,我們將一個項目從業(yè)務(wù)角度進(jìn)行一個劃分,兩個層級,模塊和頁面,模塊代表一個具體業(yè)務(wù)場景,頁面代表這個業(yè)務(wù)場景的各個頁面,我們將支持進(jìn)行單/多模塊和單/多頁面的打包。

開始

首先先看一下我們的項目目錄結(jié)構(gòu)
├── build_tasks // 構(gòu)建腳本
├── config // 配置文件
├── src // 源碼路徑
└── static //build后文件路徑
src目錄:
src
├── global.js // 項目全局工具
├── modules // 模塊
│ ├── Layout.vue
│ ├── moduleA // 具體模塊名
│ │ └── pageA // 具體頁面名稱
│ │ ├── xx.js
│ │ ├── index.vue

自動生成entry

由于我們的頁面非常之多,因此我們肯定是需要自動生成entry文件的,并且這一步是需要在進(jìn)入webpack構(gòu)建流程之前就要做好的。我們創(chuàng)建一個build_entries.ts的文件,用于編寫創(chuàng)建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]*?)>/;
/**
 * 判斷文件應(yīng)該采用的后綴
 */
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();
}

大概解釋下思路,我們規(guī)定項目目錄結(jié)構(gòu)為modules/xxmodle/xxpage,我們以命名為index.vue的頁面為入口頁面,為每個index.vue創(chuàng)建入口的js模版(getTemplete方法),生成的entry名稱為"模塊名.頁面名.js"。因為項目內(nèi)需要支持ts,因此我們還需要判斷vue內(nèi)的script標(biāo)簽的語言,以便創(chuàng)建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來剝離他們出來,不參與構(gòu)建,我們的項目中也可能會有我們自己的全局工具包,這部分代碼不適合提取,只需要在entry中再加入一個common的entry即可。對于單頁面內(nèi)是否需要使用splitchunk,在我的實踐中是沒有使用的,但是這個看情況,如果頁面引用的包確實比較大(畢竟vue這類框架包已經(jīng)被提取出去了,這個概率不大)那么可以使用splitchunk來分離,我目前的實踐是合并到一個頁面的js內(nèi),單頁面js在gzip后在200k以內(nèi)都可忍受。
下面放一下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構(gòu)建的文件名(因為每次構(gòu)建hash是不一樣的),為的是在使用webpackhtmlplugin的時候拿到這個結(jié)果注入到模版頁面中去。
生成的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使用時也是要添加多個的,這里根據(jù)生成的json拿到dll的文件名注入到模版頁面中。

按需打包

接下來我們要支持進(jìn)行按需構(gòu)建打包,支持單/多模塊以及單/多頁面的打包,這里怎么做呢,可以在構(gòu)建時傳入環(huán)境變量,然后在build_entry中判斷環(huán)境變量進(jìn)行局部打包,因為打包的入口是entry的數(shù)量決定的。
命令可以這樣構(gòu)成:

 MODULES=xxx,xxx PAGES=sss,sss npm run build

build_entry相關(guān)代碼,在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;

上面的方法根據(jù)傳入的環(huán)境變量拼對應(yīng)的頁面及模塊路徑,通過glob的支持生成對應(yīng)的entyr進(jìn)行構(gòu)建。

多頁面線上發(fā)布

多頁面構(gòu)建完成之后就是發(fā)布流程,發(fā)布流程其實也會變的簡單,如果是單頁面每次構(gòu)建完成都要整體替換靜態(tài)文件(js,css),多頁面模式下,我們只需要替換對應(yīng)頁面的文件即可,一般的思路是頁面文件可以上傳到部署的服務(wù)器,然后靜態(tài)js,css等文件直接扔到CDN上即可,發(fā)布不會影響到其他頁面,即便出錯也不會影響項目,而且效率極高,這部分代碼就不展示了,只是提供思路,畢竟每個項目發(fā)布流程都不太一樣。

總結(jié)

以上是我對多頁面應(yīng)用場景的一個思路,它是有一定的適用場景的,比較適合大而全而且模塊劃分清晰的系統(tǒng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容