1、Webpack介绍
1.1、webpack网址
webpack官网:https://webpack.js.org/
中文网站:https://www.webpackjs.com/、https://webpack.docschina.org/guides/
1.2、webpack简介
webpack 是一个用于现代JavaScript 应用程序的打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,可以在浏览器上直接运行。在webpack 看来,前端的所有资源文件(js、json、css、img、html..) 都会作为模块处理。
思考一个问题:当前端项目越来越大的时候,我们还能按照以往的思维方式继续开发么?问题症结所在:文件管理、ES6代码的转换、项目的打包…
解决方案:前端工程化(Webpack),在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。例如砌一堵墙:怎么简单怎么来,只要材料准备齐全,一个人直接干就行。盖一栋楼:需要很多大型机器,也需要更多的人,重要的必须要规范化干活,避免发生危险。
本质上,webpack 是一个用于现代JavaScript 应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于展示你的内容。
总结:
- webapck是一个构建工具,是基于node的,电脑上必须安装node,node版本需要大于16。
- 打包器,是从入口开始,按照模块依赖进行打包,最终得到浏览器的可以识别的静态资源。
- 从某种程度来说,webpack代表的是一种架构能力。
2、webpack的安装和配置
2.1、体验webpack(li隔行变色案例)
(1)、新建1_project空白文件夹,在该目录下运行npm init -y
初始化包管理配置文件 package.json。
(2)、新建src目录,在src目录下新建index.html、和index.js
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>隔行变色案例</title>
<script src="./index.js"></script>
</head>
<body>
<ul>
<li>这是第 1 个li</li>
<li>这是第 2 个li</li>
<li>这是第 3 个li</li>
<li>这是第 4 个li</li>
<li>这是第 5 个li</li>
<li>这是第 6 个li</li>
<li>这是第 7 个li</li>
<li>这是第 8 个li</li>
<li>这是第 9 个li</li>
</ul>
</body>
</html>
(3)、运行npm i jquery -S
安装jquery。
jquery安装成功后在package.json
中的dependencies
配置项下有jquery的版本信息:

在index.js引入jquery。并编写隔行变色的代码:
//index.js
import $ from 'jquery';
$(function() {
$('ul li:odd').css('background-color','red');
$('ul li:even').css('background-color','green');
})
在index.html中引入index.js,运行index.html,结果如下:

无法运行。
(4)、安装webpack
运行:npm install webpack webpack-cli -D
安装webpack和webpack-cli

安装完成以后,在devDependencies
配置项中就有了webpack和webpack-cli这两个包。
(5)、配置webpack
在项目根目录下新建webpack.config.js
配置内容如下:
//webpack.config.js
module.exports = {
mode: 'development'
}
在package.json
文件的scripts
下新增dev配置项:
"scripts": {
"dev": "webpack"
},
然后在项目根目录下运行:npm run dev

运行成功后,项目目录下出现了一个dist文件夹,打开dist文件夹,下面有一个main.js
,将这个自动生成的main.js引入index.html中,项目运行成功。

2.2、安装webpack和webpack-cli
运行:npm install webpack webpack-cli -D
2.3、配置webpack
webpack.config.js
是webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件,从而基于给定的配置,对项目进行打包。
(1)、mode配置项的应用场景
mode配置项是webpack 运行的模式,可选值有两个development
和production
。
development
:打包用时短,生成的文件大且没有被压缩和混淆。(项目开发阶段使用)
production
:打包用时长,生成的文件小且被压缩和混淆。(项目发布上线的时候使用)
(2)、webpack4.x和webpack5.x的默认约定
a、默认的打包入口文件为 src -> index.js
b、默认的输出文件路径为 dist -> main.js
测试将src目录的名称改变或者src下的index.js改成index1.js,再次运行npm run dev
,结果如下:

由于没有遵守默认约定,所以npm run dev
运行报错。
但是我们可以在 webpack.config.js 中修改打包的默认约定。
在 webpack.config.js
配置文件中,通过entry节点指定打包的入口。通过output节点指定打包的出口。
(3)、配置webpack的entry
const path = require('path');//引入node的path模块
module.exports = {
mode: 'development',
//entry: 指定要处理的文件
//path.join()用于拼接多个路径片段并返回规范化后的路径
entry: path.join(__dirname, 'src/index1.js'),
}
const path = require('path')
用来引入node的path模块。path.join()
方法用于拼接多个路径片段并返回规范化后的路径。__dirname
是一个全局变量,用于表示当前模块的目录名。它返回的是当前模块文件所在的目录的绝对路径。(本例中__dirname写在根目录下的webpack.config.js
中,__dirname就代表根目录的路径。)
通过配置entry节点,修改默认约定后,再次运行npm run dev
,不再报错,项目正常被打包。

(4)、配置webpack的output
output节点用来配置生成的文件的保存目录以及生成文件的文件名。
const path = require('path');
module.exports = {
mode: 'development',
//entry: 指定要处理的文件
entry: path.join(__dirname, 'src/index1.js'),
//指定生成的文件要存放在哪里
output: {
path: path.join(__dirname, 'lib/'),//指定存放的目录
filename:'xxx.js'//指定生成的文件名
}
}
修改完成后,运行npm run dev
:

(5)、devServer配置项
webpack.config.js中的devServer节点配置:
配置运行npm run dev
后自动打开浏览器访问项目。
devServer: {
host: '127.0.0.1',//运行项目的ip
open: true,//运行npm run dev后自动打开浏览器
port: 8080//访问项目的端口
},
注意:凡是修改了webpack.config.js文件必须重新运行npm run dev命令重启项目服务。
3、webpack的插件
3.1、webpack-dev-server插件
每当修改了源代码,webpack会自动进行项目的打包和构建。如果不使用这个插件则需要每次修改了源代码都要运行一遍npm run dev
。
(1)、安装:npm install webpack-dev-server@4.x -D
。
(2)、配置:修改package.json中的scripts中的dev 命令
"scripts": {
//由原先的"dev": "webpack"
//改为:
"dev": "webpack serve"
},
(3)、再次运行npm run dev
命令,重新进行项目的打包。

注意:webpack-dev-server 会启动一个实时打包的http服务器。
此时只要修改源代码,webpack-dev-server 都能监听到修改,然后自动打包。每次修改完以后效果都能直接体现在页面。无需每次运行npm run dev来打包。
如果要停止webpack-dev-server,则按下ctrl+c即可停止。
访问webpack-dev-server启动的服务器地址:http://localhost:8081/,页面是空的。这是由于我安装的是新版本的插件。

需要在webpack.config.js
中新增一个配置项:
devServer: {
// contentBase: __dirname, -- 请注意,这种写法已弃用
static: {
directory: path.join(__dirname, "/"),
},
},
加上以上配置项以后,按ctrl+c停止之前的服务,然后再次运行npm run dev:

点击src目录可以访问到src目录下的index.html页面。
进入页面后,发现修改源代码以后,页面并没有实时改变最新效果,这是因为webpack-dev-server实时生成的打包文件并没有直接输出到output配置的目录中,而是存储在内存中,可以通过http://localhost:8081/main.js来访问到。由于存储在内存中,不在磁盘上,所以在根目录下看不见这个文件。如果要实时看到修改后的效果,需要将src/index.html中的main.js的引入方式变成:<script src="../main.js"></script>
修改成这样以后,index.html文件中引入的实际是内存中的main.js。这个main.js会根据我们修改源代码实时变化,在页面中就能看到实时效果。
3.2、html-webpack-plugin插件
当我们访问http://localhost:8081/的时候,总是会进入一个目录页面,需要点击src目录才能访问到src下面的index.html。我们希望每次访问这个地址直接进入index.html页面的话,可以使用html-webpack-plugin
插件来解决。
(1)、安装插件:npm install html-webpack-plugin -D
(2)、配置:
在webpack.config.js文件中进行如下配置:
//导入html插件,得到一个构造函数
const HtmlPlugin = require('html-webpack-plugin');
//创建html插件的实例对象
const htmlPlugin = new HtmlPlugin({
template: './src/index.html',//指定原文件的存放路径
filename: './index.html'//指定生成文件的存放路径
});
//插件的数组,将来 webpack 在运行时,会加载并调用这些插件
plugins:[htmlPlugin]//通过plugins节点使htmlPlugin插件生效
现在的webpack.config.js文件内容如下:
const path = require('path');
//导入html插件,得到一个构造函数
const HtmlPlugin = require('html-webpack-plugin');
//创建html插件的实例对象
const htmlPlugin = new HtmlPlugin({
template: './src/index.html',//指定原文件的存放路径
filename: './index.html'//指定生成文件的存放路径
});
module.exports = {
mode: 'development',
//entry: 指定要处理的文件
entry: path.join(__dirname, 'src/index.js'),
//指定生成的文件要存放在哪里
output: {
path: path.join(__dirname, 'dist'),//指定存放的目录
filename:'main.js'//指定生成的文件名
},
devServer: {
// contentBase: __dirname, -- 请注意,这种写法已弃用
static: {
directory: path.join(__dirname),
},
},
//插件的数组,将来 webpack 在运行时,会加载并调用这些插件
plugins:[htmlPlugin]//通过plugins节点使htmlPlugin插件生效
}
再次运行npm run dev
,访问http://localhost:8081/,即可看到首页,由于根目录下的index.html存储在内存中,不在硬盘上,所以在项目根目录下看不见这个文件。此时修改源代码,页面是实时变化的。
html-webpack-plugin
插件有2个作用:
- 通过 HTML 插件可以复制指定的文件到项目根目录中(index.html文件),也被放到了内存中。
HTML插件在生成 index.html 页面的时候,自动注入了打包的main.js 文件。
所以在src/index.html中可以直接去掉/dist/main.js的引入,插件会自动注入那份在内存中的main.js。
4、webpack加载器(loader)
在实际开发过程中,webpack 默认只能打包处理以.js 后缀名结尾的模块。其他非,js 后缀名结尾的模块,
webpack 默认处理不了,需要调用loader 加载器才可以正常打包,否则会报错。
loader 加载器的作用:协助 webpack 打包处理特定的文件模块。比如:
- CSS-loader 可以打包处理.CSS 相关的文件
- less-loader 可以打包处理.less 相关的文件
- babel-loader 可以打包处理 webpack 无法处理的高级JS语法
4.1、打包处理css文件
在项目根目录下新建css文件夹,在css文件夹中新建index.css文件。在src/index.js中导入这个css文件:
//导入样式(在webpack中,一切都是模块,都可以通过es6语法导入)
import '../css/index.css';
因为webpack默认只能处理.js
文件,所以产生了一个报错:

要想让webpack能够处理.css文件,必须配置处理css文件的loader。
(1)、安装处理css文件的loader:style-loader和css-loader
运行命令:npm i style-loader css-loader -D
(2)、在 webpack.config.js
的 module -> rules 数组中,添加 loader 规则如下:
module:{ //所有第三方模块文件的匹配规则
rules:[
//其中,test 表示匹配的文件类型,use 表示对应要调用的 loader
{test:/\.css$/,use:['style-loader','css-loader']}
//注意:
// • use 数组中指定的 loader 顺序是固定的
// • 多个 loader 的调用顺序是:从后往前调用
]
}
4.2、打包处理less文件
在css文件夹中新建index.less
文件,在src/index.js中导入这个less文件:
//导入样式(在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用)
//如果某个模块中,使用 from 接收到的成员为 undefined,则没必要进行接收,而是直接用import导入即可
import '../css/index.less';
保存后,报错:

因为webpack默认不能处理.less的文件,需要安装处理.less文件的less-loader加载器和less包。
(1)、安装处理less文件的less-loader加载器和less包
运行:npm i less-loader@12.2.0 less@4.x
(2)、配置
在 webpack.config.js 的module -> rules 数组中,添加处理.less的loader规则如下:
module:{ //所有第三方模块文件的匹配规则
rules:[
//处理,css文件的loader
//其中,test 表示匹配的文件类型,use 表示对应要调用的 loader
{test:/\.css$/,use:['style-loader','css-loader']},
//注意:
// • use 数组中指定的 loader 顺序是固定的
// • 多个 loader 的调用顺序是:从后往前调用
//处理.less文件的加loader
{test:/\.less$/,use:['style-loader','css-loader','less-loader']}
]
}
4.3、打包处理样式表中与 url 路径相关的文件
(1)、安装处理样式表中与url路径相关的文件所需的loader
运行 npm i url-loader file-loader -D
命令。
(2)、在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module:{ //所有第三方模块文件的匹配规则
rules:[
//处理样式表中与url路径相关的文件所需的loader
{test:/\.jpg|png|gif$/,use:['url-loader?limit=2229']}
]
}
以上配置中,?之后的是 loader 的参数项:
limit:用来指定图片的大小,单位是字节(byte)
只有≤ limit 大小的图片,才会被转为 base64 格式的图片
(3)、实例测试
在src目录下新建images文件夹,在文件夹中放入一个图片文件,在html中准备一个img标签:

在index.js中导入这张图片:
//导入图片文件
import logo from './iamges/logo.png';
使用jQuery将具有类名为logo的元素的src属性更改为变量logo的值。
$('.logo').attr('src',logo);
查看页面效果:

图片被转换成base64格式的图片加载进了img标签中。
是否会被转换成base64取决于在 webpack.config.js 的 module -> rules 数组中的配置:
{test:/\.jpg|png|gif$/,use:['url-loader?limit=154000']}
//这个配置是图片大小≤154000字节的图片会被转换成base64
4.4、打包和处理js中的高级语法
webpack 只能打包处理一部分高级的Javascript 语法。对于那些 webpack 无法处理的高级js 语法,需要借助于 babel-loader 进行打包处理。例如 webpack 无法处理下面的Javascript 代码:
//1、定义一个名为info的装饰器
function info(target){
//2、为目标添加静态属性info
target.info = "Person info"
}
//3、为Person类应用info装饰器
@info
class Person{}
//4、打印Person的静态属性info
console.log(Person.info);
以上代码会产生报错:

(1)、安装 babel-loader 相关的包
运行:npm i babel-loader @babel/core @babel/plugin-proposal-decorators -D
(2)、在 webpack.config.js
的 module ->rules 数组中,添加 loader 规则如下:
module:{ //所有第三方模块文件的匹配规则
rules:[
//使用babel-loader处理高级的js语法
//注意:必须使用 exclude 指定排除项;因为node_modules 目录下的第三方包不需要被打包
//因为第三方包的兼容性不需要程序员关心
{test:/\.js$/,use:'babel-loader',exclude: /node_modules/}
]
}
(3)、在项目根目录下,创建名为 babel.config.js
的配置文件,定义 Babel 的配置项如下:
module.exports = {
//声明babel可用插件
plugins:[
//将来webpack在调用babel-loader的时候会先加载plugins里面的插件来使用
['@babel/plugin-proposal-decorators',{legacy: true}]
]
};

5、打包发布
(1)、在 package.json 文件的 scripts 节点下,新增 build 命令如下:
"scripts": {
//项目发布时:运行npm run build命令
"build": "webpack --mode production"
},
--mode
是一个参数项,用来指定 webpack
的运行模式。production
代表生产环境,会对打包生成的文件进行代码压缩和性能优化。
注意:通过--mode指定的参数项,会覆盖 webpack.config.js 中的model选项。
(2)、运行npm run build
命令:

运行完命令以后,生成了dist文件夹,这个文件夹可以直接部署上线。
(3)、现在的html和js、图片等文件在同一目录下,如果希望将js放在js文件夹中,可以修改webpack.config.js
文件中的output配置项:
output: {
path: path.join(__dirname, 'dist'),//指定存放的目录
//原来的写法:filename:'main.js'//指定生成的文件名
//改成:
filename:'js/main.js'
},
(4)、把图片文件统一生成到 images 目录中
修改webpack.config.js 中的 url-loader配置项,新增 outputPath选项即可指定图片文件的输出路径:
//原来的写法:{test:/\.jpg|png|gif$/,use:['url-loader?limit=2229']},
//修改成:
//在配置 url-loader 的时候,多个参数之间,使用&符号进行分隔
{test:/\.jpg|png|gif$/,use:['url-loader?limit=2229&outputPath=images']},
配置完成后,重新运行:npm run build
:

(5)、每次发布之前都要先手动删除dist文件夹来清理旧文件,如何自动清理 dist 目录下的旧文件。可以使用clean-webpack-plugin
插件来实现。
安装clean-webpack-plugin插件:npm install clean-webpack-plugin -D
配置webpack.config.js:
//1、导入构造函数
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//2、在plugins节点添加插件使插件生效
plugins:[htmlPlugin,new CleanWebpackPlugin()],//通过plugins节点使htmlPlugin插件生效
//或者
//1、先实例化一下CleanWebpackPlugin
const cleanWebpackPlugin = new CleanWebpackPlugin();
//2、在plugins节点添加插件使插件生效
plugins:[htmlPlugin, cleanWebpackPlugin],
6、Source Map
Source Map 就是一个信息文件,里面储存着位置信息。也就是说,Source Map 文件中存储着压缩混淆后的代码,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。
6.1、默认的Source Map存在的问题
开发环境下默认生成的 Source Map,记录的是生成后的代码的位置。会导致运行时报错的行数与源代码的行数不一致的问题。示意图如下:

6.2、在开发模式下解决默认Source Map存在的问题
开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数保持一致:
//eval-source-map 仅限在“开发模式“下使用,不建议在“生产模式”下使用。
//此选项生成的 Source Map 能够保证“运行时报错的行数“与“源代码的行数“保持一致
devtool:'eval-source-map',

经过以上配置,再次查看报错行号:

6.3、在生产环境下需要关闭Source Map
在生产环境下,如果省略了 devtool选项,则最终生成的文件中不包含Source Map。这能够防止原始代码通过Source Map 的形式暴露给别有所图之人。

所以为了安全,建议在生产环境下都关闭Source Map。关闭Source Map即:不添加devtool:'eval-source-map'
配置项。
6.4、在生产环境下只定位行数,不暴露源代码
在生产环境下如果只想定位报错的具体行数,且不想暴露源码。此时可以将 devtool 的值设置为nosources-source-map
。实际效果如图所示:
//只定位报错的具体行数,不暴露源码
evtool:'nosources-source-map',

6.5、Source Map的最佳实践
(1)、开发环境下:
- 建议把devtool 的值设置为
eval-source-map
- 好处:可以精准定位到具体的错误行
(2)、生产环境下:
- 建议关闭 Source Map 或将 devtool 的值设置为
nosources-source-map
- 好处:防止源码泄露,提高网站的安全性
7、webpack中的“@”
在webpack中,@符号代表源代码的src目录。
在webpack.config.js
文件中添加如下配置:
resolve:{
alias:{
//配置@代表src目录
'@': path.join(__dirname,'./src/')
}
}

配置完成以后,在需要导入文件的地方都不必再使用类似:./
或者../
的形式来定位目录了,而是直接使用@
来定位到src
目录。例如:
//原来的写法:import msg from '../../msg.js';
//@符号表示src源代码目录,从外往里查找
import msg from '@/msg.js';
//导入src/js/test/info.js
import '@/js/test/info.js';