UNPKG

@rpose/compiler

Version:
1,241 lines (1,083 loc) 489 kB
console.time("load"); /* ------- a00m-env ------- */ (() => { // ------- a00m-env start const File = require("@gotoeasy/file"); const Btf = require("@gotoeasy/btf"); const bus = require("@gotoeasy/bus"); const Err = require("@gotoeasy/err"); const npm = require("@gotoeasy/npm"); const path = require("path"); const findNodeModules = require("find-node-modules"); // 从根目录的rpose.config.btf读取路径文件配置 // 读不到则使用默认配置 bus.on( "编译环境", (function(result) { return function(opts, nocache = false) { nocache && (result = null); if (result) return result; let packagefile = File.resolve(__dirname, "./package.json"); !File.existsFile(packagefile) && (packagefile = File.resolve(__dirname, "../package.json")); let compilerVersion = JSON.parse(File.read(packagefile)).version; let defaultFile = File.path(packagefile) + "/default.rpose.config.btf"; result = parseRposeConfigBtf("rpose.config.btf", defaultFile, opts); // 相对命令行目录 result.clean = !!opts.clean; result.release = !!opts.release; result.debug = !!opts.debug; result.nocache = !!opts.nocache; result.build = !!opts.build; result.watch = !!opts.watch; result.compilerVersion = compilerVersion; if (result.path.cache) { result.path.cache = File.resolve(result.path.cwd, result.path.cache); // 缓存目录 } return result; }; })() ); function parseRposeConfigBtf(file, defaultFile, opts) { let cwd = opts.cwd || process.cwd(); cwd = path.resolve(cwd).replace(/\\/g, "/"); if (!File.existsDir(cwd)) { throw new Err("invalid path of cwd: " + opts.cwd); } let root = cwd; file = File.resolve(root, file); if (!File.exists(file)) file = defaultFile; let result = { path: {} }; // 项目配置文件 let btf = new Btf(file); let mapPath = btf.getMap("path"); mapPath.forEach((v, k) => mapPath.set(k, v.split("//")[0].trim())); let mapImport = btf.getMap("taglib"); let imports = {}; mapImport.forEach((v, k) => (imports[k] = v.split("//")[0].trim())); result.imports = imports; // 目录 result.path.cwd = cwd; result.path.root = root; result.path.src = root + "/src"; result.path.build = getConfPath(root, mapPath, "build", "build"); result.path.build_temp = result.path.build + "/temp"; result.path.build_dist = result.path.build + "/dist"; result.path.build_dist_images = mapPath.get("build_dist_images") || "images"; // 打包后的图片目录 result.path.cache = mapPath.get("cache"); // 缓存大目录 result.path.svgicons = root + "/" + (mapPath.get("svgicons") || "resources/svgicons"); // SVG图标文件目录 result.theme = btf.getText("theme") == null || !btf.getText("theme").trim() ? "@gotoeasy/theme" : btf.getText("theme").trim(); result.prerender = btf.getText("prerender") == null || !btf.getText("prerender").trim() ? "@gotoeasy/pre-render" : btf.getText("prerender").trim(); result.port = btf.getText("port") == null || !btf.getText("port").trim() ? null : btf.getText("port").trim(); result.config = root + "/rpose.config.btf"; let packagejson = root + "/package.json"; if (File.existsFile(packagejson)) { result.packageName = JSON.parse(File.read(packagejson)).name; } // 自动检查安装依赖包 autoInstallLocalModules(result.theme, result.prerender); return result; } function getConfPath(root, map, key, defaultValue) { // TODO 检查配置目录的合法性 if (!map.get(key)) { return ( root + "/" + defaultValue .split("/") .filter(v => !!v) .join("/") ); } return ( root + "/" + map .get(key) .split("/") .filter(v => !!v) .join("/") ); } // TODO 提高性能 function autoInstallLocalModules(...names) { let ignores = ["@gotoeasy/theme", "@gotoeasy/pre-render"]; let node_modules = [...findNodeModules({ cwd: __dirname, relative: false }), ...findNodeModules({ cwd: process.cwd(), relative: false })]; for (let i = 0, name; (name = names[i++]); ) { if (ignores.includes(name)) continue; let isInstalled = false; for (let j = 0, dir; (dir = node_modules[j++]); ) { if (File.isDirectoryExists(File.resolve(dir, name))) { isInstalled = true; continue; } } !isInstalled && npm.install(name); } } // ------- a00m-env end })(); /* ------- a02m-clean ------- */ (() => { // ------- a02m-clean start const Err = require("@gotoeasy/err"); const File = require("@gotoeasy/file"); const bus = require("@gotoeasy/bus"); bus.on( "clean", (function() { return () => { try { let env = bus.at("编译环境"); if (env.clean) { File.remove(env.path.build); console.info("clean:", env.path.build); } File.mkdir(env.path.build_dist); } catch (e) { throw Err.cat(" clean failed", e); } }; })() ); // ------- a02m-clean end })(); /* ------- a10m-cache ------- */ (() => { // ------- a10m-cache start const bus = require("@gotoeasy/bus"); const cache = require("@gotoeasy/cache"); const csslibify = require("csslibify"); (function(result = {}, oCache, resourcesPaths) { bus.on("清除全部编译缓存", function() { result = {}; oCache = null; resourcesPaths = null; }); bus.on("组件编译缓存", function(file, context) { if (context) { result[file] = context; return context; } if (context === undefined) { return result[file]; } delete result[file]; }); bus.on("缓存", function() { if (!oCache) { let env = bus.at("编译环境"); oCache = cache({ name: "rpose-compiler-" + env.compilerVersion, path: env.path.cache }); } return oCache; }); bus.on("缓存资源目录数组", function() { if (!resourcesPaths) { resourcesPaths = [bus.at("缓存").path + "/resources", csslibify().basePath]; // 编译器缓存及样式库缓存的resources目录的绝对路径 } return resourcesPaths; }); })(); // ------- a10m-cache end })(); /* ------- a20m-src-file-manager ------- */ (() => { // ------- a20m-src-file-manager start const bus = require("@gotoeasy/bus"); const hash = require("@gotoeasy/hash"); const File = require("@gotoeasy/file"); (function(oFiles, oTagFiles = {}) { function getSrcFileObject(file, tag) { let text = File.read(file); let hashcode = hash(text); return { file, text, hashcode, tag }; } // 项目范围内,取标签相关的页面源文件 function getRefPages(tag) { if (!tag) return []; let refFiles = []; for (let file in oFiles) { let context = bus.at("组件编译缓存", file); if (context) { let allreferences = context.result.allreferences || []; allreferences.includes(tag) && refFiles.push(file); } } return refFiles; } bus.on("标签项目源文件", function(tag) { let ary = oTagFiles[tag]; if (ary && ary.length) { return ary[0]; } // 找不到时无视错误,返回undefined }); bus.on("源文件对象清单", function(nocache = false) { if (nocache) { oFiles = null; oTagFiles = {}; } if (!oFiles) { oFiles = {}; let env = bus.at("编译环境"); let files = File.files(env.path.src, "**.rpose"); // 源文件目录 files.forEach(file => { let tag = getTagOfSrcFile(file); if (tag) { let ary = (oTagFiles[tag] = oTagFiles[tag] || []); ary.push(file); if (ary.length === 1) { oFiles[file] = getSrcFileObject(file, tag); } } else { console.error("[src-file-manager]", "ignore invalid source file ..........", file); // 无效文件出警告 } }); for (let tag in oTagFiles) { let ary = oTagFiles[tag]; if (ary.length > 1) { console.error("[src-file-manager]", "duplicate tag name:", tag); // 同名文件出警告 console.error(ary); for (let i = 1, file; (file = ary[i++]); ) { console.error(" ignore ..........", file); } } } } return oFiles; }); bus.on("源文件添加", async function(oFile) { let tag = getTagOfSrcFile(oFile.file); if (!tag) { return console.error("[src-file-manager]", "invalid source file name ..........", oFile.file); // 无效文件出警告 } let ary = (oTagFiles[tag] = oTagFiles[tag] || []); ary.push(oFile.file); if (ary.length > 1) { console.error("[src-file-manager]", "duplicate tag name:", tag); console.error(ary); console.error(" ignore ..........", oFile.file); return; } oFiles[oFile.file] = getSrcFileObject(oFile.file, tag); // 第一个有效 await bus.at("全部编译"); }); bus.on("SVG文件添加", async function(svgfile) { await bus.at("重新编译和该SVG可能相关的组件和页面", svgfile); }); bus.on("图片文件添加", async function() { // 图片文件添加时,重新编译未编译成功的组件 let oFiles = bus.at("源文件对象清单"); for (let file in oFiles) { let context = bus.at("组件编译缓存", file); if (!context) { return await bus.at("全部编译"); } } }); bus.on("源文件修改", async function(oFileIn) { let tag = getTagOfSrcFile(oFileIn.file); let refFiles = getRefPages(tag); // 关联页面文件 let oFile = oFiles[oFileIn.file]; if (!tag || !oFile) { // 无关文件的修改,保险起见清理下 delete oFiles[oFileIn.file]; return; } if (oFile.hashcode === oFileIn.hashcode) return; // 文件内容没变,忽略 // 保存输入,删除关联编译缓存,重新编译 oFiles[oFile.file] = Object.assign({}, oFileIn); refFiles.forEach(file => { bus.at("组件编译缓存", file, false); // 删除关联页面的编译缓存 removeHtmlCssJsFile(file); }); bus.at("组件编译缓存", oFile.file, false); // 删除当前文件的编译缓存 removeHtmlCssJsFile(oFile.file); await bus.at("全部编译"); }); bus.on("SVG文件修改", async function(svgfile) { await bus.at("重新编译和该SVG可能相关的组件和页面", svgfile); }); bus.on("图片文件修改", async function(imgfile) { // 图片文件修改时,找出使用该图片文件的组件,以及使用该组件的页面,都清除缓存后重新编译 let oFiles = bus.at("源文件对象清单"); let refFiles = []; for (let file in oFiles) { let context = bus.at("组件编译缓存", file); if (context) { let refimages = context.result.refimages || []; if (refimages.includes(imgfile)) { // 比较的是全路径文件名 let tag = getTagOfSrcFile(file); // 直接关联的组件标签名 refFiles.push(file); // 待重新编译的组件 refFiles.push(...getRefPages(tag)); // 待重新编译的页面 } } } if (refFiles.length) { new Set(refFiles).forEach(pageFile => { bus.at("组件编译缓存", pageFile, false); // 清除编译缓存 removeHtmlCssJsFile(pageFile); }); await bus.at("全部编译"); } }); bus.on("源文件删除", async function(file) { let tag = getTagOfSrcFile(file); let refFiles = getRefPages(tag); // 关联页面文件 let oFile = oFiles[file]; let ary = oTagFiles[tag]; // 删除输入 delete oFiles[file]; if (ary) { let idx = ary.indexOf(file); if (idx > 0) { return ary.splice(idx, 1); // 删除的是被忽视的文件 } else if (idx === 0) { ary.splice(idx, 1); if (ary.length) { oFiles[ary[0]] = getSrcFileObject(ary[0], tag); // 添加次文件对象 bus.at("组件编译缓存", ary[0], false); // 不应该的事,保险起见清除该编译缓存 } else { delete oTagFiles[tag]; } } } if (!tag || !oFile) return; // 无关文件的删除 // 删除关联编译缓存,重新编译 refFiles.forEach(file => { bus.at("组件编译缓存", file, false); // 删除关联页面的编译缓存 removeHtmlCssJsFile(file); }); bus.at("组件编译缓存", oFile.file, false); // 删除当前文件的编译缓存 removeHtmlCssJsFile(oFile.file); await bus.at("全部编译"); }); bus.on("SVG文件删除", async function(svgfile) { await bus.at("重新编译和该SVG可能相关的组件和页面", svgfile); }); bus.on("图片文件删除", async function(imgfile) { // 图片文件删除时,处理等同图片文件修改 await bus.at("图片文件修改", imgfile); }); bus.on("重新编译和该SVG可能相关的组件和页面", async function(svgfile) { // 如果是图表目录中的文件,简化的,但凡用到图标的组件和页面,统统重新编译 let env = bus.at("编译环境"); let oSetFiles = new Set(), oSetPages = new Set(); let oFiles = bus.at("源文件对象清单"); let pages; for (let file in oFiles) { let context = bus.at("组件编译缓存", file); if (context) { // 可能是图标用途的svg文件 if (svgfile.startsWith(env.path.svgicons + "/")) { if (context.result.hasSvgIcon) { oSetFiles.add(file); // 直接使用图标的组件 pages = getRefPages(getTagOfSrcFile(file)); pages.forEach(f => oSetPages.add(f)); // 使用本组件的页面 continue; } } // 可能是图片用途的svg文件 let refimages = context.result.refimages || []; if (refimages.includes(svgfile)) { oSetFiles.add(file); // 使用该svg作为图片用途的组件 pages = getRefPages(getTagOfSrcFile(file)); pages.forEach(f => oSetPages.add(f)); // 使用本组件的页面 } } } oSetPages.forEach(file => { bus.at("组件编译缓存", file, false); // 清除编译缓存 removeHtmlCssJsFile(file); }); oSetFiles.forEach(file => { bus.at("组件编译缓存", file, false); // 清除编译缓存 }); await bus.at("全部编译"); }); })(); // 取标签名,无效者undefined function getTagOfSrcFile(file) { let name = File.name(file); if (/[^a-zA-Z0-9_-]/.test(name) || !/^[a-zA-Z]/.test(name)) { return; } return name.toLowerCase(); } // 文件改变时,先删除生成的最终html等文件 function removeHtmlCssJsFile(file) { let fileHtml = bus.at("页面目标HTML文件名", file); let fileCss = bus.at("页面目标CSS文件名", file); let fileJs = bus.at("页面目标JS文件名", file); File.remove(fileHtml); File.remove(fileCss); File.remove(fileJs); } // ------- a20m-src-file-manager end })(); /* ------- a22m-file-watcher ------- */ (() => { // ------- a22m-file-watcher start const bus = require("@gotoeasy/bus"); const File = require("@gotoeasy/file"); const hash = require("@gotoeasy/hash"); const chokidar = require("chokidar"); bus.on( "文件监视", (function(oSrcHash = {}, oOthHash = {}, hashBrowserslistrc, hashRposeconfigbtf) { return function() { let env = bus.at("编译环境"); if (!env.watch) { return; } bus.at("热刷新服务器"); // 监视文件变化 let browserslistrc = env.path.root + "/.browserslistrc"; let rposeconfigbtf = env.path.root + "/rpose.config.btf"; let ready, watcher = chokidar.watch(env.path.root, { ignored: [env.path.build + "/", env.path.root + "/node_modules/"] }); watcher .on("add", async file => { if (ready) { file = file.replace(/\\/g, "/"); if (file === browserslistrc) { // 配置文件 .browserslistrc 添加 hashBrowserslistrc = hash(File.read(browserslistrc)); console.info("add ......", file); bus.at("browserslist", true) > (await bus.at("重新编译全部页面")); // 重新查询目标浏览器,然后重新编译全部页面 } else if (file === rposeconfigbtf) { // 配置文件 rpose.config.btf 添加 hashRposeconfigbtf = hash(File.read(rposeconfigbtf)); console.info("add ......", file); await bus.at("全部重新编译"); } else if (file.startsWith(bus.at("编译环境").path.src + "/") && /\.rpose$/i.test(file)) { // 源文件添加 if (isValidRposeFile(file)) { console.info("add ......", file); let text = File.read(file); let hashcode = hash(text); let oFile = { file, text, hashcode }; oSrcHash[file] = oFile; await busAt("源文件添加", oFile); } else { console.info("ignored ...... add", file); } } else if (isValidSvgiconFile(file)) { // svg文件添加 console.info("add svg ......", file); let text = File.read(file); let hashcode = hash(text); oOthHash[file] = hashcode; await busAt("SVG文件添加", file); } else if (isValidImageFile(file)) { // 图片文件添加 console.info("add img ......", file); let hashcode = hash({ file }); oOthHash[file] = hashcode; await busAt("图片文件添加"); // 只是把没编译成功的都再编译一遍,不需要传文件名 } else if (isValidCssFile(file)) { // CSS文件添加(可能影响本地样式库) console.info("add css ......", file); let hashcode = hash({ file }); oOthHash[file] = hashcode; await busAt("CSS文件添加", file); } } }) .on("change", async file => { if (ready) { file = file.replace(/\\/g, "/"); if (file === browserslistrc) { // 配置文件 .browserslistrc 修改 let hashcode = hash(File.read(browserslistrc)); if (hashBrowserslistrc !== hashcode) { hashBrowserslistrc = hashcode; console.info("change ......", file); bus.at("browserslist", true) > (await bus.at("重新编译全部页面")); // 重新查询目标浏览器,然后重新编译全部页面 } } else if (file === rposeconfigbtf) { // 配置文件 rpose.config.btf 修改 let hashcode = hash(File.read(rposeconfigbtf)); if (hashRposeconfigbtf !== hashcode) { hashRposeconfigbtf = hashcode; console.info("change ......", file); await bus.at("全部重新编译"); } } else if (file.startsWith(bus.at("编译环境").path.src + "/") && /\.rpose$/i.test(file)) { // 源文件修改 if (isValidRposeFile(file)) { let text = File.read(file); let hashcode = hash(text); if (!oSrcHash[file] || oSrcHash[file].hashcode !== hashcode) { console.info("change ......", file); let oFile = { file, text, hashcode }; oSrcHash[file] = oFile; await busAt("源文件修改", oFile); } } else { console.info("ignored ...... change", file); } } else if (isValidSvgiconFile(file)) { // svg文件修改 let text = File.read(file); let hashcode = hash(text); if (oOthHash[file] !== hashcode) { console.info("change svg ......", file); oOthHash[file] = hashcode; await busAt("SVG文件修改", file); } } else if (isValidImageFile(file)) { // 图片文件修改 let hashcode = hash({ file }); if (oOthHash[file] !== hashcode) { console.info("change img ......", file); oOthHash[file] = hashcode; await busAt("图片文件修改", file); } } else if (isValidCssFile(file)) { // CSS文件修改(可能影响本地样式库) let hashcode = hash({ file }); if (oOthHash[file] !== hashcode) { console.info("change css ......", file); oOthHash[file] = hashcode; await busAt("CSS文件修改", file); } } } }) .on("unlink", async file => { if (ready) { file = file.replace(/\\/g, "/"); if (file === browserslistrc) { // 配置文件 .browserslistrc 删除 hashBrowserslistrc = null; console.info("del ......", file); bus.at("browserslist", true) > (await bus.at("重新编译全部页面")); // 重新查询目标浏览器,然后重新编译全部页面 } else if (file === rposeconfigbtf) { // 配置文件 rpose.config.btf 删除 hashRposeconfigbtf = null; console.info("del ......", file); await bus.at("全部重新编译"); } else if (file.startsWith(bus.at("编译环境").path.src + "/") && /\.rpose$/i.test(file)) { // 源文件删除 if (/\.rpose$/i.test(file)) { if (isValidRposeFile(file)) { console.info("del ......", file); delete oSrcHash[file]; await busAt("源文件删除", file); } else { console.info("ignored ...... del", file); } } } else if (isValidSvgiconFile(file)) { // svg文件删除 console.info("del svg ......", file); delete oOthHash[file]; await busAt("SVG文件删除", file); } else if (isValidImageFile(file)) { // 图片文件删除 console.info("del img ......", file); delete oOthHash[file]; await busAt("图片文件删除", file); } else if (isValidCssFile(file)) { // CSS文件删除 console.info("del css ......", file); delete oOthHash[file]; await busAt("CSS文件删除", file); } } }) .on("ready", () => { ready = true; }); }; })() ); async function busAt(name, ofile) { let stime = new Date().getTime(); await bus.at(name, ofile); let time = new Date().getTime() - stime; console.info("build " + time + "ms"); // 异步原因,不能用timeEnd计算用时 } function isValidRposeFile(file) { let name = File.name(file); if (/[^a-zA-Z0-9_-]/.test(name) || !/^[a-zA-Z]/.test(name)) { return false; } return true; } function isValidSvgiconFile(file) { let env = bus.at("编译环境"); let buildPath = env.path.build + "/"; let node_modulesPath = env.path.root + "/node_modules/"; let dotPath = env.path.root + "/."; return /\.svg$/i.test(file) && !file.startsWith(buildPath) && !file.startsWith(node_modulesPath) && !file.startsWith(dotPath); } function isValidImageFile(file) { let env = bus.at("编译环境"); let buildPath = env.path.build + "/"; let node_modulesPath = env.path.root + "/node_modules/"; let dotPath = env.path.root + "/."; return ( /\.(jpg|png|gif|bmp|jpeg)$/i.test(file) && !file.startsWith(buildPath) && !file.startsWith(node_modulesPath) && !file.startsWith(dotPath) ); } function isValidCssFile(file) { let env = bus.at("编译环境"); let buildPath = env.path.build + "/"; let node_modulesPath = env.path.root + "/node_modules/"; let dotPath = env.path.root + "/."; return /\.css$/i.test(file) && !file.startsWith(buildPath) && !file.startsWith(node_modulesPath) && !file.startsWith(dotPath); } // ------- a22m-file-watcher end })(); /* ------- a30m-compile-all-page ------- */ (() => { // ------- a30m-compile-all-page start const bus = require("@gotoeasy/bus"); const Err = require("@gotoeasy/err"); bus.on( "全部编译", (function() { return async function() { let oFiles = bus.at("源文件对象清单"); let env = bus.at("编译环境"); bus.at("项目配置处理", env.path.root + "rpose.config.btf"); let errSet = new Set(); let stime, time; for (let file in oFiles) { try { stime = new Date().getTime(); let context = bus.at("编译组件", oFiles[file]); time = new Date().getTime() - stime; if (time > 100) { console.info("[compile] " + time + "ms -", file.replace(env.path.src + "/", "")); } await context.result.browserifyJs; } catch (e) { bus.at("组件编译缓存", file, false); // 出错时确保删除缓存(可能组件编译过程成功,页面编译过程失败) errSet.add(Err.cat(e).toString()); } } // 输出汇总的错误信息 errSet.size && console.error([...errSet].join("\n\n")); }; })() ); // ------- a30m-compile-all-page end })(); /* ------- a32m-compile-component ------- */ (() => { // ------- a32m-compile-component start const bus = require("@gotoeasy/bus"); const File = require("@gotoeasy/file"); const hash = require("@gotoeasy/hash"); const Err = require("@gotoeasy/err"); const postobject = require("@gotoeasy/postobject"); bus.on( "编译组件", (function() { return function(infile) { let oFile; if (infile.file) { oFile = infile; // 项目源文件对象 } else { let file, text, hashcode; file = bus.at("标签源文件", infile); // 标签则转为源文件,源文件时还是源文件 if (!File.existsFile(file)) { throw new Err(`file not found: ${file} (${infile})`); } text = File.read(file); hashcode = hash(text); oFile = { file, text, hashcode }; } let env = bus.at("编译环境"); let context = bus.at("组件编译缓存", oFile.file); if (context && context.input.hashcode !== oFile.hashcode) { context = bus.at("组件编译缓存", oFile.file, false); // 删除该文件相应缓存 } if (!context) { let plugins = bus.on("编译插件"); return postobject(plugins).process({ ...oFile }, { log: env.debug }); } return context; }; })() ); // ------- a32m-compile-component end })(); /* ------- a34m-rebuild-all-page ------- */ (() => { // ------- a34m-rebuild-all-page start const bus = require("@gotoeasy/bus"); bus.on( "重新编译全部页面", (function() { return async function() { let time, time1, stime = new Date().getTime(); let env = bus.at("编译环境"); let oFiles = bus.at("源文件对象清单"); for (let file in oFiles) { let context = bus.at("组件编译缓存", file); if (context && context.result && context.result.isPage) { bus.at("组件编译缓存", file, false); // 如果是页面则清除该页面的编译缓存 } } for (let key in oFiles) { time1 = new Date().getTime(); let context = bus.at("编译组件", oFiles[key]); time = new Date().getTime() - time1; if (time > 100) { console.info("[compile] " + time + "ms -", key.replace(env.path.src + "/", "")); } await context.result.browserifyJs; } time = new Date().getTime() - stime; console.info("[build] " + time + "ms"); }; })() ); // ------- a34m-rebuild-all-page end })(); /* ------- a36m-rebuild-all ------- */ (() => { // ------- a36m-rebuild-all start const bus = require("@gotoeasy/bus"); bus.on( "全部重新编译", (function() { return async function() { let time, time1, stime = new Date().getTime(); let env = bus.at("编译环境"); bus.at("清除全部编译缓存"); // 清除全部编译缓存 env = bus.at("编译环境", env, true); // 重新设定编译环境 bus.at("项目配置处理", env.path.root + "rpose.config.btf", true); // 重新解析项目配置处理 let oFiles = bus.at("源文件对象清单", true); // 源文件清单重新设定 for (let key in oFiles) { time1 = new Date().getTime(); let context = bus.at("编译组件", oFiles[key]); time = new Date().getTime() - time1; if (time > 100) { console.info("[compile] " + time + "ms -", key.replace(env.path.src + "/", "")); } await context.result.browserifyJs; } time = new Date().getTime() - stime; console.info("[build] " + time + "ms"); }; })() ); // ------- a36m-rebuild-all end })(); /* ------- a82m-dev-server-reload ------- */ (() => { // ------- a82m-dev-server-reload start const bus = require("@gotoeasy/bus"); const File = require("@gotoeasy/file"); const fs = require("fs"); const url = require("url"); const path = require("path"); const http = require("http"); const opn = require("opn"); const REBUILDING = "rebuilding..."; // ----------------------------------------------------------------------- // 某日,browser-sync启动及刷新竟然要数分钟 // 原因或是众所周知的网络问题,或是不得而知的版本依赖问题 // 总之,无心花精力细查解决它,不得已先写个简陋实现,毕竟需求非常简单 // // 此服务器只在watch模式下才开启 // // 1)以dist目录为根目录建立服务器,处理响应文件请求 // 2)服务器添加文件是否变更的查询接口 // 3)html文件请求做特殊处理,注入客户端脚本,脚本中包含页面id和哈希码 // 4)客户端脚本每隔1秒请求服务器查询当前页是否有变更,有改变则刷新 // 5)服务器启动后,间隔一秒看有无请求,没有则打开浏览器访问,避免开一堆窗口 // ----------------------------------------------------------------------- bus.on( "热刷新服务器", (function(hasQuery) { return function() { let env = bus.at("编译环境"); if (!env.watch) return; let port = env.port ? (!/^\d+$/.test(env.port) ? randomNum(3000, 9999) : env.port) : 3700; createHttpServer(env.path.build_dist, port); }; // 生成从minNum到maxNum的随机数 function randomNum(minNum = 3000, maxNum = 9999) { return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10); } // 查询 function queryHandle(req, res, oUrl) { hasQuery = true; let env = bus.at("编译环境"); let htmlpage = oUrl.query.split("&")[0].split("=")[1]; let srcFile = File.resolve(env.path.src, htmlpage.substring(0, htmlpage.length - 5) + ".rpose"); let hashcode = ""; if (File.existsFile(srcFile)) { let context = bus.at("组件编译缓存", srcFile); if (context) { hashcode = context.result.hashcode || REBUILDING; // 如果已经编译成功就会有值,否则可能是编译失败,或者是正编译中 } else { hashcode = REBUILDING; // 返回'rebuilding...'状态,前端自行判断数次后按错误处理 } } else { hashcode = "404"; // 源码文件不存在,显示404 } res.writeHead(200); res.end(hashcode); // 未成功编译时,返回空白串 } // html注入脚本 function htmlHandle(req, res, oUrl, htmlfile) { let env = bus.at("编译环境"); let srcFile = File.resolve(env.path.src, htmlfile.substring(env.path.build_dist.length + 1, htmlfile.length - 5) + ".rpose"); let htmlpage = htmlfile.substring(env.path.build_dist.length + 1); let html, hashcode = ""; if (File.existsFile(srcFile)) { let context = bus.at("组件编译缓存", srcFile); if (context) { hashcode = context.result.hashcode || ""; // 如果已经编译成功就会有值,否则是正编译中 } if (!hashcode) { // 当前不是编译成功状态,都显示500编译失败 html = ` <!doctype html> <html lang="en"> <head> <title>500</title> </head> <body> <pre style="background:#333;color:#ddd;padding:20px;font-size:24px;">500 Compile Failed</pre> </body> </html> `; hashcode = "500"; } else { html = File.read(htmlfile); } } else { // 源码文件不存在则404 html = ` <!doctype html> <html lang="en"> <head> <title>404</title> </head> <body> <pre style="background:#943E03;color:#fff;padding:20px;font-size:24px;">404 Not Found</pre> </body> </html> `; hashcode = "404"; } let script = ` <script> var _times_ = 0; function refresh() { let url = '/query?page=${htmlpage}&t=' + new Date().getTime(); ajaxAsync(url, function(rs){ if ( rs !== '${hashcode}' ) { if ( rs === '${REBUILDING}' ) { _times_++; _times_ >= 5 ? location.reload() : setTimeout(refresh, 1000); }else{ location.reload(); } }else{ _times_ = 0; setTimeout(refresh, 1000); } }, function(err){ _times_ = 0; setTimeout(refresh, 1000); }); } function ajaxAsync(url, fnCallback, fnError) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function (xxx, eee) { if (xhr.readyState === 4 && xhr.status === 200) { fnCallback(xhr.responseText); } }; xhr.onerror = fnError; xhr.open("GET", url, true); xhr.send(); } setTimeout(refresh, 3000); </script>`; html = html.replace(/<head>/i, "<head>" + script); // 极简实现,注入脚本,定时轮询服务端 res.writeHead(200, { "Content-Type": "text/html;charset=UFT8" }); // 即使404请求,也是被当正常注入返回 res.end(html); } // 创建服务器 function createHttpServer(www, port) { let server = http.createServer(function(req, res) { let oUrl = url.parse(req.url); if (/^\/query$/i.test(oUrl.pathname)) { queryHandle(req, res, oUrl); // 查询页面哈希码 return; } let reqfile = path.join(www, oUrl.pathname).replace(/\\/g, "/"); if (File.existsDir(reqfile)) { reqfile = File.resolve(reqfile, "index.html"); // 默认访问目录下的index.html } if (/\.html$/i.test(reqfile)) { htmlHandle(req, res, oUrl, reqfile); // 拦截注入脚本后返回 return; } if (File.existsFile(reqfile)) { if (/\.css$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "text/css;charset=UFT8" }); // 避免浏览器控制台警告 } else if (/\.js$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/javascript;charset=UFT8" }); } else if (/\.json$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/json;charset=UFT8" }); } else if (/\.jsonp$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/jsonp;charset=UFT8" }); } else if (/\.svg$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/svg+xml;charset=UFT8" }); } else if (/\.svgz$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/svg+xml-compressed;charset=UFT8" }); } else if (/\.jpg$/i.test(reqfile) || /\.jpeg$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/jpeg" }); } else if (/\.gif$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/gif" }); } else if (/\.bmp$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/bmp" }); } else if (/\.png$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "image/png" }); } else if (/\.pdf$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/pdf" }); } else if (/\.xml$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "text/xml;charset=UFT8" }); } else if (/\.dtd$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "text/xml-dtd;charset=UFT8" }); } else if (/\.zip$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/zip" }); } else if (/\.gzip$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/gzip" }); } else if (/\.xls$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/vnd.ms-excel" }); } else if (/\.xlsx$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); } else if (/\.doc$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/msword" }); } else if (/\.docx$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); } else if (/\.ppt$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/vnd.ms-powerpoint" }); } else if (/\.pptx$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/vnd.openxmlformats-officedocument.presentationml.presentation" }); } else if (/\.dll$/i.test(reqfile) || /\.exe$/i.test(reqfile)) { res.writeHead(200, { "Content-Type": "application/x-msdownload" }); } else { res.writeHead(200); } fs.createReadStream(reqfile).pipe(res); // 非html文件,直接输出文件流 } else { if (/favicon\.ico$/i.test(reqfile)) { res.writeHead(200); // 避免浏览器控制台警告 res.end(null); } else { res.writeHead(404); res.end("404 Not Found"); // 文件找不到 } } }); server.listen(port); let hostUrl = "http://localhost:" + port; console.log("-------------------------------------------"); console.log(` server ready ...... ${hostUrl}`); console.log("-------------------------------------------"); setTimeout(() => { !hasQuery && opn(hostUrl); // 等1秒钟还是没有请求的话,新开浏览器 }, 1000); } })() ); // ------- a82m-dev-server-reload end })(); /* ------- b00p-log ------- */ (() => { // ------- b00p-log start const bus = require("@gotoeasy/bus"); const postobject = require("@gotoeasy/postobject"); bus.on( "编译插件", (function() { return postobject.plugin("b00p-log", function(/* root, context */) { // console.info('[b00p-log]', JSON.stringify(root,null,4)); }); })() ); // ------- b00p-log end })(); /* ------- b01p-init-context ------- */ (() => { // ------- b01p-init-context start const bus = require("@gotoeasy/bus"); const postobject = require("@gotoeasy/postobject"); bus.on( "编译插件", (function() { // 解析rpose源文件,替换树节点(单个源文件的单一节点),输入{file,text} return postobject.plugin("b01p-init-context", function(root, context) { context.input = {}; // 存放原始输入(file、text) context.doc = {}; // 存放源文件的中间解析结果 context.style = {}; // 存放样式的中间编译结果 context.script = {}; // 存放脚本的中间编译结果,script的Method属性存放方法名为键的对象 context.keyCounter = 1; // 视图解析时标识key用的计数器(同一组子节点单位内递增) context.result = {}; // 存放编译结果 // 保存原始输入(file、text) root.walk( (node, object) => { context.input.file = object.file; context.input.text = object.text; context.input.hashcode = object.hashcode; }, { readonly: true } ); //console.info('compile ..........', context.input.file); }); })() ); // ------- b01p-init-context end })(); /* ------- b10m-file-parser-config-btf ------- */ (() => { // ------- b10m-file-parser-config-btf start const bus = require("@gotoeasy/bus"); // --------------------------------------------------- // 项目配置文件解析 // --------------------------------------------------- bus.on( "项目配置文件解析", (function() { return function(text) { let lines = text.split("\n"); // 行内容包含换行符 let lineCounts = []; // 行长度包含换行符 for (let i = 0, max = lines.length; i < max; i++) { lines[i] += "\n"; // 行内容包含换行符 lineCounts[i] = lines[i].length; // 行长度包含换行符 } let nodes = []; parse(nodes, lines, lineCounts); nodes.forEach(block => { let type = "ProjectBtfBlockText"; if (block.buf.length) { // 值 let lastLine = block.buf.pop(); let tmp = lastLine.replace(/\r?\n$/, ""); // 删除最后一行回车换行符 tmp && block.buf.push(tmp); // 删除最后一行回车换行符后仍有内容则加回去 let value = block.buf.join(""); // 无损拼接 // 开始位置 let start = sumLineCount(lineCounts, block.startLine); // 块内容开始位置(即块名行为止合计长度) // 结束位置 let end = sumLineCount(lineCounts, block.startLine + block.buf.length - 1) + tmp.length; block.text = { type, val