webpack API

可以使用多种接口来自定义编译过程。一些特性会在几个接口间重叠,例如,一些配置选项会从 cli 中获取,另一些配置选项只能从单个接口中获取。

CLI

命令行接口( Command line Interface - cli) 用于对构建进行配置和交互。常用场景,cli仅用于使用配置文件和标记。

不使用配置文件的用法

webpack <entry> [<entry>] -o <output>

包含统计数据的文件

通过 webpack 编译源文件时,用户可以生成包含关于 模块的统计数据的json文件。统计数据可以帮助开发者分析应用的依赖图表,可以优化编译速度。输出的 json 中包含versionhashoutputpathassetsByChunkNameassetschunksmoduleserrors等信息。

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使用WebpackOptiosDefaulterWebpackOptionsApply两个工具,通过所有内置插件,来配置Compiler实例。

run方法用于触发所有编译时工作。完成之后,执行给定的 callback函数。最终记录下来的概况信息stats和错误errors,应该在这个callback函数中获取。

这个API 一次只支持一个并发编译。当使用run时,会等待他完成后,然后才能再次调用runwatch。多个并发编译会损坏输出文件。

自定义文件系统

默认情况下,webpack使用普通文件系统来读取文件并将文件写入磁盘。还可以使用不同类型的文件系统(内存 memory,webDAV等)来更改输入或输出行为,可以使用inputFileSystemoutputFileSystem。可以使用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 产生的处理结果。这个处理结果应该是 StringBuffer,代表了模块的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都可以用StringBuffer的形式传递它的处理结果。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-promisepromise-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 阶段,可以使用 tapAsynctapPromise

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 钩子,插件作者可以使用它来自定义解析过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容