内容简介:Webpack提出了
Webpack 是现代Web应用程序的静态模块打包工具,它会递归的构建应用程序各个模块的依赖关系图,然后将所有模块打包成一个或多个 bundle 。截止本文成文之时,Webpack已经更新至 4.0.1 版本,增加了诸多打包和执行性能相关的支持,是目前应用最广泛、社区最活跃的Web前端代码打包方案。
Webpack提出了 入口 entry 、 输出 output 、 加载器 loader 、 插件 plugin 这四个核心概念,本文将会在简单介绍Webpack相关基础概念之后,对其原生实现的 import 模块导入机制进行分析,以清晰的展现Wepback在底层所进行的工作;最后逐步备注笔者在开发、生产环境下使用到的各类插件和加载器,并分享在 aves 和 rhino 两个开源脚手架项目当中( 分别基于Vue2和React16 )所使用到的最佳配置实践。
入口entry
entry 属性用来指定webpack应该使用哪个模块作为构建工作的起点,进入起点后webpack将会寻找入口起点直接或间接的依赖,并最后输出到称为 bundle 的文件。Webpack中的 entry 属性值可以是一个或多个字符串、一个对象或数组。
下面的示例代码当中只定义了一个路径字符串:
module.exports = {
entry: './app.js'
};
而下面的示例代码则通过传入对象指明了多个入口点,实质上Webpack会使用 CommonsChunkPlugin 从应用bundle中提取 vendor 引用到 vendor bundle ,并将引用 vendor 的部分替换为 __webpack_require__() 调用。
const config = {
entry: {
app: './app.js',
vendors: './jquery.js'
}
};
Webpack内置的 CommonsChunkPlugin 可以为每个页面的共享代码创建bundle,使得即使在多页面应用下,也能够复用入口起点之间的大量代码和模块。
输出output
Webpack的output属性指定其所创建的bundles输出到何处以及如何命名,
const path = require('path');
module.exports = {
entry: './app.js',
output: {
filename: 'app.bundle.js' // filename属性用于指定bundle的名称
path: path.resolve(__dirname, 'build'), // path属性指定输出bundle的位置
}
};
Webpack中的 chunk ([tʃʌŋk] n.大块,厚块,数据块)是指 一个独立的文件 ,如果创建了多个 chunk ,则应该使用 占位符 去确保每个文件具有唯一的名称。
const path = require('path');
// 将带有hash字符串后缀的app.js和search.js保存至build目录下
module.exports = {
{
entry: {
app: './app.js',
mobile: './mobile.js'
},
output: {
filename: '[name][chunkhash].js',
path: path.resolve(__dirname, 'build'),
}
}
};
output 属性的 filename 拥有如下5个占位符:
| 占位符 | 描述 |
|---|---|
[id] |
模块标识符。 |
[name] |
模块名称。 |
[hash] |
模块标识符的hash值。 |
[chunkhash] |
chunk 内容的hash值。 |
[query] |
模块的 query ,例如文件名 ? 后的字符串。 |
加载器loader
Webpack的loader加载器机制用于处理非JavaScript文件,并将其转换为Webpack能够处理的有效模块。即将JavaScript之外的其它类型文件,转换为 bundle 可以直接引用的模块;例如:将文件从TypeScript转换为JavaScript,或者把内联图片转换为data URL,甚至允许在JavaScript模块内引入CSS文件。
Webpack配置文件中的 module 属性主要用于加载各种模块,可以通过其内嵌的 rules 属性指定加载某种类型文件时所需要使用到的 loader 加载器。
const path = require('path');
module.exports = {
entry: {
app: './app.js',
mobile: './mobile.js'
},
output: {
filename: '[name][chunkhash].js',
path: path.resolve(__dirname, 'build'),
}
module: {
// 首先需要npm install ts-loader css-loader
rules: [
{
test: /\.ts$/,
use: 'ts-loader'
}, {
test: /\.css$/,
use: 'css-loader'
},
]
}
};
rules 下的 use 属性指定了需要使用的目标加载器,而 test 属性则用于标识需要加载器进行处理的文件。根据需要,开发人员还可以对同一类文件应用多个 loader 进行处理。
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true
}
}
]
}
loader 还可以在 import 语句中内联进行使用,用于针对特定 import 语句使用指定的加载器。
import Styles from 'style-loader!css-loader?modules!./styles.css';
加载器loader的解析是通过 resolver 库进行的, resolver 用于帮助Webpack找到 bundle 中需要引入的模块代码,这些代码包含在每条 require / import 语句当中。Webpack打包模块时会使用到自家的开源项目 enhanced-resolve 来解析引入的文件路径。
插件plugin
如同上面所描述的那样,loader用于转换某些类型的模块,而插件plugin得益于其丰富的接口,可以用来处理加载器loader无法实现的更加丰富的任务,例如:打包优化与压缩、重新定义环境变量等等。
Webpack中通过 plugins 属性来使用插件。
module.exports = {
entry: {
app: './app.js',
mobile: './mobile.js'
},
output: {
filename: '[name][chunkhash].js',
path: path.resolve(__dirname, 'build'),
}
module: {
// 首先需要npm install ts-loader css-loader
rules: [
{ test: /\.ts$/, use: 'ts-loader' },
{ test: /\.css$/, use: 'css-loader' },
]
},
// 使用new操作符来创建一个插件实例
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
})
]
};
Webpack的插件plugin本质是一个拥有 apply 属性的JavaScript对象,这个 apply 属性会被Webpack的 compiler 对象调用,而该对象可以在整个Webpack编译生命周期内进行访问。
// LogOnBuildPlugin.js
function LogOnBuildPlugin() {
// ... ...
};
// 注意下面的apply原型属性和compiler对象
LogOnBuildPlugin.prototype.apply = function(compiler) {
compiler.plugin('run', function(compiler, callback) {
console.info("Webpack构建过程开始!");
callback();
});
};
Webpack模块
相比NodeJS的模块机制,Webpack模块所涵盖的范围显然更加丰富:
- ES2015的
import语句 - CommonJS的
require()语句 - AMD的
define/require语句 - css/sass/less中的
@import语句。 - CSS文件中的
url(...)或HTML文件中的<img src=...>所指向的图片资源链接。
Webpack通过 enhanced-resolve 可以解析三种文件路径:
-
绝对路径:文件在操作系统上的绝对路径。
import "/home/common.js"; import "C:\\Users\\common.js";
-
相对路径:相对于当前引入操作的文件的位置。
import "./source/demo.js"; import "../demo.js"
-
模块路径:在
resolve.modules中指定的目录内搜索,默认是["node_modules"]。import Vue from "vue"; import React from "react";
打包策略
Webpack构建的应用程序中,主要存在以下三种类型的代码:
- 开发人员编写的业务代码。
- 引入的第三方
vendor库。 - Webpack运行时与所有模块进行交互的
manifest( [ˈmænɪfest] n.清单 )。
manifest 是浏览器运行时,Webpack用来连接模块所需的加载和解析逻辑( 无论 import 还是 require 模块语法,最后都会被Webpack转换为指向 模块标识符 的 __webpack_require__ 方法 )。
为了有效规避缓存问题,并最大化浏览器渲染性能,可以考虑将上述三种类型代码单独打包到三类文件。
在Webpack 3.0可以通过 CommonsChunkPlugin 插件完成这件工作。该插件可以将公共的依赖模块提取为一个新的的 chunk 文件,通过将公共模块分拆并且合成之后,便于应用进行缓存和后续使用,避免浏览器重复加载并运行同一段功能代码。
new webpack.optimize.CommonsChunkPlugin({
name: string, names: string[], // 通用chunk的名称。
filename: string, // 公共chunk的文件名模板,可以包含与output.filename相同的占位符。
chunks: string[], // 通过chunk名称选择chunks的来源,其中chunk必须是公共chunk的子模块。
children: boolean, // 如果设置为true,所有公共chunk的子级模块会被选择。
deepChildren: boolean, // 如果设置为true,所有公共chunk的全部子孙模块都会被选择。
async: boolean|string, // 设置为true会创建一个异步的公共chunk,它会作为name的子模块以及chunks的兄弟模块,并与chunks并行被加载。
minSize: number, // 公共chunk创建之前,所有公共模块的最小文件尺寸。
minChunks: number|Infinity|function(module, count) -> boolean, // 传入公共chunk之前,所需要包含chunks的最少数量。
})
entry: {
app: './app.js',
vendor: ['vue', 'vuex', 'vue-router', 'element-ui', 'axios']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
],
Webpack4当中已经废弃了 CommonsChunkPlugin 的使用,转而采用更加简单明了的 optimization.splitChunks 和 optimization.runtimeChunk 属性完成类似工作,下面的章节马上会进行介绍。
optimization
Webpack4开始提供一个依赖于 mode 属性进行配置和优化的选项,代替过去 CommonsChunkPlugin 、 UglifyjsWebpackPlugin 等第三方插件的使用。
minimize
让Webpack使用 UglifyjsWebpackPlugin 进行最小化打包。Webpack配置对象的 mode 属性为 production 时该属性默认为 true 。
module.exports = {
optimization: {
minimize: false
}
}
minimizer
通过提供一个或多个不同的 UglifyjsWebpackPlugin 实例来指定一个新的压缩器。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {=
optimization: {
minimizer: [
new UglifyJsPlugin({
/* 自定义配置 */
})
]
}
}
splitChunks
Webpack4.0为动态导入模块提供了一个新的通用代码文件打包策略,具体配置项可以参考本文中的章节。
runtimeChunk
设置该属性为 true 时,可以添加额外的代码块至每个只在运行时包含的Webpack入口点。该属性可以通过提供一个字符串值来使用插件的预设模式:
single : 建立一个所有代码块共享的运行时文件。
multiple : 为多个通用代码块建立多个运行时文件。
设置该属性为对象时,它只可能去提供 name 属性,为运行时代码块提供可替代的名称或命名工厂。
该属性默认值为 false ,表示每个入口代码块都嵌入到运行时。
module.exports = {
optimization: {
runtimeChunk: {
name: entrypoint => `runtimechunk~${entrypoint.name}`
}
}
}
noEmitOnErrors
当编译阶段出现错误时,使用 optimization.noEmitOnErrors 跳过 emitting 阶段,从而确保不会有错误的资源被 emitted 。 stats 中的 emitted 标志对于所有资源都是 false 的。
module.exports = {
optimization: {
noEmitOnErrors: true
}
};
nodeEnv
告诉Webpack设置 process.env.NODE_ENV 为指定的字符串, optimization.nodeEnv 底层使用了 DefinePlugin 插件,除非设置为 false 。如果Webpack对象设置了 mode 属性,那么 optimization.nodeEnv 默认为 mode 属性的值,否则将会回退为 "production" 。
- 任意字符串:需要设置到
process.env.NODE ENV的值。 -
false:不设置或修改process.env.NODE_ENV的值。
SplitChunksPlugin
DefinePlugin
webpack.DefinePlugin
ModuleConcatenationPlugin
附上 英文原文链接 。
过去webpack打包的时候,每个module都会被包装到独立的函数闭包,这些包装函数会让JavaScript在浏览器中执行更缓慢。经过比较, Closure Compiler 和 RollupJS 提升、连接全部模块作用域到一个闭包的方式,会让代码在浏览器中执行得更加迅速。因此,Webpack3当中提供了如下plugin来开启类似特性。
new webpack.optimize.ModuleConcatenationPlugin()
Webpack3作用域提升的实现依赖于ECMAScript模块语法,因此Webpack会基于开发人员当前使用的模块系统回滚到过去的打包方式。
不使用ModuleConcatenationPlugin打包的文件
➜ bundles git:(master) ll 总用量 8.7M -rw-rw-r-- 1 hank hank 24K 9月 6 11:44 0.210c1525bfe14cba4ee0.js -rw-rw-r-- 1 hank hank 61K 9月 6 11:44 0.210c1525bfe14cba4ee0.js.map -rw-rw-r-- 1 hank hank 5.9K 9月 6 11:44 1.d4b8545dc3dc93af7f6a.js -rw-rw-r-- 1 hank hank 28K 9月 6 11:44 1.d4b8545dc3dc93af7f6a.js.map -rw-rw-r-- 1 hank hank 765 9月 6 11:44 2.57378280a5698f76c576.js -rw-rw-r-- 1 hank hank 6.4K 9月 6 11:44 2.57378280a5698f76c576.js.map -rw-rw-r-- 1 hank hank 244K 9月 6 11:44 app.13fca0a1be03d8b4a15b.js -rw-rw-r-- 1 hank hank 710K 9月 6 11:44 app.13fca0a1be03d8b4a15b.js.map -rw-rw-r-- 1 hank hank 1.4K 9月 6 11:44 app.24004a5d2621177eb1e6bcdabc919636.css -rw-rw-r-- 1 hank hank 1.9K 9月 6 11:44 app.24004a5d2621177eb1e6bcdabc919636.css.map -rw-rw-r-- 1 hank hank 1.6K 9月 6 11:44 manifest.c3fe0dc0d40ea3fac1a5.js -rw-rw-r-- 1 hank hank 15K 9月 6 11:44 manifest.c3fe0dc0d40ea3fac1a5.js.map -rw-rw-r-- 1 hank hank 983K 9月 6 11:44 vendor.128582091fc1c542ae63.js -rw-rw-r-- 1 hank hank 6.7M 9月 6 11:44 vendor.128582091fc1c542ae63.js.map
使用ModuleConcatenationPlugin打包的文件
➜ bundles git:(master) ✗ ll 总用量 8.7M -rw-rw-r-- 1 hank hank 23K 9月 6 11:47 0.331a96e6fa24027ff29f.js -rw-rw-r-- 1 hank hank 60K 9月 6 11:47 0.331a96e6fa24027ff29f.js.map -rw-rw-r-- 1 hank hank 5.8K 9月 6 11:47 1.241c026697aa61a8ca09.js -rw-rw-r-- 1 hank hank 27K 9月 6 11:47 1.241c026697aa61a8ca09.js.map -rw-rw-r-- 1 hank hank 714 9月 6 11:47 2.731ff84fbfc108bc253b.js -rw-rw-r-- 1 hank hank 5.9K 9月 6 11:47 2.731ff84fbfc108bc253b.js.map -rw-rw-r-- 1 hank hank 1.4K 9月 6 11:47 app.24004a5d2621177eb1e6bcdabc919636.css -rw-rw-r-- 1 hank hank 1.9K 9月 6 11:47 app.24004a5d2621177eb1e6bcdabc919636.css.map -rw-rw-r-- 1 hank hank 244K 9月 6 11:47 app.d041e73d9c21f495b099.js -rw-rw-r-- 1 hank hank 709K 9月 6 11:47 app.d041e73d9c21f495b099.js.map -rw-rw-r-- 1 hank hank 1.6K 9月 6 11:47 manifest.46788d45af925b1aa79a.js -rw-rw-r-- 1 hank hank 15K 9月 6 11:47 manifest.46788d45af925b1aa79a.js.map -rw-rw-r-- 1 hank hank 983K 9月 6 11:47 vendor.154264c34d147a00c862.js -rw-rw-r-- 1 hank hank 6.7M 9月 6 11:47 vendor.154264c34d147a00c862.js.map
结论:文件尺寸有一定程度上的缩小。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Flink 核心组件原理多图剖析
- 大型网站技术架构核心原理剖析,文末附知识图谱下载
- 技术入门 | 以Transaction的生命周期为线索剖析Libra核心组件
- 突破关系型数据库桎梏:云原生数据库中间件核心剖析
- 【Java集合源码剖析】ArrayList源码剖析
- Java集合源码剖析:TreeMap源码剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Servlet & JSP Cookbook
Bruce W. Perry / O'Reilly Media / 2003-12-1 / USD 49.99
With literally hundreds of examples and thousands of lines of code, the Java Servlet and JSP Cookbook yields tips and techniques that any Java web developer who uses JavaServer Pages or servlets will ......一起来看看 《Java Servlet & JSP Cookbook》 这本书的介绍吧!