
webpack核心概念
entry(入口)
entry作用是在于指定一个项目的入口文件,从入口文件开始分析来构建其内部的依赖图,webpack会找到其依赖的模块和库并打包到指定的出口(output)文件中。
- 单个入口(多作用单页应用)
// 将src下的index.js文件及其依赖的模块和库打包到./dist目录下,生成entry.js文件
module.exports = {
entry: './src/index.js'
};
- 多个入口
应用场景:
1.分离 应用程序(app) 和 第三方库(vendor) 入口;
2.多页面应用
module.exports = {
vendor: ["jquery", "react","react-dom"],//多入口简写,数组写法
};
module.exports = {
page1: ./src/page1/index.js, // 对象写法
page2: ./src/page2/index.js,
page3: ./src/page3/index.js
};
output(出口)
output作用是指定接收webpack打包后的文件输出目录,以及如何命名这些文件。默认情况下,webpack会直接输出到./dist目录下。
module.exports ={
output:{
// 输出的文件名(指定名称+目录)
filename:js/[name].js
// 输出的文件目录
path:./dist
// 一般在生产环境使用,所有静态资源引入时的公共前缀
// 当设置为/时 静态资源路径为localhost:8080/index.js
// 当设置为/js/时 静态资源路径为localhost:8080/js/index.js
publicPath:'/',
// 场景:按需加载(异步)模块时候,指定生成的文件名
chunkFilename:'js/[name]_chunk.js' //非入口 chunk 的名称
library:'[name]' // 整个库向外暴露的的变量名,多用于自己造的轮子里
libraryTarget:'window||global|common.js|...' 是控制 webpack 打包的内容是如何暴露的
}
}
loader(解释器)
默认情况下,webpack只能处理js以及json格式的文件,为了能让wenpack处理其他类型格式的文件,需要指定对应的loader去做转换。
- 用法
module.export ={
module:{
rules[
// 存放详细的loader配置
// 不同的文件必须配置不同的loader处理
{
// 匹配那些文件 /\.js$/ 匹配以.js结尾的文件
test:/\.jsx$/,
// 指定loader 来处理jsx文件
loader:'babel-loader',
// 使用loader时的一些配置项
option:{}
},{
//如果一个文件要用多个loader处理
test:/\.css$/,
// use数组中loader执行顺序:从右到左
// 首先执行css-loader将css文件变成common.js模块加载js中,内容是样式字符串
// 接着执行style-loader 在js中创建style标签,将样式字符串插入到style标签中
use:['style-loader','css-loader']
}
]
}
}
plugins [插件]
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等。
- 用法
module.exports={
plugins:[
new HtmlWebpackPlugin({ template: './src/index.html' }),
]
}
mode (模式)
通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化
// 设置mode为‘development’webpack会自动启用以下功能
module.exports = {
mode: 'development'
// plugins: [
// new webpack.NamedModulesPlugin(), 开启 HMR 的时候使用该插件会显示模块的相对路径
// new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }), // 定义全局常量
// ]
}
// 设置mode为‘production’webpack会自动启用一下功能
module.exports = {
mode: 'development'
// plugins: [
// new UglifyJsPlugin(/* ... */), // 压缩js代码
// new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),// 定义全局常量
// new webpack.optimize.ModuleConcatenationPlugin(), // 提升js执行速度,主要作用es6模块
//当打包遇到错误时,会将错误页面打包出去。这时页面就会白屏,
//这个插件的作用就是跳过这个打包页面,将错误信息打包出去,错误信息只在控制台中输出
// new webpack.NoEmitOnErrorsPlugin()
// ]
}
如果配置loader来打包各种资源,为什么要这么配置?
- webpack默认只能打包js,json资源,如果打包其他类型的资源,就要添加loader来进行处理。
打包样式文件
打包css
module.export ={
module:{
rules[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
}
]
}
}
单独输出css
- 这样配置后的输入的文件将不会单独的css文件,所有的css样式都会打包到js文件中。
- 如果要将css文件单独打包出来,则需要
mini-css-extract-plugin插件的帮忙,具体使用方法如下
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.export ={
module:{
rules[
{
test:/\.css$/,
use:[
// 这个loader取代style-loader。作用:提取js中的css成单独文件
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test:/\.less$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
}
]
},
plugins:[
new MiniCssExtractPlugin({
// 对输出的css文件进行命名
filename: 'css/built.css'
})
]
}
处理css兼容
- 当项目里需要对css的兼容性做要求时,这时候就需要用到
postcss,它提供了一种方式用 JavaScript 代码来处理 CSS - postcss比较常用的两个功能介绍:
- Autoprefixer:基于当前浏览器支持的特性和属性数据去为你的css添加前缀
- postcss-cssnext: 可以使用一些css4语法,cssnext包含了
autopreafixer,所以autopreafixer和cssnext只安装一个,避免报错
- 可以在loader下的option中些postcss的配置,也可以目录下新建一个postcss.config.js进行配置
const autoprefixer = require('autoprefixer');
const postcssCssnext = require('postcss-cssnext');
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
{
loader:'postcss-loader',
options:{
// 固定写法
ident:'postcss',
// 这些plugins都需要用npm安装下
plugins: [
autoprefixer({
// 指定要兼容的浏览器
// 如果提示autoprefixer版本过高则替换成overrideBrowserslist
browsers:['last 10 Chrome versions', 'last 5 Firefox versions', 'Safari >= 6', 'ie> 8']
})
require('postcss-cssnext')(),
]
}
}
]
},
压缩css
- 这里用到的插件是
optimize-css-assets-webpack-plugin,也需要手动安装
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.export ={
plugins:[
// 用法很简单 直接在plugins中添加即可
new OptimizeCssAssetsWebpackPlugin()
]
}
打包js文件
处理js兼容 babel-loader
- 目前大都都是使用es6,es7的语法进行开发,这个时候就要用到babel处理我们的js文件
// 需要安装依赖 babel-loader @babel/core @babel/preset-env
module.export={
module:{
rules:[
{
test:/\.js$/,
// 不转换node_modules目录下的文件
exclude:/node_modules/,
loader:'babel-loader',
options:{
// 预设:指示babel做怎么样的兼容性处理
presets:[
'@babel/preset-env', // 按需加载 需要支持那些js新特性
]
}
}
]
}
}
- 上面的配置只是做了基本的js语法兼容,但是浏览器暂时还不能支持如Promise,Generator等全局对象,以及定义在这些全局对象的下方法。所以我们还得安装
@babel/polyfill这个包来做兼容 - 需要在入口文件中引入 '@babel/polyfill'
// index.js
import '@babel/polyfill';
const promise = new Promise(resolve=>{
setTimeout(()=>{
console.log('定时器执行完了~');
resolve();
})
)
conosle.log(promise);
- 首先打包下,发现在mode='production'情况下打包体积就已经有80多k了,这肯定不能接受,特别是在多页应用的时候,体积会更大。

- 所以还需引入
core-js,来做兼容处理的按需加载
// 需要安装依赖 core-js
module.export={
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
presets:[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
}
}
]
}
}
- 配置完之后,打包体积直接减少了4/3
- 当项目变大的时候,通过babel编译的时长就会变长。我们也可以在option下添加cacheDirectory:true,,开启缓存,下一次构建代码的时候会读取第一次的缓存

- 搭配ts 直接安装
typescriptts-loader,然后在rules里直接配置上ts-loader即可。
处理js语法检查 eslint-loader
// 需要安装依赖
// eslint eslint-loader
// eslint-config-airbnb-base airbnbd的eslint规则
// eslint-plugin-import // import语句规则
module.export={
module:{
rules:[
{
test:/\.js$/, // 如果用react的话可以直接指定 /\.js$/
exclude:/node_modules/,
loader:'eslint-loader',
// eslint-loader规则必须要先于babel-loader规则,
// 所以这里添加enforce:true预处理eslint-loader规则
enforce: 'pre'
options:{
// 自动修复eslint的错误
fix: true
}
}
]
}
}
// 在根目录下你新建一个.eslintrc.josn文件
{
"extends":"aib",
"env":{
"browser":true // 指定宿主环境
}
}
字体、图片静态资源打包
// 安装url-loader file-loader
// url-loader 可以将图片转成base64
// file-loader 可以打包图片,字体等一些静态资源
{
test:/\.(jpg|png|gif)/,
loader:'url-loader',
options: {
limit: 8 * 1024, // 如果小于8k 直接转base64
name: '[hash:6].[ext]', // 输出的文件名
outputPath: 'images', // 打包图片文件的输出目录
}
},{
// 字体文件直接用file-loader处理即可
}
html-webpack-plugin
- 这个plugins就是将打包后的样式
link插入到head元素中,script插入到head或者body中,并且生产html入口文件,单页面应用则配置单个html-webpack-plugin,多页面则配置多个。
安装 html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
plugins:[
// 当什么都不配置的时候,该插件还是会把打包后js,css插入到html中,并生产新的html入口文件
new HtmlWebpackPlugin({
// 介绍几个常用的配置
filename:'index.html' 输出的html文件名,默认为index.html
template:'./index.html' 指定模板html文件
chunks:['index'] 允许插入html的chunk,不配置默认会将entry里所有的chunk都注入模板中
// true:默认为true,表示script标签位于html文件的 body 底部
// body 同上
// head 所有JavaScript资源插入到head元素中
// false 所有css js不插入 单纯生成一个html文件
inject:true|body| head|false
// 压缩html文件
minify:{
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
// 去掉属性引用
removeAttributeQuotes:true
// 给生成的 html 文件生成一个 favicon。属性值为 favicon 文件所在的路径名
favicon
}
})
]
}
配置优化
HMR介绍
- HMR:hot module replacement 热模块替换
- 作用:主要用作开发环境:当我们项目过大的时候,每次修改文件重新编译的时长很长,影响开发效率,而启用HMR就可以做到一个模块发生变化,只会重新构建这一个模块,不会构建全部文件
安装依赖 webpack-dev-server
module.export={
mode:'development',
devServer:{
// 开启HMR功能
hot: true
}
}
oneOf
- 提高loader匹配效率,使用方法如下,匹配到一个loader后,后面的就不会再继续匹配了。可以理解为loader在匹配的时候用的时else if匹配,而oneof用的switch case来进行匹配。
module.export={
module:{
rules:{
oneOf:[
...loader
]
}
}
}
tree shaking
- 直译:树摇,这个功能就是像摇树木的时候,将坏掉的叶子也摇下来。换到tree shaking中来就是将js中无用或者没有使用过的代码给剔除掉。
- 例子
// a.js
export function add1(a, b) {
return a + b
}
export function add2(a, b) {
return a + b
}
// b.js
import {add2} from './a.js'
console.log(add2(1, 2))
- 如果使用tree shaking,add2函数将不会被打包进去。
- 有一个前提是,源码必须遵循 ES6 的模块规范 (import & export),如果是 CommonJS 规范 (require) 则无法使用。
- 开启mode='production'
- 注意:所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 package.json->sideeffect 列表中,以免在生产模式中无意中将它删除。
"sideEffects": ["*.css]
多进程构建
- 多进程构建常用的有两种
thread-loader和HappyPack,推荐使用thread-loader。多进程构建的应用场景是在项目过大,构建速度和打包速度较慢时使用,项目较小时不建议使用,因为开启多进程构建也会消耗一定时间和资源。
// 安装thread-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['thread-loader']
}
]
}
}
externals
- 当我们需要一个引入一个库,但是又不想被打包(节约打包时长),那么就可以配置externals。
- 举例 当我们引用了loadsh,并且这个库也不需要经常去更新。
module.exports ={
externals:{
// 配置完后,webpack在打包时碰到import _ from 'loadsh'时不会打包进bundle文件,而是会去script标签访问这个依赖
loadsh:'loadsh'
}
}
- 在入口的html文件中,添加script标签,引入loadsh库的cdn地址,就可以了。
DllPlugin
- 当项目偏大的时候,有很多不需要经常更新的库,我们可以把他们单独打包,这样就不用每次在打包的时候又要重新打包这些库。
- 首先创建webpack.dll.conf.js
// import webpack from 'webpack';
module.exports = {
mode:'production',
entry: {
vendor: ['react', 'react-dom','antd'],
},
output: {
path: path.join(__dirname, 'dist/dll'),
filename: '[name].dll.js',
library: '[name]'
},
plugins: [
// 打包生成一个 manifest.json --> 提供库和打包文件的映射
new webpack.DllPlugin({
path: path.join(__dirname, 'dist/[name]-manifest.json'),
name: '[name]'
}),
]
}
- 将
react react-dom antd打包出来之后,还需要用到DLLPlugin,将我们写的代码跟dll打包出来的代码完全分离,这样我们每次打包只需要打包自己写的代码了。
- webpack.config.js
import webpack from 'webpack';
module.exports = {
mode:'production',
entry: {
app: './src/index'
},
output: {
filename: 'index.js',
path: 'dist/',
},
plugins: [
// 告诉webpack哪些库不参与打包
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendors-manifest.json')
})
]
}
- 这样就完成了DllPlugin的配置,第一次运行时,先根据webpack.dll.js编译第三方库,然后运行webpack.config.js。
resolve
- 解析模块的规则
resolve: {
// 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
alias: {
@: resolve(__dirname, 'src')
},
// 配置省略文件路径的后缀名
extensions: ['.js', '.json', '.jsx'],
}
optimization
- 作用,提取公共代码,减少打包体积。应用场景:多页面应用。
- 一般来说,多页面应用各个页面都采用的相同的技术栈,如果每个页面都打包一遍这些库的代码,那么整个应用的体积就会变的很大。
- 如果我们将公共代码抽出来,这样不仅打包体积会减少,在用户切换不同页面时,公共代码的js也会直接从缓存中拿,加快了页面的响应速度
常用配置
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.export={
optimization: {
splitChunks: {
chunks: 'all' 三个可选值:initial(初始块)、async(按需加载块)、all(全部块),默认为all;
minSize: 30 * 1024, // 分割的chunk最小为30kb。默认为30k
maxSiza: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: {
common:{
chunks: 'initial',
name:'testCommon', // 打包后的文件名
minSize: 0,
minChunks: 2 // 重复2次才能打包到此模块
},
}*/
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
})
]
}
}
Hash Chunkhash Contenthash
Hash:
module.exports = {
mode:'production',
entry: {
main: './src/index'
},
output: {
filename: '[name].js',
path: 'dist/',
},
}
- 当我们修改完内容,按上面配置,打包出来的文件名称是main.js。当发布完之后,服务器会拉取main.js,这时浏览器发现跟上一个版本的main.js资源名一样,默认会采用之前缓存的main.js,这时我们修改的内容并没有被获取到。
module.exports = {
output: {
filename: '[hash].[name].js',
},
}

- 所以我们要在输出文件的名字前面加上hash值,打包之后文件名都是唯一的,这样就能解决文件被缓存的问题了。
Chunkhash
- 如果我们配置了多个入口文件或者配置其他资源时也使用hash做文件名输出,那么每次打包的时候都是一样的hash值

- 这时候修改一下main.js的文件内容,vendor.js的文件名的hash值也会发生改变,用户在请求js资源时,就会重新获取vendor.js。
- chunkhash是根据对应的chunk生产hash值,
[chunkhash].[name].js,这样当修改main.js时,vendor.js的hash值就不会发生改变。
Contenthash
- 如果在main.js里面
import './index.css',打包出单独的css文件,那么index.css和main.js的hash值都会改变,因为index.css被main.js应用了,公用一个hash值。 - 这个时候,配置
[Contenthash].[name].js,根据文件内容生产对应的hash值,index.css的hash不会发生改变
devServer
- devserver是配合webpack启动一个本地服务,能够让我们快速开发一个应用程序。
安装依赖webpack-dev-server
module.exports = {
mode:'development'
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
watchContentBase: true,
watchOptions: {
// 忽略文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 5000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器日志信息
clientLogLevel: 'none',
// 除了一些基本启动信息以外,其他内容都不要显示
quiet: true,
// 如果出错了,不要全屏提示~
overlay: false,
// 服务器代理 --> 解决开发环境跨域问题
proxy: {
// 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
'/api': {
target: 'http://localhost:3000',
// 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
总结
- 这篇实践心得是基础webpack4.x去做配置的,webpack配置并不复杂,最重要的是要去了解为什么这么配,如果不懂的可以简单配一下webpack,尝试那些方案更适合你。本文大部分都是开发中常用的解决方案,主要是对最近升级文webpack的一个小总结,如果能帮助到大家那就更好啦。