ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

webpack插件开发及修改源码的几种方式

2021-09-21 09:02:46  阅读:218  来源: 互联网

标签:插件 compilation 修改 webpack 源码 options


前言

webpack是我们平时工作中必不可少的工具,难免有时候需要对webpack编译的代码进行个性化处理操作,那么需要通过webpack插件或者修改其代码的方式来完成,本篇文章着重介绍webpack插件开发及修改源码的一些方式。很久之前我也写过webpack相关文章,例如:webpack前端技术小结,更多webpack知识,欢迎关注haorooms前端博客

插件使用

我们工作中,肯定用过webpack插件,用法如下:

// webpack.config.js
var HelloWorldPlugin = require('hello-world');
 module.exports = {  
// ... config settings here ...  
plugins: [new HelloWorldPlugin({ options: true })]
};

插屏开发

webpack 插件开发主要基于 Tapable 的插件机制提供丰富的自定义 API 和生命周期事件,可以控制 webpack 编译的每个流程,

Compiler 包含 webpack 环境的所有配置信息

Compilation 包含整个编译过程中所有环节对应的方法


class CustomPlugin {
  constructor(options) {
      ...
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('CustomPlugin', (compilation) => {
      compilation.hooks.optimizeChunkAssets.tap('CustomPlugin', (chunks) => {
        console.log(chunks)
      });
    });
  }
};

Compiler 代表着 webpack 从启动到关闭的整个生命周期,而 Compilation 只代表来一次编译,而修改源码的时机正好需要在编译的过程中修改。 上述例子中通过 optimizeChunkAssets 的钩子可以拿到所有的 chunks 信息,针对具体的 chunks 可以修改对应的源码,例如在增加头尾的源码:

// 处理源码拼接库
const ConcatSource = require('webpack-sources').ConcatSource;
class CustomPlugin {
  constructor(options) {
      ...
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('CustomPlugin', (compilation) => {
      compilation.hooks.optimizeChunkAssets.tap('CustomPlugin', (chunks) => {
        chunks.forEach((chunk) => {
          chunk.files.forEach((fileName) => {
            // 判断具体要修改的文件,假设简单通过 chunk 的文件名称判断入口
            if (filename.indexOf('index') > -1) {
              // 在源码头尾各增加内容
              compilation.assets[fileName] = new ConcatSource(
                `console.log('code before')`,
                compilation.assets[fileName],
                `console.log('code after')`,
              );
            }
          });
        });
      });
    });
  }
};

插件提供了丰富的生命周期,在修改源码过程中也要特别要注意插件的生命周期带来的影响,比如上述在 optimizeChunkAssets 阶段,这个阶段拿到的 chunk 资源已经完成各种 Loader 的处理,这个时候如果新增源码内容是 ES6,将不会再被转化。

webpack hooks 介绍

webpack官网地址:Compiler Hooks | webpack

介绍几个常用的hooks

    // tap 同步
    compiler.hooks.emit.tap("tap", (compilation) => {
      console.log("***** tap *****")
    })
    // tapAsync 参数cb未调用之前进程会暂停
    compiler.hooks.emit.tapAsync("tapAsync", (compilation,cb) => {
      start(0);
      function start(index){
          console.log(index);
          if(index<=3){
              setTimeout(() => {
                  start(++index);
              }, 1000);
          }else{
              cb()
          }
      }
    })
    // tapPromise 通过promise的方式调用
    compiler.hooks.emit.tapPromise("tapPromise", (compilation)=>{
        return new Promise((resolve,reject)=>{
            console.log("start tap-promise");
            setTimeout(()=>{
                resolve()
            },2000)
        })
    })

   compiler.hooks.afterCompile.tapAsync({
      name: 'haoroomstest',
    }, (compilation, callback) => {

      callback()
    })

插件开发demo

实现一个防止代码被调试的插件。 思路,生产环境代码中注入debuger

const { ConcatSource } = require('webpack-sources')

class HaoroomssAddDebugger {
  /**
   * @param options.min 最小间隔秒数
   * @param options.max 最大间隔秒数
   */
  constructor (options = { min: 1, max: 20 }) {
    this.min = options.min && options.min > 0 ? options.min : 1
    this.max = options.max && options.max <= 600 ? options.max : 600
  }

  apply (compiler) {
    compiler.hooks.afterCompile.tapAsync({
      name: 'HaoroomssAddDebugger',
    }, (compilation, callback) => {
      let assetNames = Object.keys(compilation.assets)
      for (const name of assetNames) {
        if (name.endsWith('.js')) { // 跳过非js文件
          let seconds = Math.ceil(Math.random() * (this.max - this.min)) +
            this.min
           let appendContent = `(() => {
        function block() {
            if (
                window.outerHeight - window.innerHeight > 200 ||
                window.outerWidth - window.innerWidth > 200
            ) {
                document.body.innerHTML =
                    "检测到非法调试,请关闭后刷新重试!";
            }
            setInterval(() => {
                (function () {
                    return false;
                }
                    ["constructor"]("debugger")
                    ["call"]());
            }, ${seconds});
        }
        try {
            block();
        } catch (err) {}
    })();`
          compilation.updateAsset(
            name,
            old => new ConcatSource(old, '\n', appendContent),
          )
        }
      }
      callback()
    })
  }
}

module.exports = HaoroomssAddDebugger

修改webpack源码

一、webpack Loader 注入代码

在 babel 7.4.0 之后,添加 polyfills 的方式推荐在入口 import core-js/stable 和 regenerator-runtime/runtime 并配合 @babel/preset-env 的 useBuiltIns 属性自动导入对应的包,我们希望工程上能自动完成入口注入的事情,而不是每次手动的增删。基于指定文件的内容处理,可以交给 webpack Loader 来实现。

webpack Loader 作为 webpack 核心能力之一,其基本工作就是将一个文件以字符串的形式读入,再对其进行语法分析和转换后再交给下一环节处理。

既然是以字符串形式读入,那修改源码就变得非常简单,结合 webpack 在指定 Loader 时的 test 匹配,可以快速定位到指定类型的文件

module: {
  rules: [
    {
      // 具体匹配文件的规则,匹配 src/index.js 文件
      test: /src\/index\.js/,
      use: [
        {
          loader: './customLoader.js'
        }
      ]
    }
  ]
}

在 customLoader 中注入代码:

module.exports = function(source) {
    return `
import "core-js/stable";
import "regenerator-runtime/runtime";
${content}
  `;
}

二、结合 AST 修改源码

上述 webpack Plugin 和 Loader 修改源码的例子,大部分都是通过简单的字符串拼接的方式,如果想具体修改源码的逻辑呢,那就需要结合 AST 进行源码修改。

而针对指定文件内容的处理,可以借鉴源码处理的三板斧:

将 Plugin / Loader 中获取到的源码转换成 AST

对 AST 修改后在转化为源码

将新生成源码返回或写入到对应资源文件中

以 Babel AST 相关操作为例:

const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const parser = require('@babel/parser');
const t = require('@babel/types');
...
// 转化 AST
const ast = parser.parse(source, {
    // 根据源码内容添加 sourceType 和 plugins
    sourceType: 'module',
    plugins: ['jsx', 'typescript']
});
traverse(ast, {
    Program(path) {
        // 在对应 AST 节点上进行操作和修改
    }
});
// 生成源码
const newSource = generate(ast, {});
...

也可以在webpack插件中,通过compilation修改。

const asset = compilation.assets[fileName];
let input = asset.source();
// 拿到源码后进行修改,一般基于 AST 修改
// 修改完成后重新写回
compilation.assets[fileName] = new ConcatSource(input);

关于AST相关文章,后面写。

三、修改 Entry入口的方式

这种方式主要是添加为项目添加 polyfills 的时候

例如如下:

module.exports = {
  entry: ['@babel/polyfill', './src/index.js'],
  ...
}

小结

以上就是今天的webpack的主要内容。该文章来源于haorooms前端博客

Haorooms博客-前端博客-前端技术,记录web前端开发的技术博客

欢迎访问haorooms前端博客额

标签:插件,compilation,修改,webpack,源码,options
来源: https://blog.csdn.net/confidence68/article/details/120398440

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有