webpack loader和plugin配置项

npx webpack –mode=development

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module: {
rules: [
// 转换 ES6 代码,解决浏览器兼容问题
{
test: '/\.js$/',
exclude: '/node_modules/',
use: [
{
loader: 'babel-loader'
},
{
loader: path.join(__dirname, '../loader/cpp-loader,js'),
options: {
log: 'hello loader'
}
}
]
},
]
}

loader

简单来说,loader就是一个nodejs模块,loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 API,并通过 this 上下文访问。
比如这样

1
2
3
4
5
6
7
8
9
const loaderUtils = require('loader-utils'); // loader 工具库 ,调用loaderUtils.getOptions拿到webpack配置参数
function loader(source) {
console.log('————source', source);
const options = loaderUtils.getOptions(this)
console.log('————options', options);
return source
}
// 导出一个函数
module.exports = loader

loader前置知识

  • loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。

  • loader 可以是同步的,也可以是异步的。

  • loader 运行在 Node.js 中,并且能够执行任何操作。

  • loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。

  • 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。
    插件(plugin)可以为 loader 带来更多特性。

  • loader 能够产生额外的任意文件

手写loader

4种本地开发测试的方法

  • 匹配(test)单个 loader,你可以简单通过在 rule 对象设置 path.resolve 指向这个本地文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module: {
    rules: [
    {
    test: /\.js$/,
    use: {
    // loader: path.join(__dirname, '../loader/cpp-loader,js'),
    // loader: 'cpp-test-loader',
    loader: path.resolve('./loader/cpp-test-loader.js'),
    options: {
    log: 'hello 这是我目前用的loader'
    }
    }
    }
    ]
    },
  • 匹配(test)多个 loaders,你可以使用 resolveLoader.modules 配置,webpack 将会从这些目录中搜索这些 loaders。例如,如果你的项目中有一个 /loaders 本地目录:

    1
    2
    3
    resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, 'loader')]
    },
  • 还阔以通过resolveLoader配置别名的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    resolveLoader: {
    alias: {
    'a-loader': path.resolve(__dirname, 'loader/a.js')
    }
    },
    module: {
    rules: [
    {
    test: /\.js$/,
    use: {
    loader: 'a-loader',
    options: {
    test: 'hello this is options'
    }
    }
    }
    ]
    }
  • npm link 软连接的方式调试独立库和包,来将其关联到你要测试的项目。node模块根目录下 运行 npm link,项目里使用 npm link cpp-test-loader。其实跟调试单独的npm包一模一样。

使用 loader-utils 能够编译 loader 的配置,还可以通过 schema-utils 对传递的配置进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'a-loader',
options: {
test: 'hello this is options' // test: string 跟下面的schema数据结构对应
}
}
}
]
}

// cpp-test-loader.js
const loaderUtils = require('loader-utils');
const validateOptions = require('schema-utils');
// 定义loader里的options选项数据结构
const schema = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
}
// source: 表示源文件字符串或者buffer
function loader(source) {
const options = loaderUtils.getOptions(this)
validateOptions(schema, options, 'Example Loader'); // 若当前传入的option不符合预期的数据结构 则会报错
return source;
}
module.exports = loader

同步 loader

无论是 return 还是 this.callback 都可以同步地返回转换后的 content 内容:

1
2
3
4
5
module.exports = function(content, map, meta) {
//一些同步操作
outputContent = someSyncOperation(content)
return outputContent;
}

this.callback灵活 支持多个参数

1
2
3
4
module.exports = function(content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 当调用 callback() 时总是返回 undefined
};

异步 loader

对于异步 loader,使用 this.async 来获取 callback 函数:

1
2
3
4
5
6
7
8
module.exports = function(content, map, meta) {
// 做异步的任务
var callback = this.async();
someAsyncOperation(content, function(err, result, sourceMaps, meta) {
if (err) return callback(err);
callback(null, result, sourceMaps, meta);
});
};

plugin

为社区用户提供一种强大方式来触及webpack编译过程(compilation process),插件能够 钩入(hook) 到在每个编译(compilation)中触发的所有关键事件。在编译的每一步,插件都具备完全访问 compiler 对象的能力,如果情况合适,还可以访问当前 compilation 对象。

在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。理解它们的角色是扩展 webpack 引擎重要的第一步。

compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

这两个组件是任何 webpack 插件不可或缺的部分(特别是 compilation),因此,开发者在阅读源码,并熟悉它们之后,会感到获益匪浅:

plugin前置知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const webpack = (options) => {
let compiler = new Compiler();
for (const plugin of options.plugins) {
plugin.apply(compiler) // 将plugin注册到compiler
}
// 注册结束
// 开始执行
compiler.run()
}

const options = {
plugins: [
new MyFirstWebpckPlugin()
]
}
// 走一个
webpack(options)

手写plugin

webpack 插件由以下组成:

  • 一个 JavaScript 命名函数,构造函数。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。(compiler.hooks.emit // Compiler.emit() 输出到dist目录)
  • 处理 webpack 内部实例的特定数据。(主要是处理compilation,比如检索遍历资源(asset)、chunk、模块和依赖、监听 chunk 的修改)
  • 功能完成后调用 webpack 提供的回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// plugin.js

function MyFirstWebpckPlugin(options) {
console.log('我的首个插件————————', options)
}
const pluginName = 'MyFirstWebpckPlugin'
MyFirstWebpckPlugin.prototype.apply = function(compiler) {
// compiler对象代表了完整的webpack环境配置
// 指定挂载到webpack自身的事件钩子
// compiler.hooks.compile.tap(pluginName, function(compilationParams) {
// console.log('环境准备好了', Object.keys(compilationParams))
// })
compiler.hooks.emit.tap(pluginName, function(compilation) {
// 处理内部实例的特定数据
// console.log('————compilation————', Object.keys(compilation))
// console.log('————compilation.hooks————', Object.keys(compilation.hooks))
if (compilation.chunks && compilation.chunks.length) {
compilation.chunks.forEach((chunk) => {
// 检索chunk模块
if (chunk && chunk.files && chunk.files.length) {
chunk.files.forEach((fileName) => {
console.log('fileName_____', fileName)
})
}
})
}
// compilation 代表一次资源版本构建
compilation.hooks.finishModules.tap(pluginName, (module) => {
console.log('——————chunks——————', module)
})
})
}
module.exports = MyFirstWebpckPlugin

// webpack.common.js
const MyFirstWebpckPlugin = require('../plugin/plugin')
...
plugins: [
new MyFirstWebpckPlugin({
options: true,
name: 'common',
mode: 'Test'
}),
]

plugin实例

island-webpack-plugin 是一个在 bundle 中添加作者信息的插件,这个插件同样是在 emit 这个钩子上触发的,同样是获取 source 后对 source 添加作者信息的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class AuthorPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tap('author-plugin', (compilation) => {
const options = this.options
return new Promise((resolve, reject) => {
const assets = compilation.assets
Object.keys(assets).forEach(e => {
let source = assets[e].source()
let info = []

if (options.author) info.push(`@Author: ${options.author}`)
if (options.email) info.push(`@Email: ${options.email}`)
if (options.homepage) info.push(`@Homepage: ${options.homepage}`)

if (info.length) {
info.push(`@Date: ${new Date()}`)
source = `/*\n ${info.join('\n\n ')}\n*/\n${source}`
}

compilation.assets[e].source = () => source
compilation.assets[e].size = () => source.size
})
resolve()
})
})
}
}

module.exports = AuthorPlugin

令人执行的compiler里的钩子函数
Tapable

参考文件