Skip to content

Commit 6e199f9

Browse files
committed
improves baseUrl resolution in typescript monorepos
Typescript configuration can inherit from files above cwd in the filesystem. If a baseUrl was declared in such a file, it would not be picked up by the webpack config. This would force users to use the next-transpile-module and duplicate configuration with unintuitive path relations (see #13197 for a detailed analysis) If baseUrl is resolved it should be used instead of dir as the root include for babel-resolve-loader.
1 parent 279da63 commit 6e199f9

17 files changed

+182
-1
lines changed

packages/next/build/webpack-config.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '../lib/constants'
1616
import { fileExists } from '../lib/file-exists'
1717
import { resolveRequest } from '../lib/resolve-request'
18+
import { getTypeScriptConfiguration } from '../lib/typescript/getTypeScriptConfiguration'
1819
import {
1920
CLIENT_STATIC_FILES_RUNTIME_MAIN,
2021
CLIENT_STATIC_FILES_RUNTIME_POLYFILLS,
@@ -261,7 +262,9 @@ export default async function getBaseWebpackConfig(
261262
let jsConfig
262263
// jsconfig is a subset of tsconfig
263264
if (useTypeScript) {
264-
jsConfig = parseJsonFile(tsConfigPath)
265+
const ts = (await import(typeScriptPath)) as typeof import('typescript')
266+
const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
267+
jsConfig = { compilerOptions: tsConfig.options }
265268
}
266269

267270
const jsConfigPath = path.join(dir, 'jsconfig.json')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//this line uses typescript specific syntax
2+
//to demonstrate that transpilation occurs
3+
export type API = () => string
4+
const api: API = () => 'Hello from a'
5+
export default api
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { API } from '@lib/api'
2+
3+
const b: API = () => 'Hello from b'
4+
export default b
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { API } from '@lib/api'
2+
3+
const b: API = () => 'Hello from only b'
4+
export default b
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => any
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const Named = () => {
2+
return <>Not aliased to d.ts file</>
3+
}
4+
export default Named
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
3+
export function Hello() {
4+
return <>Hello</>
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
3+
export function World(): JSX.Element {
4+
return <>World</>
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const path = require('path')
2+
module.exports = {
3+
webpack: function (config, { defaultLoaders }) {
4+
console.log('context: ', config.context)
5+
const resolvedBaseUrl = path.resolve(config.context, '../../')
6+
console.log('resolvedBaseUrl: ', resolvedBaseUrl)
7+
config.module.rules = [
8+
...config.module.rules,
9+
{
10+
test: /\.(tsx|ts|js|mjs|jsx)$/,
11+
include: [resolvedBaseUrl],
12+
use: defaultLoaders.babel,
13+
exclude: (excludePath) => {
14+
return /node_modules/.test(excludePath)
15+
},
16+
},
17+
]
18+
return config
19+
},
20+
21+
onDemandEntries: {
22+
// Make sure entries are not getting disposed.
23+
maxInactiveAge: 1000 * 60 * 60,
24+
},
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react'
2+
import NotAliasedToDTS from 'd-ts-alias'
3+
4+
export default function HelloPage(): JSX.Element {
5+
return (
6+
<div>
7+
<NotAliasedToDTS />
8+
</div>
9+
)
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react'
2+
import { World } from '@c/world'
3+
export default function HelloPage(): JSX.Element {
4+
return (
5+
<div>
6+
<World />
7+
</div>
8+
)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
import api from '@lib/b-only'
3+
export default function ResolveOrder(): JSX.Element {
4+
return <div>{api()}</div>
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react'
2+
import api from '@lib/api'
3+
export default function ResolveOrder(): JSX.Element {
4+
return <div>{api()}</div>
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Hello } from '@mycomponent'
2+
3+
export default function SingleAlias() {
4+
return (
5+
<div>
6+
<Hello />
7+
</div>
8+
)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-env jest */
2+
3+
import { join } from 'path'
4+
import cheerio from 'cheerio'
5+
import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils'
6+
7+
jest.setTimeout(1000 * 60 * 2)
8+
9+
const appDir = join(__dirname, '..')
10+
let appPort
11+
let app
12+
13+
async function get$(path, query) {
14+
const html = await renderViaHTTP(appPort, path, query)
15+
return cheerio.load(html)
16+
}
17+
18+
describe('TypeScript Features', () => {
19+
describe('default behavior', () => {
20+
beforeAll(async () => {
21+
appPort = await findPort()
22+
app = await launchApp(appDir, appPort, {})
23+
})
24+
afterAll(() => killApp(app))
25+
26+
it('should alias components', async () => {
27+
const $ = await get$('/basic-alias')
28+
expect($('body').text()).toMatch(/World/)
29+
})
30+
31+
it('should resolve the first item in the array first', async () => {
32+
const $ = await get$('/resolve-order')
33+
expect($('body').text()).toMatch(/Hello from a/)
34+
})
35+
36+
it('should resolve the second item in as a fallback', async () => {
37+
const $ = await get$('/resolve-fallback')
38+
expect($('body').text()).toMatch(/Hello from only b/)
39+
})
40+
41+
it('should resolve a single matching alias', async () => {
42+
const $ = await get$('/single-alias')
43+
expect($('body').text()).toMatch(/Hello/)
44+
})
45+
46+
it('should not resolve to .d.ts files', async () => {
47+
const $ = await get$('/alias-to-d-ts')
48+
expect($('body').text()).toMatch(/Not aliased to d\.ts file/)
49+
})
50+
})
51+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* This is a single line comment to check if that works */
2+
{
3+
"extends": "../../tsconfig.json",
4+
"exclude": ["node_modules"],
5+
"include": ["next-env.d.ts", "components", "pages"]
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* This is a single line comment to check if that works */
2+
{
3+
"compilerOptions": {
4+
"baseUrl": ".",
5+
"paths": {
6+
"isomorphic-unfetch": ["packages/www/types/unfetch.d.ts"],
7+
"@c/*": ["packages/www/components/*"],
8+
"@lib/*": ["packages/lib/a/*", "packages/lib/b/*"],
9+
"@mycomponent": ["packages/www/components/hello.tsx"],
10+
"d-ts-alias": [
11+
"packages/www/components/alias-to-d-ts.d.ts",
12+
"packages/www/components/alias-to-d-ts.tsx"
13+
]
14+
// This is a single line comment to check if that works
15+
},
16+
"esModuleInterop": true,
17+
"module": "esnext",
18+
"jsx": "preserve",
19+
"target": "es5",
20+
"lib": ["dom", "dom.iterable", "esnext"],
21+
"allowJs": true,
22+
"skipLibCheck": true,
23+
"strict": true,
24+
"forceConsistentCasingInFileNames": true,
25+
"noEmit": true,
26+
"moduleResolution": "node",
27+
"resolveJsonModule": true,
28+
"isolatedModules": true
29+
}
30+
}

0 commit comments

Comments
 (0)