最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • webpack之plugin编写 -- 从html-webpack-plugin源码的角度来看如何编写plugin

    正文概述 掘金(langyanyanglei)   2020-11-29   635

    webpack之plugin编写 -- 从html-webpack-plugin源码的角度来看如何编写plugin

    ​ 准备写个webpack的plugin,打开官网文档https://www.webpackjs.com/contribute/writing-a-plugin,发现有点蒙圈,看完文档好像知道怎么写,但又写不出来,所以看下html-webpack-plugin的实现,从而整体了解下插件的整体流程

    一、html-webpack-plugin源码分析

    1.插件的作用

    这个插件都用过,它的作用可以概况为两类:

    1. 生成html页面
    2. 处理bundle.js

    2.结合plugin 的编写步骤分析html-webpack-plugin的实现

    2.1流程步骤

    webpack文档的步骤:

    webpack 插件由以下组成:
    1. 一个 JavaScript 命名函数。
    2.在插件函数的 prototype 上定义一个 apply 方法。
    3.指定一个绑定到 webpack 自身的事件钩子。
    4.处理 webpack 内部实例的特定数据。
    5.功能完成后调用 webpack 提供的回调。
    

    根据文档步骤可以写出一下代码:

    class HtmlWebpackPlugin {
         constructor(options) {
    
         }
         apply(compiler){
             compiler.hooks.emit.tapAsync((compilation,callback) => {
                  ...
                 callback()
             })
         }
    }
    
    module.exports = HtmlWebpackPlugin;
    

    这就一个plugin插件的雏形,这个光看文档也能写出来,所以看源码的主要目的是看第4步,即如何处理webpack内部实例的特定数据

    2.2需要了解的钩子

    a.compiler

    www.webpackjs.com/api/compile…

    github.com/webpack/web…

    它是扩展自tapable类,用来注册和调用插件,tapable的用法有篇博客专业介绍,这里不再说了,简单来说就是compiler扩展与tapable,抛出一系列的生命周期钩子函数,以便我们在webpack的不同编译阶段进行不同的操作

    本次主要关注 emit钩子

    b.compilation

    www.webpackjs.com/api/compila…

    github.com/webpack/web…

    它可以理解为webpack编译一次生成的整个编译资源,一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。本次主要关注 assets

    2.3html-webpack-plugin如何处理数据

    a. 如何生成html

    1.插件调用:

    new HtmlWebPackPlugin({
        template: "./public/index.html",
        filename: "./index.html"
    }),
    

    当插件调用的时候会传入html 的 template以及filename

    apply(){
     ...
     this.options.template = this.getFullTemplatePath(this.options.template, compiler.context);
     ...
    }
    

    getFullTemplatePath() 会判断模板是否配置了loader,如果没有配置会使用默认的loader

    2.index.html生成过程:

    index.js
     ....
     compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
     .....
    
    
    compiler.js
    
    ....
       const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
    ...   // 创建一个跟当前Compiler一样配置的Compiler(childCompiler)对象
        childCompiler.runAsChild((err, entries, childCompilation) => {  //开始执行childCompiler编译器的run操作(独立于主编译器外的编译器)
        ...
         resolve({
              hash: entries[0].hash,  //当前模块(index.html)的hash值
              outputName: outputName,  //当前模块(index.html)的名称
              content: childCompilation.assets[outputName].source() /当前模块(index.html)编译过后的源码
            });
        })
    ....
    

    到这里其实index.html已经加载了,childCompilation.assets[outputName].source() 返回的就是index.html编译之后的源码,下一步就是把所有的打包文件添加到index.html中

    3.将生成的资源文件(js/css)插入html

    处理chunks

    const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks;
          // Filter chunks (options.chunks and options.excludeCHunks)
          let chunks = self.filterChunks(allChunks, self.options.chunks, self.options.excludeChunks);
          // Sort chunks
          chunks = self.sortChunks(chunks, self.options.chunksSortMode, compilation);
          // Let plugins alter the chunks and the chunk sorting
          if (compilation.hooks) {
            chunks = compilation.hooks.htmlWebpackPluginAlterChunks.call(chunks, { plugin: self });
          } else {
            // Before Webpack 4
            chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self });
          }
          // Get assets
          const assets = self.htmlWebpackPluginAssets(compilation, chunks);
    

    compilation的assets是map对象,html-webpack-plugin会先将assets的chunks处理一下,便于后期插入

      evaluateCompilationResult (compilation, source) {
        if (!source) {
          return Promise.reject('The child compilation didn\'t provide a result');
        }
        source = source.replace('var HTML_WEBPACK_PLUGIN_RESULT =', '');
        const template = this.options.template.replace(/^.+!/, '').replace(/\?.+$/, '');
        const vmContext = vm.createContext(_.extend({HTML_WEBPACK_PLUGIN: true, require: require}, global));
        const vmScript = new vm.Script(source, {filename: template});
        let newSource;
        try {
          newSource = vmScript.runInContext(vmContext);
        } catch (e) {
          return Promise.reject(e);
        }
        if (typeof newSource === 'object' && newSource.__esModule && newSource.default) {
          newSource = newSource.default;
        }
        return typeof newSource === 'string' || typeof newSource === 'function'
          ? Promise.resolve(newSource)
          : Promise.reject('The loader "' + this.options.template + '" didn\'t return html.');
      }
    

    这里使用node的vm(虚拟机)来构建index.html源码,source 其实就是上一步生成的compilationPromise

      generateHtmlTags (assets) {
        const scripts = assets.js.map(scriptPath => ({
          tagName: 'script',
          closeTag: true,
          attributes: {
            type: 'text/javascript',
            src: scriptPath
          }
        }));
        const selfClosingTag = !!this.options.xhtml;
        const styles = assets.css.map(stylePath => ({
          tagName: 'link',
          selfClosingTag: selfClosingTag,
          voidTag: true,
          attributes: {
            href: stylePath,
            rel: 'stylesheet'
          }
        }));
        let head = this.getMetaTags();
        let body = [];
        if (assets.favicon) {
          head.push({
            tagName: 'link',
            selfClosingTag: selfClosingTag,
            voidTag: true,
            attributes: {
              rel: 'shortcut icon',
              href: assets.favicon
            }
          });
        }
        head = head.concat(styles)
        if (this.options.inject === 'head') {
          head = head.concat(scripts);
        } else {
          body = body.concat(scripts);
        }
        return {head: head, body: body};
      }
    

    generateHtmlTags()根据assets中js和css生成要插入html的head和body,assets是从compilation.getStats()获取并整理处理的 chunks对象

    injectAssetsIntoHtml (html, assets, assetTags) {
        const htmlRegExp = /(<html[^>]*>)/i;
        const headRegExp = /(<\/head\s*>)/i;
        const bodyRegExp = /(<\/body\s*>)/i;
        const body = assetTags.body.map(this.createHtmlTag.bind(this));
        const head = assetTags.head.map(this.createHtmlTag.bind(this));
    
        if (body.length) {
          if (bodyRegExp.test(html)) {
            // Append assets to body element
            html = html.replace(bodyRegExp, match => body.join('') + match);
          } else {
            // Append scripts to the end of the file if no <body> element exists:
            html += body.join('');
          }
        }
    
        if (head.length) {
          // Create a head tag if none exists
          if (!headRegExp.test(html)) {
            if (!htmlRegExp.test(html)) {
              html = '<head></head>' + html;
            } else {
              html = html.replace(htmlRegExp, match => match + '<head></head>');
            }
          }
    
          // Append assets to head element
          html = html.replace(headRegExp, match => head.join('') + match);
        }
    
        // Inject manifest into the opening html tag
        if (assets.manifest) {
          html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
            // Append the manifest only if no manifest was specified
            if (/\smanifest\s*=/.test(match)) {
              return match;
            }
            return start + ' manifest="' + assets.manifest + '"' + end;
          });
        }
        return html;
      }
    

    injectAssetsIntoHtml()函数就是将处理过的body和header插入index.html中

    ...
    compilation.assets[self.childCompilationOutputName] = {
        source: () => html,
        size: () => html.length
     };
    ....          
    

    最后一步就是将已经插入资源的index.html加入到compilation的assets中,便于webpack打包生成文件

    然后再去看webpack官网plugin编写的案例:

    function FileListPlugin(options) {}   //第一步: 创建一个函数
    
    FileListPlugin.prototype.apply = function(compiler) {     //第二步:在函数的prototype上定义一个apply方法
      compiler.plugin('emit', function(compilation, callback) {  //第三步:指定一个webpack自身的钩子
        
        
        //第四步: 处理webpack内部实例的特定数据
          webpack的所有资源都会生成在assets对象中(html是html-webpack-plugin生成插入的),
          assets 对象:
            {
              './index.html': {
                  source: [Function:source],     //文件源码
                  size:[Function:size]           //文件大小
              }
            }
         所以如果想生成新文件可以直接在assets中插入,webpack会将文件生成在dist文件(出口文件)下,如果想处理其他文件可以在assets中修改
        
        
        var filelist = 'In this build:\n\n';
        for (var filename in compilation.assets) {
          filelist += ('- '+ filename +'\n');
        }
        compilation.assets['filelist.md'] = {
          source: function() {
            return filelist;
          },
          size: function() {
            return filelist.length;
          }
        };
    
        callback();   //第五步: 功能完成之后调用webpack的回调
      });
    };
    
    module.exports = FileListPlugin;
    

    最后本地调试插件:

    weebpack.config.js
    
    const HtmlWebPackPlugin = require('./html-webpack-plugin')  //引入本地文件
    ....
    plugin:[
       new HtmlWebPackPlugin({
          template: "./public/index.html",
          filename: "./index.html"
       }),
    ]
    
    

    本地开发完npm发布就行


    起源地下载网 » webpack之plugin编写 -- 从html-webpack-plugin源码的角度来看如何编写plugin

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元