@rpose/compiler
Version:
rpose compiler
1,241 lines (1,083 loc) • 489 kB
JavaScript
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