From 14ac5f5dade650957f18858f54307d43065f1b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Mon, 26 Nov 2018 15:15:43 +0100 Subject: [PATCH 01/16] Async TypeScript checks --- .../react-dev-utils/WebpackDevServerUtils.js | 52 +++++++++++++++++-- .../react-scripts/config/webpack.config.js | 4 +- packages/react-scripts/scripts/start.js | 4 +- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 0a3e233e641..e8a67f8e615 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -17,6 +17,7 @@ const inquirer = require('inquirer'); const clearConsole = require('./clearConsole'); const formatWebpackMessages = require('./formatWebpackMessages'); const getProcessForPort = require('./getProcessForPort'); +const typescriptFormatter = require('./typescriptFormatter'); const isInteractive = process.stdout.isTTY; let handleCompile; @@ -113,7 +114,7 @@ function printInstructions(appName, urls, useYarn) { console.log(); } -function createCompiler(webpack, config, appName, urls, useYarn) { +function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, reload) { // "Compiler" is a low-level interface to Webpack. // It lets us listen to some events and provide our own custom messages. let compiler; @@ -139,10 +140,39 @@ function createCompiler(webpack, config, appName, urls, useYarn) { }); let isFirstCompile = true; + let tsMessagesPromise; + let tsMessagesResolver; + + if (useTypeScript) { + compiler.hooks.beforeCompile.tap('beforeCompile', () => { + tsMessagesPromise = new Promise(resolve => { + tsMessagesResolver = msgs => resolve(msgs); + }); + }); + + compiler.hooks.forkTsCheckerReceive.tap('fork-ts-checker-done', async msgs => { + const format = (message) => `${message.file}\n${typescriptFormatter(message, true)}`; + + tsMessagesResolver({ + errors: msgs.filter(msg => msg.severity === 'error').map(format), + warnings: msgs.filter(msg => msg.severity === 'warning').map(format) + }); + }); + + compiler.hooks.afterCompile.tap('afterCompile', async compilation => { + // If any errors already exist, skip this. + if (compilation.errors.length > 0) return; + + const messages = await tsMessagesPromise; + compilation.errors.push(...messages.errors); + compilation.warnings.push(...messages.warnings); + if (messages.errors.length > 0 || messages.warnings.length > 0) reload(); + }); + } // "done" event fires when Webpack has finished recompiling the bundle. // Whether or not you have warnings or errors, you will get this event. - compiler.hooks.done.tap('done', stats => { + compiler.hooks.done.tap('done', async stats => { if (isInteractive) { clearConsole(); } @@ -152,9 +182,21 @@ function createCompiler(webpack, config, appName, urls, useYarn) { // them in a readable focused way. // We only construct the warnings and errors for speed: // https://github.com/facebook/create-react-app/issues/4492#issuecomment-421959548 - const messages = formatWebpackMessages( - stats.toJson({ all: false, warnings: true, errors: true }) - ); + const statsData = stats.toJson({ all: false, warnings: true, errors: true }); + + if (useTypeScript && statsData.errors.length === 0) { + process.stdout.write( + chalk.yellow('Files successfully emitted, waiting for typecheck results...') + ); + + const tsMessages = await tsMessagesPromise; + statsData.errors.push(...tsMessages.errors); + statsData.warnings.push(...tsMessages.warnings); + process.stdout.clearLine(); + process.stdout.cursorTo(0); + } + + const messages = formatWebpackMessages(statsData); const isSuccessful = !messages.errors.length && !messages.warnings.length; if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 746884a03eb..e001c646cbb 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -619,7 +619,7 @@ module.exports = function(webpackEnv) { typescript: resolve.sync('typescript', { basedir: paths.appNodeModules, }), - async: false, + async: isEnvDevelopment, checkSyntacticErrors: true, tsconfig: paths.appTsConfig, compilerOptions: { @@ -640,7 +640,7 @@ module.exports = function(webpackEnv) { ], watch: paths.appSrc, silent: true, - formatter: typescriptFormatter, + formatter: isEnvProduction ? typescriptFormatter : undefined, }), ].filter(Boolean), // Some libraries import Node modules but don't use them in the browser. diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 7750c25a176..21b94ab6a1f 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -94,9 +94,11 @@ checkBrowsers(paths.appPath, isInteractive) const config = configFactory('development'); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const appName = require(paths.appPackageJson).name; + const useTypeScript = fs.existsSync(paths.appTsConfig); const urls = prepareUrls(protocol, HOST, port); + const reloadFn = () => devServer.sockWrite(devServer.sockets, 'content-changed'); // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler(webpack, config, appName, urls, useYarn); + const compiler = createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, reloadFn); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; const proxyConfig = prepareProxy(proxySetting, paths.appPublic); From 8fb90475ad936150154e24c09b2ef7ed145ba5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Mon, 26 Nov 2018 15:56:22 +0100 Subject: [PATCH 02/16] Remove unecessary async keyword --- packages/react-dev-utils/WebpackDevServerUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index e8a67f8e615..61becf95ce0 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -150,7 +150,7 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, }); }); - compiler.hooks.forkTsCheckerReceive.tap('fork-ts-checker-done', async msgs => { + compiler.hooks.forkTsCheckerReceive.tap('fork-ts-checker-done', msgs => { const format = (message) => `${message.file}\n${typescriptFormatter(message, true)}`; tsMessagesResolver({ From 4cc09f4dce1cd6de323292408b0aa7c809073dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Tue, 27 Nov 2018 07:47:12 +0100 Subject: [PATCH 03/16] fix eslint warnings --- packages/react-dev-utils/WebpackDevServerUtils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 61becf95ce0..2160f4cad44 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -161,12 +161,16 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, compiler.hooks.afterCompile.tap('afterCompile', async compilation => { // If any errors already exist, skip this. - if (compilation.errors.length > 0) return; + if (compilation.errors.length > 0) { + return; + } const messages = await tsMessagesPromise; compilation.errors.push(...messages.errors); compilation.warnings.push(...messages.warnings); - if (messages.errors.length > 0 || messages.warnings.length > 0) reload(); + if (messages.errors.length > 0 || messages.warnings.length > 0) { + reload(); + } }); } From b8bb7c63fcceff2282f0a5480d5fbaf268751494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Tue, 27 Nov 2018 08:34:13 +0100 Subject: [PATCH 04/16] fixed smoke test --- .../react-dev-utils/WebpackDevServerUtils.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 2160f4cad44..094c4006298 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -20,20 +20,6 @@ const getProcessForPort = require('./getProcessForPort'); const typescriptFormatter = require('./typescriptFormatter'); const isInteractive = process.stdout.isTTY; -let handleCompile; - -// You can safely remove this after ejecting. -// We only use this block for testing of Create React App itself: -const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); -if (isSmokeTest) { - handleCompile = (err, stats) => { - if (err || stats.hasErrors() || stats.hasWarnings()) { - process.exit(1); - } else { - process.exit(0); - } - }; -} function prepareUrls(protocol, host, port) { const formatUrl = hostname => @@ -119,7 +105,7 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, // It lets us listen to some events and provide our own custom messages. let compiler; try { - compiler = webpack(config, handleCompile); + compiler = webpack(config); } catch (err) { console.log(chalk.red('Failed to compile.')); console.log(); @@ -128,6 +114,20 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, process.exit(1); } + // You can safely remove this after ejecting. + // We only use this block for testing of Create React App itself: + const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); + if (isSmokeTest) { + compiler.hooks.failed.tap('smokeTest', () => process.exit(1)); + compiler.hooks.done.tap('smokeTest', stats => { + if (stats.hasErrors() || stats.hasWarnings()) { + process.exit(1); + } else { + process.exit(0); + } + }); + } + // "invalid" event fires when you have changed a file, and Webpack is // recompiling a bundle. WebpackDevServer takes care to pause serving the // bundle, so if you refresh, it'll wait instead of serving the old one. From 174a81a11c4d9c4064e91c96832eef6dd4261d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Tue, 27 Nov 2018 08:55:40 +0100 Subject: [PATCH 05/16] fixed failing tests --- .../react-dev-utils/WebpackDevServerUtils.js | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 094c4006298..5c30aad0c64 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -114,20 +114,6 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, process.exit(1); } - // You can safely remove this after ejecting. - // We only use this block for testing of Create React App itself: - const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); - if (isSmokeTest) { - compiler.hooks.failed.tap('smokeTest', () => process.exit(1)); - compiler.hooks.done.tap('smokeTest', stats => { - if (stats.hasErrors() || stats.hasWarnings()) { - process.exit(1); - } else { - process.exit(0); - } - }); - } - // "invalid" event fires when you have changed a file, and Webpack is // recompiling a bundle. WebpackDevServer takes care to pause serving the // bundle, so if you refresh, it'll wait instead of serving the old one. @@ -240,6 +226,25 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, ); } }); + + // You can safely remove this after ejecting. + // We only use this block for testing of Create React App itself: + const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); + if (isSmokeTest) { + compiler.hooks.failed.tap('smokeTest', async () => { + await tsMessagesPromise; + process.exit(1) + }); + compiler.hooks.done.tap('smokeTest', async stats => { + await tsMessagesPromise; + if (stats.hasErrors() || stats.hasWarnings()) { + process.exit(1); + } else { + process.exit(0); + } + }); + } + return compiler; } From 75400fdb0784adf4cd1dfb91c891de4d5d23dffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Tue, 4 Dec 2018 16:48:34 +0100 Subject: [PATCH 06/16] Improve error/warnings reporting --- .../react-dev-utils/WebpackDevServerUtils.js | 42 +++++++++---------- .../react-dev-utils/typescriptFormatter.js | 7 +++- .../react-dev-utils/webpackHotDevClient.js | 8 +--- packages/react-scripts/scripts/start.js | 7 +++- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 5c30aad0c64..aff6009c385 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -100,7 +100,7 @@ function printInstructions(appName, urls, useYarn) { console.log(); } -function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, reload) { +function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, devSocket) { // "Compiler" is a low-level interface to Webpack. // It lets us listen to some events and provide our own custom messages. let compiler; @@ -136,28 +136,15 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, }); }); - compiler.hooks.forkTsCheckerReceive.tap('fork-ts-checker-done', msgs => { - const format = (message) => `${message.file}\n${typescriptFormatter(message, true)}`; + compiler.hooks.forkTsCheckerReceive.tap('afterTypeScriptCheck', (diagnostics, lints) => { + const allMsgs = [...diagnostics, ...lints]; + const format = message =>`${message.file}\n${typescriptFormatter(message, true)}`; tsMessagesResolver({ - errors: msgs.filter(msg => msg.severity === 'error').map(format), - warnings: msgs.filter(msg => msg.severity === 'warning').map(format) + errors: allMsgs.filter(msg => msg.severity === 'error').map(format), + warnings: allMsgs.filter(msg => msg.severity === 'warning').map(format), }); }); - - compiler.hooks.afterCompile.tap('afterCompile', async compilation => { - // If any errors already exist, skip this. - if (compilation.errors.length > 0) { - return; - } - - const messages = await tsMessagesPromise; - compilation.errors.push(...messages.errors); - compilation.warnings.push(...messages.warnings); - if (messages.errors.length > 0 || messages.warnings.length > 0) { - reload(); - } - }); } // "done" event fires when Webpack has finished recompiling the bundle. @@ -179,9 +166,20 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, chalk.yellow('Files successfully emitted, waiting for typecheck results...') ); - const tsMessages = await tsMessagesPromise; - statsData.errors.push(...tsMessages.errors); - statsData.warnings.push(...tsMessages.warnings); + const messages = await tsMessagesPromise; + statsData.errors.push(...messages.errors); + statsData.warnings.push(...messages.warnings); + // Push errors and warnings into compilation result + // to show them after page refresh triggered by user. + stats.compilation.errors.push(...messages.errors); + stats.compilation.warnings.push(...messages.warnings); + + if (messages.errors.length > 0) { + devSocket.errors(messages.errors); + } else if (messages.warnings.length > 0) { + devSocket.warnings(messages.warnings); + } + process.stdout.clearLine(); process.stdout.cursorTo(0); } diff --git a/packages/react-dev-utils/typescriptFormatter.js b/packages/react-dev-utils/typescriptFormatter.js index 2d011fc7e71..3a33b37a427 100644 --- a/packages/react-dev-utils/typescriptFormatter.js +++ b/packages/react-dev-utils/typescriptFormatter.js @@ -45,12 +45,15 @@ function formatter(message, useColors) { } const severity = hasGetters ? message.getSeverity() : message.severity; + const types = { diagnostic: 'TypeScript', lint: 'TSLint' }; return [ - messageColor.bold(`Type ${severity.toLowerCase()}: `) + + messageColor.bold(`${types[message.type]} ${severity.toLowerCase()}: `) + (hasGetters ? message.getContent() : message.content) + ' ' + - messageColor.underline(`TS${message.code}`), + messageColor.underline( + (message.type === 'lint' ? 'Rule: ' : 'TS') + message.code + ), '', frame, ].join(os.EOL); diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index cc7dc61684b..fab84875870 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -140,19 +140,15 @@ function handleWarnings(warnings) { } } + printWarnings(); + // Attempt to apply hot updates or reload. if (isHotUpdate) { tryApplyUpdates(function onSuccessfulHotUpdate() { - // Only print warnings if we aren't refreshing the page. - // Otherwise they'll disappear right away anyway. - printWarnings(); // Only dismiss it when we're sure it's a hot update. // Otherwise it would flicker right before the reload. ErrorOverlay.dismissBuildError(); }); - } else { - // Print initial warnings immediately. - printWarnings(); } } diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 21b94ab6a1f..4213cf520d2 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -96,9 +96,12 @@ checkBrowsers(paths.appPath, isInteractive) const appName = require(paths.appPackageJson).name; const useTypeScript = fs.existsSync(paths.appTsConfig); const urls = prepareUrls(protocol, HOST, port); - const reloadFn = () => devServer.sockWrite(devServer.sockets, 'content-changed'); + const devSocket = { + warnings: warnings => devServer.sockWrite(devServer.sockets, 'warnings', warnings), + errors: errors => devServer.sockWrite(devServer.sockets, 'errors', errors), + }; // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, reloadFn); + const compiler = createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, devSocket); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; const proxyConfig = prepareProxy(proxySetting, paths.appPublic); From 3c85b809956b15687c972c3c2245faab917b87bd Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Tue, 11 Dec 2018 06:47:13 +0100 Subject: [PATCH 07/16] Update packages/react-scripts/config/webpack.config.js Co-Authored-By: deftomat --- packages/react-scripts/config/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index e001c646cbb..982d34e556c 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -640,6 +640,7 @@ module.exports = function(webpackEnv) { ], watch: paths.appSrc, silent: true, + // The formatter is invoked directly in WebpackDevServerUtils during development formatter: isEnvProduction ? typescriptFormatter : undefined, }), ].filter(Boolean), From 775555f50caab9aef0a285e6470e62b60b45c1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Mon, 7 Jan 2019 14:43:06 +0100 Subject: [PATCH 08/16] E2E tests for async TypeScript errors --- .../react-dev-utils/WebpackDevServerUtils.js | 57 +++++++++++++------ .../typescript-typecheck/.disable-pnp | 0 .../typescript-typecheck/index.test.js | 34 +++++++++++ .../typescript-typecheck/package.json | 9 +++ .../fixtures/typescript-typecheck/src/App.tsx | 13 +++++ .../typescript-typecheck/src/index.tsx | 5 ++ 6 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/typescript-typecheck/.disable-pnp create mode 100644 test/fixtures/typescript-typecheck/index.test.js create mode 100644 test/fixtures/typescript-typecheck/package.json create mode 100644 test/fixtures/typescript-typecheck/src/App.tsx create mode 100644 test/fixtures/typescript-typecheck/src/index.tsx diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index aff6009c385..98bda38bfc5 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -100,7 +100,15 @@ function printInstructions(appName, urls, useYarn) { console.log(); } -function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, devSocket) { +function createCompiler( + webpack, + config, + appName, + urls, + useYarn, + useTypeScript, + devSocket +) { // "Compiler" is a low-level interface to Webpack. // It lets us listen to some events and provide our own custom messages. let compiler; @@ -136,15 +144,21 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, }); }); - compiler.hooks.forkTsCheckerReceive.tap('afterTypeScriptCheck', (diagnostics, lints) => { - const allMsgs = [...diagnostics, ...lints]; - const format = message =>`${message.file}\n${typescriptFormatter(message, true)}`; - - tsMessagesResolver({ - errors: allMsgs.filter(msg => msg.severity === 'error').map(format), - warnings: allMsgs.filter(msg => msg.severity === 'warning').map(format), - }); - }); + compiler.hooks.forkTsCheckerReceive.tap( + 'afterTypeScriptCheck', + (diagnostics, lints) => { + const allMsgs = [...diagnostics, ...lints]; + const format = message => + `${message.file}\n${typescriptFormatter(message, true)}`; + + tsMessagesResolver({ + errors: allMsgs.filter(msg => msg.severity === 'error').map(format), + warnings: allMsgs + .filter(msg => msg.severity === 'warning') + .map(format), + }); + } + ); } // "done" event fires when Webpack has finished recompiling the bundle. @@ -159,11 +173,17 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, // them in a readable focused way. // We only construct the warnings and errors for speed: // https://github.com/facebook/create-react-app/issues/4492#issuecomment-421959548 - const statsData = stats.toJson({ all: false, warnings: true, errors: true }); + const statsData = stats.toJson({ + all: false, + warnings: true, + errors: true, + }); if (useTypeScript && statsData.errors.length === 0) { - process.stdout.write( - chalk.yellow('Files successfully emitted, waiting for typecheck results...') + console.log( + chalk.yellow( + 'Files successfully emitted, waiting for typecheck results...' + ) ); const messages = await tsMessagesPromise; @@ -180,8 +200,9 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, devSocket.warnings(messages.warnings); } - process.stdout.clearLine(); - process.stdout.cursorTo(0); + if (isInteractive) { + clearConsole(); + } } const messages = formatWebpackMessages(statsData); @@ -227,11 +248,13 @@ function createCompiler(webpack, config, appName, urls, useYarn, useTypeScript, // You can safely remove this after ejecting. // We only use this block for testing of Create React App itself: - const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); + const isSmokeTest = process.argv.some( + arg => arg.indexOf('--smoke-test') > -1 + ); if (isSmokeTest) { compiler.hooks.failed.tap('smokeTest', async () => { await tsMessagesPromise; - process.exit(1) + process.exit(1); }); compiler.hooks.done.tap('smokeTest', async stats => { await tsMessagesPromise; diff --git a/test/fixtures/typescript-typecheck/.disable-pnp b/test/fixtures/typescript-typecheck/.disable-pnp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/typescript-typecheck/index.test.js b/test/fixtures/typescript-typecheck/index.test.js new file mode 100644 index 00000000000..7c47860cd77 --- /dev/null +++ b/test/fixtures/typescript-typecheck/index.test.js @@ -0,0 +1,34 @@ +const testSetup = require('../__shared__/test-setup'); +const puppeteer = require('puppeteer'); + +const expectedErrorMsg = `Argument of type '123' is not assignable to parameter of type 'string'`; + +test('shows error overlay in browser', async () => { + const { port, done } = await testSetup.scripts.start(); + + const browser = await puppeteer.launch({ headless: true }); + try { + const page = await browser.newPage(); + await page.goto(`http://localhost:${port}/`); + await page.waitForSelector('iframe', { timeout: 5000 }); + const errorMsg = await page.evaluate(() => { + const overlay = document.querySelector('iframe').contentWindow; + const error = overlay.document.querySelector('code'); + return error.innerHTML; + }); + expect(errorMsg).toContain(expectedErrorMsg); + } finally { + browser.close(); + done(); + } +}); + +test('shows error in console (dev mode)', async () => { + const { stderr } = await testSetup.scripts.start({ smoke: true }); + expect(stderr).toContain(expectedErrorMsg); +}); + +test('shows error in console (prod mode)', async () => { + const { stderr } = await testSetup.scripts.build(); + expect(stderr).toContain(expectedErrorMsg); +}); diff --git a/test/fixtures/typescript-typecheck/package.json b/test/fixtures/typescript-typecheck/package.json new file mode 100644 index 00000000000..a6c00267c54 --- /dev/null +++ b/test/fixtures/typescript-typecheck/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "*", + "react-dom": "*", + "typescript": "3.1.3" + } +} diff --git a/test/fixtures/typescript-typecheck/src/App.tsx b/test/fixtures/typescript-typecheck/src/App.tsx new file mode 100644 index 00000000000..02def3116af --- /dev/null +++ b/test/fixtures/typescript-typecheck/src/App.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +class App extends React.Component { + render() { + return
{format(123)}
; + } +} + +function format(value: string) { + return value.toUpperCase(); +} + +export default App; diff --git a/test/fixtures/typescript-typecheck/src/index.tsx b/test/fixtures/typescript-typecheck/src/index.tsx new file mode 100644 index 00000000000..bea6ed52237 --- /dev/null +++ b/test/fixtures/typescript-typecheck/src/index.tsx @@ -0,0 +1,5 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); From 7a41d2b54df4992f29dbf8fa83344016be4aee66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Mon, 7 Jan 2019 15:06:47 +0100 Subject: [PATCH 09/16] Increase puppeteer navigation timeouts --- test/fixtures/mjs-support/index.test.js | 4 ++-- test/fixtures/typescript-typecheck/index.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fixtures/mjs-support/index.test.js b/test/fixtures/mjs-support/index.test.js index 767af17b564..c43157dd762 100644 --- a/test/fixtures/mjs-support/index.test.js +++ b/test/fixtures/mjs-support/index.test.js @@ -8,7 +8,7 @@ test('can use mjs library in development', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`); + await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 }); const output = await page.evaluate(() => { return Array.from( @@ -28,7 +28,7 @@ test('can use mjs library in production', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`); + await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 }); const output = await page.evaluate(() => { return Array.from( diff --git a/test/fixtures/typescript-typecheck/index.test.js b/test/fixtures/typescript-typecheck/index.test.js index 7c47860cd77..421d3bb3b7f 100644 --- a/test/fixtures/typescript-typecheck/index.test.js +++ b/test/fixtures/typescript-typecheck/index.test.js @@ -9,7 +9,7 @@ test('shows error overlay in browser', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`); + await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); await page.waitForSelector('iframe', { timeout: 5000 }); const errorMsg = await page.evaluate(() => { const overlay = document.querySelector('iframe').contentWindow; From 29ca5501632de64b491344772f7c1f89f5ad6648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Wed, 9 Jan 2019 05:43:06 +0100 Subject: [PATCH 10/16] Revert puppeteer navigation timeouts to default --- test/fixtures/mjs-support/index.test.js | 4 ++-- test/fixtures/typescript-typecheck/index.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fixtures/mjs-support/index.test.js b/test/fixtures/mjs-support/index.test.js index c43157dd762..767af17b564 100644 --- a/test/fixtures/mjs-support/index.test.js +++ b/test/fixtures/mjs-support/index.test.js @@ -8,7 +8,7 @@ test('can use mjs library in development', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); + await page.goto(`http://localhost:${port}/`); await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 }); const output = await page.evaluate(() => { return Array.from( @@ -28,7 +28,7 @@ test('can use mjs library in production', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); + await page.goto(`http://localhost:${port}/`); await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 }); const output = await page.evaluate(() => { return Array.from( diff --git a/test/fixtures/typescript-typecheck/index.test.js b/test/fixtures/typescript-typecheck/index.test.js index 421d3bb3b7f..7c47860cd77 100644 --- a/test/fixtures/typescript-typecheck/index.test.js +++ b/test/fixtures/typescript-typecheck/index.test.js @@ -9,7 +9,7 @@ test('shows error overlay in browser', async () => { const browser = await puppeteer.launch({ headless: true }); try { const page = await browser.newPage(); - await page.goto(`http://localhost:${port}/`, { timeout: 60000 }); + await page.goto(`http://localhost:${port}/`); await page.waitForSelector('iframe', { timeout: 5000 }); const errorMsg = await page.evaluate(() => { const overlay = document.querySelector('iframe').contentWindow; From 68e2f50dacec6205c42a108b6eecfc7f6d99e459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Wed, 9 Jan 2019 06:21:53 +0100 Subject: [PATCH 11/16] Check whole iframe body for error message --- test/fixtures/typescript-typecheck/index.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/fixtures/typescript-typecheck/index.test.js b/test/fixtures/typescript-typecheck/index.test.js index 7c47860cd77..c4978c735e9 100644 --- a/test/fixtures/typescript-typecheck/index.test.js +++ b/test/fixtures/typescript-typecheck/index.test.js @@ -11,12 +11,11 @@ test('shows error overlay in browser', async () => { const page = await browser.newPage(); await page.goto(`http://localhost:${port}/`); await page.waitForSelector('iframe', { timeout: 5000 }); - const errorMsg = await page.evaluate(() => { + const overlayMsg = await page.evaluate(() => { const overlay = document.querySelector('iframe').contentWindow; - const error = overlay.document.querySelector('code'); - return error.innerHTML; + return overlay.document.body.innerHTML; }); - expect(errorMsg).toContain(expectedErrorMsg); + expect(overlayMsg).toContain(expectedErrorMsg); } finally { browser.close(); done(); From 048822802ffd5d3db84b660f2f86d22117bc7e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Sat, 19 Jan 2019 12:30:55 +0100 Subject: [PATCH 12/16] Use latest fork-ts-checker & improve error reporting --- .../react-dev-utils/WebpackDevServerUtils.js | 29 ++++++++++++------- .../react-scripts/config/webpack.config.js | 11 ++----- packages/react-scripts/package.json | 2 +- .../fixtures/typescript-typecheck/src/App.tsx | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 98bda38bfc5..c9cb44da99a 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -18,6 +18,7 @@ const clearConsole = require('./clearConsole'); const formatWebpackMessages = require('./formatWebpackMessages'); const getProcessForPort = require('./getProcessForPort'); const typescriptFormatter = require('./typescriptFormatter'); +const forkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const isInteractive = process.stdout.isTTY; @@ -134,19 +135,30 @@ function createCompiler( }); let isFirstCompile = true; + let isTsCheckInProgress = false; let tsMessagesPromise; let tsMessagesResolver; if (useTypeScript) { compiler.hooks.beforeCompile.tap('beforeCompile', () => { + isTsCheckInProgress = true; tsMessagesPromise = new Promise(resolve => { - tsMessagesResolver = msgs => resolve(msgs); + tsMessagesResolver = msgs => { + isTsCheckInProgress = false; + resolve(msgs); + }; }); }); - compiler.hooks.forkTsCheckerReceive.tap( - 'afterTypeScriptCheck', - (diagnostics, lints) => { + compiler.hooks.compilation.tap('compilation', async compilation => { + const messages = await tsMessagesPromise; + compilation.errors.push(...messages.errors); + compilation.warnings.push(...messages.warnings); + }); + + forkTsCheckerWebpackPlugin + .getCompilerHooks(compiler) + .receive.tap('afterTypeScriptCheck', (diagnostics, lints) => { const allMsgs = [...diagnostics, ...lints]; const format = message => `${message.file}\n${typescriptFormatter(message, true)}`; @@ -157,8 +169,7 @@ function createCompiler( .filter(msg => msg.severity === 'warning') .map(format), }); - } - ); + }); } // "done" event fires when Webpack has finished recompiling the bundle. @@ -179,7 +190,7 @@ function createCompiler( errors: true, }); - if (useTypeScript && statsData.errors.length === 0) { + if (useTypeScript && statsData.errors.length === 0 && isTsCheckInProgress) { console.log( chalk.yellow( 'Files successfully emitted, waiting for typecheck results...' @@ -189,10 +200,6 @@ function createCompiler( const messages = await tsMessagesPromise; statsData.errors.push(...messages.errors); statsData.warnings.push(...messages.warnings); - // Push errors and warnings into compilation result - // to show them after page refresh triggered by user. - stats.compilation.errors.push(...messages.errors); - stats.compilation.warnings.push(...messages.warnings); if (messages.errors.length > 0) { devSocket.errors(messages.errors); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 982d34e556c..9eca706026a 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -29,7 +29,7 @@ const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent') const paths = require('./paths'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); @@ -620,16 +620,9 @@ module.exports = function(webpackEnv) { basedir: paths.appNodeModules, }), async: isEnvDevelopment, + useTypescriptIncrementalApi: true, checkSyntacticErrors: true, tsconfig: paths.appTsConfig, - compilerOptions: { - module: 'esnext', - moduleResolution: 'node', - resolveJsonModule: true, - isolatedModules: true, - noEmit: true, - jsx: 'preserve', - }, reportFiles: [ '**', '!**/*.json', diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 1f6b8cfafec..4adb362c70f 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -46,7 +46,7 @@ "eslint-plugin-jsx-a11y": "6.1.2", "eslint-plugin-react": "7.11.1", "file-loader": "2.0.0", - "fork-ts-checker-webpack-plugin-alt": "0.4.14", + "fork-ts-checker-webpack-plugin": "1.0.0-alpha.4", "fs-extra": "7.0.0", "html-webpack-plugin": "4.0.0-alpha.2", "identity-obj-proxy": "3.0.0", diff --git a/test/fixtures/typescript-typecheck/src/App.tsx b/test/fixtures/typescript-typecheck/src/App.tsx index 02def3116af..75924d78b0e 100644 --- a/test/fixtures/typescript-typecheck/src/App.tsx +++ b/test/fixtures/typescript-typecheck/src/App.tsx @@ -7,7 +7,7 @@ class App extends React.Component { } function format(value: string) { - return value.toUpperCase(); + return value; } export default App; From 117f142e3030ec94ac239450193baceebe501611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Fri, 25 Jan 2019 10:40:02 +0100 Subject: [PATCH 13/16] Fix failing tests --- .../react-dev-utils/WebpackDevServerUtils.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index f8050733f8d..67f6d4164f7 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -135,27 +135,16 @@ function createCompiler( }); let isFirstCompile = true; - let isTsCheckInProgress = false; let tsMessagesPromise; let tsMessagesResolver; if (useTypeScript) { compiler.hooks.beforeCompile.tap('beforeCompile', () => { - isTsCheckInProgress = true; tsMessagesPromise = new Promise(resolve => { - tsMessagesResolver = msgs => { - isTsCheckInProgress = false; - resolve(msgs); - }; + tsMessagesResolver = msgs => resolve(msgs); }); }); - compiler.hooks.compilation.tap('compilation', async compilation => { - const messages = await tsMessagesPromise; - compilation.errors.push(...messages.errors); - compilation.warnings.push(...messages.warnings); - }); - forkTsCheckerWebpackPlugin .getCompilerHooks(compiler) .receive.tap('afterTypeScriptCheck', (diagnostics, lints) => { @@ -190,17 +179,25 @@ function createCompiler( errors: true, }); - if (useTypeScript && statsData.errors.length === 0 && isTsCheckInProgress) { - console.log( - chalk.yellow( - 'Files successfully emitted, waiting for typecheck results...' - ) - ); + if (useTypeScript && statsData.errors.length === 0) { + const delayedMsg = setTimeout(() => { + console.log( + chalk.yellow( + 'Files successfully emitted, waiting for typecheck results...' + ) + ); + }, 100); const messages = await tsMessagesPromise; + clearTimeout(delayedMsg); statsData.errors.push(...messages.errors); statsData.warnings.push(...messages.warnings); + // Push errors and warnings into compilation result + // to show them after page refresh triggered by user. + stats.compilation.errors.push(...messages.errors); + stats.compilation.warnings.push(...messages.warnings); + if (messages.errors.length > 0) { devSocket.errors(messages.errors); } else if (messages.warnings.length > 0) { From 91c76ade31dc2016b21e031bb934213143d2804a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Fri, 25 Jan 2019 10:43:55 +0100 Subject: [PATCH 14/16] Revert fs-extra dep (bad merge) --- packages/react-scripts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index f125b89cd74..d393b73190b 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -46,7 +46,7 @@ "eslint-plugin-react": "7.12.3", "file-loader": "2.0.0", "fork-ts-checker-webpack-plugin": "1.0.0-alpha.6", - "fs-extra": "7.0.0", + "fs-extra": "7.0.1", "html-webpack-plugin": "4.0.0-alpha.2", "identity-obj-proxy": "3.0.0", "jest": "23.6.0", From 9a8fc4d19eca9aa62b44ccc7d2f748afc8e73c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Fri, 25 Jan 2019 10:57:10 +0100 Subject: [PATCH 15/16] Revert Prettied files --- .github/ISSUE_TEMPLATE.md | 14 +++++++++++--- docusaurus/docs/adding-a-sass-stylesheet.md | 1 - docusaurus/docs/getting-started.md | 2 +- docusaurus/docs/updating-to-new-releases.md | 5 ++--- .../scripts/utils/verifyTypeScriptSetup.js | 4 +--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 2ecabc86e07..2d4f8c8ae01 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -6,6 +6,7 @@ (write your answer here) +