Webpack
概念
本质上,webpack 是一个现代 JavaScript 应用程序的 static module bundler(静态模块打包器)。当 webpack 处理应用程序时,它内置从 一个或多个入口 构建一个 依赖图(dependency graph),然后将项目所需的每个模块组合成一个或多个 bundles ,它们均是静态资源,为您的内容提供服务。
核心概念
为了更好理解模块打包器背后的理念及地层中如何运作的,请参考以下资源:
- Manually Bundling an Application
- Live Coding a Simple Module Bundler
- Detailed Explanation of a Simple Module Bundler
Entry
入口起点指示 webpack 用于开始构建内置 dependency graph 的模块。Webpack 将找出入口起点直接或间接以来的模块和库。
单入口(简写)语法
module.exports = {
entry: "./path/to/my/entry/file.js", // 这是下面语法的简写
entry: {
main: './path/to/my/entry/file.js',
},
}
当然,你可以传递一个 path[]
给 entry
属性,这讲创建一个所谓的 "multi-main entry"。当你想将多个依赖一起注入并且将它们依赖关系绘制成一个 chunk
。
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js'],
output: {
filename: 'bundle.js',
},
};
当您希望通过一个入口点(即库)快速为应用程序或工具设置 Webpack 配置时,单入口语法是一个不错的选择。但是,使用此语法扩展或扩展配置没有太大的灵活性。
对象语法
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js',
},
};
Tip
当您只有插件生成的入口点时,您可以将空对象 传递给entry
。
常见场景
分离 app(应用程序) 和 vendor(第三方库)入口
module.exports = {
entry: {
main: './src/app.js',
vendor: './src/vendor.js',
},
// webpack.prod.js
output: {
filename: '[name].[contenthash].bundle.js'
},
// webpack.dev.js
output: {
filename: '[name].bundle.js'
}
};
上述代码告诉 webpack 我们配置 2 个单独的入口点。
这样可以在 vendor.js
中存入未做修改的必要 library 或文件(例如 Bootstrap,jQuery,图片等),然后将它们打包在一起成为单独的chunk
。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。
提示:
在 webpack < 4 的版本中,通常将 vendor 作为一个单独的入口起点添加到 entry 选项中,以将其编译为一个单独的文件(与CommonsChunkPlugin
结合使用)。而在 webpack 4 中不鼓励这样做。而是使用
optimization.splitChunks
选项,将 vendor 和 app(应用程序) 模块分开,并为其创建一个单独的文件。不要 为 vendor 或其他不是执行起点创建 entry。
多页面应用程序
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js',
},
};
上面的示例中,告诉 webpack 需要三个独立分离的依赖图。
在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks
为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
Output
Output 属性告诉 webpack 在哪里输出它创建的 bundles ,并决定如何命名它们。
注意,即使可以存在多个
entry
起点,但只能指定一个output
配置。
高级用法
以下是对资源使用 CDN 和 hash 的复杂实例:
module.exports = {
//...
output: {
path: '/home/proj/cdn/assets/[fullhash]',
publicPath: 'https://cdn.example.com/assets/[fullhash]/',
},
};
如果在编译时,不知道最终输出文件的 publicPath
是什么地址,则可以将其留空,并且在运行时通过入口起点文件中的 __webpack_public_path__
动态设置。
__webpack_public_path__ = myRuntimePublicPath;
// 应用程序入口的其余部分
Loader
开箱即用,webpack 仅理解 JavaScript 和 JSON 文件。Loaders 允许 webpack 处理其他类型的文件,并转化为有效的 modules ,以供你的应用程序使用,且添加到 dependency graph 中。
Webpack 一个特殊的功能就是可以
import
任何类型的模块,例如.css
文件,这在其他打包程序或任务执行器中可能并不支持。
官方认为这种语言扩展很有必要,这可以允许开发人员创建出更准确的依赖关系图。
loader 用于对模块的源代码进行转换。loader 可以使你在 import
或 "load(加载)" 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import
CSS 文件!
配置
Plugins
虽然 loaders 被用于转换某些类型的模块,plugins 可以用来执行更广泛的任务,例如 打包优化
, 资源管理
和 注入环境变量
。
由于你可以根据不同的目的使用一个插件好几次,因此你需要通过 new
来创建它的实例。
在 webpack 配置中使用插件非常简单。然而,许多使用用例都值得进一步探索。Learn more about them here。
Mode
通过设置 mode
字段为 development
、production
或者 none
,你可以启用 webpack 的与每个环节相对应的内置优化,默认值为 production
。
Browser Compatibility
Webpack 支持所有符合 ES5-compliant (IE8及以下版本不支持)的浏览器。Webpack 需要 Promise
来实现 import()
and require.ensure()
。如果你想支持旧版浏览器,你需要在使用这些表达式之前 load a polyfill。
Targets
不要将
target
和output.libraryTarget
混淆。
相关链接:WebpackLibrary和LibraryTarget详解
Manifest
在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型:
- 你或你的团队编写的源码。
- 你的源码会依赖的任何第三方的 library 或 "vendor" 代码。
- webpack 的 runtime 和 manifest,管理所有模块的交互。
Runtime
runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。
manifest
一旦你的应用在浏览器中以 index.html
文件的形式被打开,一些 bundle 和应用需要的各种资源都需要用某种方式被加载与链接起来。在经过打包、压缩、为延迟加载而拆分为细小的 chunk 这些 webpack 优化
之后,你精心安排的 /src
目录的文件结构都已经不再存在。所以 webpack 如何管理所有所需模块之间的交互呢?这就是 manifest 数据用途的由来……
当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法,那些 import
或 require
语句现在都已经转换为 __webpack_require__
方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。