webpack | plugin机制详解

537 阅读2分钟

这是我参与更文挑战的第25天,活动详情查看:更文挑战

[webpack系列文章连载中...]

webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象。

compiler hooktap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中重复使用。

1. 基本结构

class Plugin{    // 插件名称
  constructor(options){
    // 通过构造函数获取传递参数
    this.options = options;
  }
  apply(compiler){    // 插件上的apply方法
    // 监听钩子触发事件
    compiler.hooks.done.tap('Plugin',(stats)=>{ // done钩子触发,stats作为参数
    	// 插件处理逻辑
    })
  }
}

module.exports = Plugin;

2. 配置文件的声明

// webpack配置文件中的配置
module.exports = {
  entry:'./index.js',
  output:{
    path:'./dist',
    filename:'[name].js'
  },
  plugins:[
    new Plugin({配置参数})
  ]
}

3. 模拟器webpack调用plugin

// webpack编译器中的调用
//tapable使用:模拟webpack的Compiler.js
const {SyncHook} = require("tapable");
module.exports = class Compiler{
    constructor(){
        this.hooks = {
            // 1. 注册同步钩子
           init:new SyncHook(['start']),  
        }
    }
    run(){
        // 3. 触发钩子函数
        this.hooks.init.call()
    }
}

//模拟 plugin.js
class Plugin{
 constructor(){}
  apply(compiler){
    // 2. 插件内监听钩子函数
  	compiler.hooks.init.tap('start',()=>{
    	console.log('compiler start')
    })
  }
}

// 模拟webpack.js
const options = {
   plugins:[new Plugin()]
}

const compiler = new Compiler()
for(const plugin of options.plugins){
  if(typeof plugin==='function'){
  	plugin.call(compiler,compiler)
  }else{
  	plugin.apply(compiler)
  }
}

compiler.run()

4. CorsPlugin跨域插件

用于处理网页域名和要载入的静态文件存放的站点域名不一致时候的跨域问题。

module.exports = class CorsPlugin {
  constructor ({ publicPath, crossorigin, integrity }) {
    /*
     * crossorigin:跨域属性
     * 值:anonymous || '' :对此元素的CORS请求将不设置凭据标志
     * 值:use-credentials :对此元素的CORS请求将设置凭证标志;这意味着请求将提供凭据
     */
    this.crossorigin = crossorigin
    // 请求完整性,供CDN 的静态文件使用,检查文身否为原版,防止使用的CDN资源被劫持篡改
    this.integrity = integrity
    this.publicPath = publicPath
  }

  apply (compiler) {
    const ID = `vue-cli-cors-plugin`
    compiler.hooks.compilation.tap(ID, compilation => {
      /*
       * Standard Subresource Integrity缩写
       * 用于解析,操作,序列化,生成和验证Subresource Integrity哈希值。
       */ 
      const ssri = require('ssri')
			
      // 计算文件的integrity值
      const computeHash = url => {
        const filename = url.replace(this.publicPath, '')
        const asset = compilation.assets[filename]
        if (asset) {
          const src = asset.source()
          const integrity = ssri.fromData(src, {
            algorithms: ['sha384']
          })
          return integrity.toString()
        }
      }

      compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(ID, data => {
        const tags = [...data.head, ...data.body]
        if (this.crossorigin != null) {
          // script || link 标签设置允许跨域
          tags.forEach(tag => {
            if (tag.tagName === 'script' || tag.tagName === 'link') {
              tag.attributes.crossorigin = this.crossorigin
            }
          })
        }
        if (this.integrity) {
          // 校验文件是否为原版
          tags.forEach(tag => {
            if (tag.tagName === 'script') {
              const hash = computeHash(tag.attributes.src)
              if (hash) {
                tag.attributes.integrity = hash
              }
            } else if (tag.tagName === 'link' && tag.attributes.rel === 'stylesheet') {
              const hash = computeHash(tag.attributes.href)
              if (hash) {
                tag.attributes.integrity = hash
              }
            }
          })

          // when using SRI, Chrome somehow cannot reuse
          // the preloaded resource, and causes the files to be downloaded twice.
          // this is a Chrome bug (https://bugs.chromium.org/p/chromium/issues/detail?id=677022)
          // for now we disable preload if SRI is used.
          data.head = data.head.filter(tag => {
            return !(
              tag.tagName === 'link' &&
              tag.attributes.rel === 'preload'
            )
          })
        }
      })

      compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => {
        data.html = data.html.replace(/\scrossorigin=""/g, ' crossorigin')
      })
    })
  }
}