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 {
|
import {
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
isNotWindows,
|
|
||||||
killPorts,
|
|
||||||
newProject,
|
newProject,
|
||||||
runCLI,
|
runCLI,
|
||||||
runCommandUntil,
|
|
||||||
tmpProjPath,
|
|
||||||
uniq,
|
uniq,
|
||||||
updateFile,
|
updateFile,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import { getData } from 'ajv/dist/compile/validate';
|
|
||||||
import { detectPackageManager } from 'nx/src/utils/package-manager';
|
|
||||||
import { checkApp } from './utils';
|
import { checkApp } from './utils';
|
||||||
import { p } from 'vitest/dist/types-b7007192';
|
|
||||||
|
|
||||||
describe('Next.js App Router', () => {
|
describe('Next.js App Router', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
|
|||||||
@ -58,6 +58,19 @@ describe('Next.js apps', () => {
|
|||||||
checkExport: false,
|
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');
|
const emotionApp = uniq('app');
|
||||||
|
|
||||||
runCLI(
|
runCLI(
|
||||||
|
|||||||
@ -15,7 +15,122 @@ describe('app', () => {
|
|||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
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 () => {
|
it('should update configurations', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
name: 'myApp',
|
name: 'myApp',
|
||||||
@ -43,70 +158,6 @@ describe('app', () => {
|
|||||||
expect(content).not.toContain('const StyledPage');
|
expect(content).not.toContain('const StyledPage');
|
||||||
expect(content).not.toContain('className={styles.page}');
|
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', () => {
|
describe('--style scss', () => {
|
||||||
@ -121,6 +172,28 @@ describe('app', () => {
|
|||||||
|
|
||||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||||
expect(indexContent).toContain(`import styles from './page.module.scss'`);
|
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');
|
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||||
expect(indexContent).toContain(`import styles from './page.module.less'`);
|
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', () => {
|
describe('--style styled-components', () => {
|
||||||
it('should generate scss styles', async () => {
|
it('should generate styles', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
name: 'myApp',
|
name: 'myApp',
|
||||||
style: 'styled-components',
|
style: 'styled-components',
|
||||||
@ -169,11 +264,73 @@ describe('app', () => {
|
|||||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||||
expect(indexContent).not.toContain(`import styles from './page.module`);
|
expect(indexContent).not.toContain(`import styles from './page.module`);
|
||||||
expect(indexContent).toContain(`import styled from 'styled-components'`);
|
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', () => {
|
describe('--style @emotion/styled', () => {
|
||||||
it('should generate scss styles', async () => {
|
it('should generate styles', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
name: 'myApp',
|
name: 'myApp',
|
||||||
style: '@emotion/styled',
|
style: '@emotion/styled',
|
||||||
@ -187,6 +344,28 @@ describe('app', () => {
|
|||||||
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
const indexContent = tree.read('apps/my-app/app/page.tsx', 'utf-8');
|
||||||
expect(indexContent).not.toContain(`import styles from './page.module`);
|
expect(indexContent).not.toContain(`import styles from './page.module`);
|
||||||
expect(indexContent).toContain(`import styled from '@emotion/styled'`);
|
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 () => {
|
it('should add jsxImportSource in tsconfig.json', async () => {
|
||||||
@ -220,6 +399,49 @@ describe('app', () => {
|
|||||||
expect(indexContent).not.toContain(
|
expect(indexContent).not.toContain(
|
||||||
`import styled from 'styled-components'`
|
`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, {
|
await applicationGenerator(tree, {
|
||||||
name: 'my-app',
|
name: 'my-app',
|
||||||
style: 'css',
|
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, {
|
await applicationGenerator(tree, {
|
||||||
name: 'my-app',
|
name: 'my-app',
|
||||||
style: 'css',
|
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, {
|
await applicationGenerator(tree, {
|
||||||
name: 'my-app',
|
name: 'my-app',
|
||||||
style: 'css',
|
style: 'css',
|
||||||
@ -448,76 +670,4 @@ describe('app', () => {
|
|||||||
expect(tsConfigApp.exclude).not.toContain('**/*.spec.js');
|
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
|
// See: https://github.com/gregberge/svgr
|
||||||
svgr: false,
|
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 = [
|
const plugins = [
|
||||||
|
|||||||
@ -65,6 +65,8 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
|||||||
join(options.appProjectRoot, 'app'),
|
join(options.appProjectRoot, 'app'),
|
||||||
templateVariables
|
templateVariables
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// RSC is not possible to unit test without extra helpers for data fetching. Leaving it to the user to figure out.
|
||||||
host.delete(
|
host.delete(
|
||||||
joinPathFragments(
|
joinPathFragments(
|
||||||
options.appProjectRoot,
|
options.appProjectRoot,
|
||||||
@ -72,6 +74,29 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
|||||||
`index.spec.${options.js ? 'jsx' : 'tsx'}`
|
`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 {
|
} else {
|
||||||
generateFiles(
|
generateFiles(
|
||||||
host,
|
host,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { NormalizedSchema } from './normalize-options';
|
|||||||
export function showPossibleWarnings(tree: Tree, options: NormalizedSchema) {
|
export function showPossibleWarnings(tree: Tree, options: NormalizedSchema) {
|
||||||
if (options.style === '@emotion/styled' && options.appDir) {
|
if (options.style === '@emotion/styled' && options.appDir) {
|
||||||
logger.warn(
|
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