什么是webpack?
webpack是一款为现代JavaScript应用设计的静态资源打包工具。
作为JavaScript工程师,我们知道模块是什么,但是在webpack中稍微有些不同。它们可以是:
ES模块-import语句CommonJS模块-require()语句AMD模块-define和require语句CSS文件-在css/sass/less文件中的
import语句图片URL-url(...)或imgsrc=”...”/webpack把这些不同的模块进行了统一处理,可以把它们导入到JavaScript代码里。
我应该学习webpack吗?
现在大多数应用是用React/Vue或其他框架编写的。它们提供了命令行工具(如create-react-app,
vue/cli)来创建应用。这些命令行工具简化了大部分的配置,提供了默认的配置。但是我们作为开发者,理解它的工作原理是必要的,因为我们早晚都需要修改一些默认的配置。开始学习
我们会创建一个简单的应用来展示webpack的用法。首先新建一个目录并初始化一个npm项目。假设你进入了新建的目录,在命令行中执行:
现在安装所需的webpack包:
打开package.json文件,删除其中的test脚本命令,然后增加一个新的脚本命令dev以在开发模式下运行webpack。我们在本地开发测试时需要用到这个命令。现在你的package.json文件看起来如下:
如果现在你运行npmrundev,会遇到一个烦人的报错:Entrymodulenotfound。这是因为默认情况,webpack会使用src/index.js这个文件作为入口。此外,webpack在默认情况下把打包后的文件输出到名为dist的目录里。
好了,现在我们创建一个目录src并在里面新建一个文件index.js。现在我们暂时在这个文件里只写一句console.log(‘hello’)。这样我们就可以解决这个报错。
现在再运行npmrundev,这次不会遇到错误,同时你会发现打包后的文件main.js出现在了名为dist的目录里。
配置webpack
要配置webpack,你需要在项目的根目录里含有一个名为webpack.config.js的配置文件。在此文件中,我们需要导出一个配置对象。
一些最常用到的术语有:
Entrypoint-指定webpack的入口,从此入口找到的所有模块依赖都会收集起来。这些依赖关系构成了依赖图谱。Output-用于指定输出的JS和静态文件存放的位置。Loaders-帮助webpack处理各种文件后缀的第三方扩展。它们会把非JS文件转化为模块。Plugins-可以修改webpack行为的第三方扩展。Mode-用于指定两种模式,开发模式和线上模式。默认是线上模式。
现在我们配置一下入口和输出的目录。
修改入口
如果我们想让webpack以source/index.js作为入口,而不是默认的src/index.js文件。需要在导出的对象里增加一个名为entry的属性。
也可以这样写:
修改输出的目录
假设我们想把打包的文件输出到另一个名为build的目录里,而不是默认的dist目录。我们这样设置output属性如下:
在HTM文件里引入打包后的文件
在每个web应用里,至少有一个HTML文件。要实现这个目标,我们需要用到一个名为html-webpack-plugin的插件。首先执行如下命令安装插件:
这个插件究竟是干什么的?
它会加载我们的HTML文件,然后把打包的文件注入到HTML页面里。
我们先新建一个简单的HTML文件index.html放在source目录中,在里面填写一些简单的示例代码。现在修改我们的webpack配置如下:
这样文件已经就绪了,我们需要一个服务器来展示这个页面。现在可以用webpack-dev-server来启动服务器,我们已经在之前的步骤中安装好了。
Webpackdevserver
要配置webpack-dev-server,我们需要打开package.json文件,然后增加一个新的脚本命令来启动服务器。例如,我们增加一个名为start的命令如下:
现在在命令行执行这个命令:
打开浏览器访问localhost:,你会看到我们的index.html文件,打开浏览器的开发者工具可以看到打包后的main.js已经嵌入到了此文件中。
使用webpack加载器
如前所述,webpack中的加载器是处理各种其他文件后缀的第三方扩展。webpack有很多加载器可供使用。
让我们继续在webpack配置文件中配置加载器。module字段里有一个rules的属性,它是一个由加载器组成的数组。对于想要作为模块处理的文件,我们需要把它们作为对象放在rules数组里。每个对象由两个属性组成:test定义文件的类型,use是一个由加载器组成的数组。需要注意的是在use数组里的加载器是从右向左加载的。定义加载器时的顺序很重要。建议使用加载器前阅读对应的文档。
在配置文件中加载器的配置句法类似于这样:
处理CSS
为了能在webpack中处理CSS,我们需要两个加载器:css-loader和style-loader。首先在命令行中安装它们:
我们在source目录中新建一个styles.css文件,然后添加一些样式,在构建完成后会应用到index.html中。接下来我们要在index.js文件中引入它,而不是在index.html中。所以现在index.js看起来如下:
还有最后一步要做,那就是在webpack.config.js中配置加载器。我们的配置文件如下:
css-loader用于加载CSS文件,而style-loader用于在DOM中加载样式。
重新运行npmstart,可以在浏览器中看到变化:
处理SASS文件
要处理SASS(.scss)文件,我们需要三个加载器:sass-loader,css-loader和style-loader,此外还需要一个NPM包sass。这里的sass-loader用于导入SASS文件。由于另外两个加载器已安装,现在只需安装其他包即可。
我们在source目录下新建styles.scss文件,然后增加如下代码。
接下来修改index.js文件,在文件的开头使用import‘./styles.scss’导入此文件。
现在webpack.config.js中增加加载器。我们的加载器看起来如下:
重启服务器,检查一下页面是否发生了变化。
处理现代JavaScript句法
Webpack自身不知道如何把现代JavaScript句法转成所有浏览器支持的格式。所以它使用了Babel来解决这个问题。我们需要安装如下包:
babel/core,它是实际的处理引擎;babel-loader,是webpack使用的加载器;babel-preset-env用于把JavaScript代码转成ES5。我们运行如下命令安装依赖包:下一步是配置Babel,我们需要在项目根目录新增一个文件babel.config.json。在这里,我们配置Babel使用刚安装的preset-env:
然后在webpack.config.js文件里增加我们刚安装的加载器。
现在我们的JS代码中使用ES6句法,然后运行npmrundev重新构建,可以在生成的main.js里看到已经自动转换成了浏览器支持的ES5代码。
在webpack里指定模式
在webpack里有两种模式:开发模式和线上模式。
在开发模式下,不会执行代码压缩。webpack只是把我们编写的JS直接加载入浏览器中,所以在浏览器中刷新应用很快。
在线上模式下,webpack会做很多优化。它会自动使用terser-webpack-plugin来压缩代码减少打包后文件的体积。还会设置process.env.NODE_ENV为production。这个环境变量很有用,我们可以根据不同的执行环境切换不同的任务。
要在线上模式下使用webpack,让我们增加另外一个脚本命令到package.json文件里,我们把它命名为build。现在脚本命令看起来如下:
优化-代码分拆
代码分拆或延迟加载是一项优化技术,用来避免包文件体积过大,也可以避免依赖重复打包。采用此机制后,我们可以按需加载代码,例如当用户点击一个按钮时,路由变化时。代码片段也称之为chunk。
在webpack中如果打包后文件体积超过KB可能会出现警告信息。有三种方式可以在webpack中实现代码分拆:
配置多个入口文件使用optimization.splitChunks动态导入
第一种方法对于小型项目很合适,但是对于复杂项目不够灵活。在webpack的配置文件中指定多个入口即可。
使用optimization.splitChunks
有时候我们需要在web应用里用到很多依赖。例如,我们要使用一个流行的日期库Moment。我选择这个库是因为它有点大。让我们安装它然后在index.js中导入它,然后运行build命令。
Moment会成功安装。现在index.js中导入这个库。
现在运行build命令。
你会在终端看到这个警告信息:
我们该怎么解决这个问题呢?很简单。让我们在webpack配置文件中增加一个optimization以及另一个名为splitChunks的属性:
你会发现入口文件的体积极大得缩小了。
动态导入
动态导入用于按条件加载代码。这种方式广泛用于React和Vue项目里。我们可以基于用户操作或者路由改变加载代码。
为了演示这种方式,我们在页面上增加一个按钮,点击后会查询获得一个帖子列表。用于查询的代码放在另一个单独的文件里,然后在index.js中动态导入它。
在source目录下新建一个api.js文件,在里面使用fetchAPI查询帖子列表。在这个文件中,我们导出一个函数,这个函数调用后返回一个promise。
我们在index.html中增加一个按钮,设置它的id为btn。
在index.js文件里,使用一个函数来动态导入api.js文件。
此外,在index.js的结尾增加这段逻辑记录获取的数据。
这段代码调用getTodos函数,会导入这个文件。在导入之后,我们从中解构出fetchTodos属性,调用此函数,然后成功后记录响应值。
我们编译文件,运行服务器,确保开发者工具是开启的并切换到其中的Network面板。你会发现点击后这个新建的JS文件被动态加载了。
图片中的0.main.js就是那个被动态加载的文件。如果你想让这个文件名更可读,只需要在动态加载时增加一个注释即可:
现在再次重复这个过程,浏览器会加载名为postAPI.js的文件,而不是0.main.js。
结语
这只是webpack的一个简单的介绍。就像我们之前说的,这篇文章主要是写给开始学习webpack的初中级web开发工程师的。当然了,在webpack中有许多可以学习的知识点。我们只是过了一下最基础的部分。如果你对webpack感兴趣,想学习更多内容,请参考webpack官方文档。