diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index d4e3106196e3c..d3b2d2551b896 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -217,6 +217,22 @@ export default async function getBaseWebpackConfig( ? config.typescript?.ignoreDevErrors : config.typescript?.ignoreBuildErrors + let jsConfig + // jsconfig is a subset of tsconfig + if (useTypeScript) { + jsConfig = require(tsConfigPath) + } + + const jsConfigPath = path.join(dir, 'jsconfig.json') + if (!useTypeScript && (await fileExists(jsConfigPath))) { + jsConfig = require(jsConfigPath) + } + + let resolvedBaseUrl + if (jsConfig?.compilerOptions?.baseUrl) { + resolvedBaseUrl = path.resolve(dir, jsConfig.compilerOptions.baseUrl) + } + const resolveConfig = { // Disable .mjs for node_modules bundling extensions: isServer @@ -889,6 +905,11 @@ export default async function getBaseWebpackConfig( ].filter((Boolean as any) as ExcludesFalse), } + // Support tsconfig and jsconfig baseUrl + if (resolvedBaseUrl) { + webpackConfig.resolve?.modules?.push(resolvedBaseUrl) + } + webpackConfig = await buildConfiguration(webpackConfig, { rootDirectory: dir, customAppFile, diff --git a/test/integration/jsconfig-baseurl/components/world.js b/test/integration/jsconfig-baseurl/components/world.js new file mode 100644 index 0000000000000..5a93b9838685c --- /dev/null +++ b/test/integration/jsconfig-baseurl/components/world.js @@ -0,0 +1,5 @@ +import React from 'react' + +export function World() { + return <>World +} diff --git a/test/integration/jsconfig-baseurl/jsconfig.json b/test/integration/jsconfig-baseurl/jsconfig.json new file mode 100644 index 0000000000000..36aa1a4dc28f1 --- /dev/null +++ b/test/integration/jsconfig-baseurl/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "baseUrl": "." + } +} diff --git a/test/integration/jsconfig-baseurl/next.config.js b/test/integration/jsconfig-baseurl/next.config.js new file mode 100644 index 0000000000000..cc17cf48c578f --- /dev/null +++ b/test/integration/jsconfig-baseurl/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + onDemandEntries: { + // Make sure entries are not getting disposed. + maxInactiveAge: 1000 * 60 * 60, + }, +} diff --git a/test/integration/jsconfig-baseurl/pages/hello.js b/test/integration/jsconfig-baseurl/pages/hello.js new file mode 100644 index 0000000000000..430fdc247a864 --- /dev/null +++ b/test/integration/jsconfig-baseurl/pages/hello.js @@ -0,0 +1,9 @@ +import React from 'react' +import { World } from 'components/world' +export default function HelloPage() { + return ( +
+ +
+ ) +} diff --git a/test/integration/jsconfig-baseurl/test/index.test.js b/test/integration/jsconfig-baseurl/test/index.test.js new file mode 100644 index 0000000000000..ec516f7766ac8 --- /dev/null +++ b/test/integration/jsconfig-baseurl/test/index.test.js @@ -0,0 +1,31 @@ +/* eslint-env jest */ +/* global jasmine */ +import { join } from 'path' +import cheerio from 'cheerio' +import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 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 render the page', async () => { + const $ = await get$('/hello') + expect($('body').text()).toMatch(/World/) + }) + }) +}) diff --git a/test/integration/typescript-baseurl/components/world.tsx b/test/integration/typescript-baseurl/components/world.tsx new file mode 100644 index 0000000000000..d7d1f66258c77 --- /dev/null +++ b/test/integration/typescript-baseurl/components/world.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export function World(): JSX.Element { + return <>World +} diff --git a/test/integration/typescript-baseurl/next.config.js b/test/integration/typescript-baseurl/next.config.js new file mode 100644 index 0000000000000..cc17cf48c578f --- /dev/null +++ b/test/integration/typescript-baseurl/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + onDemandEntries: { + // Make sure entries are not getting disposed. + maxInactiveAge: 1000 * 60 * 60, + }, +} diff --git a/test/integration/typescript-baseurl/pages/hello.tsx b/test/integration/typescript-baseurl/pages/hello.tsx new file mode 100644 index 0000000000000..1129fe708833b --- /dev/null +++ b/test/integration/typescript-baseurl/pages/hello.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import { World } from 'components/world' +export default function HelloPage(): JSX.Element { + return ( +
+ +
+ ) +} diff --git a/test/integration/typescript-baseurl/test/index.test.js b/test/integration/typescript-baseurl/test/index.test.js new file mode 100644 index 0000000000000..ec516f7766ac8 --- /dev/null +++ b/test/integration/typescript-baseurl/test/index.test.js @@ -0,0 +1,31 @@ +/* eslint-env jest */ +/* global jasmine */ +import { join } from 'path' +import cheerio from 'cheerio' +import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 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 render the page', async () => { + const $ = await get$('/hello') + expect($('body').text()).toMatch(/World/) + }) + }) +}) diff --git a/test/integration/typescript-baseurl/tsconfig.json b/test/integration/typescript-baseurl/tsconfig.json new file mode 100644 index 0000000000000..dce2837cb1bcc --- /dev/null +++ b/test/integration/typescript-baseurl/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "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 + }, + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "components", "pages"] +}