摘要:用于對(duì)模塊的源代碼進(jìn)行轉(zhuǎn)換。將基礎(chǔ)模塊打包進(jìn)動(dòng)態(tài)鏈接庫(kù),當(dāng)依賴的模塊存在于動(dòng)態(tài)鏈接庫(kù)中時(shí),無需再次打包,而是直接從動(dòng)態(tài)鏈接庫(kù)中獲取。負(fù)責(zé)打包出動(dòng)態(tài)鏈接庫(kù),負(fù)責(zé)從主要配置文件中引入插件打包好的動(dòng)態(tài)鏈接庫(kù)文件。告一段落,淺嘗輒止。
吐槽一下
webpack 自出現(xiàn)時(shí),一直備受青睞。作為強(qiáng)大的打包工具,它只是出現(xiàn)在項(xiàng)目初始或優(yōu)化的階段。如果沒有參與項(xiàng)目的構(gòu)建,接觸的機(jī)會(huì)幾乎為零。即使是參與了,但也會(huì)因?yàn)轫?xiàng)目的周期短,從網(wǎng)上東拼西湊草草了事。
縱觀網(wǎng)上的 webpack 教程,要么是蜻蜓點(diǎn)水,科普了一些常規(guī)配置項(xiàng);要么是過于深入原理,于實(shí)際操作無益。最近一段時(shí)間,我把 webpack 的官方文檔來來回回地看了幾遍,結(jié)果發(fā)現(xiàn),真香。中文版的官方文檔,通俗易懂,很感謝翻譯組的辛勤奉獻(xiàn)。看完之后,雖然達(dá)不到爐火純青的地步,但也不會(huì)捉襟見肘,疲于應(yīng)付。
對(duì)于這種工具類的博文,依然沿襲 用Type馴化JavaScript 的風(fēng)格,串聯(lián)各個(gè)概念。至于細(xì)節(jié),就是官方文檔的事了。
本文基于 webpack v4.31.0 版本。
TapableTapable 是一個(gè)小型的庫(kù),允許你對(duì)一個(gè) javascript 模塊添加和應(yīng)用插件。它可以被繼承或混入到其他模塊中。類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發(fā)和處理。除此之外,Tapable 還允許你通過回調(diào)函數(shù)的參數(shù),訪問事件的“觸發(fā)者(emittee)”或“提供者(producer)”。
tapable 是 webpack 的核心,webpack 中的很多對(duì)象(compile, compilation等)都擴(kuò)展自tapable,包括 webpack 也是 tapable 的實(shí)例。擴(kuò)展自 tapable 的對(duì)象內(nèi)部會(huì)有很多鉤子,它們貫穿了 webpack 構(gòu)建的整個(gè)過程。我們可以利用這些鉤子,在其被觸發(fā)時(shí),做一些我們想做的事情。
拋開 webpack 不談,先看看 tapable 的簡(jiǎn)單使用。
// Main.js const { SyncHook } = require("tapable"); class Main { constructor(options) { this.hooks = { init: new SyncHook(["init"]) }; this.plugins = options.plugins; this.init(); } init() { this.beforeInit(); if (Array.isArray(this.plugins)) { this.plugins.forEach(plugin => { plugin.apply(this); }) } this.hooks.init.call("初始化中。。。"); this.afterInit(); } beforeInit() { console.log("初始化前。。。"); } afterInit() { console.log("初始化后。。。"); } } module.exports = Main; // MyPlugin.js class MyPlugin { apply(main) { main.hooks.init.tap("MyPlugin", param => { console.log("init 鉤子,做些啥;", param); }); } }; module.exports = MyPlugin; // index.js const Main = require("./Main"); const MyPlugin = require("./MyPlugin"); let myPlugin = new MyPlugin(); new Main({ plugins: [myPlugin] }); // 初始化前。。。 // init 鉤子,做些啥; 初始化中。。。 // 初始化后。。。
理解起來很簡(jiǎn)單,就是在 init 處觸發(fā)鉤子,this.hooks.init.call(params) 類似于我們熟悉的 EventEmitter.emit("init", params)。main.hooks.init.tap 類似于 EventEmitter.on("init", callback),在 init鉤子上綁定一些我們想做的事情。在后面將要說的 webpack 自定義插件,就是在 webpack 中的某個(gè)鉤子處,插入自定義的事。
理清概念
依賴圖
在單頁(yè)面應(yīng)用中,只要有一個(gè)入口文件,就可以把散落在項(xiàng)目下的各個(gè)文件整合到一起。何謂依賴,當(dāng)前文件需要什么,什么就是當(dāng)前文件的依賴。依賴引入的形式有如下:
ES2015 import 語(yǔ)句
CommonJS require() 語(yǔ)句
AMD define 和 require 語(yǔ)句
樣式(url(...))或 HTML 文件()中的圖片鏈接
入口(entry)
入口起點(diǎn)(entry point)指示 webpack 應(yīng)該使用哪個(gè)模塊,來作為構(gòu)建其內(nèi)部依賴圖(dependency graph)的開始。
輸出(output)
output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的 bundle,以及如何命名這些文件。
模塊(module)
決定了如何處理項(xiàng)目中的不同類型的模塊。比如設(shè)置 loader,處理各種模塊。設(shè)置 noParse,忽略無需 webpack 解析的模塊。
解析(resolve)
設(shè)置模塊如何被解析。引用依賴時(shí),需要知道依賴間的路徑關(guān)系,應(yīng)遵循何種解析規(guī)則。比如給路徑設(shè)置別名(alias),解析模塊的搜索目錄(modules),解析 loader 包路徑(resolveLoader)等。
外部擴(kuò)展(externals)
防止將某些 import 的包(package)打包到 bundle 中,而是在運(yùn)行時(shí)(runtime)再去從外部獲取這些擴(kuò)展依賴。比如說,項(xiàng)目中引用了 jQuery 的CDN資源,在使用 import $ from "jquery";時(shí),webpack 會(huì)把 jQuery 打包進(jìn) bundle,其實(shí)這是沒有必要的,此時(shí)需要配置 externals: {jquery: "jQuery"},將其剔除 bundle。
插件(plugins)
用于以各種方式自定義 webpack 構(gòu)建過程。可以利用 webpack 中的鉤子,做些優(yōu)化或者搞些小動(dòng)作。
開發(fā)設(shè)置(devServer)
顧名思義,就是開發(fā)時(shí)用到的選項(xiàng)。比如,開發(fā)服務(wù)根路徑(contentBase),模塊熱替換(hot,需配合 HotModuleReplacementPlugin 使用),代理(proxy)等。
模式(mode)
提供 mode 配置選項(xiàng),告知 webpack 使用相應(yīng)環(huán)境的內(nèi)置優(yōu)化。具體可見 模式(mode)
優(yōu)化(optimization)
從 webpack 4 開始,會(huì)根據(jù)你選擇的 mode 來執(zhí)行不同的優(yōu)化,不過所有的優(yōu)化還是可以手動(dòng)配置和重寫。比如,CommonsChunkPlugin被 optimization.splitChunks 取代。
webpack 差不多就是這幾個(gè)配置項(xiàng),搞清楚這幾個(gè)概念,上手還是比較容易的。
代碼分離現(xiàn)在的前端項(xiàng)目越來越復(fù)雜,如果最終導(dǎo)出為一個(gè) bundle,會(huì)極大地影響加載速度。切割 bundle,控制資源加載優(yōu)先級(jí),按需加載或并行加載,合理應(yīng)用就會(huì)大大縮短加載時(shí)間。官方文檔提供了三種常見的代碼分離方法:
入口起點(diǎn)
配置多個(gè)入口文件,然后將最終生成的過個(gè) bundle 出入到 HTML 中。
// webpack.config.js entry: { index: "./src/index.js", vendor: "./src/vendor.js" } output: { filename: "[name].bundle.js", }, plugins: [ new HtmlWebpackPlugin({ chunks: ["vendor", "index"] }) ]
不過如果這兩個(gè)文件中存在相同的模塊,這就意味著相同的模塊被加載了兩次。此時(shí),我們就需要提取出重復(fù)的模塊。
防止重復(fù)
在 webpack 老的版本中,CommonsChunkPlugin 常用來提取公共的模塊。新版本中 SplitChunksPlugin 取而代之,可以通過 optimization.splitChunks 設(shè)置,多見于多頁(yè)面應(yīng)用。
動(dòng)態(tài)導(dǎo)入
就是在需要時(shí)再去加載模塊,而不是一股腦的全部加載。webpack 還提供了預(yù)取和預(yù)加載的方式。非入口 chunk,我們可以通過 chunkFilename 為其命名。常見的如,vue 路由動(dòng)態(tài)導(dǎo)入。
// webpack.config.js output: { chunkFilename: "[name].bundle.js", } // index.js import(/* webpackChunkName: "someJs" */ "someJs"); import(/* webpackPrefetch: true */ "someJs"); import(/* webpackPreload: true */ "someJs");緩存
基于瀏覽器的緩存策略,我們知道如果本地緩存命中,則無需再次請(qǐng)求資源。對(duì)于改動(dòng)不頻繁或基本不會(huì)再做改動(dòng)的模塊,可以剝離出來。
// webpack.config.js output: { filename: "[name].[contenthash].js", }
按照我們的想法,只要模塊的內(nèi)容沒有變化,對(duì)應(yīng)的名字也就不會(huì)發(fā)生變化,這樣緩存就會(huì)起作用了。事實(shí)上并非如此,webpack 打包后的文件,并非只有用戶自己的代碼,還包括管理用戶代碼的代碼,如 runtime 和 manifest。
模塊依賴間的整合并不是簡(jiǎn)單的代碼拼接,其中包括模塊的加載和解析邏輯。注入的 runtime 和 manifest 在每次構(gòu)建后都會(huì)發(fā)生變化。這就導(dǎo)致了即使用戶代碼沒有變化,某些 hash 還是發(fā)生了改變。通過 optimization.runtimeChunk 提取 runtime 代碼。通過 optimization.splitChunks 剝離第三方庫(kù)。比如, react,react-dom。
module.exports = { //... optimization: { splitChunks: { cacheGroups: { vendor: { test: /[/]node_modules[/](react|react-dom)[/]/, name: "vendor", chunks: "all", } } } } };
最后使用 HashedModuleIdsPlugin 來消除因模塊 ID 變動(dòng)帶來的影響。
loaderloader 用于對(duì)模塊的源代碼進(jìn)行轉(zhuǎn)換。loader 是導(dǎo)出為一個(gè)函數(shù)的 node 模塊。該函數(shù)在 loader 轉(zhuǎn)換資源的時(shí)候調(diào)用。給定的函數(shù)將調(diào)用 loader API,并通過 this 上下文訪問。
// loader API; this.callback( err: Error | null, content: string | Buffer, sourceMap?: SourceMap, meta?: any ); // sync loader module.exports = function(content, map, meta){ this.callback(null, syncOperation(content, map, meta)); return; } // async loader module.exports = function(content, map, meta){ let callback = this.async(); asyncOperation(content, (error, result) => { if(error) callback(error); callback(null, result, map, meta); return; }) }
多個(gè) loader 串行時(shí),在從右向左執(zhí)行 loader 之前,會(huì)向從左到右調(diào)用 loader 上的 pitch 方法。如果在 pitch 中返回了結(jié)果,則會(huì)跳過后續(xù) loader。
|- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader normal execution |- b-loader normal execution |- a-loader normal execution |- a-loader `pitch` |- b-loader `pitch` returns a module |- a-loader normal executionplugins
webpack 的自定義插件和本文開頭 Tapable 中的差不多。webpack 插件是一個(gè)具有 apply 方法的 JavaScript 對(duì)象。apply 方法會(huì)被 webpack compiler 調(diào)用,并且 compiler 對(duì)象可在整個(gè)編譯生命周期訪問。鉤子有同步的,也有異步的,這需要根據(jù) webpack 提供的 API 文檔。
// 官方例子 class FileListPlugin { apply(compiler) { // emit 是異步 hook,使用 tapAsync 觸及它,還可以使用 tapPromise/tap(同步) compiler.hooks.emit.tapAsync("FileListPlugin", (compilation, callback) => { // 在生成文件中,創(chuàng)建一個(gè)頭部字符串: var filelist = "In this build: "; // 遍歷所有編譯過的資源文件, // 對(duì)于每個(gè)文件名稱,都添加一行內(nèi)容。 for (var filename in compilation.assets) { filelist += "- " + filename + " "; } // 將這個(gè)列表作為一個(gè)新的文件資源,插入到 webpack 構(gòu)建中: compilation.assets["filelist.md"] = { source: function() { return filelist; }, size: function() { return filelist.length; } }; callback(); }); } } module.exports = FileListPlugin;
ProvidePlugin
自動(dòng)加載模塊,無需處處引用。有點(diǎn)類似 expose-loader。
// webpack.config.js new webpack.ProvidePlugin({ $: "jquery", }) // some.js $("#item");
DllPlugin
將基礎(chǔ)模塊打包進(jìn)動(dòng)態(tài)鏈接庫(kù),當(dāng)依賴的模塊存在于動(dòng)態(tài)鏈接庫(kù)中時(shí),無需再次打包,而是直接從動(dòng)態(tài)鏈接庫(kù)中獲取。DLLPlugin 負(fù)責(zé)打包出動(dòng)態(tài)鏈接庫(kù),DllReferencePlugin 負(fù)責(zé)從主要配置文件中引入 DllPlugin 插件打包好的動(dòng)態(tài)鏈接庫(kù)文件。
// webpack-dll-config.js // 先執(zhí)行該配置文件 output: { path: path.join(__dirname, "dist"), filename: "MyDll.[name].js", library: "[name]_[hash]" }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, "dist", "[name]-manifest.json"), name: "[name]_[hash]" }) ] // webpack-config.js // 后執(zhí)行該配置文件 plugins: [ new webpack.DllReferencePlugin({ manifest: require("../dll/dist/alpha-manifest.json") }), ]
HappyPack
啟動(dòng)子進(jìn)程處理任務(wù),充分利用資源。不過進(jìn)程間的通訊比較耗資源,要酌情處理。
const HappyPack = require("happypack"); // loader { test: /.js$/, use: ["happypack/loader?id=babel"], exclude: path.resolve(__dirname, "node_modules"), }, // plugins new HappyPack({ id: "babel", loaders: ["babel-loader?cacheDirectory"], }),
webpack-bundle-analyzer
webpack 打包后的分析工具。
webpack 告一段落,淺嘗輒止。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/109637.html
摘要:本次的重點(diǎn)主要集中在開發(fā)環(huán)境上,生產(chǎn)環(huán)境則是使用的默認(rèn)模式。開發(fā)環(huán)境開發(fā)環(huán)境沒什么好說的了,簡(jiǎn)單易配置,官網(wǎng)很詳細(xì)。 日常吐槽 經(jīng)過不斷的調(diào)整和測(cè)試,關(guān)于 react 的 webpack 配置終于新鮮出爐。本次的重點(diǎn)主要集中在開發(fā)環(huán)境上,生產(chǎn)環(huán)境則是使用 webpack 的 production 默認(rèn)模式。 本次配置主要有: eslint+prettier; optimizati...
摘要:前端日?qǐng)?bào)精選浮點(diǎn)數(shù)精度之謎前端面試必備基本排序算法從賀老微博引出的遍歷器加速那些奧秘進(jìn)階之深入理解數(shù)據(jù)雙向綁定全棧天中文深入理解筆記用模塊封裝代碼前端架構(gòu)經(jīng)驗(yàn)分享周二放送自制知乎專欄譯在大型應(yīng)用中使用的五個(gè)技巧掘金開發(fā)指南眾成 2017-08-02 前端日?qǐng)?bào) 精選 JavaScript 浮點(diǎn)數(shù)精度之謎前端面試必備——基本排序算法從賀老微博引出的遍歷器(Iterators)加速那些奧秘J...
摘要:最后,我們?cè)诳刂婆_(tái)中打印這個(gè)新數(shù)組。也可以借助簡(jiǎn)單的將其跑在瀏覽器上,之后可在控制臺(tái)中看到同樣的運(yùn)行結(jié)果。使用配置文件雖然會(huì)更占位置,但與此同時(shí)增加了可讀性,因?yàn)樗怯蓪懗傻摹@纾?guī)定后綴的文件要先通過檢查,再通過把語(yǔ)法轉(zhuǎn)換為語(yǔ)法。 譯者:小 boy (滬江前端開發(fā)工程師) 本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處。 原文地址:https://www.smashingmag...
摘要:系列文章核心概念淺嘗本文常言道,實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。遵循傳統(tǒng),第一個(gè)例子必須是。官方提供這個(gè)中間件來支持基于的查詢,所以,這里選用作為服務(wù)器。首先是,這里對(duì)做了一點(diǎn)小修改,給幾個(gè)字段添加了不能為空的設(shè)計(jì)。 系列文章: GraphQL 核心概念 graphql-js 淺嘗(本文) 常言道,實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。 上一篇文章講了 GraphQL 的核心概念,所提到的一些例...
摘要:同樣的你也可以測(cè)試第四次執(zhí)行的時(shí)候就會(huì)是了,需要知道的是,只有在全局檢索時(shí)才會(huì)生效,否則的話只會(huì)返回哦方法二使用正則表達(dá)式模式對(duì)字符串執(zhí)行搜索,并將更新全局對(duì)象的屬性以反映匹配結(jié)果。 之前寫正則都是各種上網(wǎng)搜索,還是沒有系統(tǒng)的學(xué)習(xí)過正則表達(dá)式的用法,今天稍稍研究了一下下,感覺還是收獲頗豐的,分享給各位,希望對(duì)于你們有所幫助~~ 修飾符 g --全局匹配 i --不區(qū)分大小寫,默認(rèn)...
閱讀 2836·2021-11-19 09:40
閱讀 3695·2021-11-15 18:10
閱讀 3281·2021-11-11 16:55
閱讀 1231·2021-09-28 09:36
閱讀 1647·2021-09-22 15:52
閱讀 3367·2019-08-30 14:06
閱讀 1160·2019-08-29 13:29
閱讀 2307·2019-08-26 17:04