From 51973b88e2f5c15dae79a8e92d5526af5e6bcb9f Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 18 Jan 2021 13:22:58 -0500 Subject: [PATCH] chore(gatsby): move gatsby plugin to main repo --- .circleci/config.yml | 2 +- .cz-config.js | 1 + docs/angular/api-gatsby/executors/build.md | 47 +++ docs/angular/api-gatsby/executors/server.md | 43 +++ .../api-gatsby/generators/application.md | 89 +++++ .../api-gatsby/generators/component.md | 107 ++++++ docs/angular/api-gatsby/generators/page.md | 107 ++++++ docs/angular/executors.json | 1 + docs/angular/generators.json | 1 + docs/map.json | 294 ++++++++++++++++ docs/node/api-gatsby/executors/build.md | 48 +++ docs/node/api-gatsby/executors/server.md | 44 +++ .../node/api-gatsby/generators/application.md | 89 +++++ docs/node/api-gatsby/generators/component.md | 107 ++++++ docs/node/api-gatsby/generators/page.md | 107 ++++++ docs/node/executors.json | 1 + docs/node/generators.json | 1 + docs/react/api-gatsby/executors/build.md | 48 +++ docs/react/api-gatsby/executors/server.md | 44 +++ .../api-gatsby/generators/application.md | 89 +++++ docs/react/api-gatsby/generators/component.md | 107 ++++++ docs/react/api-gatsby/generators/page.md | 107 ++++++ docs/react/executors.json | 1 + docs/react/generators.json | 1 + docs/shared/gatsby-plugin.md | 73 ++++ docs/shared/next-plugin.md | 37 +- e2e/gatsby/jest.config.js | 10 + e2e/gatsby/src/gatsby.test.ts | 59 ++++ e2e/gatsby/tsconfig.json | 13 + e2e/gatsby/tsconfig.spec.json | 16 + e2e/utils/index.ts | 1 + images/nx-gatsby.png | Bin 0 -> 45391 bytes jest.config.js | 1 + nx.json | 7 + .../bin/create-nx-workspace.ts | 5 + packages/gatsby/.eslintrc.json | 5 + packages/gatsby/README.md | 40 +++ packages/gatsby/babel.ts | 9 + packages/gatsby/builders.json | 15 + packages/gatsby/collection.json | 29 ++ packages/gatsby/index.ts | 3 + packages/gatsby/jest.config.js | 9 + packages/gatsby/package.json | 41 +++ .../nx-gatsby-ext-plugin/gatsby-config.ts | 3 + .../nx-gatsby-ext-plugin/gatsby-node.ts | 26 ++ .../plugins/nx-gatsby-ext-plugin/package.json | 10 + packages/gatsby/src/builders/build/builder.ts | 93 +++++ .../gatsby/src/builders/build/schema.d.ts | 10 + .../gatsby/src/builders/build/schema.json | 35 ++ .../gatsby/src/builders/server/builder.ts | 161 +++++++++ .../gatsby/src/builders/server/schema.d.ts | 9 + .../gatsby/src/builders/server/schema.json | 31 ++ packages/gatsby/src/index.ts | 0 .../application/application.spec.ts | 320 ++++++++++++++++++ .../src/schematics/application/application.ts | 282 +++++++++++++++ .../application/files/.babelrc__tmpl__ | 4 + .../schematics/application/files/.gitignore | 69 ++++ .../application/files/.prettierignore | 4 + .../schematics/application/files/.prettierrc | 4 + .../src/schematics/application/files/LICENSE | 22 ++ .../schematics/application/files/README.md | 99 ++++++ .../application/files/gatsby-browser.js | 7 + .../files/gatsby-config.js__tmpl__ | 56 +++ .../application/files/gatsby-node.js | 7 + .../application/files/gatsby-ssr.js | 7 + .../schematics/application/files/package.json | 10 + .../application/files/src/images/logo.png | Bin 0 -> 20233 bytes .../application/files/src/pages/404.js | 5 + .../files/src/pages/index.module.__style__ | 132 ++++++++ .../files/src/pages/index.spec.tsx__tmpl__ | 13 + .../files/src/pages/index.tsx__tmpl__ | 256 ++++++++++++++ .../application/files/src/pages/logo.svg | 17 + .../application/files/src/pages/star.svg | 11 + .../files/tsconfig.app.json__tmpl__ | 9 + .../application/files/tsconfig.json__tmpl__ | 22 ++ .../src/schematics/application/schema.d.ts | 9 + .../src/schematics/application/schema.json | 85 +++++ .../src/schematics/component/component.ts | 35 ++ .../src/schematics/component/schema.json | 103 ++++++ packages/gatsby/src/schematics/init/init.ts | 94 +++++ .../gatsby/src/schematics/init/schema.d.ts | 5 + .../gatsby/src/schematics/init/schema.json | 26 ++ packages/gatsby/src/schematics/page/page.ts | 32 ++ .../gatsby/src/schematics/page/schema.json | 103 ++++++ packages/gatsby/src/utils/get-project-root.ts | 13 + packages/gatsby/src/utils/styles.ts | 55 +++ packages/gatsby/src/utils/testing.ts | 27 ++ packages/gatsby/src/utils/versions.ts | 26 ++ packages/gatsby/tsconfig.json | 16 + packages/gatsby/tsconfig.lib.json | 11 + packages/gatsby/tsconfig.spec.json | 16 + packages/workspace/src/generators/new/new.ts | 7 + scripts/check-imports.js | 1 + scripts/depcheck/missing.ts | 1 + scripts/nx-release.js | 1 + scripts/package.sh | 12 +- scripts/package.ts | 2 + workspace.json | 112 ++++++ 98 files changed, 4361 insertions(+), 24 deletions(-) create mode 100644 docs/angular/api-gatsby/executors/build.md create mode 100644 docs/angular/api-gatsby/executors/server.md create mode 100644 docs/angular/api-gatsby/generators/application.md create mode 100644 docs/angular/api-gatsby/generators/component.md create mode 100644 docs/angular/api-gatsby/generators/page.md create mode 100644 docs/node/api-gatsby/executors/build.md create mode 100644 docs/node/api-gatsby/executors/server.md create mode 100644 docs/node/api-gatsby/generators/application.md create mode 100644 docs/node/api-gatsby/generators/component.md create mode 100644 docs/node/api-gatsby/generators/page.md create mode 100644 docs/react/api-gatsby/executors/build.md create mode 100644 docs/react/api-gatsby/executors/server.md create mode 100644 docs/react/api-gatsby/generators/application.md create mode 100644 docs/react/api-gatsby/generators/component.md create mode 100644 docs/react/api-gatsby/generators/page.md create mode 100644 docs/shared/gatsby-plugin.md create mode 100644 e2e/gatsby/jest.config.js create mode 100644 e2e/gatsby/src/gatsby.test.ts create mode 100644 e2e/gatsby/tsconfig.json create mode 100644 e2e/gatsby/tsconfig.spec.json create mode 100644 images/nx-gatsby.png create mode 100644 packages/gatsby/.eslintrc.json create mode 100644 packages/gatsby/README.md create mode 100644 packages/gatsby/babel.ts create mode 100644 packages/gatsby/builders.json create mode 100644 packages/gatsby/collection.json create mode 100644 packages/gatsby/index.ts create mode 100644 packages/gatsby/jest.config.js create mode 100644 packages/gatsby/package.json create mode 100644 packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-config.ts create mode 100644 packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-node.ts create mode 100644 packages/gatsby/plugins/nx-gatsby-ext-plugin/package.json create mode 100644 packages/gatsby/src/builders/build/builder.ts create mode 100644 packages/gatsby/src/builders/build/schema.d.ts create mode 100644 packages/gatsby/src/builders/build/schema.json create mode 100644 packages/gatsby/src/builders/server/builder.ts create mode 100644 packages/gatsby/src/builders/server/schema.d.ts create mode 100644 packages/gatsby/src/builders/server/schema.json create mode 100644 packages/gatsby/src/index.ts create mode 100644 packages/gatsby/src/schematics/application/application.spec.ts create mode 100644 packages/gatsby/src/schematics/application/application.ts create mode 100644 packages/gatsby/src/schematics/application/files/.babelrc__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/files/.gitignore create mode 100644 packages/gatsby/src/schematics/application/files/.prettierignore create mode 100644 packages/gatsby/src/schematics/application/files/.prettierrc create mode 100644 packages/gatsby/src/schematics/application/files/LICENSE create mode 100644 packages/gatsby/src/schematics/application/files/README.md create mode 100644 packages/gatsby/src/schematics/application/files/gatsby-browser.js create mode 100644 packages/gatsby/src/schematics/application/files/gatsby-config.js__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/files/gatsby-node.js create mode 100644 packages/gatsby/src/schematics/application/files/gatsby-ssr.js create mode 100644 packages/gatsby/src/schematics/application/files/package.json create mode 100644 packages/gatsby/src/schematics/application/files/src/images/logo.png create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/404.js create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/index.module.__style__ create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/index.spec.tsx__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/index.tsx__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/logo.svg create mode 100644 packages/gatsby/src/schematics/application/files/src/pages/star.svg create mode 100644 packages/gatsby/src/schematics/application/files/tsconfig.app.json__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/files/tsconfig.json__tmpl__ create mode 100644 packages/gatsby/src/schematics/application/schema.d.ts create mode 100644 packages/gatsby/src/schematics/application/schema.json create mode 100644 packages/gatsby/src/schematics/component/component.ts create mode 100644 packages/gatsby/src/schematics/component/schema.json create mode 100644 packages/gatsby/src/schematics/init/init.ts create mode 100644 packages/gatsby/src/schematics/init/schema.d.ts create mode 100644 packages/gatsby/src/schematics/init/schema.json create mode 100644 packages/gatsby/src/schematics/page/page.ts create mode 100644 packages/gatsby/src/schematics/page/schema.json create mode 100644 packages/gatsby/src/utils/get-project-root.ts create mode 100644 packages/gatsby/src/utils/styles.ts create mode 100644 packages/gatsby/src/utils/testing.ts create mode 100644 packages/gatsby/src/utils/versions.ts create mode 100644 packages/gatsby/tsconfig.json create mode 100644 packages/gatsby/tsconfig.lib.json create mode 100644 packages/gatsby/tsconfig.spec.json diff --git a/.circleci/config.yml b/.circleci/config.yml index e3245cab97..4879a22cc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ var_2: 'e2e-cli,e2e-nx-plugin,dep-graph-client-e2e', 'e2e-cypress,e2e-jest', 'e2e-react', - 'e2e-next', + 'e2e-next,e2e-gatsby', 'e2e-node', 'e2e-web,e2e-linter,e2e-storybook', ] diff --git a/.cz-config.js b/.cz-config.js index 6825222772..ab5dc56b50 100644 --- a/.cz-config.js +++ b/.cz-config.js @@ -19,6 +19,7 @@ module.exports = { { name: 'core', description: 'anything Nx core specific' }, { name: 'nxdev', description: 'anything related to docs infrastructure' }, { name: 'nextjs', description: 'anything Next specific' }, + { name: 'gatsby', description: 'anything Gatsby specific' }, { name: 'nest', description: 'anything Nest specific' }, { name: 'node', description: 'anything Node specific' }, { name: 'express', description: 'anything Express specific' }, diff --git a/docs/angular/api-gatsby/executors/build.md b/docs/angular/api-gatsby/executors/build.md new file mode 100644 index 0000000000..1e7af3c170 --- /dev/null +++ b/docs/angular/api-gatsby/executors/build.md @@ -0,0 +1,47 @@ +# build + +Build a Gatsby app + +Properties can be configured in angular.json when defining the executor, or when invoking it. + +## Properties + +### color + +Default: `true` + +Type: `boolean` + +Enable colored terminal output. + +### graphqlTracing + +Type: `boolean` + +Trace every graphql resolver, may have performance implications. + +### openTracingConfigFile + +Type: `string` + +Tracer configuration file (OpenTracing compatible). + +### prefixPaths + +Type: `boolean` + +Build site with link paths prefixed (set pathPrefix in your config). + +### profile + +Type: `boolean` + +Build site with react profiling. + +### uglify + +Default: `true` + +Type: `boolean` + +Build site without uglifying JS bundles (true by default). diff --git a/docs/angular/api-gatsby/executors/server.md b/docs/angular/api-gatsby/executors/server.md new file mode 100644 index 0000000000..9fea830c4d --- /dev/null +++ b/docs/angular/api-gatsby/executors/server.md @@ -0,0 +1,43 @@ +# server + +Starts server for app + +Properties can be configured in angular.json when defining the executor, or when invoking it. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### host + +Default: `localhost` + +Type: `string` + +Host to listen on. + +### https + +Default: `false` + +Type: `boolean` + +Serve using HTTPS. + +### open + +Type: `boolean` + +Open the site in your (default) browser for you. + +### port + +Default: `4200` + +Type: `number` + +Port to listen on. diff --git a/docs/angular/api-gatsby/generators/application.md b/docs/angular/api-gatsby/generators/application.md new file mode 100644 index 0000000000..6378fe7a59 --- /dev/null +++ b/docs/angular/api-gatsby/generators/application.md @@ -0,0 +1,89 @@ +# application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +A directory where the project is placed + +### e2eTestRunner + +Default: `cypress` + +Type: `string` + +Possible values: `cypress`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### name + +Type: `string` + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the project (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Adds the specified unit test runner diff --git a/docs/angular/api-gatsby/generators/component.md b/docs/angular/api-gatsby/generators/component.md new file mode 100644 index 0000000000..58421410b8 --- /dev/null +++ b/docs/angular/api-gatsby/generators/component.md @@ -0,0 +1,107 @@ +# component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +By default, Nx will search for `component` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/angular/api-gatsby/generators/page.md b/docs/angular/api-gatsby/generators/page.md new file mode 100644 index 0000000000..f188dae08d --- /dev/null +++ b/docs/angular/api-gatsby/generators/page.md @@ -0,0 +1,107 @@ +# page + +Create a page + +## Usage + +```bash +nx generate page ... +``` + +By default, Nx will search for `page` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:page ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g page ... --dry-run +``` + +### Examples + +Generate a page in the mylib library: + +```bash +nx g page my-page --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g page my-page --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/angular/executors.json b/docs/angular/executors.json index 722846ec08..ded073647f 100644 --- a/docs/angular/executors.json +++ b/docs/angular/executors.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "linter", "nest", diff --git a/docs/angular/generators.json b/docs/angular/generators.json index ae621ac6e8..46a738dc38 100644 --- a/docs/angular/generators.json +++ b/docs/angular/generators.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "nest", "next", diff --git a/docs/map.json b/docs/map.json index 818298eb1d..874923506c 100644 --- a/docs/map.json +++ b/docs/map.json @@ -1699,6 +1699,104 @@ "name": "service generator", "id": "service", "file": "react/api-nest/generators/service" + }, + { + "name": "gatsby", + "id": "gatsby", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/gatsby Overview", + "file": "shared/gatsby-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "application", + "id": "application", + "file": "angular/api-gatsby/generators/application" + }, + { + "name": "component", + "id": "component", + "file": "angular/api-gatsby/generators/component" + }, + { + "name": "page", + "id": "page", + "file": "angular/api-gatsby/generators/page" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "build", + "id": "build", + "file": "angular/api-gatsby/executors/build" + }, + { + "name": "server", + "id": "server", + "file": "angular/api-gatsby/executors/server" + } + ] + } + ] + }, + { + "name": "Nx Plugin", + "id": "nx-plugin", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/nx-plugin Overview", + "file": "shared/nx-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "executor", + "id": "executor", + "file": "angular/api-nx-plugin/generators/executor" + }, + { + "name": "migration", + "id": "migration", + "file": "angular/api-nx-plugin/generators/migration" + }, + { + "name": "plugin", + "id": "plugin", + "file": "angular/api-nx-plugin/generators/plugin" + }, + { + "name": "generator", + "id": "schematic", + "file": "angular/api-nx-plugin/generators/generator" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "e2e", + "id": "e2e", + "file": "angular/api-nx-plugin/executors/e2e" + } + ] + } + ] } ] }, @@ -2229,6 +2327,104 @@ "name": "run-commands executor", "id": "run-commands", "file": "node/api-workspace/executors/run-commands" + }, + { + "name": "gatsby", + "id": "gatsby", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/gatsby Overview", + "file": "shared/gatsby-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "application", + "id": "application", + "file": "react/api-gatsby/generators/application" + }, + { + "name": "component", + "id": "component", + "file": "react/api-gatsby/generators/component" + }, + { + "name": "page", + "id": "page", + "file": "react/api-gatsby/generators/page" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "build", + "id": "build", + "file": "react/api-gatsby/executors/build" + }, + { + "name": "server", + "id": "server", + "file": "react/api-gatsby/executors/server" + } + ] + } + ] + }, + { + "name": "Nx Plugin", + "id": "nx-plugin", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/nx-plugin Overview", + "file": "shared/nx-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "executor", + "id": "executor", + "file": "react/api-nx-plugin/generators/executor" + }, + { + "name": "migration", + "id": "migration", + "file": "react/api-nx-plugin/generators/migration" + }, + { + "name": "plugin", + "id": "plugin", + "file": "react/api-nx-plugin/generators/plugin" + }, + { + "name": "generator", + "id": "schematic", + "file": "react/api-nx-plugin/generators/generator" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "e2e", + "id": "e2e", + "file": "react/api-nx-plugin/executors/e2e" + } + ] + } + ] } ] }, @@ -2769,6 +2965,104 @@ "name": "Dependency Graph", "id": "dependency-graph", "file": "shared/workspace/structure/dependency-graph" + }, + { + "name": "gatsby", + "id": "gatsby", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/gatsby Overview", + "file": "shared/gatsby-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "application", + "id": "application", + "file": "node/api-gatsby/generators/application" + }, + { + "name": "component", + "id": "component", + "file": "node/api-gatsby/generators/component" + }, + { + "name": "page", + "id": "page", + "file": "node/api-gatsby/generators/page" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "build", + "id": "build", + "file": "node/api-gatsby/executors/build" + }, + { + "name": "server", + "id": "server", + "file": "node/api-gatsby/executors/server" + } + ] + } + ] + }, + { + "name": "Nx Plugin", + "id": "nx-plugin", + "itemList": [ + { + "id": "overview", + "name": "Overview", + "searchResultsName": "@nrwl/nx-plugin Overview", + "file": "shared/nx-plugin" + }, + { + "id": "generators", + "name": "Generators", + "itemList": [ + { + "name": "executor", + "id": "executor", + "file": "node/api-nx-plugin/generators/executor" + }, + { + "name": "migration", + "id": "migration", + "file": "node/api-nx-plugin/generators/migration" + }, + { + "name": "plugin", + "id": "plugin", + "file": "node/api-nx-plugin/generators/plugin" + }, + { + "name": "generator", + "id": "schematic", + "file": "node/api-nx-plugin/generators/generator" + } + ] + }, + { + "id": "executors", + "name": "Executors / Builders", + "itemList": [ + { + "name": "e2e", + "id": "e2e", + "file": "node/api-nx-plugin/executors/e2e" + } + ] + } + ] } ] }, diff --git a/docs/node/api-gatsby/executors/build.md b/docs/node/api-gatsby/executors/build.md new file mode 100644 index 0000000000..cfb459a584 --- /dev/null +++ b/docs/node/api-gatsby/executors/build.md @@ -0,0 +1,48 @@ +# build + +Build a Gatsby app + +Properties can be configured in workspace.json when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/node/guides/cli. + +## Properties + +### color + +Default: `true` + +Type: `boolean` + +Enable colored terminal output. + +### graphqlTracing + +Type: `boolean` + +Trace every graphql resolver, may have performance implications. + +### openTracingConfigFile + +Type: `string` + +Tracer configuration file (OpenTracing compatible). + +### prefixPaths + +Type: `boolean` + +Build site with link paths prefixed (set pathPrefix in your config). + +### profile + +Type: `boolean` + +Build site with react profiling. + +### uglify + +Default: `true` + +Type: `boolean` + +Build site without uglifying JS bundles (true by default). diff --git a/docs/node/api-gatsby/executors/server.md b/docs/node/api-gatsby/executors/server.md new file mode 100644 index 0000000000..7dcf9531e6 --- /dev/null +++ b/docs/node/api-gatsby/executors/server.md @@ -0,0 +1,44 @@ +# server + +Starts server for app + +Properties can be configured in workspace.json when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/node/guides/cli. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### host + +Default: `localhost` + +Type: `string` + +Host to listen on. + +### https + +Default: `false` + +Type: `boolean` + +Serve using HTTPS. + +### open + +Type: `boolean` + +Open the site in your (default) browser for you. + +### port + +Default: `4200` + +Type: `number` + +Port to listen on. diff --git a/docs/node/api-gatsby/generators/application.md b/docs/node/api-gatsby/generators/application.md new file mode 100644 index 0000000000..444d19ee1b --- /dev/null +++ b/docs/node/api-gatsby/generators/application.md @@ -0,0 +1,89 @@ +# application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +A directory where the project is placed + +### e2eTestRunner + +Default: `cypress` + +Type: `string` + +Possible values: `cypress`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### name + +Type: `string` + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the project (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Adds the specified unit test runner diff --git a/docs/node/api-gatsby/generators/component.md b/docs/node/api-gatsby/generators/component.md new file mode 100644 index 0000000000..505bd2d0d8 --- /dev/null +++ b/docs/node/api-gatsby/generators/component.md @@ -0,0 +1,107 @@ +# component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +By default, Nx will search for `component` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/node/api-gatsby/generators/page.md b/docs/node/api-gatsby/generators/page.md new file mode 100644 index 0000000000..9310106396 --- /dev/null +++ b/docs/node/api-gatsby/generators/page.md @@ -0,0 +1,107 @@ +# page + +Create a page + +## Usage + +```bash +nx generate page ... +``` + +By default, Nx will search for `page` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:page ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g page ... --dry-run +``` + +### Examples + +Generate a page in the mylib library: + +```bash +nx g page my-page --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g page my-page --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/node/executors.json b/docs/node/executors.json index 722846ec08..ded073647f 100644 --- a/docs/node/executors.json +++ b/docs/node/executors.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "linter", "nest", diff --git a/docs/node/generators.json b/docs/node/generators.json index ae621ac6e8..46a738dc38 100644 --- a/docs/node/generators.json +++ b/docs/node/generators.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "nest", "next", diff --git a/docs/react/api-gatsby/executors/build.md b/docs/react/api-gatsby/executors/build.md new file mode 100644 index 0000000000..72d1ac9f40 --- /dev/null +++ b/docs/react/api-gatsby/executors/build.md @@ -0,0 +1,48 @@ +# build + +Build a Gatsby app + +Properties can be configured in workspace.json when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/react/guides/cli. + +## Properties + +### color + +Default: `true` + +Type: `boolean` + +Enable colored terminal output. + +### graphqlTracing + +Type: `boolean` + +Trace every graphql resolver, may have performance implications. + +### openTracingConfigFile + +Type: `string` + +Tracer configuration file (OpenTracing compatible). + +### prefixPaths + +Type: `boolean` + +Build site with link paths prefixed (set pathPrefix in your config). + +### profile + +Type: `boolean` + +Build site with react profiling. + +### uglify + +Default: `true` + +Type: `boolean` + +Build site without uglifying JS bundles (true by default). diff --git a/docs/react/api-gatsby/executors/server.md b/docs/react/api-gatsby/executors/server.md new file mode 100644 index 0000000000..46aae31a1c --- /dev/null +++ b/docs/react/api-gatsby/executors/server.md @@ -0,0 +1,44 @@ +# server + +Starts server for app + +Properties can be configured in workspace.json when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/react/guides/cli. + +## Properties + +### buildTarget + +Type: `string` + +Target which builds the application + +### host + +Default: `localhost` + +Type: `string` + +Host to listen on. + +### https + +Default: `false` + +Type: `boolean` + +Serve using HTTPS. + +### open + +Type: `boolean` + +Open the site in your (default) browser for you. + +### port + +Default: `4200` + +Type: `number` + +Port to listen on. diff --git a/docs/react/api-gatsby/generators/application.md b/docs/react/api-gatsby/generators/application.md new file mode 100644 index 0000000000..444d19ee1b --- /dev/null +++ b/docs/react/api-gatsby/generators/application.md @@ -0,0 +1,89 @@ +# application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +A directory where the project is placed + +### e2eTestRunner + +Default: `cypress` + +Type: `string` + +Possible values: `cypress`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### name + +Type: `string` + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the project (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Adds the specified unit test runner diff --git a/docs/react/api-gatsby/generators/component.md b/docs/react/api-gatsby/generators/component.md new file mode 100644 index 0000000000..505bd2d0d8 --- /dev/null +++ b/docs/react/api-gatsby/generators/component.md @@ -0,0 +1,107 @@ +# component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +By default, Nx will search for `component` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/react/api-gatsby/generators/page.md b/docs/react/api-gatsby/generators/page.md new file mode 100644 index 0000000000..9310106396 --- /dev/null +++ b/docs/react/api-gatsby/generators/page.md @@ -0,0 +1,107 @@ +# page + +Create a page + +## Usage + +```bash +nx generate page ... +``` + +By default, Nx will search for `page` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/gatsby:page ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g page ... --dry-run +``` + +### Examples + +Generate a page in the mylib library: + +```bash +nx g page my-page --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g page my-page --project=mylib --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### name + +Type: `string` + +The name of the component. + +### project + +Alias(es): p + +Type: `string` + +The name of the project. + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. + +### style + +Alias(es): s + +Default: `css` + +Type: `string` + +Possible values: `css`, `scss`, `styl`, `less`, `styled-components`, `@emotion/styled`, `none` + +The file extension to be used for style files. diff --git a/docs/react/executors.json b/docs/react/executors.json index 722846ec08..ded073647f 100644 --- a/docs/react/executors.json +++ b/docs/react/executors.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "linter", "nest", diff --git a/docs/react/generators.json b/docs/react/generators.json index ae621ac6e8..46a738dc38 100644 --- a/docs/react/generators.json +++ b/docs/react/generators.json @@ -2,6 +2,7 @@ "angular", "cypress", "express", + "gatsby", "jest", "nest", "next", diff --git a/docs/shared/gatsby-plugin.md b/docs/shared/gatsby-plugin.md new file mode 100644 index 0000000000..e3d47d2402 --- /dev/null +++ b/docs/shared/gatsby-plugin.md @@ -0,0 +1,73 @@ +# Next.js Plugin + +The Nx Plugin for Next.js contains executors and generators for managing Next.js applications and libraries within an Nx workspace. It provides: + +- Scaffolding for creating, building, serving, linting, and testing Next.js applications. +- Integration with building, serving, and exporting a Next.js application. +- Integration with React libraries within the workspace. + +## Installing the Next.js Plugin + +Installing the Next plugin to a workspace can be done with the following: + +```shell script +yarn add -D @nrwl/next +``` + +```shell script +npm install -D @nrwl/next +``` + +## Applications + +Generating new applications can be done with the following: + +```shell script +nx generate @nrwl/next:application +``` + +This creates the following app structure: + +```treeview +myorg/ +├── apps/ +│   ├── myapp/ +│   │   ├── pages/ +│   │   │   ├── index.css +│   │   │   └── index.tsx +│   │   ├── jest.conf.js +│   │   ├── tsconfig.json +│   │   ├── tsconfig.spec.json +│   │   └── .eslintrc.json +│   └── myapp-e2e/ +│   │   ├── src/ +│   │   │   ├── integrations/ +│   │   │   │   └── app.spec.ts +│   │   │   ├── fixtures/ +│   │   │   ├── plugins/ +│   │   │   └── support/ +│   │   ├── cypress.json +│   │   ├── tsconfig.e2e.json +│   │   └── .eslintrc.json +├── libs/ +├── workspace.json +├── nx.json +├── package.json +├── tools/ +├── tsconfig.json +└── .eslintrc.json +``` + +## See Also + +- [Using Next.js](https://nextjs.org/docs/getting-started) + +## Executors / Builders + +- [build](/{{framework}}/plugins/next/executors/build) - Builds a Next.js application +- [dev-server](/{{framework}}/plugins/next/executors/dev-server) - Builds and serves a Next.js application +- [export](/{{framework}}/plugins/next/executors/package) - Export a Next.js app. The exported application is located at `dist/$outputPath/exported` + +## Generators + +- [application](/{{framework}}/plugins/next/generators/application) - Create an Next.js application diff --git a/docs/shared/next-plugin.md b/docs/shared/next-plugin.md index e3d47d2402..5ab8efa4a6 100644 --- a/docs/shared/next-plugin.md +++ b/docs/shared/next-plugin.md @@ -1,21 +1,21 @@ -# Next.js Plugin +# Gatsby Plugin -The Nx Plugin for Next.js contains executors and generators for managing Next.js applications and libraries within an Nx workspace. It provides: +The Nx Plugin for Gatsby contains executors and generators for managing Gatsby applications and libraries within an Nx workspace. It provides: -- Scaffolding for creating, building, serving, linting, and testing Next.js applications. -- Integration with building, serving, and exporting a Next.js application. +- Scaffolding for creating, building, serving, linting, and testing Gatsby applications. +- Integration with building, serving, and exporting a Gatsby application. - Integration with React libraries within the workspace. -## Installing the Next.js Plugin +## Installing the Gatsby Plugin -Installing the Next plugin to a workspace can be done with the following: +Installing the Gatsby plugin to a workspace can be done with the following: ```shell script -yarn add -D @nrwl/next +yarn add -D @nrwl/gatsby ``` ```shell script -npm install -D @nrwl/next +npm install -D @nrwl/gatsby ``` ## Applications @@ -23,7 +23,7 @@ npm install -D @nrwl/next Generating new applications can be done with the following: ```shell script -nx generate @nrwl/next:application +nx generate @nrwl/gatsby:application ``` This creates the following app structure: @@ -32,11 +32,13 @@ This creates the following app structure: myorg/ ├── apps/ │   ├── myapp/ -│   │   ├── pages/ -│   │   │   ├── index.css -│   │   │   └── index.tsx +│   │   ├── src/ +│   │   │   ├── pages/ +│   │   │   │   ├── index.module.css +│   │   │   │   └── index.tsx │   │   ├── jest.conf.js │   │   ├── tsconfig.json +│   │   ├── tsconfig.app.json │   │   ├── tsconfig.spec.json │   │   └── .eslintrc.json │   └── myapp-e2e/ @@ -60,14 +62,15 @@ myorg/ ## See Also -- [Using Next.js](https://nextjs.org/docs/getting-started) +- [Using Gatsby](https://www.gatsbyjs.com/docs/quick-start/) ## Executors / Builders -- [build](/{{framework}}/plugins/next/executors/build) - Builds a Next.js application -- [dev-server](/{{framework}}/plugins/next/executors/dev-server) - Builds and serves a Next.js application -- [export](/{{framework}}/plugins/next/executors/package) - Export a Next.js app. The exported application is located at `dist/$outputPath/exported` +- [build](/{{framework}}/plugins/gatsby/executors/build) - Builds a Gatsby application +- [server](/{{framework}}/plugins/gatsby/executors/server) - Builds and serves a Gatsby application ## Generators -- [application](/{{framework}}/plugins/next/generators/application) - Create an Next.js application +- [application](/{{framework}}/plugins/gatsby/generators/application) - Create a Gatsby application +- [component](/{{framework}}/plugins/gatsby/generators/component) - Create a Gatsby component +- [page](/{{framework}}/plugins/gatsby/generators/page) - Create a Gatsby page diff --git a/e2e/gatsby/jest.config.js b/e2e/gatsby/jest.config.js new file mode 100644 index 0000000000..20df9ab064 --- /dev/null +++ b/e2e/gatsby/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + maxWorkers: 1, + globals: { 'ts-jest': { tsConfig: '/tsconfig.spec.json' } }, + displayName: 'e2e-next', +}; diff --git a/e2e/gatsby/src/gatsby.test.ts b/e2e/gatsby/src/gatsby.test.ts new file mode 100644 index 0000000000..6a1aa9d5a6 --- /dev/null +++ b/e2e/gatsby/src/gatsby.test.ts @@ -0,0 +1,59 @@ +import { + checkFilesExist, + newProject, + runCLI, + runCLIAsync, + uniq, + updateFile, +} from '@nrwl/e2e/utils'; + +describe('Gatsby Applications', () => { + let proj: string; + + beforeEach(() => (proj = newProject())); + + it('should generate a valid gatsby application', async () => { + const appName = uniq('app'); + runCLI(`generate @nrwl/gatsby:app ${appName}`); + runCLI(`generate @nrwl/gatsby:component header --project ${appName}`); + + checkFilesExist( + `apps/${appName}/package.json`, + `apps/${appName}/src/components/header.tsx`, + `apps/${appName}/src/components/header.spec.tsx`, + `apps/${appName}/src/pages/index.tsx`, + `apps/${appName}/src/pages/index.spec.tsx` + ); + + updateFile(`apps/${appName}/src/pages/index.tsx`, (content) => { + let updated = `import Header from '../components/header';\n${content}`; + updated = updated.replace('
', '
'); + return updated; + }); + + let result = runCLI(`build ${appName}`); + expect(result).toContain('Done building in'); + + result = runCLI(`lint ${appName}`); + expect(result).not.toMatch('Lint errors found in the listed files'); + + const testResults = await runCLIAsync(`test ${appName}`); + expect(testResults.combinedOutput).toContain( + 'Test Suites: 2 passed, 2 total' + ); + }, 120000); + + test('supports --js option', async () => { + const app = uniq('app'); + runCLI(`generate @nrwl/gatsby:app ${app} --js`); + + checkFilesExist( + `apps/${app}/package.json`, + `apps/${app}/src/pages/index.js`, + `apps/${app}/src/pages/index.spec.js` + ); + + const result = runCLI(`build ${app}`); + expect(result).toContain('Done building in'); + }, 120000); +}); diff --git a/e2e/gatsby/tsconfig.json b/e2e/gatsby/tsconfig.json new file mode 100644 index 0000000000..6d5abf8483 --- /dev/null +++ b/e2e/gatsby/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/gatsby/tsconfig.spec.json b/e2e/gatsby/tsconfig.spec.json new file mode 100644 index 0000000000..af4ac638d6 --- /dev/null +++ b/e2e/gatsby/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index 983b4b3920..9d8640ec1e 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -139,6 +139,7 @@ export function newProject({ name = uniq('proj') } = {}): string { `@nrwl/express`, `@nrwl/nest`, `@nrwl/next`, + `@nrwl/gatsby`, `@nrwl/node`, `@nrwl/react`, `@nrwl/storybook`, diff --git a/images/nx-gatsby.png b/images/nx-gatsby.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9746bcbd49a0365510b885ac20d6af6da7f058 GIT binary patch literal 45391 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 literal 0 HcmV?d00001 diff --git a/jest.config.js b/jest.config.js index b4bf8dcf04..8dac1b30b5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,5 +18,6 @@ module.exports = { '/packages/create-nx-plugin', '/packages/cli', '/packages/angular', + '/packages/gatsby', ], }; diff --git a/nx.json b/nx.json index d2fd4ea908..770ca88310 100644 --- a/nx.json +++ b/nx.json @@ -124,6 +124,10 @@ "tags": [], "implicitDependencies": ["cypress"] }, + "e2e-gatsby": { + "tags": [], + "implicitDependencies": ["gatsby"] + }, "e2e-jest": { "tags": [], "implicitDependencies": ["jest"] @@ -166,6 +170,9 @@ "dep-graph-client-e2e": { "tags": [], "implicitDependencies": ["dep-graph-client"] + }, + "gatsby": { + "tags": [] } }, "affected": { diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 48055f94fc..147ecea914 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -30,6 +30,10 @@ const presetOptions: { value: Preset; name: string }[] = [ value: Preset.NextJs, name: 'next.js [a workspace with a single Next.js application]', }, + { + value: Preset.Gatsby, + name: 'gatsby [a workspace with a single Gatsby application]', + }, { value: Preset.Nest, name: 'nest [a workspace with a single Nest application]', @@ -480,6 +484,7 @@ function pointToTutorialAndCourse(preset: Preset) { case Preset.React: case Preset.ReactWithExpress: case Preset.NextJs: + case Preset.Gatsby: output.addVerticalSeparator(); output.note({ title: title, diff --git a/packages/gatsby/.eslintrc.json b/packages/gatsby/.eslintrc.json new file mode 100644 index 0000000000..9afd5d63f0 --- /dev/null +++ b/packages/gatsby/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": "../../.eslintrc", + "rules": {}, + "ignorePatterns": ["!**/*"] +} diff --git a/packages/gatsby/README.md b/packages/gatsby/README.md new file mode 100644 index 0000000000..6323a61c13 --- /dev/null +++ b/packages/gatsby/README.md @@ -0,0 +1,40 @@ +

+ +{{links}} + +
+ +# Gatsby Plugin for Nx + +{{what-is-nx}} + +{{getting-started}} + +``` +? Workspace name (e.g., org name) happyorg +? What to create in the new workspace gatsby [a workspace with a single Gatsby application] +? Application name myapp +? Default stylesheet format CSS +``` + +You can also select `empty` and add `@nrwl/gatsby` plugin using yarn or npm, and then generate a new express app using `nx g @nrwl/gatsby:app myapp`. + +If it's your first Nx project, the command will recommend you to install the `nx` package globally, so you can invoke `nx` directly without going through yarn or npm. + +### Serving Application + +- Run `nx serve myapp` to serve the newly generated application! +- Run `nx test myapp` to test it. +- Run `nx e2e myapp` to run e2e tests for your application. + +You are good to go! + +## Quick Start Videos + + +

+
+ +- [Nx Dev Tools for Monorepos, In-Depth Explainer (React)](https://www.youtube.com/watch?v=jCf92IyR-GE) + +{{resources}} diff --git a/packages/gatsby/babel.ts b/packages/gatsby/babel.ts new file mode 100644 index 0000000000..65f2393365 --- /dev/null +++ b/packages/gatsby/babel.ts @@ -0,0 +1,9 @@ +/* + * Babel preset to provide Gatsby support for Nx. + */ +module.exports = function (api, options) { + api.assertVersion(7); + return { + presets: [[require.resolve('babel-preset-gatsby'), { useBuiltIns: true }]], + }; +}; diff --git a/packages/gatsby/builders.json b/packages/gatsby/builders.json new file mode 100644 index 0000000000..88fbc9181c --- /dev/null +++ b/packages/gatsby/builders.json @@ -0,0 +1,15 @@ +{ + "$schema": "@angular-devkit/architect/src/builders-schema.json", + "builders": { + "build": { + "implementation": "./src/builders/build/builder", + "schema": "./src/builders/build/schema.json", + "description": "Build a Gatsby app" + }, + "server": { + "implementation": "./src/builders/server/builder", + "schema": "./src/builders/server/schema.json", + "description": "Starts server for app" + } + } +} diff --git a/packages/gatsby/collection.json b/packages/gatsby/collection.json new file mode 100644 index 0000000000..6208ed71ab --- /dev/null +++ b/packages/gatsby/collection.json @@ -0,0 +1,29 @@ +{ + "name": "nx/gatsby", + "version": "0.1", + "extends": ["@nrwl/react"], + "schematics": { + "init": { + "factory": "./src/schematics/init/init", + "schema": "./src/schematics/init/schema.json", + "description": "Initialize the @nrwl/gatsby plugin", + "hidden": true + }, + "application": { + "factory": "./src/schematics/application/application", + "schema": "./src/schematics/application/schema.json", + "aliases": ["app"], + "description": "Create an application" + }, + "component": { + "factory": "./src/schematics/component/component", + "schema": "./src/schematics/component/schema.json", + "description": "Create a component" + }, + "page": { + "factory": "./src/schematics/page/page", + "schema": "./src/schematics/page/schema.json", + "description": "Create a page" + } + } +} diff --git a/packages/gatsby/index.ts b/packages/gatsby/index.ts new file mode 100644 index 0000000000..85b66a09ea --- /dev/null +++ b/packages/gatsby/index.ts @@ -0,0 +1,3 @@ +export { applicationGenerator } from './src/schematics/application/application'; +export { componentGenerator } from './src/schematics/component/component'; +export { pageGenerator } from './src/schematics/page/page'; diff --git a/packages/gatsby/jest.config.js b/packages/gatsby/jest.config.js new file mode 100644 index 0000000000..338780b932 --- /dev/null +++ b/packages/gatsby/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + globals: { 'ts-jest': { tsConfig: '/tsconfig.spec.json' } }, + displayName: 'gatsby', +}; diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json new file mode 100644 index 0000000000..7d9cf263de --- /dev/null +++ b/packages/gatsby/package.json @@ -0,0 +1,41 @@ +{ + "name": "@nrwl/gatsby", + "version": "0.0.1", + "description": "Gatsby Plugin for Nx", + "repository": { + "type": "git", + "url": "git+https://github.com/nrwl/nx.git" + }, + "keywords": [ + "Monorepo", + "Node", + "Express", + "Jest", + "Cypress", + "CLI" + ], + "main": "index.js", + "types": "index.d.ts", + "author": "Victor Savkin", + "license": "MIT", + "bugs": { + "url": "https://github.com/nrwl/nx/issues" + }, + "homepage": "https://nx.dev", + "schematics": "./collection.json", + "builders": "./builders.json", + "ng-update": { + "requirements": {}, + "migrations": "./migrations.json" + }, + "dependencies": { + "@angular-devkit/core": "~11.0.1", + "@angular-devkit/schematics": "~11.0.1", + "@nrwl/cypress": "*", + "@nrwl/devkit": "*", + "@nrwl/jest": "*", + "@nrwl/node": "*", + "@nrwl/react": "*", + "gatsby-cli": "^2.17.1" + } +} diff --git a/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-config.ts b/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-config.ts new file mode 100644 index 0000000000..7b21fd565d --- /dev/null +++ b/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-config.ts @@ -0,0 +1,3 @@ +module.exports = { + plugins: [`gatsby-plugin-typescript`], +}; diff --git a/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-node.ts b/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-node.ts new file mode 100644 index 0000000000..963be0f342 --- /dev/null +++ b/packages/gatsby/plugins/nx-gatsby-ext-plugin/gatsby-node.ts @@ -0,0 +1,26 @@ +import * as path from 'path'; +import { appRootPath as workspaceRoot } from '@nrwl/workspace/src/utils/app-root'; +import { readJsonFile } from '@nrwl/workspace'; + +function onCreateBabelConfig({ actions }, options) { + const tsConfig = readJsonFile(path.join(workspaceRoot, 'tsconfig.base.json')); + const tsConfigPaths: { [key: string]: Array } = + tsConfig.compilerOptions.paths; + + const paths = Object.entries(tsConfigPaths).reduce((result, [key, paths]) => { + return { + ...result, + [key]: paths.map((p) => path.join(workspaceRoot, p)), + }; + }, {}); + + actions.setBabelPlugin({ + name: require.resolve(`babel-plugin-module-resolver`), + options: { + root: ['./src'], + alias: paths, + }, + }); +} + +export { onCreateBabelConfig }; diff --git a/packages/gatsby/plugins/nx-gatsby-ext-plugin/package.json b/packages/gatsby/plugins/nx-gatsby-ext-plugin/package.json new file mode 100644 index 0000000000..2b0b8b2312 --- /dev/null +++ b/packages/gatsby/plugins/nx-gatsby-ext-plugin/package.json @@ -0,0 +1,10 @@ +{ + "name": "nx-gatsby-ext-plugin", + "version": "1.0.0", + "main": "gatsby-config.js", + "author": "max koretskyi", + "license": "MIT", + "peerDependencies": { + "gatsby": "^2.6.0" + } +} diff --git a/packages/gatsby/src/builders/build/builder.ts b/packages/gatsby/src/builders/build/builder.ts new file mode 100644 index 0000000000..2d879b0ef9 --- /dev/null +++ b/packages/gatsby/src/builders/build/builder.ts @@ -0,0 +1,93 @@ +import { + BuilderContext, + BuilderOutput, + createBuilder, +} from '@angular-devkit/architect'; +import { fork } from 'child_process'; +import { join } from 'path'; +import { from, Observable } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { GatsbyPluginBuilderSchema } from './schema'; +import { getProjectRoot } from '../../utils/get-project-root'; + +export function runBuilder( + options: GatsbyPluginBuilderSchema, + context: BuilderContext +): Observable { + return from(getProjectRoot(context)).pipe( + concatMap((projectRoot) => { + return new Observable((subscriber) => { + runGatsbyBuild(context.workspaceRoot, projectRoot, options) + .then(() => { + subscriber.next({ + success: true, + }); + subscriber.complete(); + }) + .catch((err) => { + context.logger.error('Error during build', err); + subscriber.next({ + success: false, + }); + subscriber.complete(); + }); + }); + }) + ); +} + +export function runGatsbyBuild( + workspaceRoot: string, + projectRoot: string, + options: GatsbyPluginBuilderSchema +) { + return new Promise((resolve, reject) => { + const cp = fork( + require.resolve('gatsby-cli'), + ['build', ...createGatsbyBuildOptions(options)], + { + cwd: join(workspaceRoot, projectRoot), + } + ); + + cp.on('error', (err) => { + reject(err); + }); + + cp.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(code); + } + }); + }); +} + +function createGatsbyBuildOptions(options: GatsbyPluginBuilderSchema) { + return Object.keys(options).reduce((acc, k) => { + const val = options[k]; + if (typeof val === 'undefined') return acc; + switch (k) { + case 'prefixPaths': + return val ? acc.concat(`--prefix-paths`) : acc; + case 'uglify': + return val ? acc : acc.concat('--no-uglify'); + case 'color': + return val ? acc : acc.concat('--no-color'); + case 'profile': + return val ? acc.concat('--profile') : acc; + case 'openTracingConfigFile': + return val ? acc.concat([`--open-tracing-config-file`, val]) : acc; + case 'graphqlTracing': + return val ? acc.concat('--graphql-tracing') : acc; + case 'serve': + case 'host': + case 'port': + default: + return acc; + } + }, []); +} + +export default createBuilder(runBuilder); diff --git a/packages/gatsby/src/builders/build/schema.d.ts b/packages/gatsby/src/builders/build/schema.d.ts new file mode 100644 index 0000000000..894d9d535c --- /dev/null +++ b/packages/gatsby/src/builders/build/schema.d.ts @@ -0,0 +1,10 @@ +import { JsonObject } from '@angular-devkit/core'; + +export interface GatsbyPluginBuilderSchema extends JsonObject { + prefixPaths?: boolean; + uglify: boolean; + profile?: boolean; + openTracingConfigFile?: string; + graphqlTracing?: boolean; + color: boolean; +} diff --git a/packages/gatsby/src/builders/build/schema.json b/packages/gatsby/src/builders/build/schema.json new file mode 100644 index 0000000000..cc1e750a3d --- /dev/null +++ b/packages/gatsby/src/builders/build/schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "GatsbyPlugin builder", + "description": "", + "type": "object", + "properties": { + "prefixPaths": { + "type": "boolean", + "description": "Build site with link paths prefixed (set pathPrefix in your config)." + }, + "uglify": { + "type": "boolean", + "description": "Build site without uglifying JS bundles (true by default).", + "default": true + }, + "profile": { + "type": "boolean", + "description": "Build site with react profiling." + }, + "openTracingConfigFile": { + "type": "string", + "description": "Tracer configuration file (OpenTracing compatible)." + }, + "graphqlTracing": { + "type": "boolean", + "description": "Trace every graphql resolver, may have performance implications." + }, + "color": { + "type": "boolean", + "description": "Enable colored terminal output.", + "default": true + } + }, + "required": [] +} diff --git a/packages/gatsby/src/builders/server/builder.ts b/packages/gatsby/src/builders/server/builder.ts new file mode 100644 index 0000000000..8ea346440a --- /dev/null +++ b/packages/gatsby/src/builders/server/builder.ts @@ -0,0 +1,161 @@ +import { + BuilderContext, + BuilderOutput, + createBuilder, + targetFromTargetString, +} from '@angular-devkit/architect'; +import { fork } from 'child_process'; +import { join } from 'path'; +import { from, Observable, of } from 'rxjs'; +import { catchError, concatMap, withLatestFrom } from 'rxjs/operators'; +import { GatsbyPluginBuilderSchema } from './schema'; +import { runGatsbyBuild } from '../build/builder'; +import { GatsbyPluginBuilderSchema as BuildBuilderSchema } from '../build/schema'; +import { getProjectRoot } from '../../utils/get-project-root'; + +export function runBuilder( + options: GatsbyPluginBuilderSchema, + context: BuilderContext +): Observable { + const buildTarget = targetFromTargetString(options.buildTarget); + const baseUrl = `${options.https ? 'https' : 'http'}://${options.host}:${ + options.port + }`; + return from(getProjectRoot(context)).pipe( + concatMap((projectRoot) => { + return from(context.getTargetOptions(buildTarget)).pipe( + concatMap((buildOptions: BuildBuilderSchema) => { + return new Observable((subscriber) => { + if (context.target.configuration === 'production') { + runGatsbyBuild(context.workspaceRoot, projectRoot, buildOptions) + .then(() => + runGatsbyServe(context.workspaceRoot, projectRoot, options) + ) + .then(() => { + subscriber.next({ + success: true, + }); + }) + .catch((err) => { + context.logger.error('Error during serve', err); + }); + } else { + runGatsbyDevelop( + context.workspaceRoot, + projectRoot, + createGatsbyOptions(options) + ) + .then((success) => { + subscriber.next({ + baseUrl, + success, + }); + }) + .catch((err) => { + context.logger.error('Error during serve', err?.message); + subscriber.next({ + success: false, + }); + subscriber.complete(); + }); + } + }); + }), + catchError((err) => { + context.logger.error(err); + return of({ success: false }); + }) + ); + }) + ); +} + +function createGatsbyOptions(options) { + return Object.keys(options).reduce((acc, k) => { + if (k === 'port' || k === 'host' || k === 'https' || k === 'open') + acc.push(`--${k}=${options[k]}`); + return acc; + }, []); +} + +async function runGatsbyDevelop(workspaceRoot, projectRoot, options) { + return new Promise((resolve, reject) => { + const cp = fork(require.resolve('gatsby-cli'), ['develop', ...options], { + cwd: join(workspaceRoot, projectRoot), + env: { + ...process.env, + }, + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + }); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => cp.kill()); + + cp.on('message', ({ action }) => { + if ( + action?.type === 'ACTIVITY_END' && + action?.payload?.status === 'SUCCESS' && + action?.payload?.id === 'webpack-develop' + ) { + resolve(true); + } + }); + + cp.on('error', (err) => { + reject(err); + }); + + cp.on('exit', (code) => { + if (code !== 0) { + reject(code); + } + }); + }); +} + +function runGatsbyServe( + workspaceRoot: string, + projectRoot: string, + options: GatsbyPluginBuilderSchema +) { + return new Promise((resolve, reject) => { + const cp = fork( + require.resolve('gatsby-cli'), + ['serve', ...createGatsbyServeOptions(options)], + { cwd: join(workspaceRoot, projectRoot) } + ); + + cp.on('error', (err) => { + reject(err); + }); + + cp.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +function createGatsbyServeOptions(options: GatsbyPluginBuilderSchema) { + return Object.keys(options).reduce((acc, k) => { + const val = options[k]; + if (typeof val === 'undefined') return acc; + switch (k) { + case 'host': + return val ? acc.concat([`--host`, val]) : acc; + case 'open': + return val ? acc.concat(`--open`) : acc; + case 'prefixPaths': + return val ? acc.concat(`--prefix-paths`) : acc; + case 'port': + return val ? acc.concat([`--port`, val]) : acc; + default: + return acc; + } + }, []); +} + +export default createBuilder(runBuilder); diff --git a/packages/gatsby/src/builders/server/schema.d.ts b/packages/gatsby/src/builders/server/schema.d.ts new file mode 100644 index 0000000000..0c6c9f2c6e --- /dev/null +++ b/packages/gatsby/src/builders/server/schema.d.ts @@ -0,0 +1,9 @@ +import { JsonObject } from '@angular-devkit/core'; + +export interface GatsbyPluginBuilderSchema extends JsonObject { + buildTarget: string; + host: string; + port: string; + open: boolean; + https: boolean; +} diff --git a/packages/gatsby/src/builders/server/schema.json b/packages/gatsby/src/builders/server/schema.json new file mode 100644 index 0000000000..ddcc53e8ce --- /dev/null +++ b/packages/gatsby/src/builders/server/schema.json @@ -0,0 +1,31 @@ +{ + "title": "Gatsby development server", + "description": "Gatsby development server", + "type": "object", + "properties": { + "buildTarget": { + "type": "string", + "description": "Target which builds the application" + }, + "port": { + "type": "number", + "description": "Port to listen on.", + "default": 4200 + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + }, + "https": { + "type": "boolean", + "description": "Serve using HTTPS.", + "default": false + }, + "open": { + "type": "boolean", + "description": "Open the site in your (default) browser for you." + } + }, + "required": [] +} diff --git a/packages/gatsby/src/index.ts b/packages/gatsby/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/gatsby/src/schematics/application/application.spec.ts b/packages/gatsby/src/schematics/application/application.spec.ts new file mode 100644 index 0000000000..054c21a3a5 --- /dev/null +++ b/packages/gatsby/src/schematics/application/application.spec.ts @@ -0,0 +1,320 @@ +import { Tree } from '@angular-devkit/schematics'; +import { NxJson, readJsonInTree } from '@nrwl/workspace'; +import { createEmptyWorkspace } from '@nrwl/workspace/testing'; +import { runSchematic } from '../../utils/testing'; + +describe('app', () => { + let appTree: Tree; + + beforeEach(() => { + appTree = Tree.empty(); + appTree = createEmptyWorkspace(appTree); + }); + + it('should update workspace.json', async () => { + const tree = await runSchematic('app', { name: 'myApp' }, appTree); + const workspaceJson = readJsonInTree(tree, '/workspace.json'); + + expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app'); + expect(workspaceJson.projects['my-app-e2e'].root).toEqual( + 'apps/my-app-e2e' + ); + expect(workspaceJson.defaultProject).toEqual('my-app'); + }); + + it('should update nx.json', async () => { + const tree = await runSchematic( + 'app', + { name: 'myApp', tags: 'one,two' }, + appTree + ); + const nxJson = readJsonInTree(tree, '/nx.json'); + expect(nxJson.projects).toEqual({ + 'my-app': { + tags: ['one', 'two'], + }, + 'my-app-e2e': { + tags: [], + implicitDependencies: ['my-app'], + }, + }); + }); + + it('should generate files', async () => { + const tree = await runSchematic('app', { name: 'myApp' }, appTree); + expect(tree.exists('apps/my-app/tsconfig.json')).toBeTruthy(); + expect(tree.exists('apps/my-app/tsconfig.app.json')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/pages/index.tsx')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/pages/index.spec.tsx')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/pages/index.module.css')).toBeTruthy(); + }); + + describe('--style scss', () => { + it('should generate scss styles', async () => { + const result = await runSchematic( + 'app', + { name: 'myApp', style: 'scss' }, + appTree + ); + expect( + result.exists('apps/my-app/src/pages/index.module.scss') + ).toBeTruthy(); + + const indexContent = result + .read('apps/my-app/src/pages/index.tsx') + .toString(); + expect(indexContent).toContain( + `import styles from './index.module.scss'` + ); + }); + }); + + describe('--style less', () => { + it('should generate less styles', async () => { + const result = await runSchematic( + 'app', + { name: 'myApp', style: 'less' }, + appTree + ); + expect( + result.exists('apps/my-app/src/pages/index.module.less') + ).toBeTruthy(); + + const indexContent = result + .read('apps/my-app/src/pages/index.tsx') + .toString(); + expect(indexContent).toContain( + `import styles from './index.module.less'` + ); + }); + }); + + describe('--style styl', () => { + it('should generate stylus styles', async () => { + const result = await runSchematic( + 'app', + { name: 'myApp', style: 'styl' }, + appTree + ); + expect( + result.exists('apps/my-app/src/pages/index.module.styl') + ).toBeTruthy(); + + const indexContent = result + .read('apps/my-app/src/pages/index.tsx') + .toString(); + expect(indexContent).toContain( + `import styles from './index.module.styl'` + ); + }); + }); + + describe('--style styled-components', () => { + it('should generate scss styles', async () => { + const result = await runSchematic( + 'app', + { name: 'myApp', style: 'styled-components' }, + appTree + ); + expect( + result.exists('apps/my-app/src/pages/index.module.styled-components') + ).toBeFalsy(); + + const indexContent = result + .read('apps/my-app/src/pages/index.tsx') + .toString(); + expect(indexContent).not.toContain(`import styles from './index.module`); + expect(indexContent).toContain(`import styled from 'styled-components'`); + }); + }); + + describe('--style @emotion/styled', () => { + it('should generate emotion styles', async () => { + const result = await runSchematic( + 'app', + { name: 'myApp', style: '@emotion/styled' }, + appTree + ); + expect( + result.exists('apps/my-app/src/pages/index.module.styled-components') + ).toBeFalsy(); + + const indexContent = result + .read('apps/my-app/src/pages/index.tsx') + .toString(); + expect(indexContent).not.toContain(`import styles from './index.module`); + expect(indexContent).toContain(`import styled from '@emotion/styled'`); + }); + }); + + // TODO: We should also add styled-jsx support for Gatsby to keep React plugins consistent. + // This needs to be here before Nx 12 is released. + xdescribe('--style styled-jsx', () => { + it('should use