如何使用webpack插件来禁止部分代码被引用或被修改

413 阅读2分钟

我正在参与掘金新人创作活动,一起开启写作之路。

需求是这样产生的,团队的产品管理后台端是基于同一套前端工程模板来创建的。各产品管理端有很多相同的代码逻辑,例如登录登出,消息模块,个人中心,布局样式,公用组件,公用方法文件等等。为了便于统一管理和维护这部分代码,对这些代码进行了抽离,发布成npm包进行各产品端共享。为了便于各业务侧开发人员查阅这部分抽离出的代码,使用了一键命令在工程目录下创建npm包的影子目录,影子目录默认配置成git忽略不会上传到git仓库。但是不能完全避免业务开发人员误将影子目录中的代码放入到业务代码逻辑中。

基于上边的需求,如何解决这样问题,无外乎两种途径。

  1. 人为告知,如项目readme.md中写明规定,开通气会告知。
  2. 技术手段,使用程序判定控制,文件一旦被引入工程代码中立刻告知。

人为告知避免不了会出现通知不到位,遗忘等情况,因此使用一个技术手段控制文件不被引用很有必要。技术实现上,我主要利用webpack插件,能够在构建流程中的各个阶段劫持做一些代码处理这个特性来实现该功能。

首先要确定使用构建流程的哪个阶段,我选择的是compiler对象的compilation钩子,该钩子是compilation对象创建之后执行。compilation对象,针对的是随时可变的项目文件,只要文件有改动,compilationd就会被重新创建。同时借助compilation对象上的buildModule钩子,我们能够在每个模块构建开始之前,获取当前待构建的模块资源地址,一旦匹配到是禁用的那个文件地址,我们就执行相应的逻辑。

compiler.plugin('compilation', function(compilation) {
  compilation.plugin('buildModule', function(module) {
    if (module.resource) {
      if (disableImport instanceof Array) {
        for (let i = 0; i < disableImport.length; i++) {
          if (module.resource.indexOf(disableImport[i]) !== -1) {
            throw new Error('禁止引用:' + module.resource)
          }
        }
      } else if (typeof disableImport === 'string') {
        if (module.resource.indexOf(disableImport) !== -1) {
          throw new Error('禁止引用:' + module.resource)
        }
      } else {
        compilation.warnings.push('FilesDogWebpackPlugin 插件配置项 disableImport 字段类型只支持 String 或 Array')
      }
    }
  })
})

后来发散了一下,那我能不能控制一下文件被修改呢?一旦文件内容被修改了我就做一些限定?当然也是可以的。使用compiler对象的watchRun钩子,该钩子在监听模式下,文件一旦内容有修改将会被触发。我们可以通过compiler.watchFileSystem.watcher.mtimes获取到当前改动的是哪些资源文件,配对到需要控制的文件路径,我们就进行相应的处理。需要注意到的是必须是在监听模式下,也就是开发环境中我们可以正常使用该方式。假如我们没有启动开发模式,直接修改了被禁止修改的文件,那是不能够监听到的。

compiler.hooks.watchRun.tapAsync('FilesDogWebpackPlugin', (compiler, cb) => {
  const mtimes = compiler.watchFileSystem.watcher.mtimes
  const mtimesKeys = Object.keys(mtimes)
  if (mtimesKeys.length > 0) {
    // 改动的文件 mtimesKeys
    if (disableModify instanceof Array) {
      for (let i = 0; i < disableModify.length; i++) {
        for (let j = 0; j < mtimesKeys.length; j++) {
          if (mtimesKeys[j].indexOf(disableModify[i]) !== -1) {
            throw new Error('禁止改动:' + mtimesKeys[j])
          }
        }
      }
    } else if (typeof disableModify === 'string') {
      for (let i = 0; i < mtimesKeys.length; i++) {
        if (mtimesKeys[i].indexOf(disableModify) !== -1) {
          throw new Error('禁止改动:' + mtimesKeys[i])
        }
      }
    } else {
      console.error('FilesDogWebpackPlugin 插件配置项 disableModify 字段类型只支持 String 或 Array')
    }
  }
  cb()
})

以上就是整体的一个实现过程,全部代码及插件配置设置如下:

// webpack配置文件中使用自定义插件

const FilesDogWebpackPlugin = require('./build/files-watchdog-webpack-plugin.js')
function resolve(dir) {
  return path.join(__dirname, dir)
}
...
// webpack的plugins配置项中使用
plugins: [
  ...
  new FilesDogWebpackPlugin({
    disableImport: resolve('temp-lib'), // temp-lib 为禁止引入的文件目录名,该文件目录下的所有文件将不能被引入到代码模块中使用。disableImport字段可以是一个文件目录路径或文件路径的字符串,也可以是一个数组,数组是有多个文件目录路径或文件路径的字符串组成。
    disableModify: [resolve('src/config.js'), resolve('src/utils')] // disableModify字段可以是一个文件目录路径或文件路径的字符串,也可以是一个数组,数组是有多个文件目录路径或文件路径的字符串组成。设定改字段可以禁止文件被修改。
  })
]

// files-watchdog-webpack-plugin.js

class FilesDogWebpackPlugin {
  constructor(options) {
    this.options = options || {}
  }
  apply(compiler) {
    const disableImport = this.options.disableImport
    const disableModify = this.options.disableModify
    if (disableImport) {
      compiler.plugin('compilation', function(compilation) {
        compilation.plugin('buildModule', function(module) {
          if (module.resource) {
            if (disableImport instanceof Array) {
              for (let i = 0; i < disableImport.length; i++) {
                if (module.resource.indexOf(disableImport[i]) !== -1) {
                  throw new Error('禁止引用:' + module.resource)
                }
              }
            } else if (typeof disableImport === 'string') {
              if (module.resource.indexOf(disableImport) !== -1) {
                throw new Error('禁止引用:' + module.resource)
              }
            } else {
              compilation.warnings.push('FilesDogWebpackPlugin 插件配置项 disableImport 字段类型只支持 String 或 Array')
            }
          }
        })
      })
    }

    if (disableModify) {
      compiler.hooks.watchRun.tapAsync('FilesDogWebpackPlugin', (compiler, cb) => {
        const mtimes = compiler.watchFileSystem.watcher.mtimes
        const mtimesKeys = Object.keys(mtimes)
        if (mtimesKeys.length > 0) {
          // 改动的文件 mtimesKeys
          if (disableModify instanceof Array) {
            for (let i = 0; i < disableModify.length; i++) {
              for (let j = 0; j < mtimesKeys.length; j++) {
                if (mtimesKeys[j].indexOf(disableModify[i]) !== -1) {
                  throw new Error('禁止改动:' + mtimesKeys[j])
                }
              }
            }
          } else if (typeof disableModify === 'string') {
            for (let i = 0; i < mtimesKeys.length; i++) {
              if (mtimesKeys[i].indexOf(disableModify) !== -1) {
                throw new Error('禁止改动:' + mtimesKeys[i])
              }
            }
          } else {
            console.error('FilesDogWebpackPlugin 插件配置项 disableModify 字段类型只支持 String 或 Array')
          }
        }
        cb()
      })
    }
  }
}
module.exports = FilesDogWebpackPlugin

本次示例的开发环境为:node(v10.15.0), webpack4