UNPKG

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