一蜡饵、入坑初探
1. 設(shè)置項(xiàng)目為私有
我們只需要在package.json文件中配置,因?yàn)槭撬接许?xiàng)目不需要向外部暴露的甲献,所以我們可以去掉main: index.js
"private": true
2. 運(yùn)行webpack
一般我們安裝webpack時(shí)會(huì)同時(shí)安裝webpack-cli端壳,它的作用是使我們可以在命令行使用webpack命令,在命令行中執(zhí)行
npx webpack --config webpack.config.js
--config指定webpack執(zhí)行的文件韩玩,如果沒(méi)有垒玲,默認(rèn)是webpack.config.js,因?yàn)槲覀兪窃诿钚兄袌?zhí)行找颓,所以需要npx合愈,如果我們寫在package.json文件中,則只需要"bundle": "webpack"
就可以了击狮。
3. webpack簡(jiǎn)單配置
webpack只能識(shí)別后綴是.js的文件佛析,如果是其它類型的文件,就需要引入loader來(lái)幫助我們編譯彪蓬。
下面我們來(lái)做一個(gè)簡(jiǎn)單的對(duì)圖片和css的打包配置:
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: { // loader額外參數(shù)配置
name: '[name]_[hash].[ext]', // name: 原來(lái)的名字 ext:原來(lái)的后綴
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這種語(yǔ)法
'sass-loader',
'postcss-loader'
]
}
]
file-loader和url-loader的區(qū)別是url-loader會(huì)把圖片等(任何文件)文件直接打包到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
最后我們?cè)賮?lái)看打包完命令行中的展示琐旁,如下圖所示:
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文件也需要通過(guò)postcss-loader,sass-loader編譯敬特,如果不加,就會(huì)直接走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
我們需要自動(dòng)生成一個(gè)html文件合搅,把打包生成的js自動(dòng)引入到這個(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è)名字,否則會(huì)因打包處兩份相同的文件而報(bào)錯(cuò)
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. 只指出哪一行出錯(cuò),不指出哪一頁(yè)蒸痹。2. 只報(bào)我們的業(yè)務(wù)代碼春弥,不處理loader等中的代碼錯(cuò)誤。
module:指出loader等中的錯(cuò)誤
source-map: 生成一個(gè).map文件
inline: 將映射文件放到main.js中
eval: 將業(yè)務(wù)代碼與 以及source-map通過(guò)eval方式執(zhí)行叠荠,速度最快
具體用法參考官方文檔:https://webpack.js.org/configuration/devtool/#devtool
5. 熱啟動(dòng)
5.1 通過(guò)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
下面我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的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語(yǔ)法:https://www.webpackjs.com/api/cli/
在node中使用webpack: https://www.webpackjs.com/api/node/
6. Hot Module Replacement 熱模塊更新
當(dāng)我們每次修改代碼時(shí)苏揣,頁(yè)面都會(huì)整個(gè)刷新,這樣豈不是很麻煩推姻,有沒(méi)有辦法只更新被修改的部分平匈,而不刷新整個(gè)頁(yè)面,這時(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才會(huì)生效
hotOnly: HotModuleReplacementPlugin失效時(shí),重新刷新一次頁(yè)面
修改了某個(gè)文件后拧晕,我們就需要手動(dòng)去更新了
if(module.hot) {
module.hot.accept('./number', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
然而我們平常用css隙姿,vue和react等的時(shí)候并沒(méi)有這么去做,這是因?yàn)槭且驗(yàn)閏ss-loader防症,vue-loader孟辑,react-loader中自動(dòng)幫我們實(shí)現(xiàn)了
7. Babel 處理 ES6 語(yǔ)法
這里我們要參考babel官方文檔:https://babeljs.io/setup#installation,下面我們先來(lái)做一個(gè)簡(jiǎn)單的配置:
{
test: /\.js$/,
exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'), // 只檢測(cè)某個(gè)目錄蔫敲,exclude除掉某個(gè)目錄
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { // @babel/preset-env將es6轉(zhuǎn)為es5
useBuiltIns: 'usage'
}]]
}
}
這里我們需要注意饲嗽,我們需要配置exclude: /node_modules/, 否則這里也會(huì)去匹配node_modules中的js文件,同時(shí)我們可以看到奈嘿,如果所有配置都寫在webpack.config.js中貌虾,那將會(huì)變得非常復(fù)雜,所以這里建議新建一個(gè).babelrc文件裙犹,將babel-loader中的配置放在.babelrc中尽狠,如下所示:
{
presets: [
[
"@babel/preset-env", {
targets: {
chrome: "67", // 支持哪個(gè)版本以上的瀏覽器
},
useBuiltIns: 'usage' // 實(shí)現(xiàn)按需加載
}
]
]
}
在有些低版本瀏覽器中是不支持es5的一些語(yǔ)法的,這時(shí)我們需要@babel/polyfill
幫我們解決叶圃,我們直接在入口文件中main.js引入@babel/polyfill即可
import "@babel/polyfill";
但是我們?cè)趺磳?shí)現(xiàn)按需加載呢袄膏,我們?cè)?babelrc中添加useBuiltIns: 'usage'
如果配置了useBuiltIns: 'usage'
,會(huì)默認(rèn)引入@babel/polyfill
掺冠,不需要手動(dòng)調(diào)用
參見官網(wǎng):https://babeljs.io/docs/en/babel-polyfill
8. 類庫(kù)的配置
當(dāng)我們寫一個(gè)類庫(kù)時(shí)沉馆,我們可以用@babel/plugin-transform-runtime,相比@babel/polyfill德崭,它是通過(guò)閉包實(shí)現(xiàn)依賴注入斥黑,這樣做不會(huì)污染全局環(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大意就是只打包我們有使用的代碼,將無(wú)用的部分去掉憾股,舉例如下:
我們有一個(gè)math.js的方法庫(kù)鹿蜀,內(nèi)容如下
export const add = (a, b) => {
console.log( a + b );
}
export const minus = (a, b) => {
console.log( a - b );
}
然后我們?cè)趇ndex.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": [ // 不對(duì)下面的文件進(jìn)行tree shaking
"@babel/polly-fill",
"*.css"
]
首先來(lái)解釋下它是什么意思琐簇,即忽略掉哪些模塊不做Tree-shaking,首先我們要忽略所有的css文件座享,其次如果像import @babel/polyfill
這種形式的婉商,我們沒(méi)有引入任何東西,webpack會(huì)自動(dòng)幫我們忽略掉渣叛,這樣打包文件就出錯(cuò)了
在生產(chǎn)環(huán)境tree shaking 是自動(dòng)生效的丈秩,不用再webpack中做配置,但是我們依然需要在package.json中需要配置
"sideEffects": false // false代表沒(méi)有需要忽略的文件
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插件無(wú)法刪除dist目錄問(wèn)題靴跛,解決方法如下:
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._ = _;
然后我們?cè)趀ntry引入這個(gè)包,
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
}
3.2 配置optimization
在webpack中我們可以配置chunks來(lái)自動(dòng)幫我們做(同步)代碼分割
optimization: {
splitChunks: {
chunks: 'all'
}
}
3.3 異步模塊打包
異步模塊不需要我們做任何配置绝葡,webpack會(huì)自動(dòng)幫我們將異步代碼打包到另一個(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');
這是魔法注釋敷硅,加上后,打包出來(lái)的js會(huì)是你注釋的值墓赴,否則為一個(gè)id(如0)值
4. SplitChunksPlugin 配置參數(shù)
splitChunks默認(rèn)配置,當(dāng)我們寫一個(gè)splitChunks: {}
竞膳,默認(rèn)等于如下
splitChunks: {
chunks: "async", // async 只對(duì)異步代碼生效, all同步異步都生效诫硕, initial同步生效
minSize: 30000, // 文件大于多少時(shí)才會(huì)打包
//maxSize: 0, // 會(huì)嘗試對(duì)大于多少的文件再次分割為兩個(gè)小文件
minChunks: 1, // 當(dāng)一個(gè)模塊至少被用了幾次后才做代碼分割
maxAsyncRequests: 5, // 最多分割幾個(gè)包
maxInitialRequests: 3, // 入口文件引入的庫(kù)最多能分割成幾個(gè)包
automaticNameDelimiter: '~', // 生成文件名字中間的連接符
name: true, // 使cacheGroups中設(shè)置的文件名有效
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10 // 優(yōu)先級(jí)的意思坦辟,如果同時(shí)滿足vendors和default,這個(gè)值誰(shuí)大就打包到哪個(gè)組章办,-10大于-20
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果引入的某個(gè)文件之前已經(jīng)被打包過(guò)锉走,就不會(huì)被打包了滨彻,會(huì)直接去復(fù)用之前的
}
}
}
chunks: "all"時(shí)我們需要注意,這時(shí)webpack會(huì)繼續(xù)找到cacheGroups
挪蹭,vendors中的test表示被打包的文件是否在node_modules這個(gè)文件夾中亭饵,如果是的話,就會(huì)打包到vendors這個(gè)組中梁厉,這時(shí)打包出來(lái)的文件名字應(yīng)該是vendors~main.js
,main是定義的入口文件名字辜羊,如果我們想指定一個(gè)名字,可以在vendors中設(shè)置filename指定一個(gè)名字
default是指如果不符合vendors中的要求的文件词顾,比如我們自己寫的一個(gè)包八秃,這個(gè)包并不在node_modules中,這時(shí)會(huì)分到default組中
cacheGroups作用是做一個(gè)緩存組肉盹,如果我們引入了多個(gè)包昔驱,就會(huì)分割成很多模塊,而cacheGroups作用就是先將需要打包的文件緩存起來(lái)上忍,然后統(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"
會(huì)生成一個(gè)stats.json文件窍蓝,這是一個(gè)對(duì)打包過(guò)程的描述文件腋颠,借助一些工具我們可以進(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)秕豫,可以查看代碼的使用率,代碼使用率越高說(shuō)明優(yōu)化的越好观蓄,所以我們開發(fā)時(shí)盡量多寫異步的代碼混移,這樣代碼使用的時(shí)候才會(huì)去加載
如下所示:
// 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是等主代碼加載完才會(huì)加載,Preloading是與主代碼同時(shí)加載
6. CSS代碼分割
這里我們需要使用mini-css-extract-plugin
參考官網(wǎng):https://webpack.js.org/plugins/mini-css-extract-plugin
6.1 我們先來(lái)看一下output內(nèi)容:
output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js',
path: path.resolve(__dirname, '../dist')
}
這里說(shuō)一下filename與chunkFilename的區(qū)別:
入口文件的打包用filename侮穿,chunk文件打包用chunkFilename
6.2 分割css
如果我們不分割css歌径,webpack會(huì)默認(rèn)把css打包到j(luò)s文件中,這是我們不希望看到的亲茅,下面來(lái)看下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壓縮
我們還可以對(duì)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文件中的验残,這樣會(huì)導(dǎo)致即使我們沒(méi)有更改文件,但是包與包之間的關(guān)系變了而引起的contenthash發(fā)生變化巾乳,這時(shí)我們就需要這樣配置將這部份代碼抽離出來(lái)您没,在新版webpack中不會(huì)出現(xiàn)這個(gè)問(wèn)題
optimization: {
runtimeChunk: {
name: 'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors',
}
}
}
},
8. Shimming
一些第三方的庫(kù)(library)可能會(huì)引用一些全局依賴(例如 jQuery 中的 $)鸟召。這些庫(kù)也可能創(chuàng)建一些需要被導(dǎo)出的全局變量。這些“不符合規(guī)范的模塊”就是 shimming 發(fā)揮作用的地方氨鹏。
8.1 全局引入
new webpack.ProvidePlugin({
$: 'jquery',
_join: ['lodash', 'join']
})
當(dāng)發(fā)現(xiàn)一個(gè)模塊中用了$時(shí)欧募,會(huì)在模塊中默認(rèn)引入jquery
如果需要使用模塊中的某個(gè)方法,我們可以用一個(gè)數(shù)組的方式定義
8.2 修改this指向
每個(gè)模塊的this指向的都是模塊自身仆抵,如果想讓this指向window,需要imports-loader插件跟继,然后我們?cè)僮鋈缦屡渲茫?/p>
{
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高級(jí)使用技巧
1. 類庫(kù)代碼打包
我們對(duì)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等語(yǔ)法
}
還可以用如下寫法
library: 'library', // 可以script標(biāo)簽引入还栓,在全局掛載了一個(gè)library變量
libraryTarget: this' // 這兩個(gè)配合就不支持amd等寫法了,只會(huì)掛載一個(gè)全局變量
lodash : {
commonjs: 'lodash', // 通過(guò)require(common.js)引入時(shí)传轰,名字必須叫l(wèi)odash
amd: 'lodash',
root: '_' // 通過(guò)script標(biāo)簽引入時(shí)必須在全局掛載一個(gè)_變量
}
const lodash = require('lodash') // commonjs設(shè)置的意思是const后的名字必須叫l(wèi)odash
如果我們編寫的庫(kù)中引入了其它包,我們不希望引入的包被打包谷婆,這時(shí)我們可以設(shè)置
module.exports = {
...
externals: 'lodash',
output: {
...
}
}
這里寫成一個(gè)數(shù)組慨蛙,對(duì)象,字符串形式都可以纪挎,對(duì)象形式:
module.exports = {
...
externals: {
lodash: {
commonjs: 'lodash'
}
},
output: {
...
}
}
參考官網(wǎng):https://webpack.js.org/configuration/externals/#externals
最后我們需要把package.json的入口文件改為
"main": "./dist/library.js",
然后在npm注冊(cè)一個(gè)賬號(hào)期贫,
然后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?$/, // ?代表可有可無(wú)
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文件
}
}
一般庫(kù)的typescript版本都是@types/名字异袄,可以參考:https://github.com/DefinitelyTyped/DefinitelyTyped
4. WebpackDevServer 實(shí)現(xiàn)請(qǐng)求轉(zhuǎn)發(fā)
注意本章只在開發(fā)環(huán)境生效通砍,對(duì)生產(chǎn)環(huán)境沒(méi)有影響
4. 1. 代理接口
devServer: {
proxy: {
// index: '', // 如果要代理根路徑,需要把index設(shè)置為false或者''
'/react/api': {
target: 'https://www.dell-lee.com', // 代理請(qǐng)求接口
secure: false, // 如果是https網(wǎng)址烤蜕,這里需要設(shè)置為false
pathRewrite: { // 代理接口封孙,訪問(wèn)header.json時(shí)會(huì)幫你請(qǐng)求demo.json
'header.json': 'demo.json'
},
changeOrigin: true, // 后端可能設(shè)置了changeOrigin防止爬蟲,這里我們?cè)O(shè)置true以后就可以避開這個(gè)限制了
headers: { // 設(shè)置請(qǐng)求頭
host: 'www.dell-lee.com',
cookie: ....
},
bypass: function(req, res, proxyOptions) { // 攔截讽营,如果請(qǐng)求的是一個(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è)面應(yīng)用路由問(wèn)題
當(dāng)不使用hash路由時(shí),我們可以設(shè)置以下內(nèi)容
historyApiFallback: true, // 把對(duì)服務(wù)器的請(qǐng)求都轉(zhuǎn)換為對(duì)跟路徑的請(qǐng)求
historyApiFallback: {
rewrites: [ // 訪問(wèn)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有錯(cuò)會(huì)在瀏覽器中提示
devServer: {
overlay: true
}
設(shè)置force為pre代表強(qiáng)制先執(zhí)行橱鹏,fix會(huì)自動(dòng)修復(fù)一些項(xiàng)目中eslint簡(jiǎn)單的錯(cuò)誤
{
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í)指定檢測(cè)目錄,圖片沒(méi)有必要
{
test: /\.js$/,
include: path.resolve(__dirname, '../src'), // 只檢測(cè)某個(gè)目錄莉兰,exclude除掉某個(gè)目錄
use: [{
loader: 'babel-loader'
}]
}
3. 盡少使用plugin挑围,盡可能精簡(jiǎn)并確保可靠
4. 合理配置resolve
resolve: {
extensions: ['.js', '.jsx'],
mainFIles: ['index', 'child']
},
當(dāng)一個(gè)引入的文件沒(méi)有后綴時(shí)糖荒,會(huì)識(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)行它對(duì)第三方模塊單獨(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)這里有,就不會(huì)再重復(fù)打這個(gè)包
manifest: path.resolve(__dirname, '../dll', '../dll/vendors.manifest.json')
})
new AddAssetHtmlWebpackPlugin({ // 向html中添加引入某個(gè)文件
filepath: path.resolve(__dirname, '../dll', '../dll/vendors.dll.js')
})
自動(dòng)化引入
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)境無(wú)用插件剔除
11. noParse
module: {
noParse: /jquery/, 不去解析jquery中的依賴庫(kù)
}
12. ignoreplugin
忽略掉我們不需要引入的包文件中的部分內(nèi)容
// 我們不需要引入moment這個(gè)包里的/locale文件夾,就把它忽略掉
new webpack.IgnorePlugin(/\.\/locale/,/moment/)
七珍昨、多頁(yè)面打包配置
多入口
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'] // 指定引入文件
}),
自動(dòng)化方式
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指向來(lái)調(diào)用this中的一些方法
return source.replace('lee', 'world');
}
然后我們?cè)趙ebpack.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兔毙,通過(guò)this.query可以接收到options中的內(nèi)容
module.exports = function(source) {
return source.replace('hello', this.query.name);
}
或者我們可以通過(guò)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í)兄春,會(huì)來(lái)node_modules中找澎剥,找不到了再來(lái)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() { // 文件長(zhǎng)度
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, { // 抽象語(yǔ)法樹九串,ast
sourceType: 'module' // 如果是es6模塊方法绞佩,這里需要設(shè)置
});
const dependencies = {};
traverse(ast, {
ImportDeclaration({ node }) { // 如果有引入語(yǔ)句,就執(zhí)行
const dirname = path.dirname(filename);
const newFile = './' + path.join(dirname, node.source.value); // 改為相對(duì)根目錄的路徑
dependencies[node.source.value] = newFile;
}
});
const { code } = babel.transformFromAst(ast, null, { // 將ast抽象語(yǔ)法樹轉(zhuǎn)換為瀏覽器可以識(shí)別的代碼
presets: ["@babel/preset-env"]
});
return {
filename,
dependencies,
code
}
}
const moduleInfo = moduleAnalyser('./src/index.js'); // 入口文件
console.log(moduleInfo);
vue-cli3多頁(yè)面配置
參考:https://cli.vuejs.org/zh/config/
webpack loader與plugins編寫:http://www.reibang.com/p/21cbc228d7f5