Webpack核心概念解析
现在的Web页面越来越复杂并向着web app的方向演化,这意味着前端将会有着大量的代码。而一个复杂又庞大的代码库必然需要模块系统来分治,既然有了模块系统就少不了模块依赖的管理与打包,这就是Webpack的主要职责了。当然关于模块化的演进有许多东西可以讨论,今天主要还是从目前最流行的Webpack开始学习。
配置(Configutation)
Webpack最简单的启动方法就是在命令行输入相关打包指令,但一般为了用到其丰富的特性,通常都会创建一个配置文件。默认情况下会搜索当前目录下命名为webpack.config.js的文件。
该文件是一个node.js模块,通常使用时会导出一个JS对象。我们通过定义该对象的属性——即是webpack提供的选项去交给webpack按照我们想要的方式去解析并打包。
需要注意的是,该配置文件同样可以做到其他node.js模块能做的事情帮我们跟方便的配置选项。包括但不限于如下:
- 通过require()导入其他文件或工具函数
- 使用JS语法更方便的添加选项
- 关键值设置为变量,常量或使用函数生成
一个简单的配置文件结构如下:
let path = require('path');
module.exports = {
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
可以看到我们用到了node相关模块轻松创建了可拓展的配置。其中的一些选项可能会陌生,不过这正是我接下来需要重点介绍的东西。
入口(entry)
webpack将相互依赖的模块打包到一起,那么就需要知道这些依赖的起点。入口选项的作用就是告诉webpack从哪里开始打包。
该选项接受3种形式的值:字符串,数组,对象,但其实字符串和数组形式都是对象的简化形式,默认键名为'main',值就是该字符串或数组了。对象中的每一对键值对都代表着一个入口文件。
key值为打包后生成出口文件的[name]变量,当为路径字符串时,打包后会自动生成路径目录并将路径的最后作为[name]。
value值就代表着JS入口的文件名了。该值同样有3种选择。
字符串
必须是能被模块机制找到的文件。例如'./app.js'会被webpack解析为require('./app.js')去查找入口文件。
数组
一般是由于数组中的文件存在引用,但是没有相互依赖的情况下,就需要将他们手动打包在一起。例如html中可能引入了谷歌统计JS文件,但和主JS文件又没有依赖就可以使用数组形式打包。
对象
假设应用是多页面的,即存在多个html文件分别引用不同的bundle文件,则可以通过对象配置为每一个html生成对应的打包文件。
这里可能就有疑惑了,如果根据不同的html去打包不同的依赖,那么必然会存在相同依赖重复打包的情况。为了解决这种多余打包的情况,最直观的解决办法就是将代码中的公共模块抽出独立打包并给不同的bundle复用。这正是CommonChunks插件作用了。现在暂略,插件机制在其相关文章模块再分析。
--
出口(output)
该选项控制如何向硬盘写入打包文件,与可能存在多个的入口不同,输出位置只能有一个。该选项为一个对象,键名filename设置输出文件的文件名,path设置输出的绝对路径。这两个键名是配置时必须要设置的。
还有一个常用的可选键publicPath,该键指定此输出目录的公开URL,webpack打包时遇到引用文件会通过publicPath重新合成,通常用来解决本地开发和部署静态资源到CDN上时,URL引用路径不一致的问题。
--
模块(module)
该选项如其名,专注于处理模块间的转化问题。在webpack中一切都是模块,但模块语法与规范又是多种多样,正是因为都在这个选项中做了统一处理。可以统一处理的模块语法如下:
- ES6的import语句
- CommonJS的require语句
- AMD的define和require语句
- 样式文件中的@import语句
- 样式和HTML中的静态资源url
在模块选项中最重要的键名就是rules了,其值为一个数组,每个数组元素定义一个规则,用来匹配不同类型的模块并针对进行解析。每个规则可以分为如下两个部分:
条件
条件有两种输入方式,用来匹配需要处理的模块来源。
- resource: 模块文件的绝对路径。
- issuer: 请求模块文件的模块文件的绝对路径。
这么看可能有点绕,通俗的说由于在webpack中一切都是模块,所以模块依赖文件同时也是模块提供文件。举个简单的例子,A模块引用了B模块,B模块中又引用了C模块,那么resource匹配的就是B,C绝对路径。issuer匹配的就是A,B绝对路径。
属性test,include,exclude为rescource匹配,resource为issuer匹配。
结果
当模块匹配成功时就要应用解析结果了。解析结果的应用有两种,一种为loader,用于对源模块按其loader规则解析。另一种为parser,可以开启或关闭匹配文件的webpack解析用来优化性能。
与结果匹配的最重要的属性就是use了,该属性为数组,定义了应用于匹配模块的loader列表。每个数组元素为一个useEntry对象,用来描述loader的使用方式。必须要有一个loader字符串属性,规定使用的loader类型,可选属性为options,用来决定是否开启一些loader的特定功能。
一个常见的rules配置如下:
rules: [
{
//resource型条件
test: /\.css$/,
// 结果
use:[
// useEntry
{
loader: 'css-loader',
// 可选条件
options: {
...
}
}
]
}
]
这就代表对引入的css文件使用css-loader转化为webpack可处理的JS模块进行打包了。当然以上是最易拓展最全面的写法,当我们不需要某些选项时,也可以用简写形式快捷定义。
rules: [
{
test: /\.css$/,
loader: 'css-loader'
}
]
与上例达成的效果是一样的,而且更加易读,只是不容易拓展选项了。
loader特性
可以看出module中的loader处理是webpack的核心,在webpack中所有文件都可以作为模块处理,而webpack本身是只能理解JS的,这就需要在分析依赖时(规则的条件)去使用loader(规则的结果)将文件转化为webpack理解的一部分并添加到依赖图里用于最后的bundle文件依赖分析。
上文中我们可以看到,对于同一组匹配的use可以使用多个loader,这是由于loader的链式传递特性,能够对匹配资源进行流水线式从上到下的处理,在最后一个loader返回预期的JS模块。
解析(resolve)
模块间的相互引用少不了路径的指定,该选项用于配置webpack解析模块路径的相关属性。比较常用的属性有alias,其用来指定一些常用但是嵌套较深的路径的别名来确保模块引入变得简单,在键后末尾添加$表示精准匹配。例子如下:
alias: {
xyz$: path.resolve(__dirname, 'path/to/file.js')
}
import Test from 'xyz'; // 精确匹配成功,从path/to/file.js导入模块
import Test from 'xyz/file.js'; // 精确匹配失败,触发普通路径解析
插件(Plugins)
插件也是webpack的核心功能,主要用来完善loader打包过程中可以优化的一些功能点。例如代码丑化,分块打包,输出文件头部添加注释等等,算是在webpack的loader主功能上附加的一堆实用的副功能。使用也非常简单,每个插件都是一个JS对象,向plugins属性数组中的每个元素传入new实例即可。
-- EOF --