从零开始的Webpack4配置
作为一个前端工程师,不管怎么说,webpack配置还是要了解一下的。虽然一般情况下都用不上,但是人无我有,人有我优,是不是就能在市场有有一点优势了呢?学了webpack之后,以后要在项目中装其他乱七八糟的插件的时候也不会一脸懵逼,不知道这些东西怎么配置的,免得每次配东西都要百度。话不多说,开始学习吧!
这是根据知乎上看到的文章写的,我照着教程走了一遍,为了巩固学习成果,写篇文章记录一下。我学习过程中的代码都在这里。
webpack 的核心价值就是前端源码的打包,即将前端源码中每一个文件(无论任何类型)都当做一个 pack ,
然后分析依赖,将其最终打包出线上运行的代码。webpack 的四个核心部分
- entry 规定入口文件,一个或者多个
- output 规定输出文件的位置
- loader 各个类型的转换工具
- plugin 打包过程中各种自定义功能的插件
基础配置
初始化环境
首先创建一个文件夹my_webpack,然后npm init -y,-y 命令表示在你初始化的时候所有的选择yes or no的选项全部选yes,也就是说全部使用默认配置来初始化。然后安装webpack,npm i webpack webpack-cli -D,-D表示依赖安装到开发环境,生产环境中不会用到,对应到package.json文件的话,就是会添加到devDependencies下面。
然后创建src目录,在src目录下创建index.js文件,里面随便写点console.log('webpack start')。然后在根目录下创建webpack.config.js,内容如下:
// path模块是node.js中提供用于处理文件路径和目录路径的实用工具
const path = require('path')
module.exports = {
// mode 可选 development 或 production ,默认为后者
// production 会默认压缩代码并进行其他优化(如 tree shaking)
mode: 'development',
// __dirname就是src目录之前的目录结构,是根据文件地址自动生成的
// path.join()就是把参数拼接成目录结构,如以下的entry值就是 __dirname/src/index.js
// path的具体API可以参考node.js
entry: path.join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.js', // 表示打包出来的压缩文件名
// 表示打包出来的文件放在那个目录下面,这里指: __dirname/dist/ 下面
path: path.join(__dirname, 'dist')
}
}
然后在package.json里的scripts里添加打包命令
"scripts": {
"build": "webpack"
},
然后运行npm run build命令,就可以看到打包文件放在dist目录下面了。
区分dev与build
使用 webpack 需要两个最基本的功能:第一,开发的代码运行一下看看是否有效;第二,开发完毕了将代码打包出来。这两个操作的需求、配置都是完全不一样的。例如,运行代码时不需要压缩以便 debug ,而打包代码时就需要压缩以减少文件体积。因此,这里我们还是先把两者分开,方便接下来各个步骤的讲解。
这里先看看path.join()方法的执行结果,以便接下来的理解

首先,安装 npm i webpack-merge -D ,那么webpack-merge是干什么的呢?
// webpack-merge做了两件事:它允许连接数组并合并对象,而不是覆盖组合
const merge = require("webpack-merge");
merge(
{a : [1],b:5,c:20},
{a : [2],b:10, d: 421}
)
//合并后的结果
{a : [1,2] ,b :10 , c : 20, d : 421}
然后根目录新建 build 目录,其中新建如下三个文件。
// webpack.common.js 开发和成产环境都用的到的公共配置
const path = require('path')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
module.exports = {
entry: path.join(srcPath, 'index') // 就是指的 src/index.js
}
// webpack.dev.js 运行代码的配置(该文件暂时用不到,先创建了,下文会用到)
const path = require('path')
const webpackCommonConf = require('./webpack.common.js') // 引入公共配置
const { smart } = require('webpack-merge')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
// 那么这里使用 webpack-merge 的方法就可以把公共配置与开发环境的配置合并在一起了
module.exports = smart(webpackCommonConf, {
mode: 'development'
})
// webpack.prod.js 打包代码的配置
const path = require('path')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
// 那么这里使用 webpack-merge 的方法就可以把公共配置与生产环境的配置合并在一起了
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
// contentHash指哈希码,这里 :8指的是取哈希码的前8位
filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
}
})
修改 package.json 中的 scripts:
"scripts": {
/* 表示打包时要用到webpack.prod.js中的配置 */
"build": "webpack --config build/webpack.prod.js"
},
重新运行 npm run build 即可看到打包出来的代码。最后,别忘了将根目录下的 webpack.config.js 删除。
这将引发一个新的问题:js 代码中将如何判断是什么环境呢?需要借助 webpack.DefinedPlugin 插件来定义全局变量。可以在 webpack.dev.js 和 webpack.prod.js 中做如下配置:
// 引入 webpack
const webpack = require('webpack')
// 增加 webpack 配置
plugins: [
new webpack.DefinePlugin({
// 注意:此处 webpack.dev.js 中写 'development' ,webpack.prod.js 中写 'production'
ENV: JSON.stringify('development')
})
]
最后,修改 src/index.js 只需加入一行 console.log(ENV) ,然后重启 npm run dev 即可看到效果。
JS 模块化
webpack 默认支持 js 各种模块化,如常见的 commonJS 和 ES6 Module 。但是推荐使用 ES6 Module ,因为 production 模式下,ES6 Module 会默认触发 tree shaking ,而 commonJS 则没有这个福利。究其原因,ES6 Module 是静态引用,在编译时即可确定依赖关系,而 commonJS 是动态引用。
es6语法不熟的可以参考这里。
启动本地服务
上文创建的 webpack.dev.js 一直没使用,下面就要用起来。
使用 html
启动本地服务,肯定需要一个 html 页面作为载体,新建一个 src/index.html 并初始化内容。
<!DOCTYPE html>
<html>
<head><title>Document</title></head>
<body>
<p>this is index html</p>
</body>
</html>
要使用这个 html 文件,还需要安装 npm i html-webpack-plugin -D ,然后配置 build/webpack.common.js ,因为无论 dev 还是 prod 都需要打包 html 文件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html'
})
]
重新运行 npm run build 会发现打包出来了 dist/index.html ,且内部已经自动插入了打包的 js 文件。
webpack-dev-server
有了 html 和 js 文件,就可以启动服务了。首先安装 npm i webpack-dev-server -D ,然后打开 build/webpack.dev.js配置。只有运行代码才需要本地 server ,打包代码时不需要。
// 启动本地服务器需要安装webpack-dev-server模块,然后在开发模式下配置devServer,生产模式下不需要
devServer: {
port: 3001, // 端口号
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动gzip压缩
},
打开 package.json 修改 scripts ,增加 "dev": "webpack-dev-server --config build/webpack.dev.js", 。然后运行 npm run dev ,打开浏览器访问 localhost:3001 即可看到效果。
解决跨域
实际开发中,server 端提供的端口地址和前端可能不同,导致 ajax 收到跨域限制。使用 webpack-dev-server 可配置代理,解决跨域问题。如有需要,在 build/webpack.dev.js 中增加如下配置。
devServer: {
proxy: {
// 将本地 /api/xxx 代理到localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
changeOrigin: true, // 允许跨域
pathRewrite: {
'/api2': ''
}
}
}
},
处理 ES6
使用 babel
由于现在浏览器还不能保证完全支持 ES6 ,将 ES6 编译为 ES5 ,需要借助 babel 这个神器。安装 babel npm i babel-loader @babel/core @babel/preset-env -D ,然后修改 build/webpack.common.js 配置
module: {
rules: [
{
test: /\.js$/, // 正则匹配 .js文件
loader: ['babel-loader'], // babel-loader转换es6代码
include: srcPath, // 检测的js文件位于这里
exclude: /node_modules/ // 这里的js文件不会被检测到
},
]
},
还要根目录下新建一个 .babelrc json 文件,内容下:
{
"presets": ["@babel/preset-env"],
"plugins": []
}
在 src/index.js 中加入一行 ES6 代码,如箭头函数 const fn = () => { console.log('this is fn') } 。然后重新运行 npm run dev,可以看到浏览器中加载的 js 中,这个函数已经被编译为 function 形式。
使用高级特性
babel 可以解析 ES6 大部分语法特性,但是无法解析 class 、静态属性、块级作用域,还有很多大于 ES6 版本的语法特性,如装饰器。因此,想要把日常开发中的 ES6 代码全部转换为 ES5 ,还需要借助很多 babel 插件。
安装 npm i @babel/plugin-proposal-class-properties @babel/plugin-transform-block-scoping @babel/plugin-transform-classes -D ,然后配置 .babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes"
]
}
在 src/index.js 中新增一段 class 代码,然后重新运行 npm run build ,打包出来的代码会将 class 转换为 function 形式。
source map
source map 用于反解析压缩代码中错误的行列信息,dev 时代码没有压缩,用不到 source map ,因此要配置 build/webpack.prod.js
module.exports = smart(webpackCommonConf,{
// 生产环境下推荐使用1或3,生成独立的map文件
// source map 用于反解析压缩代码中错误的行列信息,dev时代码没有压缩,所以用不到source map,所以用在prod中
// devtool: 'source-map', // 1、生成独立的source-map文件
// devtool: 'eval-source-map', // 2、不会生成独立的文件,集成到打包出来的js文件中
// devtool: 'cheap-moudle-source-map', // 3、生成单独地souce map文件,但没有列信息(因为文件体积较小)
devtool: 'cheap-module-eval-source-map', // 4、同3,但不会生成独立的文件,集成到打包出来的js文件中
}}
生产环境下推荐使用 1 或者 3 ,即生成独立的 map 文件。修改之后,重新运行 npm run build ,会看到打包出来了 map 文件。
处理样式
处理 css
安装必要插件 npm i style-loader css-loader -D ,然后配置 build/webpack.common.js
module: {
rules: [
{ /* js loader */ },
{
test: /\.css$/, // 正则匹配 .css文件
loader: ['style-loader', 'css-loader'] // loader 的执行顺序是:从后往前
}
]
},
新建一个 css 文件,然后引入到 src/index.js 中 import './css/index.css' ,重新运行 npm run dev 即可看到效果。
处理 less
less sass 都是常用 css 预处理语言,以 less 为例讲解。安装必要插件 npm i less less-loader -D ,然后配置 build/webpack.common.js
module: {
rules: [
{ /* js loader */ },
{
test: /\.(css|less)$/, // 可以同时匹配 .css和 .less文件
loader: ['style-loader', 'css-loader', 'less-loader'] // 增加 'less-loader' ,注意顺序
}
]
},
新建一个 less 文件,然后引入到 src/index.js 中 import './css/index.less' ,重新运行 npm run dev 即可看到效果。
自动添加前缀
一些 css3 的语法,例如 transform: rotate(45deg); 为了浏览器兼容性需要加一些前缀,如 webkit- ,可以通过 webpack 来自动添加。安装 npm i postcss-loader autoprefixer -D ,然后配置build/webpack.common.js
module: {
rules: [
{ /* js loader */ },
{
// 添加css、less转换器,postcss-loader用于给css属性加浏览器兼容前缀,如webkit-,此外还需要创建postcss.config.js文件
test: /\.(css|less)$/,
loader: ['style-loader','css-loader','less-loader','postcss-loader'] // loader的执行顺序是从后往前
}
]
},
还要新建一个 postcss.config.js 文件,内容是
module.exports = {
plugins: [require('autoprefixer')]
}
重新运行 npm run dev 即可看到效果,自动增加了必要的前缀。
抽离 css 文件
默认情况下,webpack 会将 css 代码全部写入到 html 的 <style> 标签中,但是打包代码时需要抽离到单独的 css 文件中。安装 npm i mini-css-extract-plugin -D 然后配置 build/webpack.prod.js(打包代码时才需要,运行时不需要)
// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 增加 webpack 配置
module: {
rules: [
{
test: /\.(css|less)$/, // 需要抽离的样式文件
loader: [
// MiniCssExtractPlugin用于在打包时将css抽离到单独的css文件中
MiniCssExtractPlugin.loader, // 这里不再使用style-loader
'css-loader',
'less-loader',
'postcss-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
]
如需要压缩 css ,需要安装 npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D ,然后增加配置build/webpack.prod.js
// 引入插件
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// 增加 webpack 配置
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
运行 npm run build 即可看到打包出来的 css 是独立的文件,并且是被压缩过的。
处理图片
要在 js 中 import 图片,或者在 css 中设置背景图片。安装 npm i file-loader -D 然后配置 build/webpack.common.js
module: {
rules: [{
// 用来处理在js中import图片,在css中使用背景图片的操作
test: /\.(png|jpg|gif|PNG|JPG|GIF)$/,
use: 'file-loader'
}]
}
打包之后,dist 目录下会生成一个类似 917bb63ba2e14fc4aa4170a8a702d9f8.jpg 的文件,并被引入到打包出来的结果中。
多页应用
src 下有 index.js index.html 和 other.js other.html ,要打包输出两个页面,且分别引用各自的 js 文件。
第一,配置输入输出
// 在webpack.common.js中配置入口文件
// 多页应用
entry: {
index: path.join(srcPath,'index.js'),
other: path.join(srcPath,'other.js')
},
// 在webpack.prod.js文件中配置出口文件
output: {
// filename: 'bundle.[contentHash:8].js', //打包代码时,加上hash戳
filename: '[name].[contentHash:8].js', // 多页应用时打包出来的文件名不一样
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件url的前缀
},
第二,配置 html 插件
plugins: [
// 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index'] // 只引用 index.js
}),
// 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other'] // 只引用 other.js
}),
]
抽离公共代码
多个页面或者入口,如果引用了同一段代码,如上文的多页面例子中,index.js 和 other.js 都引用了 import './common.js' ,则 common.js 应该被作为公共模块打包。webpack v4 开始弃用了 commonChunkPlugin 改用 splitChunks ,可修改 build/webpack.prod.js 中的配置
module.exports = smart(webpackCommonConf,{
optimization: {
// 分割代码块
splitChunks: {
// 缓存分组
cacheGroups: {
// 公共的模块
common: {
chunks: 'initial',
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次
}
}
}
},
})
重新运行 npm run build ,即可看到有 common 模块被单独打包出来,就是 common.js 的内容。
第三方模块
同理,如果我们的代码中引用了 jquery lodash 等,也希望将第三方模块单独打包,和自己开发的业务代码分开。这样每次重新上线时,第三方模块的代码就可以借助浏览器缓存,提高用户访问网页的效率。修改配置文件,增加下面的 vendor: {...} 配置。
optimization: {
// 此配置用于打包时压缩css文件
minimizer: [new TerserWebpackPlugin({}), new OptimizeCssAssetsWebpackPlugin({})],
// 分割代码块,用于抽离公共代码
splitChunks: {
// 缓存分组
cacheGroups: {
// 第三方模块
vendor:{
priority: 1, // 权限更高,优先抽离,很重要!!
test: /node_modules/,
chunks: 'initial',
minSize: 0, // 大小限制
minChunks: 1 // 最少复用过几次
},
// 公用的模块
common: {
chunks: 'initial',
minSize: 0, // 公共模块的大小限制
minChunks: 2, // 公共模块最少复用过几次
}
}
}
}
重启 npm run build ,即可看到 vendor 模块被打包出来,里面是 jquery 或者 lodash 等第三方模块的内容。
懒加载
webpack 支持使用 import(...) 语法进行资源懒加载。安装 npm i @babel/plugin-syntax-dynamic-import -D 然后将插件配置到 .babelrc 中。
新建 src/dynamic-data.js 用于测试,内容是 export default { message: 'this is dynamic' } 。然后在 src/index.js 中加入
setTimeout(() => {
import('./dynamic-data.js').then(res => {
console.log(res.default.message) // 注意这里的 default
})
}, 1500)
重新运行 npm run dev 刷新页面,可以看到 1.5s 之后打印出 this is dynamic 。而且,dynamic-data.js 也是 1.5s 之后被加载进浏览器的 —— 懒加载,虽然文件名变了。
重新运行 npm run build 也可以看到 dynamic-data.js 的内容被打包一个单独的文件中。
本文详细介绍Webpack4的配置流程,包括环境初始化、模块化、本地服务启动、ES6转换、sourcemap生成、样式处理、图片资源管理、多页应用支持、公共代码抽离及懒加载等功能。
2万+

被折叠的 条评论
为什么被折叠?



