目录
- 起步
- 1. 基本安装
- 2. 配置出入口
- plugin
- 1. html-webpack-plugin
- 2. progress-bar-webpack-plugin
- loader
- 1. css-loader与style-loader
- 2. url-loader与file-loader
- 3. sass-loader
- 4. postcss-loader
- 5. babel-loader
- 搭建环境
- 1. 开发环境与生产环境
- 2. 配置别名
- 代码分离
- 1. webpack-bundle-analyzer
- 2. splitChunks(分离chunks)
- 3. 动态导入(按需加载chunks)
- 4. mini-css-extract-plugin(分离css)
- 缓存
- 1. contenthash
- 定义全局环境变量
- 1. 定义编译时全局变量
- 2. 定义编译后全局变量
- 优化打包体积
- 1. 开启gzip压缩
- 2.css-minimizer-webpack-plugin
- 3. externals
- 配置Vue
- 配置React
起步
版本:"webpack": "^5.68.0"
前言:一定要注意版本,webpack更新太快了。对于一个前端来说,面试的时候总是会被问到webpack相关问题,这里带大家搭建一个简易的Vue/React项目,对webpack有个初步了解,也会提一些面试被经常的问题。码字不易,点赞支持!!!
提示:修改了配置文件,要查看效果时,需要重启项目
GitHub地址:github.com/cwjbjy/webp…
文章根据评论区提出的问题,做出了相应的更新,并且将webpack的版本升级到5.68.0
1. 基本安装
(1)创建webpack5文件夹,用vscode打开,通过终端运行:
npm init -y | |
npm install -g yarn //如果安装过yarn就不用运行了 | |
yarn add webpack webpack-cli -D |
(2)在webpack5目录下新建 src,dist 文件夹
新建 src/main.js
console.log('Interesting!')
2. 配置出入口
新建build/webpack.common.js
(1)配置入口
可配置多个入口。但开发中一般是react或vue,为单页面web应用(SPA),入口一个即可
//webpack.common.js | |
const path = require('path') | |
module.exports = { | |
entry: path.resolve(__dirname, "../src/main.js"), | |
} |
(2)配置出口
只能有一个出口,这里指定输出路径为'dist'
//webpack.common.js | |
module.exports = { | |
output: { | |
path:path.resolve(__dirname,'../dist'), | |
filename: '[name].bundle.js', | |
clean:true //每次构建清除dist包 | |
}, | |
} |
现在,我们具有了最低配置。在package.json中,我们创建一个运行webpack命令构建脚本
"scripts": { | |
"build":"webpack --config build/webpack.common.js", | |
} |
现在可以运行它了:
npm run build
在dist文件夹下会生成main.bundle.js
目录结构:
plugin
插件(Plugins)是用来拓展Webpack功能的,包括:打包优化、资源管理、注入环境变量
插件使用:只需要require()它,然后把它添加到plugins数组中
1. html-webpack-plugin
html-webpack-plugin将为你生成一个HTML5文件,在body中使用script标签引入你所有webpack生成的bundle
(1)安装
yarn add -D html-webpack-plugin
(2)新建 public/index.html
<html lang="en"> | |
<head> | |
<meta charset="UTF-" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<meta name="viewport" content="width=device-width, initial-scale=.0" /> | |
<title>Document</title> | |
</head> | |
<body> | |
<div id="app"><div> | |
</div> | |
</div> | |
</body> | |
</html> |
(3)配置
//webpack.common.js | |
const HtmlWebpackPlugin = require('html-webpack-plugin') | |
module.exports = { | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
template: path.resolve(__dirname, '../public/index.html'), | |
filename: 'index.html', | |
}), | |
], | |
} |
(4)运行
npm run build
可以看到dist下多了index.html文件,并且打包生成的main.bundle.js在index.html中通过script标签被引入
2. progress-bar-webpack-plugin
作用:增加编译进度条
(1)安装:
yarn add progress-bar-webpack-plugin -D
(2)配置:
//webpack.common.js | |
const chalk = require("chalk"); | |
const ProgressBarPlugin = require("progress-bar-webpack-plugin"); | |
module.exports = { | |
plugins: [ | |
// 进度条 | |
new ProgressBarPlugin({ | |
format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`, | |
}), | |
], | |
}; |
loader
loader 用于对模块的源代码进行转换
loader都在module下的rules中配置
loader配置项包括:
- test 正则校验(必须)
- loader 调用loader的名称 / use 链式调用loader (二选一)
- include/exclude 手动添加必修处理的文件/文件夹或屏蔽不需要处理的文件/文件夹(可选)
- options 为loaders提供额外的设置选项(可选)
tip:use链式调用,都是从右向左解析,需注意调用loader的顺序。loader要记住,面试经常被问到有哪些loader以及其作用
1. css-loader与style-loader
作用:加载css
css-loader:会对@import和url()进行处理
style-loader:将CSS注入到JavaScript中,通过DOM操作控制css
(1)安装
yarn add css-loader style-loader -D
(2)在webpack.common.js中进行配置
module: { | |
rules: [ | |
{ | |
test: /\.css$/, | |
use: ["style-loader", "css-loader"], //从右向左解析 | |
}, | |
], | |
}, |
(3)示例
新建src/assets/styles/style.css
body{ | |
background-color: aqua; | |
} |
在main.js中引入
import './assets/styles/style.css'
重新编译 npm run build,在浏览器中打开 dist/index.html,可以看到css已生效
2. url-loader与file-loader
webpack5内置了资源模块(asset module),代替了file-loader和url-loader
例:加载图片资源
//在rules下增加 | |
{ | |
test: /\.(png|svg|jpg|jpeg|gif)$/i, | |
type: "asset", | |
generator: { | |
filename: "static/img/[name].[hash:][ext]", | |
}, | |
}, |
3. sass-loader
(1)安装
yarn add sass-loader node-sass -D
(2)修改原先的css规则,改为:
{ | |
test: /\.(css|scss|sass)$/, | |
use: ['style-loader', 'css-loader', 'sass-loader'] //从右往左编译的 | |
}, |
(3)新建src/assets/blue.scss文件
$blue: blue; | |
body{ | |
color: $blue; | |
} |
(4)在main.js中引入blue.scss
import './assets/styles/blue.scss'
重新编译,打开dist/index.html,可以看到页面中的123已变成蓝色
4. postcss-loader
作用:处理css的loader
配合autoprefixer,增加厂商前缀(css增加浏览器内核前缀)
tip:面试的时候被问到两次(关键词:postcss-loader,autoprefixer,browserslist)
(1)安装:
yarn add -D postcss-loader autoprefixer
(2)修改原先的css规则:
postcss-loader在css-loader和style-loader之前,在sass-loader或less-loader之后(从右往左解析)
{ | |
test: /\.(css|scss|sass)$/, | |
use: [ | |
"style-loader", | |
"css-loader", | |
{ | |
loader: "postcss-loader", | |
options: | |
{ | |
postcssOptions: | |
{ | |
plugins: ["autoprefixer"], }, | |
}, | |
}, | |
"sass-loader", | |
], | |
}, |
(3)在package.json新增browserslist配置
"browserslist": { "production": [ | |
">.2%", | |
"not dead", | |
"not op_mini all" | |
], | |
"development": [ | |
"last chrome version", | |
"last firefox version", | |
"last safari version" | |
] | |
} | |
在style.css中增加以下样式:
/* style.css */ | |
body { background: #; | |
} | |
#app div{ width:px; | |
margin-top:px; | |
transform: rotate(deg); /* 这个属性会产生浏览器内核前缀如 -webkit*/ | |
} |
重新编译,打开dist/index.html查看效果
5. babel-loader
作用:解析ES6,JSX
(1)安装
现在babel已到7的版本,使用@区分其他非官方包
Babel其实是几个模块化的包:
@babel/core:babel核心库
babel-loader:webpack的babel插件,让我们可以在webpack中运行babel
@babel/preset-env:将ES6转换为向后兼容的JavaScript
@babel/plugin-transform-runtime:处理async,await、import()等语法关键字的帮助函数
运行命令:
yarn add @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -D
(2)配置
//webpack.common.js | |
//在rules下增加配置 | |
{ test: /(\.jsx|\.js)$/, | |
use: ["babel-loader"], | |
exclude: /node_modules/, | |
}, |
(3)增加babel额外配置项
根目录新建.babelrc
{"presets": ["@babel/preset-env"], | |
"plugins": ["@babel/plugin-transform-runtime"] | |
} |
(4)注意点
babel-loader 8.x 对应@babel/core(7.x)
babel-loader 7.x 对应babel-core 6.x
(5)测试
public/index.html
使用es6的箭头语法
<button>按钮</button> | |
<script> | |
document.querySelector("button").onclick = () => { | |
console.log("es"); | |
}; | |
</script> |
重新编译,打开dist/index.html。通过点击可以看到信息被打印出来,说明es6解析成功
搭建环境
1. 开发环境与生产环境
build下新建webpack.dev.js,webpack.prod.js
(1)安装webpack-dev-server
yarn add webpack-dev-server -D
(2)安装webpack-merge
yarn add -D webpack-merge
(3)webpack.dev.js
const { merge } = require("webpack-merge"); | |
const common = require("./webpack.common.js"); | |
const path = require("path"); | |
module.exports = merge(common, {mode: "development", | |
devServer: { hot: true, //热更新 | |
open: true, //编译完自动打开浏览器 | |
compress: true, //开启gzip压缩 | |
port:, //开启端口号 | |
//托管的静态资源文件 | |
//可通过数组的方式托管多个静态资源文件 | |
static: { | |
directory: path.join(__dirname, "../public"), | |
}, | |
}, | |
}); |
(4)webpack.prod.js
const { merge } = require("webpack-merge"); | |
const common = require("./webpack.common.js"); | |
module.exports = merge(common, {mode: "production", | |
}); |
(5)修改package.json
"scripts": { "dev": "webpack serve --config build/webpack.dev.js", | |
"build": "webpack --config build/webpack.prod.js", | |
"test": "echo \"Error: no test specified\" && exit" | |
}, |
运行npm run dev查看效果,更改index.html内容,可以看到页面进行了热更新
(6)提示
在webpack:5.38.1版本中,如果使用postcss-loader,需配置browserslist,但是配置browserslist后会引起无法热更新的bug,所以还需增加 target:web(与devServer同级)
在webpack:5.54.0版本中,5.38版本的BUG已修复,无需再配置target:web。但引发了一个新问题,就是在Chrome浏览器中,如果不打开F12,更改文件内容则热更新(按需更新),但是打开F12控制台后,更改内容会导致每次更新都会重新刷新页面。在火狐浏览器,不管有没有打开F12,每次更新都会重新刷新页面
在webpack5.68.0版本中,上述问题已修复
2. 配置别名
resolve与entry同级:
//webpack.common.jsresolve: { | |
extensions: [".js", ".jsx", ".json", ".vue"], //省略文件后缀 | |
alias: { //配置别名 | |
"@": path.resolve(__dirname, "../src"), | |
}, | |
}, |
代码分离
1. webpack-bundle-analyzer
它将bundle内容展示为一个便捷的、交互式、可缩放的树状图形式。方便我们更直观了解代码的分离
(1)安装
yarn add webpack-bundle-analyzer -D
(2)配置
//webpack.prod.js | |
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; | |
plugins:[ new BundleAnalyzerPlugin() | |
] |
(3)运行 npm run build ,编译结束新开一个页面,可以看到bundle之间的关系
2. splitChunks(分离chunks)
作用:拆分chunk
//webpack.prod.js//与plugins同级 | |
optimization: { splitChunks: { | |
chunks: "all", | |
name: "vendor", | |
cacheGroups: { | |
"echarts.vendor": { | |
name: "echarts.vendor", | |
priority:, | |
test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/, | |
chunks: "all", | |
}, | |
lodash: { | |
name: "lodash", | |
chunks: "async", | |
test: /[\\/]node_modules[\\/]lodash[\\/]/, | |
priority:, | |
}, | |
"async-common": { | |
chunks: "async", | |
minChunks:, | |
name: "async-commons", | |
priority:, | |
}, | |
commons: { | |
name: "commons", | |
chunks: "all", | |
minChunks:, | |
priority:, | |
}, | |
}, | |
}, | |
}, |
(1)chunks:all / async
all:把动态和非动态模块同时进行优化打包,所有模块都扔到vendors.bundle.js里面
async:把动态模块打包进vender,非动态模块保持原样(不优化)
(2)cacheGroups(缓存组)
cacheGroups的作用是将chunks按照cacheGroups中给定的条件分组输出
(3)test
正则匹配,[\\/] 来表示路径分隔符的原因,是为了适配window与Linux系统。可通过(antd|@ant-design)匹配多个文件夹,达到将特定几个文件放入一个chunk中
(4)priority
优先级,默认组的优先级为负,自定义组的默认值为0
(5)非动态导入(直接通过import引入)
安装echarts
yarn add echarts -S
新建src/echart.js
import * as echarts from "echarts" | |
var myChart = echarts.init(document.getElementById('main')); | |
// 指定图表的配置项和数据 | |
var option = {title: { | |
text: 'ECharts 入门示例' | |
},tooltip: {}, | |
legend: { data: ['销量'] | |
},xAxis: { | |
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'] | |
},yAxis: {}, | |
series: [ { | |
name: '销量', | |
type: 'bar', | |
data: [, 20, 36, 10, 10, 20] | |
} | |
] | |
}; | |
// 使用刚指定的配置项和数据显示图表。 | |
myChart.setOption(option); |
在main.js中引入
import './echart'
修改public/index.html
<div id="main" style="width:px; height: 400px"></div>
运行npm run build,在dist下会发现新增了echarts.vendor.bundle.js。这就是通过splitChunks分离出来echarts包
3. 动态导入(按需加载chunks)
按需下载资源,如路由懒加载。可以提升首屏加载速度
(1)安装lodash
yarn add lodash -S
(2)通过import()语法实现动态导入
//在main.js添加 | |
function getComponent() {// Lodash, now imported by this script | |
return import("lodash") .then(({ default: _ }) => { | |
const element = document.createElement("div"); | |
element.innerHTML = _.join(["Hello", "webpack"], " "); | |
return element; | |
}) | |
.catch((error) => "An error occurred while loading the component"); | |
} | |
const button = document.createElement("button"); | |
button.innerHTML = "Click me "; | |
button.onclick = () => {getComponent().then((component) => { | |
document.body.appendChild(component); | |
}); | |
}; | |
document.body.appendChild(button); |
(3)在webpack.prod.js中cacheGroups下添加(在上面splitChunks中已经加过了)
lodash: {name: "lodash", | |
chunks: "async",test: /[\\/]node_modules[\\/]lodash[\\/]/, | |
priority:, | |
}, |
运行npm run build,只有点击按钮,lodash.bundle.js包才会被加载
4. mini-css-extract-plugin(分离css)
(1)安装
yarn add -D mini-css-extract-plugin
(2)配置
将webpack.common.js下面的代码剪贴到webpack.dev.js
//webpack.dev.js | |
//开发环境不需要样式分离module: { rules: [ { test: /\.(css|scss|sass)$/, use: [ "style-loader", "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: ["autoprefixer"], }, }, }, "sass-loader", ], }, ], }, |
修改webpack.prod.js
//webpack.prod.js | |
//生产环境进行样式分离 | |
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | |
module.exports={ plugins: [ | |
new MiniCssExtractPlugin({ | |
filename: "static/css/[name].[contenthash:].css", | |
}) | |
], | |
module: {rules: [ { test: /\.(css|scss|sass)$/, use: [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: ["autoprefixer"], }, }, }, "sass-loader", ], }, ], },} |
用MiniCssExtractPlugin.loader代替style-loader,配合MiniCssExtractPlugin使用。
(3)拓展:在webpack:5.38.1中,MiniCssExtractPlugin.loader需额外指定publicPath,来引用css资源。因为css样式分离到static/css文件夹下,会多两个层级目录,会使css中的背景图片路径不对。
在webpack:5.68.0中无需下面的配置了,路径问题已经帮我们解决了
//不再需要进行配置 | |
{ test: /\.(css|scss|sass)$/, | |
use: [{ | |
loader: MiniCssExtractPlugin.loader, | |
options: { | |
publicPath: '../../' | |
} | |
}, 'css-loader', 'sass-loader'] | |
} |
缓存
当把打包后dist目录部署到server上,浏览器就能够访问此server的网站及其资源。而获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为缓存的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然后,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。
所以我们需要将变动后的资源文件更改文件名,没有变动的资源(node_modules中的第三方文件)不更改包名称
1. contenthash
[contenthash]将根据资源内容创建出唯一hash。当资源内容发生变化时,[contenthash]也会发生变化。
//webpack.common.jsoutput: { | |
path: path.resolve(__dirname, "../dist"), | |
filename: "[name].[contenthash:].js", | |
clean: true, //每次构建清除dist包 | |
} |
定义全局环境变量
1. 定义编译时全局变量
(1)安装:
yarn add cross-env -D
(2)配置:
"scripts": { "dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js", | |
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js", | |
"test": "echo \"Error: no test specified\" && exit" | |
}, | |
//webpack.common.js | |
console.log('process.env.NODE_ENV',process.env.NODE_ENV) |
2. 定义编译后全局变量
通过DefinePlugin实现
新建 config/dev.env.js
module.exports = { NODE_ENV:'"development"', | |
} | |
//webpck.dev.js | |
const env = require("../config/dev.env"); | |
const webpack =require("webpack") | |
module.exports = merge(common,{plugins: [ | |
new webpack.DefinePlugin({ | |
"process.env": env, | |
}), | |
], | |
}) | |
//main.js | |
console.log(process.env) |
优化打包体积
1. 开启gzip压缩
(1)安装:
yarn add compression-webpack-plugin -D
(2)配置:
//webpack.prod.js | |
const CompressionPlugin = require("compression-webpack-plugin"); | |
module.exports = {plugins: [new CompressionPlugin()], | |
}; |
2.css-minimizer-webpack-plugin
优化和压缩CSS
(1)安装:
yarn add css-minimizer-webpack-plugin --save-dev
(2)配置:
//webpack.prod.js | |
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); | |
optimization: { minimizer: [`...`, new CssMinimizerPlugin()], | |
}, |
3. externals
防止将外部资源包打包到自己的bundle中
示例:从cdn引入jQuery,而不是把它打包
(1)index.html
<script src="https://code.jquery.com/jquery-.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous" ></script>
(2)webpack.common.js
module.exports = {//... | |
externals: { jquery: 'jQuery', | |
}, | |
}; |
(3)这样就剥离了那些不需要改动的依赖模块
import $ from 'jquery';
配置Vue
(1)安装:
yarn add -D vue-template-compiler@.6.14 vue-loader@15.9.8
注意 vue和vue-template-compiler版本号一定要一样,如果要更新vue,vue-template-compiler也要进行相应的更新
也要注意vue-loader的版本,这里使用vue2,安装15.9.8版本
vue-loader,用于解析.vue文件
vue-template-compiler,用于模板编译
(2)配置:
webpack.common.js
const {VueLoaderPlugin} = require('vue-loader'); // vue加载器 | |
module.exports={ module:{ | |
rules:[ | |
{ | |
test: /\.vue$/, | |
loader: 'vue-loader', | |
include: [path.resolve(__dirname, '../src')] | |
}, | |
] | |
}, | |
plugins:[ | |
new VueLoaderPlugin(), | |
] | |
} |
vue-loader要放在匹配规则的第一个,否则会报错
(3)配置externals
// index.html
<script src="https://cdn.bootcdn.net/ajax/libs/vue/.6.14/vue.min.js"></script> | |
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/.5.3/vue-router.min.js"></script> |
// webpack.common.js
externals: { 'vue': 'Vue', | |
'vue-router':'VueRouter' | |
} |
(4)使用
新建src/App.vue
<template> | |
<div class="app"> | |
<router-link to="/">home</router-link> | |
<router-link to="/about">about</router-link> | |
<router-view/> | |
</div> | |
</template> | |
<script>export default {name: "App"}</script> | |
<style scoped>.app {font-size:px; color: aquamarine;}</style> |
新建src/views/about.vue
<template> | |
<div> about页面 </div> | |
</template> |
新建src/views/home.vue
<template> | |
<div> Home页面 </div> | |
</template> |
新建router/index.js
Vue.use(VueRouter);const Home = () => import( /* webpackChunkName: "Home" */ '@/views/home.vue')const About = () => import( /* webpackChunkName: "About" */ '@/views/about.vue')const routes = [{path: '/', component: Home}, { path: '/about', component: About}]const router = new VueRouter({ routes})export default router
修改main.js
import App from './App.vue'; | |
import router from './router'; | |
Vue.config.productionTip = false; | |
new Vue({ router, | |
render: (h) => h(App) | |
}).$mount('#app'); |
重启项目,查看运行效果
配置React
安装其他所必须的babel外,还需安装@babel/preset-react
1. 安装 babel解析JSX
yarn add -D @babel/preset-react
2. 配置
//webpack.common.jsentry: { | |
main:path.resolve(__dirname, "../src/main.js"), //vue入口 | |
index:path.resolve(__dirname, "../src/index.js") //react入口 | |
}, | |
//.babelrc | |
{"presets": ["@babel/preset-env", "@babel/preset-react"], | |
"plugins": ["@babel/plugin-transform-runtime"] | |
} |
3. 安装react
yarn add react react-dom -S
4. 使用
修改index.html,增加
<div id="root"></div>
新建src/hello.js
import React, {Component} from 'react'; | |
let name = 'Alan'; | |
export default class Hello extends Component{ | |
render() { | |
return ( | |
{name} | |
); | |
} | |
} |
新建src/index.js
import React from 'react'; | |
import {render} from 'react-dom'; | |
import Hello from './hello'; // 可省略.js后缀名 | |
render(, document.getElementById('root')); |
重启项目,可看到运行结果