弄懂webpack,只要看这一片就够了(文末有福利)
什么是webpack
? webpack是什么,官網中是這么說的。
? 本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle。
? 從上面的概念中,我們可以看出webpack是一個會把應用程序中具有依賴關系的每個模塊打包成一個或者多個bundle的工具。既然是工具,就是代替人工完成某些復雜的操作,減少開發工作的繁瑣,所以我們需要掌握的就是如何使用它。
? 但我們在使用之前,需要明白文中的幾個概念:
-
什么是模塊:了解Node的同學都知道,在Node的世界里,萬物皆模塊;那當然,基于Node的產物webpack對模塊的定義和node是類似的,模塊可大可小,大到一個項目一個工程,小至一個文件甚至一行代碼。
-
什么是budle:和模塊相比,budle的定義則顯得比較局限,它是指打包后的資源文件,一個文件就是一個budle,一個前端工程可以打包成一個文件或者多個文件的文件夾,這就是概念中提到的“一個或多個 bundle”。
此外,除了上述的兩個概念之外,這里還需要在補充幾個相關概念。
-
hash:hash是和項目相關的hash值(一個項目一個hash值),是webpack用來給標記budle文件的唯一標識,也是監測該資源文件在二次打包時是不是要覆蓋的依據,通常用固定字節的字符串來表示。
-
chunk/chunkhash:chunk是指代碼塊,也可以泛指為1個chunk=1budle。
我們知道一個打包資源中會有多個budle文件,如果我們修改其中一個,那和項目相關的hash值就會統一改變,而那些沒有修改過的、已打包好的、并且已經緩存的budle文件也會因此失效,需要重新打包,為了解決這個問題,就出現了chunkhash的概念,一個budle文件就對應一個chunkhash。
-
contenthash:和chunkhash相比,contenthash的粒度就更細了,它主要是針對一個bundle中的內容,如果內容不變,contenthash就不變,所以我們通常會把項目中的js和css分離開來,對于css的打包文件命名我們就采用contenthash。
為什么要用webpack
-
前后端分離開發模式是催化劑
隨著開發模式的轉變,現在的前端開發已經不是想以往那些php、jsp那些,但凡懂點html/css的后端程序員都可以上手做的,現在的前端已經開始專業化,而我們的代碼也是以工程的形式存在,你可以使用任何框架和技術,但是最后跑在用戶瀏覽器上的東西還是和以前一樣,就是靜態資源和html,而從框架代碼到瀏覽器識別的資源之間的轉化就是由webpack來完成的。
-
工程化、模塊化的產物
工程化、模塊化是我們研發代碼的一種重要思想,同時也正是這種思想的存在,才會在前端領域中衍生出那么多框架和技術,比如:Vue、React、Angular等工程化框架,TS、less、Scss等各種語言的擴展,這些都是提高我們開發效率的利器,但是同時這些語言和框架是不能被瀏覽器所識別的,怎樣讓我們的代碼更優雅的被瀏覽器識別并運行,這就是webpack的主要職責。
webpack的安裝
環境準備
? webpack是基于nodek開發的工程化工具,所以開發環境支持node,并且node版本和webpack的匹配也很重要,具體參見webpack官網的發布說明,盡量使用最新版本以提高打包速度。
全局安裝 不推薦
# 安裝webpack V4+版本時,需要額外安裝webpack-cli npm install webpack webpack-cli -g # 檢查版本 webpack -v # 卸載 npm uninstall webpack webpack-cli -g項目安裝 推薦
# 安裝最新的穩定版本 npm i -D webpack # 安裝指定版本 npm i -D webpack@<version> # 安裝最新的體驗版本 可能包含bug,不不要?用于?生產環境 npm i -D webpack@beta # 安裝webpack V4+版本時,需要額外安裝webpack-cli npm i -D webpack-cliwebpack的構建
默認配置
? 首先我們先看一下webpack的默認配置代碼,如下:
const path = require('path') module.exports = {// 必填,webpack的執行入口entry: './src/main.js',output: {// 合并輸出的文件名filename: 'main.js',// 合并輸出的絕對存放地址path: path.resolve(__dirname, './dist')} }? 其中,我們需要注意的就是
- webpack默認只支持JS模塊和JSON模塊
- 支持CommonJS Es moudule AMD等模塊類型
- webpack4?持零配置使?,但是可用性很差,一般都需要額外擴展配置
執行構建
可以通過這兩種方式進行項目構建
# 方式1:npx方式 npx webpack# 方式2:npm script # 首先需要在package.json中添加test指令對應的執行腳本 ----------------------------------------------"scripts":{ "test": "webpack" // 原理就是通過shell腳本在node_modules/.bin?錄下創建?個軟鏈接}, ---------------------------------------------- # 然后在執行如下腳本 npm run test? 構建成功后會發現?錄下多出?個 dist ?錄,??有個 main.js ,這個?件是?個可執行的JavaScript文件,里面包含webpackBootstrap啟動函數。
SourceMap
源代碼與打包后的代碼的映射關系,通過sourceMap定位到源代碼。
在dev模式中,默認開啟,關閉的話可以在配置文件? devtool:"none"
// devtool的介紹 https://webpack.js.org/configuration/devtool#devtool eval: 速度最快,使用eval包裹模塊代碼, source-map: 產生.map文件 cheap: 較快,不包含列信息 Module: 第三?模塊,包含loader的sourcemap(比如jsx to js, babel的sourcemap) inline: 將.map作為DataURI嵌入,不單獨生成.map?件配置推薦:
devtool:"cheap-module-eval-source-map",// 開發環境配置// 線上不推薦開啟 devtool:"cheap-module-source-map", // 線上生成配置WebpackDevServer
webpack-dev-server提升開發效率的利?,可以解決重復打包生成dist目錄,這是因為devServer把打包后的模塊不會放在dist目錄下,二是放到內存中,從而提升速度。
# 安裝 npm install webpack-dev-server -D# 修改package.json配置 "scripts": {"server": "webpack-dev-server" },# 修改webpack.config.js配置 devServer: {contentBase: "./dist", open: true,port: 8081 }# 啟動 npm run server跨域問題的解決
- 設置服務器代理
- 本地mock server
Hot Module Replacement (HMR:熱模塊替換)
啟動hmr
devServer: {contentBase: "./dist",open: true,hot: true, // 開啟HMRhotOnly: true // 即便HMR不生效,瀏覽器也不自動刷新,就開啟hotOnly },配置webpack.config.js
const webpack = require("webpack");// 在插件配置處添加 plugins: [new CleanWebpackPlugin(),new webpack.HotModuleReplacementPlugin() ]-
注意啟動HMR后,css抽離會不生效,還有不支持contenthash,chunkhash, 建議使用style-loader將css處理到html中
-
處理js模塊HMR需要使用module.hot.accept來觀察模塊更新,從?更新
if (module.hot) {// path為需要監聽變化的js的相對路徑module.hot.accept(path, function() {// 執行操作,重新渲染修改后的js效果}); }
webpack的基本概念
entry
指定webpack打包?口文件,webpack 執?構建的第一步將從Entry開始,可理解成輸入,有三種不同的輸入方式
// 字符串: 單入口 entry: "./src/index.js",// 數組: 單入口,最終將數組文件合并成一個 entry: ["./src/index.js", "./src/other.js"],// 對象: 多入口 entry: { index: "./src/index.js",other: "./src/other.js" }output
打包轉換后的?件輸出到磁盤位置:輸出結果,在webpack經過?系列處理并得出最終想要的代碼后輸出結果。
// 默認處理(單入口) output: {filename: "bundle.js",// 輸出?文件的名稱path: path.resolve(__dirname, "dist")// 輸出?文件到磁盤的?目錄,必須是絕對路路徑 },// 多?口的處理 output: {filename: "[name][chunkhash:8].js",// 利用占位符,?件名稱不要重復path: path.resolve(__dirname, "dist")// 輸出?件到磁盤的目錄,必須是絕對路徑 },mode
mode用來指定當前的構建環境,主要取值有:
- production // 生產環境的開啟會有幫助模塊壓縮,處理副作用等一些功能
- development // 開發環境的開啟會有利于熱更新的處理,識別哪個模塊變化
- none // 5+取消了
設置mode可以自動觸發webpack內置的函數,達到優化的效果
none的賦值在5+版本中取消了,包括production時啟動的函數中TerserPlugin改成為UglifyJsPlugin
module
模塊,在webpack里一切皆模塊,?個模塊對應著一個文件。
webpack會從配置的 Entry 開始遞歸找出所有依賴的模塊。當webpack處理到不認識的模塊時,需要在webpack中的module 處進?配置,當檢測到是什么格式的模塊,使?什么loader來處理。
module:{rules:[{test:/\.xxx$/, //指定匹配規則 use:{loader: 'xxx-load'//指定使?用的loader }}] }loader
webpack 默認只支持.json 和 .js模塊,不支持不認識其他格式的模塊,而loader就是用來轉換這些不認識模塊的模塊轉換器,可以把模塊原內容按照需求轉換成新內容。
常?的loader
style-loader css-loader less-loader sass-loader ts-loader // 將Ts轉換成js babel-loader // 轉換ES6、7等js新特性語法 file-loader // 處理理圖?片?子圖 eslint-loader ...Plugins
plugins 選項用于以各種方式自定義 webpack 構建過程。
也就是說,webpack的打包過程是有生命周期的概念的,每個階段都有對應的鉤子函數,而plugin就可以在webpack運行到某個階段的時候進行一些操作,類似生命周期鉤子函數的擴展插件,最終完成在webpack構建過程中的特定時機注入擴展邏輯來生成自己想要的結果。
Loader詳解
上面我們了解到loader是webpack構建過程中的模塊轉換器,下面我們著重看幾個常用的loader
靜態資源處理類
file-loader
file-loader是把打包?口中識別出的資源模塊,移動到輸出目錄,并且返回?個地址名稱,所以file-loader通常用來處理那些只需要從源代碼移到打包目錄的靜態資源,比如:txt、svg、csv、excel以及各種圖片、字體文件。
module: {rules: [// test 表示需要識別文件的后綴名,即是這樣的后綴名的文件需要由該loader處理test: /\.(png|jpe?g|gif)$/,// use 表示處理上述匹配文件的loader,如果是一個loader,需要用對象表示,如果是多個loader,則需要用數組use: {loader: 'file-loader',options: {// [name]老資源模塊的名稱 [hash]hash值 [ext]老資源的后綴名name: '[name]_[hash].[ext]'// 打包后的存放位置outputPath: 'images/'}}] }url-loader
? url-loader是file-loader的加強版本,它的內部實現也是基于file-loader,所以可以處理file-loader可以處理的資源。
? 同時url-loader會根據默認的(或者配置的)limit參數,將滿足條件的圖片轉換成base64格式,并打包到js里,從而減小http請求,提高頁面加載速度。但是這種操作只針對小體積的圖片,不適用大圖片。
module: {rules: [{test: /\.(png|jpe?g|gif)$/,use: {loader: 'url-loader',options: {name: '[name]_[hash].[ext]',outputPath: 'images/',// 小于2048Byte(2KB),才轉換成base64limit: 2048}}}] }樣式處理類
css-loader
分析css模塊之間的關系,并合成?個css
style-loader
style-loader可以會把css-loader生成的內容,以style掛載到頁面的head部分
less-loader
less-loader會把less語法轉成css
# 在多loader轉換時,執行順序為從右到左,從下到上 module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']},{test: /\.scss$/,use: ['style-loader', 'css-loader', 'less-loader']}] }postcss-loader
postcss-loader也比較常見,通過用來批量轉化css,比如:自動添加前綴以適配不同版本的瀏覽器,具體的瀏覽器適配規則可以參見https://caniuse.com/
// webpack.config.js中的配置 module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader', 'postcss-loader']}] }// 此外,需要新建postcss.config.js module.exports = {plugins: [require('autoprefixer')({// last 2 versions: 最近的兩個大版本// >1%: 全球市場份額大于1%的瀏覽器overrideBrowerslist: ['last 2 versions', '>1%']})] }JS腳本處理類
babel-loader
在使用babel-loader,我們需要了解幾個概念
Babel是JavaScript編譯器?,能將ES6代碼轉換成ES5代碼,讓我們開發過程中放?使用JS新特性?不用擔?心兼容性問題。并且還可以通過插件機制根據需求靈活的擴展。
babel-loader是webpack與babel的通信橋梁,不做把es6轉成es5的?作,這部分?作需要用到@babel/preset-env來做,@babel/preset-env里包含了es6,7,8轉es5的轉換規則
- 在使用preset-env時,需要注意的就是按需引入,由配置參數useBuiltIns決定
- useBuiltIns選項是babel7的新功能,這個選項告訴babel如何配置@babel/polyfill。 它有三個參數可以使?:
- entry:需要在webpack的?口文件里import "@babel/polyfill"?次。 babel會根據你的使?情況導?墊片,沒有使?的功能不會被導入相應的墊?。
- usage:不需要import,全?動檢測,但是要安裝@babel/polyfill。(試驗階段)
- false:如果你import "@babel/polyfill" ,它不會排除掉沒有使用的墊?,程序體積會龐?。(不推薦)
- 請注意: usage的?為類似 babel-transform-runtime,不會造成全局污染,因此也不會對類似 Array.prototype.includes() 進行 polyfill。
默認的Babel只?持let等?些基礎的特性轉換,Promise等?些還有轉換過來,這時候需要借助墊片 @babel/polyfill,把es的新特性都裝進來,來彌補低版本瀏覽?中缺失的特性,其原理是語法轉換,也就是說在轉換后的文件里定義一個promise,并掛載到window對象上。
Babel在執?編譯的過程中,會從項?根?錄下的 .babelrc JSON 文件中讀取配置。沒有該?件會從loader的options地?讀取配置
//.babelrc {presets: [["@babel/preset-env",{targets: { // 需要指定代碼運行的瀏覽器環境edge: "17",firefox: "60", chrome: "67",safari: "11.1"},corejs: 2,// 新版本需要指定核心庫版本useBuiltIns: "usage"// 按需注入}]] }//webpack.config.js {test: /\.js$/,exclude: /node_modules/, loader: "babel-loader" }這是最后的全部配置
module: {rules: [test: /\.js$/,exclude: /node_modules/,use: {loader: "babel-loader", options: {presets: [["@babel/preset-env",{targets: { // 需要指定代碼運行的瀏覽器環境edge: "17",firefox: "60", chrome: "67",safari: "11.1"},corejs: 2,// 新版本需要指定核心庫版本useBuiltIns: "usage"// 按需注入}]]}}] }Plugin詳解
HtmlWebpackPlugin
HtmlWebpackPlugin簡化了HTML文件的創建,以便為你的webpack包提供服務。這對于在文件名中包含每次會隨著編譯而發生變化哈希的 webpack bundle 尤其有用。 你可以讓插件為你生成一個HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader。
// 配置參數 title: ?來?成?面的 title 元素 filename: 輸出的 HTML ?件名,默認是 index.html, 也可以直接配置帶有子目錄。 template: 模板?件路路徑,?持加載器,?如 html!./index.html inject: true | 'head' | 'body' | false ,注?所有的資源到特定的 template 或者 templateContent 中,如果設置為 true 或者 body,所有的 javascript 資源將被放置到 body 元 素的底部,'head' 將放置到 head 元素中。 favicon: 添加特定的 favicon 路徑到輸出的 HTML ?文件中。 minify: {} | false , 傳遞 html-minifier 選項給 minify 輸出 hash: true | false, 如果為 true, 將添加?個唯一的 webpack 編譯 hash 到所有包含的腳本和 CSS 文件,對于解除 cache 很有用。 cache: true | false,如果為 true, 這是默認值,僅僅在?件修改之后才會發布?件。 showErrors: true | false, 如果為 true, 這是默認值,錯誤信息會寫入到 HTML ?面中。 chunks: 允許只添加某些塊 (?如,僅 unit test 塊) chunksSortMode: 允許控制塊在添加到?面之前的排序方式,支持的值:'none' | 'default' | {function}-default:'auto' excludeChunks: 允許跳過某些塊,(比如,跳過單元測試的塊) // 使用案例 const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') module.exports = {......plugins: [new htmlWebpackPlugin(// 插件參數傳遞,使用對象{title: 'my App',filename: 'index.html',template: './src/index.html'})] }// index.html 中獲取插件參數 <title><%= htmlWebpackPlugin.options.title %></title>CleanWebpackPlugin
其實 clean-webpack-plugin 很容易知道它的作用,就是來清除文件的。
一般這個插件是配合 webpack -p 這條命令來使用,就是說在為生產環境編譯文件的時候,先把 build或dist (就是放生產環境用的文件) 目錄里的文件先清除干凈,再生成新的。
// 使用案例 const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports = {......plugins: [new cleanWebpackPlugin()] }MiniCssExtractPlugin
一般我們的 css 是直接打包進 js?面的,我們希望能單獨?成 css文件。 因為單獨?成css,css可以和js并行下載,提高?面加載效率
借助MiniCssExtractPlugin 完成抽離css
// webpack.config.js const MiniCssExtractPlugin = require("mini-css-extract-plugin");module: {rules: [{test: /\.scss$/,use: [// "style-loader", // 不再需要style-loader,?MiniCssExtractPlugin.loaderMiniCssExtractPlugin.loader,"css-loader", // 編譯css "postcss-loader", "sass-loader" // 編譯scss]}] }, plugins: [new MiniCssExtractPlugin({filename: "css/[name]_[contenthash:6].css",chunkFilename: "[id].css" }) ]性能優化
提升檢索速度
1. 縮小loader處理范圍
優化loader配置
-
test include exclude三個配置項來縮小loader的處理范圍
-
推薦include
include: path.resolve(__dirname, "./src"),
2. resolve.modules
-
resolve.modules?于配置webpack去哪些目錄下尋找第三?模塊,默認是[‘node_modules’]
-
尋找第三方模塊,默認是在當前項?錄下的node_modules??去找,如果沒有找到,就會去上?級目錄…/node_modules找,再沒有會去…/…/node_modules中找,以此類推,和Node.js的模塊尋找機制很類似。
-
如果我們的第三方模塊都安裝在了項目根?錄下,就可以直接指明這個路徑。
module.exports = {resolve:{modules: [path.resolve(__dirname, "./node_modules")] } }
3. resolve.alias
resolve.alias配置通過別名來將原導?路徑映射成?個新的導?路徑
拿react為例,我們引?的react庫,?般存在兩套代碼
-
cjs
- 采?用commonJS規范的模塊化代碼
-
umd
- 已經打包好的完整代碼,沒有采用模塊化,可以直接執?
默認情況下,webpack會從?口文件./node_modules/bin/react/index開始遞歸解析和處理依賴的?件。我們可以直接指定文件,避免這處的耗時。
alias: {"@": path.join(__dirname, "./pages"),react: path.resolve(__dirname, "./node_modules/react/umd/react.production.min.js"),"react-dom": path.resolve(__dirname, "./node_modules/react-dom/umd/react-dom.production.min.js") }
4. resolve.extensions
resolve.extensions在導入語句沒帶文件后綴時,webpack會?動帶上后綴后,去嘗試查找?件是否存在。
默認值:
extensions:['.js','.json','.jsx','.ts']- 后綴嘗試列表盡量的?
- 導?語句盡量的帶上后綴
5. externals
我們可以將?一些JS?文件存儲在 CDN 上(減少 Webpack 打包出來的 js 體積),在 index.html 中通過 標簽引?入,如:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body><div id="root">root</div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> </body> </html>我們希望在使用時,仍然可以通過 import 的?式去引?(如 import $ from ‘jquery’ ),并且希望 webpack 不不會對其進?打包,此時就可以配置 externals 。
//webpack.config.js module.exports = {//...externals: {//jquery通過script引?入之后,全局中即有了了 jQuery 變量量 'jquery': 'jQuery'} }6. CDN
CDN通過將資源部署到世界各地,使得?戶可以就近訪問資源,加快訪問速度。要接?CDN,需要把?
?的靜態資源上傳到CDN服務上,在訪問這些資源時,使?CDN服務提供的URL。
// webpack.config.js output:{publicPath: '//cdnURL.com', //指定存放JS?文件的CDN地址 }- 有cdn服務?地址
- 確保靜態資源?件的上傳與否
提升加載速度
1. css壓縮
-
借助 optimize-css-assets-webpack-plugin
-
借助 cssnano
# 安裝 npm install cssnano -D npm i optimize-css-assets-webpack-plugin -D // webpack.config.js const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); new OptimizeCSSAssetsPlugin({cssProcessor: require("cssnano"), //引?cssnano配置壓縮選項 cssProcessorOptions: {discardComments: { removeAll: true }} })
2. html壓縮
-
借助html-webpack-plugin
new htmlWebpackPlugin({ title: "my app",template: "./index.html", filename: "index.html", minify: {// 壓縮HTML?文件removeComments: true, // 移除HTML中的注釋 collapseWhitespace: true, // 刪除空?符與換行符 minifyCSS: true // 壓縮內聯css} }),
3. 搖樹(tree Shaking)
webpack2.x開始支持 tree shaking概念,顧名思義,“搖樹”,清除?用 css,js(Dead Code)
Dead Code ?般具有以下?幾個特征
- 代碼不會被執?,不可到達
- 代碼執?的結果不會被用到
- 代碼只會影響死變量(只寫不讀)
- Js tree shaking只支持ES module的引?方式
css tree shaking
npm i glob-all purify-css purifycss-webpack --save-dev const PurifyCSS = require('purifycss-webpack') const glob = require('glob-all') plugins:[// 清除?用 css new PurifyCSS({paths: glob.sync([// 要做 CSS Tree Shaking 的路徑?件path.resolve(__dirname, './src/*.html'), // 請注意,我們同樣需要對html?件進行 tree shakingpath.resolve(__dirname, './src/*.js')])}) ]Js tree shaking
-
只支持import方式引入,不支持commonjs的方式引入
-
只要mode是production就會生效,develpoment的tree shaking是不生效的,因為webpack為了方便你的調試
-
可以查看打包后的代碼注釋以辨別是否?效
-
生產模式不需要配置,默認開啟
// webpack.config.js optmization: {usedExports: true // 只要導出的模塊被使用了,再做打包 }
副作用
// package.json "sideEffects":false //正常對所有模塊進?行行tree shaking , 僅?生產模式有效,需要配合 usedExports 或者 在數組?里里?面排除不不需要tree shaking的模塊 "sideEffects":['*.css','@babel/polyfill']4. 代碼分割(code Splitting)
單頁面應用spa
打包完后,所有頁面只生成一個bundle.js
- 代碼體積變大,不利于下載
- 沒有合理利用瀏覽器資源
多頁面應用mpa
如果多個頁面引入了一些公共模塊,那么可以把這些公共的模塊抽離出來,單獨打包。公共代碼只需要下載一次就緩存起來,避免重復下載。
配置
其實code Splitting概念 與 webpack并沒有直接的關系,只不過webpack中提供了一種更加方便的方法供我們實現代碼分割,基于https://webpack.js.org/plugins/split-chunks-plugin/
optimization: {splitChunks: {chunks: 'async',// 對同步 initial,異步 async,所有的模塊有效 allminSize: 30000,// 最?尺寸,當模塊大于30kbmaxSize: 0,// 對模塊進行二次分割時使?用,不推薦使?minChunks: 1,// 打包?成的chunk?件最少有幾個chunk引?了這個模塊maxAsyncRequests: 5,// 最?異步請求數,默認5 maxInitialRequests: 3,// 最?初始化請求書,?口文件同步請求,默認3 automaticNameDelimiter: '-',// 打包分割符號name: true,// 打包后的名稱,除了布爾值,還可以接收?個函數function cacheGroups: {// 緩存組vendors: {test: /[\\/]node_modules[\\/]/, name:"vendor",// 要緩存的分隔出來的 chunk 名稱 priority: -10// 緩存組優先級數字越大,優先級越?}, other:{chunks: "initial",// 必須三選?: "initial"|"all"|"async"(默認)test: /react|lodash/,// 正則規則驗證,如果符合就提取 chunk, name:"other",minSize: 30000,minChunks: 1,},default: {minChunks: 2,priority: -20,reuseExistingChunk: true// 可設置是否重?用該chunk}}} }平時使用下面的配置即可
optimization:{ // 幫我們自動做代碼分割 splitChunks:{chunks:"all",// 默認是?持異步,我們使用all } }5. 作?域提升(Scope Hoisting)
作用域提升(Scope Hoisting)是指 webpack 通過 ES6 語法的靜態分析,分析出模塊之間的依賴關系,盡可能地把模塊放到同一個函數中。下?通過代碼示例來理理解:
// hello.js export default 'Hello, Webpack'; // index.js import str from './hello.js'; console.log(str);打包后, hello.js 的內容和 index.js 會分開
通過配置 optimization.concatenateModules=true`:開啟 Scope Hoisting
我們發現hello.js內容和index.js的內容合并在一起了!所以通過 Scope Hoisting 的功能可以讓 Webpack 打包出來的代碼文件更小、運行的更快
6. 動態鏈接庫(DllPlugins)
Dll動態鏈接庫 其實就是做緩存
項目中引入了很多第三方庫,這些庫在很?的?段時間內,基本不會更新,打包的時候分開打包來提升打包速度,?DllPlugin動態鏈接庫插件,其原理就是把??依賴的基礎模塊抽離出來打包到dll文件中, 當需要導入的模塊存在于某個dll中時,這個模塊不再被打包,?是去dll中獲取。
- 動態鏈接庫只需要被編譯?次,項?中?到的第三方模塊,很穩定,例如react,react-dom,只要沒有升級的需求
- webpack已經內置了對動態鏈接庫的?持
- DllPlugin:?于打包出?個單獨的動態鏈接庫文件
- DllReferencePlugin:用于在主要的配置?件中引?DllPlugin插件打包好的動態鏈接庫?件
新建webpack.dll.config.js?文件,打包基礎模塊
我們在 index.js 中使?了第三方庫 react 、 react-dom ,接下來,我們先對這兩個庫先進行打包。
// webpack.dll.config.js const path = require("path"); const { DllPlugin } = require("webpack"); module.exports = {mode: "development", entry: {react: ["react", "react-dom"] //! node_modules? },output: {path: path.resolve(__dirname, "./dll"),filename: "[name].dll.js",library: "react"}, plugins: [new DllPlugin({// manifest.json?文件的輸出位置path: path.join(__dirname, "./dll", "[name]-manifest.json"), // 定義打包的公共vendor?文件對外暴暴露露的函數名name: "react"})] }在package.json中添加
"dev:dll": "webpack --config ./build/webpack.dll.config.js",運行
npm run dev:dll你會發現多了一個dll?文件夾,?邊有dll.js文件,這樣我們就把我們的React這些已經單獨打包了
- dll?件包含了大量模塊的代碼,這些模塊被存放在?個數組里。?數組的索引號為ID,通過變量將?己暴露在全局中,就可以在window.xxx訪問到其中的模塊
- Manifest.json 描述了與其對應的dll.js包含了哪些模塊,以及ID和路徑
打包好之后需要將dll文件注入index.html中,使用依賴add-asset-html-webpack-plugin
// webpack.config.js new AddAssetHtmlWebpackPlugin({filepath: path.resolve(__dirname, '../dll/react.dll.js') // 對應的 dll ?件路徑 })運行項目
npm run dev這個理解起來不費勁,操作起來很費勁。所幸,在Webpack5中已經不用它了,而是? HardSourceWebpackPlugin ,?樣的優化效果,但是使用卻及其簡單
-
提供中間緩存的作?
-
?次構建沒有太大的變化
-
第?次構建時間就會有較大的節省
// webpack.config.js const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') const plugins = [new HardSourceWebpackPlugin() ]
7. 并發任務(happypack)
運行在 Node.之上的Webpack是單線程模型的,也就是說Webpack需要?個一個地處理任務,不能同時處理多個任務。 Happy Pack 就能讓Webpack做到這一點,它將任務分解給多個?進程去并發執行,?進程處理完后再將結果發送給主進程。從?發揮多核CPU電腦的威力。
// webpack.config.js var happyThreadPool = HappyPack.ThreadPool({ size: 5 });// module中添加 rules: [ {test: /\.jsx?$/, exclude: /node_modules/, use: [{loader: "happypack/loader?id=babel" }] },{test: /\.css$/,include: path.resolve(__dirname, "./src"), use: ["happypack/loader?id=css"]}, ] //在plugins中增加 plugins:[new HappyPack({// ?唯一的標識符id,來代表當前的HappyPack是?來處理?類特定的文件 id:'babel',// 如何處理理.js?文件,?用法和Loader配置中?一樣 loaders:['babel-loader?cacheDirectory'],threadPool: happyThreadPool,}),new HappyPack({id: "css",loaders: ["style-loader", "css-loader"] }), ]原理剖析
自定義webpack
? 我們以實現一個簡易的mini-webpack為目的,來串聯一下webpack的打包原理。
首先定義一個webpack類
- 這個類需要有接收webpack.config.js這樣的配置文件
- 這個類需要有一個執行構建的主方法,可以通過new webpack(options).start()開始構建
解析入口文件,獲取語法結構樹AST
- 這里會涉及到parser.parse方法
找到所有的依賴模塊
- 這里會涉及到babel/core中的traverse方法
- 并且只需要找到type為ImportDeclaration的節點
將AST轉化為code
- 將 AST 語法樹轉換為瀏覽器可執行代碼,我們這里使用@babel/core中的transformFromAst 和 @babel/preset-env。
遞歸查看所有依賴項
- 遞歸查看的時候需要利用的技巧就是利用動態數組的長度遍歷,一邊push一邊foreach
重寫require函數,輸出bundle
- 這塊需要注意的就是引入模塊的相對路徑轉化絕對路徑
- 閉包和自執行函數的運用
完整的mini-webpack代碼
// mini-webpack.js const fs = require("fs") const path = require("path") const parser = require("@babel/parser") const traverse = require("@babel/traverse").default const { transformFromAst } = require("@babel/core")// 導出一個打包類,通過 new webpack(options).start() 進行構建 module.exports = class webpack {// 定義構造函數constructor(options) {// 獲取配置文件中的入口和出口const {entry, output} = optionsthis.entry = entrythis.output = outputthis.modules = [] // 定義數組用來存放入口模塊和其所有的依賴模塊}// 開始構建的主方法start() {console.log('開始構建。。。。。。')// 1. 獲取入口文件console.log('--------------1--------------')const entryInfo = this.parse(this.entry)this.modules.push(entryInfo)// 2. 遞歸分析其他模塊console.log('--------------2--------------')for(let i = 0; i < this.modules.length; i++) {const item = this.modules[i]const { denpendencies } = item // 獲取依賴關系if (denpendencies) {for (let j in denpendencies) {this.modules.push(this.parse(denpendencies[j]))}}}console.log(this.modules);// 3. 結構轉化, {文件名: {依賴,代碼}}console.log('--------------3--------------')const obj = {}this.modules.forEach((item) => {obj[item.entryFile] = {denpendencies: item.denpendencies,code: item.code}})console.log(obj)// 4. 生成bundle文件console.log('--------------4--------------')this.generate(obj)}// 根據入口文件,獲取依賴和內容代碼parse(entryFile) {// 獲取到index.js中的文本內容const entryInfo = fs.readFileSync(entryFile, "utf-8")// 根據內容原文獲取抽象語法樹AST, ast.program.body才是內容的主體const ast = parser.parse(entryInfo, {sourceType: "module"})// 提取依賴,轉化處理const denpendencies = {}; // 用來記錄依賴的相對地址和絕對地址的對應關系traverse(ast, {// 提取type為ImportDeclaration的節點node,node.source.value為import()的參數ImportDeclaration({ node }) {// 通過node.source.value獲取為相對路徑,需要轉換為絕對地址,并和相對路徑一一對應const prefixPath = "./" // 這里是Mac系統的地址類型,不兼容Windows和Linuxdenpendencies[node.source.value] = prefixPath + path.join(path.dirname(entryFile), node.source.value)console.log(denpendencies)}})// 提取內容,轉化處理const { code } = transformFromAst(ast, null, {presets: ["@babel/preset-env"]})console.log(code)return {entryFile,denpendencies,code}}// 根據所有模塊及其依賴和代碼,生成bundlegenerate(obj) {// 根據output參數,獲取bundle存放路徑const filePath = path.join(this.output.path, this.output.filename)// 創建bundle的內容,內容主要有// 創建自執行函數,處理require module exports//const bundle = `(function(obj){function require(module){function reRequire(relativePath){return require(obj[module].denpendecies[relativePath])}var exports = {}(function(require, exports, code) {eval(code)})(reRequire, exports, obj[module].code)return exports}require('${this.entry}')})(${JSON.stringify(obj)})`fs.writeFileSync(filePath, bundle, "utf-8")} }結語
關于webpack的就寫到這里。其實工具對于我們研發人員來說,就是提升效率的一個途徑,這樣的途徑有很多中,比如:有gulp、rollup、grant還有百度fis等等,都各有千秋,用好了可以事半功倍,用不好反而事倍功半。
以上就是我對webpack使用過程中的簡要總結,希望能幫到大家。同時有不恰之處,還望大家批評指正。
最后喜歡我的小伙伴也可以通過關注公眾號“劍指大前端”,或者掃描下方二維碼聯系到我,進行經驗交流和分享,同時我也會定期分享一些大前端干貨,讓我們的開發從此不迷路。
關注后,回復“webpack”,即可獲取最新webpack講解教學視頻哦 🙂🙂🙂
總結
以上是生活随笔為你收集整理的弄懂webpack,只要看这一片就够了(文末有福利)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity3D 性能优化
- 下一篇: spring security:第一个程