提交 9f4904ff 编写于 作者: J Joe Haddad 提交者: JJ Kasper

Integrate type checking into compilation pipeline (#7278)

上级 e0c35458
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
import fs from 'fs'
import {
CLIENT_STATIC_FILES_RUNTIME_MAIN,
......@@ -23,13 +24,13 @@ import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
import { importAutoDllPlugin } from './webpack/plugins/dll-import'
import { HashedChunkIdsPlugin } from './webpack/plugins/hashed-chunk-ids-plugin'
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
import NextJsSsrImportPlugin from './webpack/plugins/nextjs-ssr-import'
import NextJsSSRModuleCachePlugin from './webpack/plugins/nextjs-ssr-module-cache'
import PagesManifestPlugin from './webpack/plugins/pages-manifest-plugin'
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
import { ServerlessPlugin } from './webpack/plugins/serverless-plugin'
import { SharedRuntimePlugin } from './webpack/plugins/shared-runtime-plugin'
import { DropClientPage } from './webpack/plugins/next-drop-client-page-plugin'
import { TerserPlugin } from './webpack/plugins/terser-webpack-plugin/src/index'
const fileExists = promisify(fs.exists)
......@@ -99,7 +100,8 @@ export default async function getBaseWebpackConfig(
}
: undefined
const useTypeScript = await fileExists(path.join(dir, 'tsconfig.json'))
const tsConfigPath = path.join(dir, 'tsconfig.json')
const useTypeScript = await fileExists(tsConfigPath)
const resolveConfig = {
// Disable .mjs for node_modules bundling
......@@ -375,7 +377,11 @@ export default async function getBaseWebpackConfig(
/next[\\/]dist[\\/]pages/,
],
exclude: (path: string) => {
if (/next-server[\\/]dist[\\/]lib/.test(path) || /next[\\/]dist[\\/]client/.test(path) || /next[\\/]dist[\\/]pages/.test(path)) {
if (
/next-server[\\/]dist[\\/]lib/.test(path) ||
/next[\\/]dist[\\/]client/.test(path) ||
/next[\\/]dist[\\/]pages/.test(path)
) {
return false
}
......@@ -501,6 +507,20 @@ export default async function getBaseWebpackConfig(
`profile-events-${isServer ? 'server' : 'client'}.json`
),
}),
!isServer && useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: dir,
}),
async: false,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
tsconfig: tsConfigPath,
reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
compilerOptions: { isolatedModules: true, noEmit: true },
silent: true,
formatter: 'codeframe',
}),
].filter((Boolean as any) as ExcludesFalse),
}
......
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
import { NormalizedMessage } from 'fork-ts-checker-webpack-plugin/lib/NormalizedMessage'
import webpack from 'webpack'
export function Apply(compiler: webpack.Compiler) {
const hooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler)
let additionalFiles: string[] = []
hooks.receive.tap('ForkTsCheckerWatcherHook', function(
diagnostics: NormalizedMessage[],
lints: NormalizedMessage[]
) {
additionalFiles = [
...new Set([
...diagnostics.map(d => d.file!),
...lints.map(l => l.file!),
]),
].filter(Boolean)
})
compiler.hooks.afterCompile.tap('ForkTsCheckerWatcherHook', function(
compilation
) {
additionalFiles.forEach(file => compilation.fileDependencies.add(file))
})
}
......@@ -76,6 +76,7 @@
"babel-plugin-transform-react-remove-prop-types": "0.4.15",
"chalk": "2.4.2",
"find-up": "2.1.0",
"fork-ts-checker-webpack-plugin": "1.3.0",
"fresh": "0.5.2",
"launch-editor": "2.2.1",
"loader-utils": "1.1.0",
......
......@@ -13,10 +13,13 @@ import { watchCompiler } from '../build/output'
import { findPageFile } from './lib/find-page-file'
import { recursiveDelete } from '../lib/recursive-delete'
import { promisify } from 'util'
import * as ForkTsCheckerWatcherHook from '../build/webpack/plugins/fork-ts-checker-watcher-hook'
import fs from 'fs'
const access = promisify(fs.access)
const readFile = promisify(fs.readFile)
// eslint-disable-next-line
const fileExists = promisify(fs.exists)
export async function renderScriptError (res, error) {
// Asks CDNs and others to not to cache the errored page
......@@ -68,6 +71,10 @@ function findEntryModule (issuer) {
function erroredPages (compilation, options = { enhanceName: (name) => name }) {
const failedPages = {}
for (const error of compilation.errors) {
if (!error.origin) {
continue
}
const entryModule = findEntryModule(error.origin)
const { name } = entryModule
if (!name) {
......@@ -264,6 +271,12 @@ export default class HotReloader {
multiCompiler.compilers[1]
)
const tsConfigPath = join(this.dir, 'tsconfig.json')
const useTypeScript = await fileExists(tsConfigPath)
if (useTypeScript) {
ForkTsCheckerWatcherHook.Apply(multiCompiler.compilers[0])
}
// This plugin watches for changes to _document.js and notifies the client side that it should reload the page
multiCompiler.compilers[1].hooks.done.tap('NextjsHotReloaderForServer', (stats) => {
if (!this.initialized) {
......
......@@ -81,42 +81,6 @@ export default (context, renderViaHTTP) => {
}
})
it('should detect the changes to typescript pages and display it', async () => {
let browser
try {
browser = await webdriver(context.appPort, '/typescript/hello')
await check(
() => getBrowserBodyText(browser),
/Hello World/
)
const pagePath = join(__dirname, '../', 'components', 'typescript', 'hello.ts')
const originalContent = readFileSync(pagePath, 'utf8')
const editedContent = originalContent.replace('Hello', 'COOL page')
// change the content
writeFileSync(pagePath, editedContent, 'utf8')
await check(
() => getBrowserBodyText(browser),
/COOL page/
)
// add the original content
writeFileSync(pagePath, originalContent, 'utf8')
await check(
() => getBrowserBodyText(browser),
/Hello World/
)
} finally {
if (browser) {
await browser.close()
}
}
})
it('should not reload unrelated pages', async () => {
let browser
try {
......
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
import {
renderViaHTTP,
findPort,
launchApp,
killApp
} from 'next-test-utils'
import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils'
// test suits
import hmr from './hmr'
import errorRecovery from './error-recovery'
import dynamic from './dynamic'
import processEnv from './process-env'
import typescript from './typescript'
import publicFolder from './public-folder'
const context = {}
......@@ -39,7 +33,6 @@ describe('Basic Features', () => {
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
errorRecovery(context, (p, q) => renderViaHTTP(context.appPort, p, q))
typescript(context, (p, q) => renderViaHTTP(context.appPort, p, q))
processEnv(context)
publicFolder(context)
})
module.exports = {
onDemandEntries: {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60
}
}
import React from 'react'
import {hello} from '../../components/typescript/hello'
import {World} from '../../components/typescript/world'
import { hello } from '../components/hello'
import { World } from '../components/world'
export default function HelloPage(): JSX.Element {
return <div>{hello()} <World /></div>
return (
<div>
{hello()} <World />
</div>
)
}
/* eslint-env jest */
import webdriver from 'next-webdriver'
import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import { check, getBrowserBodyText } from 'next-test-utils'
export default (context, renderViaHTTP) => {
describe('Hot Module Reloading', () => {
describe('delete a page and add it back', () => {
it('should detect the changes to typescript pages and display it', async () => {
let browser
try {
browser = await webdriver(context.appPort, '/hello')
await check(() => getBrowserBodyText(browser), /Hello World/)
const pagePath = join(__dirname, '../', 'components', 'hello.ts')
const originalContent = readFileSync(pagePath, 'utf8')
const editedContent = originalContent.replace('Hello', 'COOL page')
// change the content
writeFileSync(pagePath, editedContent, 'utf8')
await check(() => getBrowserBodyText(browser), /COOL page/)
// add the original content
writeFileSync(pagePath, originalContent, 'utf8')
await check(() => getBrowserBodyText(browser), /Hello World/)
} finally {
if (browser) {
await browser.close()
}
}
})
})
})
}
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils'
import hmr from './hmr'
import typescript from './typescript'
const context = {}
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
describe('TypeScript Features', () => {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await launchApp(join(__dirname, '../'), context.appPort)
// pre-build all pages at the start
await Promise.all([renderViaHTTP(context.appPort, '/hello')])
})
afterAll(() => killApp(context.server))
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
typescript(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})
......@@ -6,10 +6,10 @@ export default (context, render) => {
const html = await render(path, query)
return cheerio.load(html)
}
describe('Typescript', () => {
describe('TypeScript', () => {
describe('default behavior', () => {
it('should render the page', async () => {
const $ = await get$('/typescript/hello')
const $ = await get$('/hello')
expect($('body').text()).toMatch(/Hello World/)
})
})
......
{
"compilerOptions": {
"esModuleInterop": true,
"module": "esnext",
"jsx": "preserve"
},
"include": ["components", "pages"]
}
......@@ -3344,7 +3344,7 @@ chokidar@^1.7.0:
optionalDependencies:
fsevents "^1.0.0"
chokidar@^2.0.2:
chokidar@^2.0.2, chokidar@^2.0.4:
version "2.1.5"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
......@@ -5586,6 +5586,20 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
fork-ts-checker-webpack-plugin@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.3.0.tgz#1f5540467c5e971d2da69f76103354b3e6665d13"
integrity sha512-38nL1h4nRLrU/0oCAvjQY7ZJw0JpcsOOwSMZQSaMdVi2d+vXNcyvVg/n6q4mRcvGmVW2z2EIlj5ufaITk0M5bA==
dependencies:
babel-code-frame "^6.22.0"
chalk "^2.4.1"
chokidar "^2.0.4"
micromatch "^3.1.10"
minimatch "^3.0.4"
semver "^5.6.0"
tapable "^1.0.0"
worker-rpc "^0.1.0"
form-data@~2.3.1, form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
......@@ -8426,6 +8440,11 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
microevent.ts@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.0.tgz#390748b8a515083e6b63cd5112a3f18c2fe0eba8"
integrity sha1-OQdIuKUVCD5rY81REqPxjC/g66g=
micromatch@^2.1.5, micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
......@@ -13149,6 +13168,13 @@ worker-farm@^1.5.2:
dependencies:
errno "~0.1.7"
worker-rpc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.0.tgz#5f1258dca3d617cd18ca86587f8a05ac0eebd834"
integrity sha1-XxJY3KPWF80YyoZYf4oFrA7r2DQ=
dependencies:
microevent.ts "~0.1.0"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册