From ec31f7866519f597bed91520537df8097ec7fd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Mo=C4=8Dko=C5=99?= Date: Thu, 19 Mar 2020 21:22:17 +0100 Subject: [PATCH] Fix/typescript support (#214) * fix extensions in paths (and cleanup) * fix paths to allow ts and tsx extensions * get rid of webpack multicompiler mulicompiler was not compatible with TS error handling in CRA --- packages/react-scripts/config/paths.js | 28 +-- packages/react-scripts/scripts/build.js | 197 ++++++++++-------- packages/react-scripts/scripts/start.js | 22 +- .../react-scripts/scripts/utils/getEntries.js | 2 +- .../scripts/utils/getSpaPaths.js | 18 +- 5 files changed, 141 insertions(+), 126 deletions(-) diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index a46d18413b5..a07ec5e0cf8 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -64,19 +64,12 @@ module.exports = { componentsBuild: resolveApp('build/components'), patternsBuild: resolveApp('build/patterns'), appPublic: resolveApp('public'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), componentsDir: resolveApp('src/components'), - componentsJs: resolveApp('src/components/index.js'), - componentsStaticJS: resolveApp('src/components/static.js'), - patternsDir: resolveApp('src/patterns'), - patternsJs: resolveApp('src/patterns/index.js'), - styleguideIndexJs: resolveApp('src/styleguide/index.js'), - styleguideHtml: resolveApp('src/styleguide/styleguide.html'), - scriptsDir: resolveApp('src/scripts'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), @@ -84,7 +77,8 @@ module.exports = { publicUrlOrPath, libDir: resolveApp('src/lib'), icons: resolveApp('src/assets/icons'), - tokens: resolveApp('src/lib/tokens.js'), + tokens: resolveModule(resolveApp, 'src/lib/tokens'), + staticJs: resolveModule(resolveApp, 'src/scripts/index'), }; // @remove-on-eject-begin @@ -98,19 +92,12 @@ module.exports = { componentsBuild: resolveApp('build/components'), patternsBuild: resolveApp('build/patterns'), appPublic: resolveApp('public'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), componentsDir: resolveApp('src/components'), - componentsJs: resolveApp('src/components/index.js'), - componentsStaticJs: resolveApp('src/components/static.js'), - patternsDir: resolveApp('src/patterns'), - patternsJs: resolveApp('src/patterns/index.js'), - styleguideIndexJs: resolveApp('src/styleguide/index.js'), - styleguideHtml: resolveApp('src/styleguide/styleguide.html'), - scriptsDir: resolveApp('src/scripts'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), @@ -118,7 +105,8 @@ module.exports = { publicUrlOrPath, libDir: resolveApp('src/lib'), icons: resolveApp('src/assets/icons'), - tokens: resolveApp('src/lib/tokens.js'), + tokens: resolveModule(resolveApp, 'src/lib/tokens'), + staticJs: resolveModule(resolveApp, 'src/scripts/index'), // These properties only exist before ejecting: ownPath: resolveOwn('.'), ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3 @@ -156,10 +144,8 @@ if ( publicUrlOrPath, componentsDir: resolveApp('src/components'), libDir: resolveApp('src/lib'), - patternsDir: resolveApp('src/patterns'), - styleguideIndexJs: resolveApp('src/styleguide/index.js'), - styleguideHtml: resolveApp('src/styleguide/styleguide.html'), icons: resolveApp('src/assets/icons'), + staticJs: resolveModule(resolveApp, 'src/scripts/index'), // These properties only exist before ejecting: ownPath: resolveOwn('.'), ownNodeModules: resolveOwn('node_modules'), diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index 1573199f260..7437775926f 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -77,100 +77,119 @@ const spaHtmlPaths = Object.entries(spaPaths).reduce((acc, [key, value]) => { return acc; }, {}); -const staticPath = path.join(paths.appSrc, 'scripts', 'index.js'); - -// Generate configuration -const config = [ - configFactory('production', { - entries: { - app: path.join(paths.appSrc, 'index.js'), - ...(fs.existsSync(staticPath) && { static: staticPath }), - ...getEntries('lib', paths.libDir, '/*.{js,scss,css}'), - ...spaEntries, - }, - spaHtmlPaths, - }), - configFactory('lib', { - entries: { - ...getEntries('components', paths.componentsDir, '/*.{js,scss,css}'), - ...getEntries('components', paths.componentsDir, '/**/index.js'), - ...getEntries('components', paths.componentsDir, '/**/*.static.js'), - }, - }), -]; - -// We require that you explicitly set browsers and do not fall back to -// browserslist defaults. -const { checkBrowsers } = require('react-dev-utils/browsersHelper'); -checkBrowsers(paths.appPath, isInteractive) - .then(() => { - // First, read the current file sizes in build directory. - // This lets us display how much they changed later. - return measureFileSizesBeforeBuild(paths.appBuild); - }) - .then(previousFileSizes => { - // Remove all content but keep the directory so that - // if you're in it, you don't end up in Trash - fs.emptyDirSync(paths.appBuild); - // Merge with the public folder - copyPublicFolder(); - // Start the webpack build - return build(previousFileSizes); - }) - .then( - ({ stats, previousFileSizes, warnings }) => { - if (warnings.length) { - console.log(chalk.yellow('Compiled with warnings.\n')); - console.log(warnings.join('\n\n')); - console.log( - '\nSearch for the ' + - chalk.underline(chalk.yellow('keywords')) + - ' to learn more about each warning.' - ); - console.log( - 'To ignore, add ' + - chalk.cyan('// eslint-disable-next-line') + - ' to the line before.\n' - ); - } else { - console.log(chalk.green('Compiled successfully.\n')); +const appConfig = configFactory('production', { + entries: { + app: paths.appIndexJs, + ...(fs.existsSync(paths.staticJs) && { static: paths.staticJs }), + ...getEntries('lib', paths.libDir, '/*.{js,jsx,ts,tsx,scss,css}'), + ...spaEntries, + }, + spaHtmlPaths, +}); + +const componentsConfig = configFactory('lib', { + entries: { + ...getEntries( + 'components', + paths.componentsDir, + '/*.{js,jsx,ts,tsx,scss,css}' + ), + ...getEntries( + 'components', + paths.componentsDir, + '/**/index.{js,jsx,ts,tsx}' + ), + ...getEntries( + 'components', + paths.componentsDir, + '/**/*.static.{js,jsx,ts,tsx}' + ), + }, +}); + +const run = async () => { + await runBuild(appConfig, 'app'); + await runBuild(componentsConfig, 'components'); +}; + +run(); + +async function runBuild(config, buildType) { + // We require that you explicitly set browsers and do not fall back to + // browserslist defaults. + const { checkBrowsers } = require('react-dev-utils/browsersHelper'); + return checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // First, read the current file sizes in build directory. + // This lets us display how much they changed later. + return measureFileSizesBeforeBuild(paths.appBuild); + }) + .then(previousFileSizes => { + if (buildType === 'app') { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); } + // Start the webpack build + return build(previousFileSizes, config, buildType); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow(`Compiled ${buildType} with warnings.\n`)); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green(`Compiled ${buildType} successfully.\n`)); + } - console.log('File sizes after gzip:\n'); - printFileSizesAfterBuild( - stats, - previousFileSizes, - paths.appBuild, - WARN_AFTER_BUNDLE_GZIP_SIZE, - WARN_AFTER_CHUNK_GZIP_SIZE - ); - console.log(); - }, - err => { - const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; - if (tscCompileOnError) { - console.log( - chalk.yellow( - 'Compiled with the following type errors (you may want to check these before deploying your app):\n' - ) + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE ); - printBuildError(err); - } else { - console.log(chalk.red('Failed to compile.\n')); - printBuildError(err); - process.exit(1); + console.log(); + }, + err => { + const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; + if (tscCompileOnError) { + console.log( + chalk.yellow( + 'Compiled with the following buildType errors (you may want to check these before deploying your app):\n' + ) + ); + printBuildError(err); + } else { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } } - } - ) - .catch(err => { - if (err && err.message) { - console.log(err.message); - } - process.exit(1); - }); + ) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); +} // Create the production build and print the deployment instructions. -function build(previousFileSizes) { +function build(previousFileSizes, config, buildType) { // We used to support resolving modules according to `NODE_PATH`. // This now has been deprecated in favor of jsconfig/tsconfig.json // This lets you use absolute paths in imports inside large monorepos: @@ -183,7 +202,7 @@ function build(previousFileSizes) { console.log(); } - console.log('Creating an optimized production build...'); + console.log(`Creating an optimized production build of ${buildType}...`); const compiler = webpack(config); return new Promise((resolve, reject) => { diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index b4dd0f9287d..8f076b15461 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -103,19 +103,15 @@ choosePort(HOST, DEFAULT_PORT) {} ); - const staticPath = path.join(paths.appSrc, 'scripts', 'index.js'); - - const configs = [ - configFactory('development', { - entries: { - app: path.join(paths.appSrc, 'index.js'), - ...(fs.existsSync(staticPath) && { static: staticPath }), - ...getEntries('lib', paths.libDir, '/*.{js,scss,css}'), - ...spaEntries, - }, - spaHtmlPaths, - }), - ]; + const configs = configFactory('development', { + entries: { + app: paths.appIndexJs, + ...(fs.existsSync(paths.staticJs) && { static: paths.staticJs }), + ...getEntries('lib', paths.libDir, '/*.{js,jsx,ts,tsx,scss,css}'), + ...spaEntries, + }, + spaHtmlPaths, + }); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const appName = require(paths.appPackageJson).name; diff --git a/packages/react-scripts/scripts/utils/getEntries.js b/packages/react-scripts/scripts/utils/getEntries.js index dd5e3b8eda0..6dd92839e8d 100644 --- a/packages/react-scripts/scripts/utils/getEntries.js +++ b/packages/react-scripts/scripts/utils/getEntries.js @@ -16,7 +16,7 @@ function getEntries(type, dirPath, globRegex) { let entryName = path // get rid of extension from entry name - .join(type, localPath.split(/(\.js|\.css|\.scss)$/)[0]) + .join(type, localPath.split(/(\.js|\.jsx|\.ts|\.tsx|\.css|\.scss)$/)[0]) // remove leading slash .replace(/^\/|\/$/g, ''); diff --git a/packages/react-scripts/scripts/utils/getSpaPaths.js b/packages/react-scripts/scripts/utils/getSpaPaths.js index e67ccdc54fd..182eb289701 100644 --- a/packages/react-scripts/scripts/utils/getSpaPaths.js +++ b/packages/react-scripts/scripts/utils/getSpaPaths.js @@ -2,9 +2,23 @@ const glob = require('glob'); const path = require('path'); +const fs = require('fs'); const paths = require('../../config/paths'); +const resolveEntry = entryName => { + const entryPath = path.join(paths.appSrc, entryName); + const extension = paths.moduleFileExtensions.find(extension => + fs.existsSync(`${entryPath}.${extension}`) + ); + + if (extension) { + return `${entryPath}.${extension}`; + } + + return `${entryPath}.js`; +}; + function getSpaEntries() { const htmlTemplates = [ ...glob.sync(path.join(paths.appSrc, '*.html')), @@ -15,9 +29,9 @@ function getSpaEntries() { return htmlTemplates.reduce((acc, templatePath) => { const entryName = path.basename(templatePath, '.html'); - acc[entryName] = { + acc[entryName] = { htmlTemplatePath: templatePath, - entryPath: path.join(paths.appSrc, `${entryName}.js`) + entryPath: resolveEntry(entryName), }; return acc;