基础:Webpack实现前端工程化
引言
在很久之前,模块化管理还没有出现,如果我们开发一个页面想要引入一些依赖的话,最常见的做法就是将依赖文件引入到.html
文件中。比如,我们要使用JS
的一些依赖库,就要在.html
文件中使用标签引用;要引用
CSS
的依赖就要使用标签。如果页面中引入的依赖文件太多,那么向服务发送的请求也随之增多,势必会拖慢网页的加载速度,影响用户体验。另外,网页的内容也会变得很臃肿,增加维护的难度。
在移动互联网时代的网站,正在逐渐演化成Web应用(Web Application,简称WebAPP
),浏览器也在此之际不断的发展壮大,各种基于JavaScript
语言的框架横空出世,Web前端发展速度着实之快,让我们也不得不加快学习的脚步。现在的Web前端更倾向于单页面应用(single-page application,简称SPA
),减少页面的刷新次数,这就造成了庞大的页面代码管理问题,如果管理不好会导致很多问题,比如各个模块耦合度变高、难以维护等等。这就催生了模块管理器。
简单来说,Webpack
就是一个“模块打包机”,它的主要工作就是分析项目中的结构找到JavaScript
模块,根据各个模块之间的依赖关系进行静态分析,然后打包成一个独立的静态模块供浏览器调用,这样就可以大大减少请求次数,提供网页的性能,提高用户的体验。Webpack
还有一个作用就是把浏览器目前还解释不了的扩展语言(例如Scss
、TypeScript
等)进行编译,转换成浏览器可以识别的内容。React
中使用的是ES6
的语法,在一些主流的浏览器上还不支持ES6
,所有需要对Webpack
进行配置后,React才能正常运行。
Webpack
不仅是学习前端框架的前提,也是同学们将来面试必问、笔试必考、工作必用的内容,随着前端工程化的发展,Webpack
正在变得越来越重要,尤其对于大型的一线互联网公司,会不会Webpack
甚至能直接决定你是否能被录用。我从一个技术小白到全栈工程师,也经历了前端开发从刀耕火种到百家争鸣的各个阶段,着这个过程中沉淀了很多知识,也积累了大量的实践经验,也希望通过我的知识分享,让更多同学受益,避免大家踩坑。
1、Webpack简介与环境搭建
现在的网页开发越来越复杂,我们可以把网页看做是一个功能丰富的应用,为了实现复杂的功能,就需要导入很多的JavaScript
库和一大堆依赖包。为了简化开发的复杂度,前端社区也涌现出了很多的方法,以便于提高开发效率。但是利用一些方法开发的文件往往需要进行额外的处理才能让浏览器识别,而且手动处理又非常的繁琐,这就为Webpack
这样的工具诞生提供了需求。
1.1、模块化与工程化
随着Web前端的不断发展,前端开发也正在逐渐往Web应用的方式转变,应用场景也变的越来越复杂,需要更新的技术来解决开发中遇到的问题。前端模块化和工程化也呼之欲出,前些年比较流行的构建工具有Grunt
、Gulp
、FIS
等,但是经过近几年React
、Vue
框架的发展,不在使用传统的方式来操作DOM
。在这个过程中,前端逐渐发展成了模块化和单页应用为主的形式。
Webpack
也就是在这样的发展潮流中,被更多的人视为主流的前端构建工具。这也就引出了我们现在要讲的模块化和工程化。
百度百科是这样解释模块化的:
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,有多种属性、分别反映其内部特性。
在我们的生活中也经常会见到模块化的应用场景,比如现在的家用电器(电视机、电脑主机等)都是模块化的,如果这些家电坏了,维修的时候一般都是直接拿个新的模块更换一下就可以了,由此可以看出,模块化不仅仅是编程语言的概念。模块化应用到生活中,同样是可以提高办事效率的。
那我们主要说的前端模块化具体指什么呢?
前端模块化一般指的是JavaScript
的模块,最常见的是Node.js
中的NPM
包管理,有了模块化,我们在写代码的时候就避免了很多的重复工作,也不在只是做copy的事情了。有了模块化之后,开发者可以把更多的时间精力放到业务逻辑和代码的维护上。目前,有很多主流的模块化规范,比如CommonJS
、AMD
、ES6 Module
等规范,甚至在CSS
中也采用了@import
的方式实现模块化。Less
和Sass
作为CSS
的预处理语言,使用了@import
来导入一些变量、函数和mixin
的定义。
接下来,我们在聊聊什么是工程化。当开发Web应用的场景越来越复杂时,我们所面临的问题也会随之增加:
在大型项目中,多模块下如何管理依赖?
页面复杂度提升之后,多页面、多系统、多状态怎么办?
-
团队协作开发中如何统一规范?
……
以上这些问题都是在软件工程中必须要解决的问题,工程化问题需要运用工程化工具来解决。在早期,前端工程化主要是以Grunt
、Gulp
等构建工具为主,在这个时期解决的是重复任务的问题,他们将某些功能拆解成固定步骤的任务,然后编写工具来解决,比如:图片压缩、地址添加hash等,都是固定套路的重复工作。
在近几年,前端工程化的发展得益与Node.js
的发展,Webpack
的插件机制解决了前端资源依赖管理的问题,从而进化成了一整套前端工程化解决方案。
1.2、什么是Webpack
官方解释:
本质上,
webpack
是一个现代JavaScript
应用程序的静态模块打包器(module bundler)。当webpack
处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle
。
一般像Grunt
、Gulp
这类的构建工具,打包思路是:先遍历源文件,然后匹配规则,最后再打包,这个过程很耗费资源。而Webpack
可以做到按需加载,与其他构建工具不同之处在于:Webpack
是从入口文件开始,经过模块依赖加载、分析和打包三个流程完成项目的构建。在加载、分析和打包的三个过程中,可以针对性的做一些解决方案,达到按需加载的目的,比如拆分公共代码(code split)等。
Webpack
在打包时遵循“一切皆模块”的思想,即JS
是模块,CSS
等文件也是模块,还可以将ES6
转为ES5
,并且可以对Less
、Sass
这些CSS
预处理器进行编译。在打包过程中通过强大的Loader
和插件机制来完成解决方案的封装,最后对项目进行压缩和优化等操作。Webpack
解决了传统构建工具实现不了的问题,被更多的前端开发者使用。
1.3、Webpack环境搭建
1.3.1、安装Node.js
Webpack
实际是用Node.js
写的,所以要先安装Node.js
环境。
首先进入Node.js的官网,选择对应系统下载安装包:
对于 windows
用户,直接下载安装包安装即可,如果是 Macos
用户,推荐使用 brew 进行安装。
Node.js
有很多版本,包括稳定版和开发版,不同的项目需要的Node.js
版本不同,推荐大家安装 8.x
以上的版本。
1.3.2、NPM简介与常用命令
(1)NPM包管理工具
简单来说,NPM
(Node Package Manager)是包含在Node.js
里面的一个包管理工具,NPM
会随着Node.js
一起安装,我们可以在命令提示符(以下演示的命令均为windows系统环境)中查看NPM
的版本:
NPM
为开发者提供了一个代码模块共享的大平台,当我们项目中需要使用某个模块(JavaScript
包)时,可以直接使用NPM
包管理工具来下载对应的包并安装,我们也可以把自己用Node.js
写的代码发布到平台上供他人使用。
在搭建一个前端项目之前,通常会在项目的根目录下生成一个名为package.json
的文件作为NPM
包的描述文件,使用该文件来定义项目信息、配置包依赖关系。package.json
文件可以自己手动创建,也可以使用命令来创建:
npm init
在命令提示符中输入上面命令(先 cd
到项目根目录下,再执行命令)后,会向用户提问一系列问题,根据对应的提示回答问题,如果不回答直接输入回车,程序会按照默认选项进行配置。期间使用 -f
(表示force
)、-y
(表示yes
)会跳过提问阶段,直接生成一个新的package.json
文件。
文件中包含了NPM
包的基本信息(项目名称、版本号、项目描述、作者)和依赖管理,例如:
"name": "demo",
"version": "1.0.0",
"dependencies": {
"webpack": "^4.29.6"
}
}```
name:表示我们当前的项目名称,如果将来发布到`npmjs.com`平台上,会以这个名字来命名,还有一种命名方式是`@scope/name`,表示作用域包。
version:是当前项目的版本,`NPM`是围绕 [语义版本控制(semver)](https://semver.org/lang/zh-CN/)思想而设计的,这种规范是以 `主版本号.次版本号.修订号`(`MAJOR.MINOR.PATCH`)的描述形式,其分别表示的意思是:
* 主版本号:当你做了不兼容的`API`修改;
* 次版本号:当你做了向下兼容的功能性新增;
* 修订号:当你做了向下兼容的问题修正(例如,修改了一个bug);
dependencies:是`demo`这个项目中的依赖,还有`devdependencies`也是描述当前项目的依赖,这二者的区别我们会在下面进行详细的说明。上面`package.json`示例中`dependencies`里存放了有关于`webpack`的版本号,版本号前面有个`^`,意思是主版本是`4`的最新版本,每次执行安装命令的时候,会更新符合这个规则的最新包。
**(2)NPM的常用命令**
我们常用的`NPM`命令主要是做四种操作:安装、删除、初始化、配置。
> **安装某个`NPM`包**
```npm install packageName
## 简写
npm i packageName```
> **安装`package.json`中的所有依赖**
```npm install```
> **安装指定版本的包**
```npm i packageName@x.x.x```
> **全局安装依赖包**
```npm i packageName --global
## 简写
npm ipackageName -g```
> **安装依赖包,并且将依赖写入`package.json`文件的`dependencies`部分**
```npm i packageName --save
## 简写,简写中-S是大写字母
npm i packageName -S```
> **安装依赖包,并且把依赖写入`package.json`文件的`devdependencies`部分**
```npm i packageName --save-dev
## 简写
npm i packageName -D```
> **删除某个`NPM`包**
```npm uninstall packageName```
> **初始化一个`NPM`项目,自动生成`package.json`文件**
```npm init```
> **单次设置镜像,使用淘宝`NPM`镜像替换官方`NPM`镜像**
由于`NPM`网站属于境外服务器,所以我们为了保证下载`NPM`包时的网络稳定性,会将下载镜像换成国内的镜像,其中[淘宝`NPM`镜像](https://npm.taobao.org/)是国内最大的一家`NPM`镜像网站,在下载`NPM`包时,使用 `cnpm` 命令代替原来的 `npm` 命令。
```npm [命令] --registry=https://registry.npm.taobao.org```
> **设置默认`npm`使用淘宝镜像**
```npm config set registry https://registry.npm.taobao.org```
> **安装`cnpm`包,安装成功后`npm`命令更换为`cnpm`命令**
```npm install -g cnpm --registry=https://registry.npm.taobao.org
## 安装成功之后,直接像使用npm一样使用cnpm即可,例如:安装某个包就变成了
cnpm i packageName```
> **设置环境变量**
```npm set xxx
## 例如:
npm set init-author-name 'Your name'```
> **查看某个包的信息**
```npm info
## 例如:
npm info lodash```
> **查找`npm`仓库**
```npm search
## 后面可以跟字符串或者正则表达式,例如:
npm search webpack```
> **树形的展示当前项目安装的所有模块,以及对应的依赖**
```npm list
## 例如,查看全局安装的模块:
npm list --global```
**(3)dependencies 和 devDependencies 区别**
在上面关于`NPM`常用命令中,我们有讲到 `dependencies` 和 `devDependencies` 的命令分别是:
```# 自动把模块和版本号添加到dependencies部分,将依赖安装到生产环境
npm install module-name --save
# 自动把模块和版本号添加到devdependencies部分,将依赖安装到开发环境
npm install module-name --save-dev```
很多同学不太理解什么是开发环境和生产环境,简单来说,就是在项目的开发阶段就是开发环境;项目上线了,开始正式提供对外服务,在生产环境下,一般会关掉错误报告,打开错误日志等操作。
`devdependencies`配置的是开发环境,安装项目开发时所依赖的模块。比如像`webpack`工具,只是用来构建项目和打包,这些都是在开发阶段才使用的,等项目上线后就用不到`webpack`工具了,那么我们就可以把`webpack`安装到开发环境中(使用 `--save-dev`命令安装到`devdependencies`下);
`dependencies`是生产环境,安装项目运行时所依赖的模块。比如`jQuery`库,,等项目上线以后依然是要继续使用的,我们就要安装在生产环境中(使用 `--save` 命令安装到`dependencies`下),如果没有把需要的依赖安装到生产环境中,项目上线运行时就有可能会报错。
**(4)本地模式和全局模式**
我们在安装`NPM`包时,有两种模式可选:一是本地模式,二是全局模式。
默认是本地模式安装,本地模式是指在执行`npm install`命令后,会在执行命令的目录下创建`node_modules`目录,然后再把下载的依赖和安装包保存到`node_modules`目录下。
全局模式是指将下载的依赖和安装包保存到全局路径下的方式,在`Node.js`中使用`require`依赖时,会优先查找自己当前文件中的`node_modules`目录,如果没有,在循环遍历上层的`node_modules`,如果还找不到依赖,就会去全局模式下的安装目录寻找。
在`package.json`中增加`bin`字段,并且指向包内对应的文件映射路径,就可以把该文件放到`PATH`中,使用`npm`命令执行了,例如:
```"bin": {
"testCmd": "bin/cmd.js"
}```
配置好`bin`字段后,在项目根目录输入 `npm link xxx` 就可以执行`testCmd`命令了, 使用 `npm link` 命令,将 `npm` 模块链接到对应的运行项目中去,方便地对模块进行调试和测试 。
**(5)修改NPM全局模式的默认安装路径**
一般情况下,我们安装`Node.js`环境,程序会自动把`NPM`全局模块的路径设置在系统盘(通常是`C`盘下),我们在项目开发阶段不建议全局路径设置在系统盘,不但会影响电脑的性能,而且还很不安全。可以通过以下命令来设置默认下载的全局路径目录:
> 输入命令,查看当前配置
```npm config ls```
运行结果:
如果是第一次使用`NPM`安装包的话,在配置中只会看到`prefix`的选项,就是`NPM`默认的全局安装目录。但是如果有多次使用`NPM`安装包的话,就会看到`cache`和`prefix`两个路径,如下图:
第一步:
在欲更改的目录下新建两个文件夹,分别是:`node_global_modules` 和 `node_cache`,效果如图:
第二步:
打开命令提示符,执行下面两条命令:
```npm config set prefix "D:\dev\nodejs\node_modules\npm\node_global_modules"
npm config set cache "D:\dev\nodejs\node_modules\npm\node_cache"```
执行成功后,可以用 `npm config ls` 命令查看配置的结果,效果如图:
第三步:
验证配置成功后,需要配置环境变量。在环境变量中,新建一个系统变量,变量名:`NODE_HOME`,变量值:`D:\dev\nodejs`,效果如图:
在`Path`变量名中,新建变量值:
`%NODE_HOME%` `%NOED_HOME%\node_modules` `%NODE_HOME%\node_modules\npm\node_global_modules\`
保存之后,可以重新执行全局安装命令,查看`NPM`依赖包在全局的默认安装目录是否为我们配置好的目录,如果成功下载到了设置后的目录下,就成功修改了默认全局安装路径。
**(6)NPM Scripts**
`NPM`不仅可以用于模块管理,还可以用于执行脚本。可以在`package.json`文件中添加`scripts`字段,用于指定脚本命令,以供`NPM`直接调用,示例如下:
```// package.json
{
"scripts": {
"build": "webpack",
"start": "node src/scripts/dev.js"
}
}```
`scripts`里面放的是`npm`要执行的命令,格式是`key-value`形式,为了简化操作,具体命令为`value`,自定义的简化命令为`key`,当`npm`运行`key`命令时,等同于执行后面的`value`命令。例如,在`package.json`添加上面字段之后,可以直接使用`npm run build`和`npm run start`命令了 ,而且`npm run start`命令还可以简写成`npm start`。
> 注意:上面提到的简写,只有`npm run start`才可以简写,并不是所有script命令都支持这种简写的方式。
>
> 除了 `npm` 外,还有一些包管理工具,主要是针对 `npm` 的下载速度慢、`node_modules` 混乱等缺点设计的,例如[yarn](https://yarnpkg.com/)和 [pnpm](https://pnpm.js.org/)。
在后面会讲到使用`create-react-app`脚手架工具搭建`React`开发环境,在自动生成的`package.json`文件中配置的`scripts`字段内容是:
```"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}```
我们在命令窗口中直接执行`npm start`就相当于执行了`npm react-scripts start`命令,通过这个命令可以启动`React`服务,并且在浏览器输入`IP`地址就可以访问React项目了。
#### 1.3.3、安装Webpack
**(1)安装`Webpack`**
`webpack`的安装需要使用`NPM`来完成,安装命令如下:
```## 全局安装
npm install -g webpack
## 安装到项目目录中
npm install --save-dev webpack```
安装好之后,就可以使用`webpack`的命令了。
**(2)安装`Webpack-cli`**
[Webpack-cli](https://github.com/webpack/webpack-cli)是 `Webpack` 的 `CLI` (Command-line interface)工具,如果在项目中,我们可以使用下面的方式安装:
```# 本地安装
npm install webpack-cli --save-dev
# 全局安装
npm install webpack-cli -g```
由于`webpack`是用于项目构建和打包的工具,所以我们在安装时使用`--save-dev`命令把`webpack-cli`放到开发环境的字段下。
## 2、Webpack中常规配置项介绍
### 2.1、webpack.config.js 配置文件
当我们安装好`webpack`之后,就可以使用`webpack`命令了,比如要将一个`main.js`文件打包成一个`bundle.js`文件,可以使用如下命令:
```webpack main.js bundle.js```
一般在实际的项目开发中,要把这些命令写到一个`webpack.config.js`的文件中。上面打包操作可以做如下配置:
```module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
}
};```
在上面的配置文件中,我们配置了项目的入口(`entry`)和出口(`output`),在该项目的关系图中,`entry`字段指定了入口文件,即把`main.js`作为起点。`output`字段是指定了输出位置,即指定`webpack`把整理后的资源放在哪里。
### 2.2、webpack核心概念
`webpack`主要有4个核心概念:
入口(`entry`):项目入口,也是`webpack`所有依赖关系图的起点。
出口(`output`):指定`webpack`打包应用程序的目录。
加载器(`loader`):加载需要处理的模块,对模块进行转换处理。
插件(`plugins`):定义项目要用到的插件。
#### 2.2.1、webpack入口(entry)和出口(output)
在前面的内容中,我们已经了解到了,`webpack`是一个模块打包工具,需要处理具有依赖关系的各个模块,这些模块会构成一个依赖关系图(dependency graph)。`webpack`的入口就是这张关系图的起点,指的是入口文件。`webpack`出口指的是需要把这张关系图导出到哪个文件中,即导出文件。
**(1)入口(entry)**
`entry`字段指定了入口文件,也可以理解为当前项目启动时运行的第一个文件,语法为:
```entry: string | object | Arrary```
`entry`字段支持多种类型,值可以是字符串、对象、数组。简单来理解,就是`entry`可以指定一个文件入口,也可以指定多文件入口。我们举例来说明`entry`的用法:
单文件入口示例
```// 使用key-value方式
module.exports = {
entry: 'path/to/my/entry/main.js'
};
// 或者使用对象方式
module.exports = {
entry: {
main: 'path/to/my/entry/main.js'
}
};
// entry也可以使用数组指定多个入口文件路径,输出时会合并输出
module.exports = {
mode: 'development',
entry: ['./src/app.js', './src/home.js'],
output: {
filename: 'array.js'
}
};```
多文件入口
```// entry配置3个独立的入口文件,会打包成3个对应的bundle
module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
}
};```
**(2)出口(output)**
`output`字段是指定`entry`对应文件编译打包后的输出位置,也可以理解为指定`webpack`把整理好的资源放到哪里。`output`字段常用的属性有:
* `path`:指定打包完成的文件存放的路径;
* `filename`:指定打包完成的文件的文件名称;
* `publicPath`:指定一个在浏览器中被引用的URL地址;
如果不指定`path`参数,将默认将打包文件输出到`webpack.config.js`同级目录下;如果不指定`output`,打包文件会默认输出到`dis/main.js`,即`output`字段的`path`属性默认是`dis`,`filename`属性默认是`main`。
一个`webpack`的配置可以包含多个`entry`,但是只能有一个`output`。对于不同的entry可以通过`output.filename`占位符语法来区分,例如:
```module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};```
在上面的配置中,`output.filename`指定的`[name]`就是一个占位符,对应的是`entry`中的`key`(`home`、`search`、`list`),最终输出的结果是:
```path/to/my/entry/home.js → dist/home.js
path/to/my/entry/search.js → dist/search.js
path/to/my/entry/list.js → dist/list.js```
目前`webpack`支持的占位符有:
| 占位符 | 含义 |
| --- | --- |
| `[hash]` | 模块标识符的`hash` |
| `[chunkhash]` | `chunk`内容的`hash` |
| `[name]` | 模块的名称 |
| `[id]` | 模块标识符 |
| `[query]` | 模块的`query`,例如,文件名 `?` 后面的字符串 |
| `[function]` | 一个 `return` 出一个 `string` 作为 `filename` 的函数 |
#### 2.2.2、webpack 加载器(loader)
`loader`是解析处理器,大家都知道,`webpack`的任务就是把具有依赖关系的各个文件进行整合并打包,这些文件的类型有很多,比如`.html`、`.css`、`.js`、`.scss`、`.jpg`等等。但是`webpack`只认识`JavaScript`文件,那如何识别其他文件呢?`loader`就解决了这个问题。比如,很多浏览器不支持`ES6`语法,`webpack`可以通过`loader`配置,将`ES6`语法转化为`ES5`的语法,还可以将图片转化为`base64`的`dataURL` ,还可以通过`loader`直接在`JavaScript`文件中使用`import` 引入`css`和`html`。
`loader`在`webpack`构建文件的过程中起着至关重要的作用,实现可以通过`loader`识别出要对哪些文件进行预处理,然后`loader`转换这些需要预处理的文件,并添加到`bundle`(构建后的模块)中。在`React`框架开发时,经常会用到`JSX`这种扩展语言来编写`DOM`,目前几乎所有的浏览器都不支持`JSX`格式,那么`loader`就可以在使用`JSX`之前做一些预处理操作,将其转化成`JavaScript`语言,示例如下:
```module.exports = {
entry: {
app: './app.js'
}
output: {
filename: 'bundle.js',
path: './dist'
}
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: 'css-loader'
}
]
}
}```
在上面的例子中,`test`字段表示要对哪些类型的文件进行构建,`use`字段表示要用哪些模块对该类型的文件进行构建。在`test`项中使用`/\.css$/`这种正则表达式来匹配需要处理的模块文件,即匹配以`.css`为后缀的文件。
在配置`loader`之前,`use`中的模块是需要安装的,命令如下:
```npm install --save-dev babel-loader
npm install --save-dev css-loader```
> **提示:**
>
> 在`webpack`的早期版本中,`loader`的写法是:
>
> ```module.exports = {
> ......
> module: {
> loaders: [
> {
> test: /\.(js|jsx)/,
> loader: 'babel-loader'
> },
> ......
> ]
> }
> }```
>
> `webpack`最新版本中已经废弃了`loaders`、`loader`的写法,改成了 `rules`、`use`。
#### 2.2.3、webpack 插件(plugin)
`plugin`在`webpack`起着重要的作用,通过`plugin`可以解决`loader`解决不了的问题,以此来丰富`webpack`的功能。`webpack`本身就是有很多插件组成的,所以内置了很多插件。除了内置插件,我们也可以通过`NPM`包的方式来使用插件,比如现在需要安装一个`html-webpack-plugin`的插件,先执行安装命令:
```npm install --save-dev html-webpack-plugin```
然后在`webpack.config.js`文件中配置`plugins`:
```var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './app.js'
},
output: {
filename: 'bundle.js',
path: './dist'
},
module: {
rules: [
{
test: /\.(js|jsx)/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: 'css-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin()
]
}
上面的示例中,plugins
参数为一个数组,可以传入多个plugin
,另外需要注意plugin
是可以带参数的,所以plugins
属性传入的必修为new
实例。
3、使用Babel转换JavaScript代码
3.1、什么是Babel
Babel
是一个JavaScript
的编译器,我们在webpack
中可以使用ES6
以上版本的语法写代码,但是目前主流的浏览器仅支持ES5
语法,通过Babel
可以将ES6+
语法自动转换成ES5
语法,来适配浏览器的JavaScript
解释器。
比如说,我们使用ES6
语法中的箭头函数实现代码:
使用Babel
转化成ES5
语法后:
return val+1 ;
}
3.2、babel-cli命令行工具的使用
在项目中安装babel-cli
,命令如下:
## 简写
npm i -D @babel/core @babel/cli
然后再安装转换规则:
npm i -D @babel/preset-env
# 执行 CLI 添加--presets
npx babel babel.js --presets=@babel/preset-env
测试babel-cli
命令行工具,先创建一个app.js
文件,输入下面的代码:
然后在命令行中输入下面命令:
## 或者 指定输出结果到固定文件
npx babel app.js --out-file appout.js
## 简写
npx babel app.js -o appout.js
转化命令执行成功后,app.js
的语法就变成了ES5
语法:
return Math.pow(n, 2);
});
3.3、Babel配置文件
Babel
还支持配置文件的方式进行转化操作,配置文件支持两种形式:
(1)使用package.json
的babel
属性
示例:
"name": "demo",
"version": "1.0.0",
"babel": {
"presets": ["@babel/preset-env"]
}
}
(2)使用.babelrc
文件
在项目的根目录创建.babelrc
或.babelrc.js
文件,Babel
会在正在被转义的文件当前目录中查找一个.babelrc
文件, 如果不存在,它会向外层目录遍历目录树,直到找到一个 .babelrc
文件,或一个 package.json
文件中有 "babel": {}
。 在文件中配置如下:
"presets": ["@babel/preset-env"]
}
3.4、在webpack中使用babel
第一步:安装依赖包
npm i webpack babel-loader webpack-cli @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
# 将 runtime 作为依赖
npm i @babel/runtime -S
第二步:创建webpack.config.js
文件,配置如下:
//......
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
]
]
}
}
]
}
]
}
};
把Babel
配置到webpack.config.js
文件的options
中,或者是配置到.babelrc
文件, 或者使用package.json
的 babel
字段。
4、配置webpack-dev-server本地服务器
webpack-dev-server
就是一个Express
的小型服务器,通过Express
的中间件 webpack-dev-middleware
和 Webpack
进行交互的。
4.1、安装webpack-dev-server服务
在项目中安装:
启动服务:
服务启动成功后,在浏览器中访问webpack-dev-server
的默认地址 localhost:8080
,就可以默认的index.html
页面,如果没有该页面,就会显示文件目录。
4.2、整合Webpack
将webpack-dev-server
服务整合到webpack
的配置文件中,webpack.config.js
配置文件内容如下:
module.exports = {
//...
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 9000 //服务器端口
}
};
还可以将webpack-dev-server
放到package.json
的scripts
里面,来简化服务启动命令:
"scripts": {
"dev": "webpack-dev-server --mode development --config webpack.config.dev.js --hot --inline --port 3000"
}
}
配置完成后,可以使用下面命令启动服务:
执行npm run dev
实际就是执行的对应webpack-dev-server
命令。
4.3、配置热加载
热加载也叫模块热替换( Hot Module Replacement ,简称HMR
),配置热加载后,在应用运行期间修改代码,不需要重新刷新页面就可以在浏览器中加载更新后的内容。 通过配置 webpack.HotModuleReplacementPlugin
插件来开启全局的 HMR
功能。
修改webpack.config.js
文件,开启热加载:
module.exports = {
entry: './src/index.js',
devServer: {
open: true, //自动打开浏览器
contentBase: path.join(__dirname, 'dist'),//指定服务器将从哪个目录去查找内容文件
port: 9000, //服务器端口
hot: true // 开启 hmr 支持
},
plugins: [
// 添加 hmr plugin
new webpack.HotModuleReplacementPlugin()
]
};
完成上面的配置后,重启服务,在浏览器访问默认地址 http://localhost:9000
,然后再修改JavaScript
代码,就可以实时看到效果了。