從基礎到實戰 手摸手帶你掌握新版Webpack4.0詳解

無敵UFO發表於2019-04-14

專案地址 github.com/wudiufo/Web…

知識點概覽:

Loader,HMR ,Create React App, Caching, Plugin, SourceMap,Vue Cli 3.0 ,Shimming, WebpackDevServer,TreeShaking, CodeSplitting, Babel, React , Library, Eslint ,PWA, Vue, Mode,效能優化,多頁應用,原理, PreLoading, PreFetching ,環境變數,TypeScript

收穫:

徹底學會Webpack的配置 理解 Webpack的作用及原理 上手專案的打包過程配置 擁有工程化的前端思維 步入高階前端工程師行列

一:初識Webpack

官網圖鎮樓:

從基礎到實戰 手摸手帶你掌握新版Webpack4.0詳解

1. 1 什麼是WebPack

webpack 是一個現代 JavaScript 應用程式的靜態模組打包工具:它做的事情是,分析你的專案結構,找到JavaScript模組以及其它的一些瀏覽器不能直接執行的擴充語言(Scss,TypeScript等),並生成一個或多個 bundle,將其打包為合適的格式以供瀏覽器使用。

webpack構建:

構建就是把原始碼轉換成釋出到線上的可執行 JavaScrip、CSS、HTML 程式碼,包括如下內容。

1.程式碼轉換:TypeScript 編譯成 JavaScript、SCSS或Less 編譯成 CSS 等。

2.檔案優化:壓縮 JavaScript、CSS、HTML 程式碼,壓縮合並圖片等。

3.程式碼分割:提取多個頁面的公共程式碼、提取首屏不需要執行部分的程式碼讓其非同步載入。

4.模組合併:在採用模組化的專案裡會有很多個模組和檔案,需要構建功能把模組分類合併成一個檔案。

5.自動重新整理:監聽本地原始碼的變化,自動重新構建、重新整理瀏覽器,nodemon。

6.程式碼校驗:在程式碼被提交到倉庫前需要校驗程式碼是否符合規範,以及單元測試是否通過。

7.自動釋出:更新完程式碼後,自動構建出線上釋出程式碼並傳輸給釋出系統。

構建其實是工程化、自動化思想在前端開發中的體現,把一系列流程用程式碼去實現,讓程式碼自動化地執行這一系列複雜的流程。 構建給前端開發注入了更大的活力,解放了我們的生產力,更加方便了我們的開發。

1.2 什麼是 webpack 模組

1.3 搭建Webpack環境

  • 去官網下載node

    // 檢視node版本號
    node -v
    // 檢視npm版本號
    npm -v
    複製程式碼

1.4 初始化專案

mkdir webpack-productname
cd webpack-productname 
//初始化webpack配置清單package.json
npm init -y   
複製程式碼

1.5 安裝webpack

//全域性安裝(不推薦),因為如果有兩個專案用了webpack不同版本,就會出現版本不統一執行不起來的情況。只有卸了當前版本安裝對應版本非常麻煩。
npm install webpack webpack-cli -g
//檢視版本
webpack -v
//全域性解除安裝
npm uninstall webpack webpack-cli -g
複製程式碼
//在專案裡安裝webpack(推薦使用)。可以在不同專案中使用不同的webpack版本。
cd webpack-productname
npm install webpack webpack-cli -D
//檢視版本
npx webpack -v
//檢視對用包的詳細資訊
npm info webpack
//安裝指定版本包
npm install webpack@4.16.1 webpack-cli -D
複製程式碼

注意:

由於npm安裝走的是國外的網路,比較慢容易出現安裝失敗的現象。

可以用yarn安裝,首先得全域性安裝yarn,npm install yarn -g

或使用nrm快速切換npm源,首先得全域性安裝nrm, npm install -g nrm

nrm 使用:

nrm ls 檢視可選源。

nrm test npm 測試速度。看哪個快就use哪個。

nrm use cnpm 使用cnpm 。

webpack-cli:使我們們可以在命令列里正確的使用webpack

1.6 webpack的配置檔案

webpack 開箱即用,可以無需使用任何配置檔案。然而,webpack 會假定專案的入口起點為 src/index,然後會在 dist/main.js 輸出結果,並且在生產環境開啟壓縮和優化。通常,你的專案還需要繼續擴充套件此能力,為此你可以在專案根目錄下建立一個 webpack.config.js 檔案,webpack 會自動使用它。

在專案根目錄下建立 webpack.config.js 檔案,這是webpack預設配置檔案

const path = require('path')

module.exports = {
    //預設是production,打包的檔案預設被壓縮。開發時可以設定為development,不被壓縮
    mode:'production', 
    //打包專案的入口檔案
    entry: './index.js',
    //打包專案的輸出檔案
    output: {
        //自定義打包輸出檔名
        filename:'bundle.js',
        //輸出檔案的絕對路徑
        path: path.resolve(__dirname,'bundle')
    }
}
複製程式碼

也可以自己指定配置檔案來完成webpack的打包:

npx webpack --config + 自定義配置檔案
複製程式碼

詳細請看官方文件:概念 配置

1.7 webpack打包輸出內容

執行 `npm run build` 後,在控制檯輸出

Hash:1b245e275a547956bf52 //本次打包對應唯一一個hash值
Version:webpack 4.29.6 //本次打包對應webpack版本
Time:162ms Built at:2019-4-11 23:13:43 //本次打包耗時,及打包的時間
Asset Size Chunks Chunk Names //打包後的檔名,大小,id,入口檔名
bundle.js 1.36 KiB 0 [emitted] main 
Entrypoint main=bundle.js
[0]./src/index.js 159 bytes {0}[built]
[1]./src/header.js 187 bytes {e}[built]
[2]./src/sidebar.js 193 bytes {e}[built]
[3]./src/content.js 193 bytes {e} [built]

複製程式碼

二:Webpack核心概念

LOADER

2.1 什麼是Loader

webpack可以使用 loader 來預處理檔案,就是通過使用不同的Loader,webpack可以把不同的靜態檔案都編譯成js檔案,比如css,sass,less,ES6/7,vue,JSX等。

使用Loader打包靜態資源

支援載入圖片檔案

需要安裝 file-loader:解決CSS等檔案中的引入圖片路徑問題

 npm install file-loader -D
複製程式碼

webpack.config.js 裡新增 loader 配置

module.exports = {
    //配置模組,主要用來配置不同檔案的載入器
  module: {
      //配置模組規則
    rules: [
      {
        test: /\.(png|jpg|gif)$/, //正則匹配要使用相應loader的檔案
        use: [
          {
            loader: 'file-loader', //要用到的loader
              options: {
                  //palceholder佔位符
                  name:'[name].[ext]', //打包後的圖片名字,字尾和打包的之前的圖片一樣
                  outputPath: 'images/' //圖片打包後的地址
              },
          },
        ],
      },
    ],
  },
};
複製程式碼

詳細請看官方文件:file-loader

將小圖片轉換成base64格式

需要安裝 url-loader:當圖片小於limit的時候會把圖片BASE64編碼,大於limit引數的時候還是使用file-loader 進行拷貝

npm install url-loader -D
複製程式碼

webpack.config.js 裡新增 loader 配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif|bmp/)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              name:'[name].[ext]',
              outputPath: 'images/',
              limit: 8192 //小於8192kb,就可以轉化成base64格式。大於就會打包成檔案格式
            }
          }
        ]
      }
    ]
  }
}
複製程式碼

詳細請看官方文件:url-loader


支援載入樣式CSS檔案

需要安裝 css-loader style-loader:

npm install css-loader style-loader -D
複製程式碼

webpack.config.js 裡新增 loader 配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, //匹配以css為字尾的檔案
        use: ['style-loader', 'css-loader'],//loader的執行順序是從右向左,從下到上。css-loader:分析幾個css檔案之間的關係,最終合併為一個css。style-loader:在得到css生成的內容時,把其掛載到html的head裡,成為內聯樣式。
      },
    ],
  },
};
複製程式碼

支援載入樣式SASS檔案

需要安裝 sass-loader node-sass:

npm install sass-loader node-sass -D
複製程式碼

webpack.config.js 裡新增 loader 配置

module.exports = {
    ...
    module: {
        rules: [{
            test: /\.scss$/,
            use: [
                "style-loader", // 將 JS 字串生成為 style 節點
                "css-loader", // 將 CSS 轉化成 CommonJS 模組
                "sass-loader" // 將 Sass 編譯成 CSS,預設使用 Node Sass
            ]
        }]
    }
};
複製程式碼

為 css 樣式屬性加不同瀏覽器的字首

為了瀏覽器的相容性,有時候我們必須加入-webkit,-ms,-o,-moz這些字首

  • Trident核心:主要代表為IE瀏覽器, 字首為-ms
  • Gecko核心:主要代表為Firefox, 字首為-moz
  • Presto核心:主要代表為Opera, 字首為-o
  • Webkit核心:產要代表為Chrome和Safari, 字首為-webkit
npm i postcss-loader autoprefixer -D
複製程式碼

在專案跟目錄下建立 postcss.config.js

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}
複製程式碼

webpack.config.js

module.exports = {
    ...
    module: {
        rules: [{
            test: /\.scss$/,
            use: [
                "style-loader", // 將 JS 字串生成為 style 節點
                "css-loader", // 將 CSS 轉化成 CommonJS 模組
                "sass-loader", // 將 Sass 編譯成 CSS,預設使用 Node Sass
                'postcss-loader'
            ]
        }]
    }
};
複製程式碼

給loader加一些配置項:

webpack.config.js

module.exports = {
    ...
    module: {
        rules: [{
            test: /\.scss$/,
            use: [
                "style-loader", 
                {
                 	loader: "css-loader",
                    options:{
                        importLoaders:2 ,//如果sass檔案裡還引入了另外一個sass檔案,另一個檔案還會從postcss-loader向上解析。如果不加,就直接從css-loader開始解析。
                        modules: true //開啟css的模組打包。css樣式不會和其他模組發生耦合和衝突
                    }
                }, 
                "sass-loader", 
                'postcss-loader'
            ]
        }]
    }
};
複製程式碼

為字型圖示檔案配loader

阿里巴巴向量圖示庫中,把需要的字型圖示下載到本地,解壓。將iconfont.eot iconfont.svg iconfont.ttf iconfont.woff 檔案放入到專案中,在src中新建一個放字型圖示的資料夾font。將iconfont.css檔案拷貝到專案中,自己改一下引入字型圖示的路徑。

需要安裝 file-loader:

npm i file-loader -D
複製程式碼

webpack.config.js

module.exports = {
    ...
    module: {
        rules: [{
            test: /\.(eot|ttf|svg|woff)$/,
            use:{
                loader:'file-loader'
            }
        },
            ]
        }]
    }
};
複製程式碼

詳細請看官方文件:asset-management


plugin : 可以在webpack執行到某個時刻的時候,幫你做一些事情

使用plugins讓打包更便捷

HtmlWebpackPlugin :htmlWebpackPlugin 會在打包結束後,自動生成一個html檔案,並把打包生成的js自動引入到這個html檔案中

安裝:npm i html-webpack-plugin -D

基本用法:在 webpack.config.js 中:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [new HtmlWebpackPlugin({
        template: 'src/index.html' //以index.html為模板,把打包生成的js自動引入到這個html檔案中
    })]
};
複製程式碼

CleanWebpackPlugin :自動清除上一次打包的dist檔案

安裝:npm i clean-webpack-plugin -D

基本用法:在 webpack.config.js 中:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [
        new HtmlWebpackPlugin({
        template: 'src/index.html' //在打包之後,以.html為模板,把打包生成的js自動引入到這個html檔案中
    }),
        new CleanWebpackPlugin(['dist']), // 在打包之前,可以刪除dist資料夾下的所有內容
    
    ]
};
複製程式碼

Entry與Output的基礎配置

在打包多入口檔案時的配置

基本用法:在 webpack.config.js 中:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');

module.exports = {
  entry: {
   	main: './src/index.js',
    sub: './src/index.js'
  },
  output: {
    publicPath: 'http://cdn.com.cn', //將注入到html中的js檔案前面加上地址
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
    plugins: [
        new HtmlWebpackPlugin({
        template: 'src/index.html' //在打包之後,以.html為模板,把打包生成的js自動引入到這個html檔案中
    }),
        new CleanWebpackPlugin(['dist']), // 在打包之前,可以刪除dist資料夾下的所有內容
    
    ]
};
複製程式碼

詳細請看官網:Output output-management


SourceMap 的配置

sourcemap:打包編譯後的檔案和原始檔的對映關係,用於開發者除錯用。

  • source-map 把對映檔案生成到單獨的檔案,最完整但最慢

  • cheap-module-source-map 在一個單獨的檔案中產生一個不帶列對映的Map

  • eval-source-map 使用eval打包原始檔模組,在同一個檔案中生成完整sourcemap

  • cheap-module-eval-source-map sourcemap和打包後的JS同行顯示,沒有對映列

    development環境推薦使用: devtool: 'cheap-module-eval-source-map', production環境推薦使用: devtool: 'cheap-module-source-map',

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
    devtool: 'cheap-module-eval-source-map',
	//devtool:'none',//在開發者模式下,預設開啟sourcemap,將其關閉
    //devtool:'source-map'//開啟對映打包會變慢
    //devtool:'inline-source-map'//不單獨生成.map檔案,會將生成的對映檔案以base64的形式插入到打包後的js檔案的底部
    //devtool:'cheap-inline-source-map'//程式碼出錯提示不用精確顯示第幾行的第幾個字元出錯,只顯示第幾行出錯,會提高一些效能
    //devtool:'cheap-module-inline-source-map'//不僅管自己的業務程式碼出錯,也管第三方模組和loader的一些報錯
    //devtool:'eval'//執行效率最快,效能最好,但是針對比較複雜的程式碼的情況下,提示內容不全面
	//devtool: 'cheap-module-eval-source-map',//在開發環境推薦使用,提示比較全,打包速度比較快
    //devtool: 'cheap-module-source-map',//在生產環境中推薦使用,提示效果會好一些
	
	
	entry: {
		main: './src/index.js'
	},
	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

詳細請看官網:devtool


使用WebpackDevServer 提升開發效率

解決每次在src裡編寫完程式碼都需要手動重新執行 npm run dev

1.在 package.json 中配置

{
  "name": "haiyang",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",// 加--watch自動監聽程式碼的變化
    
  },
  
}

複製程式碼

2.在 webpack.config.js 中,加 devServer

安裝 npm i webpack-dev-server –D

  • contentBase :配置開發服務執行時的檔案根目錄
  • open :自動開啟瀏覽器
  • host:開發伺服器監聽的主機地址
  • compress :開發伺服器是否啟動gzip等壓縮
  • port:開發伺服器監聽的埠
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
+	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
    	proxy: {//配置跨域,訪問的域名會被代理到本地的3000埠
      		'/api': 'http://localhost:3000'
    	}
	},
	module: {
		rules: []
	},
	plugins: [],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

在 package.json 中:

{
  "name": "haiyang",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",// 加--watch自動監聽程式碼的變化
    "start": "webpack-dev-server",//配置熱更新
	
  },
 
}

複製程式碼

詳細請看官網 :dev-server

擴充知識:自己寫一個類似webpackdevserver的工具

瞭解即可,功能不全,自行擴充套件。

在 package.json 中:

{
  "name": "haiyang",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",// 加--watch自動監聽程式碼的變化
    "start": "webpack-dev-server",//配置熱更新
+	"server" : "node server.js" //自己寫一個類似webpackdevserver的工具
  },
 
}

複製程式碼

安裝 :npm i express webpack-dev-middleware -D

在 專案根目錄下建立 server.js 檔案

在 server.js 中

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config);

const app = express();

app.use(webpackDevMiddleware(complier, {}));

app.listen(3000, () => {
	console.log('server is running');
});
複製程式碼

模組熱替換(hot module replacement)

在 package.json 中:

{
  "name": "haiyang",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server" //將檔案打包到記憶體中,有助於開發
  },
}

複製程式碼

在 webpack.config.js 中

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
+		hot: true,//開啟熱更新
+		hotOnly: true//儘管html功能沒有實現,也不讓瀏覽器重新整理
	},
	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
+		new webpack.HotModuleReplacementPlugin() //使用模組熱更新外掛
	],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

index.js

//如果模組啟用了HMR,就可以用 module.hot.accept(),監聽模組的更新。
if (module.hot) {
  module.hot.accept('./library.js', function() {
    // 使用更新過的 library 模組執行某些操作...
  })
}
複製程式碼

注意點:

引入css,用框架Vue,React 時,不需要寫 module.hot.accept(),因為在使用css-loader,vue-loader,babel-preset時,就已經配置好了HMR,不需要自己寫

詳細請看官方文件:hot-module-replacement api/hot-module-replacement concepts/hot-module-replacement


使用 Babel 處理 ES6/7 語法 轉義為ES5

BABEL官網:babeljs.io/setup

安裝依賴包:

npm i babel-loader @babel/core @babel/preset-env -D
//生產依賴,相容低版本瀏覽器
npm install --save @babel/polyfill
複製程式碼

在 webpack.config.js 中

module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/,//不需要對第三方模組進行轉換,耗費效能
     	loader: "babel-loader" ,
        options:{
            "presets": [["@babel/preset-env",{
                targets: {//這個專案執行在大於什麼版本的瀏覽器上,已經支援es6的語法的高版本瀏覽器就不需要轉義成es5了
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1",
                  },
                useBuiltIns:'usage' //按需新增polyfill,把用到的程式碼都轉成低版本瀏覽器相容的
            }]]
        }
    }
  ]
}
複製程式碼

在 index.js 中:

//在業務程式碼執行之前最頂部匯入
import "@babel/polyfill";
複製程式碼

注意:在開發類庫,第三方模組或元件庫時不能用 @babel/polyfill 這種方案,因為會把宣告的變數變成全域性變數,會汙染全域性環境。

安裝:

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
複製程式碼

在 webpack.config.js 中

module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/,//不需要對第三方模組進行轉換,耗費效能
     	loader: "babel-loader" ,
        options:{
            "plugins": [["@babel/plugin-transform-runtime",{
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }]]
        }
    }
  ]
}
複製程式碼

由於babel需要配置的內容非常多,我們需要在專案根目錄下建立一個 .babelrc 檔案。

就不需要在 webpack.config.js 中寫 babel 的配置了。

.babelrc 中:

{
            "plugins": [["@babel/plugin-transform-runtime",{
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }]]
        }
複製程式碼

配置 React 程式碼的打包

業務程式碼:

.babelrc 中:

	{ 
            "presets": [
                ["@babel/preset-env",{
                targets: {
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1",
                  },
                useBuiltIns:'usage' 
            		}
                ],
                "@babel/preset-react"
            ]
        }
//執行順序:從下往上,從右向左的順序
複製程式碼

安裝:

npm i react react-dom --save
npm install --save-dev @babel/preset-react
複製程式碼

詳細內容請看官網:babel-loader


三:Webpack進階

Tree Shaking:只支援 ES Module 例如 importexport 的靜態結構特性的引入。當引入一個模組時,不引入所有的程式碼,只引入需要的程式碼

在 webpack.config.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true
	},
	module: {
		rules: []
	},
	plugins: [],
+	optimization: { //在開發環境中加,生產環境不加
		usedExports: true
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

在 package.json 中:

{
+ "sideEffects": ["*.css"], //對 所有的css檔案 不使用Tree shaking。如果填 false,就是都需要用到Tree shaking
}
複製程式碼

詳細內容請看官網:tree-shaking


Develoment 和Production模式的區分打包

在專案根目錄下建立兩個檔案,webpack.dev.js,webpack.prod.js

webpack.dev.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

webpack.prod.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
	entry: {
		main: './src/index.js'
	},
	
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		
	],
	
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

在 package.json 中:

{
  "scripts": {
    "dev": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
}

複製程式碼

解決 webpack.dev.js,webpack.prod.js 存在大量重複程式碼,在專案根目錄下建立一個 webpack.common.js 檔案,把公共程式碼提取出來

安裝 :

npm i webpack-merge -D 
複製程式碼

webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {

	entry: {
		main: './src/index.js'
	},
	
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
        new CleanWebpackPlugin(['dist'],{
            root:path.resolve(__dirname,'../')
        }),
		
	],
	
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge')
const commenConfig = require('./webpack.commin.js')

const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true
	},
	
	plugins: [	
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	},	
}
//將開發配置和公共配置做結合
module.exports = merge(commenConfig, devConfig)
複製程式碼

webpack.prod.js

const merge = require('webpack-merge')
const commenConfig = require('./webpack.commin.js')

const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
}
//將線上配置和公共配置做結合
module.exports = merge(commenConfig, prodConfig)
複製程式碼

最後在根目錄下建立一個build資料夾,將 webpack.common.js , webpack.dev.js ,webpack.prod.js 放在build資料夾下,統一管理。

在 package.json 中:

{
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
}
複製程式碼

詳細請看官網文件:guides/production


Webpack和Code Splitting

安裝: npm i lodash --save npm i babel-plugin-dynamic-import-webpack -D

程式碼分割,和webpack無關,為了提升效能 webpack中實現程式碼分割,兩種方式:

第一種方法:同步程式碼: 只需要在webpack.common.js中做optimization的配置即可

第二種方法:非同步程式碼(import): 非同步程式碼,無需做任何配置,會自動進行程式碼分割,放置到新的檔案中

第一種方法:在 webpack.common.js 中


module.exports = {
	entry: {
		main: './src/index.js'
	},
	
	module: {
		rules: []
	},
	plugins: [],
+    optimization:{
+       splitChunks:{ //啟動程式碼分割,有預設配置項
+            chunks:'all'
+        }  
+    },
	
	output: {}
}
複製程式碼

第二種方法在 .babelrc 中:

{
	presets: [
		[
			"@babel/preset-env", {
				targets: {
					chrome: "67",
				},
				useBuiltIns: 'usage'
			}
		],
		"@babel/preset-react"
	],
+	plugins: ["dynamic-import-webpack"]
}

複製程式碼

詳細內容請看官網:code-splitting


SplitChunksPlugin 配置引數詳解

安裝:npm install --save-dev @babeL/plugin-syntax-dynamic-import

在業務 index.js 中:


 function getComponent() {
 	return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
 		var element = document.createElement('div');
		element.innerHTML = _.join(['1', '2'], '-');
 		return element;
	})
}

 getComponent().then(element => {
 	document.body.appendChild(element);
 });

複製程式碼

.babelrc 中:

{
	presets: [
		[
			"@babel/preset-env", {
				targets: {
					chrome: "67",
				},
				useBuiltIns: 'usage'
			}
		],
		"@babel/preset-react"
	],
+	plugins: ["@babeL/plugin-syntax-dynamic-import"]
}

複製程式碼

在 webpack.common.js 中:


module.exports = {
	entry: {
		main: './src/index.js'
	},
	
	module: {
		rules: []
	},
	plugins: [],
+    optimization:{
+       splitChunks:{ //啟動程式碼分割,不寫有預設配置項
+            chunks: 'all',//引數all/initial/async,只對所有/同步/非同步進行程式碼分割
              minSize: 30000, //大於30kb才會對程式碼分割
              maxSize: 0,
              minChunks: 1,//打包生成的檔案,當一個模組至少用多少次時才會進行程式碼分割
              maxAsyncRequests: 5,//同時載入的模組數最多是5個
              maxInitialRequests: 3,//入口檔案最多3個模組會做程式碼分割,否則不會
              automaticNameDelimiter: '~',//檔案自動生成的連線符
              name: true,
            cacheGroups:{//對同步程式碼走快取組
             vendors: {
                  test: /[\\/]node_modules[\\/]/,
                  priority: -10,//誰優先順序大就把打包後的檔案放到哪個組
    			filename:'vendors.js'
                },
            default: {
              minChunks: 2,
              priority: -20,
              reuseExistingChunk: true,//模組已經被打包過了,就不用再打包了,複用之前的就可以
              filename:'common.js' //打包之後的檔名   
            }
        }
+        }  
+    },
	
	output: {}
}
複製程式碼

詳細請看官方文件:split-chunks-plugin


Lazy Loading 懶載入,Chunk是什麼?

使用者當前需要用什麼功能就只載入這個功能對應的程式碼,也就是所謂的按需載入 在給單頁應用做按需載入優化時,一般採用以下原則:

  • 對網站功能進行劃分,每一類一個chunk
  • 對於首次開啟頁面需要的功能直接載入,儘快展示給使用者
  • 某些依賴大量程式碼的功能點可以按需載入
  • 被分割出去的程式碼需要一個按需載入的時機

每一個檔案就是一個 chunk

詳細請看官方文件:lazy-loading


打包分析,Preloading,Prefetching

開啟網址:webpack分析工具:https://github.com/webpack/analyse

在 package.json 中

{  
  "scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js", //把打包過程的描述放在stats.json檔案中
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
}

複製程式碼

在控制檯執行 npm run dev-build ,在根目錄下生成 stats.json 檔案。開啟網址 http://webpack.github.io/analyse/ ,把stats.json檔案傳上去,會出現分析結果。

詳細請看官方文件:bundle-analysis 打包分析工具

介紹 webpack-bundle-analyzer 的使用:

通過使用webpack-bundle-analyzer可以看到專案各模組的大小,可以按需優化。

官網圖鎮樓:

從基礎到實戰 手摸手帶你掌握新版Webpack4.0詳解

安裝:

# NPM
npm install --save-dev webpack-bundle-analyzer
# Yarn
yarn add -D webpack-bundle-analyzer
複製程式碼

配置:在 webpack.config.js 中:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin(
    	{
              analyzerMode: 'server',
              analyzerHost: '127.0.0.1',
              analyzerPort: 8889,
              reportFilename: 'report.html',
              defaultSizes: 'parsed',
              openAnalyzer: true,
              generateStatsFile: false,
              statsFilename: 'stats.json',
              statsOptions: null,
              logLevel: 'info'
            }
    )
  ]
}
複製程式碼

輸出:在 package.json 中:

"analyz": "NODE_ENV=production npm_config_report=true npm run build"
複製程式碼

線上分析:在 控制檯輸入:

webpack --profile --json > stats.json
複製程式碼
  • profile:記錄下構建過程中的耗時資訊;
  • json:以 JSON 的格式輸出構建結果,最後只輸出一個 .json 檔案,這個檔案中包括所有構建相關的資訊。
  • Webpack 官方提供了一個視覺化分析工具 Webpack Analyse
  • Modules:展示所有的模組,每個模組對應一個檔案。並且還包含所有模組之間的依賴關係圖、模組路徑、模組ID、模組所屬 Chunk、模組大小;
  • Chunks:展示所有的程式碼塊,一個程式碼塊中包含多個模組。並且還包含程式碼塊的ID、名稱、大小、每個程式碼塊包含的模組數量,以及程式碼塊之間的依賴關係圖;
  • Assets:展示所有輸出的檔案資源,包括 .js、.css、圖片等。並且還包括檔名稱、大小、該檔案來自哪個程式碼塊;
  • Warnings:展示構建過程中出現的所有警告資訊;
  • Errors:展示構建過程中出現的所有錯誤資訊;
  • Hints:展示處理每個模組的過程中的耗時。

開啟谷歌控制檯檢視程式碼使用率,按 ctrl+shift+p ,輸入 coverage 檢視。

預取/預載入模組(prefetch/preload module)

假如有一個HomePage元件,其內部有一個LoginButton.js登陸元件,再點選後按需載入 LoginModel 元件。

LoginButton.js:

import(/* webpackPrefetch: true */ 'LoginModal');
複製程式碼

這會生成 <link rel="prefetch" href="login-modal-chunk.js"> 並追加到頁面頭部,指示著瀏覽器在閒置時間預取 login-modal-chunk.js 檔案。就是說,只要首頁載入完成,就會在空閒時間把登入模組也載入了。

總結:

/* webpackPrefetch: true */:把主載入流程載入完畢,在空閒時在載入其他,等再點選其他時,只需要從快取中讀取即可,效能更好。推薦使用,提高程式碼利用率。把一些互動後才能用到的程式碼寫到非同步元件裡,通過懶載入的形式,去把這塊的程式碼邏輯載入進來,效能提升,頁面訪問速度更快。

/* webpackPreload: true */: 和主載入流程一起並行載入。

詳細請看官方文件:prefetchingpreloading-modules


CSS檔案的程式碼分割

在 webpack.config.js 中

module.exports = {
	entry: {
		main: './src/index.js'
	},
    module: {
       
    },
    plugins: [],
	optimization: {
		splitChunks: {
      	 chunks: 'all'
    }
	},
	output: {
		filename: '[name].js',//入口檔案打包後生成的檔名
+         chunkFilename: '[name].chunk.js',//main.js非同步載入的間接的js檔案。用來打包import('module')方法中引入的模組
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

因為CSS的下載和JS可以並行,當一個HTML檔案很大的時候,我們可以把CSS單獨提取出來載入

  • mini-css-extract-plugin:一般線上上環境使用這個外掛,因為在開發環境中不支援HMR。
  • filename 打包入口檔案
  • chunkFilename 用來打包import('module')方法中引入的模組

安裝 :

//抽離css檔案
npm install --save-dev mini-css-extract-plugin
//壓縮css檔案
npm i optimize-css-assets-webpack-plugin -D
複製程式碼

在 webpack.prod.js 中:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');


const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
	module: {
		rules:[{
			test: /\.scss$/,
			use: [
+				MiniCssExtractPlugin.loader, 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
+				MiniCssExtractPlugin.loader,
				'css-loader',
				'postcss-loader'
			]
		}]
	},
+	optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})]
	},
	plugins: [
+		new MiniCssExtractPlugin({
			filename: '[name].css',//直接引用的css檔案
			chunkFilename: '[name].chunk.css'//間接引用的css檔案
		})
	]
}

module.exports = merge(commonConfig, prodConfig);
複製程式碼

在 webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true
	},
	module: {
		rules: [{
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	],
}

module.exports = merge(commonConfig, devConfig);
複製程式碼

在 webpack.common.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	entry: {
		main: './src/index.js',
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		})
	],
	optimization: {
		usedExports: true,//TreeShaking
		splitChunks: {
      chunks: 'all'
    }
	},
	output: {
		filename: '[name].js',
		chunkFilename: '[name].chunk.js',
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

在 package.json 中:

{
    "sideEffects": ["*.css"] //除了css檔案,其餘的都TreeShaking
}
複製程式碼

詳細請看官方文件:mini-css-extract-plugin


Webpack 與瀏覽器快取(Caching)

在 webpack.common.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	entry: {
		main: './src/index.js',
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		})
	],
	optimization: {
+		runtimeChunk: {//相容老版本webpack4,把manifest打包到runtime裡,不影響業務程式碼和第三方模組
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,//禁止提示效能上的一些問題
+	output: {
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

在 webpack.dev.js 中:

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true
	},
	module: {
		rules: [{
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	],
+	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
	}
}

module.exports = merge(commonConfig, devConfig);
複製程式碼

在 webpack.prod.js 中:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
	module: {
		rules:[{
			test: /\.scss$/,
			use: [
				MiniCssExtractPlugin.loader, 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				MiniCssExtractPlugin.loader,
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})]
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css',
			chunkFilename: '[name].chunk.css'
		})
	],
+	output: {
		filename: '[name].[contenthash].js', //原始碼不變,hash值就不會變,解決瀏覽器快取問題。打包上線時,使用者只需要更新有變化的程式碼,沒有變化的從瀏覽器快取讀取
		chunkFilename: '[name].[contenthash].js'
	}
}

module.exports = merge(commonConfig, prodConfig);
複製程式碼

詳細請看官網文件:manifest


Shimming (墊片)

在 webpack.common.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	entry: {
		main: './src/index.js',
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/,
			use: [{
				loader: 'babel-loader'
			}, {
				loader: 'imports-loader?this=>window'
			}]
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		}),
+		new webpack.ProvidePlugin({
			$: 'jquery',//發現模組中有$字串,就自動引入iquery,就可以用jquery
			_join: ['lodash', 'join']//_join代表lodash裡的join方法
		}),
	],
	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,
	output: {
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

如果想讓每個js模組的this都指向window:

安裝: npm install imports-loader -D

在 webpack.common.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	entry: {
		main: './src/index.js',
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/,
			use: [{
				loader: 'babel-loader'
			}, {//每個js模組的this都指向window
+				loader: 'imports-loader?this=>window'
			}]
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		}),
+		new webpack.ProvidePlugin({
			$: 'jquery',//發現模組中有$字串,就自動引入iquery,就可以用jquery
			_join: ['lodash', 'join']//_join代表lodash裡的join方法
		}),
	],
	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,
	output: {
		path: path.resolve(__dirname, '../dist')
	}
}
複製程式碼

詳細請看官方文件:imports-loader shimming


環境變數的使用

只需要一個common.js檔案通過在package.json中傳遞不同的引數,區分是開發環境還是生產環境。

在 package.json 中:

{
  "name": "haiyang",
  "sideEffects": [
    "*.css"
  ],
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev-build": "webpack --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js" //通過--env.production,把環境變數傳進去
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.2.0",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.2.0",
    "@babel/preset-env": "^7.2.0",
    "@babel/preset-react": "^7.0.0",
    "autoprefixer": "^9.3.1",
    "babel-loader": "^8.0.4",
    "clean-webpack-plugin": "^1.0.0",
    "css-loader": "^1.0.1",
    "express": "^4.16.4",
    "file-loader": "^2.0.0",
    "html-webpack-plugin": "^3.2.0",
    "imports-loader": "^0.8.0",
    "mini-css-extract-plugin": "^0.5.0",
    "node-sass": "^4.10.0",
    "optimize-css-assets-webpack-plugin": "^5.0.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack-cli": "^3.1.2",
    "webpack-dev-middleware": "^3.4.0",
    "webpack-dev-server": "^3.1.10",
    "webpack-merge": "^4.1.5"
  },
  "dependencies": {
    "@babel/polyfill": "^7.0.0",
    "@babel/runtime": "^7.2.0",
    "@babel/runtime-corejs2": "^7.2.0",
    "jquery": "^3.3.1",
    "lodash": "^4.17.11",
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "webpack": "^4.25.1"
  }
}

複製程式碼

在 webpack.common.js 中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge');
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const commonConfig = {
	entry: {
		main: './src/index.js',
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/,
			use: [{
				loader: 'babel-loader'
			}, {
				loader: 'imports-loader?this=>window'
			}]
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		}),
		new webpack.ProvidePlugin({
			$: 'jquery',
			_join: ['lodash', 'join']
		}),
	],
	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,
	output: {
		path: path.resolve(__dirname, '../dist')
	}
}

module.exports = (env) => {
	if(env && env.production) {//線上環境
		return merge(commonConfig, prodConfig);
	}else {//開發環境
		return merge(commonConfig, devConfig);
	}
}


複製程式碼

在 webpack.dev.js 中:

const webpack = require('webpack');

const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true
	},
	module: {
		rules: [{
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	],
	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
	}
}

module.exports = devConfig;
複製程式碼

在webpack.prod.js 中:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
	module: {
		rules:[{
			test: /\.scss$/,
			use: [
				MiniCssExtractPlugin.loader, 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				MiniCssExtractPlugin.loader,
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})]
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css',
			chunkFilename: '[name].chunk.css'
		})
	],
	output: {
		filename: '[name].[contenthash].js',
		chunkFilename: '[name].[contenthash].js'
	}
}

module.exports = prodConfig;
複製程式碼

四:Webpack實戰配置案例

Library的打包:庫程式碼通過webpack進行打包

倉庫原始碼 【41

在 webpack.config.js 中:


const path = require('path');

module.exports = {
	mode: 'production',
	entry: './src/index.js',
	externals: 'lodash',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'library.js',
		library: 'root', //支援通過<scritp src=ilibrary. js'></script> 標籤引入,在全域性變數增加一個root變數
		libraryTarget: 'umd' //別人用的時候,通過任何形式引入庫都可以,比如AMD,CMD,ES MODULE,Commonjs
        
        // library: 'root',//打包生成全域性變數root
		// libraryTarget: 'this' //把全域性變數root掛載到this上,可以填umd,this,window,global
        
        // externals: {
        // 	lodash:{
        // 		root:'_', //是用script標籤引入進來的,必須在全域性注入一個 _ 變數,下面的library才能正常執行
        // 		commonjs:'lodash',//在用commonjs規範引入是,名字必須是lodash
        // 	}

        // }

	}
}
複製程式碼

在 package.json 中:

"main": "./dist/library.js", //最終要給別人使用的
複製程式碼

npm 官網註冊一個賬號,在命令列輸入 :

//新增使用者名稱和密碼
npm adduser 
//把專案釋出到npm官網上
npm publish
//但別人用你釋出的庫時
npm i + 庫名 
複製程式碼

詳細請看官方文件:externals author-libraries


Progressive Web Application:在webpack中配置pwa

漸進式網路應用程式,PWA 可以用來做很多事。其中最重要的是,在**離線(offline)**時應用程式能夠繼續執行功能。這是通過使用名為 Service Workers 的 web 技術來實現的。線上環境時才用到pwa,開發時不需要

倉庫原始碼 【42

安裝:

//模擬伺服器
npm i http-server -D
//新增 workbox-webpack-plugin 外掛,然後調整 webpack.config.js 檔案
npm install workbox-webpack-plugin --save-dev
複製程式碼

在 package.json 中:

"scripts": {
 +   "start": "http-server dist",//在dist目錄下執行http-server服務
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
複製程式碼

線上環境時才用到pwa,開發時不需要,只需要改 webpack.prod.js ,

在 webpack.prod.js 中:

const WorkboxPlugin = require('workbox-webpack-plugin');
plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css',
			chunkFilename: '[name].chunk.css'
		}),
+		new WorkboxPlugin.GenerateSW({
			clientsClaim: true,
			skipWaiting: true
		})
	],
複製程式碼

在業務程式碼 index.js 中使用pwa

console.log('hello, haiyang');

if ('serviceWorker' in navigator) { //如果瀏覽器支援serviceWorker,就執行以下程式碼
	window.addEventListener('load', () => {
		navigator.serviceWorker.register('/service-worker.js')
			.then(registration => {//註冊成功
				console.log('service-worker registed');
			}).catch(error => {//沒註冊成功
				console.log('service-worker register error');
			})
	})
}
複製程式碼

詳細請看官方文件:progressive-web-application


TypeScript 的打包配置

TypeScript 是 JavaScript 的超集,為其增加了型別系統,可以編譯為普通 JavaScript 程式碼。這篇指南里我們將會學習是如何將 webpack 和 TypeScript 進行整合。

倉庫原始碼 【43

安裝:

npm install --save-dev typescript ts-loader
複製程式碼

在 webpack.config.js 中:

const path = require('path');

module.exports = {
	mode: 'production',
	entry: './src/index.tsx',
	module: {
		rules: [{
			test: /\.tsx?$/,
			use: 'ts-loader',
			exclude: /node_modules/
		}]
	},
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

在專案根目錄下建立 tsconfig.json 檔案:

{
	"compilerOpitons": {
		"outDir": "./dist",
		"module": "es6",//模組引入的方式
		"target": "es5",//轉換為es5,在大部分瀏覽器都能執行
		"allowJs": true, //在typescript中允許引入js檔案
	}
}
複製程式碼

在從 npm 安裝 third party library(第三方庫) 時,一定要記得同時安裝此 library 的型別宣告檔案(typing definition)。你可以從 TypeSearch 中找到並安裝這些第三方庫的型別宣告檔案。在使用時,哪有錯可以有警告提示,方便改錯。

安裝:

//在typescript裡用loadah
npm install --save-dev @types/lodash
複製程式碼

詳細請看官方文件:typescript


使用 WebpackDevServer 實現請求轉發

倉庫原始碼 【44

安裝:

//向伺服器傳送axios請求
npm i axios -D
複製程式碼

在 index.js 中:

componentDidMount() {
		axios.get('/react/api/header.json')
			.then((res) => {
				console.log(res);
			})
	}
複製程式碼

在 webpack.config.js 中:

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
+		proxy: {//開發時方便介面轉發,線上不用
			'/react/api': {//訪問 /react/api 時,代理到 target 上
				target: 'https://www.dell-lee.com',
				secure: false,//對https協議的網址的請求的轉發
     //   攔截,請求的是html,不走代理直接返回  /index.html檔案          
	//bypass: function(req, res, proxyOptions) {
      //    if (req.headers.accept.indexOf('html') !== -1) {
      //      console.log('Skipping proxy for browser request.');
       //     return '/index.html';
       //   }
     //   },
				pathRewrite: {
					'header.json': 'demo.json' //最後拿的是demo.json的資料
				},
				changeOrigin: true,//解決網站對介面的限制
				headers: {//變更請求頭
					host: 'www.dell-lee.com',
				}
			}
		}
	},
複製程式碼

詳細請看官方文件:devserverproxy


WebpackDevServer 解決單頁面應用路由問題

倉庫原始碼 【45

安裝:

npm i react-router-dom --save
複製程式碼

在 webpack.config.js 中:

devServer: {//配置只在開發時有效,上線時後端也需配置
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
+		historyApiFallback: true,
    	//historyApiFallback: {
         // rewrites: [//訪問任何路徑都展示index.html頁面
          //  { from: /\.*/, to: '/index.html' },
          //]
        //},
		proxy: {
			'/react/api': {
				target: 'https://www.dell-lee.com',
				secure: false,
				pathRewrite: {
					'header.json': 'demo.json'
				},
				changeOrigin: true,
				headers: {
					host: 'www.dell-lee.com',
				}
			}
		}
	},
複製程式碼

詳細請看官方文件:devserverhistoryapifallback


EsLint 在 Webpack 中的配置

倉庫原始碼 【46

安裝:

//安裝eslint工具,規範專案中的程式碼
npm i eslint -D
npm i babel-eslint -D
npm i eslint-loader -D
複製程式碼
//快速生成eslint配置
npx eslint --init
複製程式碼

在 .eslintrc.js 中:

module.exports = {
	"extends": "airbnb",
  "parser": "babel-eslint",
  "rules": {
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": 0
  },
  globals: {
    document: false
  }
};
複製程式碼

詳細請看官方文件:eslint

在 vscode 編輯器裡安裝 eslint 外掛,自動檢測語法錯誤。(推薦使用)

在 webpack.config.js 中:

devServer: {
+		overlay: true,//在瀏覽器彈出提示有錯誤
	},


rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
	+		use: ['babel-loader', 'eslint-loader'] //先檢查程式碼寫的是否規範,在轉換成es5
		},
        ...],
複製程式碼

在真實專案中,也可以不在webpack 中配置eslint,在提交git倉庫時,git 鉤子 eslint src 。但是沒有圖形互動式的錯誤提示。

詳細請看官方文件:eslint-loader


提升 webpack 打包速度的方法

倉庫原始碼 【47

1.跟上技術的迭代(Node,Npm,Yarn)

2.在儘可能少的模組上應用 Loader

3.Plugin 儘可能精簡併確保可靠

4.resolve 引數合理配置

倉庫原始碼 【48

引入資原始檔寫字尾,像 圖片檔案(.jpg, .png, .svg),邏輯程式碼配置在extensions中:extensions: ['.js', '.jsx']

5.使用 DLLPlugin 提高打包速度

詳細請看官方文件:dll-plugin

倉庫原始碼 【49

實現第三方模組只打包一次

安裝:

npm i add-asset-html-webpack-plugin --save
複製程式碼

在 build 資料夾裡建立 webpack.dll.js 檔案:把第三方模組單獨進行打包,生成一個vendors.dll.js 檔案,所有的第三方模組都在這個檔案裡。

const path = require('path');
const webpack = require('webpack');

module.exports = {
	mode: 'production',
	entry: {
		vendors: ['lodash'],
		react: ['react', 'react-dom'],
		jquery: ['jquery']
	},
	output: {
		filename: '[name].dll.js',
		path: path.resolve(__dirname, '../dll'),
		library: '[name]'//打包生成的庫名,通過全域性變數的形式暴露到全域性
	},
	plugins: [
		new webpack.DllPlugin({//對暴露到全域性的程式碼進行分析,生成vendors.manifest.json 的對映檔案,
			name: '[name]',
			path: path.resolve(__dirname, '../dll/[name].manifest.json'),
		})
	]
}
複製程式碼

在 webpack.common.js 中:

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
	if(/.*\.dll.js/.test(file)) {
		plugins.push(new AddAssetHtmlWebpackPlugin({//將打包好的dll檔案掛載到html中
			filepath: path.resolve(__dirname, '../dll', file)
		}))
	}
	if(/.*\.manifest.json/.test(file)) {
		plugins.push(new webpack.DllReferencePlugin({//分析第三方模組是否已經在dll檔案裡,如果裡面有就不用再node_modules在分析打包了
			manifest: path.resolve(__dirname, '../dll', file)
		}))
	}
})
複製程式碼

總結:

如果不使用使用 DLLPlugin 外掛,當引入第三方模組時,每一次打包都要進行分析,是消耗打包的效能的。使用 DLLPlugin 提高打包速度,在第一次打包時,把第三方模組單獨打包生成一個檔案 vendors.dll.js ,之後在打包時就可以直接從 vendors.dll.js 中引入之前打包好的第三方模組,速度就會變快。

要想實現,就得做一些配置:

先配置 webpack.dll.js 檔案,在配置 webpack.common.js 檔案

==============================================================

.dll 為字尾的檔案稱為動態連結庫,在一個動態連結庫中可以包含給其他模組呼叫的函式和資料

  • 把基礎模組獨立出來打包到單獨的動態連線庫裡
  • 當需要匯入的模組在動態連線庫裡的時候,模組不能再次被打包,而是去動態連線庫裡獲取 dll-plugin

定義Dll

  • DllPlugin外掛: 用於打包出一個個動態連線庫
  • DllReferencePlugin: 在配置檔案中引入DllPlugin外掛打包好的動態連線庫

在 webpack.dll.js 中:

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
複製程式碼

使用動態連結庫檔案

在 webpack.common.js 中:

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

==============================================================

6.控制包檔案大小

配置 Tree shaking,把用不到的程式碼去除掉。配置 SplitChunksPlugin。

7.thread-loader,parallel-webpack,happypack 多程式打包

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 //是否允許輸出日子
        })
    ],
複製程式碼
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, // 提取出出現多次但是沒有定義成變數去引用的靜態值
                }
            },
        })
複製程式碼

8.合理使用 sourceMap

9.結合 stats 分析打包結果

10. 開發環境記憶體編譯

11.開發環境無用外掛剔除


多頁面打包配置

配置多個 entry 裡的 html 頁面,用HtmlWebpackPlugin 外掛,將打包好的j多個js分別插入到對應的html頁面中。

倉庫原始碼 【410

在 webpack.common.js 中:

const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');


const makePlugins = (configs) => {
	const plugins = [
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		})
	];
	Object.keys(configs.entry).forEach(item => {
		plugins.push(
+			new HtmlWebpackPlugin({
				template: 'src/index.html',
				filename: `${item}.html`,
				chunks: ['runtime', 'vendors', item]
			})
		)
	});
	const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
	files.forEach(file => {
		if(/.*\.dll.js/.test(file)) {
			plugins.push(new AddAssetHtmlWebpackPlugin({
				filepath: path.resolve(__dirname, '../dll', file)
			}))
		}
		if(/.*\.manifest.json/.test(file)) {
			plugins.push(new webpack.DllReferencePlugin({
				manifest: path.resolve(__dirname, '../dll', file)
			}))
		}
	});
	return plugins;
}

const configs = {
+	entry: {
		index: './src/index.js',
		list: './src/list.js',
		detail: './src/detail.js',
	},
	resolve: {
		extensions: ['.js', '.jsx'],
	},
	module: {
		rules: [{ 
			test: /\.jsx?$/, 
			include: path.resolve(__dirname, '../src'),
			use: [{
				loader: 'babel-loader'
			}]
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}]
	},
	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,
	output: {
		path: path.resolve(__dirname, '../dist')
	}
}

configs.plugins = makePlugins(configs);

module.exports = configs
複製程式碼

五:Webpack底層原理及腳手架工具分析

如何編寫一個 Loader

倉庫原始碼 【51

mkdir make-loader
cd make-loader
npm init -y
npm i webpack webpack-cli -D
npm i loader-utils -D
複製程式碼

在根目錄下建立資料夾loaders,裡面建立自己定義的loader.js檔案

在 webpack.config.js 中:

const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	resolveLoader: { //先在 node_modules 中找用到的loader,如果沒找到,再在loaders裡查詢
		modules: ['node_modules', './loaders']
	},
	module: {
		rules: [{
			test: /\.js/,
			use: [//使用自己寫的replaceLoader
				{
					loader: 'replaceLoader',
				},
				{
					loader: 'replaceLoaderAsync',
					options: {
						name: 'lee'
					}
				},
			]
		}]
	},
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js'
	}
}
複製程式碼

詳細請看官方文件:loaders#thisquery hiscallback thisasync


如何編寫一個 Plugin

倉庫原始碼 【52

詳細請看官方文件:compiler-hooks


Bundler 原始碼編寫(模組分析)

應對 webpack 原理面試必備:

倉庫原始碼 【53

安裝 :

//高亮顯示程式碼的工具
npm i cli-highlight -g
//分析原始碼
npm install @babel/parser --save
npm install @babel/core --save
npm install @babel/traverse --save
npm install @babel/preset-env --save
複製程式碼

Bundler 原始碼編寫(Dependencies Graph)

倉庫原始碼 【54


Bundler 原始碼編寫(生成程式碼)

倉庫原始碼 【55

六:Create-React-App 和 Vue-Cli 3.0腳手架工具配置分析

通過CreateReactApp深入學習Webpack配置

倉庫原始碼 【56

詳細請看官方文件:CreateReactApp

快速開始:

npx create-react-app my-app
cd my-app
npm start
複製程式碼

把隱藏的配置項展現出來:不可逆操作

npm run eject
複製程式碼

就會多出來兩個資料夾,config,scripts資料夾


Vue-Cli 3.0

倉庫原始碼 【57

詳細請看官網:VUE Cli 全域性-cli-配置

相關文章