From d5500e5302c0bcb04bc0468c7b0d9a9846600afb Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Fri, 29 May 2020 11:41:27 +0200 Subject: [PATCH] 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. --- packages/next/build/webpack-config.ts | 9 +++- .../packages/lib/a/api.ts | 1 + .../packages/lib/b/api.ts | 1 + .../packages/lib/b/b-only.ts | 1 + .../www/components/alias-to-d-ts.d.ts | 1 + .../packages/www/components/alias-to-d-ts.tsx | 3 ++ .../packages/www/components/hello.tsx | 5 ++ .../packages/www/components/world.tsx | 5 ++ .../packages/www/next.config.js | 9 ++++ .../packages/www/pages/alias-to-d-ts.tsx | 10 ++++ .../packages/www/pages/basic-alias.tsx | 9 ++++ .../packages/www/pages/resolve-fallback.tsx | 5 ++ .../packages/www/pages/resolve-order.tsx | 5 ++ .../packages/www/pages/single-alias.tsx | 9 ++++ .../packages/www/test/index.test.js | 51 +++++++++++++++++++ .../packages/www/tsconfig.json | 6 +++ .../typescript-workspaces-paths/tsconfig.json | 30 +++++++++++ 17 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 test/integration/typescript-workspaces-paths/packages/lib/a/api.ts create mode 100644 test/integration/typescript-workspaces-paths/packages/lib/b/api.ts create mode 100644 test/integration/typescript-workspaces-paths/packages/lib/b/b-only.ts create mode 100644 test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.d.ts create mode 100644 test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/components/hello.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/components/world.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/next.config.js create mode 100644 test/integration/typescript-workspaces-paths/packages/www/pages/alias-to-d-ts.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/pages/basic-alias.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/pages/resolve-fallback.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/pages/resolve-order.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/pages/single-alias.tsx create mode 100644 test/integration/typescript-workspaces-paths/packages/www/test/index.test.js create mode 100644 test/integration/typescript-workspaces-paths/packages/www/tsconfig.json create mode 100644 test/integration/typescript-workspaces-paths/tsconfig.json diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 6b97311ef164f4..ec1cf3d76af9e0 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -52,6 +52,7 @@ import WebpackConformancePlugin, { } from './webpack/plugins/webpack-conformance-plugin' import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin' import { codeFrameColumns } from '@babel/code-frame' +import { getTypeScriptConfiguration } from '../lib/typescript/getTypeScriptConfiguration' type ExcludesFalse = (x: T | false) => x is T @@ -250,7 +251,9 @@ export default async function getBaseWebpackConfig( let jsConfig // jsconfig is a subset of tsconfig if (useTypeScript) { - jsConfig = parseJsonFile(tsConfigPath) + const ts = (await import(typeScriptPath)) as typeof import('typescript') + const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath) + jsConfig = { compilerOptions: tsConfig.options } } const jsConfigPath = path.join(dir, 'jsconfig.json') @@ -734,7 +737,9 @@ export default async function getBaseWebpackConfig( rules: [ { test: /\.(tsx|ts|js|mjs|jsx)$/, - include: [dir, ...babelIncludeRegexes], + include: resolvedBaseUrl + ? [resolvedBaseUrl, dir, ...babelIncludeRegexes] + : [dir, ...babelIncludeRegexes], exclude: (excludePath: string) => { if (babelIncludeRegexes.some((r) => r.test(excludePath))) { return false diff --git a/test/integration/typescript-workspaces-paths/packages/lib/a/api.ts b/test/integration/typescript-workspaces-paths/packages/lib/a/api.ts new file mode 100644 index 00000000000000..a27fc1b9498a40 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/lib/a/api.ts @@ -0,0 +1 @@ +export default () => 'Hello from a' diff --git a/test/integration/typescript-workspaces-paths/packages/lib/b/api.ts b/test/integration/typescript-workspaces-paths/packages/lib/b/api.ts new file mode 100644 index 00000000000000..24ee786bd1a95b --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/lib/b/api.ts @@ -0,0 +1 @@ +export default () => 'Hello from b' diff --git a/test/integration/typescript-workspaces-paths/packages/lib/b/b-only.ts b/test/integration/typescript-workspaces-paths/packages/lib/b/b-only.ts new file mode 100644 index 00000000000000..0f1c1ce795f53d --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/lib/b/b-only.ts @@ -0,0 +1 @@ +export default () => 'Hello from only b' diff --git a/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.d.ts b/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.d.ts new file mode 100644 index 00000000000000..02906bf4031208 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.d.ts @@ -0,0 +1 @@ +export default () => any diff --git a/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.tsx b/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.tsx new file mode 100644 index 00000000000000..6ac56231d0bf0f --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/components/alias-to-d-ts.tsx @@ -0,0 +1,3 @@ +export default () => { + return <>Not aliased to d.ts file +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/components/hello.tsx b/test/integration/typescript-workspaces-paths/packages/www/components/hello.tsx new file mode 100644 index 00000000000000..49c8de26f6c504 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/components/hello.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export function Hello() { + return <>Hello +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/components/world.tsx b/test/integration/typescript-workspaces-paths/packages/www/components/world.tsx new file mode 100644 index 00000000000000..d7d1f66258c778 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/components/world.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export function World(): JSX.Element { + return <>World +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/next.config.js b/test/integration/typescript-workspaces-paths/packages/www/next.config.js new file mode 100644 index 00000000000000..727ef24dc943a4 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/next.config.js @@ -0,0 +1,9 @@ +module.exports = { + webpack: function (config) { + return config + }, + onDemandEntries: { + // Make sure entries are not getting disposed. + maxInactiveAge: 1000 * 60 * 60, + }, +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/pages/alias-to-d-ts.tsx b/test/integration/typescript-workspaces-paths/packages/www/pages/alias-to-d-ts.tsx new file mode 100644 index 00000000000000..d24ac047d2f730 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/pages/alias-to-d-ts.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import NotAliasedToDTS from 'd-ts-alias' + +export default function HelloPage(): JSX.Element { + return ( +
+ +
+ ) +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/pages/basic-alias.tsx b/test/integration/typescript-workspaces-paths/packages/www/pages/basic-alias.tsx new file mode 100644 index 00000000000000..f9b23a18297029 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/pages/basic-alias.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import { World } from '@c/world' +export default function HelloPage(): JSX.Element { + return ( +
+ +
+ ) +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-fallback.tsx b/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-fallback.tsx new file mode 100644 index 00000000000000..8b1dc93cd6bf4d --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-fallback.tsx @@ -0,0 +1,5 @@ +import React from 'react' +import api from '@lib/b-only' +export default function ResolveOrder(): JSX.Element { + return
{api()}
+} diff --git a/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-order.tsx b/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-order.tsx new file mode 100644 index 00000000000000..dd83cd62e8a8a3 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/pages/resolve-order.tsx @@ -0,0 +1,5 @@ +import React from 'react' +import api from '@lib/api' +export default function ResolveOrder(): JSX.Element { + return
{api()}
+} diff --git a/test/integration/typescript-workspaces-paths/packages/www/pages/single-alias.tsx b/test/integration/typescript-workspaces-paths/packages/www/pages/single-alias.tsx new file mode 100644 index 00000000000000..df2031b90ae62a --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/pages/single-alias.tsx @@ -0,0 +1,9 @@ +import { Hello } from '@mycomponent' + +export default function SingleAlias() { + return ( +
+ +
+ ) +} diff --git a/test/integration/typescript-workspaces-paths/packages/www/test/index.test.js b/test/integration/typescript-workspaces-paths/packages/www/test/index.test.js new file mode 100644 index 00000000000000..30116ff3264fe5 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/test/index.test.js @@ -0,0 +1,51 @@ +/* eslint-env jest */ + +import { join } from 'path' +import cheerio from 'cheerio' +import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '..') +let appPort +let app + +async function get$(path, query) { + const html = await renderViaHTTP(appPort, path, query) + return cheerio.load(html) +} + +describe('TypeScript Features', () => { + describe('default behavior', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort, {}) + }) + afterAll(() => killApp(app)) + + it('should alias components', async () => { + const $ = await get$('/basic-alias') + expect($('body').text()).toMatch(/World/) + }) + + it('should resolve the first item in the array first', async () => { + const $ = await get$('/resolve-order') + expect($('body').text()).toMatch(/Hello from a/) + }) + + it('should resolve the second item in as a fallback', async () => { + const $ = await get$('/resolve-fallback') + expect($('body').text()).toMatch(/Hello from only b/) + }) + + it('should resolve a single matching alias', async () => { + const $ = await get$('/single-alias') + expect($('body').text()).toMatch(/Hello/) + }) + + it('should not resolve to .d.ts files', async () => { + const $ = await get$('/alias-to-d-ts') + expect($('body').text()).toMatch(/Not aliased to d\.ts file/) + }) + }) +}) diff --git a/test/integration/typescript-workspaces-paths/packages/www/tsconfig.json b/test/integration/typescript-workspaces-paths/packages/www/tsconfig.json new file mode 100644 index 00000000000000..72435f3401f720 --- /dev/null +++ b/test/integration/typescript-workspaces-paths/packages/www/tsconfig.json @@ -0,0 +1,6 @@ +/* This is a single line comment to check if that works */ +{ + "extends": "../../tsconfig.json", + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "components", "pages"] +} diff --git a/test/integration/typescript-workspaces-paths/tsconfig.json b/test/integration/typescript-workspaces-paths/tsconfig.json new file mode 100644 index 00000000000000..2be4d1bba03e6d --- /dev/null +++ b/test/integration/typescript-workspaces-paths/tsconfig.json @@ -0,0 +1,30 @@ +/* This is a single line comment to check if that works */ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "isomorphic-unfetch": ["packages/www/types/unfetch.d.ts"], + "@c/*": ["packages/www/components/*"], + "@lib/*": ["packages/lib/a/*", "packages/lib/b/*"], + "@mycomponent": ["packages/www/components/hello.tsx"], + "d-ts-alias": [ + "packages/www/components/alias-to-d-ts.d.ts", + "packages/www/components/alias-to-d-ts.tsx" + ] + // This is a single line comment to check if that works + }, + "esModuleInterop": true, + "module": "esnext", + "jsx": "preserve", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true + } +}