阅读 2358

Webpack打包改造--插入自定义内容

Webpack打包改造--插入自定义内容

一、描述

在实际业务开发中,有可能遇到这样一种问题,因平台打包或者差异性等原因,提交给测试的包,我们需要确定是否为某一版本,这就要求我们的生成包中需要有一版本标记,并且这个版本标记在运行时是固定不变但在下次编译时是可变的,而且在打包编译生成时写入。
前端工程化,目前使用较多的为Webpack,本文讨论的是v4.0+的版本。
效果如下:


业务要求--在代码里边写入如下类似内容:

经过分析和比较现有方案,选择使用HtmlWebpackPlugin这种自定义插件方式进行。

二、插件分析

1.HtmlWebpackPlugin

按官方描述:

This is a webpack plugin that simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply your own template using lodash templates or use your own loader.

说白了就是它能在编译各个阶段提供一系列对html,css,js资源控制的勾子回调函数,在回调函数拦截中我们可以对打包结果进行自定义输出干预。 这样就能满足当Webpack某些功不能满足我们业务需求时,可使用此功能作为有利的扩充。
HtmlWebpackPlugin

2.勾子函数

HtmlWebpackPlugin提供的勾子分成5个部份,如下图所示:

从上至下依次执行5个步骤:

  • 开始生成HTML之前勾子(HtmlWebpackPlugin BeforeHtmlGeneration)
    这一阶做一些资源归类工作,主要产出物为asset资源原始对象,该对象为插入HTML头尾部的JS,CSS资源列表及其路径。

  • 在HTML开始处理之前勾子(HtmlWebpackPlugin BeforeHtmlProcessing)
    生成不包括JS和CSS的纯HTML结果,产出物为html字符串。

  • 添加资源处理HTML勾子(HtmlWebpackPluginAlterAssetTags)
    组装要插入HTML页面中的JS,CSS等资源结构, 如果要在生成HTML页面中加入自定义或者WEBPACK不支持的<script>标签等,可操作该对象。

  • HTML处理完毕勾子(HtmlWebpackPluginAfterHtmlProcessing)
    HTML页处理完成阶段,JS,CSS完成插入,已生成可直接打包用的文本结构,一般要输出自定义内容在此处实现。

  • 勾子任务处理完毕发送事件时(HtmlWebpackPluginAfterEmit)
    处理任务最后阶段,可通过返回的html属性访问source和size属性。

三、功能实现

1.编写插件类

由以上插件编译过程分析得知,我们需要实现的功能,需要在(HtmlWebpackPluginAfterHtmlProcessing)这一阶段完成,基本原理为,生成时间截,替换<html>标签,插入自定义内容。

  • 类结构

    HtmlWbpackPlugin勾子,入口为apply函数,该函数返回compiler对象,即当前处理编译的对象,该对象有个hooks属性,利用这个属性和其子属性中可以方问tap方法,在这里可以挂载处理过程函数。 将所有勾子添加后的代码结果类似如下:
    其中,htmlPluginData为拦截的处理对象,callback为该勾子的回调,记住需要处理回调,否则,插件无法在该步骤往下执行。

  • 生成时间截

该功能为特定业务需求,这里将它们封装在一个更细化功能划分的函数中实现,最终实现是返回可读时间截。

  • 替换<html>标签
    完成业务功能之后,下一步需要的则是做打包输出改造,在这里将该字符串替换<html>标签的方式进行。

2.与Vue结合

经过前面步骤,进行的是插件封装,下一步,需要将插件与现有Webpack打包机制结合,本示例展示的是使用Vue框架情况下的Webpack功能扩展。
在Vue中,需要改造vue.config.js
vue.config.js提供了完善的Webpack扩展机制。打开配置后,我们只需关注点在configureWebpack: {...}这段配置中。

加入自定义插件:定位到configureWebpack属性中的plugins:[],添加自定义手件即可。

3.核心代码

/** 自定义插件,插入打包日期版本号 */
class HtmlWebpackCommonLibsPlugin {
	constructor(options) {
		// 外部传入配置
		this.options = options || {};
	}

	apply(compiler) {
	    // 插件名
		const pluginName = 'HtmlWebpackCommonLibsPlugin';

		if (compiler.hooks) {
			
			// webpack 4 support
			compiler.hooks.compilation.tap(pluginName, (compilation) => {

				// 开始生成html之前勾子
				compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync(
					pluginName,
					(htmlPluginData, callback) => {
						// htmlWebpackPluginBeforeHtmlGeneration 返回 HtmlWebpackPlugin对象
						// {
						// 	assets
						// 	outputName
						// 	plugin
						// }
						// console.log(htmlPluginData);
						callback(null, htmlPluginData);
					}
				);

				// 在html开始处理之前勾子
				compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(
					pluginName,
					(htmlPluginData, callback) => {
						// htmlWebpackPluginBeforeHtmlProcessing 返回 HtmlWebpackPlugin对象
						// {
						// 	html
						// 	assets
						// 	outputName
						// 	plugin
						// }
						// console.log(htmlPluginData);
						callback(null, htmlPluginData);
					}
				);

				// 添加资源处理HTML勾子
				compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(
					pluginName,
					(htmlPluginData, callback) => {
						// htmlWebpackPluginAlterAssetTags 返回 HtmlWebpackPlugin对象
						// {
						// 	head
						//  body
						//  chunks
						// 	outputName
						// 	plugin
						// }
						// console.log(htmlPluginData);
						callback(null, htmlPluginData);
					}
				);
				
				// HTML处理完毕勾子
				compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(
					pluginName,
					(htmlPluginData, callback) => {
						// 生成格式化后时间截
						const injectStr = this.getInjectContent();
						htmlPluginData.html = htmlPluginData.html.replace('</html>', injectStr);
						
						// htmlWebpackPluginAfterHtmlProcessing 返回 HtmlWebpackPlugin对象
						// {
						// 	html 字符串
						// 	assets
						// 	plugin
						// 	childCompilerHash
						// 	childCompilationOutputName
						// 	assetJson
						//  outputName
						// }
						// console.log(htmlPluginData);
						callback(null, htmlPluginData)
					}
				);
				
				// 勾子任务处理完毕发送事件时
				compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync(
					pluginName,
					(htmlPluginData, callback) => {
						// htmlWebpackPluginAfterEmit 返回 HtmlWebpackPlugin对象
						// {
						// 	html 简化对象 { source, size }
						// 	outputName
						// 	plugin
						// 	childCompilerHash
						// 	childCompilationOutputName
						// 	assetJson
						// }
						// console.log(htmlPluginData);
						callback(null, htmlPluginData);
					}
				);
			});
		}
	}

	/** 格式化小于10数字 */
	getAbsValue(value) {
		if (value < 10) {
			return `0${value.toString()}`;
		} else {
			return value.toString();
		}
	}

	/** 生成格式化后时间截 */
	getInjectContent() {
		const dateVal = new Date(); 
		const versionNum = 
			dateVal.getFullYear() + '/' +
			this.getAbsValue((dateVal.getMonth()+1)) + '/' +
			this.getAbsValue(dateVal.getDate()) + ' ' +
			this.getAbsValue(dateVal.getHours()) + ':' +
			this.getAbsValue(dateVal.getMinutes());
		const injectStr = `
			<script type="text/javascript">console.log("当前版本: ${versionNum}");</script></html>
			`;

		return injectStr;
	}
}
  
module.exports = HtmlWebpackCommonLibsPlugin;
复制代码

四、总结

本示例中使用的功能可以以此为母板,扩充到工程化中干扰Html生成时打包结果的方方面面。比如:

  • 给输出的<script>或者<style>标签加上自定义内容
  • 给输出的<script>标签添都加上crossorigin
  • 插入免打包公共底层核心等等

下图中所示内容为之前做过的一个Webpack扩充方案的部份代码片断,主要干预了<script><style>自定义输出,仅供参考: