webpack简介及自定义插件

  webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。想要写一个webpack的自定义插件,需要先了解webpack的一些基础配置,详细内容可以在 webpack官网 学习。

 

  • 入口(entry)

   entry是指入口文件的配置项,当入口只有一个时,可以简写为:

 module.exports = {
   entry: './src/main.js',
 };

   webpack还支持多个入口的情况,此时需要用数组的形式或者对象的形式给entry赋值,如:

  entry: {
    app: './src/app.js',
    main: './src/main.js',
  }

   相对于简写语法,对象语法比较繁琐,但是可扩展行更高,例如用于分离应用程序和第三方库入口,或者是多页面应用程序入口配置。

 

  • 输出(output)

   输出的配置需要根据入口的配置来变换,最简单的写法是只指定output的filepath属性,这样会默认将该文件输出到项目根目录下的dist目录中,如果webpack打包后会输出文件不少于一个,还需要用占位符,例如[name],[hash]等,确保输出的文件都具有唯一名字。除此之外,还可以利用output.path指定打包后的输出路径等等。

output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },

 

  • loader

   loader是用于对代码模块进行转换,webpack本身只支持打包js和json类型的文件,但是我们的项目中肯定不止用到这两种类型文件,例如还有css,html已经ts等等,此时就需要利用loader对这些代码进行转换。一份代码可能需要多个loader才能被转换,例如转换less样式代码:

{
    test: /\.less$/,
    /**
    * use可以使用多个loader,从下往上或者从右往左执行
    */
    use: [
      {
        loader: MiniCssExtractPlugin.loader, // creates link tag
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
      },
      {
        loader: "less-loader", // compiles Less to CSS
      },
    ],
 },

  常用的一些loader:

  1. style-loader:用于将编译好后的css样式以style标签的形式插入到html文件中

  2. css-loader: 用于处理css文件,需要搭配style-loader或者MiniCssExtractPlugin使用

  3. less-loader:用于处理less样式文件,需要跟css-loader搭配使用

  4. postcss-loader:自动添加浏览器前缀,解决样式浏览器兼容问题

  5. babel-loader:使用babel处理js代码

  6. ts-loader:用于处理ts文件,需要配置tsconfig.json文件

  7. url-loader:可以将文件大小小于设置的limit值的文件转成base64编码,减少请求次数,提升页面加载速度

 

  • plugins

  接下来进入到我们的主题,插件部分,在 webpack官网插件部分 的介绍中,插件是webpack的支柱功能,插件的目的是为了解决loader解决不了的问题,由此可见,插件的功能是十分强大的,如果要使用webpack插件,需要在webpack配置中的plugins传入一个new示例,因为可以使用多个插件,对应plugins是一个数组,每添加一个插件,就需要在plugins中新增一个new示例,每个插件传递的参数由使用情况决定,例如:

plugins: [
    new MyPlugin(/Test(1|2)/, /(t|j|cs)s$/), //自定义插件
    new MiniCssExtractPlugin(), //将打包后的css文件以link标签的方式导入
    new HtmlWebpackPlugin({   //根据传入的模板,生成一个html文件
      filename: "index.html",
      template: "./src/index.html",
    }),
],

 

  当我们在webpack打包的过程中想要实现某些操作时,如果已经存在的插件不能满足我们的需求,就需要自己开发自定义插件

 

  • 自定义插件

   webpack插件可以是一个javascript命名函数或者一个javascript类,在类中需要实现一个apply方法,该方法的参数是compiler对象。compiler是webpack的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。compiler包含很多钩子函数,在调用钩子函数时,可以通过如下方式访问:

compiler.hooks.someHook.tap('MyPlugin', (params) => {
  /* ... */
});

  someHook表示自己要使用的钩子函数,除tap方法外,还可以使用tapAsync方法和tapPromise方法,当使用tapAsync方法绑定插件时,就可以在钩子的回调函数中使用异步方法,但是必须在最后调用该钩子的回调函数的参数中指定的回调函数,否则会导致webpack不能继续执行,当然也有同步的钩子不支持tapAsync。在调用tapPromise,需要返回一个Promise,在异步调用完成后resolve。compiler有很多的钩子函数,如果想要知道每个钩子的具体调用位置,需要在webpack的源码中,通过搜索 hooks.<hook name>.call,查找具体使用位置。在complier的某些钩子函数的参数中,可以获取到compilation对象,Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore),compliation和compiler用法相同,某些钩子函数也可以使用tapAsync和tapPromise方法。

  • 插件实例

const fs = require("fs")

class MyPlugin {
  //构造函数,auchor代表锚点,type表示需要匹配的文件类型的数组
  constructor(auchor, type) {
    this.auchor = auchor
    this.type = type
  }

  apply(compiler) {
    let resultArr = []
    compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
      compilation.hooks.finishModules.tap("MyPlugin", (modules) => {
        modules.forEach((module) => {
          if (
            module.resource &&
            !module.resource.includes("node_modules") &&
            module.resource.split(".")[1]?.match(this.type)
          ) {
            console.log(module.resource)
            const result = matchAnchor(module.resource, this.auchor)
            // 对数组进行拼接;
            resultArr = resultArr.concat(result)
          }
        })
      })
    })
    compiler.hooks.emit.tap("Myplugin", (compilation) => {
      //将匹配到的数据输出到webpack的输出目录中
      compilation.assets["matchResult.json"] = {
        source() {
          return JSON.stringify(resultArr)
        },
        size() {
          return resultArr.length
        },
      }
    })
  }
}
//匹配函数,返回封装好的对象数组
function matchAnchor(path, anchor) {
  //匹配出的结果数组,默认为空
  const result = []
  // 读取文件内容
  const data = fs.readFileSync(path, "utf-8").toString()
  // 分行
  const lines = data.split("\n")
  lines.forEach((line, index) => {
    //将匹配条件转化成正则
    const reg = new RegExp(anchor)
    const matchArr = reg.exec(line)
    if (matchArr) {
      result.push({
        filepath: path.replaceAll("\\", "/"), // 匹配到的文件路径
        line: index + 1, // 匹配到的行号
        match: matchArr[0], // 匹配到的字符串
      })
    }
  })
  return result
}

module.exports = MyPlugin

  该插件由一个js类和一个匹配函数组成,需求是找出指定文件类型中的某些字符串,需要在调用的时候传递两个参数,在类的构造函数中初始化。之后在apply方法中,实现我们想要的效果。这里用到了compiler的两个钩子函数,第一个是compilation钩子。compilation在编译创建之后执行,这是一个同步 SyncHook 钩子,通过回调函数中的compilation的finishModules钩子,可以访问到webpack构建的依赖树。之后通过module.resource,可以拿到每个模块的依赖文件的路径,之后通过文件读写操作就可以查找想要的字符串。第二个compiler钩子函数时emit,emit在输出 asset 到 output 目录之前执行,这个钩子 不会 被复制到子编译器。在该钩子函数中,通过compilation.assets,将查询到的结果一并输出到dist文件夹中。最后就是需要在webpack的配置中导入该插件,并在plugins中new一个该类的实例,而apply方法会在new该实例时自动被调用。

posted @ 2022-11-15 17:34  zhoushaowen  阅读(474)  评论(0)    收藏  举报