From c8a264a9326cd8c4e53bd5918fae9332a0576b3a Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Tue, 30 May 2023 01:33:08 -0400 Subject: [PATCH] fix(react): update .swcrc with plugin for CSS-in-JS solution when SWC is used for apps (#17295) --- .../src/generators/application/application.ts | 6 +- .../src/generators/component/component.ts | 8 +- packages/next/src/generators/page/page.ts | 8 +- packages/next/src/utils/styles.ts | 69 ++++++++++---- packages/react/index.ts | 2 +- .../src/generators/application/application.ts | 2 +- .../files/base-webpack/.babelrc__tmpl__ | 13 --- .../lib/create-application-files.ts | 93 +++++++++++++++++-- .../src/generators/component/component.ts | 2 +- .../src/rules/add-styled-dependencies.ts | 15 ++- packages/react/src/utils/styled.ts | 38 +++++++- packages/react/src/utils/versions.ts | 4 + 12 files changed, 208 insertions(+), 52 deletions(-) delete mode 100644 packages/react/src/generators/application/files/base-webpack/.babelrc__tmpl__ diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts index 81325c356b..5bb9aad0c7 100644 --- a/packages/next/src/generators/application/application.ts +++ b/packages/next/src/generators/application/application.ts @@ -1,6 +1,7 @@ import { convertNxGenerator, formatFiles, + joinPathFragments, runTasksInSerial, Tree, } from '@nx/devkit'; @@ -36,7 +37,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) { const lintTask = await addLinting(host, options); updateJestConfig(host, options); updateCypressTsConfig(host, options); - const styledTask = addStyleDependencies(host, options.style); + const styledTask = addStyleDependencies(host, { + style: options.style, + swc: !host.exists(joinPathFragments(options.appProjectRoot, '.babelrc')), + }); setDefaults(host, options); if (options.customServer) { diff --git a/packages/next/src/generators/component/component.ts b/packages/next/src/generators/component/component.ts index 8fb53e76bf..2f2a542af3 100644 --- a/packages/next/src/generators/component/component.ts +++ b/packages/next/src/generators/component/component.ts @@ -2,6 +2,8 @@ import { convertNxGenerator, formatFiles, getProjects, + joinPathFragments, + readProjectConfiguration, runTasksInSerial, Tree, } from '@nx/devkit'; @@ -37,6 +39,7 @@ function getDirectory(host: Tree, options: Schema) { * extra dependencies for css, sass, less, styl style options. */ export async function componentGenerator(host: Tree, options: Schema) { + const project = readProjectConfiguration(host, options.project); const componentInstall = await reactComponentGenerator(host, { ...options, directory: getDirectory(host, options), @@ -45,7 +48,10 @@ export async function componentGenerator(host: Tree, options: Schema) { skipFormat: true, }); - const styledInstall = addStyleDependencies(host, options.style); + const styledInstall = addStyleDependencies(host, { + style: options.style, + swc: !host.exists(joinPathFragments(project.root, '.babelrc')), + }); if (!options.skipFormat) { await formatFiles(host); diff --git a/packages/next/src/generators/page/page.ts b/packages/next/src/generators/page/page.ts index 0a7befd299..9732a0cfa6 100644 --- a/packages/next/src/generators/page/page.ts +++ b/packages/next/src/generators/page/page.ts @@ -2,6 +2,8 @@ import { componentGenerator as reactComponentGenerator } from '@nx/react'; import { convertNxGenerator, formatFiles, + joinPathFragments, + readProjectConfiguration, runTasksInSerial, Tree, } from '@nx/devkit'; @@ -15,6 +17,7 @@ import { Schema } from './schema'; * it is under `pages` folder. */ export async function pageGenerator(host: Tree, options: Schema) { + const project = readProjectConfiguration(host, options.project); const directory = options.directory ? `pages/${options.directory}` : 'pages'; const componentTask = await reactComponentGenerator(host, { ...options, @@ -29,7 +32,10 @@ export async function pageGenerator(host: Tree, options: Schema) { skipFormat: true, }); - const styledTask = addStyleDependencies(host, options.style); + const styledTask = addStyleDependencies(host, { + style: options.style, + swc: !host.exists(joinPathFragments(project.root, '.babelrc')), + }); if (!options.skipFormat) { await formatFiles(host); diff --git a/packages/next/src/utils/styles.ts b/packages/next/src/utils/styles.ts index 6bc5902a8b..74e3e20402 100644 --- a/packages/next/src/utils/styles.ts +++ b/packages/next/src/utils/styles.ts @@ -6,7 +6,10 @@ import { } from '@nx/devkit'; import { lessVersion, stylusVersion } from '@nx/react/src/utils/versions'; -import { CSS_IN_JS_DEPENDENCIES } from '@nx/react/src/utils/styled'; +import { + cssInJsDependenciesBabel, + cssInJsDependenciesSwc, +} from '@nx/react/src/utils/styled'; import { babelPluginStyledComponentsVersion, emotionServerVersion, @@ -15,21 +18,7 @@ import { stylusLoader, } from './versions'; -export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = { - 'styled-components': { - dependencies: CSS_IN_JS_DEPENDENCIES['styled-components'].dependencies, - devDependencies: { - ...CSS_IN_JS_DEPENDENCIES['styled-components'].devDependencies, - 'babel-plugin-styled-components': babelPluginStyledComponentsVersion, - }, - }, - '@emotion/styled': { - dependencies: { - ...CSS_IN_JS_DEPENDENCIES['@emotion/styled'].dependencies, - '@emotion/server': emotionServerVersion, - }, - devDependencies: CSS_IN_JS_DEPENDENCIES['@emotion/styled'].devDependencies, - }, +const nextSpecificStyleDependenciesCommon = { css: { dependencies: {}, devDependencies: {}, @@ -54,11 +43,50 @@ export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = { }, }; +export const nextSpecificStyleDependenciesBabel = { + ...nextSpecificStyleDependenciesCommon, + 'styled-components': { + dependencies: cssInJsDependenciesBabel['styled-components'].dependencies, + devDependencies: { + ...cssInJsDependenciesBabel['styled-components'].devDependencies, + 'babel-plugin-styled-components': babelPluginStyledComponentsVersion, + }, + }, + '@emotion/styled': { + dependencies: { + ...cssInJsDependenciesBabel['@emotion/styled'].dependencies, + '@emotion/server': emotionServerVersion, + }, + devDependencies: + cssInJsDependenciesBabel['@emotion/styled'].devDependencies, + }, +}; + +export const nextSpecificStyleDependenciesSwc = { + ...nextSpecificStyleDependenciesCommon, + 'styled-components': { + dependencies: cssInJsDependenciesSwc['styled-components'].dependencies, + devDependencies: { + ...cssInJsDependenciesSwc['styled-components'].devDependencies, + 'babel-plugin-styled-components': babelPluginStyledComponentsVersion, + }, + }, + '@emotion/styled': { + dependencies: { + ...cssInJsDependenciesSwc['@emotion/styled'].dependencies, + '@emotion/server': emotionServerVersion, + }, + devDependencies: cssInJsDependenciesSwc['@emotion/styled'].devDependencies, + }, +}; + export function addStyleDependencies( host: Tree, - style: string + options: { style?: string; swc?: boolean } ): GeneratorCallback { - const extraDependencies = NEXT_SPECIFIC_STYLE_DEPENDENCIES[style]; + const extraDependencies = options.swc + ? nextSpecificStyleDependenciesSwc[options.style] + : nextSpecificStyleDependenciesBabel[options.style]; if (!extraDependencies) return () => {}; @@ -70,7 +98,10 @@ export function addStyleDependencies( // @zeit/next-less & @zeit/next-stylus internal configuration is working only // for specific CSS loader version, causing PNPM resolution to fail. - if (host.exists('pnpm-lock.yaml') && (style === 'less' || style === 'styl')) { + if ( + host.exists('pnpm-lock.yaml') && + (options.style === 'less' || options.style === 'styl') + ) { updateJson(host, `package.json`, (json) => { json.resolutions = { ...json.resolutions, 'css-loader': '1.0.1' }; return json; diff --git a/packages/react/index.ts b/packages/react/index.ts index c028d98886..5940f9bbee 100644 --- a/packages/react/index.ts +++ b/packages/react/index.ts @@ -2,7 +2,7 @@ export { extraEslintDependencies, extendReactEslintJson, } from './src/utils/lint'; -export { CSS_IN_JS_DEPENDENCIES } from './src/utils/styled'; +export { cssInJsDependenciesBabel } from './src/utils/styled'; export { assertValidStyle } from './src/utils/assertion'; export { reactDomVersion, reactVersion } from './src/utils/versions'; export { applicationGenerator } from './src/generators/application/application'; diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 7b6692ed13..fa6e7814db 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -205,7 +205,7 @@ export async function applicationGenerator( updateSpecConfig(host, options); const stylePreprocessorTask = installCommonDependencies(host, options); tasks.push(stylePreprocessorTask); - const styledTask = addStyledModuleDependencies(host, options.styledModule); + const styledTask = addStyledModuleDependencies(host, options); tasks.push(styledTask); const routingTask = addRouting(host, options); tasks.push(routingTask); diff --git a/packages/react/src/generators/application/files/base-webpack/.babelrc__tmpl__ b/packages/react/src/generators/application/files/base-webpack/.babelrc__tmpl__ deleted file mode 100644 index 88746cdac6..0000000000 --- a/packages/react/src/generators/application/files/base-webpack/.babelrc__tmpl__ +++ /dev/null @@ -1,13 +0,0 @@ -{ - "presets": [ - [ - "@nx/react/babel", { - "runtime": "automatic"<% if (style === '@emotion/styled') { %>,<% } %> - <% if (style === '@emotion/styled') { %>"importSource": "@emotion/react" <% } %> - } - ] - ], - "plugins": [ - <% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %><% if (style === 'styled-jsx') { %>"styled-jsx/babel"<% } %><% if (style === '@emotion/styled') { %>"@emotion/babel-plugin"<% } %> - ] -} diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 7245c3d962..5c1743be78 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -1,4 +1,11 @@ -import { names, offsetFromRoot, Tree, toJS, generateFiles } from '@nx/devkit'; +import { + generateFiles, + names, + offsetFromRoot, + toJS, + Tree, + writeJson, +} from '@nx/devkit'; import { getRelativePathToRootTsConfig } from '@nx/js'; import { join } from 'path'; import { createTsConfig } from '../../../utils/create-ts-config'; @@ -34,16 +41,86 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { inSourceVitestTests: getInSourceVitestTestsTemplate(appTests), }; - let fileDirectory: string; if (options.bundler === 'vite') { - fileDirectory = join(__dirname, '../files/base-vite'); + generateFiles( + host, + join(__dirname, '../files/base-vite'), + options.appProjectRoot, + templateVariables + ); } else if (options.bundler === 'webpack') { - fileDirectory = join(__dirname, '../files/base-webpack'); - } else if (options.bundler === 'rspack') { - fileDirectory = join(__dirname, '../files/base-rspack'); - } + generateFiles( + host, + join(__dirname, '../files/base-webpack'), + options.appProjectRoot, + templateVariables + ); + if (options.compiler === 'babel') { + writeJson(host, `${options.appProjectRoot}/.babelrc`, { + presets: [ + [ + '@nx/react/babel', + { + runtime: 'automatic', + importSource: + options.style === '@emotion/styled' + ? '@emotion/react' + : undefined, + }, + ], + ], + plugins: [ + options.style === 'styled-components' + ? ['styled-components', { pure: true, ssr: true }] + : undefined, + options.style === 'styled-jsx' ? 'styled-jsx/babel' : undefined, + options.style === '@emotion/styled' + ? '@emotion/babel-plugin' + : undefined, + ].filter(Boolean), + }); + } else if ( + options.style === 'styled-components' || + options.style === '@emotion/styled' || + options.style === 'styled-jsx' + ) { + writeJson( + host, + `${options.appProjectRoot}/.swcrc`, - generateFiles(host, fileDirectory, options.appProjectRoot, templateVariables); + { + jsc: { + experimental: { + plugins: [ + options.style === 'styled-components' + ? [ + '@swc/plugin-styled-components', + { + displayName: true, + ssr: true, + }, + ] + : undefined, + options.style === 'styled-jsx' + ? ['@swc/plugin-styled-jsx', {}] + : undefined, + options.style === '@emotion/styled' + ? ['@swc/plugin-emotion', {}] + : undefined, + ].filter(Boolean), + }, + }, + } + ); + } + } else if (options.bundler === 'rspack') { + generateFiles( + host, + join(__dirname, '../files/base-rspack'), + options.appProjectRoot, + templateVariables + ); + } if ( options.unitTestRunner === 'none' || diff --git a/packages/react/src/generators/component/component.ts b/packages/react/src/generators/component/component.ts index a5911cee3d..aa4f203378 100644 --- a/packages/react/src/generators/component/component.ts +++ b/packages/react/src/generators/component/component.ts @@ -30,7 +30,7 @@ export async function componentGenerator(host: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; - const styledTask = addStyledModuleDependencies(host, options.styledModule); + const styledTask = addStyledModuleDependencies(host, options); tasks.push(styledTask); addExportsToBarrel(host, options); diff --git a/packages/react/src/rules/add-styled-dependencies.ts b/packages/react/src/rules/add-styled-dependencies.ts index 9bd910121a..1524d41142 100644 --- a/packages/react/src/rules/add-styled-dependencies.ts +++ b/packages/react/src/rules/add-styled-dependencies.ts @@ -1,8 +1,17 @@ -import { CSS_IN_JS_DEPENDENCIES } from '../utils/styled'; import { addDependenciesToPackageJson, Tree } from '@nx/devkit'; +import { + cssInJsDependenciesBabel, + cssInJsDependenciesSwc, +} from '../utils/styled'; -export function addStyledModuleDependencies(host: Tree, styledModule: string) { - const extraDependencies = CSS_IN_JS_DEPENDENCIES[styledModule]; +export function addStyledModuleDependencies( + host: Tree, + options: { styledModule?: string; compiler?: 'babel' | 'swc' } +) { + const extraDependencies = + options.compiler === 'swc' + ? cssInJsDependenciesSwc[options.styledModule] + : cssInJsDependenciesBabel[options.styledModule]; if (extraDependencies) { return addDependenciesToPackageJson( diff --git a/packages/react/src/utils/styled.ts b/packages/react/src/utils/styled.ts index c788d43a55..14b941b408 100644 --- a/packages/react/src/utils/styled.ts +++ b/packages/react/src/utils/styled.ts @@ -6,14 +6,15 @@ import { reactIsVersion, styledComponentsVersion, styledJsxVersion, + swcPluginEmotionVersion, + swcPluginStyledComponentsVersion, + swcPluginStyledJsxVersion, typesReactIsVersion, typesStyledComponentsVersion, } from './versions'; import { PackageDependencies } from './dependencies'; -export const CSS_IN_JS_DEPENDENCIES: { - [style: string]: PackageDependencies; -} = { +export const cssInJsDependenciesBabel: Record = { 'styled-components': { dependencies: { 'react-is': reactIsVersion, @@ -41,3 +42,34 @@ export const CSS_IN_JS_DEPENDENCIES: { devDependencies: {}, }, }; + +export const cssInJsDependenciesSwc: Record = { + 'styled-components': { + dependencies: { + 'react-is': reactIsVersion, + 'styled-components': styledComponentsVersion, + }, + devDependencies: { + '@types/styled-components': typesStyledComponentsVersion, + '@types/react-is': typesReactIsVersion, + '@swc/plugin-styled-components': swcPluginStyledComponentsVersion, + }, + }, + '@emotion/styled': { + dependencies: { + '@emotion/styled': emotionStyledVersion, + '@emotion/react': emotionReactVersion, + }, + devDependencies: { + '@swc/plugin-emotion': swcPluginEmotionVersion, + }, + }, + 'styled-jsx': { + dependencies: { + 'styled-jsx': styledJsxVersion, + }, + devDependencies: { + '@swc/plugin-styled-jsx': swcPluginStyledJsxVersion, + }, + }, +}; diff --git a/packages/react/src/utils/versions.ts b/packages/react/src/utils/versions.ts index 10fa58e1d1..3393c5cbc0 100755 --- a/packages/react/src/utils/versions.ts +++ b/packages/react/src/utils/versions.ts @@ -63,3 +63,7 @@ export const stylusVersion = '^0.55.0'; // rollup plugins (if needed) export const rollupPluginUrlVersion = '^7.0.0'; export const svgrRollupVersion = '^8.0.1'; + +export const swcPluginStyledJsxVersion = '^1.5.67'; +export const swcPluginEmotionVersion = '^2.5.67'; +export const swcPluginStyledComponentsVersion = '^1.5.67';