diff --git a/packages/create-sitecore-jss/src/templates/nextjs/package.json b/packages/create-sitecore-jss/src/templates/nextjs/package.json index 9f816c4f0b..4cd2a11f67 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/package.json +++ b/packages/create-sitecore-jss/src/templates/nextjs/package.json @@ -34,8 +34,8 @@ "graphql-tag": "^2.11.0", "next": "^12.3.1", "next-localization": "^0.12.0", - "react": "^18.1.0", - "react-dom": "^18.1.0" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@graphql-codegen/cli": "^1.19.1", diff --git a/packages/create-sitecore-jss/src/templates/react/.eslintrc b/packages/create-sitecore-jss/src/templates/react/.eslintrc index fe81f61a51..4ae1040451 100644 --- a/packages/create-sitecore-jss/src/templates/react/.eslintrc +++ b/packages/create-sitecore-jss/src/templates/react/.eslintrc @@ -1,78 +1,82 @@ -{ - "root": true, - "extends": [ - "prettier", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:react/recommended", - "plugin:yaml/recommended", - "plugin:prettier/recommended" - ], - "parser": "babel-eslint", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - } - }, - "plugins": [ - "import", - "react", - "yaml" - ], - "settings": { - "import/ignore": [ - "node_modules", - ".png$", - ".jpg$" - ], - "react": { - "version": "detect" - } - }, - "globals": { - "window": true, - "document": true - }, - "rules": { - "import/no-unresolved": "off", - "import/no-duplicates": "off", - "import/no-named-as-default": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "optionalDependencies": true - } - ], - "linebreak-style": "off", - "react/jsx-filename-extension": 0, - "jsx-quotes": ["error", "prefer-double"], - "import/prefer-default-export": "off", - "react/forbid-prop-types": "off", - "react/prop-types": 0, - "react/no-danger": "off", - "react/require-default-props": "off", - "react/no-array-index-key": "off", - "no-use-before-define": 0, - "global-require": 0, - "no-param-reassign": 0, - "no-useless-escape": "off", - "spaced-comment": "error", - "curly": ["error", "multi-line"], - "eol-last": ["error", "always"], - "guard-for-in": "error", - "no-unused-labels": "error", - "no-caller": "error", - "no-bitwise": "error", - "no-multiple-empty-lines": "error", - "no-new-wrappers": "error", - "no-eval": "error", - "dot-notation": "error", - "no-trailing-spaces": "error", - "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], - "brace-style": "error", - "quotes": ["error", "single"], - "default-case": "error", - "eqeqeq": "error" - } -} +{ + "root": true, + "extends": [ + "prettier", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:react/recommended", + "plugin:yaml/recommended", + "plugin:prettier/recommended" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false, + "ecmaFeatures": { + "jsx": true + }, + "babelOptions": { + "presets": ["@babel/preset-react"] + } + }, + "plugins": [ + "import", + "react", + "yaml" + ], + "settings": { + "import/ignore": [ + "node_modules", + ".png$", + ".jpg$" + ], + "react": { + "version": "detect" + } + }, + "globals": { + "window": true, + "document": true + }, + "rules": { + "import/no-unresolved": "off", + "import/no-duplicates": "off", + "import/no-named-as-default": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": true, + "optionalDependencies": true + } + ], + "linebreak-style": "off", + "react/jsx-filename-extension": 0, + "jsx-quotes": ["error", "prefer-double"], + "import/prefer-default-export": "off", + "react/forbid-prop-types": "off", + "react/prop-types": 0, + "react/no-danger": "off", + "react/require-default-props": "off", + "react/no-array-index-key": "off", + "no-use-before-define": 0, + "global-require": 0, + "no-param-reassign": 0, + "no-useless-escape": "off", + "spaced-comment": "error", + "curly": ["error", "multi-line"], + "eol-last": ["error", "always"], + "guard-for-in": "error", + "no-unused-labels": "error", + "no-caller": "error", + "no-bitwise": "error", + "no-multiple-empty-lines": "error", + "no-new-wrappers": "error", + "no-eval": "error", + "dot-notation": "error", + "no-trailing-spaces": "error", + "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], + "brace-style": "error", + "quotes": ["error", "single"], + "default-case": "error", + "eqeqeq": "error" + } +} diff --git a/packages/create-sitecore-jss/src/templates/react/config-overrides.js b/packages/create-sitecore-jss/src/templates/react/config-overrides.js index 40803e17c6..d47587d724 100644 --- a/packages/create-sitecore-jss/src/templates/react/config-overrides.js +++ b/packages/create-sitecore-jss/src/templates/react/config-overrides.js @@ -4,5 +4,11 @@ module.exports = function override(config) { // Provide alias to don't have duplicates of `react` config.resolve.alias.react = path.resolve(process.cwd(), '.', 'node_modules', 'react'); + // This will remove the CRA plugin that prevents to import modules from + // outside the `src` directory, useful for symlinks + config.resolve.plugins = config.resolve.plugins.filter( + (p) => p.constructor.name !== 'ModuleScopePlugin' + ); + return config; }; diff --git a/packages/create-sitecore-jss/src/templates/react/package.json b/packages/create-sitecore-jss/src/templates/react/package.json index dd503a59e2..308a9692f9 100644 --- a/packages/create-sitecore-jss/src/templates/react/package.json +++ b/packages/create-sitecore-jss/src/templates/react/package.json @@ -1,120 +1,122 @@ -{ - "name": "<%- appName %>", - "description": "Application utilizing Sitecore JavaScript Services and React (create-react-app).", - "version": "21.1.0-canary", - "private": true, - "config": { - "appName": "<%- appName %>", - "rootPlaceholders": [ - "<%- helper.getAppPrefix(appPrefix, appName) %>jss-main" - ], - "sitecoreDistPath": "/dist/<%- appName %>", - "sitecoreConfigPath": "/App_Config/Include/zzz", - "graphQLEndpointPath": "/sitecore/api/graph/edge", - "language": "en", - "tunnelUrl": "http://jss.ngrok.io" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "author": { - "name": "Sitecore Corporation", - "url": "https://jss.sitecore.com" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/sitecore/jss.git" - }, - "bugs": { - "url": "https://github.com/sitecore/jss/issues" - }, - "license": "Apache-2.0", - "dependencies": { - "@apollo/client": "^3.5.6", - "@sitecore-jss/sitecore-jss-react": "^21.1.0-canary", - "axios": "^0.21.1", - "bootstrap": "^4.3.1", - "cross-fetch": "^3.0.6", - "deep-equal": "^2.0.5", - "graphql": "~14.5.7", - "graphql-tag": "~2.10.1", - "i18next": "^19.9.2", - "js-sha256": "^0.9.0", - "react": "^18.1.0", - "react-app-polyfill": "^1.0.6", - "react-dom": "^18.1.0", - "react-helmet": "~6.1.0", - "react-i18next": "~11.18.1", - "react-router-dom": "~5.1.0", - "react-scripts": "4.0.1", - "serialize-javascript": "~3.1.0" - }, - "devDependencies": { - "@babel/core": "^7.16.0", - "@babel/preset-env": "^7.10.4", - "@babel/register": "~7.6.2", - "@sitecore-jss/sitecore-jss-cli": "^21.1.0-canary", - "@sitecore-jss/sitecore-jss-dev-tools": "^21.1.0-canary", - "@sitecore-jss/sitecore-jss-rendering-host": "^21.1.0-canary", - "babel-eslint": "^10.1.0", - "babel-loader": "8.1.0", - "babel-preset-react-app": "~9.0.2", - "chalk": "~2.4.2", - "chokidar": "~3.1.1", - "constant-case": "^3.0.4", - "cross-env": "~6.0.0", - "cross-spawn": "^7.0.3", - "del-cli": "^5.0.0", - "dotenv": "^16.0.0", - "eslint": "^7.16.0", - "eslint-config-prettier": "^6.11.0", - "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-yaml": "^0.2.0", - "express": "^4.17.1", - "fs-extra": "^8.1.0", - "graphql.macro": "~1.4.2", - "html-loader": "~0.5.5", - "http-proxy-middleware": "~0.20.0", - "move-cli": "^1.2.1", - "ncp": "^2.0.0", - "ngrok": "^3.2.5", - "npm-run-all": "~4.1.5", - "null-loader": "~3.0.0", - "prettier": "^2.0.5", - "react-app-rewired": "^2.2.1", - "speed-measure-webpack-plugin": "^1.3.1", - "stats-webpack-plugin": "^0.7.0", - "url-loader": "~2.1.0", - "webpack": "4.44.2", - "webpack-cli": "~3.3.9" - }, - "scripts": { - "jss": "jss", - "start": "cross-env-shell JSS_MODE=disconnected \"npm-run-all --serial bootstrap --parallel start:react start:proxy start:watch-components\"", - "start:connected": "npm-run-all --serial bootstrap start:react start:watch-components", - "start:rendering-host": "cross-env-shell NODE_ENV=development JSS_RENDER_ENGINE=http \"node scripts/http-renderer.js\"", - "build": "npm-run-all --serial bootstrap build:client build:server", - "build:rendering-host": "npm-run-all --serial bootstrap build:client:rendering-host build:server afterbuild:rendering-host", - "afterbuild:rendering-host": "del-cli build-rendering-host && move-cli build build-rendering-host", - "scaffold": "node scripts/scaffold-component.js", - "start:react": "react-scripts start", - "start:proxy": "node scripts/disconnected-mode-proxy.js", - "start:watch-components": "node scripts/generate-component-factory.js --watch", - "build:client": "cross-env-shell PUBLIC_URL=$npm_package_config_sitecoreDistPath \"react-scripts build\"", - "build:client:rendering-host": "cross-env-shell PUBLIC_URL=$npm_package_config_tunnelUrl \"react-scripts build\"", - "build:server": "cross-env-shell NODE_ENV=production \"webpack --config server/server.webpack.config.js\"", - "bootstrap": "node scripts/bootstrap.js", - "graphql:update": "node -r @babel/register ./scripts/update-graphql-fragment-data.js", - "test": "react-scripts test --env=jsdom", - "lint": "eslint ./src/**/*.js ./sitecore/definitions/**/*.js ./scripts/**/*.js ./server/**/*.js ./data/**/*.yml", - "eject": "react-scripts eject", - "eject:script": "ncp ./scripts ./scripts-copy && node ./scripts-copy/eject.js && ncp ./scripts-copy ./scripts && del-cli ./scripts-copy" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" - ] -} +{ + "name": "<%- appName %>", + "description": "Application utilizing Sitecore JavaScript Services and React (create-react-app).", + "version": "21.1.0-canary", + "private": true, + "config": { + "appName": "<%- appName %>", + "rootPlaceholders": [ + "<%- helper.getAppPrefix(appPrefix, appName) %>jss-main" + ], + "sitecoreDistPath": "/dist/<%- appName %>", + "sitecoreConfigPath": "/App_Config/Include/zzz", + "graphQLEndpointPath": "/sitecore/api/graph/edge", + "language": "en", + "tunnelUrl": "http://jss.ngrok.io" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "author": { + "name": "Sitecore Corporation", + "url": "https://jss.sitecore.com" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sitecore/jss.git" + }, + "bugs": { + "url": "https://github.com/sitecore/jss/issues" + }, + "license": "Apache-2.0", + "dependencies": { + "@apollo/client": "^3.7.1", + "@sitecore-jss/sitecore-jss-react": "^21.1.0-canary", + "axios": "^1.2.0", + "bootstrap": "^5.2.3", + "cross-fetch": "^3.1.5", + "deep-equal": "^2.1.0", + "graphql": "~16.6.0", + "graphql-tag": "~2.12.6", + "i18next": "^22.0.6", + "js-sha256": "^0.9.0", + "react": "^18.2.0", + "react-app-polyfill": "^3.0.0", + "react-dom": "^18.2.0", + "react-helmet": "~6.1.0", + "react-i18next": "~12.0.0", + "react-router-dom": "~6.4.4", + "react-scripts": "~5.0.1", + "serialize-javascript": "^6.0.0" + }, + "devDependencies": { + "@babel/core": "^7.20.5", + "@babel/eslint-parser": "^7.19.1", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/register": "~7.18.9", + "@sitecore-jss/sitecore-jss-cli": "^21.1.0-canary", + "@sitecore-jss/sitecore-jss-dev-tools": "^21.1.0-canary", + "@sitecore-jss/sitecore-jss-rendering-host": "^21.1.0-canary", + "babel-loader": "~9.1.0", + "babel-preset-react-app": "~10.0.1", + "chalk": "~4.1.2", + "chokidar": "~3.5.3", + "constant-case": "^3.0.4", + "cross-env": "~7.0.3", + "cross-spawn": "^7.0.3", + "del-cli": "^5.0.0", + "dotenv": "^16.0.3", + "eslint": "^8.28.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.31.11", + "eslint-plugin-yaml": "^0.5.0", + "express": "^4.18.2", + "fs-extra": "^11.1.0", + "graphql.macro": "~1.4.2", + "html-loader": "~4.2.0", + "http-proxy-middleware": "~2.0.6", + "move-cli": "^2.0.0", + "ncp": "^2.0.0", + "ngrok": "^4.3.3", + "npm-run-all": "~4.1.5", + "null-loader": "~4.0.1", + "prettier": "^2.8.0", + "react-app-rewired": "^2.2.1", + "speed-measure-webpack-plugin": "^1.5.0", + "stats-webpack-plugin": "^0.7.0", + "webpack": "~5.75.0", + "webpack-cli": "~5.0.0" + }, + "scripts": { + "jss": "jss", + "start": "cross-env-shell JSS_MODE=disconnected \"npm-run-all --serial bootstrap --parallel start:react start:proxy start:watch-components\"", + "start:connected": "npm-run-all --serial bootstrap start:react start:watch-components", + "start:rendering-host": "cross-env-shell NODE_ENV=development JSS_RENDER_ENGINE=http \"node scripts/http-renderer.js\"", + "build": "npm-run-all --serial bootstrap build:client build:server", + "build:rendering-host": "npm-run-all --serial bootstrap build:client:rendering-host build:server afterbuild:rendering-host", + "afterbuild:rendering-host": "del-cli build-rendering-host && move-cli build build-rendering-host", + "scaffold": "node scripts/scaffold-component.js", + "start:react": "react-scripts start", + "start:proxy": "node scripts/disconnected-mode-proxy.js", + "start:watch-components": "node scripts/generate-component-factory.js --watch", + "build:client": "cross-env-shell PUBLIC_URL=$npm_package_config_sitecoreDistPath \"react-scripts build\"", + "build:client:rendering-host": "cross-env-shell PUBLIC_URL=$npm_package_config_tunnelUrl \"react-scripts build\"", + "build:server": "cross-env-shell NODE_ENV=production \"webpack --config server/server.webpack.config.js\"", + "bootstrap": "node scripts/bootstrap.js", + "graphql:update": "node -r @babel/register ./scripts/update-graphql-fragment-data.js", + "test": "react-scripts test --env=jsdom", + "lint": "eslint ./src/**/*.js ./sitecore/definitions/**/*.js ./scripts/**/*.js ./server/**/*.js ./data/**/*.yml", + "eject": "react-scripts eject", + "eject:script": "ncp ./scripts ./scripts-copy && node ./scripts-copy/eject.js && ncp ./scripts-copy ./scripts && del-cli ./scripts-copy" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/packages/create-sitecore-jss/src/templates/react/server/server.js b/packages/create-sitecore-jss/src/templates/react/server/server.js index 9da4cbd5cc..f8d558836b 100644 --- a/packages/create-sitecore-jss/src/templates/react/server/server.js +++ b/packages/create-sitecore-jss/src/templates/react/server/server.js @@ -1,6 +1,6 @@ import serializeJavascript from 'serialize-javascript'; import React from 'react'; -import { StaticRouter, matchPath } from 'react-router-dom'; +import { StaticRouter } from 'react-router-dom/server'; import { renderToStringWithData } from '@apollo/client/react/ssr'; import Helmet from 'react-helmet'; import axios from 'axios'; @@ -9,7 +9,7 @@ import https from 'https'; import GraphQLClientFactory from '../src/lib/GraphQLClientFactory'; import config from '../src/temp/config'; import i18ninit from '../src/i18n'; -import AppRoot, { routePatterns } from '../src/AppRoot'; +import AppRoot, { parseRouteParams } from '../src/AppRoot'; import { getHtmlTemplate } from './htmlTemplateFactory'; /** Asserts that a string replace actually replaced something */ @@ -109,7 +109,7 @@ export function renderView(callback, path, data, viewBag) { // Inject the rendered app into the index.html template (built from /public/index.html) // IMPORTANT: use serialize-javascript or similar instead of JSON.stringify() to emit initial state, // or else you're vulnerable to XSS. - let html = htmlTemplate; + let html = htmlTemplate?.default; // write the React app html = assertReplace( @@ -162,22 +162,9 @@ export function parseRouteUrl(url) { return null; } - let result = null; + const result = parseRouteParams(url); - // use react-router-dom to find the route matching the incoming URL - // then return its match params - // we are using .some() as a way to loop with a short circuit (so that we stop evaluating route patterns after the first match) - routePatterns.some((pattern) => { - const match = matchPath(url, { path: pattern }); - if (match && match.params) { - result = match.params; - return true; - } - - return false; - }); - - return result; + return { sitecoreRoute: result.route, lang: result.language }; } function parseServerData(data, viewBag) { diff --git a/packages/create-sitecore-jss/src/templates/react/server/server.webpack.config.js b/packages/create-sitecore-jss/src/templates/react/server/server.webpack.config.js index 60e6acace7..c782f5b4ab 100644 --- a/packages/create-sitecore-jss/src/templates/react/server/server.webpack.config.js +++ b/packages/create-sitecore-jss/src/templates/react/server/server.webpack.config.js @@ -20,6 +20,12 @@ module.exports = { }, module: { rules: [ + { + // react-router-dom@6 uses mjs files + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, { test: /\.m?jsx?$/, use: { @@ -50,16 +56,19 @@ module.exports = { { test: /\.html$/, exclude: /node_modules/, - use: { loader: 'html-loader' }, + use: { + loader: 'html-loader', + options: { + sources: false, + }, + }, }, { // anything not JS or HTML, we load as a URL // this makes static image imports work with SSR test: /\.(?!js|mjs|jsx|html|graphql$)[^.]+$/, exclude: /node_modules/, - use: { - loader: 'url-loader', - }, + type: 'asset/inline', }, { // anything in node_modules that isn't js, @@ -78,6 +87,9 @@ module.exports = { // > WARNING in ./node_modules/encoding/lib/iconv-loader.js // > Critical dependency: the request of a dependency is an expression new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, () => {}), + // prevents cross-fetch -> node-fetch from throwing `Can't resolve 'encoding'` error + // see https://github.com/node-fetch/node-fetch/issues/412 + new webpack.IgnorePlugin({ resourceRegExp: /^encoding$/, contextRegExp: /node-fetch/ }), ], <% if (helper.isDev) { %> resolve: { diff --git a/packages/create-sitecore-jss/src/templates/react/src/AppRoot.js b/packages/create-sitecore-jss/src/templates/react/src/AppRoot.js index d6c778ab53..4cfcb81384 100644 --- a/packages/create-sitecore-jss/src/templates/react/src/AppRoot.js +++ b/packages/create-sitecore-jss/src/templates/react/src/AppRoot.js @@ -1,6 +1,6 @@ import React from 'react'; import { SitecoreContext } from '@sitecore-jss/sitecore-jss-react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes, useParams } from 'react-router-dom'; import { ApolloProvider } from '@apollo/client'; import componentFactory from './temp/componentFactory'; import RouteHandler from './RouteHandler'; @@ -9,23 +9,31 @@ import RouteHandler from './RouteHandler'; // By default the app's normal rendering is delegated to that handles the loading of JSS route data. // support languages in the URL prefix -// e.g. /<%- language %>/path, or /en/path, or /path -export const routePatterns = [ - '/:lang([a-z]{2}-[A-Z]{2})/:sitecoreRoute*', - '/:lang([a-z]{2})/:sitecoreRoute*', - '/:sitecoreRoute*', -]; +// e.g. /da-DK/path, or /en/path, or /path +const LANGUAGE_REG_EXP = /^\/?(([a-z]{2}-[A-Z]{2})|([a-z]{2}))(\/|$)/g; +export const parseRouteParams = (url) => { + const language = url.match(LANGUAGE_REG_EXP); + const route = url.replace(LANGUAGE_REG_EXP, ''); + + return { + route: route.startsWith('/') ? route : `/${route}`, + language: language ? language[0].replace(/\//g, '') : undefined, + }; +}; + +const JssRoute = (props) => { + const params = useParams(); + const url = params['*']; + + return ; +}; // wrap the app with: // ApolloProvider: provides an instance of Apollo GraphQL client to the app to make Connected GraphQL queries. // Not needed if not using connected GraphQL. // SitecoreContext: provides component resolution and context services via withSitecoreContext // Router: provides a basic routing setup that will resolve Sitecore item routes and allow for language URL prefixes. class AppRoot extends React.Component { - renderRoute = (props) => { - return ; - }; - render() { const { path, Router, graphQLClient } = this.props; @@ -33,11 +41,9 @@ class AppRoot extends React.Component { - - - - - + + } /> + diff --git a/packages/create-sitecore-jss/src/templates/react/src/Layout.js b/packages/create-sitecore-jss/src/templates/react/src/Layout.js index 05591b21ea..78c1463e60 100644 --- a/packages/create-sitecore-jss/src/templates/react/src/Layout.js +++ b/packages/create-sitecore-jss/src/templates/react/src/Layout.js @@ -23,12 +23,12 @@ import logo from './assets/sc_logo.svg'; // Most apps may also wish to use GraphQL for their navigation construction; this sample does not simply to support disconnected mode. let Navigation = ({ t, i18n }) => (
-
+
Sitecore
-