diff --git a/app/react/src/server/__mocks__/mockConfig.js b/app/react/src/server/__mocks__/mockConfig.js index 509fd2c7cbfd..34b2b5df9687 100644 --- a/app/react/src/server/__mocks__/mockConfig.js +++ b/app/react/src/server/__mocks__/mockConfig.js @@ -8,6 +8,7 @@ export default { alias: { baseAlias: 'base-alias', }, + modules: [], }, module: { noParse: /jquery/, diff --git a/app/react/src/server/__snapshots__/cra-config.test.js.snap b/app/react/src/server/__snapshots__/cra-config.test.js.snap index 13c99b384187..73d95cb829ec 100644 --- a/app/react/src/server/__snapshots__/cra-config.test.js.snap +++ b/app/react/src/server/__snapshots__/cra-config.test.js.snap @@ -46,6 +46,7 @@ Object { ".js", ".jsx", ], + "modules": Array [], }, "resolveLoader": Object { "modules": Array [ @@ -115,6 +116,7 @@ Object { ".ts", ".tsx", ], + "modules": Array [], }, "resolveLoader": Object { "modules": Array [ diff --git a/app/react/src/server/cra-config.js b/app/react/src/server/cra-config.js index 403458403e5b..15be0ff52571 100644 --- a/app/react/src/server/cra-config.js +++ b/app/react/src/server/cra-config.js @@ -5,6 +5,10 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import { normalizeCondition } from 'webpack/lib/RuleSet'; import { logger } from '@storybook/node-logger'; +const JSCONFIG = 'jsconfig.json'; +const TSCONFIG = 'tsconfig.json'; + +const appDirectory = fs.realpathSync(process.cwd()); const cssExtensions = ['.css', '.scss', '.sass']; const cssModuleExtensions = ['.module.css', '.module.scss', '.module.sass']; const typeScriptExtensions = ['.ts', '.tsx']; @@ -14,7 +18,6 @@ let reactScriptsPath; export function getReactScriptsPath({ noCache } = {}) { if (reactScriptsPath && !noCache) return reactScriptsPath; - const appDirectory = fs.realpathSync(process.cwd()); let reactScriptsScriptPath = fs.realpathSync( path.join(appDirectory, '/node_modules/.bin/react-scripts') ); @@ -106,6 +109,23 @@ export const getTypeScriptRules = (webpackConfigRules, configDir) => { ]; }; +export const getModulePath = () => { + // As with CRA, we only support `jsconfig.json` if `tsconfig.json` doesn't exist. + let configName; + if (fs.existsSync(path.join(appDirectory, TSCONFIG))) { + configName = TSCONFIG; + } else if (fs.existsSync(path.join(appDirectory, JSCONFIG))) { + configName = JSCONFIG; + } + + if (configName) { + // eslint-disable-next-line import/no-dynamic-require,global-require + const config = require(path.join(appDirectory, configName)); + return config.compilerOptions && config.compilerOptions.baseUrl; + } + return false; +}; + function mergePlugins(basePlugins, additionalPlugins) { return [...basePlugins, ...additionalPlugins].reduce((plugins, plugin) => { if ( @@ -146,6 +166,10 @@ export function applyCRAWebpackConfig(baseConfig, configDir) { const tsExtensions = hasTsSupport ? typeScriptExtensions : []; const extensions = [...cssExtensions, ...tsExtensions]; + // Support for this was added in `react-scripts@3.0.0`. + // https://github.com/facebook/create-react-app/pull/6656 + const modulePath = isReactScriptsInstalled('3.0.0') && getModulePath(); + // Remove any rules from baseConfig that test true for any one of the extensions const filteredBaseRules = baseConfig.module.rules.filter( rule => !rule.test || !extensions.some(normalizeCondition(rule.test)) @@ -175,6 +199,7 @@ export function applyCRAWebpackConfig(baseConfig, configDir) { resolve: { ...baseConfig.resolve, extensions: [...baseConfig.resolve.extensions, ...tsExtensions], + modules: baseConfig.resolve.modules.concat(modulePath || []), }, resolveLoader: { modules: ['node_modules', path.join(getReactScriptsPath(), 'node_modules')], diff --git a/app/react/src/server/cra-config.test.js b/app/react/src/server/cra-config.test.js index 659b632ac1d6..f7afea9a12e8 100644 --- a/app/react/src/server/cra-config.test.js +++ b/app/react/src/server/cra-config.test.js @@ -1,13 +1,18 @@ import fs from 'fs'; import path from 'path'; -import { getReactScriptsPath, getTypeScriptRules, applyCRAWebpackConfig } from './cra-config'; +import { + applyCRAWebpackConfig, + getModulePath, + getReactScriptsPath, + getTypeScriptRules, +} from './cra-config'; import mockRules from './__mocks__/mockRules'; import mockConfig from './__mocks__/mockConfig'; jest.mock('fs', () => ({ - realpathSync: jest.fn(), + realpathSync: jest.fn(() => '/test-project'), readFileSync: jest.fn(), - existsSync: () => true, + existsSync: jest.fn(() => true), })); jest.mock('mini-css-extract-plugin', () => {}); @@ -16,11 +21,6 @@ const SCRIPT_PATH = '.bin/react-scripts'; const stripCwd = loaderPath => loaderPath.replace(process.cwd(), ''); describe('cra-config', () => { - beforeEach(() => { - fs.realpathSync.mockReset(); - fs.realpathSync.mockImplementationOnce(() => '/test-project'); - }); - describe('when used with the default react-scripts package', () => { beforeEach(() => { fs.realpathSync.mockImplementationOnce(filePath => @@ -94,6 +94,21 @@ exit $ret` const rules = getTypeScriptRules(mockRules, './.storybook'); expect(rules[0].include.findIndex(string => string.includes('.storybook'))).toEqual(1); }); + + it('should get the baseUrl from a tsconfig.json', () => { + jest.spyOn(path, 'join').mockImplementation(() => 'project/tsconfig.json'); + jest.mock( + 'project/tsconfig.json', + () => ({ + compilerOptions: { + baseUrl: 'src', + }, + }), + { virtual: true } + ); + expect(getModulePath()).toEqual('src'); + path.join.mockRestore(); + }); }); describe('when used with react-scripts < 2.1.0', () => { diff --git a/examples/cra-ts-kitchen-sink/.env b/examples/cra-ts-kitchen-sink/.env index e70a111949cd..34d70579f45a 100644 --- a/examples/cra-ts-kitchen-sink/.env +++ b/examples/cra-ts-kitchen-sink/.env @@ -1,2 +1,2 @@ SKIP_PREFLIGHT_CHECK=true -NODE_PATH=src \ No newline at end of file +NODE_PATH=src