webpack
webpack介绍
webpack是一个静态模块打包工具,主要的作用是打包模块以及资源管理,webpack提供很多实用的功能,例如模块打包、Tree-shaking、代码分隔、sourcemap、devServer、热更新等,webpack是目前最流行的模块打包工具。
webpack支持Commonjs、AMD、ESModule模块机制。
webpack的概念
webpack的大部分配置都在webpack.config.js
文件中,webpack的核心概念:
- 入口与输出
- loader
- plugin
- mode
入口
webpack最主要的功能就是打包模块,因此需要你指定一个入口文件,webpack会从这个入口文件开始执行,找到所有的依赖文件并且把它们打包起来。
输出
用户可以指定一个目录,webpack会将打包好的文件按照配置输出到该目录下
module.exports = {
mode: "development",
entry: "./src/main.js",
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
}
loader
loader是webpack提供的一个强大的转换机制,作用是将资源内容转换为js能够识别的内容,例如将typescript转换成JavaScript。
loader写在module.rules中,module.rules是一个数组,接收所有的loader,使用规则如下
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.ts/,
use: ["ts-loader"],
},
],
}
}
loader核心属性test
、use
,作用分别是指定loader作用于哪些文件和使用哪些loader。
loader的执行顺序是从右到左。
loader还有其他的配置属性,例如include
和exclude
可以指定转换条件。
常用的loader有
- babel-loader 将ES6语法转换成ES5语法,此外还能支持jsx
- ts-loader 将typescript转换成JavaScript
- css-loader 解析css文件
- style-loader 将css添加到文档的style标签里
- sass-loader 解析sass/scss文件
- vue-loader 将vue文件转换成js
plugin
tree-shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于ESM。
由于webpack支持AMD、CommonJs和ESM三种模块机制,因此开发者必须手动声明哪些模块是ESM,由此来启动tree-shaking功能,这由通过 package.json
的 sideEffects"
属性作为标记属性进行标记。
// package.json
// 标记所有的模块都是无副作用的
{
"name": "your-project",
"sideEffects": false
}
// 指定无副作用的模块
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
]
}
「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
要使tree-shaking生效,需要执行npm run build -p
或者在webpack.config.js
文件中配置mode: 'production'
来启动压缩机制。
代码分离
webpack的代码分离能够将代码分离到不同的bundle文件中,然后按需加载或者并行加载这些文件。代码分离能够获得更小的bundle文件,这在HTTP1.x中有比较好的效果。
webpack比较常用的代码分离方案有两种:
- 防止重复:使用
CommonsChunkPlugin
去重和分离 chunk。 - 动态导入:通过模块的内联函数调用来分离代码。
防止重复
CommonsChunkPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指定公共 bundle 的名称。
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import()
语法。第二种,则是使用 webpack 特定的 require.ensure
懒加载
懒加载是对代码分离的进一步扩展,虽然代码分离可以将模块提取成单独的bundle,但是它仍然会被浏览器加载,这在性能上并没有太大的收益,假如我们能够实现按需加载,那么就能起到比较好的性能优化效果了。
webpack-chain
webpack.config.js
能够满足单个项目的配置,但是很在多个项目中共享,例如类似于storybook这种项目,本身基于webpack构建,但是却又需要暴露API给开发者进行二次配置,因此就需要用到webpack-chain了。
webpack-chain提供链式API来创建和修改webpack,这有利于跨项目修改配置。
基本用法
创建和导出配置
// 导入 webpack-chain 模块,该模块导出了一个用于创建一个webpack配置API的单一构造函数。
const Config = require('webpack-chain');
// 对该单一构造函数创建一个新的配置实例
const config = new Config();
// 导出这个修改完成的要被webpack使用的配置对象
module.exports = config.toConfig();
链式调用
webpack-chain大部分API都支持链式调用
config.devServer.set("hot", true).set("port", 3000);
核心API
ChainedMap
webpack-chain 中的核心API接口之一是 ChainedMap
. 一个 ChainedMap
的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedMap
, 则它将具有如下的API和方法:
除非另有说明,否则这些方法将返回 ChainedMap
, 允许链式调用这些方法。
// 从 Map 移除所有 配置.
clear()
// 通过键值从 Map 移除单个配置.
// key: *
delete(key)
// 获取 Map 中相应键的值
// key: *
// returns: value
get(key)
// 获取 Map 中相应键的值
// 如果键在Map中不存在,则ChainedMap中该键的值会被配置为fn的返回值.
// key: *
// fn: Function () -> value
// returns: value
getOrCompute(key, fn)
// 配置Map中 已存在的键的值
// key: *
// value: *
set(key, value)
// Map中是否存在一个配置值的特定键,返回 真或假
// key: *
// returns: Boolean
has(key)
// 返回 Map中已存储的所有值的数组
// returns: Array
values()
// 返回Map中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,
// 如果Map是空,返回 \`undefined\`
// 使用 \`.before() 或 .after()\` 的ChainedMap, 则将按照属性名进行排序。
// returns: Object, undefined if empty
entries()
// 提供一个对象,这个对象的属性和值将 映射进 Map。
// 你也可以提供一个数组作为第二个参数以便忽略合并的属性名称。
// obj: Object
// omit: Optional Array
merge(obj, omit)
// 对当前配置上下文执行函数。
// handler: Function -> ChainedMap
// 一个把ChainedMap实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedMap
// 当条件为真,调用把ChainedMap实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedMap
// 当条件为假,调用把ChainedMap实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)
ChainedSet
webpack-chain 中的核心API接口另一个是 ChainedSet
. 一个 ChainedSet
的操作类似于JavaScript Set, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedSet
, 则它将具有如下的API和方法:
除非另有说明,否则这些方法将返回 ChainedSet
, 允许链式调用这些方法。
// 添加/追加 给Set末尾位置一个值.
// value: *
add(value)
// 添加 给Set开始位置一个值.
// value: *
prepend(value)
// 移除Set中全部值.
clear()
// 移除Set中一个指定的值.
// value: *
delete(value)
// 检测Set中是否存在一个值.
// value: *
// returns: Boolean
has(value)
// 返回Set中值的数组.
// returns: Array
values()
// 连接给定的数组到 Set 尾部。
// arr: Array
merge(arr)
// 对当前配置上下文执行函数。
// handler: Function -> ChainedSet
// 一个把 ChainedSet 实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedSet
// 当条件为真,调用把 ChainedSet 实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedSet
// 当条件为假,调用把 ChainedSet 实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)
loader
// 创建
config.module
.rule('自定义名称')
.test(/\.js$/)
.include
.add('src')
.add('test')
.end()
.use('babel')
.loader('babel-loader')
.options({
presets: [
['@babel/preset-env', { modules: false }]
]
});
// 修改配置
config.module
.rule(name)
.use(name)
.tap(options => newOptions)
plugin
// 添加
config
.plugin(name)
// 修改配置
config
.plugin(name)
.tap(args => newArgs)
// 修改实例
config
.plugin(name)
.init((Plugin, args) => new Plugin(...args));
// 移除
config.plugins.delete(name)
不要用
new
去创建插件,因为已经为你做好了。
合并配置
webpack-chain 支持将对象合并到配置实例,改实例类似于 webpack-chain 模式的布局。 请注意,这不是 webpack 配置对象,但您可以再将webpack配置对象提供给webpack-chain 以匹配器布局之前对其进行转换。
config.merge({ devtool: 'source-map' });
config.get('devtool') // "source-map"