@nestjs/cli
Version:
Nest - modern, fast, powerful node.js web framework (@cli)
133 lines (132 loc) • 5.52 kB
JavaScript
import * as ts from 'typescript';
import { isEsmProject } from '../../utils/is-esm-project.js';
import { getValueOrDefault } from '../helpers/get-value-or-default.js';
export const swcDefaultsFactory = (tsOptions, configuration, tsConfigExclude = [], appName) => {
const builderOptions = typeof configuration?.compilerOptions?.builder !== 'string'
? configuration?.compilerOptions?.builder?.options
: {};
const esm = resolveSwcModuleType(tsOptions) === 'es6';
// In a monorepo the root "sourceRoot" is the default project's, so reading
// it directly makes every other project compile the default project's
// sources into its own outDir. Resolve it per application, the way the rest
// of the compiler does.
const sourceRoot = configuration
? (getValueOrDefault(configuration, 'sourceRoot', appName, 'sourceRoot') ?? 'src')
: 'src';
return {
swcOptions: {
sourceMaps: tsOptions?.sourceMap || (tsOptions?.inlineSourceMap && 'inline'),
module: esm
? {
type: 'es6',
// Node's ESM resolver requires full specifiers, so give the ones
// swc rewrites from "paths" (and any extensionless relative ones)
// the extension of the emitted file, or "/index.js" for directories.
resolveFully: true,
}
: {
type: 'commonjs',
},
jsc: {
target: 'es2021',
parser: {
syntax: 'typescript',
decorators: true,
dynamicImport: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
useDefineForClassFields: false,
},
keepClassNames: true,
baseUrl: resolveBaseUrl(tsOptions),
paths: tsOptions?.paths,
// swc drops import attributes by default, but Node needs
// `with { type: 'json' }` to import JSON from ES modules.
...(esm && { experimental: { keepImportAttributes: true } }),
},
minify: false,
swcrc: true,
},
cliOptions: {
outDir: tsOptions?.outDir ? convertPath(tsOptions.outDir) : 'dist',
filenames: [sourceRoot],
sync: false,
extensions: ['.js', '.ts'],
copyFiles: false,
includeDotfiles: false,
ignore: tsConfigExclude.length ? tsConfigExclude : undefined,
quiet: false,
watch: false,
stripLeadingPaths: shouldStripLeadingPaths(tsOptions?.rootDir, sourceRoot),
...builderOptions,
},
};
};
/**
* Picks the module format tsc would emit for the project's `.ts` files: ES
* modules when the package is `"type": "module"`, unless the tsconfig pins an
* older, non-ES module kind (e.g. `"module": "commonjs"`). Projects without
* `"type": "module"` keep CommonJS output.
*/
export function resolveSwcModuleType(tsOptions, esmPackage = isEsmProject()) {
if (!esmPackage) {
return 'commonjs';
}
const moduleKind = tsOptions?.module;
if (moduleKind !== undefined && moduleKind < ts.ModuleKind.ES2015) {
return 'commonjs';
}
return 'es6';
}
/**
* Without "baseUrl" (deprecated as of TypeScript 6), tsc resolves "paths"
* relative to the directory of the tsconfig that declares them, which the
* TypeScript parser exposes as "pathsBasePath". swc has no such fallback and
* panics when "paths" is set without "jsc.baseUrl", so pass that directory.
*/
function resolveBaseUrl(tsOptions) {
if (tsOptions?.baseUrl || !tsOptions?.paths) {
return tsOptions?.baseUrl;
}
const pathsBasePath = tsOptions.pathsBasePath;
return typeof pathsBasePath === 'string' ? pathsBasePath : undefined;
}
/**
* Mirrors tsc's output layout: tsc flattens the source directory out of the
* output when its (effective) rootDir is the source root, so swc must strip
* leading paths in the same cases — no rootDir configured (tsc infers the
* common source directory), or a rootDir that points at the source root
* (relative or resolved absolute). A rootDir above the source root (e.g. '.')
* keeps the source directory in the output, so nothing is stripped.
*
* "@swc/cli" strips only the first path segment, so a nested source root
* (e.g. "apps/api/src" in a monorepo) cannot be flattened: it would come out
* as "api/src/main.js". Keep the full source root instead, which is the
* "<outDir>/<sourceRoot>/<entryFile>" layout "nest start" looks for first.
*/
function shouldStripLeadingPaths(rootDir, sourceRoot) {
const normalizedSourceRoot = convertPath(sourceRoot)
.replace(/^\.\//, '')
.replace(/\/+$/, '');
if (normalizedSourceRoot.includes('/')) {
return false;
}
if (!rootDir) {
return true;
}
const normalizedRootDir = convertPath(rootDir).replace(/\/+$/, '');
return (normalizedRootDir === normalizedSourceRoot ||
normalizedRootDir.endsWith(`/${normalizedSourceRoot}`));
}
/**
* Converts Windows specific file paths to posix
* @param windowsPath
*/
function convertPath(windowsPath) {
return windowsPath
.replace(/^\\\\\?\\/, '')
.replace(/\\/g, '/')
.replace(/\/\/+/g, '/');
}