import { performance } from 'perf_hooks'; import { isAbsolute, resolve, extname, dirname, relative } from 'path'; import fs from 'fs-extra'; import { loadEntryFiles, loadSource, INCLUDES_UTF8_FILE_TYPE } from '../helpers/load.js'; import { createPluginContainer } from '../helpers/pluginContainer.js'; import { isObject, isDirectory, timeFrom } from '../utils.js'; import { createLogger } from '../helpers/logger.js'; import type { ComponentContext, ComponentConfig } from '../types.js'; export interface File { // globby parsed path, which is relative filePath: string; // absolute path absolutePath: string; // extension // ext: 'jsx' | 'js' | 'ts' | 'tsx' | 'mjs' | 'png' | 'scss' | 'less' | 'css' | 'png' | 'jpg'; ext: string; // where to store target files dest?: string; // parsed code code?: string; // source map map?: string; } export default async function runTransform(cfg: ComponentConfig, ctx: ComponentContext) { const { rootDir } = ctx; const { outputDir, entry, rollupPlugins } = cfg; const logger = createLogger('transform'); const entryDir = entry; let files: File[]; if (isDirectory(entry)) { files = loadEntryFiles( resolve(rootDir, entryDir), ) .map((filePath) => ({ filePath, absolutePath: resolve(rootDir, entryDir, filePath), ext: extname(filePath), })); } else { const relativeFilePath = isAbsolute(entry) ? relative(entry, rootDir) : entry; files = [{ filePath: relativeFilePath, absolutePath: resolve(rootDir, relativeFilePath), ext: extname(relativeFilePath), }]; } const container = await createPluginContainer({ plugins: rollupPlugins, root: rootDir, output: outputDir, logger, build: { rollupOptions: cfg, }, }); const transformStart = performance.now(); logger.info('Build start...'); // @ts-ignore FIXME: ignore await container.buildStart(cfg); fs.removeSync(outputDir); for (let i = 0; i < files.length; ++i) { const traverseFileStart = performance.now(); const dest = resolve(outputDir, files[i].filePath); files[i].dest = dest; fs.ensureDirSync(dirname(dest)); const id = (await container.resolveId(files[i].filePath))?.id || files[i].filePath; const loadResult = await container.load(id); let code: string = null; let map: string = null; // 除特定类型外,需要用户提供额外的插件的 load 来处理这些文件 if (loadResult === null && !INCLUDES_UTF8_FILE_TYPE.test(files[i].ext)) { // 直接拷贝这些文件 fs.copyFileSync(files[i].absolutePath, dest); logger.debug(`Transform file ${files[i].absolutePath}`, timeFrom(traverseFileStart)); logger.debug(`Copy File ${files[i].absolutePath} to ${dest}`); continue; } if (loadResult === null) { code = loadSource(files[i].absolutePath); // Need to generate .map ? } else if (isObject(loadResult)) { code = loadResult.code; map = loadResult.map as string; } const transformResult = await container.transform(code, files[i].absolutePath); if (transformResult === null || (isObject(transformResult) && transformResult.code === null) ) { // 不存在 transfrom 逻辑,code 保持不变 } else { files[i].code = code = transformResult.code; files[i].map = map = transformResult.map as string; const finalExtname = transformResult?.meta?.ext; // If extname changed if (finalExtname) { files[i].dest = (files[i].dest).replace(files[i].ext, finalExtname); } } fs.writeFileSync(files[i].dest, code, 'utf-8'); logger.debug(`Transform file ${files[i].absolutePath}`, timeFrom(traverseFileStart)); // FIXME: 生成出来的 maps 有问题,应该是最开始就应该提供 sourcemap 文件? // writeFileSync(`${dest}.map`, map, 'utf-8'); } await container.close(); logger.info(`⚡️ Build success in ${(performance.now() - transformStart).toFixed(2)}ms`); return files; }