日更挑戰(zhàn)-Vue-cli之構(gòu)建多模塊項(xiàng)目


越不懂的越愛裝
大家都同等:IT世界沒有難不難,只有是否了解過

挑戰(zhàn)目錄

什么是Vue-cli多模塊打包?

在一個(gè)項(xiàng)目中,通過配置達(dá)到可以共用公共文件,且打包只屬于當(dāng)前功能的文件的項(xiàng)目包的場景。


Vue-cli多模塊打包的用途?

  1. 比如我比較常用的:離線包模式。單獨(dú)功能模塊打成單獨(dú)的離線包供安卓、IOS使用。
  2. 一個(gè)項(xiàng)目的不同版本的實(shí)現(xiàn)。多模塊打包可以做到抽出公共部分專注不同部分。
  3. 等等

Webpack為什么可以實(shí)現(xiàn)多模塊打包?

  1. (打包?)由于:Vue-cli(使用webpack)會(huì)從入口js,通過導(dǎo)入語句自動(dòng)尋找所依賴的模塊進(jìn)行打包

    所以:只要通過不同的入口,執(zhí)行打包命令就能打出不同的包。

  2. (執(zhí)行?)由于:腳本文件.js文件中可以獲取如命令:node 腳本文件.js xxx xxx 類的命令的命令行參數(shù)

    所以:可以實(shí)現(xiàn)通過在package.json文件配置node xxx.js的執(zhí)行命令并傳入?yún)?shù)自動(dòng)實(shí)現(xiàn)配置化打包命令。

  3. (配置?)由于:Vue-cli提供了配置參數(shù)pages用來配置多頁應(yīng)用。(當(dāng)然webpack也有自己的一套,這里主要是使用Vue)

    所以:可以通過解析命令行參數(shù)拼出pages所需配置格式,然后進(jìn)行打包。

    pages多頁配置格式如下:

    {     //下面最少存在一個(gè)包名
         "包名1":{
             // page 的入口
          entry: `src/${包名1}/main.js`,
          // 模板來源
          template: "public/index.html",
          // 在 dist/`${包名1}.html` 的輸出
          filename: `${包名1}.html`,
          / 當(dāng)使用 title 選項(xiàng)時(shí),
          // template 中的 title 標(biāo)簽需要是 <title><%= htmlWebpackPlugin.options.title %></title>
          title: 包名1,
          // 在這個(gè)頁面中包含的塊,默認(rèn)情況下會(huì)包含
          // 提取出來的通用 chunk 和 vendor chunk
          chunks: ["chunk-vendors", "chunk-common", 包名1]
        },
         "包名2":{
          entry: `src/${包名2}/main.js`,
          template: "public/index.html",
          filename: `${包名2}.html`,
          title: 包名2,
          chunks: ["chunk-vendors", "chunk-common", 包名2]
        },
        ...
    }
    
  4. (執(zhí)行?) 由于pages是配置在Vue-cli上的。Vue-cli提供了將其搜索配置的包名啟動(dòng)為服務(wù)和打包的功能。

    所以: 只需要在配置vue.config.js的module.exports = {pages}之前動(dòng)態(tài)配置pages,就可以啟動(dòng)一個(gè)或多個(gè)服務(wù)了。

    //通過fs.readdirSync(path.resolve(__dirname, "../src"));讀取指定目錄的文件夾。拼裝出上面所以的pages的配置
    const MultiModulesConfig = require("./config/modules.config.js");
    let pages = {};
    
    //process.env.NODE_ENV會(huì)預(yù)先根據(jù)命令行參數(shù)賦值
    //開發(fā)環(huán)境 啟動(dòng)全部的模塊,如果每個(gè)包名啟動(dòng)一次。會(huì)導(dǎo)致本地出現(xiàn)多個(gè)端口服務(wù)。
    //如果是打包則只打所以的包指定模塊名,若模塊名為all則表示一次性打包所以
    if (process.env.NODE_ENV == "development"||pageName==="all" ) {  
      pages = MultiModulesConfig; 
    } else {
        pages[pageName] = MultiModulesConfig[pageName];
    }
    
    module.exports = {
      pages: pages,
    }
    
  5. pages配置多模塊時(shí),配置的多個(gè)模塊的入口html會(huì)打包到同一個(gè)文件夾(module.exports = {outputDir: "dist/front/" + pageName,})下,所有啟動(dòng)的服務(wù)需要帶具體哪個(gè)模塊名去訪問。當(dāng)需要打多個(gè)文件夾的不同模塊包是,需要分享設(shè)置pages和outputDir。

由上可知:基于Vue-cli腳手架后,多模塊運(yùn)行服務(wù)和多模塊打包,簡單到只要配置pages參數(shù)然后繼續(xù)正常的步驟就行了。至于是一個(gè)或多個(gè)模塊,就看pages里面配置的是多少模塊而已。


多模塊打包如何解決路由問題?

  • 跨模塊路由守衛(wèi)攔截
1. 定義路由攔截。(即定義router.beforeEach(to, from, next) => {...}內(nèi)部的參數(shù))。
    const loginIntercept=(to, from, next) => {
        // ...
    }
2. 分別將上述定義的攔截注冊到模塊內(nèi)核模塊外路由攔截中。(多個(gè)遍歷執(zhí)行多次即可)
    router.beforeEach(loginIntercept); //模塊內(nèi)部攔截
    MyRoutreIntercept.beforeCrossModule(interceptor); // 跨模塊攔截
3. 通過Promise定義一個(gè)攔截器執(zhí)行隊(duì)列,用于串行執(zhí)行所有攔截器
     import { clone,isUndefined } from "lodash"
   syncInterceptorSeries = function(to, from, interceptors) {
    return new Promise((resolve, reject) => {
      const cloneArr = clone(interceptors);
      let exe = function(x) {
        if (!isUndefined(x)) {
          reject(x);
        }
        let fn = cloneArr.shift();
        if (!fn) {
          resolve();
          return;
        }
        fn(to, from, next);
      };
      exe();
    });
  };
4. 區(qū)分跨模塊跳轉(zhuǎn)和正常跳轉(zhuǎn)(這里也可以通過額外傳是否跨模塊參數(shù)確定)。對(duì)跨模塊跳轉(zhuǎn)進(jìn)行攔截處理。
   // 組裝跨模塊目標(biāo)頁面的數(shù)據(jù)結(jié)構(gòu)
   const to = {
      fullPath: res.path || "",
      hash: "",
      matched: [{}],
      meta: res.meta || {},
      name: res.name || "",
      params: param.param || {},
      path: res.path,
      query: {
        ...param.query,
        crossModule: true,
        moduleName: param.moduleName
      },
      crossModule: true,
      moduleName: param.moduleName,
      fullUrl: this.getCrossModulePath(param)
      };
      const from = this.router.currentRoute;
            //調(diào)用上面的串行執(zhí)行攔截器方法
            return this.syncInterceptorSeries(
          to,
          from,
          this.crossModuleInterceptors
        ).then(
          () => {
            // 執(zhí)行完隊(duì)列未發(fā)生攔截行為
            return { granted: true };
          },
          reject => {
            // 發(fā)生攔截行為,此處reject為攔截器隊(duì)列函數(shù)中傳入next()函數(shù)的參數(shù)
            if (reject === false) {
              return { granted: false };
            }
            return { granted: false, redirect: true, params: reject };
          }
        );
  • 跨模塊路由跳轉(zhuǎn)

push:跳轉(zhuǎn)前調(diào)用上面的攔截,模塊內(nèi)和跨模塊區(qū)分處理。

import * as _ from "lodash";  
push(params) {
    /** 跨模塊跳轉(zhuǎn) */
    if (_(params).get("crossModule")) {
            //跳轉(zhuǎn)前調(diào)用上面的攔截
      this.beforeCrossModuleAction(params).then(res => {
        if (res.granted) {
          this.openFullPath(this.getCrossModulePath(params));
          return;
        }
        if (res.redirect) {
          // 重定向
          if (
            res.params.path === params.path ||
            res.params.name === params.name
          ) {
            // 重定向頁面為原始頁面
            this.openFullPath(this.getCrossModulePath(params));
            return;
          }
          this.push(res.params);
          return;
        }
      });
      return;
    }
    // 模塊內(nèi)
    this.router.push(params);
  }
    
    //跨模塊的跳轉(zhuǎn)方法,fullPath參數(shù)通過encodeURIComponent處理后的拼上參數(shù)的地址
  openFullPath(fullPath) {
    window.location.href = fullPath;
  }

replace: 和push同樣處理,不同在于openFullPath方法。

openFullPath(fullPath) {
  window.location.replace(fullPath);
}

openInNewWindow:打開新的頁面
由于項(xiàng)目由手機(jī)和網(wǎng)站項(xiàng)目之分。所以該部分打開過程有如下判斷

openInNewWindow(fullPath) {
   if (Native.isApp()) {
      Native.openNewWindow({
        url: fullPath
      });
   } else {
      // window.open(fullPath, "_blank");
      window.location.href = fullPath;
  }
}

back|go(n):

back(params){
      localStorage.setItem(RouteBackStorageKey,Json.stringify(params));
      window.history.back();
}

PS:

為了防止push到同一個(gè)地址報(bào)錯(cuò)在main.js里面重寫push方法。
Router.prototype.push = function push(location, onResolve, onReject) {
    if (onResolve || onReject){
          return originalPush.call(this, location, onResolve, onReject);
    }
    return originalPush.call(this, location).catch(err => err);
};
  • 跨模塊數(shù)據(jù)傳遞
push,replace可以通過上面舉例的是通過拼接URl的方式傳遞,
另外還可以自己維護(hù)Storage本地存儲(chǔ)(略)
back的傳參不太一致:(在被返回的頁面上添加onBack方法,當(dāng)返回時(shí)會(huì)自動(dòng)回調(diào)并傳遞參數(shù))
export const onBackGoMixinListener = function (Vue: any) {
  Vue.mixin({
    created() {
      if (this.$options.name === this.$route.name) { //當(dāng)前頁面組件
        localStorage.removeItem(RouteBackStorageKey)
      }
    },
    beforeRouteEnter(to: any, from: any, next: any) {
      next(((vue: any) => {
          //當(dāng)前頁面組件
          if (this.$options.name === this.$route.name) {
            const isReturn = localStorage.getItem(RouteBackStorageKey);
            //當(dāng)前頁面組件返回事件
            if (isReturn) {
              if (vue.onBack) {
                //調(diào)用當(dāng)前頁面組件返回事件
                vue.onBack(to, from, JSON.parse(isReturn || ""))
              }
            }
          }
        }
      ));
    }
  })
};


多模塊打包如何解決devServer問題?

這個(gè)其實(shí)應(yīng)該不會(huì)有太大問題,最后就是參數(shù)的配置不同而已。在執(zhí)行node 自定義.js 腳本的時(shí)候,根據(jù)命令行參數(shù)或默認(rèn)的命令行參數(shù)將要用到的數(shù)據(jù)都賦值給process.env.xxxx。用于全局獲取使用。

其他的問題應(yīng)該就是代理的配置問題,不屬于該討論范圍內(nèi)


多模塊打包如何解決Vuex.Store問題?

這里使用vuex-persistedstate插件,解決多模塊和刷新時(shí)VueX數(shù)據(jù)丟失的問題。

  • 安裝

    npm install --save vuex-persistedstate

  • 使用:store入口js文件引入并進(jìn)行配置

    import createPersistedState from "vuex-persistedstate";
    const store = new Vuex.Store({
    plugins: [createPersistedState()]
    });

  • 修改默認(rèn)配置

    默認(rèn)使用localStorage存儲(chǔ),存儲(chǔ)鍵名key是“vuex

    參數(shù) 描述
    key 存儲(chǔ)數(shù)據(jù)的鍵名。(默認(rèn):vuex)
    paths 部分路徑可部分保留狀態(tài)的數(shù)組。如果沒有給出路徑,則將保留完整狀態(tài)。如果給出一個(gè)空數(shù)組,則不會(huì)保留任何狀態(tài)。必須使用點(diǎn)符號(hào)指定路徑。如果使用模塊,請(qǐng)包括模塊名稱(默認(rèn):[])
    reducer 將根據(jù)給定路徑調(diào)用以減少狀態(tài)持久化的函數(shù)
    storage 指定存儲(chǔ)數(shù)據(jù)的方式。默認(rèn)為localStorage ,也可以設(shè)置 sessionStorage
    getState 用來重新補(bǔ)充先前持久狀態(tài)的功能,默認(rèn)使用:storage定義獲取的方式
    setState 用以保持給定狀態(tài)的函數(shù)。默認(rèn)使用:storage定義的設(shè)置值方式
    filter 一個(gè)將被調(diào)用以過濾setState最終將在存儲(chǔ)中篩選過濾的函數(shù)。默認(rèn)為() => true。

    詳細(xì)配置請(qǐng)參考源碼 vuex-persistedstate


上面簡單的提一下多模塊打包項(xiàng)目的的幾個(gè)方面,其他的以后在寫吧
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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