diff --git a/resources/build-deno.ts b/resources/build-deno.ts index 04424624e7..c5da8ce706 100644 --- a/resources/build-deno.ts +++ b/resources/build-deno.ts @@ -5,36 +5,34 @@ import ts from 'typescript'; import { changeExtensionInImportPaths } from './change-extension-in-import-paths.js'; import { inlineInvariant } from './inline-invariant.js'; -import { readdirRecursive, showDirStats, writeGeneratedFile } from './utils.js'; +import { readTSConfig, showDirStats, writeGeneratedFile } from './utils.js'; fs.rmSync('./denoDist', { recursive: true, force: true }); fs.mkdirSync('./denoDist'); -const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ }); -for (const filepath of srcFiles) { - if (filepath.endsWith('.ts')) { - const srcPath = path.join('./src', filepath); - - const sourceFile = ts.createSourceFile( - srcPath, - fs.readFileSync(srcPath, 'utf-8'), - ts.ScriptTarget.Latest, - ); - - const transformed = ts.transform(sourceFile, [ - changeExtensionInImportPaths({ extension: '.ts' }), - inlineInvariant, - ]); - const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); - const newContent = printer.printBundle( - ts.factory.createBundle(transformed.transformed), - ); - - transformed.dispose(); - - const destPath = path.join('./denoDist', filepath); - writeGeneratedFile(destPath, newContent); +const tsProgram = ts.createProgram(['src/index.ts'], readTSConfig()); +for (const sourceFile of tsProgram.getSourceFiles()) { + if ( + tsProgram.isSourceFileFromExternalLibrary(sourceFile) || + tsProgram.isSourceFileDefaultLibrary(sourceFile) + ) { + continue; } + + const transformed = ts.transform(sourceFile, [ + changeExtensionInImportPaths({ extension: '.ts' }), + inlineInvariant, + ]); + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const newContent = printer.printBundle( + ts.factory.createBundle(transformed.transformed), + ); + + transformed.dispose(); + + const filepath = path.relative('./src', sourceFile.fileName); + const destPath = path.join('./denoDist', filepath); + writeGeneratedFile(destPath, newContent); } fs.copyFileSync('./LICENSE', './denoDist/LICENSE'); diff --git a/resources/build-npm.ts b/resources/build-npm.ts index 3fbbbd657b..aec7d99f48 100644 --- a/resources/build-npm.ts +++ b/resources/build-npm.ts @@ -6,70 +6,24 @@ import ts from 'typescript'; import { inlineInvariant } from './inline-invariant.js'; import { - localRepoPath, - readdirRecursive, readPackageJSON, + readTSConfig, showDirStats, writeGeneratedFile, } from './utils.js'; -fs.rmSync('./npmDist', { recursive: true, force: true }); -fs.mkdirSync('./npmDist'); - -// Based on https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#getting-the-dts-from-a-javascript-file -const tsConfigPath = localRepoPath('tsconfig.json'); -const { config: tsConfig, error: tsConfigError } = ts.parseConfigFileTextToJson( - tsConfigPath, - fs.readFileSync(tsConfigPath, 'utf-8'), -); -assert(tsConfigError === undefined, 'Fail to parse config: ' + tsConfigError); -assert( - tsConfig.compilerOptions, - '"tsconfig.json" should have `compilerOptions`', -); - -const { options: tsOptions, errors: tsOptionsErrors } = - ts.convertCompilerOptionsFromJson( - { - ...tsConfig.compilerOptions, - module: 'es2020', - noEmit: false, - declaration: true, - declarationDir: './npmDist', - outDir: './npmDist', - }, - process.cwd(), - ); - -assert( - tsOptionsErrors.length === 0, - 'Fail to parse options: ' + tsOptionsErrors, -); - -const tsHost = ts.createCompilerHost(tsOptions); -tsHost.writeFile = writeGeneratedFile; - -const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost); -const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, { - after: [inlineInvariant], -}); -assert( - !tsResult.emitSkipped, - 'Fail to generate `*.d.ts` files, please run `npm run check`', -); +buildPackage(); +showDirStats('./npmDist'); -fs.copyFileSync('./LICENSE', './npmDist/LICENSE'); -fs.copyFileSync('./README.md', './npmDist/README.md'); +function buildPackage() { + fs.rmSync('./npmDist', { recursive: true, force: true }); + fs.mkdirSync('./npmDist'); -// Should be done as the last step so only valid packages can be published -writeGeneratedFile( - './npmDist/package.json', - JSON.stringify(buildPackageJSON()), -); + fs.copyFileSync('./LICENSE', './npmDist/LICENSE'); + fs.copyFileSync('./README.md', './npmDist/README.md'); -showDirStats('./npmDist'); + const { emittedTSFiles } = emitTSFiles('./npmDist'); -function buildPackageJSON() { const packageJSON = readPackageJSON(); delete packageJSON.private; @@ -97,10 +51,10 @@ function buildPackageJSON() { packageJSON.exports = {}; - for (const filepath of readdirRecursive('./src', { ignoreDir: /^__.*__$/ })) { - if (path.basename(filepath) === 'index.ts') { - const key = path.dirname(filepath); - packageJSON.exports[key] = filepath.replace(/\.ts$/, '.js'); + for (const filepath of emittedTSFiles) { + if (path.basename(filepath) === 'index.js') { + const relativePath = './' + path.relative('./npmDist', filepath); + packageJSON.exports[path.dirname(relativePath)] = relativePath; } } @@ -136,5 +90,37 @@ function buildPackageJSON() { ); } - return packageJSON; + // Should be done as the last step so only valid packages can be published + writeGeneratedFile('./npmDist/package.json', JSON.stringify(packageJSON)); +} + +// Based on https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#getting-the-dts-from-a-javascript-file +function emitTSFiles(outDir: string): { + emittedTSFiles: ReadonlyArray; +} { + const tsOptions = readTSConfig({ + module: 'es2020', + noEmit: false, + declaration: true, + declarationDir: outDir, + outDir, + listEmittedFiles: true, + }); + + const tsHost = ts.createCompilerHost(tsOptions); + tsHost.writeFile = writeGeneratedFile; + + const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost); + const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, { + after: [inlineInvariant], + }); + assert( + !tsResult.emitSkipped, + 'Fail to generate `*.d.ts` files, please run `npm run check`', + ); + + assert(tsResult.emittedFiles != null); + return { + emittedTSFiles: tsResult.emittedFiles.sort((a, b) => a.localeCompare(b)), + }; } diff --git a/resources/utils.ts b/resources/utils.ts index d1ae8c0b4b..66b90e6e4c 100644 --- a/resources/utils.ts +++ b/resources/utils.ts @@ -5,6 +5,7 @@ import os from 'node:os'; import path from 'node:path'; import prettier from 'prettier'; +import ts from 'typescript'; export function localRepoPath(...paths: ReadonlyArray): string { const repoDir = new URL('..', import.meta.url).pathname; @@ -109,30 +110,21 @@ function spawn( childProcess.spawnSync(command, args, { stdio: 'inherit', ...options }); } -export function readdirRecursive( - dirPath: string, - opts: { ignoreDir?: RegExp } = {}, -): Array { - const { ignoreDir } = opts; - const result = []; - for (const dirent of fs.readdirSync(dirPath, { withFileTypes: true })) { - const name = dirent.name; - if (!dirent.isDirectory()) { - result.push(dirent.name); - continue; - } - - if (ignoreDir?.test(name)) { - continue; +function* readdirRecursive(dirPath: string): Generator<{ + name: string; + filepath: string; + stats: fs.Stats; +}> { + for (const name of fs.readdirSync(dirPath)) { + const filepath = path.join(dirPath, name); + const stats = fs.lstatSync(filepath); + + if (stats.isDirectory()) { + yield* readdirRecursive(filepath); + } else { + yield { name, filepath, stats }; } - const list = readdirRecursive(path.join(dirPath, name), opts).map((f) => - path.join(name, f), - ); - result.push(...list); } - - result.sort((a, b) => a.localeCompare(b)); - return result.map((filepath) => './' + filepath); } export function showDirStats(dirPath: string): void { @@ -141,18 +133,14 @@ export function showDirStats(dirPath: string): void { } = {}; let totalSize = 0; - for (const filepath of readdirRecursive(dirPath)) { - const name = filepath.split(path.sep).at(-1); - assert(name != null); - const [base, ...splitExt] = name.split('.'); - const ext = splitExt.join('.'); + for (const { name, filepath, stats } of readdirRecursive(dirPath)) { + const ext = name.split('.').slice(1).join('.'); + const filetype = ext ? '*.' + ext : name; - const filetype = ext ? '*.' + ext : base; - fileTypes[filetype] = fileTypes[filetype] || { filepaths: [], size: 0 }; + fileTypes[filetype] = fileTypes[filetype] ?? { filepaths: [], size: 0 }; - const { size } = fs.lstatSync(path.join(dirPath, filepath)); - totalSize += size; - fileTypes[filetype].size += size; + totalSize += stats.size; + fileTypes[filetype].size += stats.size; fileTypes[filetype].filepaths.push(filepath); } @@ -163,7 +151,8 @@ export function showDirStats(dirPath: string): void { if (numFiles > 1) { stats.push([filetype + ' x' + numFiles, typeStats.size]); } else { - stats.push([typeStats.filepaths[0], typeStats.size]); + const relativePath = path.relative(dirPath, typeStats.filepaths[0]); + stats.push([relativePath, typeStats.size]); } } stats.sort((a, b) => b[1] - a[1]); @@ -218,3 +207,31 @@ export function readPackageJSON( const filepath = path.join(dirPath, 'package.json'); return JSON.parse(fs.readFileSync(filepath, 'utf-8')); } + +export function readTSConfig(overrides?: any): ts.CompilerOptions { + const tsConfigPath = localRepoPath('tsconfig.json'); + + const { config: tsConfig, error: tsConfigError } = + ts.parseConfigFileTextToJson( + tsConfigPath, + fs.readFileSync(tsConfigPath, 'utf-8'), + ); + assert(tsConfigError === undefined, 'Fail to parse config: ' + tsConfigError); + assert( + tsConfig.compilerOptions, + '"tsconfig.json" should have `compilerOptions`', + ); + + const { options: tsOptions, errors: tsOptionsErrors } = + ts.convertCompilerOptionsFromJson( + { ...tsConfig.compilerOptions, ...overrides }, + process.cwd(), + ); + + assert( + tsOptionsErrors.length === 0, + 'Fail to parse options: ' + tsOptionsErrors, + ); + + return tsOptions; +}