webpack 是一個模塊打包器。webpack 的主要目標是將 javaScript
文件打包在一塊兒,打包後的文件用於在瀏覽器中使用,但它也可以勝任轉換(transform)、打包(bundle)或包裹(package)任何資源(resource or asset)。css
隨着 webpack 不斷地發展,webpack 配置變得愈來愈簡單,構建速度也愈來愈快,官方文檔上說 webpack4 比 webpack3 構建速度快了 98%,這還不只如此,官方標識在 webpack5 中,會使用多進程構建,進一步優化構建速度。html
本文已同步到我的博客,歡迎 start,謝謝。前端
入口是 webpack 構建開始的地方,經過入口文件,webpack 能夠找到入口文件所依賴的文件,並逐步遞歸,找出全部依賴的文件。vue
module.exports = { entry: "./path/to/my/entry/file.js" };
output 屬性告訴 webpack 在哪裏輸出它所建立的 bundles,以及如何命名這些文件。java
const path = require("path"); module.exports = { entry: "./path/to/my/entry/file.js", output: { path: path.resolve(__dirname, "dist"), filename: "my-first-webpack.bundle.js" } };
webpack 自身只支持 JavaScript。而 loader 可以讓 webpack 處理那些非 JavaScript 文件,而且先將它們轉換爲有效的模塊,而後添加到依賴圖中,這樣就能夠提供給應用程序使用。node
const path = require("path"); module.exports = { output: { filename: "my-first-webpack.bundle.js" }, module: { rules: [ { // 根據後綴名匹配須要處理的文件 test: /\.txt$/, // 使用對應的loader處理文件 use: "raw-loader" } ] } };
loader 其實就是一個 function,接收一個參數 source,就是當前的文件內容,而後稍加處理,就能夠 return 出一個新的文件內容。react
const loaderUtils = require("loader-utils"); module.exports = function(source) { // 獲取loader中傳遞的配置信息 const options = loaderUtils.getOptions(this); // 返回處理後的內容 this.callback(null, "/ *增長一個註釋 */" + source); // 也能夠直接return // return "/ *增長一個註釋 */" + source; };
插件其實就是一個類,經過監聽 webpack 執行流程上的鉤子函數,能夠更精密地控制 webpack 的輸出,包括:打包優化、資源管理和注入環境變量。webpack
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { module: { rules: [{ test: /\.txt$/, use: "raw-loader" }] }, plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })] };
咱們能夠利用 webpack 提供的鉤子函數,編寫自定義插件,至關於監聽 webpack 的事件,作出對應的響應,webpack 是經過Tapable進行事件流管理的。git
class APlugin { // apply方法,會在new plugin後被webpack自動執行。 apply(compiler) { // 能夠在任意的鉤子函數中去觸發自定義事件,也能夠監聽其餘事件:compiler.hooks.xxxx compiler.hooks.compilation.tap("APlugin", compilation => { compilation.hooks.afterOptimizeChunkAssets.tap("APlugin", chunks => { // 這裏只是簡單的打印了chunks,你若是有更多的想法,均可以在這裏實現。 console.log("打印chunks:", chunks); }); }); } }
在 webpack4 發佈後,相比 webpack3 的構建進行了高效地優化,速度提升了 98%,一些常規優化 webpack 都已經幫咱們作了,使得 webpack 變得愈來愈簡單,甚至能夠達到零配置,可是對於零配置而言,不能知足所有需求,因此仍是建議進行手動配置。github
最懶人的寫法,在 webpack 配置項中 mode = production ,webpack 就幫咱們把經常使用的配置都配好了,並且徹底能夠勝任大部分需求。
module.exports = { mode: "production" };
使用該配置後,webpack 會將 process.env.NODE_ENV 的值設爲 production。
而且還會幫咱們配置好如下插件:
若是不使用 plugin,webpack 會把全部文件都打包在一個 js 文件中,這每每會使得文件很大,加載時間會變得很長,咱們能夠配置 optimization.splitChunks 來設置拆分文件規則。
這是 webpack 默認的配置,也能夠根據本身需求作對應修改。
module.exports = { optimization: { splitChunks: { chunks: "async", // 參數多是:all,async和initial,這裏表示拆分異步模塊。 minSize: 30000, // 若是模塊的大小大於30kb,纔會被拆分 minChunks: 1, maxAsyncRequests: 5, // 按需加載時最大的請求數,意思就是說,若是拆得很小,就會超過這個值,限制拆分的數量。 maxInitialRequests: 3, // 入口處的最大請求數 automaticNameDelimiter: "~", // webpack將使用塊的名稱和名稱生成名稱(例如vendors~main.js) name: true, // 拆分塊的名稱 cacheGroups: { // 緩存splitchunks vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, // 一個模塊至少出現2次引用時,纔會被拆分 priority: -20, reuseExistingChunk: true } } } } };
縱觀 webpack 構建流程,咱們能夠發現整個構建過程主要花費時間的部分也就是遞歸遍歷各個 entry 而後尋找依賴逐個編譯的過程,每次遞歸都須要經歷 String->AST->String 的流程,通過 loader 還須要處理一些字符串或者執行一些 JS 腳本,介於 node.js 單線程的壁壘,webpack 構建慢一直成爲它飽受詬病的緣由。
// @file: webpack.config.js var HappyPack = require("happypack"); var happyThreadPool = HappyPack.ThreadPool({ size: 5 }); module.exports = { // ... plugins: [ new HappyPack({ id: "jsx", threadPool: happyThreadPool, loaders: ["babel-loader"] }), new HappyPack({ id: "styles", threadPool: happyThreadPool, loaders: ["style-loader", "css-loader", "less-loader"] }) ] }; exports.module.rules = [ { test: /\.js$/, use: "happypack/loader?id=jsx" }, { test: /\.less$/, use: "happypack/loader?id=styles" } ];
Happypack 其實是使用了 node processes 執行多線程構建,可讓多個 loader 並行執行,從而加快構建。
DllPlugin:用於打包單獨的動態連接庫文件。
DllReferencePlugin:用於在主要的配置文件中引入 DllPlugin 插件打包好的動態連接庫文件。
這裏須要建 2 個配置文件,先執行 webpack.dll.config.js,生成 mainfest,而後再執行 webpack.config.js 打包文件,能夠看到,構建速度有了很是大的提高。
動態連接庫配置:
// webpack.dll.config.js // 這裏配置DllPlugin,生成mainifest module.exports = { entry:{ // 將react相關,放入一個單獨的動態連接庫中 react:['react','react-dom'] }, output:{ filename:'[name].dll.js' }, plugins:[ new webpack.DllPlugin({ name: '_dll_[name]', path: path.join(__dirname, '[name].manifest.json'), ); ] };
使用打包後的動態連接庫:
// webpack.config.js // 這裏配置DllPlugin,生成mainifest module.exports = { plugins:[ new webpack.DllReferencePlugin({ manifest: require('./react.manifest.json') }); ] };
一、在處理 loader
時,配置 include
,縮小 loader
檢查範圍。
二、使用 alias
能夠更快地找到對應文件。
三、若是在 require
模塊時不寫後綴名,默認 webpack 會嘗試.js
,.json
等後綴名匹配,extensions
配置,讓 webpack 少作一點後綴匹配。
四、thread-loader
能夠將很是消耗資源的 loaders 轉存到 worker pool 中。
五、使用 cache-loader
啓用持久化緩存。使用 package.json 中的 "postinstall" 清除緩存目錄。
一、選擇合理額 Devtool 在大多數狀況下,cheap-module-eval-source-map
是最好的選擇。
二、開發階段通常不須要進行壓縮合並,提權單獨文件等操做。
三、webpack 會在輸出文件中生成路徑信息。然而在打包數千個模塊的項目中,會致使形成垃圾回收性能壓力。在 options.output.pathinfo
設置中關閉.
四、在開發階段,能夠直接引用 cdn
上的庫文件,使用 externals
配置全局對象,避免打包。
一、靜態資源上 cdn。
二、使用 tree shaking
,只打包用到的模塊,刪除沒有用到的模塊。
三、配置 scope hoisting
做用域提高,將多個 IIFE 放在一個 IIFE 中。
相關的代碼以下:
module.exports = { output: { // 靜態資源上cdn publicPath: "//xxx/cdn.com", // 不生成「所包含模塊信息」的相關注釋 pathinfo: false }, module: { rules: [ { test: /\.txt$/, use: "raw-loader", // 縮小loader檢查範圍 include: path.join(__dirname, "src") } ] }, plugins: [ // 開啓scope hoisting new ModuleConcatenationPlugin() ], resolve: { // 使用別名,加快搜索 alias: { "~": path.resolve(__dirname, "../src") }, // 配置用到的後綴名,方便webpack查找 extensions: ["js", "css"] }, // 開發階段引用cdn上文件,能夠避免打包庫文件 externals: { vue: "Vue", "element-ui": "ELEMENT" } };
webpack 在運行時大體分爲這幾個階段:
一、讀取 webpack.config.js 配置文件,生成 compiler 實例,並把 compiler 實例注入 plugin 中的 apply 方法中。
二、讀取配置的 Entries
,遞歸遍歷全部的入口文件。
三、對入口文件進行編譯,開始 compilation
過程,使用 loader
對文件內容編譯,再將編譯好的文件內容解析成 AST
靜態語法樹。
四、遞歸依賴的模塊,重複第 3 步,生成 AST
語法樹,在 AST
語法樹中能夠分析到模塊之間的依賴關係,對應作出優化。
五、將全部模塊中的 require 語法替換成__webpack_require__
來模擬模塊化操做。
六、最後把全部的模塊打包進一個自執行函數(IIFE)中。
這張圖畫的很好,把webpack的流程畫的很細緻。
圖片是參考文章Webpack揭祕——走向高階前端的必經之路裏的,若有侵權,請聯繫我,立刻刪除。
::: tip 參考資料
webpack 官網