fec-builder
Version:
通用的前端构建工具,屏蔽业务无关的细节配置,开箱即用
244 lines (243 loc) • 11.1 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.watchBuildConfig = exports.setNeedAnalyze = exports.getNeedAnalyze = exports.findBuildConfig = exports.shouldAddRuntimePolyfill = exports.shouldAddGlobalPolyfill = exports.shouldAddPolyfill = exports.PolyfillType = void 0;
const lodash_1 = require("lodash");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const files_1 = __importDefault(require("../constants/files"));
const _1 = require(".");
const paths_1 = require("./paths");
const logger_1 = __importDefault(require("./logger"));
var PolyfillType;
(function (PolyfillType) {
/** 在全局环境做 polyfill,适合应用 */
PolyfillType["Global"] = "global";
/** 在运行的上下文做 polyfill,不污染全局,适合工具类库 */
PolyfillType["Runtime"] = "runtime";
})(PolyfillType = exports.PolyfillType || (exports.PolyfillType = {}));
function shouldAddPolyfill(value) {
return !!value;
}
exports.shouldAddPolyfill = shouldAddPolyfill;
function shouldAddGlobalPolyfill(value) {
return value === true || value === PolyfillType.Global;
}
exports.shouldAddGlobalPolyfill = shouldAddGlobalPolyfill;
function shouldAddRuntimePolyfill(value) {
return value === PolyfillType.Runtime;
}
exports.shouldAddRuntimePolyfill = shouldAddRuntimePolyfill;
/** merge two config content */
function mergeConfig(cfg1, cfg2) {
return _1.extend(cfg1, cfg2, {
transforms: _1.extend(cfg1.transforms, cfg2.transforms),
envVariables: _1.extend(cfg1.envVariables, cfg2.envVariables),
optimization: _1.extend(cfg1.optimization, cfg2.optimization),
test: _1.extend(cfg1.test, cfg2.test),
engines: _1.extend(cfg1.engines, cfg2.engines)
});
}
/** parse config content */
const parseConfig = (cnt) => JSON.parse(cnt);
/** read and parse config content */
const readConfig = (configFilePath) => {
const configFileRawContent = fs_1.default.readFileSync(configFilePath, { encoding: 'utf8' });
const configFileContent = parseConfig(configFileRawContent);
return configFileContent;
};
/** check if extends target should be looked up from deps (`node_modules/`) */
function isExtendsTargetFromDeps(
/** name of extends target */
name) {
// 满足以下格式的 name 认为应该从 `node_modules/` 中查找
return (/^build-config-/.test(name) // build-config-xxx
|| /@[^/]+\/build-config(-|$|\/)/.test(name) // @xxx/build-config / @xxx/build-config-xxx
);
}
function tryResolve(name, paths) {
logger_1.default.debug('resolve name:', name);
logger_1.default.debug('resolve paths:', paths);
try {
const result = require.resolve(name, { paths });
logger_1.default.debug(`resolve result: ${result}`);
return result;
}
catch (e) {
logger_1.default.debug('resolve error:', e);
return null;
}
}
/** lookup extends target */
function lookupExtendsTarget(
/** name of extends target */
name,
/** path of source config file */
sourceConfigFilePath) {
logger_1.default.debug(`lookup extends target config: ${name}`);
if (isExtendsTargetFromDeps(name)) {
logger_1.default.debug(`lookup extends target in node_modules/: ${name}`);
const paths = [sourceConfigFilePath, paths_1.getBuildRoot()]; // 从这俩路径出发进行查找
const resolvedPath = ( // 分别尝试查找 `${name}/build-config.json` 以及 `name`
tryResolve(path_1.default.join(name, files_1.default.config), paths)
|| tryResolve(name, paths));
if (resolvedPath == null) {
return Promise.reject(new Error(`lookup ${name} in node_modules/ failed, you may forget to add it as deps of your project`));
}
return Promise.resolve(resolvedPath);
}
const presetConfigFilePath = path_1.default.resolve(__dirname, `../../preset-configs/${name}.json`);
logger_1.default.debug(`try preset config: ${presetConfigFilePath}`);
if (fs_1.default.existsSync(presetConfigFilePath)) {
logger_1.default.debug(`found preset config: ${presetConfigFilePath}`);
return Promise.resolve(presetConfigFilePath);
}
const sourceConfigFileDir = path_1.default.dirname(sourceConfigFilePath);
const localConfigFilePath = path_1.default.resolve(sourceConfigFileDir, name);
logger_1.default.debug(`try local config: ${localConfigFilePath}`);
if (fs_1.default.existsSync(localConfigFilePath)) {
logger_1.default.debug(`found local config: ${localConfigFilePath}`);
return Promise.resolve(localConfigFilePath);
}
const localConfigFilePathWithExtension = path_1.default.resolve(sourceConfigFileDir, `${name}.json`);
logger_1.default.debug(`try local config with extension: ${localConfigFilePathWithExtension}`);
if (fs_1.default.existsSync(localConfigFilePathWithExtension)) {
logger_1.default.debug(`found local config with extension: ${localConfigFilePathWithExtension}`);
return Promise.resolve(localConfigFilePathWithExtension);
}
const message = `lookup extends target config failed: ${name}`;
logger_1.default.debug(message);
return Promise.reject(new Error(message));
}
/** get extends target content */
function getExtendsTarget(
/** name of extends target */
name,
/** path of source config file */
sourceConfigFilePath) {
return __awaiter(this, void 0, void 0, function* () {
const configFilePath = yield lookupExtendsTarget(name, sourceConfigFilePath);
return readAndResolveConfig(configFilePath);
});
}
/** resolve config content by recursively get and merge config to `extends` */
function readAndResolveConfig(
/** path of given config */
configFilePath) {
return __awaiter(this, void 0, void 0, function* () {
const config = readConfig(configFilePath);
const extendsTarget = config.hasOwnProperty('extends') ? config['extends'] : 'default';
if (!extendsTarget) {
return Promise.resolve(config);
}
const extendsConfig = yield getExtendsTarget(extendsTarget, configFilePath);
return mergeConfig(extendsConfig, config);
});
}
function normalizePage({ template, entries: _entries, path }) {
const entries = typeof _entries === 'string' ? [_entries] : _entries;
return { template, path, entries };
}
function normalizePages(input) {
return lodash_1.mapValues(input, normalizePage);
}
function normalizeTransforms(input) {
return lodash_1.mapValues(input, value => (typeof value === 'string'
? { transformer: value }
: value));
}
function normalizeConfig({ extends: _extends, publicUrl: _publicUrl, srcDir, staticDir, distDir, entries, pages: _pages, transforms: _transforms, envVariables, optimization, devProxy, deploy, targets, test, engines }) {
if (_extends == null)
throw new Error('Invalid value of field extends');
if (_publicUrl == null)
throw new Error('Invalid value of field publicUrl');
if (srcDir == null)
throw new Error('Invalid value of field srcDir');
if (staticDir == null)
throw new Error('Invalid value of field staticDir');
if (distDir == null)
throw new Error('Invalid value of field distDir');
if (entries == null)
throw new Error('Invalid value of field entries');
if (_pages == null)
throw new Error('Invalid value of field pages');
if (_transforms == null)
throw new Error('Invalid value of field transforms');
if (envVariables == null)
throw new Error('Invalid value of field envVariables');
if (optimization == null)
throw new Error('Invalid value of field optimization');
if (devProxy == null)
throw new Error('Invalid value of field devProxy');
if (deploy == null)
throw new Error('Invalid value of field deploy');
if (targets == null)
throw new Error('Invalid value of field targets');
if (test == null)
throw new Error('Invalid value of field test');
if (engines == null)
throw new Error('Invalid value of field engines');
const publicUrl = _publicUrl.replace(/\/?$/, '/');
const pages = normalizePages(_pages);
const transforms = normalizeTransforms(_transforms);
return {
extends: _extends, publicUrl, srcDir, staticDir, distDir, entries, pages,
transforms, envVariables, optimization, devProxy,
deploy, targets, test, engines
};
}
/** 获取最终使用的 build config 文件路径 */
function resolveBuildConfigFilePath() {
// 若指定了 build config file path,则使用之
// 否则使用 build root 下的 build config 文件
return paths_1.getBuildConfigFilePath() || paths_1.abs(files_1.default.config);
}
let cached = null;
/** find config file and resolve config content based on paths info */
function findBuildConfig(disableCache = false) {
return __awaiter(this, void 0, void 0, function* () {
if (cached && !disableCache) {
return cached;
}
const configFilePath = resolveBuildConfigFilePath();
logger_1.default.debug(`use build config file: ${configFilePath}`);
return cached = readAndResolveConfig(configFilePath).then(config => {
const normalized = normalizeConfig(config);
logger_1.default.debug('result build config:');
logger_1.default.debug(normalized);
return normalized;
});
});
}
exports.findBuildConfig = findBuildConfig;
let needAnalyze = false;
/** Whether analyze bundle */
function getNeedAnalyze() {
return needAnalyze;
}
exports.getNeedAnalyze = getNeedAnalyze;
function setNeedAnalyze(value) {
needAnalyze = value;
}
exports.setNeedAnalyze = setNeedAnalyze;
/** watch build config, call listener when build config changes */
function watchBuildConfig(listener) {
const configFilePath = resolveBuildConfigFilePath();
logger_1.default.debug(`watch build config file: ${configFilePath}`);
return _1.watchFile(configFilePath, () => __awaiter(this, void 0, void 0, function* () {
const buildConfig = yield findBuildConfig(true); // 把 build config 缓存刷掉
listener(buildConfig);
}));
}
exports.watchBuildConfig = watchBuildConfig;
;