webpack 打包
模塊化開發(fā)為我們解決了很多問題刮吧,使得代碼組織管理非常的方便捐川,但是又帶來了新的問題友鼻,ES Module 存在環(huán)境兼容問題酗昼,劃分的文件太多廊谓,就會(huì)導(dǎo)致網(wǎng)絡(luò)請(qǐng)求頻繁,不能保證所有資源的模塊化
如果能我們享受模塊化帶來的開發(fā)優(yōu)勢(shì)麻削,又能不必?fù)?dān)心生產(chǎn)環(huán)境的存在這些問題蒸痹,于是就有了 webpack, rollup, Parcel 等工具
webpack 模塊化不等于 js ES modele 模塊,相對(duì)來講是前端的模塊化處理方案呛哟,更加宏觀
- 快速上手
$ yarn init --yes
$ yarn add webpack webpack-cli -D
$ yarn webpack --version
$ yarn webpack // 默認(rèn)打包src/index.js // 最終存放到dist/main.js
- webpack 配置文件
在項(xiàng)目根目錄添加 webpack.config.js
const path = require('path');
module.exports = {
entry: './src/main.js', // 入口文件
output: {
filename: 'bundle.js', // 輸出文件名
path: path.join(__dirname, 'output'), // 輸出文件路徑(絕對(duì)路徑)
},
};
- 工作模式
webpack4 新增了工作模式的用法叠荠,大大簡化了配置的復(fù)雜程度;三種工作模式 mode: production development none
$ webpack --mode none
$ webpack --mode production // 默認(rèn)模式
$ webpack --mode development
或者采用配置的方式
const path = require('path');
module.exports = {
// 這個(gè)屬性有三種取值,分別是 production扫责、development 和 none榛鼎。
// 1. 生產(chǎn)模式下,Webpack 會(huì)自動(dòng)優(yōu)化打包結(jié)果鳖孤;
// 2. 開發(fā)模式下者娱,Webpack 會(huì)自動(dòng)優(yōu)化打包速度,添加一些調(diào)試過程中的輔助淌铐;
// 3. None 模式下肺然,Webpack 就是運(yùn)行最原始的打包,不做任何額外處理腿准;
mode: 'development',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
},
};
-
資源模塊加載
webpack 內(nèi)部的 loader 只能處理 js 文件,其他文件我們需要配置對(duì)應(yīng)的 loader 才可以完成打包拾碌,否則會(huì)報(bào)錯(cuò)吐葱。
const path = require('path');
module.exports = {
mode: 'none',
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.css$/,
// css-loader作用就是將css代碼轉(zhuǎn)化為js模塊
// style-loader作用就是將cssloader轉(zhuǎn)化的結(jié)果追加到頁面
use: ['style-loader', 'css-loader'],
},
],
},
};
- 導(dǎo)入資源模塊
入口文件為 js 文件,根據(jù)代碼的需要?jiǎng)討B(tài)導(dǎo)入其他資源,由 javascript 驅(qū)動(dòng)整個(gè)前端應(yīng)用
const path = require('path');
module.exports = {
mode: 'none',
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
};
// main.js
import './main.css';
-
文件資源加載器
- file-loader
經(jīng)過 file-loader 處理后校翔,將文件資源放到我們打包目錄的根目錄弟跑。返回文件資源的訪問路徑,通過 import 就可以拿到文件資源的路徑防症。webpack 默認(rèn)認(rèn)為文件資源放在網(wǎng)站的根目錄下
會(huì)發(fā)起文件請(qǐng)求
- file-loader
const path = require('path');
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/',
},
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /.png$/,
use: 'file-loader',
},
],
},
};
// main.js
import createHeading from './heading.js';
import './main.css';
import iconURL from './icon.png';
// 經(jīng)過file-loader處理后孟辑,將圖片放到我們打包目錄的根目錄。返回圖片的訪問路徑蔫敲,通過import就可以拿到圖片的路徑饲嗽。webpack默認(rèn)認(rèn)為圖片放在網(wǎng)站的根目錄下
const heading = createHeading();
document.body.append(heading);
const img = new Image();
img.src = iconURL;
document.body.append(img);
-
url-loader
將文件資源轉(zhuǎn)化為 Data Url, 最終返回這個(gè) Data Url奈嘿,不單獨(dú)生成資源文件貌虾,直接嵌入到 bundle.js 中
當(dāng)資源文件過大時(shí),導(dǎo)致 base64 邊長裙犹,打包的 bundle.js 體積過大-
Data URLs
直接表示文件內(nèi)容,使用這種 Url 不會(huì)發(fā)起 Http 請(qǐng)求data:[<mediatype>][;base64],<data> // 協(xié)議 + 媒體類型以及編碼+ 文件內(nèi)容(圖片會(huì)被轉(zhuǎn)化為base64)
-
最佳實(shí)踐:小文件使用 Data URLs, 減少請(qǐng)求次數(shù)尽狠。大文件單獨(dú)提取衔憨,避免 bundle.js 過大,加載時(shí)間過長
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /.png$/,
use: {
// 必須同時(shí)安裝file-loader,當(dāng)超過limit設(shè)置的值袄膏,url-loader會(huì)自動(dòng)讓file-loader處理
loader: 'url-loader',
options: {
limit: 10 * 1024, // 10 KB
},
},
},
];
}
-
常用 loader 分類
編譯轉(zhuǎn)化類型
文件操作類型
代碼質(zhì)量檢查
-
處理 ES6+新特性
webpack 只是打包工具 默認(rèn)處理代碼中的 export 和 import,但對(duì)其他 ES6+新特性不做處理践图,需要 babel-loader
$ yarn add babel-loader @babel/core @babel/preset-env -D
// babel 只是一個(gè)js的轉(zhuǎn)換平臺(tái)〕凉荩基于平臺(tái)通過不同的插件實(shí)現(xiàn)轉(zhuǎn)化
{
"test": /.js$/,
"use": {
"loader": "babel-loader",
"options": {
"presets": ["@babel/preset-env"]
}
}
}
-
模塊加載方式
webpack 兼容多種標(biāo)準(zhǔn)的模塊加載方式- ES Module
- CommonJs
- AMD
- import('XXX.css')
- @import ()
- @import url()
- html 中的 img 的 src 屬性
- background 屬性的 url
- a 標(biāo)簽的 herf 屬性
...
-
webpack 核心工作原理
首先設(shè)置入口文件平项,webpack 會(huì)根據(jù)配置找到入口文件(如果不設(shè)置默認(rèn) src 下面的 index.js 文件)作為我們的打包入口
根據(jù)代碼中出現(xiàn)的 import 或者 require 解析推斷出這個(gè)文件所依賴的其他資源模塊
然后分別延伸解析每一個(gè)資源模塊對(duì)應(yīng)的依賴,形成一個(gè)整個(gè)項(xiàng)目當(dāng)中所有用的資源文件的依賴樹
然后遞歸這個(gè)依賴樹悍及,找到每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的資源文件闽瓢,根據(jù)配置文件的 rules 屬性找到當(dāng)前模塊的加載器(loader),然后交給加載器加載這個(gè)模塊
最終將執(zhí)行完成的結(jié)果放到 output 對(duì)應(yīng)的 bundle.js 中
在整個(gè)過程中,會(huì)通過 webpack 提供的鉤子函數(shù)(生命周期函數(shù))加載對(duì)應(yīng)的任務(wù)心赶。這個(gè)任務(wù)我們也成 plugins
webpack 開發(fā)一個(gè) loader
原則: 對(duì)同一文件所用到的 loader 執(zhí)行完成后, 最終必須返回 javascropt 代碼扣讼,也就是處理當(dāng)前資源的最后的 loader 必須是返回 javascript 代碼
const path = require('path');
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/',
},
module: {
rules: [
{
test: /.md$/,
// 將 md 轉(zhuǎn)化為 html
use: ['html-loader', './markdown-loader'],
},
],
},
};
// main.js
import about from './about.md';
console.log(about);
// markdown-loader.js
const marked = require('marked');
module.exports = source => {
// source為加載進(jìn)來的資源內(nèi)容
const html = marked(source);
// 如果不交給下個(gè)loader處理
// return `module.exports = "${html}"`
// return `export default ${JSON.stringify(html)}`
// 如果交給下個(gè)loader處理
// 返回 html 字符串交給下一個(gè) loader 處理
return html;
};
-
常用插件 Plugin
clean-webpack-plugin
每次打包前先清除 webpack 輸出目錄HtmlWebpackPlugin
每次打包的文件自動(dòng)生成 html 文件,自動(dòng)引入打包結(jié)果
plugins: [
new webpack.ProgressPlugin(),
new CleanWebpackPlugin(),
// 不額外添加模板的使用
new HtmlWebpackPlugin({
title: 'glh', // 設(shè)置標(biāo)題
meta: {
// 設(shè)置meta標(biāo)簽
viewport: 'width=device-width',
},
// ...
}),
];
// 添加模板,讓HtmlWebpackPlugin根據(jù)模板生成
new HtmlWebpackPlugin({
title: 'glh', // 設(shè)置標(biāo)題
meta: {
// 設(shè)置meta標(biāo)簽
viewport: 'width=device-width',
},
template: './public/index.html',
templateParameters: {
// 自定義變量
BASE_URL: './',
},
// ...
});
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
// 用于生成index.html
new HtmlWebpackPlugin({
template: './public/index.html',
// ...
});
// 用于生成about.html
new HtmlWebpackPlugin({
filename: 'about.html',
// ...
});
copy-webpack-plugin
對(duì)一些公共資源文件直接復(fù)制到打包目錄中缨叫。比如 public/favicon.ico
new CopyWebpackPlugin({
patterns: [{ from: 'public/favicon.ico', to: '.' }],
});
我們一般在使用插件的時(shí)候掌握一些經(jīng)常用的就可以椭符。后面根據(jù)需求再去提煉關(guān)鍵詞,搜索自己想用的插件耻姥,當(dāng)然也可以自己寫销钝。插件的約定名稱一般都是 XXX-webpack-plugin,比如我們想要壓縮圖片就可以找 imagemin-webpack-plugin
- 實(shí)現(xiàn)一個(gè)自定義 plugin
首先要明白:
- Plugin 其實(shí)就是通過在生命周期的鉤子中掛載函數(shù)實(shí)現(xiàn)擴(kuò)展。類似于我們 React 中的聲明周期琐簇。
webpack 在工作的過程中給每一個(gè)環(huán)節(jié)都埋下了鉤子蒸健,我們只需要在對(duì)應(yīng)的鉤子下掛載任務(wù)就可以輕松的擴(kuò)展 webpack 的能力
自定義的 Plugin 其實(shí)就是一個(gè)函數(shù),或者包含 apply 的方法的對(duì)象
apply 方法接受一個(gè) compiler 對(duì)象參數(shù)婉商,這個(gè)參數(shù)包含我們整個(gè)構(gòu)建過程中的所有配置信息似忧,通過這個(gè)對(duì)象我們可以注冊(cè)鉤子函數(shù),通過 tap 方法注冊(cè)任務(wù)
tap 方法又接受兩個(gè)參數(shù)丈秩,一個(gè)是插件名稱盯捌,一個(gè)是當(dāng)前次打包執(zhí)行的上下文
- 和 loader 區(qū)別:loader 是專注實(shí)現(xiàn)資源模塊加載轉(zhuǎn)化
Plugin 是解決處理資源加載轉(zhuǎn)化之外的的一些自動(dòng)化工作
相比于 Loader,Plugin 的能力范圍更寬
因?yàn)?Loader 只是在加載模塊的范圍工作,而插件的工作范圍可以觸及到 webpack 的每一個(gè)環(huán)節(jié)
class MyPlugin {
apply(compiler) {
console.log('MyPlugin 啟動(dòng)');
// 這里要做的事情就是在emit鉤子上掛載一個(gè)任務(wù)蘑秽,這個(gè)任務(wù)幫我們?nèi)コ虬鬀]有必要的注釋(mode=none情況下)其他鉤子可參考官網(wǎng)
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation => 可以理解為此次打包的上下文
for (const name in compilation.assets) {
// console.log(name)
// console.log(compilation.assets[name].source())
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source();
const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length,
};
}
}
});
}
}
plugins: [new MyPlugin()];
- 增強(qiáng) webpack 的開發(fā)體驗(yàn)
// 不使用Webpack Dev Server情況下饺著,自動(dòng)監(jiān)聽打包文件的變化
$ yarn webpack --watch
$ http-server -c-1 dist //or $ browser-sync dist --file "**/*"
以上方式效率太低,文件不斷的被讀寫操作肠牲,有待優(yōu)化
-
Webpack Dev Server
編寫源代碼=> webpack 打包=> 運(yùn)行應(yīng)用=> 刷新瀏覽器
我們可以借助 Webpack Dev Server 來提升開發(fā)體驗(yàn)幼衰,更接近生產(chǎn)環(huán)境的運(yùn)行狀態(tài),同時(shí)也可以設(shè)置 proxy埂材,對(duì)于錯(cuò)誤我們可以使用 souceMap 來快速定位源代碼問題
$ yarn add webpack-dev-server -D
$ yarm webpack-dev-server --open
webpack-dev-server 并不會(huì)將打包結(jié)果放到磁盤中塑顺,暫時(shí)存放到內(nèi)存中,從臨時(shí)內(nèi)存中讀取內(nèi)容發(fā)送給瀏覽器,從而大大提高了效率
- webpackDevServer 的靜態(tài)資源訪問
devServer: {
contentBase: './public', //也可以指定數(shù)組標(biāo)識(shí)多個(gè)目錄
}
- 代理 proxy
代理方式適用于后端沒有配置 cors 的情況
如果我們的項(xiàng)目最終上線前后端代碼符合同源策略严拒,也就沒必要設(shè)置 cors 了扬绪,這個(gè)時(shí)候可以通過本地服務(wù)器配置代理的方式實(shí)現(xiàn)跨域請(qǐng)求
devServer: {
proxy: {
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: {
'^/api': '' // 根據(jù)后端接口文件路勁因情況而定,這里只是用github舉例說明
},
// 不能使用 localhost:8080 作為請(qǐng)求 GitHub 的主機(jī)名
changeOrigin: true
}
}
}
// main.js;
// 跨域請(qǐng)求裤唠,雖然 GitHub 支持 CORS挤牛,但是不是每個(gè)服務(wù)端都應(yīng)該支持。
// fetch('https://api.github.com/users')
fetch('/api/users') // http://localhost:8080/api/users
.then(res => res.json())
.then(data => {
data.forEach(item => {
const li = document.createElement('li');
li.textContent = item.login;
ul.append(li);
});
});
-
sourceMap
由于編寫的代碼和運(yùn)行的代碼不一致种蘸,sourceMap 幫我們定位源代碼錯(cuò)誤
webpack 提供了 12 中 sourceMap 方式墓赴。每種方式的效果和效率不同,效果最好的航瞭,效率最差诫硕,效果最差的,效率最高刊侯,因此我們只需要實(shí)際開發(fā)中符合需求的最佳實(shí)踐
cheap: 定位到行章办,不定位列
eval: 定位到文件
module: 定位 loader 處理前的源代碼
inline: 把 sourcemap 嵌入到打包文件中,不額外生成對(duì)應(yīng)的.map 文件
hidden: 會(huì)有錯(cuò)誤信息滨彻,但是不是源文件藕届。開發(fā)第三方包的時(shí)候可以用
nosources: 看不到源代碼,但是會(huì)有行列信息亭饵,保護(hù)在生產(chǎn)環(huán)境中源代碼不被暴露
devtool: // 開發(fā)環(huán)境 'cheap-module-eval-source-map',
// 生產(chǎn)環(huán)境 'none',
// 如果對(duì)自己上線代碼沒有信心 'nosources-source-map'
const HtmlWebpackPlugin = require('html-webpack-plugin');
const allModes = [
'eval',
'cheap-eval-source-map',
'cheap-module-eval-source-map',
'eval-source-map',
'cheap-source-map',
'cheap-module-source-map',
'inline-cheap-source-map',
'inline-cheap-module-source-map',
'source-map',
'inline-source-map',
'hidden-source-map',
'nosources-source-map',
];
module.exports = allModes.map(item => {
return {
devtool: item,
mode: 'none',
entry: './src/main.js',
output: {
filename: `js/${item}.js`,
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
filename: `${item}.html`,
}),
],
};
});
- 熱更新(HMR)代替自動(dòng)刷新
自動(dòng)刷新導(dǎo)致頁面狀態(tài)丟失
熱更新就是在頁面不跟新的情況下休偶,只將修改的模塊實(shí)時(shí)替換到應(yīng)用中
$ yarn webpack-dev-server --hot
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js',
},
devtool: 'source-map',
devServer: {
hot: true,
// hotOnly: true // 只使用 HMR,不會(huì) fallback 到 live reloading
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// ...
],
};
默認(rèn)的 HMR 開啟后還需要我們手動(dòng)去處理熱更新的邏輯辜羊。當(dāng)然在 css 文件中由于 cssloader 中已經(jīng)幫我們處理了踏兜,所以我們可以看到修改 css 可以出發(fā)熱跟新
編寫的 js 模塊由于代碼太過靈活,如果沒有框架的約束只冻,wabpack 很難實(shí)現(xiàn)通用的熱更新
- HMR API
import createEditor from './editor';
import background from './better.png';
import './global.css';
const editor = createEditor();
document.body.appendChild(editor);
const img = new Image();
img.src = background;
document.body.appendChild(img);
// ============ 以下用于處理 HMR庇麦,與業(yè)務(wù)代碼無關(guān) ============
// console.log(createEditor)
if (module.hot) {
let lastEditor = editor;
// 處理js模塊的熱更新
module.hot.accept('./editor', () => {
// console.log('editor 模塊更新了,需要這里手動(dòng)處理熱替換邏輯')
// console.log(createEditor)
const value = lastEditor.innerHTML;
document.body.removeChild(lastEditor);
const newEditor = createEditor();
// 解決文本框狀態(tài)丟失
newEditor.innerHTML = value;
document.body.appendChild(newEditor);
lastEditor = newEditor;
});
// 處理img熱更新
module.hot.accept('./better.png', () => {
img.src = background;
console.log(background);
});
}
以上例子 只是說明 webpack 沒辦法提供通用方案喜德。實(shí)現(xiàn)一個(gè)熱更新原理就是利用 module.hot,HotModuleReplacementPluginApi 提供的這個(gè)垮媒。大部分框架中都集成了 HMR舍悯。
- 不同環(huán)境的配置文件
// 函數(shù)方式配置
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env, argv) => {
const config = {
// ...
};
if (env === 'production') {
config.mode = 'production';
config.devtool = false;
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public']),
];
}
return config;
};
文件劃分的配置
// webpack.common.js
module.exports = {};
// webpack.dev.js
const common = require('./webpack.common');
const merge = require('webpack-merge'); // 安裝webpack-merge合并配置
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public',
},
plugins: [new webpack.HotModuleReplacementPlugin()],
});
// webpack.prod.js
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(['public'])],
});
$ yarn webpack --config webpack.prod.js
$ yarn webpack-dev-server --config webpack.dev.js
- DefinePlugin
為代碼注入全局成員
默認(rèn)注入 process.evn.NODE_ENV 常量
plugins: [
new webpack.DefinePlugin({
// 值要求的是一個(gè)代碼片段
API_BASE_URL: JSON.stringify('https://api.example.com'),
}),
];
- Tree-shaking
將未引用代碼去除掉 生產(chǎn)環(huán)境下自動(dòng)開啟
在其他模式下開啟需要:
optimization: {
// 模塊只導(dǎo)出被使用的成員
usedExports: true,
// 盡可能合并每一個(gè)模塊到一個(gè)函數(shù)中
concatenateModules: true, // scope Hoisting
// 壓縮輸出結(jié)果
minimize: true
}
Tree-shaking && babel
由于 Tree-shaking 是基于 ESModule 實(shí)現(xiàn)的,但是 舊版本 babel 中如果用到 preset-env 的插件集合的時(shí)候會(huì)默認(rèn)開啟轉(zhuǎn)化 ESModule 的導(dǎo)入導(dǎo)出語法為 Commonjs 的規(guī)范睡雇。所以導(dǎo)致 Tree-shaking 失效萌衬,新版本已默認(rèn)關(guān)閉sideEffects 新特新
標(biāo)識(shí)代碼是否有副作用,為 Tree-shaking 提供更大的壓縮空間
// webpack.config.js
optimization: {
sideEffects: true; // 開啟sideEffects功能
}
// package.json
"siedEffects": false // 標(biāo)識(shí)代碼是否有副作用
副作用需要我們手動(dòng)添加并且謹(jǐn)慎使用它抱,一般用在開發(fā)第三方包中秕豫,當(dāng)我們的代碼有副作用,但是卻配置了以上兩個(gè)屬性,就會(huì)導(dǎo)致程序報(bào)錯(cuò)混移。
// package.json 配置有副作用的文件祠墅,這樣webpack在打包的過程中就不會(huì)忽略這些
"siedEffects" :[
"./src/extend.js",
"*/css"
]
-
Code Splitting
打包成一個(gè)文件導(dǎo)致體積過大,加載時(shí)間過長歌径。
應(yīng)用啟動(dòng)的首屏并不是所有模塊都工作的
所以我們需要分包毁嗦,按需加載- 多入口打包
適用于多頁面應(yīng)用
entry: { index: './src/index.js', album: './src/album.js' }, output: { filename: '[name].bundle.js' }, optimization: { splitChunks: { // 自動(dòng)提取所有公共模塊到單獨(dú) bundle chunks: 'all' } }, plugins: [ new HtmlWebpackPlugin({ title: '首頁', template: './src/index.html', filename: 'index.html', chunks: ['index'] // 對(duì)不同的頁面指定不同的打包js文件 }), new HtmlWebpackPlugin({ title: '其他頁面', template: './src/album.html', filename: 'album.html', chunks: ['album'] }) ]
- 動(dòng)態(tài)導(dǎo)入 import()
適用于單頁面應(yīng)用
在 react 或者 vue 中一般都是通過路由映射組件實(shí)現(xiàn)動(dòng)態(tài)加載
webpack 會(huì)根據(jù) import()把對(duì)應(yīng)的模塊拆分到不同的輸出文件,根據(jù)加載的需要執(zhí)行不同的 js 文件
// import posts from './posts/posts' // import album from './album/album' const render = () => { const hash = window.location.hash || '#posts' const mainElement = document.querySelector('.main') mainElement.innerHTML = '' if (hash === '#posts') { // mainElement.appendChild(posts()) import(/_ webpackChunkName: 'components' _/'./posts/posts').then(({ default: posts }) => { mainElement.appendChild(posts()) }) } else if (hash === '#album') { // mainElement.appendChild(album()) import(/_ webpackChunkName: 'components' _/'./album/album').then(({ default: album }) => { mainElement.appendChild(album()) }) } } render() window.addEventListener('hashchange', render)
- 多入口打包
魔法注釋
通過在 import 語句中加注釋的方式為 webpack 提供打包后的名稱如果設(shè)置一樣則打包到一個(gè)文件
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */ './posts/posts').then(
({ default: posts }) => {
mainElement.appendChild(posts());
}
);
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */ './album/album').then(
({ default: album }) => {
mainElement.appendChild(album());
}
);
}
- MiniCssExtractPlugin
提取 css 到單個(gè)文件
需要考慮 css 大小回铛,如果很少寫 css 那么還是采用 stypeLoader 注入到頁面的 style 標(biāo)簽中
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 將樣式通過 style 標(biāo)簽注入
MiniCssExtractPlugin.loader, // 將樣式通過Link標(biāo)簽方式注入
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin()
]
- webpack 內(nèi)部提供的生產(chǎn)環(huán)境的壓縮只是針對(duì) JS 代碼的狗准。如果想要壓縮其他形式資源,需要單獨(dú)安裝對(duì)應(yīng)的插件
optimization: {
minimize: [
// 要使用其他壓縮茵肃,這里要把默認(rèn)的js壓縮的插件也安裝進(jìn)來腔长,是因?yàn)閣ebpack會(huì)覆蓋了 optimization原有的默認(rèn)配置
// 這里配置的壓縮都只會(huì)在生產(chǎn)環(huán)境起作用,符合我們的預(yù)期验残,不用再去放到webpack.prod.js或者根據(jù)環(huán)境變量判斷
new TreserWebpackPlugin(),
// 這里以壓縮css為例捞附,其他的參見官網(wǎng)
new OptimizeCssAssetsWebpackPlugin()]
}
- 輸出文件名稱 Hash
一般我們部署前端資源文件的時(shí)候,都會(huì)開啟靜態(tài)資源緩存胚膊,避免每次都請(qǐng)求資源故俐,整體應(yīng)用的響應(yīng)速度就會(huì)大幅度提升,不過也會(huì)有問題紊婉,當(dāng)我們緩存時(shí)間設(shè)置過長药版,我們的應(yīng)用更新過后,瀏覽器并不會(huì)及時(shí)更新喻犁。這就需要我們?cè)谏a(chǎn)環(huán)境中需要給文件添加 Hash 值槽片,全新的文件名就是全新的請(qǐng)求,也就避免了上述問題
hash: 只要內(nèi)容修改肢础,所有文件 hash 都會(huì)跟新
contenthash: 文件級(jí)別的 hash,當(dāng)前修改的文件以及被引用的文件 hash 會(huì)被動(dòng)跟新
chunk: 當(dāng)文件內(nèi)容改變还栓,只修改當(dāng)前同類的 hash 值
output: {
filename: '[name]-[contenthash:8].bundle.js'
},
還有一些其他的配置項(xiàng)比如 preformance target externals resolve other option 我們只需要查閱官方文檔即可,另外還需要多理解 manifest 和 runtime 這樣的 webpack 概念