@jakesidsmith/tsb
Version:
Dead simple TypeScript bundler, watcher, dev server, transpiler, and polyfiller
132 lines (112 loc) • 3.7 kB
text/typescript
import * as yup from 'yup';
import * as path from 'path';
import * as fs from 'fs';
import * as ts from 'typescript';
import * as logger from './logger';
import { Tsconfig } from './types';
import { VALID_JSX_OPTIONS } from './constants';
import { getErrorMessages } from './utils';
const TSCONFIG_VALIDATOR = yup
.object()
.shape<Tsconfig>({
extends: yup.string().optional(),
include: yup.array().of(yup.string().required()).optional(),
compilerOptions: yup.lazy<Tsconfig['compilerOptions']>((value) => {
if (value) {
return yup.object().shape({
jsx: yup.mixed().oneOf(VALID_JSX_OPTIONS).optional(),
sourceMap: yup.boolean().optional(),
module: yup.string().optional(),
});
}
return yup.mixed<undefined>().optional();
}),
})
.required();
export const resolveTsconfigPath = (
root: string,
tsconfigPath: string
): string => {
const resolvedTsconfigPath = path.resolve(root, tsconfigPath);
const fullTsconfigPath =
fs.existsSync(resolvedTsconfigPath) &&
fs.lstatSync(resolvedTsconfigPath).isDirectory()
? path.resolve(resolvedTsconfigPath, 'tsconfig.json')
: resolvedTsconfigPath;
if (!fs.existsSync(fullTsconfigPath)) {
logger.error(`Could not resolve tsconfig.json at "${fullTsconfigPath}"`);
return process.exit(1);
}
return fullTsconfigPath;
};
export const resolveTsconfig = (
tsconfigPath: string
): { raw: Tsconfig; resolved: Tsconfig } => {
const tsconfigDir = path.dirname(tsconfigPath);
const tsconfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
if (tsconfig.error) {
logger.error(
ts.flattenDiagnosticMessageText(tsconfig.error.messageText, '\n')
);
logger.error(`Error reading tsconfig.json at "${tsconfigPath}"`);
return process.exit(1);
}
const validTsconfig: Tsconfig = tsconfig.config;
try {
TSCONFIG_VALIDATOR.validateSync(validTsconfig);
} catch (error) {
logger.error(getErrorMessages(error));
logger.error(`Invalid tsconfig.json at "${tsconfigPath}"`);
return process.exit(1);
}
const extendedPath = validTsconfig.extends
? resolveTsconfigPath(tsconfigDir, validTsconfig.extends)
: null;
if (extendedPath === tsconfigPath) {
logger.error(
`Invalid tsconfig.json at "${tsconfigPath}" - cannot extend itself`
);
return process.exit(1);
}
const extended = extendedPath ? resolveTsconfig(extendedPath) : null;
return {
raw: validTsconfig,
resolved: {
...extended?.raw,
...validTsconfig,
include:
validTsconfig.include?.map((include) =>
path.resolve(tsconfigDir, include)
) ?? extended?.resolved.include,
compilerOptions: {
...extended?.raw.compilerOptions,
...validTsconfig.compilerOptions,
},
},
};
};
export const getTsconfig = (configPath: string): Tsconfig => {
const { resolved } = resolveTsconfig(configPath);
if (!resolved.compilerOptions?.sourceMap) {
logger.warn(
'No sourceMap enabled in tsconfig.json - source maps will not be generated'
);
}
if (!resolved.include?.length) {
logger.error(
'No files in tsconfig.json include option - specify some files to parse'
);
return process.exit(1);
}
if (
resolved.compilerOptions?.module &&
!resolved.compilerOptions.module.toLowerCase().startsWith('es')
) {
logger.warn(
`Your tsconfig.json module was set to "${resolved.compilerOptions.module}".
You should target an ES module type e.g. "ESNext" to get the full benefits of this bundler.
We'll handle converting everything to CommonJS for you.`
);
}
return resolved;
};