Skip to content

Commit

Permalink
build-npm/deno: use TS to find all *.ts files
Browse files Browse the repository at this point in the history
Motivation: Remove custom code for finding *.ts files and reuse code from typescript library
  • Loading branch information
IvanGoncharov committed Sep 19, 2022
1 parent 612abd3 commit be345af
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 118 deletions.
48 changes: 23 additions & 25 deletions resources/build-deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
106 changes: 46 additions & 60 deletions resources/build-npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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<string>;
} {
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)),
};
}
83 changes: 50 additions & 33 deletions resources/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>): string {
const repoDir = new URL('..', import.meta.url).pathname;
Expand Down Expand Up @@ -109,30 +110,21 @@ function spawn(
childProcess.spawnSync(command, args, { stdio: 'inherit', ...options });
}

export function readdirRecursive(
dirPath: string,
opts: { ignoreDir?: RegExp } = {},
): Array<string> {
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 {
Expand All @@ -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);
}

Expand All @@ -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]);
Expand Down Expand Up @@ -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;
}

0 comments on commit be345af

Please sign in to comment.