关于webpack:YYDS-Webpack-Plugin开发

4次阅读

共计 8430 个字符,预计需要花费 22 分钟才能阅读完成。


@[toc]
  作为一名踏足前端工夫不长的小开发必须得聊一聊 webpack,刚开始接触 webpack 时第一反馈这是啥(⊙_⊙)? 怎么这么简单,感觉好难呀,算了先不论这些!工夫是个好货色呀,随着对 前端工程化 的实际和了解缓缓加深,跟 webpack 接触越来越多,最终还是被 ta 折服,不禁高呼一声“webpack yyds(永远滴神)!

  去年年中就想写一些对于 webpack 的文章,因为各种起因耽误了(次要是感觉对 webpack 了解还不够,不敢妄自下笔);邻近年节,工夫也有些了,与其 “ 摸鱼 ” 不如摸摸 webpack,整顿一些 ” 年货 ” 分享给须要的 xdm!后续会持续写一些【Webpack】系列文章,xdm 监督···

导读

  本文次要通过实现一个 cdn 优化 的插件 CdnPluginInject 介绍下 webpack 的插件 plugin 开发的具体流程,两头会波及到 html-webpack-plugin 插件的应用、vue/cli3+我的项目中 webpack 插件的配置以及 webpack 相干知识点的阐明。全文大略 2800+ 字,预计耗时 5~10 分钟,心愿 xdm 看完有所学、有所思、有所输入!

留神:文章中实例基于 vue/cli3+ 工程开展!

一、cdn 惯例应用

index.html:

<head>
  ···
</head>
<body>
  <div id="app"></div>
  <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
  <script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
  ···
</body>

vue.config.js:

module.exports = {
  ···
  configureWebpack: {
    ···
    externals: {
      'vuex': 'Vuex',
      'vue-router': 'VueRouter',
      ···
    }
  },

二、开发一个 webpack plugin

webpack 官网如此介绍到:插件向第三方开发者提供了 webpack 引擎中残缺的能力。应用阶段式的构建回调,开发者能够引入它们本人的行为到 webpack 构建流程中。创立插件比创立 loader 更加高级,因为你将须要了解一些 webpack 底层的外部个性来实现相应的钩子!

一个插件由以下形成:

  • 一个具名 JavaScript 函数。
  • 在它的原型上定义 apply 办法。
  • 指定一个涉及到 webpack 自身的 事件钩子。
  • 操作 webpack 外部的实例特定数据。
  • 在实现性能后调用 webpack 提供的 callback。
// 一个 JavaScript class
class MyExampleWebpackPlugin {
// 将 `apply` 定义为其原型办法,此办法以 compiler 作为参数
 apply(compiler) {
   // 指定要附加到的事件钩子函数
     compiler.hooks.emit.tapAsync(
       'MyExampleWebpackPlugin',
       (compilation, callback) => {console.log('This is an example plugin!');
         console.log('Here’s the `compilation` object which represents a single build of assets:', compilation);
         // 应用 webpack 提供的 plugin API 操作构建后果
         compilation.addModule(/* ... */);
         callback();}
     );
 }
}

三、cdn 优化插件实现

思路:

  • 1、创立一个具名 JavaScript 函数(应用ES6class实现);
  • 2、在它的原型上定义 apply 办法;
  • 3、指定一个涉及到 webpack 自身的事件钩子(此处涉及 compilation 钩子:编译 (compilation) 创立之后,执行插件);
  • 4、在钩子事件中操作 index.html(将cdnscript 标签 插入到 index.html 中);
  • 5、在 apply 办法执行完之前将 cdn 的参数 放入 webpack内部扩大 externals中;
  • 6、在实现性能后调用 webpack 提供的 callback

实现步骤:

1、创立一个具名 JavaScript 函数(应用ES6class实现)

  创立类cdnPluginInject,增加类的构造函数接管传递过去的参数;此处咱们定义接管参数的格局如下:

modules:[
  {
    name: "xxx",    //cdn 包的名字
    var: "xxx",    //cdn 引入库在我的项目中应用时的变量名
    path: "http://cdn.url/xxx.js" //cdn 的 url 链接地址
  },
  ···
]

定义类的变量 modules 接管传递的 cdn 参数 的处理结果:

class CdnPluginInject {
  constructor({modules,}) {
    // 如果是数组,将 this.modules 变换成对象模式
    this.modules = Array.isArray(modules) ? {["defaultCdnModuleKey"]: modules } : modules; 
  }
 ···
}
module.exports = CdnPluginInject;

2、在它的原型上定义 apply 办法

插件是由一个构造函数(此构造函数上的 prototype 对象具备 apply 办法)的所实例化进去的。这个 apply 办法在装置插件时,会被 webpack compiler 调用一次。apply 办法能够接管一个 webpack compiler 对象的援用,从而能够在回调函数中拜访到 compiler 对象

cdnPluginInject.js代码如下:

class CdnPluginInject {
  constructor({modules,}) {
    // 如果是数组,将 this.modules 变换成对象模式
    this.modules = Array.isArray(modules) ? {["defaultCdnModuleKey"]: modules } : modules; 
  }
  //webpack plugin 开发的执行入口 apply 办法
  apply(compiler) {···}

module.exports = CdnPluginInject;

3、指定一个涉及到 webpack 自身的事件钩子

  此处涉及 compilation 钩子:编译 (compilation) 创立之后,执行插件。

  compilation compiler 的一个 hooks 函数,compilation 会创立一次新的编译过程实例,一个 compilation 实例能够 拜访所有模块和它们的依赖,在获取到这些模块后,依据须要对其进行操作解决!

class CdnPluginInject {
  constructor({modules,}) {
    // 如果是数组,将 this.modules 变换成对象模式
    this.modules = Array.isArray(modules) ? {["defaultCdnModuleKey"]: modules } : modules; 
  }
  //webpack plugin 开发的执行入口 apply 办法
  apply(compiler) {
    // 获取 webpack 的输入配置对象
    const {output} = compiler.options;
    // 解决 output.publicPath,决定最终资源绝对于援用它的 html 文件的绝对地位
    output.publicPath = output.publicPath || "/";
    if (output.publicPath.slice(-1) !== "/") {output.publicPath += "/";}
    // 触发 compilation 钩子函数
    compiler.hooks.compilation.tap("CdnPluginInject", compilation => {···}
}

module.exports = CdnPluginInject;

4、在钩子事件中操作index.html

  这一步次要是要实现 cdnscript 标签 插入到 index.html;如何实现呢?在 vue 我的项目中 webpack 进行打包时其实是应用 html-webpack-plugin 生成 .html 文件的,所以咱们此处也能够借助 html-webpack-plugin 对 html 文件进行操作插入 cdn 的 script 标签。

// 4.1 引入 html-webpack-plugin 依赖
const HtmlWebpackPlugin = require("html-webpack-plugin");

class CdnPluginInject {
  constructor({modules,}) {
    // 如果是数组,将 this.modules 变换成对象模式
    this.modules = Array.isArray(modules) ? {["defaultCdnModuleKey"]: modules } : modules; 
  }
  //webpack plugin 开发的执行入口 apply 办法
  apply(compiler) {
    // 获取 webpack 的输入配置对象
    const {output} = compiler.options;
    // 解决 output.publicPath,决定最终资源绝对于援用它的 html 文件的绝对地位
    output.publicPath = output.publicPath || "/";
    if (output.publicPath.slice(-1) !== "/") {output.publicPath += "/";}
    // 触发 compilation 钩子函数
    compiler.hooks.compilation.tap("CdnPluginInject", compilation => { 
      // 4.2 html-webpack-plugin 中的 hooks 函数,当在资源生成之前异步执行
      HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration
       .tapAsync("CdnPluginInject", (data, callback) => {   // 注册异步钩子
            // 获取插件中的 cdnModule 属性(此处为 undefined,因为没有 cdnModule 属性)
          const moduleId = data.plugin.options.cdnModule;  
          // 只有不是 false(禁止)就行
          if (moduleId !== false) {    
             // 4.3 失去所有的 cdn 配置项
            let modules = this.modules[moduleId || Reflect.ownKeys(this.modules)[0] 
            ];
            if (modules) {
              // 4.4 整合已有的 js 援用和 cdn 援用
              data.assets.js = modules
                .filter(m => !!m.path)
                .map(m => {return m.path;})
                .concat(data.assets.js);
              // 4.5 整合已有的 css 援用和 cdn 援用
              data.assets.css = modules
                .filter(m => !!m.style)
                .map(m => {return m.style;})
                .concat(data.assets.css); 
            }
          }
            // 4.6 返回 callback 函数
          callback(null, data);
        });
  }
}

module.exports = CdnPluginInject;

接下来逐渐对上述实现进行剖析:

  • 4.1、引入 html-webpack-plugin 依赖,这个不必多说;
  • 4.2、调用 html-webpack-plugin 中的 hooks 函数,在 html-webpack-plugin 中资源生成之前异步执行;这里由衷的夸夸 html-webpack-plugin 的作者了,ta 在开发 html-webpack-plugin 时就在插件中内置了很多的 hook 函数供开发者在调用插件的不同阶段嵌入不同操作;因而,此处咱们能够应用 html-webpack-pluginbeforeAssetTagGeneration对 html 进行操作;
  • 4.3、在 beforeAssetTagGeneration 中,获取失去所有的须要进行 cdn 引入的配置数据;
  • 4.4、整合已有的 js 援用和 cdn 援用;通过 data.assets.js 能够获取到 compilation 阶段所有生成的 js 资源(最终也是插入 index.html 中)的链接 / 门路,并且将须要配置的cdn 的 path 数据(cdn 的 url) 合并进去;
  • 4.5、整合已有的 css 援用和 cdn 援用;通过 data.assets.css 能够获取到 compilation 阶段所有生成的 css 资源(最终也是插入 index.html 中)的链接 / 门路,并且将须要配置的 css 类型cdn 的 path 数据(cdn 的 url) 合并进去;
  • 4.6、返回 callback 函数,目标是通知 webpack 该操作曾经实现,能够进行下一步了;

5、设置 webpack内部扩大 externals

  在 apply 办法执行完之前还有一步必须实现:将 cdn 的参数 配置到 内部扩大 externals中;能够间接通过 compiler.options.externals 获取到 webpack 中 externals 属性,通过操作将 cdn 配置中数据配置好就 ok 了。

6、 callback

  返回 callback,通知 webpack CdnPluginInject插件曾经实现;

// 4.1 引入 html-webpack-plugin 依赖
const HtmlWebpackPlugin = require("html-webpack-plugin");

class CdnPluginInject {
  constructor({modules,}) {
    // 如果是数组,将 this.modules 变换成对象模式
    this.modules = Array.isArray(modules) ? {["defaultCdnModuleKey"]: modules } : modules; 
  }
  //webpack plugin 开发的执行入口 apply 办法
  apply(compiler) {
    // 获取 webpack 的输入配置对象
    const {output} = compiler.options;
    // 解决 output.publicPath,决定最终资源绝对于援用它的 html 文件的绝对地位
    output.publicPath = output.publicPath || "/";
    if (output.publicPath.slice(-1) !== "/") {output.publicPath += "/";}
    // 触发 compilation 钩子函数
    compiler.hooks.compilation.tap("CdnPluginInject", compilation => { 
      // 4.2 html-webpack-plugin 中的 hooks 函数,当在资源生成之前异步执行
      HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration
       .tapAsync("CdnPluginInject", (data, callback) => {   // 注册异步钩子
            // 获取插件中的 cdnModule 属性(此处为 undefined,因为没有 cdnModule 属性)
          const moduleId = data.plugin.options.cdnModule;  
          // 只有不是 false(禁止)就行
          if (moduleId !== false) {    
             // 4.3 失去所有的 cdn 配置项
            let modules = this.modules[moduleId || Reflect.ownKeys(this.modules)[0] 
            ];
            if (modules) {
              // 4.4 整合已有的 js 援用和 cdn 援用
              data.assets.js = modules
                .filter(m => !!m.path)
                .map(m => {return m.path;})
                .concat(data.assets.js);
              // 4.5 整合已有的 css 援用和 cdn 援用
              data.assets.css = modules
                .filter(m => !!m.style)
                .map(m => {return m.style;})
                .concat(data.assets.css); 
            }
          }
            // 4.6 返回 callback 函数
          callback(null, data);
        });
      
      // 5.1 获取 externals
         const externals = compiler.options.externals || {};
      // 5.2 cdn 配置数据增加到 externals
      Reflect.ownKeys(this.modules).forEach(key => {const mods = this.modules[key];
        mods
          .forEach(p => {externals[p.name] = p.var || p.name; //var 为我的项目中的应用命名
        });
      });
      // 5.3 externals 赋值
      compiler.options.externals = externals; // 配置 externals
      
      // 6 返回 callback
      callback();}
}

module.exports = CdnPluginInject;

  至此,一个残缺的 webpack 插件 CdnPluginInject 就开发实现了!接下来应用着试一试。

四、cdn 优化插件应用

  在 vue 我的项目的 vue.config.js 文件中引入并应用CdnPluginInject

cdn 配置文件 CdnConfig.js:

/*
 * 配置的 cdn
 * @name: 第三方库的名字
 * @var:第三方库在我的项目中的变量名
 * @path:第三方库的 cdn 链接
 */
module.exports = [
  {
    name: "moment",
    var: "moment",
    path: "https://cdn.bootcdn.net/ajax/libs/moment.js/2.27.0/moment.min.js"
  },
  ···
];

configureWebpack 中配置:

const CdnPluginInject = require("./CdnPluginInject");
const cdnConfig = require("./CdnConfig");

module.exports = {
  ···
  configureWebpack: config => {
    // 只有是生产山上线打包才应用 cdn 配置
    if(process.env.NODE.ENV =='production'){
      config.plugins.push(
        new CdnPluginInject({modules: CdnConfig})
      )
      }
  }
  ···
}

chainWebpack 中配置:

const CdnPluginInject = require("./CdnPluginInject");
const cdnConfig = require("./CdnConfig");

module.exports = {
  ···
  chainWebpack: config => {
    // 只有是生产山上线打包才应用 cdn 配置
    if(process.env.NODE.ENV =='production'){config.plugin("cdn").use(
        new CdnPluginInject({modules: CdnConfig})
      )
      }
  }
  ···
}

  通过应用CdnPluginInject

  • 1、通过配置实现对 cdn 优化的治理和保护;
  • 2、实现针对不同环境做 cdn 优化配置(开发环境间接应用本地装置依赖进行调试,生产环境适应 cdn 形式优化加载);

五、小结

  看完后必定有 webpack 大佬有一丝丝纳闷,这个插件不就是 webpack-cdn-plugin 的乞丐版!CdnPluginInject只不过是自己依据 webpack-cdn-plugin 源码的学习,联合本人我的项目理论所需批改的仿写版本,相较于 webpack-cdn-plugin 将 cdn 链接的生成进行封装,CdnPluginInject是间接将 cdn 链接进行配置,对于抉择 cdn 显配置更加简略。想要进一步学习的 xdm 能够看看 webpack-cdn-plugin 的源码,通过作者的一直的迭代更新,其提供的可配置参数更加丰盛,性能更加弱小(再次膜拜)。

重点:整顿不易,感觉还能够的 xdm 记得 一键三连 哟!

文章参考

  • 揭秘 webpack-plugin
  • webpack-cdn-plugin 的 github
正文完
 0