diff --git a/src/utils/get-rollup-configs.ts b/src/utils/get-rollup-configs.ts index b007192..aeb2c40 100644 --- a/src/utils/get-rollup-configs.ts +++ b/src/utils/get-rollup-configs.ts @@ -15,6 +15,7 @@ import { esbuildTransform, esbuildMinify } from './rollup-plugins/esbuild.js'; import { externalizeNodeBuiltins } from './rollup-plugins/externalize-node-builtins.js'; import { patchBinary } from './rollup-plugins/patch-binary.js'; import { resolveTypescriptMjsCts } from './rollup-plugins/resolve-typescript-mjs-cjs.js'; +import { resolveTsconfigPaths } from './rollup-plugins/resolve-tsconfig-paths.js'; import { stripHashbang } from './rollup-plugins/strip-hashbang.js'; import { getExternalDependencies } from './parse-package-json/get-external-dependencies.js'; @@ -40,6 +41,7 @@ type Output = OutputOptions[] & Record; const getConfig = { type: async ( options: Options, + tsconfig: TsConfigResult | null, ) => { const dts = await import('rollup-plugin-dts'); @@ -48,6 +50,11 @@ const getConfig = { preserveEntrySignatures: 'strict' as const, plugins: [ externalizeNodeBuiltins(options), + ...( + tsconfig + ? [resolveTsconfigPaths(tsconfig)] + : [] + ), resolveTypescriptMjsCts(), dts.default({ respectExternal: true, @@ -90,6 +97,11 @@ const getConfig = { preserveEntrySignatures: 'strict' as const, plugins: [ externalizeNodeBuiltins(options), + ...( + tsconfig + ? [resolveTsconfigPaths(tsconfig)] + : [] + ), resolveTypescriptMjsCts(), alias({ entries: aliases, @@ -170,7 +182,7 @@ export const getRollupConfigs = async ( let config = configs.type; if (!config) { - config = await getConfig.type(flags); + config = await getConfig.type(flags, tsconfig); config.external = externalTypeDependencies; configs.type = config; } diff --git a/src/utils/rollup-plugins/resolve-tsconfig-paths.ts b/src/utils/rollup-plugins/resolve-tsconfig-paths.ts new file mode 100644 index 0000000..7714cbf --- /dev/null +++ b/src/utils/rollup-plugins/resolve-tsconfig-paths.ts @@ -0,0 +1,51 @@ +import { createPathsMatcher, type TsConfigResult } from 'get-tsconfig'; +import type { Plugin } from 'rollup'; + +const name = 'resolve-tsconfig-paths'; + +const isRelative = (filePath: string) => filePath[0] === '.'; +const isAbsolute = (filePath: string) => filePath[0] === '/' || /^[\s\S]:/.test(filePath); +const isImports = (filePath: string) => filePath[0] === '#'; + +export const resolveTsconfigPaths = ( + tsconfig: TsConfigResult, +): Plugin => { + const pathsMatcher = createPathsMatcher(tsconfig); + if (!pathsMatcher) { + return { + name, + }; + } + + return { + name, + async resolveId(id, importer, options) { + if ( + !importer + || isRelative(id) + || isAbsolute(id) + || isImports(id) + || id.startsWith('\0') + ) { + return null; + } + + const possiblePaths = pathsMatcher(id); + for (const tryPath of possiblePaths) { + const resolved = await this.resolve( + tryPath, + importer, + { + skipSelf: true, + ...options, + }, + ); + if (resolved) { + return resolved; + } + } + + return null; + }, + }; +}; diff --git a/tests/specs/builds/typescript.ts b/tests/specs/builds/typescript.ts index 943a869..8c5dbff 100644 --- a/tests/specs/builds/typescript.ts +++ b/tests/specs/builds/typescript.ts @@ -1,7 +1,8 @@ import { testSuite, expect } from 'manten'; import { createFixture } from 'fs-fixture'; +import { outdent } from 'outdent'; import { pkgroll } from '../../utils.js'; -import { createPackageJson, createTsconfigJson } from '../../fixtures.js'; +import { createPackageJson, createTsconfigJson, installTypeScript } from '../../fixtures.js'; export default testSuite(({ describe }, nodePath: string) => { describe('TypeScript', ({ test }) => { @@ -52,6 +53,98 @@ export default testSuite(({ describe }, nodePath: string) => { const content = await fixture.readFile('dist/index.js', 'utf8'); expect(content).toBe('console.log(1);\n'); }); + + test('resolves baseUrl', async () => { + await using fixture = await createFixture({ + src: { + 'index.ts': outdent` + import { qux } from 'dir/exportee.js'; + import { quux } from 'dir/deep/exportee.js'; + console.log(qux, quux); + `, + 'importee.ts': 'export const foo = \'foo\'', + dir: { + 'importee.ts': 'export const bar = \'bar\'', + 'exportee.ts': outdent` + import { foo } from 'importee.js'; + import { baz } from 'dir/deep/importee.js'; + export const qux = foo + baz;`, + deep: { + 'importee.ts': 'export const baz = \'baz\'', + 'exportee.ts': outdent` + import { foo } from 'importee.js'; + import { bar } from 'dir/importee.js'; + import { baz } from 'dir/deep/importee.js'; + export const quux = foo + bar + baz;`, + }, + }, + }, + 'package.json': createPackageJson({ + exports: './dist/index.mjs', + }), + 'tsconfig.json': createTsconfigJson({ + compilerOptions: { + baseUrl: './src', + }, + }), + }); + + const pkgrollProcess = await pkgroll(['--minify'], { + cwd: fixture.path, + nodePath, + }); + + expect(pkgrollProcess.exitCode).toBe(0); + expect(pkgrollProcess.stderr).toBe(''); + + const content = await fixture.readFile('dist/index.mjs', 'utf8'); + expect(content).toMatch('"foo"'); + expect(content).toMatch('"bar"'); + expect(content).toMatch('"baz"'); + }); + + test('resolves paths', async () => { + await using fixture = await createFixture({ + ...installTypeScript, + src: { + 'index.ts': outdent` + import * as foo from '@foo/index.js'; + import { bar } from '~bar'; + export { foo, bar };`, + foo: { + 'index.ts': 'export { a } from \'@foo/a.js\';', + 'a.ts': 'export const a = \'a\';', + }, + 'bar/index.ts': 'export const bar = \'bar\';', + }, + 'package.json': createPackageJson({ + exports: { + types: './dist/index.d.mts', + default: './dist/index.mjs', + }, + }), + 'tsconfig.json': createTsconfigJson({ + compilerOptions: { + paths: { + '@foo/*': ['./src/foo/*'], + '~bar': ['./src/bar/index.ts'], + }, + }, + }), + }); + + const pkgrollProcess = await pkgroll(['--minify'], { + cwd: fixture.path, + nodePath, + }); + + expect(pkgrollProcess.exitCode).toBe(0); + expect(pkgrollProcess.stderr).toBe(''); + + const content = await fixture.readFile('dist/index.mjs', 'utf8'); + expect(content).toMatch('"a"'); + expect(content).toMatch('"bar"'); + }); }); describe('custom tsconfig.json path', ({ test }) => {