@aniyajs/rotor
Version:
基于webpack5开发的一款专注于打包、运行的工具
438 lines (383 loc) • 13.2 kB
JavaScript
;
// 确定当前构建环境
process.env.NODE_ENV = "development";
// 抛出所有未被捕获的错误
process.on("unhandledRejection", (error) => {
throw error;
});
const paths = require("../utils/paths");
const information = require("../utils/information");
const {
checkFilesExists,
depthDependFilePaths,
itemOrEvent,
aRecursive,
clearConfigFilesCache,
depthCompareObject,
clearConsole,
funcOrStr,
} = require("../utils/common");
const PluginGenerator = require("../utils/pluginGenerator");
// 检查所需的文件是否存在
checkFilesExists([paths.appHtml, paths.appConfigJs]);
information.loading("初始化开始");
// 依赖
const chokidar = require("chokidar");
const watchjs = require("watchjs");
const fs = require("fs-extra");
const chalk = require("chalk");
const path = require("path");
const execa = require("execa");
const escape = require("escape-string-regexp");
const {
initRequireFileHandle,
initTempHandle,
} = require("../utils/initialize");
// 当前是否处于终端
const isInteractive = process.stdout.isTTY;
// 可忽略的依赖后缀
const dependentSuffixs = [".ts", ".tsx", ".js", ".jsx", ".json"];
const pluginMainSuffixs = [".ts", ".js"];
// 忽略所有隐藏文件
const hiddenFileRule = /(^|[/\\])\../;
// 匹配mock文件
const mockFileRule = new RegExp(`^${escape(paths.appMockPath)}/`);
/**
* 初始化 meta.json,
* `src/.aniya/meta.json`,用于记录比如当前config文件及深度依赖路径等其他信息
*/
initRequireFileHandle()
.then((isInitRequire) => {
if (!isInitRequire) {
return new Error(null);
}
// 获取配置文件路径及深度依赖,用于监听自动重启项目
return depthDependFilePaths(
paths.appConfigJs,
dependentSuffixs,
hiddenFileRule,
);
})
.then((dependFiles) => {
let FSWatcher;
let webpackProcess;
let watchObj = {
changing: 1,
start: false,
};
let timer;
// 项目初始化
let initApp = true;
// 匹配环境变量文件
const envFileReg = /^config\.[a-zA-Z0-9]+\.(ts|js)$/;
// 正在更改的文件
let changingFiles = [];
FSWatcher = chokidar.watch(
[paths.appConfigPath, paths.appMockPath, ...dependFiles],
{
ignored: hiddenFileRule, // 忽略所有隐藏文件和文件夹,不加入监听
persistent: true,
},
);
FSWatcher.on("all", async (event, listenPath) => {
if (event === "addDir" || event === "unlinkDir") {
return null;
}
console.log(
chalk.green(`[${event}] ${path.relative(paths.appPath, listenPath)}`),
);
if (
changingFiles.findIndex(
(changingFile) => changingFile === listenPath,
) === -1
) {
changingFiles.push(listenPath);
}
if (/^unlink/.test(event)) {
changingFiles = changingFiles.filter(
(changingFile) => changingFile !== listenPath,
);
}
watchObj.changing++;
});
FSWatcher.on("ready", () => {
const oldDependFileInfos = FSWatcher.getWatched();
let cacheListenPaths = [];
for (const oldDependFileKey in oldDependFileInfos) {
oldDependFileInfos[oldDependFileKey].forEach((oldDependFile) => {
cacheListenPaths.push(path.resolve(oldDependFileKey, oldDependFile));
});
}
fs.writeJsonSync(
paths.appTempMetaJs,
{
cacheListenPaths,
},
{ spaces: 2 },
);
initApp = false;
});
FSWatcher.on("unlink", (filePath) => {
const data = fs.readJsonSync(paths.appTempMetaJs);
const cacheListenPaths = data.cacheListenPaths;
const unlinkIndex = cacheListenPaths.indexOf(filePath);
cacheListenPaths.splice(unlinkIndex, 1);
fs.writeJsonSync(
paths.appTempMetaJs,
{ ...data, cacheListenPaths },
{ spaces: 2 },
);
});
FSWatcher.on("add", (filePath) => {
if (!initApp) {
const data = fs.readJsonSync(paths.appTempMetaJs);
fs.writeJsonSync(
paths.appTempMetaJs,
{
...data,
cacheListenPaths: [...data.cacheListenPaths, filePath].reduce(
(pre, cur) => (pre.includes(cur) ? [...pre] : [...pre, cur]),
[],
),
},
{ spaces: 2 },
);
}
});
FSWatcher.on("change", async (filePath) => {
// 跳过多环境配置文件更改。
// 跳过mock文件更改
// 多环境配置文件不允许外部导入
if (
(path.dirname(filePath) === paths.appConfigPath &&
envFileReg.test(path.basename(filePath))) ||
path.dirname(filePath) === paths.appMockPath
) {
return;
}
const newData = await depthDependFilePaths(
paths.appConfigJs,
dependentSuffixs,
hiddenFileRule,
);
if (!newData) {
return;
}
const { cacheListenPaths } = fs.readJsonSync(paths.appTempMetaJs);
const envConfigFiles = fs
.readdirSync(paths.appConfigPath)
.reduce((pre, cur) => {
pre.push(path.resolve(paths.appConfigPath, cur));
return pre;
}, []);
const { removeData, addData } = itemOrEvent(
cacheListenPaths,
[paths.appConfigPath, ...envConfigFiles, ...newData],
paths.appMockPath,
);
if (removeData.length > 0 || addData.length > 0) {
const data = fs.readJsonSync(paths.appTempMetaJs);
const cacheListenPaths = data.cacheListenPaths;
if (removeData.length > 0) {
removeData.forEach((removeFile) => {
const removeIndex = cacheListenPaths.indexOf(removeFile);
cacheListenPaths.splice(removeIndex, 1);
console.log(
chalk.green(
`[unlink] ${path.relative(paths.appPath, removeFile)}`,
),
);
changingFiles = changingFiles.filter(
(changingFile) => changingFile !== removeFile,
);
});
FSWatcher.unwatch(removeData);
}
if (addData.length > 0) {
addData.forEach((addFile) => {
cacheListenPaths.push(addFile);
});
FSWatcher.add(addData);
}
fs.writeJsonSync(
paths.appTempMetaJs,
{ ...data, cacheListenPaths },
{ spaces: 2 },
);
}
});
watchjs.watch(watchObj, ["changing"], () => {
clearTimeout(timer);
timer = setTimeout(() => {
watchObj.start = true;
}, 1000);
});
watchjs.watch(watchObj, ["start"], async () => {
if (watchObj.start) {
if (isInteractive) {
clearConsole();
}
console.log();
const data = fs.readJsonSync(paths.appTempMetaJs);
const cacheListenPaths = data.cacheListenPaths;
// config是否更改
let changeConfig = !!changingFiles.filter(
(changingFile) => !mockFileRule.test(changingFile),
).length;
// mock是否更改
let changeMock =
changingFiles.findIndex((changingFile) =>
mockFileRule.test(changingFile),
) > -1;
// 初始化mock、config配置缓存文件,并拿到config缓存文件的文件及config目录
const initSuccessPaths = await initTempHandle(
envFileReg,
cacheListenPaths,
changeMock,
changeConfig,
);
changingFiles = [];
if (initSuccessPaths) {
// 在 `meta.json` 中缓存部分路径数据
const metaJson = fs.readJSONSync(paths.appTempMetaJs);
const configCacheFiles = await aRecursive(paths.appConfigTempPath);
// 清除缓存避免出现修改前的数据
const isDelCache = await clearConfigFilesCache(configCacheFiles);
if (isDelCache) {
const config = require(
initSuccessPaths.appConfigTempIndexJs,
).default;
let pluginInfos = [];
if (config.aniyaPlugins && config.aniyaPlugins.length) {
// 检查插件文件是否存在
checkFilesExists(config.aniyaPlugins, paths.appNodeModules);
config.aniyaPlugins.forEach((pluginRelativePath) => {
const pluginResolvePath = path.join(
paths.appNodeModules,
pluginRelativePath,
"lib",
);
const extension = pluginMainSuffixs.find((extension) =>
fs.existsSync(`${pluginResolvePath}${extension}`),
);
if (extension) {
const pluginGenertor = new PluginGenerator({
pluginIndex: `${pluginResolvePath}${extension}`,
config,
});
pluginGenertor.initialize();
// 确保commonjs可以引入module
// eslint-disable-next-line no-global-assign
require = require("esm")(module);
pluginGenertor.use(
require(`${pluginResolvePath}${extension}`).default,
);
pluginInfos.push(pluginGenertor.describeParams);
pluginGenertor.ending();
}
});
}
const strConfig = funcOrStr({
...pluginInfos.reduce((pre, cur) => {
if (cur.config && cur.config.default !== undefined) {
pre[cur.key] = cur.config.default;
}
return pre;
}, {}),
...config,
});
// 判断config是否更改
const isStart =
metaJson.lastConfig == null
? true
: depthCompareObject(metaJson.lastConfig, strConfig, [
"publicPath",
"proxy",
"devtool",
"dynamicImport",
"alias",
"define",
"devServer",
"open",
"outputPath",
"routes",
"chainWebpack",
"aniyaPlugins",
"disableESLintPlugin",
"fastRefresh",
"useTailwindcss",
// plugin 字段或许不要涉及到项目重启
// 最好是刷新一下webpack即可
/* *****待优化***** */
...pluginInfos.map((pluginInfo) => pluginInfo.key),
]);
fs.writeJsonSync(
paths.appTempMetaJs,
{
...metaJson,
paths: initSuccessPaths,
lastConfig: strConfig,
},
{ spaces: 2 },
);
// // 项目启动
// 首次启动、config指定配置变更、mock更改
// 我们只有在确定项目需要启动或重启才检测插件
if (isStart || changeMock) {
webpackProcess && webpackProcess.kill && webpackProcess.kill();
webpackProcess = execa(
"node",
[path.resolve(__dirname, "../webpack/webpackStartServer.js")],
{
cwd: process.cwd(),
stdio: "inherit",
},
);
}
}
}
}
watchObj.start = false;
});
// 所需的文件夹必须存在。
FSWatcher.on("unlinkDir", (folderPath) => {
if (path.resolve(paths.appConfigPath) === path.resolve(folderPath)) {
console.log();
console.log(chalk.red("找不到所需的路径。"));
console.log(chalk.red("路径名称:") + chalk.cyan(folderPath));
FSWatcher && FSWatcher.close();
webpackProcess && webpackProcess.kill && webpackProcess.kill();
process.exit(1);
}
});
// 所需的文件必须存在。
FSWatcher.on("unlink", (filePath) => {
if (path.resolve(paths.appConfigJs) === path.resolve(filePath)) {
console.log();
console.log(chalk.red("找不到所需的路径。"));
console.log(chalk.red("路径名称:") + chalk.cyan(filePath));
FSWatcher && FSWatcher.close();
webpackProcess && webpackProcess.kill && webpackProcess.kill();
process.exit(1);
}
});
// 监听报错退出程序
FSWatcher.on("error", (error) => {
console.log(chalk.red("Error: ") + error.message || error);
FSWatcher && FSWatcher.close();
webpackProcess && webpackProcess.kill && webpackProcess.kill();
process.exit(1);
});
// CTRL + C 退出程序
["SIGINT", "SIGTERM"].forEach(function (sig) {
process.on(sig, function () {
webpackProcess && webpackProcess.kill && webpackProcess.kill();
FSWatcher && FSWatcher.close();
process.exit();
});
});
})
.catch((error) => {
console.log(error);
process.exit(1);
});