diff --git a/packages/babel-config/src/__tests__/common.test.ts b/packages/babel-config/src/__tests__/common.test.ts index 3045aa68e985..f5fa4f55eefc 100644 --- a/packages/babel-config/src/__tests__/common.test.ts +++ b/packages/babel-config/src/__tests__/common.test.ts @@ -1,14 +1,6 @@ import { vol } from 'memfs' -import { ensurePosixPath } from '@redwoodjs/project-config' - -import { - getCommonPlugins, - getPathsFromTypeScriptConfig, - parseTypeScriptConfigFiles, -} from '../common' - -jest.mock('fs', () => require('memfs').fs) +import { getCommonPlugins } from '../common' const redwoodProjectPath = '/redwood-app' process.env.RWJS_CWD = redwoodProjectPath @@ -17,11 +9,10 @@ afterEach(() => { vol.reset() }) -describe('common', () => { - it("common plugins haven't changed unintentionally", () => { - const commonPlugins = getCommonPlugins() +test("common plugins haven't changed unintentionally", () => { + const commonPlugins = getCommonPlugins() - expect(commonPlugins).toMatchInlineSnapshot(` + expect(commonPlugins).toMatchInlineSnapshot(` [ [ "@babel/plugin-transform-class-properties", @@ -43,195 +34,4 @@ describe('common', () => { ], ] `) - }) - - describe('TypeScript config files', () => { - it("returns `null` if it can't find TypeScript config files", () => { - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: {}, - web: {}, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - expect(typeScriptConfig).toHaveProperty('api', null) - expect(typeScriptConfig).toHaveProperty('web', null) - }) - - it('finds and parses tsconfig.json files', () => { - const apiTSConfig = '{"compilerOptions": {"noEmit": true}}' - const webTSConfig = '{"compilerOptions": {"allowJs": true}}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'tsconfig.json': apiTSConfig, - }, - web: { - 'tsconfig.json': webTSConfig, - }, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiTSConfig)) - expect(typeScriptConfig.web).toMatchObject(JSON.parse(webTSConfig)) - }) - - it('finds and parses jsconfig.json files', () => { - const apiJSConfig = '{"compilerOptions": {"noEmit": true}}' - const webJSConfig = '{"compilerOptions": {"allowJs": true}}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'jsconfig.json': apiJSConfig, - }, - web: { - 'jsconfig.json': webJSConfig, - }, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiJSConfig)) - expect(typeScriptConfig.web).toMatchObject(JSON.parse(webJSConfig)) - }) - - describe('getPathsFromTypeScriptConfig', () => { - it("returns an empty object if there's no TypeScript config files", () => { - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: {}, - web: {}, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - - const apiPaths = getPathsFromTypeScriptConfig(typeScriptConfig.api) - expect(apiPaths).toMatchObject({}) - - const webPaths = getPathsFromTypeScriptConfig(typeScriptConfig.web) - expect(webPaths).toMatchObject({}) - }) - - it("returns an empty object if there's no compilerOptions, baseUrl, or paths", () => { - const apiTSConfig = '{}' - const webTSConfig = '{"compilerOptions":{"allowJs": true}}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'tsconfig.json': apiTSConfig, - }, - web: { - 'tsconfig.json': webTSConfig, - }, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - - const apiPaths = getPathsFromTypeScriptConfig(typeScriptConfig.api) - expect(apiPaths).toMatchInlineSnapshot(`{}`) - - const webPaths = getPathsFromTypeScriptConfig(typeScriptConfig.web) - expect(webPaths).toMatchInlineSnapshot(`{}`) - }) - - it('excludes "src/*", "$api/*", "types/*", and "@redwoodjs/testing"', () => { - const apiTSConfig = - '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/api/src/*"],"types/*":["./types/*","../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/api"]}}}' - const webTSConfig = - '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/web/src/*"],"$api/*":[ "../api/*" ],"types/*":["./types/*", "../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/web"]}}}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'tsconfig.json': apiTSConfig, - }, - web: { - 'tsconfig.json': webTSConfig, - }, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - - const apiPaths = getPathsFromTypeScriptConfig(typeScriptConfig.api) - expect(apiPaths).toMatchInlineSnapshot(`{}`) - - const webPaths = getPathsFromTypeScriptConfig(typeScriptConfig.web) - expect(webPaths).toMatchInlineSnapshot(`{}`) - }) - - it('gets and formats paths', () => { - const apiTSConfig = - '{"compilerOptions":{"baseUrl":"./","paths":{"@services/*":["./src/services/*"]}}}' - const webTSConfig = - '{"compilerOptions":{"baseUrl":"./","paths":{"@ui/*":["./src/ui/*"]}}}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'tsconfig.json': apiTSConfig, - }, - web: { - 'tsconfig.json': webTSConfig, - }, - }, - redwoodProjectPath - ) - - const typeScriptConfig = parseTypeScriptConfigFiles() - - const apiPaths = getPathsFromTypeScriptConfig(typeScriptConfig.api) - expect(ensurePosixPath(apiPaths['@services'])).toMatchInlineSnapshot( - `"src/services"` - ) - - const webPaths = getPathsFromTypeScriptConfig(typeScriptConfig.web) - expect(ensurePosixPath(webPaths['@ui'])).toMatchInlineSnapshot( - `"src/ui"` - ) - }) - }) - - it('handles invalid JSON', () => { - const apiTSConfig = - '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/api/src/*"],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"]},"typeRoots": ["../node_modules/@types","./node_modules/@types"],"types": ["jest"],},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/api-*","../types"]}' - const webTSConfig = - '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/web/src","../api/src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/web/src/*","../api/src/*","../.redwood/types/mirror/api/src/*"],"$api/*": [ "../api/*" ],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/web"]},"typeRoots": ["../node_modules/@types", "./node_modules/@types"],"types": ["jest", "@testing-library/jest-dom"],"jsx": "preserve",},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/web-*","../types","./types"]}' - - vol.fromNestedJSON( - { - 'redwood.toml': '', - api: { - 'tsconfig.json': apiTSConfig, - }, - web: { - 'tsconfig.json': webTSConfig, - }, - }, - redwoodProjectPath - ) - - expect(parseTypeScriptConfigFiles).not.toThrow() - }) - }) }) diff --git a/packages/babel-config/src/__tests__/tsconfigParsing.test.ts b/packages/babel-config/src/__tests__/tsconfigParsing.test.ts new file mode 100644 index 000000000000..3ca80f58fa43 --- /dev/null +++ b/packages/babel-config/src/__tests__/tsconfigParsing.test.ts @@ -0,0 +1,237 @@ +import { vol } from 'memfs' + +import { ensurePosixPath } from '@redwoodjs/project-config' + +import { + getPathsFromTypeScriptConfig, + parseTypeScriptConfigFiles, +} from '../common' + +jest.mock('fs', () => require('memfs').fs) + +const redwoodProjectPath = '/redwood-app' +process.env.RWJS_CWD = redwoodProjectPath + +afterEach(() => { + vol.reset() +}) + +describe('TypeScript config file parsing', () => { + it("returns `null` if it can't find TypeScript config files", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + web: {}, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig).toHaveProperty('api', null) + expect(typeScriptConfig).toHaveProperty('web', null) + }) + + it('finds and parses tsconfig.json files', () => { + const apiTSConfig = '{"compilerOptions": {"noEmit": true}}' + const webTSConfig = '{"compilerOptions": {"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiTSConfig)) + expect(typeScriptConfig.web).toMatchObject(JSON.parse(webTSConfig)) + }) + + it('finds and parses jsconfig.json files', () => { + const apiJSConfig = '{"compilerOptions": {"noEmit": true}}' + const webJSConfig = '{"compilerOptions": {"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'jsconfig.json': apiJSConfig, + }, + web: { + 'jsconfig.json': webJSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiJSConfig)) + expect(typeScriptConfig.web).toMatchObject(JSON.parse(webJSConfig)) + }) + + it('handles invalid JSON', () => { + const apiTSConfig = + '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/api/src/*"],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"]},"typeRoots": ["../node_modules/@types","./node_modules/@types"],"types": ["jest"],},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/api-*","../types"]}' + const webTSConfig = + '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/web/src","../api/src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/web/src/*","../api/src/*","../.redwood/types/mirror/api/src/*"],"$api/*": [ "../api/*" ],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/web"]},"typeRoots": ["../node_modules/@types", "./node_modules/@types"],"types": ["jest", "@testing-library/jest-dom"],"jsx": "preserve",},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/web-*","../types","./types"]}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + expect(parseTypeScriptConfigFiles).not.toThrow() + }) +}) + +describe('getPathsFromTypeScriptConfig', () => { + const FAKE_API_ROOT = + process.platform === 'win32' ? '/d/redwood-app/api' : '/redwood-app/api' + const FAKE_WEB_ROOT = + process.platform === 'win32' ? '/d/redwood-app/web' : '/redwood-app/web' + + it("returns an empty object if there's no TypeScript config files", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + web: {}, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchObject({}) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchObject({}) + }) + + it("returns an empty object if there's no compilerOptions, baseUrl, or paths", () => { + const apiTSConfig = '{}' + const webTSConfig = '{"compilerOptions":{"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchInlineSnapshot(`{}`) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchInlineSnapshot(`{}`) + }) + + it('excludes "src/*", "$api/*", "types/*", and "@redwoodjs/testing"', () => { + const apiTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/api/src/*"],"types/*":["./types/*","../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/api"]}}}' + const webTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/web/src/*"],"$api/*":[ "../api/*" ],"types/*":["./types/*", "../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/web"]}}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchInlineSnapshot(`{}`) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchInlineSnapshot(`{}`) + }) + + it('gets and formats paths', () => { + const apiTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"@services/*":["./src/services/*"]}}}' + const webTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"@ui/*":["./src/ui/*"]}}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + + expect(ensurePosixPath(apiPaths['@services'])).toEqual( + ensurePosixPath(`${FAKE_API_ROOT}/src/services`) + ) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(ensurePosixPath(webPaths['@ui'])).toEqual( + ensurePosixPath(`${FAKE_WEB_ROOT}/src/ui`) + ) + }) +}) diff --git a/packages/babel-config/src/api.ts b/packages/babel-config/src/api.ts index 604a02c7a7aa..73dcaceacf51 100644 --- a/packages/babel-config/src/api.ts +++ b/packages/babel-config/src/api.ts @@ -87,7 +87,7 @@ export const getApiSideBabelPlugins = ( alias: { src: './src', // adds the paths from [ts|js]config.json to the module resolver - ...getPathsFromTypeScriptConfig(tsConfig.api), + ...getPathsFromTypeScriptConfig(tsConfig.api, getPaths().api.base), }, root: [getPaths().api.base], cwd: 'packagejson', diff --git a/packages/babel-config/src/common.ts b/packages/babel-config/src/common.ts index 7987da35a661..17e737b01c77 100644 --- a/packages/babel-config/src/common.ts +++ b/packages/babel-config/src/common.ts @@ -99,14 +99,19 @@ export const parseTypeScriptConfigFiles = () => { } } +type CompilerOptionsForPaths = { + compilerOptions: { baseUrl: string; paths: string } +} /** * Extracts and formats the paths from the [ts|js]config.json file * @param config The config object + * @param rootDir {string} Where the jsconfig/tsconfig is loaded from * @returns {Record} The paths object */ -export const getPathsFromTypeScriptConfig = (config: { - compilerOptions: { baseUrl: string; paths: string } -}): Record => { +export const getPathsFromTypeScriptConfig = ( + config: CompilerOptionsForPaths, + rootDir: string +): Record => { if (!config) { return {} } @@ -116,6 +121,12 @@ export const getPathsFromTypeScriptConfig = (config: { } const { baseUrl, paths } = config.compilerOptions + + // Convert it to absolute path - on windows the baseUrl is already absolute + const absoluteBase = path.isAbsolute(baseUrl) + ? baseUrl + : path.join(rootDir, baseUrl) + const pathsObj: Record = {} for (const [key, value] of Object.entries(paths)) { // exclude the default paths that are included in the tsconfig.json file @@ -128,9 +139,10 @@ export const getPathsFromTypeScriptConfig = (config: { } const aliasKey = key.replace('/*', '') const aliasValue = path.join( - baseUrl, + absoluteBase, (value as string)[0].replace('/*', '') ) + pathsObj[aliasKey] = aliasValue } return pathsObj diff --git a/packages/babel-config/src/web.ts b/packages/babel-config/src/web.ts index 22c99ee1f897..e7b1f011db9b 100644 --- a/packages/babel-config/src/web.ts +++ b/packages/babel-config/src/web.ts @@ -36,7 +36,7 @@ export const getWebSideBabelPlugins = ( // the `cwd`: https://github.com/facebook/jest/issues/7359 forJest ? rwjsPaths.web.src : './src', // adds the paths from [ts|js]config.json to the module resolver - ...getPathsFromTypeScriptConfig(tsConfigs.web), + ...getPathsFromTypeScriptConfig(tsConfigs.web, rwjsPaths.web.base), }, root: [rwjsPaths.web.base], cwd: 'packagejson',