fix(nextjs): fix Next.js schematics to support dynamic routes and remove unnecessary next plugins (as of 9.5) (#3574)

Closes #2478, #2490
This commit is contained in:
Jack Hsu 2020-08-21 14:25:22 -04:00 committed by GitHub
parent 26c13d66a7
commit 9f35646c65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 821 additions and 1219 deletions

View File

@ -38,6 +38,14 @@ nx g component my-component --project=mylib --classComponent
## Options
### directory
Alias(es): d
Type: `string`
Create the page under this directory (can be nested). Will be created under 'pages/'.
### export
Alias(es): e

View File

@ -38,6 +38,14 @@ nx g component my-component --project=mylib --classComponent
## Options
### directory
Alias(es): d
Type: `string`
Create the page under this directory (can be nested). Will be created under 'pages/'.
### export
Alias(es): e

View File

@ -80,31 +80,18 @@ forEachCli('nx', () => {
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
runCLI(
`generate @nrwl/react:lib ${libName} --no-interactive --style=none`
);
const mainPath = `apps/${appName}/pages/index.tsx`;
updateFile(mainPath, `import '@proj/${libName}';\n` + readFile(mainPath));
updateFile(
`apps/${appName}/next.config.js`,
`
const withCSS = require('@zeit/next-css');
module.exports = withCSS({
cssModules: false,
generateBuildId: function () {
return 'fixed';
}
});
`
);
await checkApp(appName, {
checkUnitTest: true,
checkLint: true,
checkE2E: false,
});
// check that the configuration was consumed
expect(readFile(`dist/apps/${appName}/.next/BUILD_ID`)).toEqual('fixed');
}, 120000);
it('should be able to dynamically load a lib', async () => {
@ -113,7 +100,9 @@ module.exports = withCSS({
const libName = uniq('lib');
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
runCLI(
`generate @nrwl/react:lib ${libName} --no-interactive --style=none`
);
const mainPath = `apps/${appName}/pages/index.tsx`;
updateFile(
@ -181,8 +170,8 @@ module.exports = withCSS({
import { TestComponent } from '@proj/${tsxLibName}';\n\n
` +
content.replace(
`<main>`,
`<main>
`</h2>`,
`</h2>
<div>
{testFn()}
<TestComponent text="Hello Next.JS" />

View File

@ -166,7 +166,7 @@
"mime": "2.4.4",
"mini-css-extract-plugin": "0.8.0",
"minimatch": "3.0.4",
"next": "9.3.3",
"next": "9.5.2",
"ng-packagr": "9.1.0",
"ngrx-store-freeze": "0.2.4",
"npm-run-all": "^4.1.5",

View File

@ -39,7 +39,7 @@
"version": "10.1.0-beta.0",
"packages": {
"next": {
"version": "9.5.1",
"version": "9.5.2",
"alwaysAddToPackageJson": false
},
"node-sass": {

View File

@ -40,6 +40,7 @@ function run(
exportApp(
root,
{
statusMessage: 'Exporting',
silent: options.silent,
threads: options.threads,
outdir: `${buildOptions.outputPath}/exported`,

View File

@ -56,7 +56,9 @@ describe('app', () => {
{ name: 'myApp', style: 'scss' },
appTree
);
expect(result.exists('apps/my-app/pages/index.scss')).toEqual(true);
expect(result.exists('apps/my-app/pages/index.module.scss')).toEqual(
true
);
});
});

View File

@ -1,11 +1,4 @@
<% if (style === 'scss') { %>
const withSass = require('@zeit/next-sass');
module.exports = withSass({
// Set this to true if you use CSS modules.
// See: https://github.com/css-modules/css-modules
cssModules: false
});
<% } else if (style === 'less') { %>
<% if (style === 'less') { %>
const withLess = require('@zeit/next-less');
module.exports = withLess({
// Set this to true if you use CSS modules.
@ -19,13 +12,6 @@ module.exports = withStylus({
// See: https://github.com/css-modules/css-modules
cssModules: false
});
<% } else if (style === 'css') { %>
const withCSS = require('@zeit/next-css');
module.exports = withCSS({
// Set this to true if you use CSS modules.
// See: https://github.com/css-modules/css-modules
cssModules: false
});
<% } else if (
style === 'styled-components'
||style === '@emotion/styled'
@ -34,11 +20,6 @@ module.exports = withCSS({
) { %>
module.exports ={};
<% } else {
// Defaults to CSS %>
const withCSS = require('@zeit/next-css');
module.exports = withCSS({
// Set this to true if you use CSS modules.
// See: https://github.com/css-modules/css-modules
cssModules: false
});
// Defaults to CSS/SASS (which don't need plugin as of Next 9.3) %>
module.exports ={};
<% } %>

View File

@ -1,16 +1,15 @@
import React from 'react';
<% if (styledModule && styledModule !== 'styled-jsx') {
var wrapper = 'StyledApp';
var wrapper = 'StyledPage';
%>import styled from '<%= styledModule %>';<% } else {
var wrapper = 'div';
%>
<%= style !== 'styled-jsx' ? `import './${fileName}.${style}';` : '' %>
<%= style !== 'styled-jsx' ? `import styles from './${fileName}.module.${style}';` : '' %>
<% }
%>
import { ReactComponent as NxLogo } from '../public/nx-logo-white.svg';
<% if (styledModule && styledModule !== 'styled-jsx') { %>
const StyledApp = styled.div`<%= styleContent %>`;
const StyledPage = styled.div`<%= styleContent %>`;
<% }%>
export const Index = () => {
@ -20,7 +19,7 @@ export const Index = () => {
* Note: The corresponding styles are in the ./${fileName}.${style} file.
*/
return (
<<%= wrapper %><% if (!styledModule || styledModule === 'styled-jsx') {%> className="app"<% } %>>
<<%= wrapper %><% if (!styledModule || styledModule === 'styled-jsx') {%> className={styles.page}<% } %>>
<%= styledModule === 'styled-jsx' ? `<style jsx>{\`${styleContent}\`}</style>` : `` %>
<%= appContent %>
</<%= wrapper %>>

View File

@ -0,0 +1,26 @@
import React from 'react';
import { AppProps } from 'next/app';
import Head from 'next/head';
import { ReactComponent as NxLogo } from '../public/nx-logo-white.svg';
import './styles.css';
const CustomApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<Head>
<title>Welcome to <%= name %>!</title>
</Head>
<div className="app">
<header className="flex">
<NxLogo width="75" height="50" />
<h1>Welcome to <%= name %>!</h1>
</header>
<main>
<Component {...pageProps}/>
</main>
</div>
</>
);
};
export default CustomApp;

View File

@ -19,7 +19,6 @@ export default class CustomDocument extends Document<{ styleTags: ReactElement[]
return (
<html>
<Head>
<title>Welcome to <%= name %>!</title>
{this.props.styleTags}
</Head>
<body>

View File

@ -8,9 +8,4 @@ describe('Index', () => {
const { baseElement } = render(<Index />);
expect(baseElement).toBeTruthy();
});
it('should have a greeting as the title', () => {
const { getByText } = render(<Index />);
expect(getByText('Welcome to <%= projectName %>!')).toBeTruthy();
});
});

View File

@ -1,10 +1,5 @@
export function createAppJsx(name: string) {
return `
<header className="flex">
<NxLogo alt="" width="75" height="50" />
<h1>Welcome to ${name}!</h1>
</header>
<main>
<h2>Resources &amp; Tools</h2>
<p>
Thank you for using and showing some for Nx.
@ -86,80 +81,60 @@ nx affected:test
nx affected:e2e
\`}</pre>
</details>
</main>
`;
}
interface CreateStyleRulesOptions {
isUsingJsxBasedSolution: boolean;
createHostBlock: boolean;
}
export function createStyleRules({
isUsingJsxBasedSolution,
createHostBlock,
}: CreateStyleRulesOptions) {
const childSelectorPrefix = isUsingJsxBasedSolution ? '' : '.app';
return `${
createHostBlock
? `
export function createStyleRules() {
return `
.app {
font-family: sans-serif;
min-width: 300px;
max-width: 600px;
margin: 50px auto;
}
`
: `
font-family: sans-serif;
min-width: 300px;
max-width: 600px;
margin: 50px auto;
`
}
${childSelectorPrefix} .gutter-left {
.app .gutter-left {
margin-left: 9px;
}
${childSelectorPrefix} .col-span-2 {
.app .col-span-2 {
grid-column: span 2;
}
${childSelectorPrefix} .flex {
.app .flex {
display: flex;
align-items: center;
justify-content: center;
}
${childSelectorPrefix} header {
.app header {
background-color: #143055;
color: white;
padding: 5px;
border-radius: 3px;
}
${childSelectorPrefix} main {
.app main {
padding: 0 36px;
}
${childSelectorPrefix} p {
.app p {
text-align: center;
}
${childSelectorPrefix} h1 {
.app h1 {
text-align: center;
margin-left: 18px;
font-size: 24px;
}
${childSelectorPrefix} h2 {
.app h2 {
text-align: center;
font-size: 20px;
margin: 40px 0 10px 0;
}
${childSelectorPrefix} .resources {
.app .resources {
text-align: center;
list-style: none;
padding: 0;
@ -168,7 +143,7 @@ ${childSelectorPrefix} .resources {
grid-template-columns: 1fr 1fr;
}
${childSelectorPrefix} .resource {
.app .resource {
color: #0094ba;
height: 36px;
background-color: rgba(0, 0, 0, 0);
@ -178,18 +153,18 @@ ${childSelectorPrefix} .resource {
text-decoration: none;
}
${childSelectorPrefix} .resource:hover {
.app .resource:hover {
background-color: rgba(68, 138, 255, 0.04);
}
${childSelectorPrefix} pre {
.app pre {
padding: 9px;
border-radius: 4px;
background-color: black;
color: #eee;
}
${childSelectorPrefix} details {
.app details {
border-radius: 4px;
color: #333;
background-color: rgba(0, 0, 0, 0);
@ -198,25 +173,25 @@ ${childSelectorPrefix} details {
margin-bottom: 9px;
}
${childSelectorPrefix} summary {
.app summary {
outline: none;
height: 36px;
line-height: 36px;
}
${childSelectorPrefix} .github-star-container {
.app .github-star-container {
margin-top: 12px;
line-height: 20px;
}
${childSelectorPrefix} .github-star-container a {
.app .github-star-container a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
}
${childSelectorPrefix} .github-star-badge {
.app .github-star-badge {
color: #24292e;
display: flex;
align-items: center;
@ -229,12 +204,12 @@ ${childSelectorPrefix} .github-star-badge {
font-weight: 600;
}
${childSelectorPrefix} .github-star-badge:hover {
.app .github-star-badge:hover {
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
border-color: rgba(27,31,35,.35);
background-position: -.5em;
}
${childSelectorPrefix} .github-star-badge .material-icons {
.app .github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;

View File

@ -24,11 +24,7 @@ export function createApplicationFiles(options: NormalizedSchema): Rule {
tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
appContent: createAppJsx(options.name),
styleContent: createStyleRules({
isUsingJsxBasedSolution: !!options.styledModule,
createHostBlock:
!options.styledModule || options.styledModule === 'styled-jsx',
}),
styleContent: createStyleRules(),
}),
options.styledModule
? filter((file) => !file.endsWith(`.${options.style}`))

View File

@ -32,6 +32,11 @@
},
"x-prompt": "What name would you like to use for the component?"
},
"directory": {
"type": "string",
"description": "Create the component under this directory (can be nested).",
"alias": "d"
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",

View File

@ -23,4 +23,24 @@ describe('component', () => {
expect(tree.exists('apps/my-app/src/pages/hello.tsx')).toBeTruthy();
expect(tree.exists('apps/my-app/src/pages/hello.css')).toBeTruthy();
});
it('should support dynamic routes and directories', async () => {
const tree = await runSchematic(
'page',
{ name: '[dynamic]', directory: 'posts', project: projectName },
appTree
);
expect(
tree.exists('apps/my-app/src/pages/posts/[dynamic].tsx')
).toBeTruthy();
expect(
tree.exists('apps/my-app/src/pages/posts/[dynamic].css')
).toBeTruthy();
const content = tree
.read('apps/my-app/src/pages/posts/[dynamic].tsx')
.toString();
expect(content).toMatch(/DynamicProps/);
});
});

View File

@ -1,12 +1,6 @@
import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';
import { addStyleDependencies } from '../../utils/styles';
interface Schema {
name: string;
project: string;
style: string;
withTests?: boolean;
}
import { Schema } from './schema';
/*
* This schematic is basically the React component one, but for Next we need
@ -17,7 +11,7 @@ export default function (options: Schema): Rule {
return chain([
externalSchematic('@nrwl/react', 'component', {
...options,
directory: 'pages', // This HAS to be here or Next won't work!
directory: options.directory ? `pages/${options.directory}` : 'pages',
pascalCaseFiles: false,
export: false,
classComponent: false,

View File

@ -0,0 +1,9 @@
export interface Schema {
name: string;
directory: string;
fileName: string;
project: string;
style: string;
withTests?: boolean;
js?: boolean;
}

View File

@ -32,6 +32,11 @@
},
"x-prompt": "What name would you like to use for the component?"
},
"directory": {
"type": "string",
"description": "Create the page under this directory (can be nested). Will be created under 'pages/'.",
"alias": "d"
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",

View File

@ -4,11 +4,9 @@ import { CSS_IN_JS_DEPENDENCIES } from '@nrwl/react';
import {
babelPluginStyledComponentsVersion,
emotionServerVersion,
zeitNextCss,
zeitNextLess,
zeitNextSass,
zeitNextStylus,
nodeSass,
zeitNextLess,
zeitNextStylus,
} from './versions';
export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = {
@ -27,15 +25,11 @@ export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = {
devDependencies: CSS_IN_JS_DEPENDENCIES['@emotion/styled'].devDependencies,
},
css: {
dependencies: {
'@zeit/next-css': zeitNextCss,
},
dependencies: {},
devDependencies: {},
},
scss: {
dependencies: {
'@zeit/next-sass': zeitNextSass,
},
dependencies: {},
devDependencies: {
'node-sass': nodeSass,
},

View File

@ -1,6 +1,6 @@
export const nxVersion = '*';
export const nextVersion = '9.5.1';
export const nextVersion = '9.5.2';
export const zeitNextCss = '1.0.1';
export const zeitNextSass = '1.0.1';
export const nodeSass = '4.14.1';

View File

@ -1,6 +1,6 @@
import { schema } from '@angular-devkit/core';
import { fileSync } from 'tmp';
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
import { readFileSync, unlinkSync, writeFileSync } from 'fs';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { Architect } from '@angular-devkit/architect';
import { join } from 'path';
@ -89,7 +89,6 @@ describe('Command Runner Builder', () => {
//wait a tick for the serial runner to schedule the first task
await Promise.resolve();
const run = await scheduleRun;
const result = await run.result;
expect(exec).toHaveBeenCalledWith(`echo --a=123 --b=456`, {
stdio: [0, 1, 2],

View File

@ -0,0 +1,27 @@
import * as utils from './name-utils';
describe('name-utils', () => {
it('should support class names', () => {
expect(utils.toClassName('foo-bar')).toEqual('FooBar');
expect(utils.toClassName('foo_bar')).toEqual('FooBar');
expect(utils.toClassName('fooBar')).toEqual('FooBar');
expect(utils.toClassName('[fooBar]')).toEqual('FooBar');
expect(utils.toClassName('[...fooBar]')).toEqual('FooBar');
});
it('should support property names', () => {
expect(utils.toPropertyName('foo-bar')).toEqual('fooBar');
expect(utils.toPropertyName('foo_bar')).toEqual('fooBar');
expect(utils.toPropertyName('FooBar')).toEqual('fooBar');
expect(utils.toPropertyName('[fooBar]')).toEqual('fooBar');
expect(utils.toPropertyName('[...fooBar]')).toEqual('fooBar');
});
it('should support file names', () => {
expect(utils.toFileName('foo-bar')).toEqual('foo-bar');
expect(utils.toFileName('foo_bar')).toEqual('foo-bar');
expect(utils.toFileName('FooBar')).toEqual('foo-bar');
expect(utils.toFileName('[fooBar]')).toEqual('[foo-bar]');
expect(utils.toFileName('[...fooBar]')).toEqual('[...foo-bar]');
});
});

View File

@ -34,6 +34,7 @@ export function toPropertyName(s: string): string {
.replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) =>
chr ? chr.toUpperCase() : ''
)
.replace(/[^a-zA-Z\d]/g, '')
.replace(/^([A-Z])/, (m) => m.toLowerCase());
}

1751
yarn.lock

File diff suppressed because it is too large Load Diff