fix(nextjs): adjust generated CSS-in-JS to match latest Next.js recommendations (#17294)
This commit is contained in:
parent
911f753763
commit
a7c6d5aadb
@ -1,18 +1,11 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
isNotWindows,
|
||||
killPorts,
|
||||
newProject,
|
||||
runCLI,
|
||||
runCommandUntil,
|
||||
tmpProjPath,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e/utils';
|
||||
import { getData } from 'ajv/dist/compile/validate';
|
||||
import { detectPackageManager } from 'nx/src/utils/package-manager';
|
||||
import { checkApp } from './utils';
|
||||
import { p } from 'vitest/dist/types-b7007192';
|
||||
|
||||
describe('Next.js App Router', () => {
|
||||
let proj: string;
|
||||
|
||||
@ -58,6 +58,19 @@ describe('Next.js apps', () => {
|
||||
checkExport: false,
|
||||
});
|
||||
|
||||
const scAppWithAppRouter = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/next:app ${scAppWithAppRouter} --no-interactive --style=styled-components --appDir=true`
|
||||
);
|
||||
|
||||
await checkApp(scAppWithAppRouter, {
|
||||
checkUnitTest: false, // No unit tests for app router
|
||||
checkLint: false,
|
||||
checkE2E: false,
|
||||
checkExport: false,
|
||||
});
|
||||
|
||||
const emotionApp = uniq('app');
|
||||
|
||||
runCLI(
|
||||
|
||||
@ -15,7 +15,122 @@ describe('app', () => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
});
|
||||
|
||||
describe('not nested', () => {
|
||||
it('should add a .gitkeep file to the public directory', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
expect(tree.exists('apps/my-app/public/.gitkeep')).toBe(true);
|
||||
});
|
||||
|
||||
it('should update tags and implicit dependencies', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
tags: 'one,two',
|
||||
});
|
||||
|
||||
const projects = Object.fromEntries(getProjects(tree));
|
||||
|
||||
expect(projects).toMatchObject({
|
||||
'my-app': {
|
||||
tags: ['one', 'two'],
|
||||
},
|
||||
'my-app-e2e': {
|
||||
tags: [],
|
||||
implicitDependencies: ['my-app'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
|
||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'apps/my-app/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.json');
|
||||
});
|
||||
|
||||
describe('App Router', () => {
|
||||
it('should generate files for app layout', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'apps/test-app/tsconfig.json');
|
||||
expect(tsConfig.include).toEqual([
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'../../apps/test-app/.next/types/**/*.ts',
|
||||
'../../dist/apps/test-app/.next/types/**/*.ts',
|
||||
'next-env.d.ts',
|
||||
]);
|
||||
expect(tree.exists('apps/test-app/pages/styles.css')).toBeFalsy();
|
||||
expect(tree.exists('apps/test-app/app/global.css')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/page.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/layout.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/api/hello/route.ts')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/page.module.css')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/public/favicon.ico')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add layout types correctly for standalone apps', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'css',
|
||||
appDir: true,
|
||||
rootProject: true,
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'tsconfig.json');
|
||||
expect(tsConfig.include).toEqual([
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'.next/types/**/*.ts',
|
||||
'dist/test-app/.next/types/**/*.ts',
|
||||
'next-env.d.ts',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate an unstyled component page', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'none',
|
||||
appDir: true,
|
||||
rootProject: true,
|
||||
});
|
||||
|
||||
const content = tree.read('app/page.tsx').toString();
|
||||
|
||||
expect(content).not.toContain('import styles from');
|
||||
expect(content).not.toContain('const StyledPage');
|
||||
expect(content).not.toContain('className={styles.page}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pages Router', () => {
|
||||
it('should generate files for pages layout', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
appDir: false,
|
||||
});
|
||||
expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/pages/index.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/specs/index.spec.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/pages/index.module.css')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update configurations', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
@ -43,70 +158,6 @@ describe('app', () => {
|
||||
expect(content).not.toContain('const StyledPage');
|
||||
expect(content).not.toContain('className={styles.page}');
|
||||
});
|
||||
|
||||
it('should update tags and implicit dependencies', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
tags: 'one,two',
|
||||
});
|
||||
|
||||
const projects = Object.fromEntries(getProjects(tree));
|
||||
|
||||
expect(projects).toMatchObject({
|
||||
'my-app': {
|
||||
tags: ['one', 'two'],
|
||||
},
|
||||
'my-app-e2e': {
|
||||
tags: [],
|
||||
implicitDependencies: ['my-app'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate files for app router layout', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/app/page.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/app/page.module.css')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should generate files for pages layout', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
appDir: false,
|
||||
});
|
||||
expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/pages/index.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/specs/index.spec.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/pages/index.module.css')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should extend from root tsconfig.base.json', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'apps/my-app/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.base.json');
|
||||
});
|
||||
|
||||
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
|
||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'apps/my-app/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('--style scss', () => {
|
||||
@ -121,6 +172,28 @@ describe('app', () => {
|
||||
|
||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||
expect(indexContent).toContain(`import styles from './page.module.scss'`);
|
||||
expect(tree.read('apps/my-app/app/layout.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.css';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to my-app',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -136,6 +209,28 @@ describe('app', () => {
|
||||
|
||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||
expect(indexContent).toContain(`import styles from './page.module.less'`);
|
||||
expect(tree.read('apps/my-app/app/layout.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.less';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to my-app',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -155,7 +250,7 @@ describe('app', () => {
|
||||
});
|
||||
|
||||
describe('--style styled-components', () => {
|
||||
it('should generate scss styles', async () => {
|
||||
it('should generate styles', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'styled-components',
|
||||
@ -169,11 +264,73 @@ describe('app', () => {
|
||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||
expect(indexContent).not.toContain(`import styles from './page.module`);
|
||||
expect(indexContent).toContain(`import styled from 'styled-components'`);
|
||||
expect(tree.read('apps/my-app/app/layout.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.css';
|
||||
import { StyledComponentsRegistry } from './registry';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to demo2',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/my-app/app/registry.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useServerInsertedHTML } from 'next/navigation';
|
||||
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
|
||||
|
||||
export function StyledComponentsRegistry({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// Only create stylesheet once with lazy initial state
|
||||
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const styles = styledComponentsStyleSheet.getStyleElement();
|
||||
|
||||
// Types are out of date, clearTag is not defined.
|
||||
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/65021
|
||||
(styledComponentsStyleSheet.instance as any).clearTag();
|
||||
|
||||
return <>{styles}</>;
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') return <>{children}</>;
|
||||
|
||||
return (
|
||||
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
|
||||
{children}
|
||||
</StyleSheetManager>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--style @emotion/styled', () => {
|
||||
it('should generate scss styles', async () => {
|
||||
it('should generate styles', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: '@emotion/styled',
|
||||
@ -187,6 +344,28 @@ describe('app', () => {
|
||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||
expect(indexContent).not.toContain(`import styles from './page.module`);
|
||||
expect(indexContent).toContain(`import styled from '@emotion/styled'`);
|
||||
expect(tree.read('apps/my-app/app/layout.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.css';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to my-app',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add jsxImportSource in tsconfig.json', async () => {
|
||||
@ -220,6 +399,49 @@ describe('app', () => {
|
||||
expect(indexContent).not.toContain(
|
||||
`import styled from 'styled-components'`
|
||||
);
|
||||
expect(tree.read('apps/my-app/app/layout.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.css';
|
||||
import { StyledJsxRegistry } from './registry';
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<StyledJsxRegistry>{children}</StyledJsxRegistry>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/my-app/app/registry.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useServerInsertedHTML } from 'next/navigation';
|
||||
import { StyleRegistry, createStyleRegistry } from 'styled-jsx';
|
||||
|
||||
export function StyledJsxRegistry({ children }: { children: React.ReactNode }) {
|
||||
// Only create stylesheet once with lazy initial state
|
||||
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||
const [jsxStyleRegistry] = useState(() => createStyleRegistry());
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const styles = jsxStyleRegistry.styles();
|
||||
jsxStyleRegistry.flush();
|
||||
return <>{styles}</>;
|
||||
});
|
||||
|
||||
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -245,7 +467,7 @@ describe('app', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should set up the nrwl next build builder', async () => {
|
||||
it('should set up the nx next build builder', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'my-app',
|
||||
style: 'css',
|
||||
@ -260,7 +482,7 @@ describe('app', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set up the nrwl next server builder', async () => {
|
||||
it('should set up the nx next server builder', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'my-app',
|
||||
style: 'css',
|
||||
@ -283,7 +505,7 @@ describe('app', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set up the nrwl next export builder', async () => {
|
||||
it('should set up the nx next export builder', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'my-app',
|
||||
style: 'css',
|
||||
@ -448,76 +670,4 @@ describe('app', () => {
|
||||
expect(tsConfigApp.exclude).not.toContain('**/*.spec.js');
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a .gitkeep file to the public directory', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myApp',
|
||||
style: 'css',
|
||||
});
|
||||
|
||||
expect(tree.exists('apps/my-app/public/.gitkeep')).toBe(true);
|
||||
});
|
||||
|
||||
describe('--appDir', () => {
|
||||
it('should generate app directory instead of pages', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'css',
|
||||
appDir: true,
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'apps/test-app/tsconfig.json');
|
||||
expect(tsConfig.include).toEqual([
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'../../apps/test-app/.next/types/**/*.ts',
|
||||
'../../dist/apps/test-app/.next/types/**/*.ts',
|
||||
'next-env.d.ts',
|
||||
]);
|
||||
expect(tree.exists('apps/test-app/pages/styles.css')).toBeFalsy();
|
||||
expect(tree.exists('apps/test-app/app/global.css')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/page.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/layout.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/api/hello/route.ts')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/app/page.module.css')).toBeTruthy();
|
||||
expect(tree.exists('apps/test-app/public/favicon.ico')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add layout types correctly for standalone apps', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'css',
|
||||
appDir: true,
|
||||
rootProject: true,
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'tsconfig.json');
|
||||
expect(tsConfig.include).toEqual([
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
'**/*.js',
|
||||
'**/*.jsx',
|
||||
'.next/types/**/*.ts',
|
||||
'dist/test-app/.next/types/**/*.ts',
|
||||
'next-env.d.ts',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate an unstyled component page', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'testApp',
|
||||
style: 'none',
|
||||
appDir: true,
|
||||
rootProject: true,
|
||||
});
|
||||
|
||||
const content = tree.read('app/page.tsx').toString();
|
||||
|
||||
expect(content).not.toContain('import styles from');
|
||||
expect(content).not.toContain('const StyledPage');
|
||||
expect(content).not.toContain('className={styles.page}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import './global.<%= stylesExt %>';
|
||||
import { StyledComponentsRegistry } from './registry';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to demo2',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useServerInsertedHTML } from 'next/navigation';
|
||||
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
|
||||
|
||||
export function StyledComponentsRegistry({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// Only create stylesheet once with lazy initial state
|
||||
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const styles = styledComponentsStyleSheet.getStyleElement();
|
||||
|
||||
// Types are out of date, clearTag is not defined.
|
||||
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/65021
|
||||
(styledComponentsStyleSheet.instance as any).clearTag();
|
||||
|
||||
return <>{styles}</>;
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') return <>{children}</>;
|
||||
|
||||
return (
|
||||
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
|
||||
{children}
|
||||
</StyleSheetManager>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import './global.<%= stylesExt %>';
|
||||
import { StyledJsxRegistry } from './registry';
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<StyledJsxRegistry>{children}</StyledJsxRegistry>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useServerInsertedHTML } from 'next/navigation';
|
||||
import { StyleRegistry, createStyleRegistry } from 'styled-jsx';
|
||||
|
||||
export function StyledJsxRegistry({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// Only create stylesheet once with lazy initial state
|
||||
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||
const [jsxStyleRegistry] = useState(() => createStyleRegistry());
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const styles = jsxStyleRegistry.styles();
|
||||
jsxStyleRegistry.flush();
|
||||
return <>{styles}</>;
|
||||
});
|
||||
|
||||
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>;
|
||||
}
|
||||
@ -62,6 +62,17 @@ const nextConfig = {
|
||||
// See: https://github.com/gregberge/svgr
|
||||
svgr: false,
|
||||
},
|
||||
<% if (style === 'styled-components') { %>
|
||||
compiler: {
|
||||
// For other options, see https://styled-components.com/docs/tooling#babel-plugin
|
||||
styledComponents: true,
|
||||
},
|
||||
<% } else if (style === '@emotion/styled') { %>
|
||||
compiler: {
|
||||
// For other options, see https://nextjs.org/docs/architecture/nextjs-compiler#emotion
|
||||
emotion: true,
|
||||
},
|
||||
<% } %>
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
|
||||
@ -65,6 +65,8 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
||||
join(options.appProjectRoot, 'app'),
|
||||
templateVariables
|
||||
);
|
||||
|
||||
// RSC is not possible to unit test without extra helpers for data fetching. Leaving it to the user to figure out.
|
||||
host.delete(
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
@ -72,6 +74,29 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
||||
`index.spec.${options.js ? 'jsx' : 'tsx'}`
|
||||
)
|
||||
);
|
||||
|
||||
if (options.style === 'styled-components') {
|
||||
generateFiles(
|
||||
host,
|
||||
join(__dirname, '../files/app-styled-components'),
|
||||
join(options.appProjectRoot, 'app'),
|
||||
templateVariables
|
||||
);
|
||||
} else if (options.style === 'styled-jsx') {
|
||||
generateFiles(
|
||||
host,
|
||||
join(__dirname, '../files/app-styled-jsx'),
|
||||
join(options.appProjectRoot, 'app'),
|
||||
templateVariables
|
||||
);
|
||||
} else {
|
||||
generateFiles(
|
||||
host,
|
||||
join(__dirname, '../files/app-default-layout'),
|
||||
join(options.appProjectRoot, 'app'),
|
||||
templateVariables
|
||||
);
|
||||
}
|
||||
} else {
|
||||
generateFiles(
|
||||
host,
|
||||
|
||||
@ -4,7 +4,7 @@ import { NormalizedSchema } from './normalize-options';
|
||||
export function showPossibleWarnings(tree: Tree, options: NormalizedSchema) {
|
||||
if (options.style === '@emotion/styled' && options.appDir) {
|
||||
logger.warn(
|
||||
`Emotion may not work with the App Router. See: https://beta.nextjs.org/docs/styling/css-in-js`
|
||||
`Emotion may not work with the App Router. See: https://nextjs.org/docs/app/building-your-application/styling/css-in-js`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user