一、入坑初探
1. 設(shè)置項(xiàng)目為私有
我們只需要在package.json文件中配置,因?yàn)槭撬接许?xiàng)目不需要向外部暴露的,所以我們可以去掉main: index.js
"private": true
2. 運(yùn)行webpack
一般我們安裝webpack時(shí)會同時(shí)安裝webpack-cli,它的作用是使我們可以在命令行使用webpack命令,在命令行中執(zhí)行
npx webpack --config webpack.config.js
--config指定webpack執(zhí)行的文件,如果沒有,默認(rèn)是webpack.config.js,因?yàn)槲覀兪窃诿钚兄袌?zhí)行,所以需要npx,如果我們寫在package.json文件中,則只需要"bundle": "webpack"就可以了。
3. webpack簡單配置
webpack只能識別后綴是.js的文件,如果是其它類型的文件,就需要引入loader來幫助我們編譯。
下面我們來做一個(gè)簡單的對圖片和css的打包配置:
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: { // loader額外參數(shù)配置
name: '[name]_[hash].[ext]', // name: 原來的名字 ext:原來的后綴
outputPath: 'images/', // 輸出路徑
limit: 10240 // 限制,大于10240kb時(shí)才進(jìn)行此操作,否則直接打到j(luò)s文件中
}
}
},{
test: /\.scss$/,
use: [
'style-loader', // 將css掛載到header中
// options: {
// insertAt: 'top' // 插到頂部
// },
'css-loader', // 分析當(dāng)前有幾個(gè)css文件,將css文件整合,分析@import這種語法
'sass-loader',
'postcss-loader'
]
}
]
file-loader和url-loader的區(qū)別是url-loader會把圖片等(任何文件)文件直接打包到j(luò)s中,如果圖片很小,我們可以使用這種方式,如果圖片較大,我們就需要將圖片打包到統(tǒng)一的images目錄中,在上面代碼中我們做了一個(gè)限制,當(dāng)圖片大于10kb時(shí),就打包到images目錄中,否則直接打包到j(luò)s中
注意loader執(zhí)行順序是從下到上執(zhí)行的,如css這里,執(zhí)行順序?yàn)?
postcss-loader->sass-loader->css-loader->style-loader
最后我們再來看打包完命令行中的展示,如下圖所示:

Chunks: 打包的js的id,
Chunk Names: 打包的js名字
二、loader篇
1. css相關(guān)loader
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2, // 如果當(dāng)前引入的scss文件又引入了其它scss文件,讓引入的scss文件也需要通過postcss-loader,sass-loader編譯,如果不加,就會直接走css-loader,2代表前兩個(gè),幾就代表前幾個(gè)
modules: true // 開啟css模塊化,開啟后css需要用模塊化引入的寫法
}
},
'sass-loader',
'postcss-loader'
]
}
關(guān)于配置css-next的方法查看postcss-loader的文檔:https://webpack.js.org/loaders/postcss-loader
2. 打包字體文件
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}
打包字體文件用file-loader把字體文件打包到dist目錄中就可以了
三、webpack基礎(chǔ)
plugins相當(dāng)于vue,react中的鉤子,可以在webpack運(yùn)行到某個(gè)時(shí)刻的時(shí)候,幫助我們做一些事情
1. html-webpack-plugin
我們需要自動生成一個(gè)html文件,把打包生成的js自動引入到這個(gè)html文件中
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html' // 指定模版文件
})
2. CleanWebpackPlugin
我們需要在每次打包后刪掉上一次的打包文件
new CleanWebpackPlugin(['dist'])]
關(guān)于dist目錄和webpack配置文件不在同一個(gè)根目錄下,我們需要如下解決方法
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
2. copyWebpackPlugin
有些時(shí)候我們需要拷貝一些靜態(tài)資源文件到dist目錄
new CopyWebpackPlugin([
{from: 'doc', to: './'}
])
2. bannerPlugin
版權(quán)聲明插件,可以在我們打包生成的文件前生成一些版權(quán)信息等
new webpack.BannerPlugin('zxhnext@qq.com')
3. 打包多份js,指定cdn引用路徑
首先我們需要配置多入口
entry: {
main: './src/index.js',
sub: './src/index.js'
}
出口處我們不能寫死一個(gè)名字,否則會因打包處兩份相同的文件而報(bào)錯
output: {
publicPath: 'http://cdn.com.cn', // 設(shè)置前綴(cdn地址)
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
4. sourceMap
devtool: 'cheap-module-eval-source-map' // development
devtool: 'cheap-module-source-map' // production
一般在開發(fā)環(huán)境中我們使用cheap-module-eval-source-map,在線上環(huán)境使用cheap-module-source-map,如果要關(guān)閉sourceMap我們需要把devtool置為none
cheap:1. 只指出哪一行出錯,不指出哪一頁。2. 只報(bào)我們的業(yè)務(wù)代碼,不處理loader等中的代碼錯誤。
module:指出loader等中的錯誤
source-map: 生成一個(gè).map文件
inline: 將映射文件放到main.js中
eval: 將業(yè)務(wù)代碼與 以及source-map通過eval方式執(zhí)行,速度最快
具體用法參考官方文檔:https://webpack.js.org/configuration/devtool/#devtool
5. 熱啟動
5.1 通過shell腳本
"watch": "webpack --watch",
我們只需要在package.json文件中設(shè)置watch即可,但是這種方法存在很多缺陷,如果我們需要開啟一個(gè)本地服務(wù),那么我們需要使用webpack-dev-server
5.2. webpack-dev-server
devServer: {
contentBase: './dist',
open: true, // 是否打開瀏覽器
port: 8080
}
我們需要注意的是,使用webpack-dev-server時(shí)我們并未發(fā)現(xiàn)有dist目錄,這時(shí)因?yàn)閣ebpack-dev-server將打包好的文件隱藏到計(jì)算機(jī)的內(nèi)存中了,這樣執(zhí)行更快。
關(guān)于webpack-dev-server的更多配置參考官網(wǎng):https://webpack.js.org/configuration/dev-server
下面我們來實(shí)現(xiàn)一個(gè)簡單的webpack-dev-server
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
// 編譯
const complier = webpack(config);
const app = express();
// 在應(yīng)用里使用webpack
app.use(webpackDevMiddleware(complier, {
// config.output.publicPath
}));
app.listen(3000, () => {
console.log('server is running');
});
在命令行中使用webpack語法:https://www.webpackjs.com/api/cli/
在node中使用webpack: https://www.webpackjs.com/api/node/
6. Hot Module Replacement 熱模塊更新
當(dāng)我們每次修改代碼時(shí),頁面都會整個(gè)刷新,這樣豈不是很麻煩,有沒有辦法只更新被修改的部分,而不刷新整個(gè)頁面,這時(shí)我們需要用到HotModuleReplacementPlugin
const webpack = require('webpack');
module.exports = {
...
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true,
hotOnly: true
},
...
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
這里我們要注意的是,必須在devServer加上hot: true,hotOnly: true,Hot Module Replacement才會生效
hotOnly: HotModuleReplacementPlugin失效時(shí),重新刷新一次頁面
修改了某個(gè)文件后,我們就需要手動去更新了
if(module.hot) {
module.hot.accept('./number', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
然而我們平常用css,vue和react等的時(shí)候并沒有這么去做,這是因?yàn)槭且驗(yàn)閏ss-loader,vue-loader,react-loader中自動幫我們實(shí)現(xiàn)了
7. Babel 處理 ES6 語法
這里我們要參考babel官方文檔:https://babeljs.io/setup#installation,下面我們先來做一個(gè)簡單的配置:
{
test: /\.js$/,
exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'), // 只檢測某個(gè)目錄,exclude除掉某個(gè)目錄
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { // @babel/preset-env將es6轉(zhuǎn)為es5
useBuiltIns: 'usage'
}]]
}
}
這里我們需要注意,我們需要配置exclude: /node_modules/, 否則這里也會去匹配node_modules中的js文件,同時(shí)我們可以看到,如果所有配置都寫在webpack.config.js中,那將會變得非常復(fù)雜,所以這里建議新建一個(gè).babelrc文件,將babel-loader中的配置放在.babelrc中,如下所示:
{
presets: [
[
"@babel/preset-env", {
targets: {
chrome: "67", // 支持哪個(gè)版本以上的瀏覽器
},
useBuiltIns: 'usage' // 實(shí)現(xiàn)按需加載
}
]
]
}
在有些低版本瀏覽器中是不支持es5的一些語法的,這時(shí)我們需要@babel/polyfill幫我們解決,我們直接在入口文件中main.js引入@babel/polyfill即可
import "@babel/polyfill";
但是我們怎么實(shí)現(xiàn)按需加載呢,我們再.babelrc中添加useBuiltIns: 'usage'
如果配置了useBuiltIns: 'usage',會默認(rèn)引入@babel/polyfill,不需要手動調(diào)用
參見官網(wǎng):https://babeljs.io/docs/en/babel-polyfill
8. 類庫的配置
當(dāng)我們寫一個(gè)類庫時(shí),我們可以用@babel/plugin-transform-runtime,相比@babel/polyfill,它是通過閉包實(shí)現(xiàn)依賴注入,這樣做不會污染全局環(huán)境
{
"plugins": [["@babel/plugin-transform-runtime", {
"corejs": 2, // 設(shè)為2可以實(shí)現(xiàn)按需引入而不是全局引入,設(shè)為2后需要安裝@babel/runtime-corejs2
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
9. watch用法
watch: true,
watchOptions: { // 監(jiān)控的選項(xiàng)
poll: 1000, // 每秒監(jiān)控多少次
aggregateTimeout: 500, // 防抖,停止輸入500ms后再打包
ignored: /node_modules/ // 不需要監(jiān)控的文件夾
}
四、Webpack進(jìn)階
1. Tree-shaking
Tree-shaking大意就是只打包我們有使用的代碼,將無用的部分去掉,舉例如下:
我們有一個(gè)math.js的方法庫,內(nèi)容如下
export const add = (a, b) => {
console.log( a + b );
}
export const minus = (a, b) => {
console.log( a - b );
}
然后我們在index.js中使用math.js的add方法
import { add } from './math.js';
add(1, 7);
這里有一點(diǎn)我們需要注意,Tree-shaking只支持import這種ES Module,不支持require這種形式的。
雖然我們只引入了add方法,但是webpck默認(rèn)把math.js中所有的文件都幫我們打包了,如何做到只打包我們使用的部分代碼呢?這時(shí)我們需要在webpack中作如下配置
plugins: [],
...
optimization: {
usedExports: true
},
然后我們需要在package.json文件中這樣配置:
"sideEffects": [ // 不對下面的文件進(jìn)行tree shaking
"@babel/polly-fill",
"*.css"
]
首先來解釋下它是什么意思,即忽略掉哪些模塊不做Tree-shaking,首先我們要忽略所有的css文件,其次如果像import @babel/polyfill這種形式的,我們沒有引入任何東西,webpack會自動幫我們忽略掉,這樣打包文件就出錯了
在生產(chǎn)環(huán)境tree shaking 是自動生效的,不用再webpack中做配置,但是我們依然需要在package.json中需要配置
"sideEffects": false // false代表沒有需要忽略的文件
2. Develoment 和 Production
我們仿照create-react-app,創(chuàng)建build目錄存放我們的weback配置文件,首先我們將公用文件提到webpack.common.js,然后我們用webpack-merge合并,如下所示:
const commonConfig = require('./webpack.common.js');
const devConfig = {
...
}
module.exports = merge(commonConfig, devConfig);
因?yàn)槲覀儗ebpack配置文件放在了build目錄中,此時(shí)dist與webpack配置文件不在同一根目錄下,這是我們需要解決dist和webpack不在同一個(gè)根目錄下而產(chǎn)生的clean插件無法刪除dist目錄問題,解決方法如下:
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
3. Code Splitting代碼分割
3.1 多入口打包方法
如果我們想把引入的模塊單獨(dú)打包,我們需要單獨(dú)創(chuàng)建一個(gè)文件引入這個(gè)包,然后掛載到window上,再在入口處引入這個(gè)文件
這里我們以lodash為例:
新建lodash.js文件,內(nèi)容如下:
import _ from 'lodash';
window._ = _;
然后我們在entry引入這個(gè)包,
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
}
3.2 配置optimization
在webpack中我們可以配置chunks來自動幫我們做(同步)代碼分割
optimization: {
splitChunks: {
chunks: 'all'
}
}
3.3 異步模塊打包
異步模塊不需要我們做任何配置,webpack會自動幫我們將異步代碼打包到另一個(gè)文件中。
在使用異步加載的寫法時(shí)(vue中懶加載模塊),我們需要安裝@babel/plugin-syntax-dynamic-import,然后在.babelrc中配置
{
presets: [
[
"@babel/preset-env", {
targets: {
chrome: "67",
},
useBuiltIns: 'usage'
}
]
],
plugins: ["@babel/plugin-syntax-dynamic-import"]
}
異步代碼寫法
function getComponent() {
return import('lodash').then(({ default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
});
// es7寫法
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
const element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
}
document.addEventListener('click', () =>{
getComponent().then(element => {
document.body.appendChild(element);
});
})
import(/* webpackChunkName:"lodash" */ 'lodash');
這是魔法注釋,加上后,打包出來的js會是你注釋的值,否則為一個(gè)id(如0)值
4. SplitChunksPlugin 配置參數(shù)
splitChunks默認(rèn)配置,當(dāng)我們寫一個(gè)splitChunks: {},默認(rèn)等于如下
splitChunks: {
chunks: "async", // async 只對異步代碼生效, all同步異步都生效, initial同步生效
minSize: 30000, // 文件大于多少時(shí)才會打包
//maxSize: 0, // 會嘗試對大于多少的文件再次分割為兩個(gè)小文件
minChunks: 1, // 當(dāng)一個(gè)模塊至少被用了幾次后才做代碼分割
maxAsyncRequests: 5, // 最多分割幾個(gè)包
maxInitialRequests: 3, // 入口文件引入的庫最多能分割成幾個(gè)包
automaticNameDelimiter: '~', // 生成文件名字中間的連接符
name: true, // 使cacheGroups中設(shè)置的文件名有效
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10 // 優(yōu)先級的意思,如果同時(shí)滿足vendors和default,這個(gè)值誰大就打包到哪個(gè)組,-10大于-20
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果引入的某個(gè)文件之前已經(jīng)被打包過,就不會被打包了,會直接去復(fù)用之前的
}
}
}
chunks: "all"時(shí)我們需要注意,這時(shí)webpack會繼續(xù)找到cacheGroups,vendors中的test表示被打包的文件是否在node_modules這個(gè)文件夾中,如果是的話,就會打包到vendors這個(gè)組中,這時(shí)打包出來的文件名字應(yīng)該是vendors~main.js,main是定義的入口文件名字,如果我們想指定一個(gè)名字,可以在vendors中設(shè)置filename指定一個(gè)名字
default是指如果不符合vendors中的要求的文件,比如我們自己寫的一個(gè)包,這個(gè)包并不在node_modules中,這時(shí)會分到default組中
cacheGroups作用是做一個(gè)緩存組,如果我們引入了多個(gè)包,就會分割成很多模塊,而cacheGroups作用就是先將需要打包的文件緩存起來,然后統(tǒng)一打包到一個(gè)組中
vendors, default也可以設(shè)置為false
5. 打包分析,Preloading, Prefetching
5.1 打包分析
在package.json中設(shè)置一個(gè)下面的命令,然后運(yùn)行
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js"
會生成一個(gè)stats.json文件,這是一個(gè)對打包過程的描述文件,借助一些工具我們可以進(jìn)行分析。
參考analyse:https://github.com/webpack/analyse
參考官網(wǎng):https://webpack.js.org/guides/code-splitting/#bundle-analysis
5.2 代碼使用率
在瀏覽器調(diào)試工具中按command+shift+p,然后我們選擇show coverage選項(xiàng),可以查看代碼的使用率,代碼使用率越高說明優(yōu)化的越好,所以我們開發(fā)時(shí)盡量多寫異步的代碼,這樣代碼使用的時(shí)候才會去加載
如下所示:
// click.js
function handleClick() {
const element = document.createElement('div');
element.innerHTML = 'Dell Lee';
document.body.appendChild(element);
}
export default handleClick;
// index.js
document.addEventListener('click', () =>{
import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
func();
})
});
Prefetching是等主代碼加載完才會加載,Preloading是與主代碼同時(shí)加載
6. CSS代碼分割
這里我們需要使用mini-css-extract-plugin
參考官網(wǎng):https://webpack.js.org/plugins/mini-css-extract-plugin
6.1 我們先來看一下output內(nèi)容:
output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js',
path: path.resolve(__dirname, '../dist')
}
這里說一下filename與chunkFilename的區(qū)別:
入口文件的打包用filename,chunk文件打包用chunkFilename
6.2 分割css
如果我們不分割css,webpack會默認(rèn)把css打包到j(luò)s文件中,這是我們不希望看到的,下面來看下mini-css-extract-plugin的使用方法。注意,如果打包失敗,需要看一下是不是package.json文件中這里配置有誤,可能是tree shaking影響了
"sideEffects": "false"
// 改為
"sideEffects": ["*.css"]
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
]
6.3 css壓縮
我們還可以對css進(jìn)行壓縮,這時(shí)我們需要用到optimize-css-assets-webpack-plugin
然后配置如下:
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
6.4 多入口的css打包到一個(gè)css中
這個(gè)配置意思是只要是css文件就打包到這個(gè)組中
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
}
enforce為true表示忽略其它的默認(rèn)參數(shù)
6.5 不同入口打包到不同組
參考官網(wǎng):https://webpack.js.org/plugins/mini-css-extract-plugin
6.6 去掉性能上的警告
performance: false, // 去掉性能上的警告
output: {
path: path.resolve(__dirname, '../dist')
}
7. runtimeChunk
配置runtimeChunk是因?yàn)樵谝恍├习姹镜膚ebpack中,manifest(包與包之間的關(guān)系)文件是加在main與vendors文件中的,這樣會導(dǎo)致即使我們沒有更改文件,但是包與包之間的關(guān)系變了而引起的contenthash發(fā)生變化,這時(shí)我們就需要這樣配置將這部份代碼抽離出來,在新版webpack中不會出現(xiàn)這個(gè)問題
optimization: {
runtimeChunk: {
name: 'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors',
}
}
}
},
8. Shimming
一些第三方的庫(library)可能會引用一些全局依賴(例如 jQuery 中的 $)。這些庫也可能創(chuàng)建一些需要被導(dǎo)出的全局變量。這些“不符合規(guī)范的模塊”就是 shimming 發(fā)揮作用的地方。
8.1 全局引入
new webpack.ProvidePlugin({
$: 'jquery',
_join: ['lodash', 'join']
})
當(dāng)發(fā)現(xiàn)一個(gè)模塊中用了$時(shí),會在模塊中默認(rèn)引入jquery
如果需要使用模塊中的某個(gè)方法,我們可以用一個(gè)數(shù)組的方式定義
8.2 修改this指向
每個(gè)模塊的this指向的都是模塊自身,如果想讓this指向window,需要imports-loader插件,然后我們再做如下配置:
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
}
9. 環(huán)境變量的使用
module.exports = (env) => {
if(env && env.production) {
return merge(commonConfig, prodConfig);
}else {
return merge(commonConfig, devConfig);
}
}
然后在package.json中設(shè)置環(huán)境變量
"build": "webpack --env.production --config ./build/webpack.common.js"
五、webpack高級使用技巧
1. 類庫代碼打包
我們對package.json進(jìn)行設(shè)置
"license": "MIT", // 開源
然后在output中做如下設(shè)置:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library', // 可以script標(biāo)簽引入,在全局掛載了一個(gè)library變量
libraryTarget: 'umd' // 使支持amd,cmd,require等語法
}
還可以用如下寫法
library: 'library', // 可以script標(biāo)簽引入,在全局掛載了一個(gè)library變量
libraryTarget: this' // 這兩個(gè)配合就不支持amd等寫法了,只會掛載一個(gè)全局變量
lodash : {
commonjs: 'lodash', // 通過require(common.js)引入時(shí),名字必須叫l(wèi)odash
amd: 'lodash',
root: '_' // 通過script標(biāo)簽引入時(shí)必須在全局掛載一個(gè)_變量
}
const lodash = require('lodash') // commonjs設(shè)置的意思是const后的名字必須叫l(wèi)odash
如果我們編寫的庫中引入了其它包,我們不希望引入的包被打包,這時(shí)我們可以設(shè)置
module.exports = {
...
externals: 'lodash',
output: {
...
}
}
這里寫成一個(gè)數(shù)組,對象,字符串形式都可以,對象形式:
module.exports = {
...
externals: {
lodash: {
commonjs: 'lodash'
}
},
output: {
...
}
}
參考官網(wǎng):https://webpack.js.org/configuration/externals/#externals
最后我們需要把package.json的入口文件改為
"main": "./dist/library.js",
然后在npm注冊一個(gè)賬號,
然后npm adduser添加用戶名和密碼
再npm publish
2. PWA 的打包配置
安裝workbox-webpack-plugin
在plugins中配置:
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
js文件為
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('service-worker registed');
}).catch(error => {
console.log('service-worker register error');
})
})
}
3. TypeScript 的打包配置
我們需要安裝ts-loader typescript
在rules中配置:
{
test: /\.tsx?$/, // ?代表可有可無
use: 'ts-loader',
exclude: /node_modules/
}
同時(shí)創(chuàng)建tsconfig.json文件,做如下配置
{
"compilerOpitons": {
"outDir": "./dist",
"module": "es6", // 使用es6的模塊引入方法
"target": "es5", // 轉(zhuǎn)換為es5形式
"allowJs": true // 允許ts中引入js文件
}
}
一般庫的typescript版本都是@types/名字,可以參考:https://github.com/DefinitelyTyped/DefinitelyTyped
4. WebpackDevServer 實(shí)現(xiàn)請求轉(zhuǎn)發(fā)
注意本章只在開發(fā)環(huán)境生效,對生產(chǎn)環(huán)境沒有影響
4. 1. 代理接口
devServer: {
proxy: {
// index: '', // 如果要代理根路徑,需要把index設(shè)置為false或者''
'/react/api': {
target: 'https://www.dell-lee.com', // 代理請求接口
secure: false, // 如果是https網(wǎng)址,這里需要設(shè)置為false
pathRewrite: { // 代理接口,訪問header.json時(shí)會幫你請求demo.json
'header.json': 'demo.json'
},
changeOrigin: true, // 后端可能設(shè)置了changeOrigin防止爬蟲,這里我們設(shè)置true以后就可以避開這個(gè)限制了
headers: { // 設(shè)置請求頭
host: 'www.dell-lee.com',
cookie: ....
},
bypass: function(req, res, proxyOptions) { // 攔截,如果請求的是一個(gè)html內(nèi)容,則返回index.html
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
}
}
}
}
webpackdevserver proxy底層用了 http-proxy-middleware這個(gè)插件
如何使用mock數(shù)據(jù)
devServer: {
before(app) {
app.get('/user', (req, res) => {
res.json(....)
})
}
}
4. 2. WebpackDevServer 解決單頁面應(yīng)用路由問題
當(dāng)不使用hash路由時(shí),我們可以設(shè)置以下內(nèi)容
historyApiFallback: true, // 把對服務(wù)器的請求都轉(zhuǎn)換為對跟路徑的請求
historyApiFallback: {
rewrites: [ // 訪問abc.html時(shí)代理到index.html
{ from: /abc.html/, to: '/views/index.html' }
]
}
historyApiFallback: true相當(dāng)于
historyApiFallback: {
rewrites: [
{ from: /\.*\/, to: '/index.html' }
]
}
底層用了connect-history-api-fallback這個(gè)插件
5. EsLint 在 Webpack 中的配置
安裝eslint
npx eslint --init
module.exports = {
"extends": "airbnb", // 使用那個(gè)規(guī)則
"parser": "babel-eslint", // 解析器
"rules": {
"react/prefer-stateless-function": 0,
"react/jsx-filename-extension": 0
},
globals: {
document: false // 不允許覆蓋全局變量document
}
};
在webpack中使用eslint
安裝eslint-loader:https://webpack.js.org/loaders/eslint-loader
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader']
}
同時(shí)配置overlay: true,eslint有錯會在瀏覽器中提示
devServer: {
overlay: true
}
設(shè)置force為pre代表強(qiáng)制先執(zhí)行,fix會自動修復(fù)一些項(xiàng)目中eslint簡單的錯誤
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'eslint-loader',
options: {
fix: true
},
force: 'pre'
},
'babel-loader'
]
}
六、webpack 性能優(yōu)化
1. 經(jīng)常更新版本
2. 使用loader時(shí)指定檢測目錄,圖片沒有必要
{
test: /\.js$/,
include: path.resolve(__dirname, '../src'), // 只檢測某個(gè)目錄,exclude除掉某個(gè)目錄
use: [{
loader: 'babel-loader'
}]
}
3. 盡少使用plugin,盡可能精簡并確保可靠
4. 合理配置resolve
resolve: {
extensions: ['.js', '.jsx'],
mainFIles: ['index', 'child']
},
當(dāng)一個(gè)引入的文件沒有后綴時(shí),會識別它是不是.js,.jsx文件
引入一個(gè)目錄,回去查找目錄下是否有index,child文件
給文件或路徑設(shè)置別名
resolve: {
extensions: ['.js', '.jsx'],
alias: {
child: path.resolve(__dirname, '../src/child')
}
},
5. 第三方模塊只打包一次
新建一個(gè)webpack.dll.js, 運(yùn)行它對第三方模塊單獨(dú)打包,并生成vendors.manifest.json映射文件
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom'],
jquery: ['jquery']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]' // 將它暴露出去
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
然后再配置webpack.common.js
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
new webpack.DllReferencePlugin({ // 查找vendors.manifest.json,如果發(fā)現(xiàn)這里有,就不會再重復(fù)打這個(gè)包
manifest: path.resolve(__dirname, '../dll', '../dll/vendors.manifest.json')
})
new AddAssetHtmlWebpackPlugin({ // 向html中添加引入某個(gè)文件
filepath: path.resolve(__dirname, '../dll', '../dll/vendors.dll.js')
})
自動化引入
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
6. 控制包文件大小
7. thread-loader,parallel-webpack,happypack多線程打包
let happypack = require('happypack');
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.resolve('src'),
use: 'happypack/loader?id=js'
}
]
plugins: [
new happypack({
id: 'js',
use: [{
loader: 'babel-loader',
options: {
...
}
}]
})
]
8. 合理使用sourceMap
9. 開發(fā)環(huán)境內(nèi)存編譯
webpackdevserver用的就是內(nèi)存編譯
10. 開發(fā)環(huán)境無用插件剔除
11. noParse
module: {
noParse: /jquery/, 不去解析jquery中的依賴庫
}
12. ignoreplugin
忽略掉我們不需要引入的包文件中的部分內(nèi)容
// 我們不需要引入moment這個(gè)包里的/locale文件夾,就把它忽略掉
new webpack.IgnorePlugin(/\.\/locale/,/moment/)
七、多頁面打包配置
多入口
entry: {
index: './src/index.js',
list: './src/list.js',
detail: './src/detail.js',
}
生成多個(gè)html
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index.html',
chunks: ['runtime', 'vendors', 'list'] // 指定引入文件
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'list.html',
chunks: ['runtime', 'vendors', 'list'] // 指定引入文件
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'detail.html',
chunks: ['runtime', 'vendors', 'list'] // 指定引入文件
}),
自動化方式
const makePlugins = (configs) => {
const plugins = [
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
];
Object.keys(configs.entry).forEach(item => {
plugins.push(
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item]
})
)
});
return plugins;
}
八、webpack原理篇
1. 編寫一個(gè) Loader
1.1 同步操作
新建loader文件夾,在文件夾中新建replaceLoader.js文件
const loaderUtils = require('loader-utils');
module.exports = function(source) { // 注意這里不能使用箭頭函數(shù),我們需要變更this指向來調(diào)用this中的一些方法
return source.replace('lee', 'world');
}
然后我們在webpack.config.js中引入
rules: [{
test: /\.js/,
use: [path.resolve(__dirname, './loaders/replaceLoader.js')]
}]
同時(shí)我們還可以傳入一些參數(shù)
rules: [{
test: /\.js/,
use: [
{
loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
options: {
name: 'zxh'
}
}
]
}]
這時(shí)我們就可以在replaceLoader.js,通過this.query可以接收到options中的內(nèi)容
module.exports = function(source) {
return source.replace('hello', this.query.name);
}
或者我們可以通過webpack官方提供的loader-utils模塊,使用方法如下
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const result = source.replace('dell', options.name);
return source.replace('hello', options.name);
}
想要返回多個(gè)值時(shí)可以用this.callback

const loaderUtils = require('loader-utils');
module.exports = function(source) {
const options = loaderUtils.getOptions(this)
const result = source.replace('dell', options.name);
this.callback(null, result, source, mata)
}
1.2 使用異步操作 this.async
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const callback = this.async(); // 聲明是異步操作
setTimeout(() => {
const result = source.replace('dell', options.name);
callback(null, result);
}, 1000);
}
引入模塊時(shí),會來node_modules中找,找不到了再來loaders文件夾中找,這時(shí)我們就可以像引入node_modules中的loader那樣寫了
entry: {
main: './src/index.js'
},
resolveLoader: {
modules: ['node_modules', './loaders']
},
module: {
rules: [{
test: /\.js/,
use: [
{
loader: 'replaceLoader',
}
]
}]
}
2. 編寫一個(gè) Plugin
發(fā)布,訂閱設(shè)計(jì)模式
https://webpack.js.org/api/compiler-hooks
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => { // 同步,不用傳callback
console.log('compiler');
})
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => { // emit是異步的,我們需要在后面寫tapAsync,打包完放到文件夾時(shí),compiler是所有打包文件,compilation是本次打包文件
debugger;
compilation.assets['copyright.txt']= {
source: function() { // 內(nèi)容
return 'copyright by dell lee'
},
size: function() { // 文件長度
return 21;
}
};
cb(); // 最后必須調(diào)一下cb()
})
}
}
module.exports = CopyrightWebpackPlugin;
開啟node調(diào)試工具
"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js"
3. Bundler源碼編寫
安裝cli-highlight:命令行高亮顯示工具
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser'); // 幫助分析源代碼
const traverse = require('@babel/traverse').default; // 幫助遍歷module
const babel = require('@babel/core');
const moduleAnalyser = (filename) => {
const content = fs.readFileSync(filename, 'utf-8'); // 讀取文件內(nèi)容
const ast = parser.parse(content, { // 抽象語法樹,ast
sourceType: 'module' // 如果是es6模塊方法,這里需要設(shè)置
});
const dependencies = {};
traverse(ast, {
ImportDeclaration({ node }) { // 如果有引入語句,就執(zhí)行
const dirname = path.dirname(filename);
const newFile = './' + path.join(dirname, node.source.value); // 改為相對根目錄的路徑
dependencies[node.source.value] = newFile;
}
});
const { code } = babel.transformFromAst(ast, null, { // 將ast抽象語法樹轉(zhuǎn)換為瀏覽器可以識別的代碼
presets: ["@babel/preset-env"]
});
return {
filename,
dependencies,
code
}
}
const moduleInfo = moduleAnalyser('./src/index.js'); // 入口文件
console.log(moduleInfo);
vue-cli3多頁面配置

參考:https://cli.vuejs.org/zh/config/
webpack loader與plugins編寫:http://www.itdecent.cn/p/21cbc228d7f5