diff --git a/.babelrc-v9.json b/.babelrc-v9.json index 5a8a73d837805..80848a9e62503 100644 --- a/.babelrc-v9.json +++ b/.babelrc-v9.json @@ -1,22 +1,9 @@ { "presets": [ [ - "@griffel", + "@fluentui/scripts-babel/preset-v9", { - "babelOptions": { - "plugins": [ - [ - "babel-plugin-module-resolver", - { - "root": ["../../../"], - "alias": { - "@fluentui/tokens": "packages/tokens/lib/index.js", - "^@fluentui/(?!react-icons)(.+)": "packages/react-components/\\1/lib/index.js" - } - } - ] - ] - } + "tsBaseConfigPath": "./tsconfig.base.json" } ] ] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 85dac4ae69d1e..5675c992ad76f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -52,6 +52,7 @@ /yarn.lock @microsoft/fluentui-react-build /*.config.js @microsoft/fluentui-react-build /typings @microsoft/fluentui-react-build +/.babelrc-v9.json @microsoft/fluentui-react-build #### NX related files /nx.json @microsoft/fluentui-react-build diff --git a/scripts/babel/jest.config.js b/scripts/babel/jest.config.js index 2c367f7b9b2b0..61ae84e2393cf 100644 --- a/scripts/babel/jest.config.js +++ b/scripts/babel/jest.config.js @@ -9,6 +9,12 @@ module.exports = { transform: { '^.+\\.tsx?$': 'ts-jest', }, + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + isolatedModules: true, + }, + }, coverageDirectory: './coverage', testEnvironment: 'node', }; diff --git a/scripts/babel/preset-v9.js b/scripts/babel/preset-v9.js new file mode 100644 index 0000000000000..5734d8c36c1f8 --- /dev/null +++ b/scripts/babel/preset-v9.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); + +const { workspaceRoot } = require('@nrwl/devkit'); + +const cwd = process.cwd(); +const rootOffset = path.relative(cwd, workspaceRoot); + +/** + * + * @param {{tsBaseConfigPath:string}} options + * @returns + */ +function createModuleResolverAliases(options) { + const { tsBaseConfigPath } = options; + const tsBaseConfigPathAbsolute = path.join(workspaceRoot, tsBaseConfigPath); + const tsConfigBase = JSON.parse(fs.readFileSync(tsBaseConfigPathAbsolute, 'utf-8')); + + /** + * @type {Record} + */ + const allPathAliases = tsConfigBase.compilerOptions.paths; + return Object.entries(allPathAliases).reduce((acc, [packageName, aliasTuple]) => { + const tsSourceRoot = aliasTuple[0]; + const jsSourceRoot = tsSourceRoot.replace('src', 'lib').replace('.ts', '.js'); + const aliasRegex = `^${packageName}$`; + acc[aliasRegex] = `${rootOffset}/${jsSourceRoot}`; + return acc; + }, /** @type {Record} */ ({})); +} + +/** @type {Array} */ +const presets = []; + +/** @type {Array} */ +const plugins = []; + +/** + * @param {import('@babel/core').ConfigAPI} api + * @param {import('./types').BabelPresetOptions} options + */ +const preset = (api, options) => { + const moduleResolverAliasMappings = createModuleResolverAliases(options); + /** @type {Array} */ + const dynamicPresets = [ + [ + '@griffel', + { + babelOptions: { + plugins: [ + [ + 'babel-plugin-module-resolver', + { + alias: moduleResolverAliasMappings, + }, + ], + ], + }, + }, + ], + ]; + + presets.push(...dynamicPresets); + + return { presets, plugins }; +}; + +module.exports = preset; diff --git a/scripts/babel/preset-v9.spec.ts b/scripts/babel/preset-v9.spec.ts new file mode 100644 index 0000000000000..bbc82a40ccd12 --- /dev/null +++ b/scripts/babel/preset-v9.spec.ts @@ -0,0 +1,44 @@ +import preset from './preset-v9'; + +describe(`babel preset v9`, () => { + const babelMockedApi = { + assertVersion: jest.fn(), + caller: jest.fn(), + env: jest.fn(), + cache: {}, + version: '0', + } as unknown as import('@babel/core').ConfigAPI; + it(`should generate module-resolve alias mappings for @griffel preset`, () => { + const presetConfig = preset(babelMockedApi, { tsBaseConfigPath: './tsconfig.base.json' }); + + const griffelPreset = presetConfig.presets.find(presetItem => presetItem[0] === '@griffel')!; + const griffelPresetConfig = griffelPreset[1]; + + expect(griffelPresetConfig).toEqual({ + babelOptions: { + plugins: [ + [ + 'babel-plugin-module-resolver', + { + alias: expect.any(Object), + }, + ], + ], + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const alias = (griffelPresetConfig as any).babelOptions.plugins[0][1].alias; + const aliasesEntries = Object.entries(alias); + + /** + * we pick 5th entry from the big list of path aliases entries. there is nothing special about it and we can pick any existing entry index. + * I chose to not pick first nor last. In general this is an integration test that checks if we would introduce some violation within tsconfig.base.json path aliases config + */ + const aliasDeclaration = aliasesEntries[5]; + const [aliasKey, aliasPath] = aliasDeclaration; + + expect(aliasKey).toEqual(expect.stringContaining('^@fluentui/')); + expect(aliasPath).toEqual(expect.stringMatching(/[../]+[a-z-/]+\/lib\/index\.js/)); + }); +}); diff --git a/scripts/babel/types.ts b/scripts/babel/types.ts new file mode 100644 index 0000000000000..a0dcb31cfb32d --- /dev/null +++ b/scripts/babel/types.ts @@ -0,0 +1,7 @@ +import type { PluginTarget } from '@babel/core'; + +export interface BabelPresetOptions { + tsBaseConfigPath: string; +} + +export type BabelPluginItem = [PluginTarget, Record];