YYDS: Webpack Plugin development

YYDS: Webpack Plugin development
  As a small developer who has not been on the front end for a long time, I have to talk about it webpack. When I first started contacting webpack, what was the first reaction (⊙_⊙)? Why is it so complicated, it feels so difficult, forget it! Time is a good thing. As the right 前端工程化practice and understanding gradually deepened, and more and more contact with webpack, he was finally convinced by him, and he couldn't help shouting " webpack yyds(永远滴神)!"

  I wanted to write some articles about webpack in the middle of last year, but it was delayed due to various reasons (mainly I felt that I didn’t understand webpack enough, and I didn’t dare to write it arrogantly); Touch webpack, organize some "new year goods" and share with xdm in need! I will continue to write some [Webpack] articles in the follow-up, supervised by xdm···

Guide

  This article mainly introduces the specific process of plug-in development through the implementation of a cdn优化plug - in. The middle will involve the use of plug-ins, the configuration of webpack plug-ins in the project, and the description of webpack-related knowledge points. The full text is about 2800+ words, and it is expected to take 5 to 10 minutes. I hope xdm can learn, think, and output after reading it!CdnPluginInjectwebpackpluginhtml-webpack-pluginvue/cli3+

Note: The examples in the article are based on vue/cli3+project deployment!

1. Regular use of 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',
      ···
    }
  },

2. Develop a webpack plugin

The webpack official website introduces this: The plugin provides third-party developers with complete capabilities in the webpack engine. Using staged build callbacks, developers can introduce their own behaviors into the webpack build process. Creating a plugin is more advanced than creating a loader, because you will need to understand some of the underlying internal features of webpack to implement the corresponding hooks!

A plug-in consists of the following:

  • A named JavaScript function.
  • Define the apply method on its prototype.
  • Specify a touch webpack own event hook .
  • Manipulate instance-specific data inside webpack.
  • Call the callback provided by webpack after the function is implemented.
    // 一个 JavaScript class
    class MyExampleWebpackPlugin {
    // 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数
    apply(compiler) {
    // 指定要附加到的事件钩子函数
     compiler.hooks.emit.tapAsync(
       'MyExampleWebpackPlugin',
       (compilation, callback) =&gt; {
         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();
       }
     );
    }
    }

Three, CDN optimization plug-in implementation

Ideas:

  • 1, create a named JavaScriptfunction (use ES6of classrealization);
  • 2, it is defined in the prototype applymethod;
  • 3. Specify an event hook that touches webpack itself (the compilationhook is touched here : after compilation is created, the plugin is executed);
  • 4, in the event of operation of the hook index.html(to cdnthe script标签inserted into the index.htmlmiddle);
  • 5. Put it in applybefore the method is executed ;cdn的参数webpack外部扩展externals
  • 6. Call webpackprovided after the function is implemented callback;

Implementation steps:

1, create a named JavaScript(using the function ES6of the classrealization)

  Create a class cdnPluginInject, add the constructor of the class to receive the passed parameters; here we define the format of the received parameters as follows:

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

The variables of the definition class modulesreceive the passed cdn参数processing results:

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

2, it is defined in the prototype applymethod

Plug-in is a constructor (prototype object has this constructor applymethod) instantiated out. This applymethod of installing the plug, will be called once webpack compiler. applyThe method can receive a reference to the webpack compiler object, so that the compiler object can be accessed in the callback function

cdnPluginInject.jscode show as below:

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

module.exports = CdnPluginInject;

3. Specify an event hook that touches webpack itself

  The compilationhook is touched here : after the compilation is created, the plugin is executed.

YYDS: Webpack Plugin development

  compilationYes compiler, a hook function. Compilation creates a new instance of the compilation process. A compilation instance can be used 访问所有模块和它们的依赖. After obtaining these modules, you can manipulate them as needed!

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. Operate in the hook eventindex.html

  This step is mainly to be achieved to cdnthe script标签inserted index.htmlin ; how to achieve it? In the vue project, webpack actually uses html-webpack-plugin to generate .htmlfiles when packaging , so we can also html-webpack-plugininsert cdn script tags by operating html files here .

// 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;

Next, analyze the above realization step by step:

  • 4.1. Introduce html-webpack-plugin dependency, it goes without saying;
  • 4.2. html-webpack-pluginThe hooksfunction in the call is html-webpack-pluginexecuted asynchronously before the resource is generated; here is html-webpack-pluginthe author sincerely boasting , ta html-webpack-pluginbuilt a lot of hook functions in the plugin during development for developers to embed different operations at different stages of calling the plugin ; therefore, here we can use html-webpack-plugina beforeAssetTagGenerationpair of html operate;
  • 4.3. beforeAssetTagGenerationObtain all the configuration data that need to be imported by cdn in.
  • 4.4. Integrate existing js references and cdn references; data.assets.jsyou can get compilationall js资源the links/paths generated in the stage (and finally inserted into index.html), and cdn的path数据(cdn的url)merge the ones that need to be configured ;
  • 4.5. Integrate the existing css references and cdn references; data.assets.cssyou can get compilationall css资源the links/paths generated in the stage (and finally inserted into index.html), and cdn的path数据(cdn的url)merge the css types that need to be configured ;
  • 4.6. Return the callback function, the purpose is to tell webpackthe operation has been completed, you can proceed to the next step;

5, setting webpackthe外部扩展externals

  Before the applymethod is executed, there is one more step that must be completed: cdn的参数configure 外部扩展externalsit; you can directly compiler.options.externalsobtain the externals property in webpack, and configure the data in the cdn configuration after operation.

6、callback

  Return the callback to tell the webpack CdnPluginInjectplug-in has been completed;

// 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;

  At this point, a complete webpack plugin CdnPluginInjecthas been developed! Let's try it next.

Four, cdn optimized plug-in use

vue.config.jsIntroduce and use   in the file of the vue project CdnPluginInject:

cdn configuration file 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"
  },
  ···
];

Configure in 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
        })
      )
    }
  }
  ···
}

Configuration in 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
        })
      )
    }
  }
  ···
}

  By using CdnPluginInject:

  • 1. Realize the management and maintenance of CDN optimization through configuration;
  • 2. Realize the optimization of CDN configuration for different environments (the development environment directly uses the local installation dependencies for debugging, and the production environment adapts to the CDN mode to optimize loading);

V. Summary

  After reading it, there must be webpacksome doubts from the big guys , this plugin is not the beggar version of webpack-cdn-plugin ! CdnPluginInjectIt's just that I webpack-cdn-pluginlearn from the source code, combined with the imitation version that my project actually needs to modify. Compared webpack-cdn-pluginwith encapsulating the generation of the cdn link, it CdnPluginInjectis to directly configure the cdn link , which is easier to choose the cdn configuration. If you want to learn more, you can look at webpack-cdn-pluginthe source code of xdm . After the author's continuous iterative update, it provides more configurable parameters and more powerful functions (again).

重点:整理不易,觉得还可以的xdm记得 一键三连 哟!

Article reference

Guess you like

Origin blog.51cto.com/15066867/2597973