diff --git a/packages/create-next-app/helpers/install.ts b/packages/create-next-app/helpers/install.ts
index 8a36345297acf88..4cac7d9747f5d74 100644
--- a/packages/create-next-app/helpers/install.ts
+++ b/packages/create-next-app/helpers/install.ts
@@ -95,7 +95,14 @@ export function install(
*/
const child = spawn(command, args, {
stdio: 'inherit',
- env: { ...process.env, ADBLOCK: '1', DISABLE_OPENCOLLECTIVE: '1' },
+ env: {
+ ...process.env,
+ ADBLOCK: '1',
+ // we set NODE_ENV to development as pnpm skips dev
+ // dependencies when production
+ NODE_ENV: 'development',
+ DISABLE_OPENCOLLECTIVE: '1',
+ },
})
child.on('close', (code) => {
if (code !== 0) {
diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts
index e07653513653891..5b1980ef297599d 100644
--- a/packages/next/build/index.ts
+++ b/packages/next/build/index.ts
@@ -187,14 +187,14 @@ function verifyTypeScriptSetup(
typeCheckWorker.getStderr().pipe(process.stderr)
return typeCheckWorker
- .verifyTypeScriptSetup(
+ .verifyTypeScriptSetup({
dir,
intentDirs,
typeCheckPreflight,
tsconfigPath,
disableStaticImages,
- cacheDir
- )
+ cacheDir,
+ })
.then((result) => {
typeCheckWorker.end()
return result
diff --git a/packages/next/build/load-jsconfig.ts b/packages/next/build/load-jsconfig.ts
index aa775327fc94267..7dac1bab01bb1f8 100644
--- a/packages/next/build/load-jsconfig.ts
+++ b/packages/next/build/load-jsconfig.ts
@@ -5,6 +5,7 @@ import * as Log from './output/log'
import { getTypeScriptConfiguration } from '../lib/typescript/getTypeScriptConfiguration'
import { readFileSync } from 'fs'
import isError from '../lib/is-error'
+import { hasNecessaryDependencies } from '../lib/has-necessary-dependencies'
let TSCONFIG_WARNED = false
@@ -42,7 +43,14 @@ export default async function loadJsConfig(
) {
let typeScriptPath: string | undefined
try {
- typeScriptPath = require.resolve('typescript', { paths: [dir] })
+ const deps = await hasNecessaryDependencies(dir, [
+ {
+ pkg: 'typescript',
+ file: 'typescript/lib/typescript.js',
+ exportsRestrict: true,
+ },
+ ])
+ typeScriptPath = deps.resolved.get('typescript')
} catch (_) {}
const tsConfigPath = path.join(dir, config.typescript.tsconfigPath)
const useTypeScript = Boolean(
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 646fb93e2eca691..581cec0af01ee34 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -531,10 +531,7 @@ export default async function getBaseWebpackConfig(
const isClient = compilerType === COMPILER_NAMES.client
const isEdgeServer = compilerType === COMPILER_NAMES.edgeServer
const isNodeServer = compilerType === COMPILER_NAMES.server
- const { useTypeScript, jsConfig, resolvedBaseUrl } = await loadJsConfig(
- dir,
- config
- )
+ const { jsConfig, resolvedBaseUrl } = await loadJsConfig(dir, config)
const supportedBrowsers = await getSupportedBrowsers(dir, dev, config)
@@ -832,22 +829,8 @@ export default async function getBaseWebpackConfig(
const resolveConfig = {
// Disable .mjs for node_modules bundling
extensions: isNodeServer
- ? [
- '.js',
- '.mjs',
- ...(useTypeScript ? ['.tsx', '.ts'] : []),
- '.jsx',
- '.json',
- '.wasm',
- ]
- : [
- '.mjs',
- '.js',
- ...(useTypeScript ? ['.tsx', '.ts'] : []),
- '.jsx',
- '.json',
- '.wasm',
- ],
+ ? ['.js', '.mjs', '.tsx', '.ts', '.jsx', '.json', '.wasm']
+ : ['.mjs', '.js', '.tsx', '.ts', '.jsx', '.json', '.wasm'],
modules: [
'node_modules',
...nodePathList, // Support for NODE_PATH environment variable
@@ -1831,11 +1814,14 @@ export default async function getBaseWebpackConfig(
webpackConfig.resolve?.modules?.push(resolvedBaseUrl)
}
- if (jsConfig?.compilerOptions?.paths && resolvedBaseUrl) {
- webpackConfig.resolve?.plugins?.unshift(
- new JsConfigPathsPlugin(jsConfig.compilerOptions.paths, resolvedBaseUrl)
+ // allows add JsConfigPathsPlugin to allow hot-reloading
+ // if the config is added/removed
+ webpackConfig.resolve?.plugins?.unshift(
+ new JsConfigPathsPlugin(
+ jsConfig?.compilerOptions?.paths || {},
+ resolvedBaseUrl || dir
)
- }
+ )
const webpack5Config = webpackConfig as webpack.Configuration
diff --git a/packages/next/build/webpack/plugins/jsconfig-paths-plugin.ts b/packages/next/build/webpack/plugins/jsconfig-paths-plugin.ts
index 287a07ffb7821ef..25b73740cdd4ce3 100644
--- a/packages/next/build/webpack/plugins/jsconfig-paths-plugin.ts
+++ b/packages/next/build/webpack/plugins/jsconfig-paths-plugin.ts
@@ -169,23 +169,16 @@ type Paths = { [match: string]: string[] }
export class JsConfigPathsPlugin implements webpack.ResolvePluginInstance {
paths: Paths
resolvedBaseUrl: string
+ jsConfigPlugin: true
+
constructor(paths: Paths, resolvedBaseUrl: string) {
this.paths = paths
this.resolvedBaseUrl = resolvedBaseUrl
+ this.jsConfigPlugin = true
log('tsconfig.json or jsconfig.json paths: %O', paths)
log('resolved baseUrl: %s', resolvedBaseUrl)
}
apply(resolver: any) {
- const paths = this.paths
- const pathsKeys = Object.keys(paths)
-
- // If no aliases are added bail out
- if (pathsKeys.length === 0) {
- log('paths are empty, bailing out')
- return
- }
-
- const baseDirectory = this.resolvedBaseUrl
const target = resolver.ensureHook('resolve')
resolver
.getHook('described-resolve')
@@ -196,6 +189,15 @@ export class JsConfigPathsPlugin implements webpack.ResolvePluginInstance {
resolveContext: any,
callback: (err?: any, result?: any) => void
) => {
+ const paths = this.paths
+ const pathsKeys = Object.keys(paths)
+
+ // If no aliases are added bail out
+ if (pathsKeys.length === 0) {
+ log('paths are empty, bailing out')
+ return callback()
+ }
+
const moduleName = request.request
// Exclude node_modules from paths support (speeds up resolving)
@@ -246,7 +248,7 @@ export class JsConfigPathsPlugin implements webpack.ResolvePluginInstance {
// try next path candidate
return pathCallback()
}
- const candidate = path.join(baseDirectory, curPath)
+ const candidate = path.join(this.resolvedBaseUrl, curPath)
const obj = Object.assign({}, request, {
request: candidate,
})
diff --git a/packages/next/lib/has-necessary-dependencies.ts b/packages/next/lib/has-necessary-dependencies.ts
index 10c3bd67f92d37e..5b81a859d2acf37 100644
--- a/packages/next/lib/has-necessary-dependencies.ts
+++ b/packages/next/lib/has-necessary-dependencies.ts
@@ -1,5 +1,7 @@
-import { existsSync } from 'fs'
-import { join, relative } from 'path'
+import { promises as fs } from 'fs'
+import { fileExists } from './file-exists'
+import { resolveFrom } from './resolve-from'
+import { dirname, join, relative } from 'path'
export interface MissingDependency {
file: string
@@ -17,31 +19,36 @@ export async function hasNecessaryDependencies(
requiredPackages: MissingDependency[]
): Promise {JSON.stringify(firstData)}
{JSON.stringify(secondData)}
` + )}` + ) + + expect(await hasRedbox(browser, true)).toBe(true) + expect(await getRedboxSource(browser)).toContain('"@lib/second-data"') + + await next.patchFile( + tsConfigFile, + JSON.stringify( + { + ...parsedTsConfig, + compilerOptions: { + ...parsedTsConfig.compilerOptions, + paths: { + ...parsedTsConfig.compilerOptions.paths, + '@lib/*': ['lib/first-lib/*', 'lib/second-lib/*'], + }, + }, + }, + null, + 2 + ) + ) + + expect(await hasRedbox(browser, false)).toBe(false) + + const html2 = await browser.eval('document.documentElement.innerHTML') + expect(html2).toContain('first button') + expect(html2).toContain('second button') + expect(html2).toContain('first-data') + expect(html2).toContain('second-data') + } finally { + await next.patchFile(indexPage, indexContent) + await next.patchFile(tsConfigFile, tsconfigContent) + await check(async () => { + const html3 = await browser.eval('document.documentElement.innerHTML') + return html3.includes('first-data') && !html3.includes('second-data') + ? 'success' + : html3 + }, 'success') + } + }) + + it('should automatically fast refresh content when path is added without error', async () => { + const indexContent = await next.readFile(indexPage) + const tsconfigContent = await next.readFile(tsConfigFile) + const parsedTsConfig = JSON.parse(tsconfigContent) + + const browser = await webdriver(next.url, '/') + + try { + const html = await browser.eval('document.documentElement.innerHTML') + expect(html).toContain('first button') + expect(html).toContain('second button') + expect(html).toContain('first-data') + + await next.patchFile( + tsConfigFile, + JSON.stringify( + { + ...parsedTsConfig, + compilerOptions: { + ...parsedTsConfig.compilerOptions, + paths: { + ...parsedTsConfig.compilerOptions.paths, + '@myotherbutton': ['components/button-3.js'], + }, + }, + }, + null, + 2 + ) + ) + await next.patchFile( + indexPage, + indexContent.replace('@mybutton', '@myotherbutton') + ) + + expect(await hasRedbox(browser, false)).toBe(false) + + await check(async () => { + const html2 = await browser.eval('document.documentElement.innerHTML') + expect(html2).toContain('first button') + expect(html2).not.toContain('second button') + expect(html2).toContain('third button') + expect(html2).toContain('first-data') + return 'success' + }, 'success') + } finally { + await next.patchFile(indexPage, indexContent) + await next.patchFile(tsConfigFile, tsconfigContent) + await check(async () => { + const html3 = await browser.eval('document.documentElement.innerHTML') + return html3.includes('first button') && + !html3.includes('third button') + ? 'success' + : html3 + }, 'success') + } + }) + } + + describe('jsconfig', () => { + runTests({}) + }) + + describe('jsconfig added after starting dev', () => { + runTests({ addAfterStart: true }) + }) +}) diff --git a/test/development/tsconfig-path-reloading/app/components/button-1.tsx b/test/development/tsconfig-path-reloading/app/components/button-1.tsx new file mode 100644 index 000000000000000..296068bbb66d68e --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/components/button-1.tsx @@ -0,0 +1,3 @@ +export function Button1(props) { + return +} diff --git a/test/development/tsconfig-path-reloading/app/components/button-2.tsx b/test/development/tsconfig-path-reloading/app/components/button-2.tsx new file mode 100644 index 000000000000000..f1208886efac471 --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/components/button-2.tsx @@ -0,0 +1,3 @@ +export function Button2(props) { + return +} diff --git a/test/development/tsconfig-path-reloading/app/components/button-3.tsx b/test/development/tsconfig-path-reloading/app/components/button-3.tsx new file mode 100644 index 000000000000000..0359c00285d08d0 --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/components/button-3.tsx @@ -0,0 +1,3 @@ +export function Button2(props) { + return +} diff --git a/test/development/tsconfig-path-reloading/app/lib/first-lib/first-data.ts b/test/development/tsconfig-path-reloading/app/lib/first-lib/first-data.ts new file mode 100644 index 000000000000000..fec1d03f066ee6f --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/lib/first-lib/first-data.ts @@ -0,0 +1,3 @@ +export const firstData = { + hello: 'world', +} diff --git a/test/development/tsconfig-path-reloading/app/lib/second-lib/second-data.ts b/test/development/tsconfig-path-reloading/app/lib/second-lib/second-data.ts new file mode 100644 index 000000000000000..86498777ff51145 --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/lib/second-lib/second-data.ts @@ -0,0 +1,3 @@ +export const secondData = { + hello: 'again', +} diff --git a/test/development/tsconfig-path-reloading/app/pages/index.tsx b/test/development/tsconfig-path-reloading/app/pages/index.tsx new file mode 100644 index 000000000000000..859719d413a9821 --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/pages/index.tsx @@ -0,0 +1,13 @@ +import { Button1 } from '@c/button-1' +import { Button2 } from '@mybutton' +import { firstData } from '@lib/first-data' + +export default function Page(props) { + return ( + <> +{JSON.stringify(firstData)}
+ > + ) +} diff --git a/test/development/tsconfig-path-reloading/app/tsconfig.json b/test/development/tsconfig-path-reloading/app/tsconfig.json new file mode 100644 index 000000000000000..3075aad9c6766f0 --- /dev/null +++ b/test/development/tsconfig-path-reloading/app/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@c/*": ["components/*"], + "@lib/*": ["lib/first-lib/*"], + "@mybutton": ["components/button-2.tsx"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/test/development/tsconfig-path-reloading/index.test.ts b/test/development/tsconfig-path-reloading/index.test.ts new file mode 100644 index 000000000000000..ef679796d489de2 --- /dev/null +++ b/test/development/tsconfig-path-reloading/index.test.ts @@ -0,0 +1,190 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { + check, + hasRedbox, + renderViaHTTP, + getRedboxSource, +} from 'next-test-utils' +import cheerio from 'cheerio' +import { join } from 'path' +import webdriver from 'next-webdriver' +import fs from 'fs-extra' + +describe('tsconfig-path-reloading', () => { + let next: NextInstance + const tsConfigFile = 'tsconfig.json' + const indexPage = 'pages/index.tsx' + + function runTests({ addAfterStart }: { addAfterStart?: boolean }) { + beforeAll(async () => { + let tsConfigContent = await fs.readFile( + join(__dirname, 'app/tsconfig.json'), + 'utf8' + ) + + next = await createNext({ + files: { + components: new FileRef(join(__dirname, 'app/components')), + pages: new FileRef(join(__dirname, 'app/pages')), + lib: new FileRef(join(__dirname, 'app/lib')), + ...(addAfterStart + ? {} + : { + [tsConfigFile]: tsConfigContent, + }), + }, + dependencies: { + typescript: 'latest', + '@types/react': 'latest', + '@types/node': 'latest', + }, + }) + + if (addAfterStart) { + await next.patchFile(tsConfigFile, tsConfigContent) + } + }) + afterAll(() => next.destroy()) + + it('should load with initial paths config correctly', async () => { + const html = await renderViaHTTP(next.url, '/') + const $ = cheerio.load(html) + expect(html).toContain('first button') + expect(html).toContain('second button') + expect($('#first-data').text()).toContain( + JSON.stringify({ + hello: 'world', + }) + ) + }) + + it('should recover from module not found when paths is updated', async () => { + const indexContent = await next.readFile(indexPage) + const tsconfigContent = await next.readFile(tsConfigFile) + const parsedTsConfig = JSON.parse(tsconfigContent) + + const browser = await webdriver(next.url, '/') + + try { + const html = await browser.eval('document.documentElement.innerHTML') + expect(html).toContain('first button') + expect(html).toContain('second button') + expect(html).toContain('first-data') + expect(html).not.toContain('second-data') + + await next.patchFile( + indexPage, + `import {secondData} from "@lib/second-data"\n${indexContent.replace( + '', + `{JSON.stringify(secondData)}
` + )}` + ) + + expect(await hasRedbox(browser, true)).toBe(true) + expect(await getRedboxSource(browser)).toContain('"@lib/second-data"') + + await next.patchFile( + tsConfigFile, + JSON.stringify( + { + ...parsedTsConfig, + compilerOptions: { + ...parsedTsConfig.compilerOptions, + paths: { + ...parsedTsConfig.compilerOptions.paths, + '@lib/*': ['lib/first-lib/*', 'lib/second-lib/*'], + }, + }, + }, + null, + 2 + ) + ) + + expect(await hasRedbox(browser, false)).toBe(false) + + const html2 = await browser.eval('document.documentElement.innerHTML') + expect(html2).toContain('first button') + expect(html2).toContain('second button') + expect(html2).toContain('first-data') + expect(html2).toContain('second-data') + } finally { + await next.patchFile(indexPage, indexContent) + await next.patchFile(tsConfigFile, tsconfigContent) + await check(async () => { + const html3 = await browser.eval('document.documentElement.innerHTML') + return html3.includes('first-data') && !html3.includes('second-data') + ? 'success' + : html3 + }, 'success') + } + }) + + it('should automatically fast refresh content when path is added without error', async () => { + const indexContent = await next.readFile(indexPage) + const tsconfigContent = await next.readFile(tsConfigFile) + const parsedTsConfig = JSON.parse(tsconfigContent) + + const browser = await webdriver(next.url, '/') + + try { + const html = await browser.eval('document.documentElement.innerHTML') + expect(html).toContain('first button') + expect(html).toContain('second button') + expect(html).toContain('first-data') + + await next.patchFile( + tsConfigFile, + JSON.stringify( + { + ...parsedTsConfig, + compilerOptions: { + ...parsedTsConfig.compilerOptions, + paths: { + ...parsedTsConfig.compilerOptions.paths, + '@myotherbutton': ['components/button-3.tsx'], + }, + }, + }, + null, + 2 + ) + ) + await next.patchFile( + indexPage, + indexContent.replace('@mybutton', '@myotherbutton') + ) + + expect(await hasRedbox(browser, false)).toBe(false) + + await check(async () => { + const html2 = await browser.eval('document.documentElement.innerHTML') + expect(html2).toContain('first button') + expect(html2).not.toContain('second button') + expect(html2).toContain('third button') + expect(html2).toContain('first-data') + return 'success' + }, 'success') + } finally { + await next.patchFile(indexPage, indexContent) + await next.patchFile(tsConfigFile, tsconfigContent) + await check(async () => { + const html3 = await browser.eval('document.documentElement.innerHTML') + return html3.includes('first button') && + !html3.includes('third button') + ? 'success' + : html3 + }, 'success') + } + }) + } + + describe('tsconfig', () => { + runTests({}) + }) + + describe('tsconfig added after starting dev', () => { + runTests({ addAfterStart: true }) + }) +}) diff --git a/test/development/typescript-auto-install/index.test.ts b/test/development/typescript-auto-install/index.test.ts new file mode 100644 index 000000000000000..4f5cca72d8eabb3 --- /dev/null +++ b/test/development/typescript-auto-install/index.test.ts @@ -0,0 +1,61 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { check, renderViaHTTP } from 'next-test-utils' +import webdriver from 'next-webdriver' +// @ts-expect-error missing types +import stripAnsi from 'strip-ansi' + +describe('typescript-auto-install', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + export default function Page() { + returnhello world
+ } + `, + }, + startCommand: 'yarn next dev', + installCommand: 'yarn', + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should work', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('hello world') + }) + + it('should detect TypeScript being added and auto setup', async () => { + const browser = await webdriver(next.url, '/') + const pageContent = await next.readFile('pages/index.js') + + await check( + () => browser.eval('document.documentElement.innerHTML'), + /hello world/ + ) + await next.renameFile('pages/index.js', 'pages/index.tsx') + + await check( + () => stripAnsi(next.cliOutput), + /We detected TypeScript in your project and created a tsconfig\.json file for you/i + ) + + await check( + () => browser.eval('document.documentElement.innerHTML'), + /hello world/ + ) + await next.patchFile( + 'pages/index.tsx', + pageContent.replace('hello world', 'hello again') + ) + + await check( + () => browser.eval('document.documentElement.innerHTML'), + /hello again/ + ) + }) +}) diff --git a/test/integration/typescript-version-warning/app/node_modules/typescript/index.js b/test/integration/typescript-version-warning/app/node_modules/typescript/index.js deleted file mode 100644 index 740559576178f4f..000000000000000 --- a/test/integration/typescript-version-warning/app/node_modules/typescript/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const mod = require('../../../../../../node_modules/typescript') - -mod.version = '3.8.3' - -module.exports = mod diff --git a/test/integration/typescript-version-warning/app/node_modules/typescript/lib/typescript.js b/test/integration/typescript-version-warning/app/node_modules/typescript/lib/typescript.js new file mode 100644 index 000000000000000..ca151aa41523844 --- /dev/null +++ b/test/integration/typescript-version-warning/app/node_modules/typescript/lib/typescript.js @@ -0,0 +1,5 @@ +const mod = require('../../../../../../../node_modules/typescript') + +mod.version = '3.8.3' + +module.exports = mod diff --git a/test/integration/typescript-version-warning/app/node_modules/typescript/package.json b/test/integration/typescript-version-warning/app/node_modules/typescript/package.json index 5331dd9817e332b..69c3d513633ec81 100644 --- a/test/integration/typescript-version-warning/app/node_modules/typescript/package.json +++ b/test/integration/typescript-version-warning/app/node_modules/typescript/package.json @@ -1,5 +1,5 @@ { "name": "typescript", "version": "3.8.3", - "main": "./index.js" + "main": "./lib/typescript.js" } diff --git a/test/integration/typescript-version-warning/app/tsconfig.json b/test/integration/typescript-version-warning/app/tsconfig.json index 93a83a407c40c84..b8d597880a1ae63 100644 --- a/test/integration/typescript-version-warning/app/tsconfig.json +++ b/test/integration/typescript-version-warning/app/tsconfig.json @@ -12,7 +12,8 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve" + "jsx": "preserve", + "incremental": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/test/integration/typescript-version-warning/test/index.test.js b/test/integration/typescript-version-warning/test/index.test.js index f47f1be582daf4f..a37ae2ad57a61f7 100644 --- a/test/integration/typescript-version-warning/test/index.test.js +++ b/test/integration/typescript-version-warning/test/index.test.js @@ -4,7 +4,7 @@ import { join } from 'path' import { nextBuild, findPort, launchApp, killApp } from 'next-test-utils' const appDir = join(__dirname, '../app') -const tsFile = join(appDir, 'node_modules/typescript/index.js') +const tsFile = join(appDir, 'node_modules/typescript/lib/typescript.js') describe('Minimum TypeScript Warning', () => { it('should show warning during next build with old version', async () => { diff --git a/test/production/missing-dep-error/index.test.ts b/test/production/missing-dep-error/index.test.ts deleted file mode 100644 index d888763af1ca225..000000000000000 --- a/test/production/missing-dep-error/index.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createNext } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' - -describe('missing-dep-error', () => { - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'pages/index.tsx': ` - export default function Page() { - returnhello world
- } - `, - }, - skipStart: true, - }) - }) - afterAll(() => next.destroy()) - - it('should only show error once', async () => { - await next.start().catch(() => {}) - expect( - next.cliOutput.match(/It looks like you're trying to use TypeScript/g) - ?.length - ).toBe(1) - }) -})