模塊打包器:開(kāi)發(fā)一個(gè)項(xiàng)目,業(yè)務(wù)邏輯會(huì)很多,開(kāi)發(fā)會(huì)按照功能邏輯拆分成一個(gè)個(gè)的模塊奔誓,這樣開(kāi)發(fā)的時(shí)候更加有條理,維護(hù)起來(lái)也會(huì)更加方便。
但這樣就會(huì)涉及到一個(gè)問(wèn)題厨喂,模塊之間會(huì)有復(fù)雜的依賴關(guān)系和措,在處理這些模塊依賴的時(shí)候,后端的開(kāi)發(fā)有著得天獨(dú)厚的條件蜕煌,模塊化是天生支持的派阱。
模塊打包器會(huì)先分析項(xiàng)目依賴,然后按照復(fù)雜的規(guī)則把它們打包在一起斜纪,當(dāng)然這些規(guī)則是隱藏起來(lái)的贫母,不需要知道是怎么打包的,只需要知道這個(gè)打包器專門(mén)會(huì)把你的模塊依賴的代碼都打包到一起盒刚,輸出一個(gè)新文件腺劣,你會(huì)得到新的js文件。
它不僅能幫你打包js文件因块,還有其他資源文件橘原,都會(huì)視為模塊,都會(huì)打包涡上。
但如果只是打包趾断,那就太小瞧webpack了,它有著很強(qiáng)大的生態(tài)吓懈,有各種loader,來(lái)幫你處理文件的內(nèi)容歼冰,比如編譯語(yǔ)法,處理路徑耻警,還有插件輔助你開(kāi)發(fā)和項(xiàng)目構(gòu)建隔嫡,從而加快你的開(kāi)發(fā)效率,如果你在開(kāi)發(fā)一個(gè)大型單頁(yè)應(yīng)用甘穿,它的代碼分割功能對(duì)頁(yè)面的性能是意義非凡的腮恩,從項(xiàng)目的起始到項(xiàng)目上線,它參與了整個(gè)的項(xiàng)目周期温兼。
更新部分:
NamedModulesPlugin =》 optimization.namedModules秸滴;CommonsChunkPlugin =》 optimization.splitChunks, optimization.runtimeChunk
名詞:
- chunk:代碼塊
- bundle : 被打包過(guò)的
- module : 模塊
核心概念:
-
entry:
- 代碼的入口
- 打包的入口(依次查找依賴)
- 單個(gè)或多個(gè)
module.exports = {
entry : 'index.js'
====
entry:['index.js','vendors.js'] //創(chuàng)建多個(gè)入口
====
// 推薦,擴(kuò)展性好,直接新增即可
entry : {
index : 'index.js'募判,// index對(duì)應(yīng)的是index.js
vendor : 'verdor.js'
}
}
-
output:
- 打包生成的文件(bundle)
- 一個(gè)或多個(gè)
- 自定義規(guī)則
- 配個(gè)cdn
module.exports = {
entry : {
index : 'index.js'荡含,// index對(duì)應(yīng)的是index.js
vendor : 'verdor.js'
}
output : {
filename : 'index.min.js'
==
//多個(gè):自定義規(guī)則
// [name]對(duì)應(yīng)入口文件,hash是打包過(guò)程后的版本號(hào)
filename:'[name].min.[hash:5].js'
}
}
-
loaders:
loader 讓 webpack 能夠去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)届垫。
loader 可以將所有類型的文件轉(zhuǎn)換為 webpack 能夠處理的有效模塊释液,然后你就可以利用 webpack 的打包能力,對(duì)它們進(jìn)行處理装处。
主要負(fù)責(zé):
- 處理文件
- 轉(zhuǎn)化為模塊
屬性:
- test:用于標(biāo)識(shí)出應(yīng)該被對(duì)應(yīng)的 loader 進(jìn)行轉(zhuǎn)換的某個(gè)或某些文件误债。
- use:表示進(jìn)行轉(zhuǎn)換時(shí),應(yīng)該使用哪個(gè) loader。
const config = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
//以txt結(jié)尾的文件寝蹈,用raw-loade轉(zhuǎn)換
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
-
plugins:
- 參與打包整個(gè)過(guò)程
- 打包優(yōu)化和壓縮
- 配置編譯時(shí)的變量
- 及其靈活
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
webpack使用
- webpack-cli
安裝:npm install webpack-cli -g- 交互式的初始化一個(gè)項(xiàng)目
- 遷移項(xiàng)目李命!v1-v2
打包
一、打包JS
命令
webpack entry<entry> output
===
webpack --config wenpack.conf.js
示例1:es6模塊化打包
//app.js,入口文件
import sum from './sum'
console.log('sum(23,24) = ', sum(23,24))
終端輸入命令:
webpack app.js bundle.js
這時(shí)候就打包出了一個(gè)bundle.js箫老,在index中引入這個(gè)js文件即可封字。
示例2:common.js打包
//minus.js
module.exports = function(a,b){
return a - b
}
//app.js
let minus = require('./minus')
console.log('minus(24,17) = ', minus(24,17))
執(zhí)行命令:webpack app.js bundle.js,可以看到成功的被打包了槽惫。
示例2:AMD打包
//muti.js
define(function(require, factory) {
'use strict';
return function(a,b){
return a*b
}
});
//app.js
require(['muti'],function(muti){
console.log('muti(2,3)=',muti(2,3))
})
打包成功V芏!!界斜!這時(shí)候發(fā)現(xiàn)打包出了兩個(gè)文件:0.bundle.js和bundle.js
- webpack.config.js :配置文件,使用commonJS語(yǔ)法
//webpack.config.js
module.exports = {
entry : {
app : './app.js'
},
output:{
filename : '[name].[hash:5]'
}
}
這時(shí)候打包了兩個(gè)文件:
二合冀、編譯 ES6
babel
npm install babel-loader@8.0.0-beta.0 @babel/core
新建index.html,webpack.config.js
module:{
rules : [
{
test:/\.js$/,
use:'babel-loader',
// 排除在規(guī)則之外的不編譯
exclude:'/node_module/'
}
]
}
- babel presets
參數(shù):- targets : 目標(biāo)各薇,告訴babel,當(dāng)編譯的時(shí)候根據(jù)指定的目標(biāo)選擇哪些語(yǔ)法進(jìn)行編譯,哪些不編譯
- targets.browsers :瀏覽器環(huán)境
- targets.vrowers : 'last 2 versions'
- target.browers :">1%" : 大于市場(chǎng)份額1%的瀏覽器
- browerslist : 指定支持的瀏覽器
- can I use
npm install @babel/preset-env -save-dev
為babel指定presets編譯es5,es5....
rules : [
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
// 給loader指定presets
presets:['@babel/preset-env',{
targets:{
browsers:['>1%','last 2 versions']
}
}]
}
},
exclude:'/node_module/'
}
打包君躺,看結(jié)果:
可以看出峭判,es6語(yǔ)法被轉(zhuǎn)成了es5。
babel的兩個(gè)插件: babel plyfill , babel runtime transform
babel-preset-es2015 是一個(gè)babel的插件棕叫,用于將部分ES6 語(yǔ)法轉(zhuǎn)換為ES5 語(yǔ)法林螃。但是babel-preset并不會(huì)轉(zhuǎn)換promise、generator等函數(shù)俺泣,我們還要引入babel-polify庫(kù)疗认。
項(xiàng)目中現(xiàn)在一般直接使用babel-preset-env,她整合了babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017伏钠,而且可以配置需要支持的瀏覽器/運(yùn)行環(huán)境横漏,僅轉(zhuǎn)化需要支持的語(yǔ)法,使文件更輕量
插件作用:解決實(shí)現(xiàn)低版本函數(shù)和方法不支持熟掂。
- Generator
- set
- Map
- Arry.from
- Array.protptype.includes
-
babel plyfill :
全局墊片缎浇,為應(yīng)用準(zhǔn)備。打包后可以在文件中看到:它會(huì)判斷如果瀏覽器不支持這個(gè)方法(如includes),就會(huì)在生成的打包文件加上這個(gè)方法的實(shí)現(xiàn)赴肚。npm install babel-polyfill --save //--save,真實(shí)項(xiàng)目中的依賴 //使用 import "babel-polyfill" ```
-
babel runtime transform:
局部墊片素跺,不會(huì)污染全局,為框架準(zhǔn)備誉券。npm install @babel/runtime --save npm install @babel/plgin-transform-runtime --save-dev
2指厌、使用 :
- 安裝這兩個(gè)插件
- 入口文件引入:
import "babel-polyfill/runtime
,即可使用新的語(yǔ)法。 - 根目錄新建 : .babelrc,把babel都寫(xiě)在這個(gè)文件横朋,之前寫(xiě)在webpack.config.js中的options拿過(guò)來(lái)仑乌,如下:
{
"presets" : [
["@babel/preset-env",{
"targets":{
"browsers":["last 2 versions"]
}
}]
],
"plugins": ["@babel/transform-runtime"]
}
三、編譯 Typescript:js
Typescript:js的超集,來(lái)自微軟晰甚,需要使用對(duì)應(yīng)的loader衙传。
-
typescript-loader
- 安裝:
npm i typescript ts-loader --save-dev //官方 npm i typescript awesome-typescript-loader --save-dev //第三方開(kāi)發(fā)
- 配置
- tsconfig.json,建在根目錄
- 配置選項(xiàng):詳見(jiàn)官網(wǎng)
- 常用選項(xiàng):
- compilerOptions
- include
- exclude
- webpack.config.js
使用:
- tsconfig.json,建在根目錄
下載 :
npm install webpack typescript ts-loader awesome-typescript-loader --save-dev
新建tsconfig.json厕九,在根目錄
{
"compilerOptions": {
"module": "commonjs",
// 指定編譯后的文件的運(yùn)行環(huán)境
"target":"es5",
"allowJs": true
},
// 路徑
"include": [
"./src/*"
],
// 指定不編譯的部分
"exclude": [
"./node_modules"
]
}
//webpack.config.js
module.exports = {
entry : {
'app' : './src/app.ts'
},
output : {
filename:'[name].bundel.js'
},
module : {
rules : [
{
test : /\.tsx?$/,
user : {
loader : 'ts-loader'
}
}
]
}
}
// app.ts
const NUM = 45
interface Cat {
name : String,
gender : String
}
function touchCat (cat:Cat){
console.log('miao~',cat.name)
}
touchCat({
name : 'tom',
gender : 'male'
})
打包公共代碼 : SplitChunksPlugin
現(xiàn)在都是模塊化開(kāi)發(fā)蓖捶,這樣就會(huì)有模塊互相依賴的情況,公共模塊就是公共代碼扁远。
SplitChunksPlugin的登場(chǎng)就是為了抹平之前CommonsChunkPlugin的痛的俊鱼,它能夠抽出懶加載模塊之間的公共模塊,并且不會(huì)抽到父級(jí)畅买,而是會(huì)與首次用到的懶加載模塊并行加載并闲,這樣我們就可以放心的使用懶加載模塊了.
SplitChunksPlugin的好,好在解決了入口文件過(guò)大的問(wèn)題還能有效自動(dòng)化的解決懶加載模塊之間的代碼重復(fù)問(wèn)題
目的:
- 減少代碼冗余
- 提高加載速度
插件: - SplitChunksPlugin(之前的版本是CommonsChunkPlugin)
-
參數(shù) :
- chunks: 表示顯示塊的范圍谷羞,有三個(gè)可選值:initial(初始?jí)K)帝火、async(按需加載塊)、 all(全部塊)湃缎,默認(rèn)為all;
- minSize: 表示在壓縮前的最小模塊大小犀填,默認(rèn)為0;
- minChunks: 表示被引用次數(shù)嗓违,默認(rèn)為1九巡;
- maxAsyncRequests: 最大的按需(異步)加載次數(shù),默認(rèn)為1蹂季;
- maxInitialRequests: 最大的初始化加載次數(shù)冕广,默認(rèn)為1;
- name: 拆分出來(lái)塊的名字(Chunk Names)乏盐,默認(rèn)由塊名和hash值自動(dòng)生成佳窑;
- cacheGroups: 緩存組。
-
使用場(chǎng)景:
- 單頁(yè)應(yīng)用
- 單頁(yè)應(yīng)用 + 第三方依賴
- 多頁(yè)應(yīng)用 + 第三方依賴 + webpack生成代碼
-
使用步驟:
- 安裝局部webpack :
npm install webpack --save-dev
,因?yàn)檫@個(gè)插件是webpack自帶的父能,所以需要安裝在本地神凑。(全局webpack理解為是工具,局部就是本文件夾處理依賴的) - 新建subpageA.js(a),subpageB.js(b),module.js(c),a何吝、b都引用c,這時(shí)c就是公共模塊溉委。在配置文件里寫(xiě) :
- 安裝局部webpack :
-
!!!!CommonsChunkPlugin已被棄用
// 代碼合并
new webpack.optimize.CommonsChunkPlugin({
// 生成的文件
name : 'common',
minChunks : 2
})
!!!現(xiàn)在用SplitChunksPlugin
new webpack.optimize.SplitChunksPlugin({
name : 'common',
minChunks: 2,
})
!!!或者寫(xiě)成以下(和plugins同級(jí))
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "common",
chunks: "initial",
minChunks: 2
}
}
}
}
這時(shí)候打包發(fā)現(xiàn)并沒(méi)有把公共部分提取出來(lái)。
附詳細(xì)配置
new webpack.optimize.SplitChunksPlugin({
chunks: "initial", // 必須三選一: "initial" | "all"(默認(rèn)就是all) | "async"
minSize: 0, // 最小尺寸爱榕,默認(rèn)0
minChunks: 1, // 最小 chunk 瓣喊,默認(rèn)1
maxAsyncRequests: 1, // 最大異步請(qǐng)求數(shù), 默認(rèn)1
maxInitialRequests: 1, // 最大初始化請(qǐng)求書(shū)黔酥,默認(rèn)1
name: function () {
}, // 名稱藻三,此選項(xiàng)可接收 function
cacheGroups: { // 這里開(kāi)始設(shè)置緩存的 chunks
priority: 0, // 緩存組優(yōu)先級(jí)
vendor: { // key 為entry中定義的 入口名稱
chunks: "initial", // 必須三選一: "initial" | "all" | "async"(默認(rèn)就是異步)
name: "vendor", // 要緩存的 分隔出來(lái)的 chunk 名稱
minSize: 0,
minChunks: 1,
enforce: true,
maxAsyncRequests: 1, // 最大異步請(qǐng)求數(shù)洪橘, 默認(rèn)1
maxInitialRequests: 1, // 最大初始化請(qǐng)求書(shū),默認(rèn)1
reuseExistingChunk: true // 可設(shè)置是否重用該chunk(查看源碼沒(méi)有發(fā)現(xiàn)默認(rèn)值)
}
}
})
代碼分割和懶加載
在前端的優(yōu)化過(guò)程中棵帽,一個(gè)很常見(jiàn)的手段就是對(duì)代碼切分熄求,讓用戶在瀏覽的時(shí)候加載更少的代碼,通過(guò)代碼分割和懶加載逗概,可以節(jié)省加載時(shí)間弟晚,如果用戶只瀏覽一個(gè)頁(yè)面的時(shí)候卻下載了所有的代碼,那么就會(huì)對(duì)用戶的帶寬造成浪費(fèi)逾苫,影響瀏覽時(shí)間卿城。
代碼分割和懶加載是一回事,wenpack會(huì)自動(dòng)把代碼分割之后再把需要加載的代碼加載過(guò)來(lái)铅搓。
這兩個(gè)雖然是webpack的功能瑟押,但是并不在webpack的配置中。
實(shí)現(xiàn)方式 :
- webpack methods
- require.ensure:動(dòng)態(tài)加載模塊,對(duì)promise有依賴
參數(shù) :- [] : dependencies
- callback : 執(zhí)行
- errorCallback: 可以省略
- chunkName
require.ensure(['lodash'],function () {
// 此處require是異步狸吞,不是commonjs
var _ = require('lodash')
},'vendor')
- require.include
當(dāng)兩個(gè)模塊都依賴了第三方模塊的時(shí)候勉耀,
可以提前把第三方模塊放到父模塊里,這樣動(dòng)態(tài)加載兩個(gè)模塊的時(shí)候蹋偏,由于父模塊已經(jīng)有了這個(gè)第三方模塊,所以不會(huì)重復(fù)加載至壤。比如a,b依賴c,將c在引用ab的父級(jí)模塊中用此方法先引入威始,就可以把c模塊提取在這個(gè)父級(jí)模塊中。
- ES 2015 Loader spec
- import():返回的是promise像街,傳入動(dòng)態(tài)加載的模塊名后黎棠,就可以像使用promise一樣去使用 :import().then()
import與require.ensure最大的區(qū)別:他在引入的時(shí)候會(huì)直接執(zhí)行,而不需要require了
import('./subPageA').then(function(){
})
業(yè)務(wù)場(chǎng)景
一镰绎、代碼分割
- 分離業(yè)務(wù)代碼和第三方依賴
- 分離業(yè)務(wù)代碼業(yè)務(wù)公共代碼和第三方依賴
- 分離首次加載和訪問(wèn)后加載的代碼(提高首屏加載速度)
代碼演示:
- 單entry:把第三方依賴單獨(dú)出來(lái)
import './subpageA'
import './subpageB'
// vendor指定chunk名稱
// 外層ensure只是將lodash加載到了頁(yè)面中脓斩,并不執(zhí)行
// 在回調(diào)中require才是真正的執(zhí)行
// ensure中可以省略不寫(xiě)參數(shù)ensure(['']
// 打包結(jié)果 : lodash被提取到了vendor,實(shí)現(xiàn)了第三方依賴了業(yè)務(wù)代碼的分離
require.ensure(['lodash'],function () {
// 此處require是異步,不是commonjs
var _ = require('lodash')
_.join(['1','2'],'3')
},'vendor')
export default 'pageA'
- 如果兩個(gè)文件(a畴栖,b)依賴同一個(gè)文件(c)随静,希望把c單獨(dú)出來(lái),如果是同步的引用方式吗讶,是無(wú)法實(shí)現(xiàn)的燎猛,在入口文件引用a,b的時(shí)候照皆,就可以動(dòng)態(tài)引入:
//import './subpageA'
//import './subpageB'
require.ensure(['./subpageA'],function (params) {
let subpageA = require('./subpageA')
},'subpageA')
require.ensure(['./subpageB'],function (params) {
let subpageA = require('./subpageB')
},'subpageB')
require.ensure(['lodash'],function () {
// 此處require是異步重绷,不是commonjs
var _ = require('lodash')
_.join(['1','2'],'3')
},'vendor')
export default 'pageA'
這時(shí)候發(fā)現(xiàn),c沒(méi)有被單獨(dú)打包出來(lái).
把c模塊先引進(jìn),這樣就把c模塊提取到了引用ab模塊的父級(jí)模塊上膜毁。
概括一下 : a,b依賴c,將c在引用ab的父級(jí)模塊中用此方法先引入昭卓,就可以把c模塊提取在這個(gè)父級(jí)模塊中愤钾。
require.include('./moduleA')
.......(省略代碼和上面的代碼一致)
前端面試之webpack面試常見(jiàn)問(wèn)題
概念問(wèn)題一:什么是webpack和grunt和gulp有什么不同
Webpack是一個(gè)模塊打包器,他可以遞歸的打包項(xiàng)目中的所有模塊候醒,最終生成幾個(gè)打包后的文件能颁。他和其他的工具最大的不同在于他支持code-splitting、模塊化(AMD火焰,ESM劲装,CommonJs)、全局分析昌简。
問(wèn)題二:什么是bundle,什么是chunk占业,什么是module?
答案:bundle是由webpack打包出來(lái)的文件,chunk是指webpack在進(jìn)行模塊的依賴分析的時(shí)候纯赎,代碼分割出來(lái)的代碼塊谦疾。module是開(kāi)發(fā)中的單個(gè)模塊。
問(wèn)題三:什么是Loader?什么是Plugin?
答案:
1)Loaders是用來(lái)告訴webpack如何轉(zhuǎn)化處理某一類型的文件犬金,并且引入到打包出的文件中
2)Plugin是用來(lái)自定義webpack打包過(guò)程的方式念恍,一個(gè)插件是含有apply方法的一個(gè)對(duì)象,通過(guò)這個(gè)方法可以參與到整個(gè)webpack打包的各個(gè)流程(生命周期)晚顷。
問(wèn)題:如何可以自動(dòng)生成webpack配置峰伙?
答案: webpack-cli /vue-cli /etc ...腳手架工具
問(wèn)題一:webpack-dev-server和http服務(wù)器如nginx有什么區(qū)別?
答案:webpack-dev-server使用內(nèi)存來(lái)存儲(chǔ)webpack開(kāi)發(fā)環(huán)境下的打包文件,并且可以使用模塊熱更新该默,他比傳統(tǒng)的http服務(wù)對(duì)開(kāi)發(fā)更加簡(jiǎn)單高效瞳氓。
問(wèn)題二:什么 是模塊熱更新?
答案:模塊熱更新是webpack的一個(gè)功能栓袖,他可以使得代碼修改過(guò)后不用刷新瀏覽器就可以更新匣摘,是高級(jí)版的自動(dòng)刷新瀏覽器。
化問(wèn)題一:什么是長(zhǎng)緩存裹刮?在webpack中如何做到長(zhǎng)緩存優(yōu)化音榜?
答案:瀏覽器在用戶訪問(wèn)頁(yè)面的時(shí)候,為了加快加載速度捧弃,會(huì)對(duì)用戶訪問(wèn)的靜態(tài)資源進(jìn)行存儲(chǔ)赠叼,但是每一次代碼升級(jí)或是更新,都需要瀏覽器去下載新的代碼塔橡,最方便和簡(jiǎn)單的更新方式就是引入新的文件名稱梅割。在webpack中可以在output縱輸出的文件指定chunkhash,并且分離經(jīng)常更新的代碼和框架代碼。通過(guò)NameModulesPlugin或是HashedModuleIdsPlugin使再次打包文件名不變葛家。
化問(wèn)題二:什么是Tree-shaking?CSS可以Tree-shaking嗎
答案:Tree-shaking是指在打包中去除那些引入了户辞,但是在代碼中沒(méi)有被用到的那些死代碼。在webpack中Tree-shaking是通過(guò)uglifySPlugin來(lái)Tree-shaking
JS癞谒。Css需要使用Purify-CSS底燎。