關於Webpack詳述系列文章 (第二篇)

  • 1.縮小文件搜索範圍
    • 1.1.1 include & exclude
      module:{
              rules:[
                  {
                       test:/\.js$/,
                       use:['babel-loader?cacheDirectory'],
                      include:path.resolve(__dirname,'src'),
                      exclude:/node_modules/
                  }
              ]
          }
    • 1.1.2 resolve.modules
      resolve: {
              modules: [path.resolve(__dirname, 'node_modules')]
      },

       

    • 1.1.3 resolve.mainFields

      • mainFields用於配置第三方模塊使用那個入口文件 isomorphic-fetch
        • 當target爲web或webworker時,值是["browswer","module","main"]
        • 當target爲其餘狀況時,值是["module","main"] ```js resolve: {
        • mainFields:['main'] }, `   
    • 1.1.4 resolve.alias
      • resolve.alias配置項經過別名來把原導入路徑映射成一個新的導入路徑 此優化方法會影響使用Tree-Shaking去除無效代碼
        alias: {
                    'react': path.resolve(__dirname, './node_modules/react/cjs/eact.production.min.js')
                }

         

    • 1.1.5 resolve.extensions 
      • 在導入語句沒帶文件後綴時,Webpack會自動帶上後綴後去嘗試詢問文件是否存在 默認後綴是 extensions: ['.js', '.json']
        • 後綴列表儘量小
        • 頻率最高的往前方
        • 導出語句裏儘量帶上後綴
          resolve: {
                  extensions: ['js']
          },

           

    • 1.1.6 module.noParse
      • module.noParse 配置項可讓 Webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理
        module: {
                noParse: [/react\.min\.js/]
            }

        被忽略掉的文件裏不該該包含 import 、 require 、 define 等模塊化語句css

  • 2.DLL 

    • .dll 爲後綴的文件稱爲動態連接庫,在一個動態連接庫中能夠包含給其餘模塊調用的函數和數據
      • 把基礎模塊獨立出來打包到單獨的動態鏈接庫裏
      • 當須要導入的模塊在動態鏈接庫裏的時候,模塊不能再次被打包,而是去動態鏈接庫裏獲取 dll-plugin
      • 2.1 定義Dll

      • DllPlugin插件: 用於打包出一個個動態鏈接庫
      • DllReferencePlugin: 在配置文件中引入DllPlugin插件打包好的動態鏈接庫
        module.exports = {
            entry: {
                react: ['react'] //react模塊打包到一個動態鏈接庫
            },
            output: {
                path: path.resolve(__dirname, 'dist'),
                filename: '[name].dll.js', //輸出動態鏈接庫的文件名稱
                library: '_dll_[name]' //全局變量名稱
            },
            plugins: [
                new webpack.DllPlugin({
                    name: '_dll_[name]', //和output.library中一致,值就是輸出的manifest.json中的 name值
                    path: path.join(__dirname, 'dist', '[name].manifest.json')
                })
            ]
        }
        webpack --config webpack.dll.config.js --mode production
      • 2.2 使用動態連接庫文件

        plugins: [
                new webpack.DllReferencePlugin({
                    manifest: require(path.join(__dirname, 'dist', 'react.manifest.json')),
                })
            ],
        webpack --config webpack.config.js --mode development

 

  • 3. HappyPack

    • HappyPack就能讓Webpack把任務分解給多個子進程去併發的執行,子進程處理完後再把結果發送給主進程。 happypack
      npm i happypack@next -D
      module: {
              rules: [{
                  test: /\.js$/,
                  //把對.js文件的處理轉交給id爲babel的HappyPack實例
                use: 'happypack/loader?id=babel',
                  include: path.resolve(__dirname, 'src'),
                  exclude: /node_modules/
              }, {
                  //把對.css文件的處理轉交給id爲css的HappyPack實例
                  test: /\.css$/,
                 use: 'happypack/loader?id=css',
                  include: path.resolve(__dirname, 'src')
              }],
              noParse: [/react\.min\.js/]
          },
      plugins: [
              //用惟一的標識符id來表明當前的HappyPack是用來處理一類特定文件
              new HappyPack({
                  id: 'babel',
                  //如何處理.js文件,和rules裏的配置相同
                  loaders: [{
                      loader: 'babel-loader',
                      query: {
                          presets: [
                              "env", "react"
                          ]
                      }
                  }]
              }),
              new HappyPack({
                  id: 'css',
                  loaders: ['style-loader', 'css-loader'],
                  threads: 4, //表明開啓幾個子進程去處理這一類型的文件
                  verbose: true //是否容許輸出日子
              })
          ],

       

  • 4. ParallelUglifyPlugin

    • ParallelUglifyPlugin能夠把對JS文件的串行壓縮變爲開啓多個子進程並行執行
      npm i -D webpack-parallel-uglify-plugin
      new ParallelUglifyPlugin({
                  workerCount: 3, //開啓幾個子進程去併發的執行壓縮。默認是當前運行電腦的 CPU 核數減去1
                  uglifyJS: {
                      output: {
                          beautify: false, //不須要格式化
                          comments: false, //不保留註釋
                      },
                      compress: {
                          warnings: false, // 在UglifyJs刪除沒有用到的代碼時不輸出警告
                          drop_console: true, // 刪除全部的 `console` 語句,能夠兼容ie瀏覽器
                          collapse_vars: true, // 內嵌定義了可是隻用到一次的變量
                          reduce_vars: true, // 提取出出現屢次可是沒有定義成變量去引用的靜態值
                      }
                  },
              })
  • 5. 服務器自動刷新

    • 咱們能夠監聽到本地源碼文件發生變化時,自動從新構建出可運行的代碼後再刷新瀏覽器
      • 5.1 文件監聽

        watch: true, //只有在開啓監聽模式時,watchOptions纔有意義
         watchOptions: {
            ignored: /node_modules/,
            aggregateTimeout: 300, //監聽到變化發生後等300ms再去執行動做,防止文件更新太快致使編譯頻率過高
            poll: 1000 //經過不停的詢問文件是否改變來判斷文件是否發生變化,默認每秒詢問1000次
         }
      • 5.2 文件監聽流程

        • webpack定時獲取文件的更新時間,並跟上次保存的時間進行比對,不一致就表示發生了變化,poll就用來配置每秒問多少次
        • 當檢測文件再也不發生變化,會先緩存起來,等待一段時間後以後再通知監聽者,這個等待時間經過aggregateTimeout配置
        • webpack只會監聽entry依賴的文件
        • 咱們須要儘量減小須要監聽的文件數量和檢查頻率,固然頻率的下降會致使靈敏度降低
      • 5.3 自動刷新瀏覽器

        devServer: {
                contentBase: './dist',
                inline: true
            },

        webpack負責監聽文件變化,webpack-dev-server負責刷新瀏覽器 這些文件會被打包到chunk中,它們會代理客戶端向服務器發起WebSocket鏈接node

         (webpack)-dev-server/client/overlay.js 3.58 KiB {0} [built]
         (webpack)-dev-server/client/socket.js 1.05 KiB {0} [built]
        ./node_modules/loglevel/lib/loglevel.js 7.68 KiB {0} [built]
        ./node_modules/strip-ansi/index.js 161 bytes {0} [built]
        ./node_modules/url/url.js 22.8 KiB {0} [built]
        (webpack)-dev-server/client?http://localhost:8080 7.75 KiB {0} [built]
         multi (webpack)-dev-server/client?http://localhost:8080 ./src/index.js 40 bytes {0} [built]
      • 5.4 模塊熱替換 

        • 模塊熱替換(Hot Module Replacement)的技術可在不刷新整個網頁的狀況下只更新指定的模塊 原理是當一個源碼發生變化時,只從新編譯發生變化的模塊,再用新輸出的模塊替換掉瀏覽器中對應的老模塊
          • 反應更快,時間更短
          • 不刷新網頁能夠保留網頁運行狀態
            devServer: {
                   hot:true
                }
            [./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
               [0] multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./src/index.js 52 bytes {main} [built]
            [./node_modules/webpack/hot/dev-server.js] (webpack)/hot/dev-server.js 1.66 KiB {main} [built]
            [./node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 77 bytes {main} [built]
            if (module.hot) {
                module.hot.accept('./index.js', function () {
                    console.log('accept index.js');
                });
            }

            優化模塊熱替換瀏覽器日誌react

            plugins: [
                    new webpack.NamedModulesPlugin(),
                    new webpack.HotModuleReplacementPlugin(),
            ]
            • 監聽更少的文件
            • 忽略掉 node_modules 目錄下的文件
  • 6.區分環境 

    • 在開發網頁的時候,通常都會有多套運行環境,例如:webpack

      • 在開發過程當中方便開發調試的環境。
      • 發佈到線上給用戶使用的運行環境。
    • 6.1 環境區別

      • 線上的代碼被壓縮
      • 開發環境可能會打印只有開發者才能看到的日誌
      • 開發環境和線上環境後端數據接口可能不一樣
    • 6.2 如何使用 

      if(process.env.NODE_ENV == 'production'){
           console.log('生產環境');
      }else{
          console.log('開發環境');
      }

      當你使用process模塊的時候,webpack會把process模塊打包進來git

      new webpack.DefinePlugin({
                   'process.env': {
                       NODE_ENV:JSON.stringify('production')
                   }
               }),

      定義環境變量的值時用 JSON.stringify 包裹字符串的緣由是環境變量的值須要是一個由雙引號包裹的字符串,而 JSON.stringify('production')的值正好等於'"production"'github

      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      })

 

    • 6.3 三種區分環境文案

      • 經過npm命令區分
      • 經過環境變量區分 webpack-merge
      • 代碼中區分
  • 7. CDN 

    • CDN 又叫內容分發網絡,經過把資源部署到世界各地,用戶在訪問時按照就近原則從離用戶最近的服務器獲取資源,從而加速資源的獲取速度。
      • HTML文件不緩存,放在本身的服務器上,關閉本身服務器的緩存,靜態資源的URL變成指向CDN服務器的地址
      • 靜態的JavaScript、CSS、圖片等文件開啓CDN和緩存,而且文件名帶上HASH值
      • 爲了並行加載不阻塞,把不一樣的靜態資源分配到不一樣的CDN服務器上
        output: {
                path: path.resolve(__dirname, 'dist'),
                filename: '[name]_[hash:8].js',
                publicPath: 'http://img.zhufengpeixun.cn'
            },

 

  • 8.Tree Shaking

    • Tree Shaking 能夠用來剔除JavaScript中用不上的死代碼。它依賴靜態的ES6模塊化語法,例如經過importexport導入導出。
    • 使用Tree
      • 不要編譯ES6模塊
        {
                         loader: 'babel-loader',
                         query: {
                             presets: [
                                 [
                                      "env", {
                                          modules: false //含義是關閉 Babel 的模塊轉換功能,保留本來的 ES6 模塊化語法
                                      }
                                 ],
                                 "react"
                             ]
                         }
                     }
        webpack --display-used-exports
        const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
        
         plugins: [
           new UglifyJSPlugin()
         ]
        webpack --display-used-exports --optimize-minimize
        webpack --mode  production

         

  • 9.提取公共代碼

    • 9.1 爲何須要提取公共代碼

      • 大網站有多個頁面,每一個頁面因爲採用相同技術棧和樣式代碼,會包含不少公共代碼,若是都包含進來會有問題
        • 相同的資源被重複的加載,浪費用戶的流量和服務器的成本;
        • 每一個頁面須要加載的資源太大,致使網頁首屏加載緩慢,影響用戶體驗。 若是能把公共代碼抽離成單獨文件進行加載能進行優化,能夠減小網絡傳輸流量,下降服務器成本
    • 9.2 如何提取

      • 基礎類庫,方便長期緩存
      • 頁面之間的公用代碼
      • 各個頁面單獨生成文件
    • 如何使用 common-chunk-and-vendor-chunk
      entry: {
              pageA: './src/pageA',
              pageB: './src/pageB'
      },
      
      optimization: {
              splitChunks: {
                  cacheGroups: {
                      commons: {
                          chunks: "initial",
                          minChunks: 2,
                          maxInitialRequests: 5, // The default limit is too small to showcase the effect
                          minSize: 0 // This is example is too small to create commons chunks
                      },
                      vendor: {
                          test: /node_modules/,
                          chunks: "initial",
                          name: "vendor",
                          priority: 10,
                          enforce: true
                      }
                  }
              }
          },

       

  • 10.開啓 Scope Hoisting

    • Scope Hoisting 可讓 Webpack 打包出來的代碼文件更小、運行的更快, 它又譯做 "做用域提高",是在 Webpack3 中新推出的功能。
      • 代碼體積更小,由於函數申明語句會產生大量代碼
      • 代碼在運行時由於建立的函數做用域更少了,內存開銷也隨之變小 hello.js
        export default 'Hello';
        import str from './hello.js';
        console.log(str);
        var util = ('Hello');
        console.log(util);

        函數由兩個變成了一個,hello.js 中定義的內容被直接注入到了 main.js 中web

      • const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
        
        module.exports = {
          resolve: {
            // 針對 Npm 中的第三方模塊優先採用 jsnext:main 中指向的 ES6 模塊化語法的文件
            mainFields: ['jsnext:main', 'browser', 'main']
          },
          plugins: [
            // 開啓 Scope Hoisting
            new ModuleConcatenationPlugin(),
          ],
        };
        --display-optimization-bailout
  • 11.代碼分離

    • 代碼分離是 webpack 中最引人注目的特性之一。此特性可以把代碼分離到不一樣的 bundle 中,而後能夠按需加載或並行加載這些文件。 有三種經常使用的代碼分離方法:
      • 入口起點:使用 entry 配置手動地分離代碼。
      • 防止重複:使用 splitChunks 去重和分離 chunk。
      • 動態導入:經過模塊的內聯函數調用來分離代碼。
    • 11.1 多個入口

      entry: {
        index: './src/index.js',
        another: './src/another-module.js'
      }
    • 11.2 防止重複 

      • splitChunks能夠將公共的依賴模塊提提取到一個新生成的 chunk. common-chunk-and-vendor-chunk
        optimization: {
                splitChunks: {
                    cacheGroups: {
                        commons: {
                            chunks: "initial",
                            minChunks: 2
                        },
                        vendor: {
                            test: /node_modules/,
                            chunks: "initial",
                            name: "vendor",
                        }
                    }

         

    • 11.3 動態導入和懶加載(dynamic imports)

      • 用戶當前須要用什麼功能就只加載這個功能對應的代碼,也就是所謂的按需加載 在給單頁應用作按需加載優化時,通常採用如下原則:
        • 對網站功能進行劃分,每一類一個chunk
        • 對於首次打開頁面須要的功能直接加載,儘快展現給用戶
        • 某些依賴大量代碼的功能點能夠按需加載
        • 被分割出去的代碼須要一個按需加載的時機
          document
              .getElementById('clickMe')
              .addEventListener('click', () => {
                  import (/*webpackChunkName:"alert"*/
                  './alert').then(alert => {
                      console.log(alert);
          
                      alert.default('hello');
                  });
              });
          loaders: [
                          {
                              loader: 'babel-loader',
                              query: {
                                  presets: ["env", "stage-0", "react"]
                              }
                          }
                      ]

           

  • 12. webpack-dev-middleware 插件 

    • webpack-dev-middleware 插件對更改的文件進行監控,編譯,通常和 webpack-hot-middleware 配合使用,實現熱加載功能 webpack-dev-middlewarewebpack-hot-middleware
      const path = require("path")
      const express = require("express")
      const webpack = require("webpack")
      const webpackDevMiddleware = require("webpack-dev-middleware")
      const webpackConfig = require('./webpack.config.js')
      const app = express(),
                  DIST_DIR = path.join(__dirname, "dist"),// 設置靜態訪問文件路徑
                  PORT = 9090, // 設置啓動端口
                  complier = webpack(webpackConfig)
      
      app.use(webpackDevMiddleware(complier, {
      //綁定中間件的公共路徑,與webpack配置的路徑相同
          publicPath: webpackConfig.output.publicPath,
          quiet: true  //向控制檯顯示內容
      }))
      
      // 這個方法和下邊註釋的方法做用同樣,就是設置訪問靜態文件的路徑
      app.use(express.static(DIST_DIR))
      app.listen(PORT,function(){
          console.log("成功啓動:localhost:"+ PORT)
      })
  • 13. 輸出分析 

    • profile:記錄下構建過程當中的耗時信息;
    • json:以 JSON 的格式輸出構建結果,最後只輸出一個 .json 文件,這個文件中包括全部構建相關的信息。
      webpack --profile --json > stats.json

      Webpack 官方提供了一個可視化分析工具 Webpack Analyseexpress

    • Modules:展現全部的模塊,每一個模塊對應一個文件。而且還包含全部模塊之間的依賴關係圖、模塊路徑、模塊ID、模塊所屬 Chunk、模塊大小;
    • Chunks:展現全部的代碼塊,一個代碼塊中包含多個模塊。而且還包含代碼塊的ID、名稱、大小、每一個代碼塊包含的模塊數量,以及代碼塊之間的依賴關係圖;
    • Assets:展現全部輸出的文件資源,包括 .js、.css、圖片等。而且還包括文件名稱、大小、該文件來自哪一個代碼塊;
    • Warnings:展現構建過程當中出現的全部警告信息;
    • Errors:展現構建過程當中出現的全部錯誤信息;
    • Hints:展現處理每一個模塊的過程當中的耗時。

  

備註:

  libraryTarget 和 library:

    當用 Webpack 去構建一個能夠被其餘模塊導入使用的庫時須要用到它們。   npm

      • output.libraryTarget 配置以何種方式導出庫。
      • output.library 配置導出庫的名稱。 它們一般搭配在一塊兒使用。

  output.libraryTarget 是字符串的枚舉類型,支持如下配置。 json

  var (默認)

  編寫的庫將經過 var 被賦值給經過 library 指定名稱的變量。

  假如配置了 output.library='LibraryName',則輸出和使用的代碼以下:

// Webpack 輸出的代碼
var LibraryName = lib_code;

// 使用庫的方法
LibraryName.doSomething();
假如 output.library 爲空,則將直接輸出:

  lib_code 其中 lib_code 代指導出庫的代碼內容,是有返回值的一個自執行函數。

  commonjs

  編寫的庫將經過 CommonJS 規範導出。

  假如配置了 output.library='LibraryName',則輸出和使用的代碼以下:

// Webpack 輸出的代碼
exports['LibraryName'] = lib_code;

// 使用庫的方法
require('library-name-in-npm')['LibraryName'].doSomething();
其中 library-name-in-npm 是指模塊發佈到 Npm 代碼倉庫時的名稱。

  commonjs2

  編寫的庫將經過 CommonJS2 規範導出,輸出和使用的代碼以下:

// Webpack 輸出的代碼
module.exports = lib_code;

// 使用庫的方法
require('library-name-in-npm').doSomething();
CommonJS2 和 CommonJS 規範很類似,差異在於 CommonJS 只能用 exports 導出,而 CommonJS2 在 CommonJS 的基礎上增長了 module.exports 的導出方式。

在 output.libraryTarget 爲 commonjs2 時,配置 output.library 將沒有意義。

  this

  編寫的庫將經過 this 被賦值給經過 library 指定的名稱,輸出和使用的代碼以下:

// Webpack 輸出的代碼
this['LibraryName'] = lib_code;

// 使用庫的方法
this.LibraryName.doSomething();

  window

  編寫的庫將經過 window 被賦值給經過 library 指定的名稱,即把庫掛載到 window 上,輸出和使用的代碼以下:

// Webpack 輸出的代碼
window['LibraryName'] = lib_code;

// 使用庫的方法
window.LibraryName.doSomething();

  global 

編寫的庫將經過 global 被賦值給經過 library 指定的名稱,即把庫掛載到 global 上,輸出和使用的代碼以下:

// Webpack 輸出的代碼
global['LibraryName'] = lib_code;

// 使用庫的方法
global.LibraryName.doSomething();
相關文章
相關標籤/搜索