UNPKG

@aniyajs/rotor

Version:

基于webpack5开发的一款专注于打包、运行的工具

438 lines (383 loc) 13.2 kB
"use strict"; // 确定当前构建环境 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); });