# gulp手写注入插件
# 背景
因项目需要对打包文件加hash值做版本控制,于是使用gulp-rev
进行hash值的生成,看官方文档,要想将生成的文件注入到需要的地方,需要使用rev.manifest
生成个json文件,再从json文件中通过记录找出对应的hash值。
写法如下:
const gulp = require('gulp');
const rev = require('gulp-rev');
exports.default = () => (
// By default, Gulp would pick `assets/css` as the base,
// so we need to set it explicitly:
gulp.src(['assets/css/*.css', 'assets/js/*.js'], {base: 'assets'})
.pipe(gulp.dest('build/assets')) // 复制原资产build目录
.pipe(rev())
.pipe(gulp.dest('build/assets')) // 写rev的资产build目录
.pipe(rev.manifest())
.pipe(gulp.dest('build/assets')) // 写入清单以build目录
);
因为存在多个打包入口,使用上述写法rev.manifest
会被后执行的覆盖,所以得使用merge
选项进行合并。
.pipe(rev.manifest({
base: 'build/assets',
merge: true // 与现有清单合并(如果存在)
}))
在使用了上述写法后,发现当打包的文件有变化时,生成的json映射文件中的内容却没有变化,网上也没找到类似的状况,加上gulp的注入插件gulp-inject
在针对php文件时没生效,于是决定自己写一个注入插件。
# 实现
# 流处理
gulp插件就是对文件流进行处理
所以使用through2
来处理
var through2 = require('through2');
/**
* 注入
* @param {*} options 参数:
* store: 用于存储文件名及语言类型的数组
* index: 文件的加载顺序(文件大小不同,存入的顺序不同,所以要根据所以存)
* fileNumber: 总文件数(用于判断结束)
* injectStartKey: 用于注入时判断注入开始的标识
* injectEndKey: 用于注入时判断注入结束的标识(在开始与结束之间的内容将被替换)
* injectFilesPath: 要被注入的文件路径,可以是字符串或者数组
* @returns
*/
function inject (options) {
return through2.obj(function (file, encoding, cb) {
recordFileData(file, options);
cb(null, file);
});
}
module.exports = inject;
将当前流文件传入方法recordFileData
中进行数据的记录。
处理完成后调callback函数让gulp继续走下去。
# 记录文件数据
下面来看下主要的处理逻辑
// 生成hash值后的回调,因为是异步的,所以得每次判断长度,全部执行完时再执行注入操作
function recordFileData (file, options) {
var nameArr = file.history[file.history.length - 1].split('\\');
var store = options.store;
store[options.index] = {
name: nameArr[nameArr.length - 1],
language: options.language
};
if (store.length === options.fileNumber) {
injectStaticFile(options);
}
}
因为gulp-rev
处理后的文件会带有history
属性,其中最后一个就是带hash值的文件名(绝对路径),将文件名截出来,并且和文件的语言类型记录到传入的store中,用于后面的文件注入。
之前在options
中有传文件总数量和该文件在总文件中的顺序索引(因为存在多个打包入口,根据文件大小,打包完成时间不会按照顺序来,所以这里用索引来记录顺序),判断store
的总长度等于文件总数时,即认为全部文件都存入完成,开始注入。
# 注入
从options
中取出注入的标识injectStartKey
和injectEndKey
以及需要替换的文件路径injectFilesPath
循环store,拼接资源链接字符串。
如果是有语言区分的(中英文环境),需要用php变量进行判断,要注意使用php代码时字符串要转义。
拼好字符串后根据分隔符插入字符串,并写入到文件中,完成了需求。
看代码:
// 向文件注入文件名
function injectStaticFile (options) {
var store = options.store
var injectStartKey = options.injectStartKey;
var injectEndKey = options.injectEndKey;
// 需要替换的文件路径数组
var injectFilesPath = options.injectFilesPath;
// 传的是字符串的话转成数组
if (typeof injectFilesPath === 'string') {
injectFilesPath = [injectFilesPath];
}
var fileArr = [];
var lineFeed = '\n';
store.forEach(function (item) {
var str = '';
if (item.language) {
str = '<?php' +
' if ($language === "' + item.language + '") {' +
'echo "' + generateFilesLink(item, true) + '";' +
' }' +
' ?>'
} else {
str = generateFilesLink(item, false);
}
fileArr.push(str);
});
// 拼出需引用资源的链接的字符串
var linkStr = lineFeed + fileArr.join(lineFeed) + lineFeed;
injectFilesPath.forEach(function(phpFilePath) {
injectStaticFileSingle(phpFilePath, linkStr, injectStartKey, injectEndKey);
})
}
// 向单个文件注入文件名
function injectStaticFileSingle (phpFilePath, linkStr, injectStartKey, injectEndKey) {
var phpFile = fs.readFileSync(path.resolve(__dirname, phpFilePath), 'utf8');
var injectStartArr = phpFile.split(injectStartKey);
var injectStart = injectStartArr[0];
var injectEndArr = injectStartArr[1].split(injectEndKey);
var injectEnd = injectEndArr[1];
phpFile = injectStart + injectStartKey + linkStr + injectEndKey + injectEnd;
fs.writeFileSync(path.resolve(__dirname, phpFilePath), phpFile, 'utf8');
}
// php里的字符要转义
function generateFilesLink (item, inPhp) {
if (inPhp) {
if (item.name.indexOf('.js') !== -1) {
return "<script type=\\\"text/javascript\\\" src=\\\"./static/" + item.name + "\\\"></script>";
} else {
return "<link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"./static/"+ item.name + "\\\" />";
}
} else {
if (item.name.indexOf('.js') !== -1) {
return '<script type="text/javascript" src="./static/' + item.name + '"></script>';
} else {
return '<link rel="stylesheet" type="text/css" href="./static/'+ item.name + '" />';
}
}
}