代码中使用的Webpack版本:4.43.0
在过去的很长一段时间里,JavaScript这门语言并没有模块这一概念。如果工程中有多个JS文件,只能通过script标签将它们一个个插入页面中。但这种做法有很多缺点:
- 需要手动维护JS的加载顺序。页面的多个script之前通常会有依赖关系,但由于这种依赖关系是隐式的,除了添加注释以外很难清晰地指明谁依赖了谁,这样当页面中加载的文件过多时就很容易出现问题。
- 每一个script标签都意味着需要向服务器请求一次静态资源,在HTTP2还没出现的时期,建立连接的成本是很高的,过多的请求会严重拖慢网页的渲染速度。
- 在每个script标签中,顶层作用域即全局作用域,如果没有任何处理而直接在代码中进行变量声明或函数声明,就会造成全局作用域的污染。
从2009年开始,JavaScript社区开始对模块化进行不断地尝试,并依次出现了AMD、CommonJS、CMD等解决方案,但这些都只是由社区提出的,并不能算语言本身的特性。在2015年,ES6正式定义了JavaScript模块标准。模块化解决了上述的所有问题:
- 通过导入和导出语句可以清晰地看到模块间的依赖关系。
- 模块可以借助工具来进行打包,在页面中只需要加载合并后的资源文件,减少了网络开销。
- 多个模块之间的作用域是隔离的,彼此不会有命名冲突。
ES6模块标准目前已经得到大多数现代浏览器的支持,但在实际应用方面还需要等待一段时间,主要有以下几点原因:
- 无法使用Webpack的两个特别重要的特性:code splitting和tree shaking。
- 大多数npm模块还是CommonJS的形式,而浏览器并不支持其语法,因此这些包没有办法直接拿来用。
- 仍然需要考虑个别浏览器及平台的兼容性问题。
如何能让工程在使用模块化的同时也能正常运行在浏览器中呢?就需要使用模块打包工具了。模块打包工具的任务就是解决模块间的依赖,使其打包后的结果能运行在浏览器上。它的工作方式主要分为两种:
- 将存在依赖关系的模块按照特定规则合并为单个JS文件,一次全部加载进页面中。
- 在页面初始时加载一个入口模块,其他模块异步地进行加载。
Webpack:
Webpack是一个打包模块化JavaScript的工具,在Webpack里一切文件皆模块,通过Loader转换文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。
Webpack在启动后会从Entry里配置的Module开始,递归解析出Entry依赖的所有Module。每找到一个Module,就会根据配置的Loader去找出对应的转换规则,对Module进行转换后,再解析出当前Module依赖的Module。这些模块会以Entry为单位进行分组,一个Entry及其所有依赖的Module被分到一个组也就是一个Chunk。最后,Webpack会将所有Chunk转换成文件输出。在整个流程中,Webpack会在恰当的时机执行Plugin里定义的逻辑。
Webpack的优缺点:
Webpack的优点是:
- 专注于处理模块化的项目,能做到开箱即用、一步到位。
- 可通过Plugin扩展,完整好用又不失灵活。
- 使用场景不局限于Web开发。
- 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展。
- 良好的开发体验。
Webpack的缺点是只能用于采用模块化开发的项目。
安装Webpack:
Webpack对于操作系统没有要求,使用windows、Mac、Linux操作系统均可。它唯一的依赖就是Node.js。
全局安装:
使用Node.js的包管理器npm来安装Webpack到全局。全局安装的好处是npm会帮我们绑定一个命令行环境变量,一次安装,处处使用。
命令:npm i -g webpack
本地安装:
使用Node.js的包管理器npm来安装Webpack到本地。本地安装会添加其成为项目中的依赖,只能在项目内部使用。
命令:
npm i -D webpack
:安装最新的稳定版;npm i -D wenpack@<version>
:安装指定版本;npm i -D webpack@beta
:安装最新的体验版本;
npm i -D
:是npm install --save-dev
的简写,是指安装模块并保存到package.json的devDependencies。
推荐本地安装的方式,原因是:
- 如果采用全局安装,那么在与他人进行项目协作的时候,由于每个人系统中的Webpack版本不同,可能会导致输出结果不一致。
- 部分依赖于Webpack的插件会调用项目中Webpack的内部模块,这种情况下仍然需要在项目本地安装Webpack,而如果全局和本地都有,则容易造成混淆。
使用Webpack:
- 新建一个目录webpack-demo,从命令行进行该目录,并执行npm的初始化命令
npm init
,会生成package.jaon文件(相当于npm项目的说明书,里面记录了项目名称、版本、仓库地址等信息)。
- 在项目的根目录下执行
npm install webpack webpack-cli –-save-dev
命令,安装Webpack到本地,会生成node_modules文件夹和package-lock.json文件,而且package.json中的devDependencies下也增加了webpack和webpack-cli。
- 新建HTML和JS文件:
- 在项目的根目录下执行
webpack --entry=./index.js --output-filename=bundle.js --mode=development
命令进行打包。
5. 用浏览器打开index.html即可看到效果。
使用npm scripts:
从上面的例子发现,每进行一次打包都要输入一段冗长的命令,这样做不仅耗时而且容易出错。为了使命令行指令更加简洁,可以在pageage.json中添加一个脚本命令scripts,scripts是npm提供的脚本命令功能,在这里可以直接使用由模板所添加的指令。
- 在package.json文件中配置scripts。
"scripts": { "build": "webpack --entry=./index.js --output-filename=bundle.js --mode=development" }
- 可以对add-content.js的内容稍加修改,然后执行
npm run build
命令重新打包。 - 浏览器打开index.html即可验证效果。
使用默认目录配置:
上面的index.js是放在工程根目录下的,而通常情况下我们会分别设置源码目录与资源输出目录。工程源代码放在/src中,输出资源放在/dist中。
- 在工程中创建一个src目录,并将index.js和add-content.js移动到该目录下。对于输出资源目录来说,Webpack已经默认是/dist,我们不需要做任何改动。而且,Webpack默认的源代码入口就是src/index.js,因此可以省略掉entry的配置了。
- 重新执行
npm run build
命令进行打包,打开浏览器查看效果。
使用配置文件:
从之前在package.json中添加的脚本命令来看,当项目需要越来越多的配置时,就要往命令中添加更多的参数,那么到后期维护起来就会相当困难。为了解决这个问题,可以把这些参数改为对象的形式专门放在一个配置文件里,在Webpack每次打包的时候读取该配置文件即可。Webpack的默认配置文件为webpack.config.js。
- 在工程根目录下创建webpack.config.js,并添加如下代码。
module.exports = { entry:'./src/index.js', output:{ filename:'bundle.js' }, mode:'development' }
- 去掉package.json中配置的打包参数。
"scripts": { "build": "webpack" }
- 对add-content.js的内容稍加修改;然后执行npm run build,Webpack就会预先读取webpack.config.js,然后进行打包;完成之后打开index.html进行验证即可。
webpack-dev-server:
webpack-dev-server的作用是启动一个本地服务,可以处理打包资源与静态文件的请求。当服务启动时,会先让Webpack进行模块打包并将资源准备好;当webpack-dev-server接收到浏览器的资源请求时,首先会进行URL地址检验,如果该地址是资源服务地址,就会从Webpack的打包结果中寻找该资源并返回给浏览器,反之如果请求地址不属于资源服务地址,则直接读取硬盘中的源文件并将其返回。
webpack-dev-server的两大职能:令Webpack进行模块打包,并处理打包结果的请求资源;作为普通的Web Server,处理静态资源文件请求。
直接用Webpack开发和使用webpack-dev-server有一个很大的区别:前者每次都会生成bundle.js,而webpack-dev-server只是将打包结果放在内存中,并不会写入实际的bundle.js,在每次webpack-dev-server接收到请求时都只是将内存中的打包结果返回给浏览器。
这一点可以通过删除工程中的dist目录来验证,会发现即使dist目录不存在,刷新页面后功能仍然是正常的。
-
安装命令:
npm install webpack-dev-server --save-dev
。安装指令中的–save-dev参数是将webpack-dev-server作为工程的devDependenceis(开发环境依赖)记录在package.json中。这样做是因为webpack-dev-server仅仅在本地开发时才会用到,在生产环境中并不需要它,所以放在devDependencies中是比较恰当的。
-
为了便捷地启动webpack-dev-serve,在package.json中添加一个dev指令。
-
编辑webpack.config.js,对webpack-dev-server进行配置。
-
更改一下add-content.js,执行
npm run dev
并用浏览器打开http://localhost:8080/
即可看到效果。
webpack-dev-server还有一项很便捷的特性就是live-reloading(自动刷新),当webpack-dev-server发现工程源文件进行了更新操作就会自动刷新浏览器,显示更新后的内容,该特性可以提升本地开发的效率。可以保持本地服务启动以及浏览器打开的状态,到编辑器去更改add-content.js,此时切回到浏览器,会发现浏览器的内容自动更新了。
后面还会学到hot-module-replacement(模块热替换),甚至不需要刷新浏览器就能获取更新后的内容。
使用Loader:
Loader具有文件转换的功能。webpack.config.js配置里的module.rules数组配置规则,告诉Webpack在遇到哪些文件时使用哪些Loader去加载和转换。
3. 新建main.css文件,为项目引入CSS代码以让文字居中显示。
4. Webpack将一切文件看作模块,CSS文件也不例外。要引入CSS文件,需要像引入JS文件那样,修改入口文件main.js。
5. 但是这样修改后去执行Webpack构建是会报错的,因为Webpack不原生支持解析CSS文件。要支持非JS类型的文件,需要使用Webpack的Loader机制。首先安装要使用的Loader:npm i -D style-loader css-loader
;然后修改webpack.config.js中的配置。
6. 重新执行webpack
命令构建,会发现bundle.js更新了,里面注入了在main.css中写的CSS,而不会额外生成一个CSS文件,而且重新刷新index.html,“Hello,Webpack”也居中了。
CSS可以被写在JS里,都是style-loader的功劳。它的工作原理大概是将CSS的内容用JS里的字符串存储起来,在网页执行JS的时候通过DOM操作,动态地向HTML head标签里插入 HTML style标签。这样做会导致JS文件变大并且加载网页的时间变长,可以通过Webpack Plugin机制来让Webpack单独输出CSS文件。
使用Plugin:
Plugin是用来扩展Webpack功能,通过在构建流程里注入钩子实现。
Webpack是通过plugins属性来配置需要使用的插件列表的。plugins属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时通过构造函数传入这个组件支持的配置属性。
- 上面通过Loader加载了CSS文件,现在通过Plugin将注入bundle.js文件里的CSS提取到单独的文件中。首先安装插件
npm i -D extract-text-webpack-plugin
;然后修改webpack.config.js中的配置。
ExtractTextPlugin插件的作用是提取出JS代码里的CSS到一个单独的文件中。
filename属性告诉插件输出的CSS文件名称是通过[name]_[md5:contenthash:hex:8].css字符串模板生成的,里面的[name]代表文件的名称,[md5:contenthash:hex:8]代表根据文件内容算出来的8位的Hash值。
执行
webpack
命令构建,会报错:Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead。
原因:extract-text-webpack-plugin还不能支持webpack4.0.0以上的版本。
解决方法:执行npm install -–save-dev [email protected]
命令。
- 重新执行
webpack
命令构建,dist目录下多出了一个CSS文件,bundle.js文件里也没有CSS代码了。将该CSS文件通过link标签引入index.html中即可。