gulp-build-html
Version:
Used to process html files to automatically concat css and js files, and meanwhile update html references.
188 lines (157 loc) • 6.75 kB
JavaScript
var path = require("path"),
fs = require("fs"),
Vinyl = require('vinyl'),
utils = require("wzh.node-utils"),
BuildBlock = require("./BuildBlock"),
lib = require("./lib"),
constants = require("./constants");
var logger = lib.logger;
var rBuildBegin = /<!--\s*build\s*:\s*(\w*)\s*(.*?)\s*-->/gi,
rBuildEnd = /<!--\s*endbuild\s*-->/gi;
/**
* 从给定的字符串中解析构建块。构建块格式:
* <!-- build:[type] targetFileName -->
* ....
* <!-- endbuild -->
*
* @param {String} str 声明有构建块的字符串
* @param {String} [containingHtmlFilePath] 声明了该构建块的html文件路径
* @returns {BuildBlock[]}
*/
var parseBuildBlocks = function(str, containingHtmlFilePath){
rBuildBegin.lastIndex = 0;
rBuildEnd.lastIndex = 0;
var beginList = [], endIndexList = [];
if(utils.string.isEmptyString(str, true))
return [];
/* 确定构建块开始头 */
var tmp;
while((tmp = rBuildBegin.exec(str)) != null){
logger.debug("Matched build block beginning: {}", tmp[0]);
var targetFileName = utils.string.fillParamValue(tmp[2], {
fileName: path.basename(containingHtmlFilePath),
fileBaseName: path.basename(containingHtmlFilePath, path.extname(containingHtmlFilePath)),
});
var buildBlock = new BuildBlock().setBeginIndex(tmp.index).setType(tmp[1].toLowerCase()).setTargetFileName(targetFileName);
if(null != containingHtmlFilePath)
buildBlock.setContainingHtmlFilePath(containingHtmlFilePath);
beginList.push(buildBlock);
}
/* 确定构建块结束尾 */
while((tmp = rBuildEnd.exec(str)) != null){
logger.debug("Matched build block ending: {}", tmp[0]);
endIndexList.push({index: tmp.index, str: tmp[0]});
}
/* 确定构建块的开始和结束位置 */
var list = [];
for(var i = 0; i < endIndexList.length; i++){
var endIndex = endIndexList[i].index,
endStr = endIndexList[i].str;
var matchedBeginListIndex = -1;
for(var j = 0; j < beginList.length; j++){
var beginIndex = beginList[j].getBeginIndex();
if(beginIndex < endIndex && beginIndex > matchedBeginListIndex)
matchedBeginListIndex = j;
}
if(-1 !== matchedBeginListIndex){
beginList[matchedBeginListIndex].setEndIndex(endIndex + endStr.length - 1);
list.push(beginList[matchedBeginListIndex]);
beginList.splice(matchedBeginListIndex, 1);
}
}
/* 确定各个构建块之间的引用 */
list.forEach(function(buildBlock){
var type = String(buildBlock.getType()).toLowerCase(),
targetFileName = buildBlock.getTargetFileName();
var referenceStr = str.substring(buildBlock.getBeginIndex(), buildBlock.getEndIndex() + 1);
switch(type){
case "js":
while((tmp = constants.scriptRegexp.exec(referenceStr)) != null){
var src = utils.html.getAttribute(tmp[0], "src");
if(null != src && !utils.string.isEmptyString(src.value, true))/* 外联脚本 */
buildBlock.addReference(src.value);
else if(!utils.string.isEmptyString(tmp[1], true))/* 内联脚本 */
buildBlock.addReference(tmp[1], true);
}
break;
case "css":
while((tmp = constants.styleRegexp.exec(referenceStr)) != null){
var rel = utils.html.getAttribute(tmp[0], "rel");
if(null != rel && "stylesheet" === rel.value.trim().toLowerCase())
buildBlock.addReference(tmp[1]);
}
break;
default:
logger.error("Unknown build type: " + type + " to generate file: " + targetFileName);
}
});
return list;
};
/**
* 执行构建动作
* @param {String} fileContent 文件正文
* @param {String} fileAbsolutePath 文件的绝对路径
* @param {Object} [ops] 控制选项
* @param {GeneratedFileInstaller} [ops.generatedCssFileInstaller] 生成的css文件的安装器
* @param {GeneratedContentInstaller} [ops.generatedCssContentInstaller] 生成的css文件的安装器
* @param {GeneratedFileInstaller} [ops.generatedJsFileInstaller] 生成的js文件的安装器
* @param {GeneratedContentInstaller} [ops.generatedJsContentInstaller] 生成的js文件的安装器
* @param {BuildCompleteListener} [ops.oncomplete] 构建完成后要执行的方法
* @returns {{newFileContent: String, generatedVinylFiles: Vinyl[]}} 生成的vinyl文件实例集合
*/
var build = function(fileContent, fileAbsolutePath, ops){
var rst = {
newFileContent: "",
generatedVinylFiles: []
};
if(utils.string.isEmptyString(fileContent, true))
return rst;
logger.debug("Processing file: {}", fileAbsolutePath);
var blocks = parseBuildBlocks(fileContent, fileAbsolutePath);
var lastIndex = 0;
blocks.forEach(function(block){
var targetFileName = block.getTargetFileName(true);
/* 过滤不存在的文件引用 */
var filePathsOrContents = block.getReferencedFilePathsOrContents(true);
filePathsOrContents = filePathsOrContents.filter(function(filePathOrContent){
if(BuildBlock.isReferenceContent(filePathOrContent))
return true;
if(!fs.existsSync(filePathOrContent) || !fs.statSync(filePathOrContent).isFile()){
logger.error("File: {} not found while generating file: {} for {}", filePathOrContent, targetFileName, fileAbsolutePath);
return false;
}
return true;
});
logger.info("Generating file: {}", targetFileName);
var generatedFileContent = block.concatReferenceContent(filePathsOrContents, path.dirname(targetFileName));
utils.fs.mkdirSync(path.dirname(targetFileName));
fs.appendFileSync(targetFileName, generatedFileContent);
if(null != ops)
utils.function.try2Call(ops.oncomplete, null, targetFileName, block.getReferencedFilePathsOrContents(true));
rst.generatedVinylFiles.push(new Vinyl({
base: path.dirname(fileAbsolutePath),
path: targetFileName,
contents: new Buffer(generatedFileContent)
}));
var installStr = block.getTargetFileReferringHtml();
switch(block.getType()){
case "css":
if(null != ops && typeof ops.generatedCssFileInstaller === "function")
installStr = ops.generatedCssFileInstaller(targetFileName, generatedFileContent, fileAbsolutePath);
break;
case "js":
if(null != ops && typeof ops.generatedJsFileInstaller === "function")
installStr = ops.generatedJsFileInstaller(targetFileName, generatedFileContent, fileAbsolutePath);
break;
}
rst.newFileContent += fileContent.substring(lastIndex, block.getBeginIndex());
rst.newFileContent += "\r\n" + installStr + "\r\n";
lastIndex = block.getEndIndex() + 1;/* 结束索引为“构建块最后一个字符所在的索引” */
});
rst.newFileContent += fileContent.substring(lastIndex);
return rst;
};
module.exports = {
parseBuildBlocks: parseBuildBlocks,
build: build
};