webpack配置从初级到高级进阶路(附源码哦)
webpack配置源码
git上的源码是配合本篇文章实现webpack的基础配置源码,快来看呀~~~
前言
webpack 对于现在的前端开发息息相关,或许没有真正去了解过它是如何使用的,但你的项目中确实使用过它,因为项目的打包编译都跟他息息相关
来吧,跟我一起花几分钟系统的学习回顾一下webpack相关知识
一、webpack是什么
官方文档 这么说:
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
其实,它就是一个模块化打包工具,它所做的事情就是分析你的项目结构,找到JavaScript模块并对其进行代码转换(scss 转换为css 、Typescript 转换为 Javscript),文件优化(压缩JavaScript、CSS、HTML、图片等),模块合并 (把模块分类合并成一个文件)等一系列的操作,最终打包为合适的格式在让你的项目可以在浏览器中运行
其主要工作原理就如下图所示:
二、webpack核心概念
在真正上手 webpack 之前,我们需要对其几个 核心概念 有所了解
(1)Entry:入口路径,webpack执行构建的第一步的输入文件。
(2)Module:模块,在 webpack 里一切皆模块,一个模块对应着一个文件,webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
(3)Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
(4)Loader:模块转换器,用于把模块原内容按照需求转换成新内容,例如scss到css的转换等,构建模块的第一步就是使用loader来加载并处理模块内容
(5)Plugin:扩展插件,webpack 的重要组成部分,其 webpack 源码的很多核心功能也都是插件实现的,由于webpack提供了在不同的生命周期内提供了很多不同的hooks钩子函数,插件的工作原理就是在 webpack 构建流程中的不同的(hooks)时机注入扩展逻辑来改变构建结果或做个性化操作。
(6)Output:输出结果,在 webpack 经过一系列处理后最终代码后输出配置。
(7)小结:
整个流程串起来大概就是:
webpack启动后会从Entry里配置的Module开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module,就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。
这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个Chunk。最后 webpack 会把所有 Chunk 转换成文件输出,在这整个流程中 webpack 会在不同的生命周期内执行配置的 Plugin 里定义的逻辑。
了解了上面的一些概念和流程之后,接下来,我们一步步来配置,打包我们的项目~**
三、webpack配置详细步骤
1.创建文件夹
首先,我们创建一个你喜欢的文件夹,并通过 npm init -y 来初始化项目配置,并在其目录下创建一个我们源代码的目录src和一个打包后的文件输入目录dist。
2.创建入口文件 index.js
然后我们在 src 目录下创建一个 index.js 文件,作为我们的打包入口文件,并写上我们熟悉的 console.log(‘hello world’); 作为打包内容。
3.安装webpack
webpack4 之后将 webpack 和 cli 分成了两个包,我们需要通过 npm install webpack webpack-cli -D 安装我们所需的 webpack 依赖。
注意:目前还是不能直接进行打包,因为我们的 webpack 是在项目下安装的,所以不能直接运行,想要正确运行webpakc我们可以有下面2种方式:
使用 npx 命令,可以直接运行 node_modules/.bin 目录下的命令
通过配置 package.json 中的 scripts 脚本命令进行执行
为了贴近我们的项目,这里我们选择第二种方式
4.配置package.json脚本
"dev" : "webpack --mode development",
"build" : "webpack --mode production",
dev 表示开发模式,build 是生产模式,不同在于 dev 会有很多方便我们开发调试的功能,比如代码不压缩混淆,有开发服务器之类的,我们学习阶段采用 dev 即可~
5.创建配置文件
这时候,我们已经可以通过 npm run dev 命令打包我们的代码,并在dist目录下看到我们打包后的 main.js 文件了~
到此,我们可能发现自己什么都还没配置,咋就可以打包了!!
这是因为 webapck4 为了简化我们开发人员繁琐的配置工作,在内部写好了一套配置
哇哦~~~
不行不行,那我们还学习什么呢?
要想加载自己的配置,我们需要在我们的项目根目录下创建 webpack.config.js 文件,并创建我们的基础配置。
//path
const path = require('path');
//配置信息
module.exports = {
//入口文件
entry : './src/index.js' ,
//出口文件
output : {
//打包后路径,只能写绝对路径
path : path.resolve(__dirname,'dist'),
//打包后文件名
filename : '[name].[hash].js'
},
//模块转换规则
module : {
},
//插件
plugins : [
],
//开发服务器
devServer : {
}
}
我们注意到 output 中的 filename 中有一个 [name],[hash],其中name是 entry 的名字,默认是 main ,hash 是打包根据内容后计算出的一个 hash值,保证文件的唯一性,可以通过[hash:8] 表示取其前8位。
现在运行 npm run dev 在 dist 目录下就会打包出类似 main.47bfc309d4ba9b75d346.js 的文件。
这里,我们为了方便,先将其改成 main.js
6.创建index.html文件
为了方便我们在浏览器测试,我们在我们的 dist 目录创建一个 index.html ,并引入我们编译好的main.js文件,如下:
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack</title>
</head>
<body>
<div id="app">webpack</div>
<script src="main.js"></script>
</body>
</html>
我们在 index.js 文件下加上这么一句话:
document.querySelector(’#app’).style.color=‘red’;
测试一下打包后的文件好不好使。
运行 npm run dev,打开我们的 index.html 预览,不出意外的话,结果应该和我一样,页面的 webpack 文字变红,控制台输出 hello world;
至此,我们才将我们的项目基础配置搞定~
7.加载css
如果我们现在想加入 css 文件,优化我的样式,首先在 src 目录下创建 stylesheets 目录,并添加 index.css 文件,我们就先以 body { background : #f2f2f2; } 为例,之后在 index.js 中通过 import ‘./stylesheets/index.css’ 的方式导入样式文件。
如果现在我们直接进行打包肯定会报错,因为 css 文件并不是 js 模块,webpack 在打包的时候没法直接处理,需要借助转换工具,这转换工具就是 Loader。
什么是 Loader ?
Loader 就是文件转换器,通过使用不同的Loader,webpack就可以把不同的文件都转成js文件,比如CSS、ES6/7、JSX等,它通常有下面几个配置项:
test:匹配处理文件的扩展名的正则表达式
use:loader名称,就是你要使用模块的名称
include/exclude:手动指定必须处理的文件夹或屏蔽不需要处理的文件夹
query:为loaders提供额外的设置选项
接下来,我们来使用 style-loader,css-loader 来处理我们的 css 文件。
执行 npm install style-loader css-loader -D 进行安装 loader
在 webpack.config.js 中的module下面添加解析规则:
module: {
rules: [{
test: /\.css/,
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
use: [{
loader: 'style-loader',
options: {
insert:'top'
}
},'css-loader']
}]
}
要注意的是配置多个 loader 是 有顺序 的,webpack 会安装配置的 loader 顺序 从右向左 执行的,配置的时候要格外注意!
拿我们上面的 style-loader 和 css-loader 来说,两个loader配置的顺序不可调换,因为 css-loader是解析处理css里的url路径,并将css文件转换成一个模块,style-loader是 将css文件变成style标签插入到head中的。
现在我们 npm run dev 试试,打包没有报错,预览 index.html 也成功生效 !
8.配置开发服务器
到此我们会发现,我们在平时的开发过程中,怎么没有遇到说让我每次通过预览dist下的 index.html来看我们打包效果的,这是由于我们平时的开发中会在自己本地起一个服务器,来帮我们做这件事。
现在,我们也来试试~
通过 npm i webpack-dev-server –D 安装我们的开发服务器依赖
修改我们的启动脚本,将原来的 package.json 中的 dev 脚本修改为 webpack-dev-server --open ,其中 --open 是自动打开浏览器的意思
此时,直接运行 npm run dev 是看不到我们预期的效果的,因为我们还没有对我们的服务器进行配置。
我们在 webpack.config.js 的 devServer 下添加如下配置:
devServer : {
contentBase : path.resolve(__dirname,'dist'),
host : 'localhost' ,
port : 8000 ,
compress : true
}
contentBase 配置开发服务运行时的文件根目录,也就是静态资源访问地址
host 开发服务器监听的主机地址
port 开发服务器监听的端口,默认是 8080
compress 开发服务器是否启动gzip等压缩
要注意的是,webpack dev server 产生的打包文件是在 内存中!硬盘是访问不到的,怎么验证这一点呢?
很简单,你可以删除掉你dist目录下的 main.js ,重新运行 npm run dev ,你可以看到 dist 目录下并没有 main.js,但你访问 localhost:8000 确实能正确访问,并且访问 http://localhost:8000/main.js 也能看到打包后的源码,证明它产生的打包文件确实是在你的 内存中。
注意,我们这里打包出来的只有一个 main.js 文件和一个index.css文件,index.html 文件是我们手动添加进去的,不是打包产生的,如果删掉 index.html 是无法正确访问页面的,内存里可没有 index.html 文件。
9.自动生产HTML文件
我们之前是在 dist 目录下写好了 index.html 文件,并在里面通过 script 标签引入我们打包后的内容,即 main.js ~
还记得我们之前打包后的 filename 中可以加入一个 hash 值也区别不同的文件,如果 hash 值发生变化了,我们的 index.html 岂不是找不到资源了?所以我们希望自动能产出HTML文件,并在里面引入产出后的资源,这样就不必为上面的问题担心了。
我们删除掉 dist 目录下的 index.html 文件,并在 src 目录下创建一个 index.html 文件,我们称它为模板,以它为模板产生 html 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>webpack</title>
</head>
<body>
<div id="app">webpack</div>
</body>
</html>
这时,我们需要接触到第一个插件 html-webpack-plugin!
之前也说了,插件在 webpack 中有很重要的作用,在 webpack 的构建流程中,plugin 用于处理更多其他的一些构建任务,模块代码转换的工作由 loader 来处理,除此之外的其他任何工作都可以交由 plugin 来完成。
首先我们需要通过 npm install html-webpack-plugin -D 去安装它。
然后在 webpack.config.js 中 plugins 下去配置它(需要先通过 require 引入)
//自动产出HTML模版
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
hash: true,
minify: {
removeAttributeQuotes: true,
removeComments: true
}
})
其中 template 是指定模板文件,filename 是指定产出后的文件名,hash 是为了避免缓存,可以在产出的资源后面添加hash值,minify 是 压缩相关的配置,minify.removeAttributeQuotes 是为了去掉属性的双引号,minify.removeComments 是为了压缩文件,删除模板中的注释。
此时,我们运行 npm run dev 访问 localhost:8000 发现已经可以正常访问了,但是 dist 目录下却没有任何东西,因为我们之前提到了,开发服务器打包的文件是写入内存中的,不是硬盘里。
为了方便我们看效果,我们在 package.json 中的 scripts 中在增加一行 “dev-build”: “webpack --mode development” 用来打包我们开发环境的代码。
运行 npm run dev-build 脚本,完成之后可以发现 dist 目录已经打包出来了 index.html 文件 和 main.[hash].js 文件,打开 index.html 文件可以发现标签的双引号已经没去掉了,并且引入的脚本也自动加上了 ?[hash] 值。
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=UTF-8>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Title</title>
</head>
<body>
<div id=app>webpack</div>
<script type=text/javascript src=main.e6570abab6c2814d1608.js?e6570abab6c2814d1608></script></body>
</html>
预览 index.html 文件,一切也都正常。
10.支持图片
前端项目中肯定少不了图片资源,如果我们直接在 css 或者 js 文件中引入图片资源去打包,那么肯定是通不过了,因为 webpack 无法识别图片资源,因为图片也不是一个有效的模块。
此时,我们需要引入这个两个 loader 去解决它, file-loader url-loader,file-loader 解决CSS等文件中的引入图片路径问题,url-loader 当图片小于limit的时候会把图片base64编码,大于limit参数的时候还是使用file-loader 进行拷贝
通过 npm install file-loader url-loader -D 下载依赖
在src目录下创建一个 images 文件夹,存放几张你喜欢的图片
在 index.js 和 index.css 中引入图片,并将其插入到页面中去(我这里以本地的 avatar.jpg 和 scene.jpg 为例)
//index.js
import Avatar from './images/avatar.jpg'
let img = new Image();
img.src = Avatar;
document.body.appendChild(img);
//index.css
body{
background-color: #f2f2f2;
background-image: url("../images/scene.jpg");
background-repeat: no-repeat;
}
在webpack.config.js中的module下面添加如下配置:
{
test:/\.(png|jpg|gif|svg|bmp)$/,
use:{
loader: 'url-loader',
options: {
limit: 10 * 1024,
outputPath: 'images/'
}
}
}
options 中的 limit 就是图片的限制,这里我指定的是 10kb ,小于 10kb 的图片会以 base64 编码输出,outputPath 指定了拷贝文件输出目录,默认是dist目录下。
11.编译less 和 sass
现在开发过程中,为了简化我们书写 css 的过程,我们一般项目中引入了 less 和 sass 这样的预加载器~
同样的你不处理之前 webpack 是不认识 less 和 sass 文件的,毕竟它不是一个 js 模块,我们通过需要借助 less-loader 和 sass-loader 来处理这些文件~
通过运行 npm install less less-loader -D npm install node-sass sass-loader -D 分别安装 loader
在 webpack.config.js中添加loader解析规则
{
test: /\.less/,
use: ['style-loader', 'css-loader', 'less-loader']
}, {
test: /\.scss/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
这样,就可以放心在你的项目里引入 css 预处理了~
12.分离css
因为CSS的下载和js可以并行,当一个HTML文件很大的时候,那么页面加载肯定会非常慢,那么我们希望可以把CSS单独提取出来加载,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,按需加载。
我们需要 mini-css-extract-plugin 这么一个插件
通过 npm install mini-css-extract-plugin -D 安装模块依赖
在 webpack.config.js 中 plugins 下去配置一下
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: "css/[id].css"
})
除此之外,我们的还需要做一个操作就是将我们之前处理css的 style-loader 改写成下面这种形式:
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader
}, 'css-loader']
},{
test: /\.less/,
use: [{
loader: MiniCssExtractPlugin.loader
}, 'css-loader', 'less-loader']
}, {
test: /\.scss/,
use: [{
loader: MiniCssExtractPlugin.loader
}, 'css-loader', 'sass-loader']
}
npm run dev-build 进行打包~
预览 index.html 页面,打开控制台network可以发现下载文件的时候多出了一个 main.css 文件,并且我们 html 的 head 头,已经换成了 link 方式,并且你的 dist 目录下会多出一个 css 的文件夹,里面存放打包后的 css 文件。
tips: 如果你在外部的css 文件中文件中引入图片,而图片放在了images目录下,那么打包上线后的图片会出现 404 的情况,你可以查看打包后的 css 文件,就可以反向是路径的问题,需要配置一下 publicPath 即可~
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '/'
}
}
注意,这个一般是在 服务器 上会出现,本地打包后没有起服务也是看不到的。
13.处理CSS3属性前缀
我们在书写 css 的时候,为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些浏览器前缀,但我们又不想去书写这些繁琐的东西,这时候可以交给我们 postcss 来处理~
postcss的主要功能只有两个:
第一个就是前面提到的把 CSS 解析成 JavaScript 可以操作的 抽象语法树结构(Abstract Syntax Tree,AST)
第二个就是调用插件来处理 AST 并得到结果
我们首先通过 npm install postcss-loader autoprefixer -D 来安装依赖
想要这个 postcss 正常工作,我们需要配置两个东西
在根目录下创建 postcss.config.js 配置文件,表示使用 autoprefixer 来进行补全
module.exports={
plugins:[require('autoprefixer')]
}
在 webpack.config.js 中修改并添加对css处理的 loader
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader
}, 'css-loader', 'postcss-loader'],
include: path.join(__dirname, './src'),
exclude: /node_modules/
}
14.转义ES6/ES7
虽然 es6 和 es7 的代码我们已经或多或少都在项目中,但是大部分浏览器对于 es6 和 es7 代码的支持度并不高,很大部分的浏览器还是只能解析 es5 的代码,为了让我们能正常使用 es6、es7的代码,我们需要借助 webpack 对其进行转义,转成 es5 代码。
我们需要借助 babel 这个工具,它是一个编译JavaScript的平台,可以把ES6/ES7的代码转义为ES5代码。
通过 npm install babel-loader @babel/core @babel/preset-env -D 安装依赖
在 webpack.config.js 中添加 babel-loader ,因为 babel-loader只是告诉了 webpack 怎么处理 ES6,ES7 代码,但它并不会将 ES6,ES7 代码翻译成向后兼容版本的代码,因此需要指定一个 preset,它包含了代码转换的规则
{
test: /\.js$/,
loader : 'babel-loader' ,
exclude:'/node_modules/'
}
在我们的项目根目录创建一个 .babelrc 的文件来配置,用来配置我们的 babel 相关
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"> 1%",
"last 2 versions"
]
},
"debug":true //调试使用
}
]
]
}
我们在 index.js 文件中添加以下代码测试一下,并打包测试一下:
const test = (n)=> {
return new Promise(function (resolve) {
setTimeout(()=>{
resolve([1,2,3,4].map(v=>v * v ))
},n*1000)
}).then(res=>{
console.log(res);
})
}
console.log(test)
查看打包后的文件,发现转换的不够彻底,只能针对语法进行了转换,对于 Promise、map 这些高级用法并没有被转换,这肯定是不行的,我们还要想办法把这些新的特性,兼容到低版本的浏览器里。
我们还需要 babel 提供的另一个工具—— polyfill,
npm install @babel/polyfill -D 安装它
在我们的入口文件 index.js 最顶部添加 import ‘@babel/polyfill’
运行 npm run dev-build 编译打包,虽然控制台好想没啥变化,但是我们的打包的文件 main.js 发生了很大变化,很明显的一点,体积大了好几十倍
这是由于为了兼容低版本浏览器,polyfill 里面添加了很多辅助代码来帮助实现比如 Promise 和 map 这些新特性,默认情况下会被添加到每一个需要它的文件中,并且会全局注入,造成全局污染,如果我们在开发框架之类的,可能会发生冲突。
为了解决这个问题,我们引入 @babel/runtime 这个模块,来避免重复导入的问题
如果是写第三方库或者框架
运行 npm install @babel/runtime @babel/plugin-transform-runtime -D安装模块依赖
配置@babel/runtime,并修改一下 preset-env的配置,加上 useBuiltIns: ‘usage’ ,表示会根据业务需求自动转换需要转换的新语法
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"targets": {
"browsers": [
"> 1%",
"last 2 versions"
]
},
"debug":true //调试使用
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": true
}
]
]
}
15.拷贝静态文件
我们有些项目中的文件,不是 js ,也不是 css , 图片之类的,比如 README.md这些静态资源,我也希望能打包到我的项目里,怎么办呢?
其实就是文件的拷贝操作,我们只需要将这些文件拷贝到目标目录下即可,我们可以利用 copy-webpack-plugin 这个插件
运行 npm run copy-webpack-plugin -D 安装插件
在项目目录下创建一个 static/README.md 的测试文件
修改 webpack.config.js , 将插件添加到配置里去
new CopyWebpackPlugin({
from: path.resolve(__dirname,'src/static'),
to:path.resolve(__dirname,'dist/static')
})
from 是静态资源目录源地址,to是要拷贝文件的目标地址,so easy~
npm run dev-build 打包运行!
可以发现我们的 dist 目录已经将 src/static 的文件拷贝到了 dist/static 中
16.打包前清空
我们修改了文件之后,每次打包都会在 dist 目录下产生一个新的 main.[hash].js , 随着我们打包次数的增加,dist 目录下会生产出一堆的 main.[hash].js,不出意外,你的dist目录应该已经有不少了~
为了保证我们每次看到的都是最新的打包资源,而不受之前打包文件的干扰,这里我们需要引入另一个插件—— clean-webpack-plugin
通过 npm install clean-webpack-plugin -D 下载插件
在 webpack.config.js 中 plugins 下去配置它
plugins:[
new CleanWebpackPlugin()
]
该插件引入方式是稍微有点不同,通过以下这种方式引入:
const { CleanWebpackPlugin } = require(‘clean-webpack-plugin’)
现在,每次打包前都会先清空 dist 目录下的文件,然后才产出打包后的文件,这样看起来就清晰多了!
17.服务器代理
我们在开发时,有时候希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用,代理 API 的配置对于 webpack来说,配置就非常简单了,只需要在 proxy 中添加代理规则即可
proxy : {
"/api/test": {
target: 'http://lohost:3000/',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api/test': '/test'
}
}
上面这行配置,就可以将 /api/test 开头的接口地址,会被代理到 http://localhost:3000/test 下,如果是https接口,需要配置 secure 这个参数 为 true ,如果接口跨域,需要配置changeOrigin这个参数为 true
18.压缩JS和CSS
我们现在打包出来的文件无论是 js 和 css 都是源文件,我们希望在打包的时候压缩我们的代码,一来是为了安全,二来是可以减少我们打包文件的体积。
我们选择使用terser-webpack-plugin来压缩js文件,替换掉之前的 uglifyjs-webpack-plugin,解决uglifyjs不支持es6语法问题,使用 optimize-css-assets-webpack-plugin 来压缩 css 文件
运行 npm install terser-webpack-plugin optimize-css-assets-webpack-plugin -D安装插件
在 webpack.config.js 中引入并配置插件
new TerserPlugin({
parallel: true,
cache: true
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp:/\.css$/g,
cssProcessor:require('cssnano')
})
TerserPlugin 中的 parallel 代表开启多进程,cache 代表设置缓存,OptimizeCSSAssetsPlugin 中加载了一个 cssnano 的东西, cssnano是PostCSS的CSS优化和分解插件,会自动采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。
总结
现在,想必你对 webpack 已经有了一个比较完善的认识,对常见的一些配置打包的 loader 或者 plugin 都有一定的了解了,总结会发现,套路基本都差不多,需要什么 loader 和 plugin 只要去对应去查找即可~
其实 webpack 的功能非常强大,配置也是相当的多样化,这里只是列举了一些比较常见的功能,更深层次了解,来张飞机票 webpack
参考文章:
https://www.jianshu.com/p/1192cfd4a012
https://www.cnblogs.com/fps2tao/p/10879910.html
https://blog.csdn.net/solar_lan/article/details/82751165
https://www.jianshu.com/p/c88d019d038f