Webpack - 核心概念
如果你稍微了解过 HTTP,肯定知道一大堆文件请求对于浏览器是什么样的灾难。然而,数以百计的 JS 文件和 CSS 文件在现在的 Web Applications 中已经是常态了,这么多的文件如果一个个通过 HTML 标签引入,别说浏览器,自己都要疯掉。
简单来说,Webpack 就是一个打包工具,它可以让你把众多文件,合并成一个。就像 Webpack 官网图里展示的那样。
bundle everything对一个没有引入 Webpack 的项目来说,如果要实现这种效果,最先遇到的问题就是:
- 要怎么打包?
- 打包之后的文件会在哪?
解决了这两个问题,我们就能在项目里开始使用 Webpack 了。
所有问题的答案,都可以在 Webpack 的配置文件 webpack.config.js
中找到。
基础概念
对于第一个问题,要怎么打包?
这是 Webpack 要考虑的问题,但同时我们也需要考虑 -- 怎么让 Webpack 知道把 哪些文件 打成一个 bundle?
Entry
以 JS 文件为例,通常来说,项目中的 JS 文件之间都是相互依赖的。你极有可能需要把 util.js
通过 import
引入另一个业务相关的文件 work.js
。那么 work.js
在被打包的时候, 它依赖的 util.js
也需要被打包在同一个 JS 文件中, 程序的逻辑才不会被破坏。
如果通过引用一直向上溯源的话,通常会找到命名类似 src/index.js
的文件,它直接或间接的引用了几乎所有的 JS 文件(Webpack 可以将文件打包为多个,这里我们只考虑打包为一个的情况,认为 index.js
引用了所有文件)。那么我们如果从 index.js
出发,将它所有直接或间接通过 import
引用的文件全部都打包进一个 JS 文件 ,那 index.js
就是我们打包的开始、起点、入口,Webpack 中称为:entry。
module.exports = {
entry: './src/index.js'
};
entry 可以接收字符串、对象和数组。
Webpack 认为,如果一个文件依赖于另一个文件(非代码资源,像是图片,Web 字体等),就存在 dependency。Webpack 从 entry 开始处理,递归地建立一个 dependency graph,包含工程所需的所有文件,最终将它们打包为一个文件供浏览器加载。
那这个文件最终会在哪,文件名又是什么呢?知道这些信息我们就可以在 index.html
中直接引用它。
Output
这个问题的答案当然是自己定,我们可以通过 output 属性指定将来打包完毕的文件所在的路径,以及文件名。Webpack 4 中的默认值为 ./dist/main.js
( dist : distribution ),配置方式:
const path = require('path');
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
path
和 filename
分别指定 bundle 文件所在文件夹的绝对路径的和文件名。
似乎问题已经解决了?是的,目前为止我们已经可以打包 JS 了。
只不过还有一些问题,比如我们需要打包的 JS 文件中不能使用 ES6 的特性,不能在 JS 中引入 CSS 文件(包括 SASS 文件),打包后文件数量数量少了但 bundle 文件所占空间还是比较大。
前面两个问题是打包途中就需要考虑的,如果 Webpack 不能处理引入的文件(CSS 文件 和 浏览器不能辨识的 ES6 JS 文件),那打包的过程势必会收到影响。
最后一个问题在 bundle 文件生成后处理就可以,比如压缩一下文件。
这两类问题都需要额外的工具协助。
对于第一类问题,我需要可以解析引入到 JS 的 CSS 文件的工具(css-loader
和 style-loader
),以及将 ES6 标准下的 JS 转为浏览器能识别版本的工具( babel-loader
)。 Webpack 将这些解析工具称为 loader。
Loaders
Webpack 只理解 javascript 和 json,Loaders 可以让 Webpack 将其他文件转化为 module,随后就可以加入 denpendency graph。
Loaders 有两个属性可以在 config 文件中配置:
-
test
: 指定要转化的文件 -
use
:指定转化文件使用的 loader
对于上面的问题:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
翻译一下:
- 当 webpack 的编译器遇到某个文件中
require/import
以js
结尾的文件时, 先使用babel-loader
转化再打包。 - 当 webpack 的编译器遇到某个文件中
require/import
以css
结尾的文件时, 先使用css-loader
和style-loader
转化再打包。
对于第二类问题,对打包好的 bundle 文件的处理,比如优化、压缩等等。通常通过一些插件进行操作。
Plugins
比如常用的压缩,Webpack 内置这个插件。
这里我们更进一步,通过 Webpack 插件生成 HTML 文件,并且自动引入 bundle 之后的文件。显然,Webpack 需要知道生成的 HTML 文件是什么样子的,也就是它需要一个样板、模板( template ),我们假设程序的入口 HTML 文件是 src/index.html
,通过插件 html-webpack-plugin
来进行这个操作。
使用 plugin 需要先通过 require
引入,再加入到 plugins
数组中。
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin({minimize: true}),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
现在我们直接使用 dist/index.html
就好了, Webpack 已经打包了 JS、CSS 文件并一并注入了这个 HTML 文件。
Plugin 还可以执行更广泛的任务,如打包的优化,资源管理和环境变量注入。大多 plugin 都可以定制化。
汇总
一份包含上面所有概念的 webpack.config.js
看起来差不多是这样:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({minimize: true}),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
打包入口 entry ,输出路径 output,解析文件的 loader ,打包文件处理的插件 plugin。
今天就到这里了。