可以使用多种接口来自定义编译过程。一些特性会在几个接口间重叠,例如,一些配置选项会从 cli 中获取,另一些配置选项只能从单个接口中获取。
CLI
命令行接口( Command line Interface - cli) 用于对构建进行配置和交互。常用场景,cli仅用于使用配置文件和标记。
不使用配置文件的用法
webpack <entry> [<entry>] -o <output>
包含统计数据的文件
通过 webpack 编译源文件时,用户可以生成包含关于 模块的统计数据的json文件。统计数据可以帮助开发者分析应用的依赖图表,可以优化编译速度。输出的 json 中包含
version
、hash
、outputpath
、assetsByChunkName
、assets
、chunks
、modules
、errors
等信息。
webpack complication-stats.json
// 要包含依赖的图表以及各种其他的编译信息,也会把 --profile 一起加入,这样每一个包含自身编译数据的模块对象都会添加 profile
模块
当使用webpack处理模块时,理解不同的模块语法(主要是模块方法和模块变量)非常重要。这些模块语法 webpack 都可以支持。
Node
常用的配置只需要用到配置文件就可以,对编译的更细粒度控制,需要通过 node 接口实现。包括传递多个配置文件、可编程方式的编译执行或观察文件,以及收集概要信息。当需要自定义构建或开发流程时,node.js API 非常有用,因为所有的报告和错误处理必须自行实现,webpack 仅负责编译的部分。
导入的webpack 函数需要传入一个webpack配置对象,当传入回调函数时会执行 webpack compiler
const webpack = require('webpack')
webpack({
// 配置对象
}, (err, stats) => {
})
Compiler 实例
如果不向 webpack
执行函数传入回调函数,会得到 webpack Compiler
实例,可以通过它手动触发 webpack 执行器,或者让他执行构建并监听变更。Compiler
实例提供了以下方法
.run(callback)
.watch(watchOptions, handler)
通常情况下,虽然会创建一些子compiler
来代理到特定任务,只会创建一个主Compiler
实例,Compiler
基本上只是执行最低限度的功能,以维持生命周期运行的功能。它将所有的加载、打包和写入工作,都委托到注册过的插件上。Compiler
实例上的 hooks
属性,用于将一个插件,注册到Compiler
的生命周期的所有钩子事件上,webpack使用WebpackOptiosDefaulter
和 WebpackOptionsApply
两个工具,通过所有内置插件,来配置Compiler
实例。
run
方法用于触发所有编译时工作。完成之后,执行给定的 callback
函数。最终记录下来的概况信息stats
和错误errors
,应该在这个callback
函数中获取。
这个API 一次只支持一个并发编译。当使用run
时,会等待他完成后,然后才能再次调用run
或watch
。多个并发编译会损坏输出文件。
自定义文件系统
默认情况下,webpack
使用普通文件系统来读取文件并将文件写入磁盘。还可以使用不同类型的文件系统(内存 memory,webDAV等)来更改输入或输出行为,可以使用inputFileSystem
或outputFileSystem
。可以使用memory-fs
替换默认的outputFileSystem
,将文件写入到内存中,而不是写入磁盘。
const MemoryFS = require('memory-fs')
const webpack = require('webpack')
const fs = new MemoryFS()
const compiler = webpack({ /* options */})
compiler.outputFileSystem = fs
compiler.run((err, stats) => {
// 之后读取输出
cosnt content = fs.readFileSync('...')
})
被webpack-dev-server
及众多其他包依赖的webpack-dev-middleware
通过这种方式,将文件隐藏起来,仍然能为浏览器提供给服务。
loader
loader 是转译模块源代码的转换规则。loader 被编写为,接收源代码作为参数的函数,并返回这些转换过的新版本代码。
loader是一个导出为函数的 javascript 模块,loader runner 会调用这个函数,把上一个loader 产生的结果或资源文件传入进去。函数的
this
上下文将由 webpack 填充,并且loader runner具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。
第一个loader的传入参数只有一个:资源文件的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是
String
或Buffer
,代表了模块的javascript源码,还可以传递一个可选的 SourceMap 结果。
如果是单个处理结果,可以在同步模式中直接返回。如果有多个处理结果,则必须调用 this.callback
。在异步模式中, 必须调用this.async
来指示 loader runner等待异步结果,它会返回this.callback()
回调函数,随后 loader 必须返回 undefined
并且调用该回调函数。
同步loader
无论是 return
还是this.callback
都可以同步返回转换后的content
内容
module.exports = function(){
return someSyncOperation(content)
}
module.exports = function(){
this.callback(null, someSyncOperation(content), map, meta)
return // 调用callback时总是返回 return
}
异步loader
对于异步loader,使用this.async
来获取callback
module.exports = function(){
var callback = this.async()
someAsyncOperation(content, function(err, result) {
if (err) return callback(err)
callback(null, result, map, meta)
})
}
module.exports = function(){
var callback = this.async()
someAsyncOperation(content, function(err, result, sourceMaps, meta){
if(err) return this.callback(err)
callback(null, result, sourceMaps, meta)
})
}
loader最初被设计为可以在同步 loader pipeline (nodejs, 使用 enhanced-require)与 异步 pipeline (webpack) 中运行,不建议在nodejs这样的单线程环境下进行耗时长的同步计算,应尽可能使loader异步化。
Raw Loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传递给 loader。通过设置 raw
,loader可以接收原始的Buffer
。每一个 loader都可以用String
或Buffer
的形式传递它的处理结果。Compiler
将会把它们在loader之间相互转换。
module.exports = function(content){
assert(content instanceof Buffer)
return someSyncOperation(content) // 返回值如果不是 raw loader也可以是一个 Buffer
}
module.exports.raw = true
Pitching loader
loader总是从右到左地被调用。有些情况下,loader只关心 request后面的元数据,并且忽略前一个 loader的结果。在实际从右到左执行loader之前,会先从左到右调用loader上的pitch方法。
- 首先,传递给
pitch
方法的数据在执行阶段会暴露在this.data
下,在周期早期捕获和共享信息很有用。 - 如果某个loader在pitch阶段返回一个值, 那么这个过程会调转方向,跳过剩下的loader。
Logger Interface
记录输出是向用户显示消息的另一种方式。loader和plugin中可以使用 webpack logger,由用户在 webpack configuration 中配置,作为 Stats 的一部分触发。
如何在自己的 plugin 和 loader 中使用 logger
/* plugin */
cosnt PLUGIN_NAME = 'my-webpack-plugin'
export class MyWebpackPlugin {
apply(compiler) {
const logger = compiler.getInfrastructureLogger(PLUGIN_NAME)
logger.info('xxxx')
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
cosnt logger = compilation.getLogger(PLUGIN_NAME)
logger.info('yyyy')
})
}
}
/* loader */
module.exports = function(source) {
const logger = this.getLogger('my-webpack-loader')
logger.info('zzzz')
return source
}
Module methods
ES6
import
以静态方式,导入另一个通过 export 导出的模块
export
导出整个模块或者具名模块
import()
动态地加载模块。调用 import()
的地方,被作为分离的模块起点,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk中。
if(module.hot) {
import('lodash').then(_ => {
// dosomething with lodash
})
}
import()
特性依赖于内置的Promise
。如果在低版本中使用 import()
,需要使用es6-promise
或promise-polyfill
库,用于 shim Promise
环境。
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
)
webpackMode
,从webpack 2.6.0开始,可以指定以不同的模式解析动态导入。
属性 | 意义 |
---|---|
lazy |
为每个import() 导入的模块,生成一个可以延迟加载的chunk |
lazy-once |
生成一个可以满足所有import() 调用的单个可延迟加载chunk
|
eager |
不会生成额外的chunk,所有模块都被当前chunk引入,且没有额外的网络请求 |
weak |
尝试加载模块,只有在客户端上已有该chunk时才成功解析 |
CommonJs
require
以同步的方式检索其他模块的导出,由 compiler
确保依赖项在最终输出 bundle 中可用
var $ = require('jquery')
require.resolve
以同步的方式获取模块的id。由 compiler 来确保依赖项在最终输出 bundle 中可用。
AMD
define(通过 factory 方法导出)
define(name, dependencies, factoryMethod)
define(['jquery', 'my-module'], function($, mtModule){
// 导出一个函数
return function doSomething() {}
})
require
require(dependencies, callback)
给定 dependencies 参数,将其对应的文件拆分到一个单独的 bundle 中,此 bundle 会被异步加载。然后会调用 callback 回调函数,并传入 dependencies 数组中每一项的导出。
- 如果提供 dependencies 参数,将会调用 factoryMethod 方法,并以相同的顺序传入每个依赖项的导出
- 如果未提供 dependencies 参数,则调用 factoryMethod 方法时传入 require,exports和module。
如果此方法返回一个值,则返回值会作为此模块的导出。由 compiler 来确保依赖项在最终输出 bundle 中可用。
plugin
插件接口可以帮助用户直接触及到编译过程。插件可以将处理函数注册到编译过程中的不同事件点运行的生命周期的钩子函数上。当执行到每个钩子时,插件能够完全访问到编译的当前状态
插件能够 hook 到在每个 compilation 中触发的所有关键事件,在编译的每一步,插件都具备完全访问 compiler 对象的能力,如果情况合适,还可以访问当前 compilation 对象。
Tapable
tapable 这个 library 是 webpack 的核心工具,以提供类似插件的接口。webpack 中许多对象扩展自 Tapable 类,这个类暴露 tap, tapAsync 和 tapPromise 方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发。
根据所使用的 Hook 和 tap 方法,插件可以以多种不同的方式运行。根据触发到 tap 事件,插件可能会以不同的方式运行,当钩入 compiler
阶段时,只能使用同步的tap方法
compiler.hooks.compile.tap('MyPlugin', params => {
console.log('以同步方式触发 compile 钩子')
})
在 run 阶段,可以使用 tapAsync
或 tapPromise
compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
console.log('以异步方式触及 run 钩子')
callback()
})
compiler.hooks.run.tapPromise('MyPlugin', compiler => {
return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
console.log('以具有延迟的异步方式触及 run 钩子')
})
})
Compiler 钩子
Compiler 模块是 webpack 的支柱引擎,通过 CLI 或 NodeAPI 传递的所有选项,创建出一个 compilation 实例。扩展自 Tapable 类,便于注册和调用插件。大多数面向用户的插件,会先在 Compiler 上注册。
Compiler 支持可以监控文件系统的监听机制,并且在文件修改时重新编译。当处于监听模式时,compiler 会触发诸如 watchRun,watchClose和invalid 等额外的事件。通常用于开发环境中使用,也常常会在 webpack-dev-server
这些工具的底层之下调用,由此开发人员无须每次都使用手动方式重新编译。
compilation钩子
compilation 模块会被 Compiler 用来创建新的编译 或 构建。compilation 实例能够访问所有的模块和他们的依赖,会对应用程序的依赖图中所有模块进行字面上的编译。在编译阶段,模块会被加载 loaded、封存 sealed、优化 optimized、分块 chunked、哈希 hashed 和重建 restored。 compilation 类扩展自Tapable,并提供了相关生命周期钩子。
parser
parser 实例是用来解析由 webpack 处理过的每个模块。parser 是扩展自tapable 的webpack类,并且提供多种 tapable 钩子,插件作者可以使用它来自定义解析过程。