摘要:系列文章系列第一篇基礎(chǔ)雜記系列第二篇插件機(jī)制雜記系列第三篇流程雜記前言公司的前端項(xiàng)目基本都是用來(lái)做工程化的,而雖然只是一個(gè)工具,但內(nèi)部涉及到非常多的知識(shí),之前一直靠來(lái)解決問(wèn)題,之知其然不知其所以然,希望這次能整理一下相關(guān)的知識(shí)點(diǎn)。
系列文章
Webpack系列-第一篇基礎(chǔ)雜記
Webpack系列-第二篇插件機(jī)制雜記
Webpack系列-第三篇流程雜記
公司的前端項(xiàng)目基本都是用Webpack來(lái)做工程化的,而Webpack雖然只是一個(gè)工具,但內(nèi)部涉及到非常多的知識(shí),之前一直靠CV來(lái)解決問(wèn)題,之知其然不知其所以然,希望這次能整理一下相關(guān)的知識(shí)點(diǎn)。
簡(jiǎn)介這是webpack官方的首頁(yè)圖
本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴(lài)關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。
那么打個(gè)比方就是我們搭建一個(gè)項(xiàng)目好比搭建一個(gè)房子,我們把所需要的材料(js文件、圖片等)交給webpack,最后webpack會(huì)幫我們做好一切,并把房子(即bundle)輸出。
webpack中有幾個(gè)概念需要記住
entry(入口)入口起點(diǎn)(entry point)即是webpack通過(guò)該起點(diǎn)找到本次項(xiàng)目所直接或間接依賴(lài)的資源(模塊、庫(kù)等),并對(duì)其進(jìn)行處理,最后輸出到bundle中。入口文件由用戶(hù)自定義,可以是一個(gè)或者多個(gè),每一個(gè)entry最后對(duì)應(yīng)一個(gè)bundle。
output(出口)通過(guò)配置output屬性可以告訴webpack將bundle命名并輸出到對(duì)應(yīng)的位置。
loaderwebpack核心,webpack本身只能識(shí)別js文件,對(duì)于非js文件,即需要loader轉(zhuǎn)換為js文件。換句話(huà)說(shuō),,Loader就是資源轉(zhuǎn)換器。由于在webpack里,所有的資源都是模塊,不同資源都最終轉(zhuǎn)化成js去處理。針對(duì)不同形式的資源采用不同的Loader去編譯,這就是Loader的意義。
插件(plugin)webpack核心,loader處理非js文件,那么插件可以有更廣泛的用途。整個(gè)webpack其實(shí)就是各類(lèi)的插件形成的,插件的范圍包括,從打包優(yōu)化和壓縮,一直到重新定義環(huán)境中的變量。插件接口功能極其強(qiáng)大,可以用來(lái)處理各種各樣的任務(wù)。
Chunk被entry所依賴(lài)的額外的代碼塊,同樣可以包含一個(gè)或者多個(gè)文件。chunk也就是一個(gè)個(gè)的js文件,在異步加載中用處很大。chunk實(shí)際上就是webpack打包后的產(chǎn)物,如果你不想最后生成一個(gè)包含所有的bundle,那么可以生成一個(gè)個(gè)chunk,并通過(guò)按需加載引入。同時(shí)它還能通過(guò)插件提取公共依賴(lài)生成公共chunk,避免多個(gè)bundle中有多個(gè)相同的依賴(lài)代碼。
配置webpack的相關(guān)配置語(yǔ)法官方文檔比較詳細(xì),這里就不贅述了。
指南
配置
url-loader 可以在文件大?。▎挝?byte)低于指定的限制,將文件轉(zhuǎn)換為DataURL,這在實(shí)際開(kāi)發(fā)中非常有效,能夠減少請(qǐng)求數(shù),在vue-cli和create-react-app中也都能看到對(duì)這個(gè)loader的使用。
// "url" loader works just like "file" loader but it also embeds // assets smaller than specified size as data URLs to avoid requests. { test: [/.bmp$/, /.gif$/, /.jpe?g$/, /.png$/], loader: require.resolve("url-loader"), options: { limit: 10000, name: "static/media/[name].[hash:8].[ext]", }, },
image-webpack-loader 這是一個(gè)可以通過(guò)設(shè)置質(zhì)量參數(shù)來(lái)壓縮圖片的插件,但個(gè)人覺(jué)得在實(shí)際開(kāi)發(fā)中并不會(huì)經(jīng)常使用,圖片一般是UI提供,一般來(lái)說(shuō),他們是不會(huì)同意圖片的質(zhì)量有問(wèn)題。
資源私有化以這種方式加載資源,你可以以更直觀的方式將模塊和資源組合在一起。無(wú)需依賴(lài)于含有全部資源的 /assets 目錄,而是將資源與代碼組合在一起。例如,類(lèi)似這樣的結(jié)構(gòu)會(huì)非常有用
- |- /assets + |– /components + | |– /my-component + | | |– index.jsx + | | |– index.css + | | |– icon.svg + | | |– img.png
當(dāng)然,這種選擇見(jiàn)仁見(jiàn)智
Tree-Shaking前端中的tree-shaking就是將一些無(wú)關(guān)的代碼刪掉不打包。在Webpack項(xiàng)目中,我們通常會(huì)引用很多文件,但實(shí)際上我們只引用了其中的某些模塊,但卻需要引入整個(gè)文件進(jìn)行打包,會(huì)導(dǎo)致我們的打包結(jié)果變得很大,通過(guò)tree-shaking將沒(méi)有使用的模塊搖掉,這樣來(lái)達(dá)到刪除無(wú)用代碼的目的。
Tree-Shaking的原理可以參考這篇文章
歸納起來(lái)就是
1.ES6的模塊引入是靜態(tài)分析的,故而可以在編譯時(shí)正確判斷到底加載了什么代碼。
2.分析程序流,判斷哪些變量未被使用、引用,進(jìn)而刪除此代碼
Tree-Shaking不起作用,代碼沒(méi)有被刪?
歸納起來(lái)就是
因?yàn)锽abel的轉(zhuǎn)譯,使得引用包的代碼有了副作用,而副作用會(huì)導(dǎo)致Tree-Shaking失效。
Webpack 4 默認(rèn)啟用了 Tree Shaking。對(duì)副作用進(jìn)行了消除,以下是我在4.19.1的實(shí)驗(yàn)
index.js import { cube } from "./math.js" console.log(cube(5))
math.js
// 不打包square export class square { constructor() { console.log("square") } } export class cube { constructor(x) { return x * x * x } }
// babel編譯后 同不打包 "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cube = cube; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var square = exports.square = function square() { _classCallCheck(this, square); console.log("square"); }; function cube(x) { console.log("cube"); return x * x * x; }
// 不打包 export function square(x) { console.log("square") return x.a } export function cube (x) { return x * x * x }
// wow 被打包 export function square() { console.log("square") return x.a } square({a: 1}) export function cube () { return x * x * x }sourcemap
簡(jiǎn)單說(shuō),Source map就是一個(gè)信息文件,里面儲(chǔ)存著位置信息。也就是說(shuō),轉(zhuǎn)換后的代碼的每一個(gè)位置,所對(duì)應(yīng)的轉(zhuǎn)換前的位置。
有了它,出錯(cuò)的時(shí)候,除錯(cuò)工具將直接顯示原始代碼,而不是轉(zhuǎn)換后的代碼。這無(wú)疑給開(kāi)發(fā)者帶來(lái)了很大方便。
webpack中的devtool配置項(xiàng)可以設(shè)置sourcemap,可以參考官方文檔然而,devtool的許多選項(xiàng)都講的不是很清楚,這里推薦該文章,講的比較詳細(xì)
要注意,避免在生產(chǎn)中使用 inline- 和 eval-,因?yàn)樗鼈兛梢栽黾?bundle 大小,并降低整體性能。
模塊熱替換熱替換這一塊目前大多數(shù)都是用的webpack-dev-middleware插件配合服務(wù)器使用的,而官方提供的watch模式反而比較少用,當(dāng)然,webpack-dev-middleware的底層監(jiān)聽(tīng)watch mode,至于為什么不直接使用watch模式,則是webpack-dev-middleware快速編譯,走內(nèi)存;只依賴(lài)webpack的watch mode來(lái)監(jiān)聽(tīng)文件變更,自動(dòng)打包,每次變更,都將新文件打包到本地,就會(huì)很慢。
DefinePluginwebpack.DefinePlugin 定義環(huán)境變量process.env,這在實(shí)際開(kāi)發(fā)中比較常用,參考create-react-app中的代碼如下:
// Makes some environment variables available to the JS code, for example: // if (process.env.NODE_ENV === "development") { ... }. See `./env.js`. new webpack.DefinePlugin(env.stringified),
不過(guò),要注意不能在config中使用,因?yàn)?/p>
process.env.NODE_ENV === "production" ? "[name].[hash].bundle.js" : "[name].bundle.js"
NODE_ENV is set in the compiled code, not in the webpack.config.js file. You should not use enviroment variables in your configuration. Pass options via --env.option abc and export a function from the webpack.config.js.
大致意思就是NODE_ENV是設(shè)置在compiled里面,而不是config文件里。
ExtractTextWebpackPluginExtractTextWebpackPlugin,將css抽取成多帶帶文件,可以通過(guò)這種方式配合后端對(duì)CSS文件進(jìn)行緩存。
SplitChunksPluginwebpack4的代碼分割插件。
webpack4中支持了零配置的特性,同時(shí)對(duì)塊打包也做了優(yōu)化,CommonsChunkPlugin已經(jīng)被移除了,現(xiàn)在是使用optimization.splitChunks代替。
SplitChunksPlugin的配置有幾個(gè)需要比較關(guān)注一下
chunks: async | initial | all
async: 默認(rèn)值, 將按需引用的模塊打包
initial: 分開(kāi)優(yōu)化打包異步和非異步模塊
all: all會(huì)把異步和非異步同時(shí)進(jìn)行優(yōu)化打包。也就是說(shuō)moduleA在indexA中異步引入,indexB中同步引入,initial下moduleA會(huì)出現(xiàn)在兩個(gè)打包塊中,而all只會(huì)出現(xiàn)一個(gè)。
cacheGroups
使用cacheGroups可以自定義配置打包塊。
更多詳細(xì)內(nèi)容參考該文章
動(dòng)態(tài)引入則是利用動(dòng)態(tài)引入的文件打包成另一個(gè)包,并懶加載它。其與SplitChunksPlugin的cacheGroups區(qū)別:
Bundle splitting:實(shí)際上就是創(chuàng)建多個(gè)更小的文件,并行加載,以獲得更好的緩存效果;主要的作用就是使瀏覽器并行下載,提高下載速度。并且運(yùn)用瀏覽器緩存,只有代碼被修改,文件名中的哈希值改變了才會(huì)去再次加載。
Code splitting:只加載用戶(hù)最需要的部分,其余的代碼都遵從懶加載的策略;主要的作用就是加快頁(yè)面加載速度,不加載不必要加載的東西。
參考代碼:
+ import _ from "lodash"; + + function component() { var element = document.createElement("div"); + var button = document.createElement("button"); + var br = document.createElement("br"); + button.innerHTML = "Click me and look at the console!"; element.innerHTML = _.join(["Hello", "webpack"], " "); + element.appendChild(br); + element.appendChild(button); + + // Note that because a network request is involved, some indication + // of loading would need to be shown in a production-level site/app. + button.onclick = e => import(/* webpackChunkName: "print" */ "./print").then(module => { + var print = module.default; + + print(); + }); return element; } + document.body.appendChild(component());
注意當(dāng)調(diào)用 ES6 模塊的 import() 方法(引入模塊)時(shí),必須指向模塊的 .default 值,因?yàn)樗攀?promise 被處理后返回的實(shí)際的 module 對(duì)象。緩存runtimeChunk
因?yàn)閣ebpack會(huì)把運(yùn)行時(shí)代碼放到最后的一個(gè)bundle中, 所以即使我們修改了其他文件的代碼,最后的一個(gè)bundle的hash也會(huì)改變,runtimeChunk是把運(yùn)行時(shí)代碼多帶帶提取出來(lái)的配置。這樣就有利于我們和后端配合緩存文件。
配置項(xiàng)
single: 所有入口共享一個(gè)生成的runtimeChunk
true/mutiple: 每個(gè)入口生成一個(gè)多帶帶的runtimeChunk
模塊標(biāo)識(shí)符有時(shí)候我們只是添加了個(gè)文件print.js, 并在index引入
import Print from "./print"
打包的時(shí)候,期望只有runtime和main兩個(gè)bundle的hash發(fā)生改變,但是通常所有bundle都發(fā)生了變化,因?yàn)槊總€(gè) module.id 會(huì)基于默認(rèn)的解析順序(resolve order)進(jìn)行增量。也就是說(shuō),當(dāng)解析順序發(fā)生變化,ID 也會(huì)隨之改變。
可以使用兩個(gè)插件來(lái)解決這個(gè)問(wèn)題。第一個(gè)插件是 NamedModulesPlugin,將使用模塊的路徑,而不是數(shù)字標(biāo)識(shí)符。雖然此插件有助于在開(kāi)發(fā)過(guò)程中輸出結(jié)果的可讀性,然而執(zhí)行時(shí)間會(huì)長(zhǎng)一些。第二個(gè)選擇是使用 HashedModuleIdsPlugin。
參考文章
ProvidePlugin通過(guò)ProvidePlugin處理全局變量
其他更細(xì)粒度的處理
首先了解一下polyfills, 雖然在webpack中能夠使用es6es7等的API,但并不代表編譯器支持這些API,所以通常我們會(huì)用polyfills來(lái)自定義一個(gè)API。
那么在webpack中,一般是使用babel-polyfill VS babel-runtime VS babel-preset-env等來(lái)支持這些API,而這三種怎么選擇也是一個(gè)問(wèn)題。
在真正進(jìn)入主題之前,我們先看一個(gè)preset-env的配置項(xiàng),同時(shí)也是package.json中的一個(gè)配置項(xiàng)browserslist
{ "browserslist": [ "last 1 version", "> 1%", "maintained node versions", "not dead" ] }
根據(jù)這個(gè)配置,preset-env或者postcss等會(huì)根據(jù)你的參數(shù)支持不同的polyfills,具體的參數(shù)配置參考該文章
另外推薦一個(gè)網(wǎng)站,可以看各種瀏覽器的使用情況。
babel-polyfill 只需要引入一次,但會(huì)重寫(xiě)一些原生的已支持的方法,而且體積很大。
transform-runtime 是利用 plugin 自動(dòng)識(shí)別并替換代碼中的新特性,你不需要再引入,只需要裝好 babel-runtime 和 配好 plugin 就可以了。好處是按需替換,檢測(cè)到你需要哪個(gè),就引入哪個(gè) polyfill,值得注意的是,instance 上新添加的一些方法,babel-plugin-transform-runtime 是沒(méi)有做處理的,比如 數(shù)組的 includes, filter, fill 等
babel-preset-env 根據(jù)當(dāng)前的運(yùn)行環(huán)境,自動(dòng)確定你需要的 plugins 和 polyfills。通過(guò)各個(gè) es標(biāo)準(zhǔn) feature 在不同瀏覽器以及 node 版本的支持情況,再去維護(hù)一個(gè) feature 跟 plugins 之間的映射關(guān)系,最終確定需要的 plugins。
參考文章
后編譯日常我們引用的Npm包都是編譯好的,這樣帶來(lái)的方便的同時(shí)也暴露了一些問(wèn)題。
代碼冗余:一般來(lái)說(shuō),這些 NPM 包也是基于 ES2015+ 開(kāi)發(fā)的,每個(gè)包都需要經(jīng)過(guò) babel 編譯發(fā)布后才能被主應(yīng)用使用,而這個(gè)編譯過(guò)程往往會(huì)附加很多“編譯代碼”;每個(gè)包都會(huì)有一些相同的編譯代碼,這就造成大量代碼的冗余,并且這部分冗余代碼是不能通過(guò) Tree Shaking 等技術(shù)去除掉的。非必要的依賴(lài):考慮到組件庫(kù)的場(chǎng)景,通常我們?yōu)榱朔奖阋还赡X引入了所有組件;但實(shí)際情況下對(duì)于一個(gè)應(yīng)用而言可能只是用到了部分組件,此時(shí)如果全部引入,也會(huì)造成代碼冗余。
所以我們自己的公司組件可以采用后編譯的形式,即發(fā)布的是未經(jīng)編譯的npm包,在項(xiàng)目構(gòu)建時(shí)才編譯,我們公司采用的也是這種做法,因?yàn)槲覀兊陌荚谝粋€(gè)目錄下,所以不用考慮遞歸編譯的問(wèn)題。
更多的詳細(xì)請(qǐng)直接參考該文章
設(shè)置環(huán)境變量這個(gè)比較簡(jiǎn)單,直接看代碼或者官方文檔即可
webpack --env.NODE_ENV=local --env.production --progress其他插件
插件總結(jié)歸類(lèi)
CompressionWebpackPlugin 將文件壓縮 文件大小減小很多 需要后端協(xié)助配置
mini-css-extract-plugin將CSS分離出來(lái)
wbepack.IgnorePlugin忽略匹配的模塊
uglifyjs-webpack-plugin代碼丑化,webpack4的mode(product)自動(dòng)配置
optimize-css-assets-webpack-plugincss壓縮
webpack-md5-hash使你的chunk根據(jù)內(nèi)容生成md5,用這個(gè)md5取代 webpack chunkhash。
dllPlugin提高構(gòu)建速度
總結(jié)Webpack本身并不難于理解,難的是各種各樣的配置和周?chē)鷳B(tài)帶來(lái)的復(fù)雜,然而也是這種復(fù)雜給我們帶來(lái)了極高的便利性,理解這些有助于在搭建項(xiàng)目更好的優(yōu)化。后面會(huì)繼續(xù)寫(xiě)出兩篇總結(jié),分別是webpack的內(nèi)部原理流程和webpack的插件開(kāi)發(fā)原理。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101926.html
摘要:最后執(zhí)行了的回調(diào)函數(shù),觸發(fā)了事件點(diǎn),并回到函數(shù)的回調(diào)函數(shù)觸發(fā)了事件點(diǎn)執(zhí)行對(duì)于當(dāng)前模塊,或許存在著多個(gè)依賴(lài)模塊。 系列文章 Webpack系列-第一篇基礎(chǔ)雜記 Webpack系列-第二篇插件機(jī)制雜記 Webpack系列-第三篇流程雜記 前言 本文章個(gè)人理解, 只是為了理清webpack流程, 沒(méi)有關(guān)注內(nèi)部過(guò)多細(xì)節(jié), 如有錯(cuò)誤, 請(qǐng)輕噴~ 調(diào)試 1.使用以下命令運(yùn)行項(xiàng)目,./scrip...
摘要:系列文章系列第一篇基礎(chǔ)雜記系列第二篇插件機(jī)制雜記系列第三篇流程雜記前言本身并不難,他所完成的各種復(fù)雜炫酷的功能都依賴(lài)于他的插件機(jī)制。的插件機(jī)制依賴(lài)于一個(gè)核心的庫(kù),。是什么是一個(gè)類(lèi)似于的的庫(kù)主要是控制鉤子函數(shù)的發(fā)布與訂閱。 系列文章 Webpack系列-第一篇基礎(chǔ)雜記 Webpack系列-第二篇插件機(jī)制雜記 Webpack系列-第三篇流程雜記 前言 webpack本身并不難,他所完成...
摘要:入口文件打包出口地址在中可以配置我們的地址這里你要有一個(gè)七牛云的賬戶(hù)。特別像是七牛云這樣擁有圖片處理引擎的服務(wù)商,我們還可以通過(guò)來(lái)處理上傳至的圖片。 本項(xiàng)目源碼均可在 這里 找到。 之前公司的官網(wǎng)項(xiàng)目靜態(tài)文件都是放在靜態(tài)服務(wù)器中,這其中的弊端就不贅述了。簡(jiǎn)單說(shuō)一下 CDN 的好處: CDN 可以解決因分布、帶寬、服務(wù)器性能帶來(lái)的訪(fǎng)問(wèn)延遲問(wèn)題,適用于站點(diǎn)加速、點(diǎn)播、直播等場(chǎng)景。使用戶(hù)可就...
摘要:入口模塊返回的賦值給總結(jié)在剖析了整體的流程之后,可以看到相關(guān)的技術(shù)細(xì)節(jié)還是比較清晰的,學(xué)無(wú)止境引用混合使用詳解的語(yǔ)法前端模塊化規(guī)范 前言 CMDAMD簡(jiǎn)介 Commonjs簡(jiǎn)介 Module簡(jiǎn)介 Common和Module的區(qū)別 Module與webpack Module與Babel 一些問(wèn)題 總結(jié) 引用 前言 前端模塊化在近幾年層出不窮,有Node的CommonJs,也有屬于cl...
摘要:此外,其也能夠提供強(qiáng)大的反向代理功能。是由為俄羅斯訪(fǎng)問(wèn)量第二的站點(diǎn)開(kāi)發(fā)的,第一個(gè)公開(kāi)版本發(fā)布于年月日。 keepalived+nginx 實(shí)現(xiàn)高可用雙機(jī)熱備 + 負(fù)載均衡架構(gòu) 1 準(zhǔn)備4個(gè)ubuntu16.04虛擬機(jī)(啟用網(wǎng)卡二并使用橋接模式):A服務(wù)器:192.168.0.103 主B服務(wù)器:192.168.0.104 主(備) 前端工程師學(xué)習(xí) Nginx ...
閱讀 569·2019-08-30 15:55
閱讀 973·2019-08-29 15:35
閱讀 1231·2019-08-29 13:48
閱讀 1944·2019-08-26 13:29
閱讀 2977·2019-08-23 18:26
閱讀 1287·2019-08-23 18:20
閱讀 2863·2019-08-23 16:43
閱讀 2731·2019-08-23 15:58