欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > webpack - loader、plugin原理

webpack - loader、plugin原理

2024/10/25 1:34:26 来源:https://blog.csdn.net/qq_41604686/article/details/141130738  浏览:    关键词:webpack - loader、plugin原理

一、loader

  • loader 就是一个函数,函数里面要将处理过的文件内容 return 出去
  • 当 webpack 解析资源时,会调用相应的 loader 去处理
  • loader 接收三个参数:
  • content:文件内容,map:SourceMap,meta:别的 loader 传递的数据

1、手写一个简单的 loader

webpack.js 中配置 loader

module: {rules:[{test: /\.js$/,		// 匹配以 .js 结尾的文件loader: './loaders/test-loader.js'	// 处理匹配到的文件用的 loader}]},

loaders/test-loader.js


// 同步 loader
/***  loader 就是一个函数,函数里面要将处理过的文件内容 return 出去*  当 webpack 解析资源时,会调用相应的 loader 去处理*  loader 接收三个参数:*    content:文件内容,map:SourceMap,meta:别的 loader 传递的数据*/
// module.exports = function(content, map, meta) {
//   // content = 'console.log("12")'  // 修改 content
//   return content
// }// 同步 loader
module.exports = function (content, map, meta) {/***  第一个参数:err 代表是否有错误* 第二个参数:content 是处理后的内容 * 第三个参数:map 是 source-map 继续传递source-map * 第四个参数:meta 是给下一个 loader 传递的参数*/this.callback(null, content, map, meta)
}// 异步 loader
module.exports = function (content, map, meta) {const callback = this.async()setTimeout(() => {console.log('test2')callback(null, content, map, meta)}, 1000)
}

以上代码的作用:通过 test-loader.js 去处理匹配到以 .js 结尾的文件。

2、raw loader

raw loader 中,拿到的 contetn 是一个 Buffer 数据

// 方法1:
// module.exports = function (content) {
//   console.log(content)
//   return content
// }
// // raw loader 接收到的 content 是 Buffer 数据
// module.exports.raw = trueconst { truncate } = require("fs")// 方法2:
function rawLoader(content) {console.log(content)return content
}
rawLoader.raw = true
module.exports = rawLoader

3、pitch loader

如果 webpack.config.js 中处理某些文件使用了多个 pitch loader =》pitch_loader1.js、pitch_loader2.js、pitch_loader3.js,那么 loader 执行顺序是:
pitch_loader1 中的 pitch =》
pitch_loader2 中的 pitch =》
pitch_loader3 中的 pitch =》
pitch_loader3 中的 normal 方法 =》
pitch_loader2 中的 normal 方法 =》
pitch_loader1 中的 normal 方法
如果某一个 loader 的pitch中使用了 return,那么该 loader 的 normal 方法和该 loader 之后的 loader.js文件 中的 normal 方法 和 pitch 都不会再执行,从该 loader 的前一个 loader 的 normal 方法开始执行,
比如 pitch_loader2.js 中的 pitch 使用 了 return,那么执行顺序是:

pitch_loader1 中的 pitch =》
pitch_loader2 中的 pitch =》

pitch_loader1 中的 normal 方法

// loader
module.exports = function (content) {return content
}
// pitch
module.exports.pitch = function () {console.log('pitch loader')// return ''
}

4、loader API

在这里插入图片描述

5、自定义 loader

1)定义一个清除文件内容中 console.log(XXX) 的 loader
module.exports = function (content) {// 清除文件内容中 console.log(xxx)return content.replace(/console\.log\(.*\);?/g, '')
}
2)定义一个 给打包出书文件添加注释的loader

webpack.config.js

module.exports = {module: {rules:[{test: /\.js$/,loader: './loaders/author_loader/index.js',options: {author: 'zhangsan'}}]}
}

author_loader/index.js

const schema = require('./schema')
module.exports = function (content) {// 获取 webpack.config.js 中配置的对象const options = this.getOptions(schema)let prefix = `/** Author: ${options.author}*/`return `${prefix}${content}`
}

author_loader/schema.json

{"type": "object","properties": {"author": {"type": "string"}},"additionalProperties": false
}

二、plugins

webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换为输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
---- 【深入浅出 Webpack】

站在代码逻辑的角度就是:webpack 在编译代码过程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到对应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。

1、创建一个简单的 plugins

webpack.config.js

const TestPlugin = require('./plugins/test-plugin')...plugins: [new TestPlugin()
],...

test-plugin.js

const { resolve } = require("path")/*** 1、webpack 加载 webpack.config.js 中所有配置,此时就会 new TestPlugin(),执行插件的 constructor* 2、webpack 创建 compiler 对象* 3、遍历所有 plugins 中的插件,调用插件的 apply 方法* 4、执行剩下编译流程(出发各个 hooks 事件)*/
class TestPlugin {constructor() {console.log('TestPlugin constructor')}apply(compiler) {console.log('TestPlugin apply')// environment 是同步钩子,所以需要 tab 注册compiler.hooks.environment.tap("TestPlugin", () => {console.log("TestPlugin environment")})// emit 是异步串行钩子compiler.hooks.emit.tap("TestPlugin", (compilation) => {console.log("TestPlugin emit 111")})compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("TestPlugin emit 222")callback()}, 2000)})compiler.hooks.emit.tapPromise("TestPlugin", (compilation) => {return new Promise(() => setTimeout(() => {console.log("TestPlugin emit 333")resolve()}, 1000))})// make 是异步并行钩子compiler.hooks.make.tapAsync("TestPlugin",(compilation, callback)=>{setTimeout(() => {console.log("TestPlugin make 111")callback()}, 3000)})}
}
module.exports = TestPlugin

2、定义一个 给打包出书文件添加注释的 plugin

思路:

  • 在打包输入前添加注释:需要使用compiler.hooks.emit钩子,它是打包输出前触发
  • 如何让获取打包输出的资源:compilation.assets 可以获取所有即将输出的资源文件
    webpack.config.js
const AuthorWebpackPlugin = require('./plugins/author-webpack-plugin')
...
plugins: [new AuthorWebpackPlugin({author: "王武"})
],
...

author-webpack-plugin.js

/*** 插件功能:给所有的 css、js 文件内容前,增加前缀 anthor*/
class AuthorWebpackPlugin {constructor(options) {// 获取 webpack.config.js 使用插件时传入的配置项this.options = options}apply(compiler) {compiler.hooks.emit.tap("AuthorWebpackPlugin", (compilation) => {// 该插件处理 css、js 文件资源const extensions = ["css", "js"]// 1、获取即将输出的资源文件对象:compilation.assets(是一个对象,key 是包含当前文件名的文件路径,value 是文件内容相关信息)// 2、过滤只保留 js 和 css 文件const assets = Object.keys(compilation.assets).filter(assetPath => {// 通过 . 对文件路径进行切分const splitted = assetPath.split(".")// 获取文件后缀const extension = splitted[splitted.length - 1]// 判断是否是 js 或者 css 文件return extensions.includes(extension)})const prefix = `/*
* Author: ${this.options.author}
*/`// 遍历所有 js、css 资源添加注释assets.forEach(asset => {// 获取原文件内容const source = compilation.assets[asset].source()// 在原文件内容的基础上添加前缀const content = prefix + source// 修改资源compilation.assets[asset] = {// 最终资源输出时,调用 source 方法,source 返回的值时资源的具体内容source() {return content},// 资源大小size() {return content.length}}})})}
}module.exports = AuthorWebpackPlugin

3、定义一个 清除上次打包内容的 plugin

作用:在 webpack 打包输出前,将上次打包内容情况,类似于 webpack.config.js 中 output 的 clean: true
开发思路:

  • 如何在打包输出前执行:需要使用 complier.hooks.emit 钩子,它是打包输出前触发
  • 如何清空上次打包内容:
    · 获取打包输出目录:通过 complier 对象
    · 通过文件操作情况内容:通过complier.outputFileSystem

webpack.config.js

const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin")
...
plugins: [new CleanWebpackPlugin()
],
...

clean-webpack-plugin.js

class CleanWebpackPlugin {apply(complier) {// 2、获取打包输出的目录const outputPath = complier.options.output.path// 获取 fs 模块处理文件const fs = complier.outputFileSystem// 1、注册钩子:在打包输出之前 emitcomplier.hooks.emit.tap("CleanWebpackPlugin", (compilation) => {// 3、通过 fs 删除打包输出目录下的所有文件this.removeFiles(fs, outputPath)})}removeFiles(fs, filePath) {// 法一:递归删除文件和目录fs.rmSync(filePath, {recursive: true})// 法二:删除文件(空目录不会被删除)/*// 删除打包输出目录下所有的资源,需要先将目录下的资源删除,才能删除这个目录// 1、读取当前目录下所有资源(这里读取到的只有第一层文件和文件夹)const files = fs.readdirSync(filePath)// 2、遍历 files 一个个删除files.forEach(file => {// 2.1 遍历所有资源,判断时文件夹还是文件const path = `${filePath}/${file}`// 获取资源信息const fileStat = fs.statSync(path)if(fileStat.isDirectory()) {// 2.2 如果是文件夹,就要递归删除下面所有文件this.removeFiles(fs, path)} else {// 2.3 如果是文件,就直接删除fs.unlinkSync(path)}})*/}}module.exports = CleanWebpackPlugin

拓展:浏览器控制台调试 node

场景:编写 plugin 文件时,要查看 compiler 或者 compilation

  • 1、node 文件中,增加 debugger
    test-plugin.js
class TestPlugin {constructor() {console.log('TestPlugin constructor')}apply(compiler) {debuggerconsole.log('TestPlugin apply')}
}
module.exports = TestPlugin
  • 2、package.json 文件中的 scripts 属性中新增 debug 命令 "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
    package.json
{..."scripts": {"debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"},...
}
  • 3、终端执行命令 npm run debug
  • 4、打开谷歌浏览器控制台,左上角会显示一个 node 图标
    在这里插入图片描述
  • 5、点击图标,就可以进行断点调试,调试按钮跟正常 js 调试一样使用
    在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com