From 683db9a8ad2660c0de12dfb44ef6808a9726cb20 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Mon, 15 Aug 2022 15:33:25 +0200 Subject: [PATCH] Support tsconfig paths without baseurl (#34926) tsconfig behaves differently with `tsc` vs. Next.js. When `baseurl` is omitted, `paths` should be resolved against the tsconfig location. Consequentially, in this case the paths must start with `./`. This PR aligns Next.js behavior with `tsc` around `paths` without `baseurl`. Related: https://github.com/microsoft/TypeScript/pull/40101 --- packages/next/build/load-jsconfig.ts | 12 +++++- .../jsconfig-paths/test/index.test.js | 31 +++++++++++++- .../typescript-paths/test/index.test.js | 42 ++++++++++++++++++- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/next/build/load-jsconfig.ts b/packages/next/build/load-jsconfig.ts index bb99b55f518a6a3..aa775327fc94267 100644 --- a/packages/next/build/load-jsconfig.ts +++ b/packages/next/build/load-jsconfig.ts @@ -49,6 +49,7 @@ export default async function loadJsConfig( typeScriptPath && (await fileExists(tsConfigPath)) ) + let implicitBaseurl let jsConfig // jsconfig is a subset of tsconfig if (useTypeScript) { @@ -65,17 +66,24 @@ export default async function loadJsConfig( )) as typeof import('typescript') const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath, true) jsConfig = { compilerOptions: tsConfig.options } + implicitBaseurl = path.dirname(tsConfigPath) } const jsConfigPath = path.join(dir, 'jsconfig.json') if (!useTypeScript && (await fileExists(jsConfigPath))) { jsConfig = parseJsonFile(jsConfigPath) + implicitBaseurl = path.dirname(jsConfigPath) } let resolvedBaseUrl - if (jsConfig?.compilerOptions?.baseUrl) { - resolvedBaseUrl = path.resolve(dir, jsConfig.compilerOptions.baseUrl) + if (jsConfig) { + if (jsConfig.compilerOptions?.baseUrl) { + resolvedBaseUrl = path.resolve(dir, jsConfig.compilerOptions.baseUrl) + } else { + resolvedBaseUrl = implicitBaseurl + } } + return { useTypeScript, jsConfig, diff --git a/test/integration/jsconfig-paths/test/index.test.js b/test/integration/jsconfig-paths/test/index.test.js index 7037101d88735a7..8680963c1a014fa 100644 --- a/test/integration/jsconfig-paths/test/index.test.js +++ b/test/integration/jsconfig-paths/test/index.test.js @@ -3,6 +3,7 @@ import fs from 'fs-extra' import { join } from 'path' import cheerio from 'cheerio' +import * as path from 'path' import { renderViaHTTP, findPort, @@ -10,7 +11,9 @@ import { nextBuild, killApp, check, + File, } from 'next-test-utils' +import * as JSON5 from 'json5' const appDir = join(__dirname, '..') let appPort @@ -21,7 +24,7 @@ async function get$(path, query) { return cheerio.load(html) } -describe('TypeScript Features', () => { +function runTests() { describe('default behavior', () => { let output = '' @@ -141,4 +144,30 @@ describe('TypeScript Features', () => { ).toBe(false) }) }) +} + +describe('jsconfig paths', () => { + runTests() +}) + +const jsconfig = new File(path.resolve(__dirname, '../jsconfig.json')) + +describe('jsconfig paths without baseurl', () => { + beforeAll(() => { + const jsconfigContent = JSON5.parse(jsconfig.originalContent) + delete jsconfigContent.compilerOptions.baseUrl + jsconfigContent.compilerOptions.paths = { + '@c/*': ['./components/*'], + '@lib/*': ['./lib/a/*', './lib/b/*'], + '@mycomponent': ['./components/hello.js'], + '*': ['./node_modules/*'], + } + jsconfig.write(JSON.stringify(jsconfigContent, null, 2)) + }) + + afterAll(() => { + jsconfig.restore() + }) + + runTests() }) diff --git a/test/integration/typescript-paths/test/index.test.js b/test/integration/typescript-paths/test/index.test.js index f7100467a146201..aeb3c297dd62709 100644 --- a/test/integration/typescript-paths/test/index.test.js +++ b/test/integration/typescript-paths/test/index.test.js @@ -2,7 +2,15 @@ import { join } from 'path' import cheerio from 'cheerio' -import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils' +import * as path from 'path' +import { + renderViaHTTP, + findPort, + launchApp, + killApp, + File, +} from 'next-test-utils' +import * as JSON5 from 'json5' const appDir = join(__dirname, '..') let appPort @@ -13,7 +21,7 @@ async function get$(path, query) { return cheerio.load(html) } -describe('TypeScript Features', () => { +function runTests() { describe('default behavior', () => { beforeAll(async () => { appPort = await findPort() @@ -46,4 +54,34 @@ describe('TypeScript Features', () => { expect($('body').text()).toMatch(/Not aliased to d\.ts file/) }) }) +} + +describe('typescript paths', () => { + runTests() +}) + +const tsconfig = new File(path.resolve(__dirname, '../tsconfig.json')) + +describe('typescript paths without baseurl', () => { + beforeAll(async () => { + const tsconfigContent = JSON5.parse(tsconfig.originalContent) + delete tsconfigContent.compilerOptions.baseUrl + tsconfigContent.compilerOptions.paths = { + 'isomorphic-unfetch': ['./types/unfetch.d.ts'], + '@c/*': ['./components/*'], + '@lib/*': ['./lib/a/*', './lib/b/*'], + '@mycomponent': ['./components/hello.tsx'], + 'd-ts-alias': [ + './components/alias-to-d-ts.d.ts', + './components/alias-to-d-ts.tsx', + ], + } + tsconfig.write(JSON.stringify(tsconfigContent, null, 2)) + }) + + afterAll(() => { + tsconfig.restore() + }) + + runTests() })