@lcap/builder
Version:
lcap builder utils
328 lines (327 loc) • 13.8 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.LcapBuildModulesCSS = LcapBuildModulesCSS;
exports.getModuleEntries = getModuleEntries;
exports.buildModules = buildModules;
const vite_1 = require("vite");
const lodash_1 = require("lodash");
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const rollup_1 = __importDefault(require("rollup"));
const rollup_plugin_dts_1 = require("rollup-plugin-dts");
const virtual_file_names_1 = require("../constants/virtual-file-names");
const logger_1 = __importDefault(require("../utils/logger"));
const lcap_1 = require("../utils/lcap");
const build_utils_1 = require("../utils/build-utils");
const exec_1 = require("../utils/exec");
function getTagName(name, framework) {
let key = name;
if (framework.startsWith('vue')) {
key = (0, lodash_1.kebabCase)(name);
}
return key;
}
const genCompEntryName = (name, framework) => `components/${getTagName(name, framework)}/index`;
function LcapBuildModulesCSS() {
const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
const cssModuleRE = new RegExp(`\\.module${CSS_LANGS_RE.source}`);
const isModuleCSSRequest = (request) => cssModuleRE.test(request);
const cssInjectorText = `
export function injectStyle(css, insertAt = 'top') {
if (!css || typeof document === 'undefined') return
const head = document.head || document.querySelector('head')
const firstChild = head.querySelector(':first-child')
const style = document.createElement('style')
style.appendChild(document.createTextNode(css))
if (insertAt === 'top' && firstChild) {
head.insertBefore(style, firstChild)
} else {
head.appendChild(style)
}
}
`;
const stylesMap = new Map();
return [{
name: 'vite-lcap:build-modules-css-pre',
resolveId(source) {
if (source === virtual_file_names_1.virtualInjectCSSFileId) {
return virtual_file_names_1.virtualInjectCSSFileId;
}
if (source === virtual_file_names_1.virtualThemeCSSFileId) {
return virtual_file_names_1.virtualThemeCSSFileId;
}
return undefined;
},
load(id) {
if (id === virtual_file_names_1.virtualInjectCSSFileId) {
return cssInjectorText;
}
if (id === virtual_file_names_1.virtualThemeCSSFileId) {
return '';
}
return undefined;
},
transform(code, id) {
if (isModuleCSSRequest(id)) {
stylesMap.set(id, code);
}
},
}, {
name: 'vite-lcap:build-modules-css-post',
enforce: 'post',
transform(code, id) {
if (!isModuleCSSRequest(id) || !stylesMap.has(id)) {
return null;
}
const cssCode = stylesMap.get(id) || '';
const cssString = cssCode.replace(/ *\\9/g, '').replace(/\\(\d+)/g, '0o$1');
const injections = `\nimport { injectStyle } from '${virtual_file_names_1.virtualInjectCSSFileId}';\ninjectStyle(${JSON.stringify(cssString)}, 'bottom');`;
return {
code: code + injections,
map: null,
};
},
generateBundle(_, bundle) {
Object.keys(bundle).forEach((file) => {
if (bundle[file].type === 'asset') {
delete bundle[file];
}
});
},
}];
}
function getModuleEntries(options, components) {
const entries = Object.assign({}, (options.entries ? options.entries : {}));
components.forEach(({ name, tsPath }) => {
const entryName = genCompEntryName(name, options.framework);
const basename = path_1.default.basename(tsPath);
if (basename !== 'api.ts') {
return;
}
entries[entryName] = (0, vite_1.normalizePath)(path_1.default.relative(options.rootPath, path_1.default.resolve(tsPath, '../index')));
});
const logicEntry = 'src/logics/index.ts';
if (fs_extra_1.default.existsSync(logicEntry)) {
entries['logics/index'] = 'src/logics/index.ts';
}
return entries;
}
function viteBuildModules(options, components) {
return __awaiter(this, void 0, void 0, function* () {
const loadResult = yield (0, vite_1.loadConfigFromFile)({ command: 'build', mode: 'production' });
if (!loadResult || !loadResult.config) {
throw new Error('未找到 vite 配置文件');
}
const { external } = (0, build_utils_1.getBuildOutputConifg)(options);
if (options.external && options.external.length > 0) {
external.push(...options.external);
}
const entries = getModuleEntries(options, components);
const exportsMap = {};
Object.keys(entries).forEach((name) => {
exportsMap[name] = [];
});
let buildConfig = {
define: {
'process.env': {
NODE_ENV: 'production',
},
},
build: {
emptyOutDir: true,
copyPublicDir: false,
minify: false,
sourcemap: false,
lib: {
entry: entries,
formats: ['es'],
},
rollupOptions: {
external,
treeshake: {
moduleSideEffects: options.moduleSideEffects || ((id) => id.includes('node_modules')),
},
output: {
format: 'es',
hoistTransitiveImports: false,
chunkFileNames: '_chunks/dep-[hash].mjs',
},
},
outDir: options.outDir,
},
};
const { config } = loadResult;
if (config.build && config.build.lib) {
delete config.build.lib;
}
buildConfig = (0, vite_1.mergeConfig)(config, buildConfig);
if (!buildConfig.plugins) {
buildConfig.plugins = [];
}
else {
buildConfig.plugins = buildConfig.plugins.flat().filter((p) => (p && (!p.name || !p.name.startsWith('vite:lcap-')))) || [];
}
buildConfig.plugins.push(LcapBuildModulesCSS());
buildConfig.plugins.push({
name: 'vite:lcap-collect-export',
renderChunk(_, chunk) {
if (!Array.isArray(exportsMap[chunk.name]) || chunk.fileName.startsWith('_chunks')) {
return;
}
exportsMap[chunk.name].push(...chunk.exports);
},
});
yield (0, vite_1.build)(Object.assign({ configFile: false, envFile: false }, buildConfig));
return exportsMap;
});
}
function generateModulesJSON(options, exportsMap, components) {
return __awaiter(this, void 0, void 0, function* () {
const filePath = path_1.default.resolve(options.rootPath, options.outDir, 'modules.json');
const exportNameMap = {};
const apiPathMap = {};
components.forEach((metaInfo) => {
const apiPath = path_1.default.relative(options.rootPath, metaInfo.tsPath);
apiPathMap[metaInfo.name] = apiPath;
if (Array.isArray(metaInfo.children)) {
metaInfo.children.forEach((m) => {
apiPathMap[m.name] = apiPath;
});
}
});
Object.keys(exportsMap).forEach((entry) => {
let exportNames = exportsMap[entry];
if (!exportNames || exportNames.length === 0) {
return;
}
exportNames = exportNames.filter((name) => name !== 'default');
if (exportNames.length === 0) {
const comp = components.find((cp) => (genCompEntryName(cp.name, options.framework) === entry));
if (comp) {
exportNameMap[comp.name] = {
src: entry,
isDefault: true,
};
}
return;
}
exportNames.forEach((name) => {
exportNameMap[name] = {
src: entry,
isDefault: false,
};
});
});
const moduleInfo = { exports: exportNameMap, api: apiPathMap };
fs_extra_1.default.writeJSONSync(filePath, moduleInfo, { spaces: 2 });
return moduleInfo;
});
}
function generateIndex(options, exportNameMap) {
return __awaiter(this, void 0, void 0, function* () {
const codes = Object.keys(exportNameMap).map((key) => {
const { src, isDefault } = exportNameMap[key];
return `export { ${isDefault ? `default as ${key}` : key} } from './${src}';`;
});
codes.push('');
fs_extra_1.default.writeFileSync(path_1.default.resolve(options.rootPath, options.outDir, 'index.mjs'), codes.join('\n'));
});
}
function generateIndexDts(options, exportNameMap) {
return __awaiter(this, void 0, void 0, function* () {
const codes = Object.keys(exportNameMap).map((key) => {
return `export declare const ${key}: any;`;
});
codes.push('');
fs_extra_1.default.writeFileSync(path_1.default.resolve(options.rootPath, options.outDir, 'index.d.ts'), codes.join('\n'));
});
}
function buildDts(options) {
return __awaiter(this, void 0, void 0, function* () {
const typesPath = '_temp/types';
const command = options.framework === 'vue3' ? 'vue-tsc' : 'tsc';
try {
yield (0, exec_1.exec)(`npx ${command} -d --emitDeclarationOnly -p ${options.tsconfigPath} --outDir ${typesPath}`);
}
catch (e) {
logger_1.default.error(e);
}
let entry = `${typesPath}/index.d.ts`;
if (!fs_extra_1.default.existsSync(entry)) {
entry = `${typesPath}/src/index.d.ts`;
}
try {
const bundle = yield rollup_1.default.rollup({
input: entry,
plugins: [
(0, rollup_plugin_dts_1.dts)(),
{
name: 'rollup-temp-dts',
resolveId(source) {
if (source.endsWith('.css') || source.endsWith('.less') || source.endsWith('.scss') || source.endsWith('.vue')) {
return 'vite__temp.d.ts';
}
return undefined;
},
load(id) {
if (id === 'vite__temp.d.ts') {
return {
code: 'declare const _temp: any; export default _temp;',
};
}
return undefined;
},
},
],
});
yield Promise.all([{ file: `${options.outDir}/index.d.ts`, format: 'es' }].map(bundle.write));
}
catch (e) {
logger_1.default.error('构建 d.ts 失败');
throw e;
}
finally {
fs_extra_1.default.rmSync('_temp', { recursive: true });
}
});
}
function buildModules(options) {
return __awaiter(this, void 0, void 0, function* () {
if (!options.modules) {
return;
}
const buildModulesOptions = {
rootPath: options.rootPath,
type: options.type,
framework: options.framework,
outDir: 'es',
};
if (typeof options.modules === 'object') {
Object.assign(buildModulesOptions, options.modules);
}
logger_1.default.start('开始模块构建....');
const components = yield (0, lcap_1.getComponentMetaInfos)(options.rootPath, true);
const exportsMap = yield viteBuildModules(buildModulesOptions, components);
const moduleInfo = yield generateModulesJSON(buildModulesOptions, exportsMap, components);
yield generateIndex(buildModulesOptions, moduleInfo.exports);
if (buildModulesOptions.tsconfigPath && fs_extra_1.default.existsSync(buildModulesOptions.tsconfigPath)) {
yield buildDts(buildModulesOptions);
}
else {
yield generateIndexDts(buildModulesOptions, moduleInfo.exports);
}
logger_1.default.success('已完成模块构建....');
});
}