时下最流行前端构建工具Webpack 入门总结
作者:wenjuanrao,騰訊 PCG 前端開發工程師
最近梳理了下以前 webpack 的相關開發經驗,整理和總結了一份入門筆記,歡迎大家圍觀和批評指正。
隨著 web 應用越來越復雜和龐大,前端技術迅猛發展,各路大神各顯神通,多種優秀的前端框架、新語言和其他相關技術(如下圖所示)不斷涌現,這些都極大地提高了我們的開發效率。
前端技術棧然鵝,我們都知道這些技術都有一個共同點,那就是源代碼都無法直接在瀏覽器上運行。此時,我們就需要通過構建工具將這些代碼轉換成瀏覽器可執行的 JS、CSS、HTML。這對前端構建工具有了更高的要求。
歷史上也出現了一系列構建工具,一些常見的如下:
常見的構建工具其中,Webpack 憑借其強大的功能與良好的使用體驗,還有有龐大的社區支持,在眾多構建工具中脫穎而出成為時下最流行的構建工具。
在言歸正傳之前,我們先來簡單了解一下 webpack。
webpackWebpack 簡介
根據官網介紹,Webpack 是一個用于現代 JavaScript 應用程序的 靜態模塊打包工具。當 webpack 處理應用程序時,它會在內部從一個或多個入口點構建一個 依賴圖(dependency graph),然后將你項目中所需的每一個模塊組合成一個或多個 bundles,它們均為靜態資源,用于展示你的內容。
Webpack 一些核心概念:
Entry:入口,指示 Webpack 應該使用哪個模塊,來作為構建其內部 依賴圖(dependency graph) 的開始。
Output:輸出結果,告訴 Webpack 在哪里輸出它所創建的 bundle,以及如何命名這些文件。
Module:模塊,在 Webpack 里一切皆模塊,一個模塊對應著一個文件。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊。
Chunk:代碼塊,一個 Chunk 由多個模塊組合而成,用于代碼合并與分割。
Loader:模塊代碼轉換器,讓 webpack 能夠去處理除了 JS、JSON 之外的其他類型的文件,并將它們轉換為有效 模塊,以供應用程序使用,以及被添加到依賴圖中。
Plugin:擴展插件。在 webpack 運行的生命周期中會廣播出許多事件,plugin 可以監聽這些事件,在合適的時機通過 webpack 提供的 api 改變輸出結果。常見的有:打包優化,資源管理,注入環境變量。
Mode:模式,告知 webpack 使用相應模式的內置優化
Browser Compatibility:瀏覽器兼容性,Webpack 支持所有符合 ES5 標準 的瀏覽器(IE8 以上版本)
Webpack 的作用
Webpack 的作用非常多,簡單列舉幾點如下:
我們可以通過 loader 和 plugin 機制去進一步擴展能力,按照項目需要去實現個性化的功能。
鋪墊了那么多,現在回歸主題吧!
Webpack 是由 nodejs 編寫的前端資源加載/打包工具,由 nodejs 提供了強大的文件處理,IO 能力。
Loader 和 Plugin 在 Webpack 里是支柱能力。在整個構建流程中,Loader 和 Plugin 對編譯結果起著決定性的作用,下面主要講一下 Webpack 中一些常用的 Loader 和 Plugin。
Loader
簡介
webpack 中提供了一種處理多種文件格式的機制,這便是 Loader,我們可以把 Loader 當成一個轉換器,它可以將某種格式的文件轉換成 Wwebpack 支持打包的模塊。
在 Webpack 中,一切皆模塊,我們常見的 Javascript、CSS、Less、Typescript、Jsx、圖片等文件都是模塊,不同模塊的加載是通過模塊加載器來統一管理的,當我們需要使用不同的 Loader 來解析不同類型的文件時,我們可以在 module.rules 字段下配置相關規則。
loader 特點
loader 本質上是一個函數,output=loader(input) // input 可為工程源文件的字符串,也可是上一個 loader 轉化后的結果;
第一個 loader 的傳入參數只有一個:資源文件(resource file)的內容;
loader 支持鏈式調用,webpack 打包時是按照數組從后往前的順序將資源交給 loader 處理的。
支持同步或異步函數。
代碼結構
代碼結構通常如下:
// source:資源輸入,對于第一個執行的 loader 為資源文件的內容;后續執行的 loader 則為前一個 loader 的執行結果 //?sourceMap:?可選參數,代碼的?sourcemap?結構 //?data:?可選參數,其它需要在?Loader?鏈中傳遞的信息,比如?posthtml/posthtml-loader?就會通過這個參數傳遞參數的?AST?對象const?loaderUtils?=?require('loader-utils'); module.exports?=?function(source,?sourceMap?,?data?)?{//?獲取到用戶給當前?Loader?傳入的?optionsconst?options?=?loaderUtils.getOptions(this);// TODO:?此處為轉換source的邏輯return?source; };常用的 Loader
1. babel-loader
babel-loader 基于 babel,用于解析 JavaScript 文件。babel 有豐富的預設和插件,babel 的配置可以直接寫到 options 里或者單獨寫道配置文件里。
Babel 是一個 Javscript 編譯器,可以將高級語法(主要是 ECMAScript 2015+ )編譯成瀏覽器支持的低版本語法,它可以幫助你用最新版本的 Javascript 寫代碼,提高開發效率。
webpack 通過 babel-loader 使用 Babel。
用法:
#?環境要求: webpack?4.x?||?5.x?|?babel-loader?8.x?|?babel?7.x#?安裝依賴包: npm?install?-D?babel-loader?@babel/core?@babel/preset-env?webpack然后,我們需要建立一個 Babel 配置文件來指定編譯的規則。
Babel 配置里的兩大核心:插件數組(plugins) 和 預設數組(presets)。
Babel 的預設(preset)可以被看作是一組 Babel 插件的集合,由一系列插件組成。
常用預設:
@babel/preset-env? ? ? ? ? ? ? ES2015+ 語法
@babel/preset-typescript? ? TypeScript
@babel/preset-react? ? ? ? ? ? React
@babel/preset-flow? ? ? ? ? ? ? Flow
插件和預設的執行順序:
插件比預設先執行
插件執行順序是插件數組從前向后執行
預設執行順序是預設數組從后向前執行
webpack 配置代碼:
//?webpack.config.js module:?{rules:?[{test:?/\.m?js$/,exclude:?/node_modules/,use:?{loader:?'babel-loader',options:?{presets:?[['@babel/preset-env',?{?targets:?"defaults"?}]],plugins:?['@babel/plugin-proposal-class-properties'],//?緩存?loader?的執行結果到指定目錄,默認為node_modules/.cache/babel-loader,之后的?webpack?構建,將會嘗試讀取緩存cacheDirectory:?true,}}}] }以上 options 參數也可單獨寫到配置文件里,許多其他工具都有類似的配置文件:ESLint (.eslintrc)、Prettier (.prettierrc)。
配置文件我們一般只需要配置 presets(預設數組) 和 plugins(插件數組) ,其他一般也用不到,代碼示例如下:
//?babel.config.js module.exports?=?(api)?=>?{return?{presets:?['@babel/preset-react',['@babel/preset-env',?{useBuiltIns:?'usage',corejs:?'2',targets:?{chrome:?'58',ie:?'10'}}]],plugins:?['@babel/plugin-transform-react-jsx','@babel/plugin-proposal-class-properties']}; };推薦閱讀:
babel 配置文件相關文檔
插件手冊
2. ts-loader
為 webpack 提供的 TypeScript loader,打包編譯 Typescript。
安裝依賴:
npm?install?ts-loader?--save-dev npm?install?typescript?--devwebpack 配置如下:
//?webpack.config.json module.exports?=?{mode:?"development",devtool:?"inline-source-map",entry:?"./app.ts",output:?{filename:?"bundle.js"},resolve:?{//?Add?`.ts`?and?`.tsx`?as?a?resolvable?extension.extensions:?[".ts",?".tsx",?".js"]},module:?{rules:?[//?all?files?with?a?`.ts`?or?`.tsx`?extension?will?be?handled?by?`ts-loader`{?test:?/\.tsx?$/,?loader:?"ts-loader"?}]} };還需要 typescript 編譯器的配置文件tsconfig.json:
{"compilerOptions":?{//?目標語言的版本"target":?"esnext",//?生成代碼的模板標準"module":?"esnext","moduleResolution":?"node",//?允許編譯器編譯JS,JSX文件"allowJS":?true,//?允許在JS文件中報錯,通常與allowJS一起使用"checkJs":?true,"noEmit":?true,//?是否生成source?map文件"sourceMap":?true,//?指定jsx模式"jsx":?"react"},//?編譯需要編譯的文件或目錄"include":?["src","test"],//?編譯器需要排除的文件或文件夾"exclude":?["node_modules","**/*.spec.ts"] }更多配置請看官網
3. markdown-loader
markdown 編譯器和解析器
用法:只需將 loader 添加到您的配置中,并設置 options。
js 代碼里引入 markdown 文件:
//?file.js import?md?from?'markdown-file.md'; console.log(md);webpack 配置:
//?wenpack.config.js const?marked?=?require('marked'); const?renderer?=?new?marked.Renderer(); module.exports?=?{//?...module:?{rules:?[{test:?/\.md$/,use:?[{loader:?'html-loader'},{loader:?'markdown-loader',options:?{pedantic:?true,renderer}}]}],}, };4. raw-loader
可將文件作為字符串導入:
//?app.js import?txt?from?'./file.txt';//?webpack.config.js module.exports?=?{module:?{rules:?[{test:?/\.txt$/,use:?'raw-loader'}]} }5. file-loader
用于處理文件類型資源,如 jpg,png 等圖片。返回值為 publicPath 為準:
//?file.js import?img?from?'./webpack.png'; console.log(img);?//?編譯后:https://www.tencent.com/webpack_605dc7bf.png//?webpack.config.js module.exports?=?{module:?{rules:?[{test:?/\.(png|jpe?g|gif)$/i,loader:?'file-loader',options:?{name:?'[name]_[hash:8].[ext]',publicPath:?"https://www.tencent.com",},},],}, };css 文件里的圖片路徑變成如下:
/*?index.less?*/ .tag?{background-color:?red;background-image:?url(./webpack.png); } /*?編譯后:*/ background-image:?url(https://www.tencent.com/webpack_605dc7bf.png);6. url-loader
它與 file-loader 作用相似,也是處理圖片的,只不過 url-loader 可以設置一個根據圖片大小進行不同的操作,如果該圖片大小大于指定的大小,則將圖片進行打包資源,否則將圖片轉換為 base64 字符串合并到 js 文件里。
module.exports?=?{module:?{rules:?[{test:?/\.(png|jpg|jpeg)$/,use:?[{loader:?'url-loader',options:?{name:?'[name]_[hash:8].[ext]',//?這里單位為(b)?10240?=>?10kb//?這里如果小于10kb則轉換為base64打包進js文件,如果大于10kb則打包到對應目錄limit:?10240,}}]}]} }7. svg-sprite-loader
會把引用的 svg 文件 塞到一個個 symbol 中,合并成一個大的 SVG sprite,使用時則通過 SVG 的 <use> 傳入圖標 id 后渲染出圖標。最后將這個大的 svg 放入 body 中。symbol 的 id 如果不特別指定,就是你的文件名。
該 loader 可以搭配svgo-loader一起使用,svgo-loader 是 svg 的優化器,它可以刪除和修改 SVG 元素,折疊內容,移動屬性等,具體不展開描述。感興趣的可以移步官方介紹。
用途:可以用來開發統一的圖標管理庫。
效果示例代碼:
//?js文件里用法 import?webpack?from?'./webpack/webpack.svg'; const?type?=?'webpack'; const?svg?=??`<svg><use?xlink:href="#${type}"/></svg>`; const?dom?=?`<div?class="tag">${svg}</div>`;document.getElementById('react-app').innerHTML?=?dom;//?webpack.config.js module.exports?=?{module:?{rules:?[{test:?/\.(png|jpg|jpeg)$/,use:?[{test:?/\.svg$/,use:?[{loader:?'svg-sprite-loader'},'svgo-loader']},]}]} }原理:利用 svg 的 symbol 元素,將每個 icon 包裹在 symbol 中,通過 use 使用該 symbol。
8. style-loader
通過注入<style>標簽將 CSS 插入到 DOM 中。
注意:
如果因為某些原因你需要將 CSS 提取為一個文件(即不要將 CSS 存儲在 JS 模塊中),此時你需要使用插件mini-css-extract-plugin(后面的 Pugin 部分會介紹);
對于 development 模式(包括 webpack-dev-server)你可以使用 style-loader,因為它是通過<style></style>標簽的方式引入 CSS 的,加載會更快;
不要將 style-loader 和 mini-css-extract-plugin 針對同一個 CSS 模塊一起使用!
代碼示例見下文 postcss-loader。
9. css-loader
僅處理 css 的各種加載語法(@import 和 url()函數等),就像 js 解析 import/require() 一樣。
代碼示例見下文 postcss-loader。
10. postcss-loader
PostCSS 是一個允許使用 JS 插件轉換樣式的工具。這些插件可以檢查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 編譯尚未被瀏覽器廣泛支持的先進的 CSS 語法,內聯圖片,以及其它很多優秀的功能。
PostCSS 在業界被廣泛地應用。PostCSS 的autoprefixer插件是最流行的 CSS 處理工具之一。autoprefixer 添加了瀏覽器前綴,它使用 Can I Use 上面的數據。
安裝
npm?install?postcss-loader?autoprefixer?--save-dev代碼示例:
//?webpack.config.js const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin'); const?isDev?=?process.NODE_ENV?===?'development';module.exports?=?{module:?{rules:?[{test:?/\.(css|less)$/,exclude:?/node_modules/,use:?[isDev???'style-loader'?:?MiniCssExtractPlugin.loader,{loader:?'css-loader',options:?{importLoaders:?1,}},{loader:?'postcss-loader'},{loader:?'less-loader',options:?{lessOptions:?{javascriptEnabled:?true}}}]}]} }然后在項目根目錄創建 postcss.config.js,并且設置支持哪些瀏覽器,必須設置支持的瀏覽器才會自動添加添加瀏覽器兼容:
module.exports?=?{plugins:?[require('precss'),require('autoprefixer')({'browsers':?['defaults','not?ie?<?11','last?2?versions','>?1%','iOS?7','last?3?iOS?versions']})] }截止到目前,PostCSS 有 200 多個插件。你可以在插件列表或搜索目錄找到它們。
了解更多請移步鏈接
11. less-loader
解析 less,轉換為 css。
代碼示例見上文 postcss-loader
了解更多請移步鏈接。
Plugin
Plugin 簡介
Webpack 就像一條生產線,要經過一系列處理流程后才能將源文件轉換成輸出結果。這條生產線上的每個處理流程的職責都是單一的,多個流程之間有存在依賴關系,只有完成當前處理后才能交給下一個流程去處理。插件就像是一個插入到生產線中的一個功能,在特定的時機對生產線上的資源做處理。
Webpack 通過 Tapable 來組織這條復雜的生產線。Webpack 在運行過程中會廣播事件,插件只需要監聽它所關心的事件,就能加入到這條生產線中,去改變生產線的運作。Webpack 的事件流機制保證了插件的有序性,使得整個系統擴展性很好。
——「深入淺出 Webpack」
常用 Plugin
1. copy-webpack-plugin
將已經存在的單個文件或整個目錄復制到構建目錄。
const?CopyPlugin?=?require("copy-webpack-plugin"); module.exports?=?{plugins:?[new?CopyPlugin({patterns:?[{from:?'./template/page.html',to:?`${__dirname}/output/cp/page.html`},],}),], };2. html-webpack-plugin
基本作用是生成 html 文件:
單頁應用可以生成一個 html 入口,多頁應用可以配置多個 html-webpack-plugin 實例來生成多個頁面入口;
為 html 引入外部資源如 script、link,將 entry 配置的相關入口 chunk 以及 mini-css-extract-plugin 抽取的 css 文件插入到基于該插件設置的 template 文件生成的 html 文件里面,具體的方式是 link 插入到 head 中,script 插入到 head 或 body 中。
3. clean-webpack-plugin
默認情況下,這個插件會刪除 webpack 的 output.path 中的所有文件,以及每次成功重新構建后所有未使用的資源。
這個插件在生產環境用的頻率非常高,因為生產環境經常會通過 hash 生成很多 bundle 文件,如果不進行清理的話每次都會生成新的,導致文件夾非常龐大。
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin'); module.exports?=?{plugins:?[new?CleanWebpackPlugin(),] };4. mini-css-extract-plugin
本插件會將 CSS 提取到單獨的文件中,為每個包含 CSS 的 JS 文件創建一個 CSS 文件。
//?建議?mini-css-extract-plugin?與?css-loader?一起使用 //?將?loader?與?plugin?添加到?webpack?配置文件中const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin'); module.exports?=?{plugins:?[new?MiniCssExtractPlugin()],module:?{rules:?[{test:?/\.css$/i,use:?[MiniCssExtractPlugin.loader,?'css-loader'],},],}, };可以結合上文關于 style-loader 的介紹一起了解該插件。
5. webpack.HotModuleReplacementPlugin
模塊熱替換插件,除此之外還被稱為 HMR。
該功能會在應用程序運行過程中,替換、添加或刪除 模塊,而無需重新加載整個頁面。主要是通過以下幾種方式,來顯著加快開發速度:
保留在完全重新加載頁面期間丟失的應用程序狀態;
只更新變更內容,以節省寶貴的開發時間;
在源代碼中 CSS/JS 產生修改時,會立刻在瀏覽器中進行更新,這幾乎相當于在瀏覽器 devtools 直接更改樣式。
啟動方式有 2 種:
引入插件 webpack.HotModuleReplacementPlugin 并且設置 devServer.hot: true
命令行加 --hot 參數
package.json 配置:
{"scripts":?{"start":?"NODE_ENV=development?webpack?serve?--progress?--mode=development?--config=scripts/dev.config.js?--hot"} }webpack 的配置如下:
//?scripts/dev.config.js文件 const?webpack?=?require('webpack'); const?path?=?require('path'); const?outputPath?=?path.resolve(__dirname,?'./output/public'); module.exports?=?{mode:?'development',entry:?{preview:?['./node_modules/webpack-dev-server/client/index.js?path=http://localhost:9000',path.resolve(__dirname,?'../src/preview/index.js')],},output:?{filename:?'static/js/[name]/index.js',//?動態生成的chunk在輸出時的文件名稱chunkFilename:?'static/js/[name]/chunk_[chunkhash].js',path:?outputPath},plugins:?[//?大多數情況下不需要任何配置new?webpack.HotModuleReplacementPlugin(),],devServer:?{//?僅在需要提供靜態文件時才進行配置contentBase:?outputPath,//?publicPath:?'',?//?值默認為'/'compress:?true,port:?9000,watchContentBase:?true,hot:?true,//?在服務器啟動后打開瀏覽器open:?true,//?指定打開瀏覽器時要瀏覽的頁面openPage:?['pages/preview.html'],//?將產生的文件寫入硬盤。?寫入位置為 output.path 配置的目錄writeToDisk:?true,} }注意:HMR 絕對不能被用在生產環境。
6. webpack.DefinePlugin
創建一個在編譯時可以配置的全局常量。這會對開發模式和生產模式的構建允許不同的行為非常有用。因為這個插件直接執行文本替換,給定的值必須包含字符串本身內的實際引號。
通常,有兩種方式來達到這個效果,使用'"production"', 或者使用 JSON.stringify('production')
//?webpack.config.js const?isProd?=?process.env.NODE_ENV?===?'production'; module.exports?=?{plugins:?[new?webpack.DefinePlugin({PAGE_URL:?JSON.stringify(isProd??'https://www.tencent.com/page':?'http://testsite.tencent.com/page')}),] }//?代碼里面直接使用 console.log(PAGE_URL);7. webpack-bundle-analyzer
可以看到項目各模塊的大小,可以按需優化.一個 webpack 的 bundle 文件分析工具,將 bundle 文件以可交互縮放的 treemap 的形式展示。
const?BundleAnalyzerPlugin?=?require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports?=?{plugins:?[new?BundleAnalyzerPlugin()] }啟動服務:
生產環境查看:NODE_ENV=production npm run build
開發環境查看:NODE_ENV=development npm run start
最終效果:
了解更多請移步鏈接
8. SplitChunksPlugin
代碼分割。
module.exports?=?{optimization:?{splitChunks:?{//?分隔符//?automaticNameDelimiter:?'~',//?all,?async,?and?initialchunks:?'all',//?它可以繼承/覆蓋上面 splitChunks 中所有的參數值,除此之外還額外提供了三個配置,分別為:test, priority 和 reuseExistingChunkcacheGroups:?{vendors:?{//?表示要過濾?modules,默認為所有的?modules,可匹配模塊路徑或?chunk?名字,當匹配的是?chunk?名字的時候,其里面的所有?modules?都會選中test:?/[\\/]node_modules\/antd\//,// priority:表示抽取權重,數字越大表示優先級越高。因為一個 module 可能會滿足多個 cacheGroups 的條件,那么抽取到哪個就由權重最高的說了算;//?priority:?3,// reuseExistingChunk:表示是否使用已有的 chunk,如果為 true 則表示如果當前的 chunk 包含的模塊已經被抽取出去了,那么將不會重新生成新的。reuseExistingChunk:?true,name:?'antd'}}}}, }騰訊程序員視頻號直播來了!
騰訊程序員視頻號最新視頻一鍵輕松轉換PDF與Office 超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生
總結
以上是生活随笔為你收集整理的时下最流行前端构建工具Webpack 入门总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue 跨平台性能优化十法
- 下一篇: 实时监控:基于流计算 Oceanus (