UNPKG

takin

Version:

Front end engineering base toolchain and scaffold

681 lines 24.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Config = exports.PluginTypes = void 0; const fs = __importStar(require("fs-extra")); const module_1 = require("module"); const path = __importStar(require("path")); const constants_1 = require("./constants"); const environment_1 = require("./environment"); const errors_1 = require("./errors"); const logger_1 = require("./logger"); const utils_1 = require("./utils"); /** * 插件类型 */ var PluginTypes; (function (PluginTypes) { /** * 通过 npm 自动载入的 */ PluginTypes["auto"] = "auto"; /** * 通过 use 方法传入 */ PluginTypes["use"] = "use"; /** * 通过 Runner.run 方法直接指定 */ PluginTypes["runner"] = "runner"; /** * 通过 userConfig 传入 */ PluginTypes["config"] = "config"; /** * 通过 命令行选项 传入 */ PluginTypes["cli"] = "cli"; })(PluginTypes = exports.PluginTypes || (exports.PluginTypes = {})); /** * 用户配置支持 * 1. 支持自定义 配置 配置文件的名称 * 2. 支持 js、ts、mjs、json 四种配置文件方式 * 3. 不同的文件使用不同的方式载入 * 4. 支持用户自定义配置文件名称, 并指定配置文件的支持类型 * 5. 支持通过插件注册配置字段和校验schema * 6. 支持 开启配置数组, 并通过用户指定的 字段来区分 * 7. 支持配置合并 */ class Config { /** * 初始化时使用的配置文件名称 * @param name - 配置文件名称 */ constructor(name) { /** * option 名称 */ this.optionName = constants_1.DEFAULT_CONFIG_OPTION_NAME; /** * option 别名 */ this.optionNameAlias = constants_1.DEFAULT_CONFIG_OPTION_NAME_ALIAS; /** * 是否开启多配置支持 * 默认为 `false` * 多配置支持示例: `[{ name: 'config-one' }, { name: 'config-two' }]` */ this.multipleConfigEnabled = false; /** * 是否开启通过 package.json 读取配置 * 如: `{ takin: {} }` */ this.packageJsonConfigEnabled = false; /** * 是否开启了 env 支持 */ this.env = new environment_1.Environment(); /** * 多配置名称字段 * 默认为 `name` */ this.multipleConfigNameField = constants_1.DEFAULT_MULTIPLE_CONFIG_FIELD; /** * 允许自定义支持的配置文件类型 */ this.supportConfigExtensions = Object.values(constants_1.SupportConfigExtensions); /** * 支持设置用户配置文件名称, 不包含后缀设置 * 默认为 `takin.config` */ this.supportConfigNames = []; /** * 当前项目的 package.json 路径 */ this.pkgPath = ''; /** * 已使用的插件 */ this.usedPlugins = new Map(); /** * 插件动态载入规则配置, 支持一组正则 */ this.pluginAutoLoadPatterns = []; /** * 项目执行文件根目录, 默认为 `process.cwd()` */ this.cwd = constants_1.DEFAULT_ROOT; this.setName(name); this.setSupportConfigFileNames([`${name}.config`]); } /** * 设置全局名称 * @param name - 命令行全局名称 * @return this */ setName(name) { if (!name) throw new errors_1.ConfigError('配置缺少名称'); this._name = name; // 创建 config 专属 logger this.logger = (0, logger_1.createLogger)('info', { prefix: `[${name}]`, debugPrefix: `${name}:config` }); return this; } /** * 获取 cli 全局名称 */ get name() { return this._name; } /** * 设置 option 名称和别名, 用于命令行 * @param name - 名称 * @param alias - 别名 * @return this */ setOptionName(name, alias) { this.optionName = name; this.optionNameAlias = alias; return this; } /** * 配置支持的配置文件名称 * @param names - 文件名称, 如 `['takin.config']` * @return this */ setSupportConfigFileNames(names) { if (names === null || names === void 0 ? void 0 : names.length) this.supportConfigNames = names; return this; } /** * 配置支持的配置文件名称 * @param extensions - 后缀名, 如 `['.js']` * @return this */ setSupportConfigFileExtensions(extensions) { if (extensions === null || extensions === void 0 ? void 0 : extensions.length) this.supportConfigExtensions = extensions; return this; } /** * 开启通过 package.json 读取用户配置 * @return this */ enablePackageJsonConfig() { this.packageJsonConfigEnabled = true; return this; } /** * 关闭通过 package.json 读取用户配置 * @return this */ disablePackageJsonConfig() { this.packageJsonConfigEnabled = false; return this; } /** * 开启多配置支持 * @param opts - 多配置支持选项 * @return this */ enableMultipleConfig(opts) { this.multipleConfigEnabled = true; this.multipleConfigNameField = (opts === null || opts === void 0 ? void 0 : opts.by) || constants_1.DEFAULT_MULTIPLE_CONFIG_FIELD; return this; } /** * 关闭多配置支持 * @return this */ disableMultipleConfig() { this.multipleConfigEnabled = false; this.multipleConfigNameField = undefined; return this; } /** * 设置插件自动加载规则 * @param patterns - 规则 * @return this */ setPluginAutoLoadPatterns(patterns) { this.pluginAutoLoadPatterns = patterns; return this; } /** * 从 package.json 的 dependencies 和 devDependencies 中基于规则自动载入插件- */ async autoLoadPlugins() { var _a, _b, _c; // 如果为设置规则, 代表不开启这项功能 if (!((_a = this.pluginAutoLoadPatterns) === null || _a === void 0 ? void 0 : _a.length)) return; // 未发现 pkg 则自动载入 if (!this.pkg) await this.loadPackageJson(); const dependencies = { ...(((_b = this.pkg) === null || _b === void 0 ? void 0 : _b.dependencies) || {}), ...(((_c = this.pkg) === null || _c === void 0 ? void 0 : _c.devDependencies) || {}) }; const plugins = new Set(); for (const packageName in dependencies) { for (const pattern of this.pluginAutoLoadPatterns) { if (pattern.test(packageName) && !plugins.has(packageName)) { plugins.add(packageName); break; } } } this.usePlugins(Array.from(plugins), PluginTypes.auto); } /** * @private * 设置内部插件 */ usePlugins(plugins, pluginType) { this.resolveUserPlugins(plugins, true, pluginType).forEach((pluginInfo, pluginName) => { this.warnDuplicatePlugin(this.usedPlugins, pluginName, pluginInfo.version); this.usedPlugins.set(pluginName, pluginInfo); }); } /** * @private * 警告插件重复 * @returns true 为 重复, false 为不重复 */ warnDuplicatePlugin(plugins, pluginName, version) { if (plugins.has(pluginName)) { this.logger.warnOnce(`插件: ${pluginName} 被重复载入, 将使用最后一次载入的版本 ${version}`); return true; } else { return false; } } /** * @private * 加载用户 插件列表, 并执行 * 支持的格式 * ``` * plugins: [ * 'plugin_name', * ['plugin_name', { somePluginOption: 1 }], * new Plugin({}), * ] * ``` * @param plugins - 插件列表 * @param invokeOnUse - 是否触发插件 onUse 回调 * @param pluginType - 插件调用类型 */ resolveUserPlugins(plugins, invokeOnUse = false, pluginType) { const resolvedPlugins = new Map(); // 定制 require 函数 const requireFn = this.cwd ? (0, module_1.createRequire)(path.resolve(this.cwd, 'noop.js')) : require; try { (0, utils_1.asArray)(plugins).forEach((p) => { let _p; let _name; let _version = '*'; if (!p) throw new errors_1.PluginError(`${p} 不是一个有效的插件.`); if (Array.isArray(p) || typeof p === 'string') { const [pluginName, pluginOption] = (0, utils_1.asArray)(p); _name = pluginName; const P = (0, utils_1.interopRequireDefault)(requireFn(pluginName)) .default; if (!P) { throw new errors_1.PluginError(`插件: ${pluginName} 未找到, 或没有被正确的 export .`); } _p = new P(pluginOption); if (_p === null || _p === void 0 ? void 0 : _p.version) { _version = _p.version; } // 尝试载入 npm 插件版本信息 else { try { const pluginPkg = requireFn(`${pluginName}/package.json`); _version = (pluginPkg === null || pluginPkg === void 0 ? void 0 : pluginPkg.version) || _version; } catch (error) { this.logger.debug(`未找到插件 ${pluginName} 版本信息`); } } } else { _p = p; if (_p.name) { _name = _p.name; } else { _name = _p.constructor.name; } _version = (_p === null || _p === void 0 ? void 0 : _p.version) || _version; } // 检查插件是否包含 apply 方法 if (typeof (_p === null || _p === void 0 ? void 0 : _p.apply) !== 'function') { throw new errors_1.PluginError(`插件: ${_name} 缺少 apply 方法, 请检查.`); } // 自动载入的插件打印信息 if (pluginType === PluginTypes.auto) { this.logger.info(`插件: ${_name}@${_version} 已自动载入`); } // 触发插件 onUse 方法 if (invokeOnUse && typeof (_p === null || _p === void 0 ? void 0 : _p.onUse) === 'function') { _p.onUse(this); this.logger.debug(`插件: ${_name} 已触发 onUse 方法`); } this.warnDuplicatePlugin(resolvedPlugins, _name, _version); resolvedPlugins.set(_name, { plugin: _p, pluginType, version: _version }); }); } catch (err) { const error = err; error.message = (error.message || '插件加载失败').replace(/^Cannot find module '(\S+)'/, "找不到名称为 '$1' 的插件"); this.logger.error(error.message, { error, color: true }); throw err; } return resolvedPlugins; } /** * 对用户使用的插件进行排序 * plugin.enforce 属性只处理 pre 和 post 两个值, 其他值会忽略 * 原则上除了将部分插件提前或置后之外, 不改变用户自行设定的插件顺序 * plugin.enforce = 'pre' 会排在所有插件最前面 * plugin.enforce = 'post' 会排在所有插件最后面 * @param plugins - 插件列表 */ sortUserPlugins(plugins) { const prePlugins = []; const postPlugins = []; const normalPlugins = []; if (plugins) { plugins.flat().forEach((p) => { if (p.enforce === 'pre') prePlugins.push(p); else if (p.enforce === 'post') postPlugins.push(p); else normalPlugins.push(p); }); } return [prePlugins, normalPlugins, postPlugins]; } /** * 解析配置过滤条件 * @param filters - 过滤条件 * @returns 解析后的过滤条件 */ parseFilter(filters) { if (!filters) return; if (!Array.isArray(filters)) filters = String(filters).split(','); const parsedFilters = new Set(); filters.forEach(function (f) { if (f == null) return; parsedFilters.add(String(f).valueOf().trim()); }); return parsedFilters; } /** * 过滤用户配置 * @param filters - 过滤条件 * @param userConfigs - 需要过滤的用户配置 * @returns 过滤后的用户配置 */ filterBy(filters, userConfigs) { // 优先使用传入的 userConfigs, 如无 则使用当前 config 中载入的 const configs = (0, utils_1.asArray)(userConfigs ? userConfigs : this.userConfig || []); if (!configs.length) return []; // 未开启多配置的情况下不过滤 if (!this.multipleConfigEnabled) return (0, utils_1.asArray)(configs[0]); const validFilters = this.parseFilter(filters); // 无有效过滤条件, 返回全部 if (!validFilters) return configs; const configNameField = this.multipleConfigNameField; // 配置命中的条件 // 1. 配置不为空 // 2. 配置名称字段存在且 filters 中包含配置名称 或 filters 中包含索引值 return configs.filter((c, index) => { if (c == null) return false; if (configNameField && (c === null || c === void 0 ? void 0 : c[configNameField]) != null && validFilters.has(c === null || c === void 0 ? void 0 : c[configNameField])) { return true; } if (validFilters.has(String(index))) return true; return false; }); } /** * 载入和返回 package.json 的内容, 并存储在 this.pkg 中 * @returns */ async loadPackageJson() { if (this.pkg) return this.pkg; try { const pkgPath = (0, utils_1.lookupFile)(this.cwd, ['package'], ['.json'], { pathOnly: true }); if (pkgPath) { this.pkgPath = pkgPath; this.pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8')); } return this.pkg || {}; } catch (e) { this.logger.debug(`未找到或未能正确解析 package.json 文件, 原因为: ${e.message}`); // 即使解析失败, 这里也存储一个空对象 this.pkg = {}; } return this.pkg; } /** * 载入用户配置文件 * @param configFile - 配置文件地址 * @returns 配置文件内容 */ async loadConfigFromFile(configFile) { const start = Date.now(); let resolvedPath; let isTs = false; let isMjs = false; let isJson = false; let isPackageJson = false; const pkg = await this.loadPackageJson(); // 检查 package.json 中 "type" 字段 是否为 "module" 并设置 `isMjs` 为 true if ((pkg === null || pkg === void 0 ? void 0 : pkg.type) === 'module') isMjs = true; // 指定 configFile 路径的配置文件, 只从项目根目录载入 if (configFile) { resolvedPath = path.resolve(configFile); isTs = configFile.endsWith('.ts'); } // 未指定的情况下, 根据配置文件规则自动查询 else { resolvedPath = (0, utils_1.lookupFile)(this.cwd, this.supportConfigNames, this.supportConfigExtensions, { pathOnly: true }); } let userConfig; // 如果开启了 package.json 配置支持 if (!resolvedPath && this.packageJsonConfigEnabled) { resolvedPath = this.pkgPath; } // 如果未找到配置文件 if (!resolvedPath) { this.logger.debug('没有找到配置文件.'); return null; } // 判断文件类型 if (resolvedPath.endsWith(constants_1.PKG_FILE)) isPackageJson = true; if (resolvedPath.endsWith(constants_1.SupportConfigExtensions.mjs)) isMjs = true; if (resolvedPath.endsWith(constants_1.SupportConfigExtensions.ts)) isTs = true; if (resolvedPath.endsWith(constants_1.SupportConfigExtensions.json)) isJson = true; if (resolvedPath.endsWith(constants_1.SupportConfigExtensions.jsonc)) isJson = true; if (resolvedPath.endsWith(constants_1.SupportConfigExtensions.json5)) isJson = true; // 临时配置文件地址, 后续逻辑使用 const tempConfigFileName = `.${this.name}.config.temp.js`; const tempConfigFilePath = this.getCachedFilePath(tempConfigFileName); try { if (isJson) { if (isPackageJson) { userConfig = pkg === null || pkg === void 0 ? void 0 : pkg[this.name]; // 如果 package.json 里面无对应字段或对应的字段为 undefined // 则我们不将该文件作为 用户配置文件 if (userConfig == null) { this.logger.debug(`文件 package.json 中未发现 \`${this.name}\` 相关配置, 已忽略`); return null; } this.logger.debug(`已从 package.json 中加载配置, 耗时: ${Date.now() - start}ms, 路径信息:`, resolvedPath); } else { const jsonType = path.extname(resolvedPath).slice(1); userConfig = await (0, utils_1.readJsonLike)(resolvedPath); this.logger.debug(`${jsonType} 配置文件已加载, 耗时: ${Date.now() - start}ms`, resolvedPath); } } this.logger.info(`发现配置文件: ${configFile ? configFile : path.relative(this.cwd, resolvedPath)}`); if (!userConfig) { userConfig = await (0, utils_1.importJsOrMjsOrTsFromFile)({ cwd: this.cwd, filePath: resolvedPath, tempFilePath: tempConfigFilePath, isMjs, isTs }); } // 支持配置函数 const config = await (typeof userConfig === 'function' ? await userConfig() : userConfig); this.userConfig = (0, utils_1.asArray)(config); this.logger.success(`配置文件加载成功: ${configFile ? configFile : path.relative(this.cwd, resolvedPath)}`); // 保存已载入的用户配置文件地址 this.userConfigFilePath = resolvedPath; return this.userConfig; } catch (e) { const error = e; this.logger.error(`配置文件加载失败: ${configFile ? configFile : path.relative(this.cwd, resolvedPath)}`, { error }); throw e; } } /** * 获取临时目录文件夹 * @returns 当前 cwd 下的临时文件夹, 通常为 .[name] 文件夹 */ getTempDir() { const tempDir = this.tempDir ? this.tempDir : path.join(this.cwd, `.${this.name}`); fs.ensureDirSync(tempDir); return tempDir; } /** * 设置临时文件夹 */ setTempDir(tempDir) { this.tempDir = path.resolve(this.cwd, tempDir); } /** * 清空临时文件夹 */ clearTempDir() { const tempDir = this.getTempDir(); if (tempDir) fs.emptyDirSync(tempDir); } /** * 清空缓存文件夹 */ clearCacheDir() { const cacheDir = this.getCacheDir(); if (cacheDir) fs.emptyDirSync(cacheDir); } /** * 写入到 cache 文件夹 * @param fileName - 文件名称 * @param content - 文件内容 * @returns 缓存文件完整路径 */ writeToCacheDir(fileName, content) { const filePath = this.getCachedFilePath(fileName); fs.ensureDirSync(path.dirname(filePath)); fs.writeFileSync(filePath, content, 'utf-8'); return filePath; } /** * 读取缓存文件 * @param fileName - 文件名称 * @returns 文件内容 */ async loadCachedFile(fileName) { const filePath = this.getCachedFilePath(fileName); try { if (fs.statSync(filePath).isFile()) { return await fs.readFile(filePath, 'utf-8'); } // eslint-disable-next-line no-empty } catch (e) { } } /** * 根据文件名称, 获取缓存文件完整地址 * @param fileName - 文件名称 * @returns 缓存文件完整地址 */ getCachedFilePath(fileName) { return path.join(this.getCacheDir(), // 转换 ../abc 文件为 abc // 同时需要兼容 windows 路径 path.relative('/', path.resolve('/', fileName))); } /** * 获取 缓存 文件夹 * @returns 文件夹地址 */ getCacheDir() { if (this.cacheDir) return this.cacheDir; const cwd = this.cwd; let dir = cwd; for (;;) { try { if (fs.statSync(path.join(dir, constants_1.PKG_FILE)).isFile()) break; // eslint-disable-next-line no-empty } catch (e) { } const parent = path.dirname(dir); if (dir === parent) { dir = undefined; break; } dir = parent; } let cacheDir; if (!dir) { cacheDir = path.resolve(cwd, `.cache/${this.name}`); } else if (process.versions.pnp === '1') { cacheDir = path.resolve(dir, `.pnp/.cache/${this.name}`); } else if (process.versions.pnp === '3') { cacheDir = path.resolve(dir, `.yarn/.cache/${this.name}`); } else { cacheDir = path.resolve(dir, `node_modules/.cache/${this.name}`); } this.cacheDir = cacheDir; return cacheDir; } } exports.Config = Config; //# sourceMappingURL=config.js.map