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 提案階段,還沒被正式釋出
// .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/] // 指定需要載入的資料夾 }] } 複製程式碼
-
exclude
、include
的值:-
值可以是單獨項、可以是陣列
-
可以是路徑、可以是正則
-
-
專案實戰:
專案開發中,針對主要 lodaer 如
babel-loader
、style-loader
、sass-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')] } }; 複製程式碼
-
-
引入的模組,尋找規則:
不建議寫檔案字尾名,就意味著引入的模組路徑,路徑最後一層有可能是檔案;有可能是資料夾
預設將路徑的最後一層 視為檔名,依次匹配 配置的字尾名
如果沒找到該檔案,將路徑的最後一層視為資料夾,依次匹配 設定的檔名,找到後再 依次匹配設定的字尾名
如都沒找到,就會報錯
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的值用if–else判斷 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