webpack4.X 實戰(三):企業SPA 24點總結(上)

Mr_Hermit發表於2019-03-25

1. 區分 開發 / 生產環境 webpack-merge

  • 遵循不重複原則(DRY)

    webpack 的相關配置需要保留一個 common配置、一個dev配置、一個prod配置

    通過 webpack-merge 包將其整合

  • 開發環境時 一些工具的使用是沒有意義的,比如 壓縮程式碼、檔名雜湊、分離程式碼等...

  • 專案中 安裝、配置如下

    npm i webpack-merge@4.1.5 -D
    複製程式碼
    // webpack/webpack.common.js
    
    
    const path = require('path');
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {
        app: './src/index.js'
      },
      plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
          title: 'Production'
        })
      ],
      output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
      }
    };
    複製程式碼
    // webpack/webpack.dev.js
    
    
    const merge = require('webpack-merge');
    const common = require('./webpack.common.js');
    
    module.exports = merge(common, {
      devtool: 'inline-source-map',
      devServer: {
        contentBase: './dist'
      }
    });
    複製程式碼
    // webpack/webpack.prod.js
    
    
    const merge = require('webpack-merge');
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    const common = require('./webpack.common.js');
    
    module.exports = merge(common, {
      plugins: [
        new UglifyJSPlugin()
      ]
    });
    複製程式碼
    // package.json 配置 npm script
    
    {
      "scripts": {
        "start": "webpack-dev-server --open --config webpack/webpack.dev.js",
        "build": "webpack --config webpack/webpack.prod.js"
      }
    }
    複製程式碼

2. 配置 source map

  • source map 反應資源的對映關係,用於 定位程式碼中的錯誤

  • 開發環境下 建議

    {
        devtool: "cheap-module-eval-source-map"
    }
    複製程式碼
  • 生產環境下 建議

    {
        devtool: false
    }
    複製程式碼
  • 詳細配置 見官方

3. 搖樹優化 Tree Shaking

  • 作用:

    • 淨化 JS 中無用的程式碼
  • 符合如下條件,自動開啟 Tree Shaking:

    • webpack 4.X 生產模式下

    • 編碼時,遵循 ES6 模組化語法(require 無效)

    • 編譯時,不要編譯 ES6模組

    // .babelrc 配置如下
    
    {
        "presets": [
            [
                "@babel/preset-env",
                {
                    "modules": false
                }
            ]
        ]
    }
    複製程式碼
  • 生產模式下 ES6 模組化語法 實踐:

    • 實踐一:

      // a.js 檔案
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export default {
          dict,
          dictMedia
      };
      複製程式碼
      // 引入 a.js 檔案
      
      import dicts from '/a';
      
      console.log(dicts);
      // a.js 檔案中的 dict、dictMedia 都被打包   
      複製程式碼
    • 實踐二:

      // a.js 檔案
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export default {
          dict,
          dictMedia
      };
      複製程式碼
      // 引入 a.js 檔案
      
      import dicts from '/a';
      
      console.log(dicts.dict);    
      // a.js 檔案中的 dict 被打包;dictMedia 不被打包
      複製程式碼
    • 實踐三:

      // a.js 檔案
         
      const dict = 'dict';
      const dictMedia = 'dictMedia';
         
      export {
          dict,
          dictMedia
      };
      複製程式碼
      // 引入 a.js 檔案
         
      import {dict, dictMedia} from '/a';
         
      console.log(dict, dictMedia);    
      // a.js 檔案中的 dict 被打包;dictMedia 不被打包
      複製程式碼
    • 實踐四:

      // a.js 檔案
      
      const dict = 'dict';
      const dictMedia = 'dictMedia';
      
      export {
          dict,
          dictMedia
      };
      複製程式碼
      // 引入 a.js 檔案
      
      import {dict} from '/a';
      
      console.log(dict);  
      // a.js 檔案中的 dict 被打包;dictMedia 不被打包
      複製程式碼

4. 作用域提升 Scope Hoisting

  • 概述

    • Scope Hoisting 可以讓webpack打包出來的程式碼檔案更小、執行更快

    • webpack4 的生產模式,預設開啟 Scope Hoisting

    • webpack3 推出的功能,需要手動開啟 粗略講解 在此

  • 原理:

    • 分析出模組之間的依賴關係,儘可能的把打散的模組合併到一個函式中去

    • 前提是不能造成程式碼冗餘

  • 是否開啟 Scope Hoisting 的對比

    // util.js 檔案
    
    export default 'Hello,Webpack';
    複製程式碼
    // main.js 入口檔案
    
    import str from './util.js';
    console.log(str);
    複製程式碼
    // 未開啟 Scope Hoisting 打包後如下
    
    [
      (function (module, __webpack_exports__, __webpack_require__) {
        var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
        console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]);
      }),
      (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_exports__["a"] = ('Hello,Webpack');
      })
    ]
    複製程式碼
    // 開啟 Scope Hoisting 打包後如下
    
    [
      (function (module, __webpack_exports__, __webpack_require__) {
        var util = ('Hello,Webpack');
        console.log(util);
      })
    ]
    複製程式碼

5. 啟動開發服務 熱替換 HMR

  • 全稱:Hot Module Replacement

  • 應用場景:開發環境下

  • 作用:熱替換 HMR,在啟動開發服務時,區域性載入 頁面被修改之處;加快開發編譯速度

    保留在完全重新載入頁面時丟失的應用程式狀態

    只更新變更內容,以節省寶貴的開發時間

    調整樣式更加快速:幾乎相當於在瀏覽器偵錯程式中更改樣式

  • 限制:HMR 是可選功能(只會影響包含 HMR 程式碼的模組)

    舉個例子,通過 style-loader 為 style 樣式追加補丁。為了執行追加補丁,style-loader 實現了 HMR 介面;當它通過 HMR 接收到更新,它會使用新的樣式替換舊的樣式

    如果一個模組沒有 HMR 處理函式,更新就會冒泡(bubble up)。這意味著一個簡單的處理函式能夠對整個模組樹(complete module tree)進行更新

  • 配置 HMR

    // webpack 配置檔案中
    const webpack = require('webpack');
    
    
    module.exports = {
        devServer: {
          contentBase: path.resolve(__dirname,'dist'),
          compress: true,
          host: 'localhost',   
          port:3000,
          hot: true         // 開啟 熱替換
        },
    
        plugins: [
          new webpack.NamedModulesPlugin(),         // 必要的配置
          new webpack.HotModuleReplacementPlugin()  // 必要的配置
        ]
    };
    複製程式碼

6. 專案開發中 善用 按需載入

  • 方式一:使用 ES6 的模組化語法,動態載入模組 import()

    import('') 語法目前只是 ECMAScript 提案階段,還沒被正式釋出

    詳細見 import API

    // .babelrc 中
    // babel 7
    
    {
        "plugins": [
            "@babel/plugin-syntax-dynamic-import"
        ]
    }
    複製程式碼
  • 方式二:使用 webpack 對程式碼進行分割,按需載入(webpack 的 require.ensure 語法)

    • require.ensure 是 webpack 語法:

      • 引數1:要依賴的模組;型別為 字串陣列;一般為空

      • 引數2:載入依賴後,自動執行的回撥函式

      • 引數3:打包後,js 檔案的輸出路徑、js 檔名(chunk名稱)

      const router = new VueRouter({
          routes: [               // 定義路由資訊物件
              {
                  path: string,
                 
                  name?: string,    
                 
                  component: (resolve) => {
                      require.ensure([], () => {
                          resolve(require('../../view/demo/index.vue'))
                      }, 'demo')
                  }
                 
              }
          ]
      });
      複製程式碼

7. 配置 loader 生效範圍

  • 作用:配置 loader 的生效範圍,可提高編譯速度

  • 以配置 babel-loader 為例

    // 不推薦
    // webpack配置檔案中 配置
    const path = require('path');
    
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader']
        }]
    }
    複製程式碼
    // 推薦
    // webpack配置檔案中 配置
    const path = require('path');
    
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader'],
            exclude: '', // 排除不要載入的資料夾
            include: [path.resolve(__dirname, 'src'), /node_modules/] // 指定需要載入的資料夾
        }]
    }
    複製程式碼
  • excludeinclude 的值:

    • 值可以是單獨項、可以是陣列

    • 可以是路徑、可以是正則

  • 專案實戰:

    專案開發中,針對主要 lodaer 如 babel-loaderstyle-loadersass-lodaer

    配置生效範圍: 除了要轉換專案程式碼,還要轉換 node_modules 中程式碼;否則 針對沒有完全轉成JS的node包,會報錯

8. 配置 如何解析模組 resolve

  • 給模組起別名:resolve.alias

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        alias: {
            '@components': '/src/common'
        }
      }
    };
    複製程式碼
    // 專案程式碼
    
    import a from '@components/utils';
    
    // 等同於
    import a from '/src/common/utils';
    複製程式碼
  • 自動新增字尾 規則:resolve.extensions

    當引入模組時不帶檔案字尾,webpack 會根據配置依次新增字尾 尋找檔案

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        extensions: ['.vue', '.js', '.scss', '.css', '.json']
      }
    };
    複製程式碼
  • 解析目錄時要使用的檔名:resolve.mainFiles

    預設尋找 index 命名的檔案

    // webpack 配置
    module.exports = {
      //...
      resolve: {
        mainFiles: ["index"]
      }
    };
    複製程式碼
  • 指明第三方模組存放的位置,以減少搜尋步驟

    • 預設值:[node modules]

    • 預設搜尋步驟:先去當前目錄的 /node modules 目錄下去找我們想找的模組,如果沒找到,就去上一級目錄 ../node modules 中找,再沒有就去 ../../node modules 中找

     // webpack 配置
    module.exports = {
      //...
      resolve: {
        modules: [path.resolve(__dirname, 'node_modules')]
      }
    };
    複製程式碼
  • 引入的模組,尋找規則:

    不建議寫檔案字尾名,就意味著引入的模組路徑,路徑最後一層有可能是檔案;有可能是資料夾

    預設將路徑的最後一層 視為檔名,依次匹配 配置的字尾名

    如果沒找到該檔案,將路徑的最後一層視為資料夾,依次匹配 設定的檔名,找到後再 依次匹配設定的字尾名

    如都沒找到,就會報錯

  • resolve 詳細配置 見官網

9. 配置 noParse 讓webpack編譯時 忽略一些檔案

  • 場景:

    • 一些沒有采用模組化的檔案,沒有必要讓 webpack 進行處理編譯

    • 通過配置 modules.noParse,可以讓 webpack 忽略,提升編譯速度

  • 配置:

    module.exports = merge(common, {
        modules: {
            // 使用正規表示式
            noParse: /jquery|chartjs/
           
            // 或
           
            // 使用函式,從 Webpack 3.0.0 開始支援
            noParse: (content)=> {
              // content 代表一個模組的檔案路徑
              // 返回 true or false
              return /jquery|chartjs/.test(content);
            }
        }
    });
    複製程式碼

10. 配置 performance 在生產環境 移除效能警告

  • webpack 打包後,如果檔案體積超出預設(250kb)大小,會輸出警告

  • 開發環境下這是一個不錯的提示,但生產環境下完全沒必要,可以通過 performance 進行相關配置

  • 配置如下

    詳細配置 見官網

    // webpack 配置
    
    module.exports = merge(common, {
        performance: {
            hints: false,               // 關閉警告
            maxEntrypointSize: 400000   // 預警值設定成40kb
        }
    });
    複製程式碼

11. 配置 專案環境變數 webpack.DefinePlugin

  • 場景:專案開發過程中,配置 每個檔案中都可以使用的 JS 變數

  • 直接獲取 開發環境變數 process.env.NODE_ENV

    使用webpack4.x,webpack 會將環境變數 process.env.NODE_ENV 的值,設定為 webpack配置中 mode 的值

    在專案程式碼中 可以直接使用 process.env.NODE_ENV

    // 專案程式碼中
    
    console.log(process.env.NODE_ENV);  // development
    複製程式碼
  • 設定 / 獲取 新的環境變數

    使用 webpack 的內建外掛 DefinePlugin,可以為專案程式碼定義環境變數

    限制:DefinePlugin 設定的環境變數只能在專案程式碼中獲取,不能再 webpack 的配置檔案中獲取

    // webpack 配置如下
       
    const webpack = require('webpack');
       
    module.exports = merge(common, {
        plugins: [
            new webpack.DefinePlugin({
                'process.env.NODE_ENV': JSON.stringify('ddd'),      // 覆蓋 process.env.NODE_ENV
                'process.env.globalName': JSON.stringify('globalName'),
                'globalName': JSON.stringify('eeee'),
    
            })
        ]
    });
    複製程式碼
    // 專案程式碼中
    
    console.log(process.env.NODE_ENV);      // ddd
    console.log(process.env.globalName);    // globalName
    console.log(globalName);                // eeee
    複製程式碼

12. npm script 指令傳參(三種方式)

  • 場景:配置webpack時,可能需要通過 npm script 傳參,用來處理不同場景下的不同需求

  • 方式一:不同系統存在 相容問題

    • 在指令碼命令的配置(package.json 的 script 下) 中傳參(window)

      // window 系統下:傳參
      
      "scripts": {
          "server": "webpack-dev-server --open",
          "build:dev":"set type=dev&webapck",
          "build:prod": "set type=prod&webpack"
      },
      複製程式碼
    • 在指令碼命令的配置(package.json 的 script 下) 中傳參(mac)

      // mac 系統下:傳參
      
      "scripts": {
          "server": "webpack-dev-server --open",
          "build:dev":"export type=dev&&webpack",
          "build:prod": "export type=prod&&webpack"
      },
      複製程式碼
    • 接收引數

      // node的語法來讀取type的值,然後根據type的值用ifelse判斷
      
      if(process.env.type== "build"){
          // 生產環境
          var website={
              publicPath:"http://192.168.0.104:1717/"
          }
      }else{
          // 開發環境
          var website={
              publicPath:"http://cdn.jspang.com/"
          }
      }
      複製程式碼
  • 方式二:要求 webpack 配置項輸入函式(無系統相容問題)

    • 在指令碼命令的配置(package.json 的 script 下) 中傳參

      // package.json 如下
         
      {
          "scripts": {
              "build": "webpack --env.NODE_ENV=local --env.production --progress"
          },
      }
      複製程式碼
    • webpack 配置檔案中 獲取環境變數

      必須對 webpack 配置進行一處修改。通常,module.exports 指向配置物件;要使用 env 變數,你必須將 module.exports 轉換成一個函式

      module.exports = env => {
        console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
        console.log('Production: ', env.production) // true
      
        return {
          entry: './src/index.js',
          output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
          }
        }
      };
      複製程式碼
    • 執行命令 npm run build,即可在控制檯中 看到輸出的 環境變數的值

      這樣就可以在 webpack 中通過區分不同的環境變數,來配置不同的webpack

  • 方式三:無系統相容問題,無過多要求

    • 在指令碼命令的配置(package.json 的 script 下) 中傳參

      // package.json 如下
         
      {
          "scripts": {
              "build": "webpack --prod"
          },
      }
      複製程式碼
    • webpack 配置檔案中 獲取引數如下

      // webpack 配置檔案中
      
      console.log(process.argv);
      // 輸出 [ node 路徑, webpack 路徑, '--prod']
      複製程式碼
    • 如果只是想傳遞一個布林值,獲取引數如下

      // 安裝 minimist
      
      npm i minimist@1.2.0 -D
      複製程式碼
      // webpack 配置檔案中
      const processArgv = require('minimist')(process.argv.slice(2));
      
      
      console.log(processArgv.prod);      // true
      複製程式碼

附言

  • 小夥伴們,有什麼問題 可以留言,一起交流哈

  • 接下來,我還會發布幾篇 webpack4.X 實戰文章,敬請關注

  • 我是一名熱衷於程式設計的前端開發,WX:ZXvictory66

相關文章