Skip to content

Commit

Permalink
Experimental feature for allowing importing Typescript files outside …
Browse files Browse the repository at this point in the history
…of the root directory (#22867)

This PR attempts to provide an option to allow importing TS/TSX from outside of the current Next.js project root directory. Although this goes against the design decision that no source code should be imported from outside of root and [might bring tons of issues](#19928 (comment)), it will still be helpful in some monorepo use cases.

This PR assumes that the external files are following the same language syntax rules and under the same tooling versions as the source code inside your project root. And it's also not allowed to enable the `baseUrl` feature in the external directory (as the project should only have 1 import base URL).

X-ref: #9474, #15569, #19928, #20374.
  • Loading branch information
shuding authored Mar 19, 2021
1 parent 79066ae commit 32c435d
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 1 deletion.
5 changes: 4 additions & 1 deletion packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,10 @@ export default async function getBaseWebpackConfig(
: []),
{
test: /\.(tsx|ts|js|mjs|jsx)$/,
include: [dir, ...babelIncludeRegexes],
...(config.experimental.externalDir
? // Allowing importing TS/TSX files from outside of the root dir.
{}
: { include: [dir, ...babelIncludeRegexes] }),
exclude: (excludePath: string) => {
if (babelIncludeRegexes.some((r) => r.test(excludePath))) {
return false
Expand Down
1 change: 1 addition & 0 deletions packages/next/next-server/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const defaultConfig: NextConfig = {
scrollRestoration: false,
scriptLoader: false,
stats: false,
externalDir: false,
},
future: {
strictPostcssConfiguration: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react'

export function World(): JSX.Element {
return <>World</>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
onDemandEntries: {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60,
},
experimental: {
externalDir: true,
},
}
16 changes: 16 additions & 0 deletions test/integration/typescript-external-dir/project/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'

import { World } from 'components/world'

// External
import { Counter } from '../../shared/components/counter'

export default function HelloPage(): JSX.Element {
return (
<div>
Hello <World />!
<br />
<Counter />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* 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 render the page with external TS/TSX dependencies', async () => {
const $ = await get$('/')
expect($('body').text()).toMatch(/Hello World!Counter: 0/)
})
})
})
20 changes: 20 additions & 0 deletions test/integration/typescript-external-dir/project/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { useState } from 'react'

import inc from '../libs/inc'

export function Counter(): JSX.Element {
const [x, setX] = useState(0)
return (
<button id="counter" onClick={() => setX(inc(x))}>
Counter: {x}
</button>
)
}
3 changes: 3 additions & 0 deletions test/integration/typescript-external-dir/shared/libs/inc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function inc(x: number) {
return x + 1
}
18 changes: 18 additions & 0 deletions test/integration/typescript-external-dir/shared/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"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
},
"include": ["components", "libs"]
}

0 comments on commit 32c435d

Please sign in to comment.