diff --git a/dotcom-rendering/index.d.ts b/dotcom-rendering/index.d.ts index 4a609e3acfb..43cc9db9544 100644 --- a/dotcom-rendering/index.d.ts +++ b/dotcom-rendering/index.d.ts @@ -14,6 +14,59 @@ declare module 'dynamic-import-polyfill' { }) => void; } +declare module '@guardian/react-crossword' { + import type { FC } from 'react'; + + export type Cell = { + number: number; + value: string; + }; + + export type Clue = { + id: string; + number: number; + humanNumber: string; + direction: 'across' | 'down'; + position: { x: number; y: number }; + separatorLocations: { + ','?: number[]; + '-'?: number[]; + }; + length: number; + clue: string; + group: string[]; + solution?: string; + format?: string; + }; + + export type CrosswordProps = { + id: string; + data: { + id?: string; + number: number; + name: string; + date: string; + dimensions: { cols: number; rows: number }; + entries: Clue[]; + solutionAvailable: boolean; + hasNumbers: boolean; + randomCluesOrdering: boolean; + instructions?: string; + creator?: { name: string; webUrl: string }; + pdf?: string; + annotatedSolution?: string; + dateSolutionAvailable: string; + }; + onCorrect?: (cell: Cell) => void; + onLoaded?: () => void; + }; + + const Crossword: FC; + + // eslint-disable-next-line import/no-default-export -- react-crossword uses default exports + export default Crossword; +} + // SVG handling declare module '*.svg' { const content: any; diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index bf1abcc3bda..25e40e2e627 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -49,6 +49,7 @@ "@guardian/identity-auth-frontend": "4.0.0", "@guardian/libs": "19.2.1", "@guardian/ophan-tracker-js": "2.2.5", + "@guardian/react-crossword": "2.0.2", "@guardian/shimport": "1.0.2", "@guardian/source": "8.0.0", "@guardian/source-development-kitchen": "12.0.0", diff --git a/dotcom-rendering/src/client/main.editionsCrossword.tsx b/dotcom-rendering/src/client/main.editionsCrossword.tsx new file mode 100644 index 00000000000..bb6bff20b4f --- /dev/null +++ b/dotcom-rendering/src/client/main.editionsCrossword.tsx @@ -0,0 +1,24 @@ +/* eslint-disable ssr-friendly/no-dom-globals-in-module-scope */ +// @ts-expect-error: Cannot find module +import css from '@guardian/react-crossword/lib/index.css'; +import { createRoot } from 'react-dom/client'; +import { Crosswords } from '../components/Crosswords.editions'; +import type { FEEditionsCrossword } from '../types/editionsCrossword'; + +const style = document.createElement('style'); +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- We know this will be a string +style.innerHTML = css; +document.body.appendChild(style); + +const element = document.getElementById('editions-crossword-player'); +if (!element) { + throw new Error('No element found with id "editions-crossword-player"'); +} +const crosswordsData = element.dataset.crosswords; +if (!crosswordsData) { + throw new Error('No data found for "editions-crossword-player"'); +} + +const crosswords = JSON.parse(crosswordsData) as FEEditionsCrossword[]; +const root = createRoot(element); +root.render(); diff --git a/dotcom-rendering/src/components/CrosswordSelect.editions.tsx b/dotcom-rendering/src/components/CrosswordSelect.editions.tsx index a44b9236972..685041882fa 100644 --- a/dotcom-rendering/src/components/CrosswordSelect.editions.tsx +++ b/dotcom-rendering/src/components/CrosswordSelect.editions.tsx @@ -1,3 +1,6 @@ +import { from } from '@guardian/source/foundations'; +import { Option, Select } from '@guardian/source/react-components'; + type Props = { /** * Crosswords organised by date. @@ -40,25 +43,34 @@ export function CrosswordSelect({ } return ( - <> - - onDateChange(e.target.value)} + label="Date" > {dates.map((crosswordDate) => ( - + ))} - + {crosswordsByDate[date] === undefined || crosswordsByDate[date].length === 0 ? null : ( <> - - + )} - + ); } diff --git a/dotcom-rendering/src/components/Crosswords.editions.stories.tsx b/dotcom-rendering/src/components/Crosswords.editions.stories.tsx index 833e0641923..3221f8ec889 100644 --- a/dotcom-rendering/src/components/Crosswords.editions.stories.tsx +++ b/dotcom-rendering/src/components/Crosswords.editions.stories.tsx @@ -50,7 +50,7 @@ export const UKTimezone = { await waitFor(() => { const clue = canvas.getByRole('listitem'); return expect(clue.textContent).toEqual( - args.crosswords[0]?.entries[0]?.clue, + `1${args.crosswords[0]?.entries[0]?.clue}`, ); }); }, diff --git a/dotcom-rendering/src/components/Crosswords.editions.tsx b/dotcom-rendering/src/components/Crosswords.editions.tsx index dadaade8628..ce340f414cf 100644 --- a/dotcom-rendering/src/components/Crosswords.editions.tsx +++ b/dotcom-rendering/src/components/Crosswords.editions.tsx @@ -1,3 +1,4 @@ +import Crossword from '@guardian/react-crossword'; import { useEffect, useState } from 'react'; import { type CrosswordsByDate, @@ -83,11 +84,11 @@ const CrosswordsWithInitialDate = ({ onCrosswordIndexChange={setCrosswordIndex} /> {crossword === undefined ? null : ( -
    - {crossword.entries.map((entry) => ( -
  • {entry.clue}
  • - ))} -
+ )} ); diff --git a/dotcom-rendering/src/components/EditionsCrosswordPage.tsx b/dotcom-rendering/src/components/EditionsCrosswordPage.tsx new file mode 100644 index 00000000000..3b7e490921a --- /dev/null +++ b/dotcom-rendering/src/components/EditionsCrosswordPage.tsx @@ -0,0 +1,17 @@ +import { StrictMode } from 'react'; +import type { FEEditionsCrosswords } from '../types/editionsCrossword'; + +interface Props { + editionsCrosswords: FEEditionsCrosswords; +} + +export const EditionsCrosswordPage = ({ editionsCrosswords }: Props) => { + return ( + +
+
+ ); +}; diff --git a/dotcom-rendering/src/lib/assets.ts b/dotcom-rendering/src/lib/assets.ts index 0fba7f5e889..43c92234c61 100644 --- a/dotcom-rendering/src/lib/assets.ts +++ b/dotcom-rendering/src/lib/assets.ts @@ -62,7 +62,8 @@ export type Build = | 'client.apps' | 'client.web' | 'client.web.variant' - | 'client.web.legacy'; + | 'client.web.legacy' + | 'client.editionsCrossword'; type ManifestPath = `./manifest.${Build}.json`; @@ -108,6 +109,9 @@ export const WEB = getScriptRegex('client.web'); export const WEB_VARIANT_SCRIPT = getScriptRegex('client.web.variant'); export const WEB_LEGACY_SCRIPT = getScriptRegex('client.web.legacy'); export const APPS_SCRIPT = getScriptRegex('client.apps'); +export const EDITIONS_CROSSWORD_SCRIPT = getScriptRegex( + 'client.editionsCrossword', +); export const generateScriptTags = (scripts: string[]): string[] => scripts.filter(isString).map((script) => { @@ -117,7 +121,8 @@ export const generateScriptTags = (scripts: string[]): string[] => if ( script.match(WEB) ?? script.match(WEB_VARIANT_SCRIPT) ?? - script.match(APPS_SCRIPT) + script.match(APPS_SCRIPT) ?? + script.match(EDITIONS_CROSSWORD_SCRIPT) ) { return ``; } diff --git a/dotcom-rendering/src/model/editions-crossword-schema.json b/dotcom-rendering/src/model/editions-crossword-schema.json index 330df0bc36b..615f8c38b18 100644 --- a/dotcom-rendering/src/model/editions-crossword-schema.json +++ b/dotcom-rendering/src/model/editions-crossword-schema.json @@ -114,8 +114,7 @@ "length", "number", "position", - "separatorLocations", - "solution" + "separatorLocations" ] } }, diff --git a/dotcom-rendering/src/server/handler.editionsCrossword.ts b/dotcom-rendering/src/server/handler.editionsCrossword.ts index ee0b5ea9d78..52305b476f7 100644 --- a/dotcom-rendering/src/server/handler.editionsCrossword.ts +++ b/dotcom-rendering/src/server/handler.editionsCrossword.ts @@ -1,8 +1,13 @@ import type { RequestHandler } from 'express'; import { validateAsEditionsCrosswordType } from '../model/validate'; +import { makePrefetchHeader } from './lib/header'; +import { renderCrosswordHtml } from './render.editionsCrossword'; export const handleEditionsCrossword: RequestHandler = ({ body }, res) => { const editionsCrosswords = validateAsEditionsCrosswordType(body); - console.log(editionsCrosswords); - res.sendStatus(200); + const { html, prefetchScripts } = renderCrosswordHtml({ + editionsCrosswords, + }); + + res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); }; diff --git a/dotcom-rendering/src/server/htmlCrosswordPageTemplate.ts b/dotcom-rendering/src/server/htmlCrosswordPageTemplate.ts new file mode 100644 index 00000000000..c1cd9a411c3 --- /dev/null +++ b/dotcom-rendering/src/server/htmlCrosswordPageTemplate.ts @@ -0,0 +1,21 @@ +interface Props { + html: string; + scriptTags: string[]; +} + +export const htmlCrosswordPageTemplate = (props: Props): string => { + const { html, scriptTags } = props; + + return ` + + + + + + ${scriptTags.join('\n')} + + + ${html} + + `; +}; diff --git a/dotcom-rendering/src/server/lib/get-content-from-url.js b/dotcom-rendering/src/server/lib/get-content-from-url.js index d36dd5d75d4..12119c6a426 100644 --- a/dotcom-rendering/src/server/lib/get-content-from-url.js +++ b/dotcom-rendering/src/server/lib/get-content-from-url.js @@ -63,24 +63,40 @@ exports.parseURL = parseURL; /** @type {import('webpack-dev-server').ExpressRequestHandler} */ exports.getContentFromURLMiddleware = async (req, res, next) => { - const sourceURL = parseURL(req.originalUrl); - - if (sourceURL) { - if ( - req.path.startsWith('/AMP') && - sourceURL.hostname === 'www.theguardian.com' - ) { - res.redirect( - req.path.replace('www.theguardian.com', 'amp.theguardian.com'), - ); - } - + if (req.path === '/EditionsCrossword') { try { - req.body = await getContentFromURL(sourceURL, req.headers); + const url = new URL( + 'https://www.theguardian.com/crosswords/digital-edition', + ); + const content = await getContentFromURL(url, req.headers); + req.body = content; + next(); } catch (error) { console.error(error); next(error); } + } else { + const sourceURL = parseURL(req.originalUrl); + if (sourceURL) { + if ( + req.path.startsWith('/AMP') && + sourceURL.hostname === 'www.theguardian.com' + ) { + res.redirect( + req.path.replace( + 'www.theguardian.com', + 'amp.theguardian.com', + ), + ); + } + + try { + req.body = await getContentFromURL(sourceURL, req.headers); + } catch (error) { + console.error(error); + next(error); + } + } + next(); } - next(); }; diff --git a/dotcom-rendering/src/server/render.editionsCrossword.tsx b/dotcom-rendering/src/server/render.editionsCrossword.tsx new file mode 100644 index 00000000000..2c97ecffa21 --- /dev/null +++ b/dotcom-rendering/src/server/render.editionsCrossword.tsx @@ -0,0 +1,31 @@ +import { isString } from '@guardian/libs'; +import { EditionsCrosswordPage } from '../components/EditionsCrosswordPage'; +import { generateScriptTags, getPathFromManifest } from '../lib/assets'; +import { renderToStringWithEmotion } from '../lib/emotion'; +import type { FEEditionsCrosswords } from '../types/editionsCrossword'; +import { htmlCrosswordPageTemplate } from './htmlCrosswordPageTemplate'; + +interface Props { + editionsCrosswords: FEEditionsCrosswords; +} + +export const renderCrosswordHtml = ({ + editionsCrosswords, +}: Props): { html: string; prefetchScripts: string[] } => { + const { html } = renderToStringWithEmotion( + , + ); + + const prefetchScripts = [ + getPathFromManifest('client.editionsCrossword', 'index.js'), + ].filter(isString); + + const scriptTags = generateScriptTags(prefetchScripts); + + const pageHtml = htmlCrosswordPageTemplate({ + html, + scriptTags, + }); + + return { html: pageHtml, prefetchScripts }; +}; diff --git a/dotcom-rendering/src/types/editionsCrossword.ts b/dotcom-rendering/src/types/editionsCrossword.ts index af2b57e31f8..c9e150e271d 100644 --- a/dotcom-rendering/src/types/editionsCrossword.ts +++ b/dotcom-rendering/src/types/editionsCrossword.ts @@ -15,7 +15,7 @@ type FECrosswordEntry = { length: number; clue: string; group: string[]; - solution: string; + solution?: string; format?: string; }; diff --git a/dotcom-rendering/webpack/webpack.config.client.js b/dotcom-rendering/webpack/webpack.config.client.js index fc9d3f317f7..2d9f75c3ea2 100644 --- a/dotcom-rendering/webpack/webpack.config.client.js +++ b/dotcom-rendering/webpack/webpack.config.client.js @@ -37,6 +37,8 @@ const generateName = (build) => { */ const getEntryIndex = (build) => { switch (build) { + case 'client.editionsCrossword': + return './src/client/main.editionsCrossword.tsx'; case 'client.apps': return './src/client/main.apps.ts'; default: @@ -78,6 +80,7 @@ const getLoaders = (build) => { }, }, ]; + case 'client.editionsCrossword': case 'client.apps': return swcLoader(['android >= 5', 'ios >= 12']); case 'client.web.variant': @@ -97,7 +100,7 @@ module.exports = ({ build }) => ({ }, optimization: // We don't need chunk optimization for apps as we use the 'LimitChunkCountPlugin' to produce just 1 chunk - build === 'client.apps' + build === 'client.apps' || build === 'client.editionsCrossword' ? undefined : { splitChunks: { @@ -138,7 +141,7 @@ module.exports = ({ build }) => ({ new WebpackManifestPlugin({ fileName: `manifest.${build}.json`, }), - ...(build === 'client.apps' + ...(build === 'client.apps' || build === 'client.editionsCrossword' ? [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, @@ -186,7 +189,15 @@ module.exports.getLoaders = getLoaders; * Tracking is done natively. * * @param {Build} build */ -const getExternalModules = (build) => - build === 'client.apps' - ? { '@guardian/ophan-tracker-js': 'guardian.ophan' } - : undefined; +const getExternalModules = (build) => { + if (build === 'client.apps') { + return { '@guardian/ophan-tracker-js': 'guardian.ophan' }; + } + if (build === 'client.editionsCrossword') { + return { + '@guardian/ophan-tracker-js': 'guardian.ophan', + '@guardian/commercial': 'guardian.commercial', + }; + } + return undefined; +}; diff --git a/dotcom-rendering/webpack/webpack.config.js b/dotcom-rendering/webpack/webpack.config.js index b9df5279a26..3840ad4fa4b 100644 --- a/dotcom-rendering/webpack/webpack.config.js +++ b/dotcom-rendering/webpack/webpack.config.js @@ -116,6 +116,7 @@ const clientBuilds = [ ? /** @type {const} */ (['client.web.legacy']) : []), 'client.apps', + 'client.editionsCrossword', ]; module.exports = [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11f524903a2..b64bb6a0eeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,6 +361,9 @@ importers: '@guardian/ophan-tracker-js': specifier: 2.2.5 version: 2.2.5 + '@guardian/react-crossword': + specifier: 2.0.2 + version: 2.0.2 '@guardian/shimport': specifier: 1.0.2 version: 1.0.2 @@ -4021,7 +4024,7 @@ packages: '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@5.5.3) eslint: 8.56.0 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) tslib: 2.6.2 typescript: 5.5.3 transitivePeerDependencies: @@ -4253,6 +4256,20 @@ packages: tslib: 2.6.2 dev: false + /@guardian/react-crossword@2.0.2: + resolution: {integrity: sha512-pFvCpuUH+GKz12uUzW4+Lck/ZhDWvqLodr1UwXIE7qjJCz8V4NEfuiGZkkIpVoPh+dEHTkiDQ6Ks4653KdH01g==} + dependencies: + bonzo: 2.0.0 + fastdom: 1.0.12 + lodash: 4.17.21 + qwery: 4.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + wolfy87-eventemitter: 5.2.9 + optionalDependencies: + fsevents: 2.3.3 + dev: false + /@guardian/renditions@0.2.0: resolution: {integrity: sha512-AXUD83v9uY1oAXj6GcnVwP64UscjEbJhwLkq2dwmHXuP0UZkSxft9Q6WtTZemjHG4kpijYU+eulS8zoYXbiSLQ==} dependencies: @@ -6184,7 +6201,7 @@ packages: react-docgen-typescript: 2.2.2(typescript@5.5.3) tslib: 2.6.2 typescript: 5.5.3 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color dev: false @@ -7745,8 +7762,8 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) dev: false /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.94.0): @@ -7756,8 +7773,8 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) dev: false /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.0.4)(webpack@5.94.0): @@ -7771,8 +7788,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) webpack-dev-server: 5.0.4(webpack-cli@5.1.4)(webpack@5.94.0) dev: false @@ -8280,7 +8297,7 @@ packages: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /babel-plugin-istanbul@6.1.1: @@ -8491,6 +8508,10 @@ packages: multicast-dns: 7.2.5 dev: false + /bonzo@2.0.0: + resolution: {integrity: sha512-RQG5bX6VhxP6H8hOcU5LNk9FmfKwppbxVj8J1cVSZcHYyKaxyC+tV5VUhRYzpgsw5AsYmlsQpBLIGu6DPXPngA==} + dev: false + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: false @@ -9439,7 +9460,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-value-parser: 4.2.0 semver: 7.5.4 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /css-loader@7.1.2(webpack@5.94.0): @@ -10425,7 +10446,7 @@ packages: enhanced-resolve: 5.17.0 eslint: 8.56.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.15.1 @@ -11409,7 +11430,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 5.5.3 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /form-data@3.0.1: @@ -15384,6 +15405,10 @@ packages: engines: {node: '>=10'} dev: false + /qwery@4.0.0: + resolution: {integrity: sha512-aQapsrAZapZXVbMIUTPsuclkkxU8/nEk3PanYLRu1WaGZoAGxm5BGcq353RVATVfqObBandy8sbAh6gR28TNNg==} + dev: false + /rambda@7.5.0: resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==} dev: false @@ -15445,6 +15470,16 @@ packages: - supports-color dev: false + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.2 + dev: false + /react-dom@18.3.1(react@18.3.1): resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -15498,6 +15533,13 @@ packages: scheduler: 0.23.2 dev: false + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + /react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -16609,7 +16651,7 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /stylelint-config-recommended@14.0.0(stylelint@16.5.0): @@ -17088,7 +17130,7 @@ packages: semver: 7.5.4 source-map: 0.7.4 typescript: 5.5.3 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /ts-node@10.9.2(@swc/core@1.9.2)(@types/node@16.18.68)(typescript@5.1.6): @@ -17850,7 +17892,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /webpack-dev-middleware@7.2.1(webpack@5.94.0): @@ -17868,7 +17910,7 @@ packages: on-finished: 2.4.1 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /webpack-dev-middleware@7.4.2(webpack@5.94.0): @@ -17930,8 +17972,8 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) webpack-dev-middleware: 7.2.1(webpack@5.94.0) ws: 8.17.1 transitivePeerDependencies: @@ -17976,7 +18018,7 @@ packages: webpack: ^5.47.0 dependencies: tapable: 2.2.1 - webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-sources: 2.3.1 dev: false patched: true @@ -18295,6 +18337,10 @@ packages: winston-transport: 4.6.0 dev: false + /wolfy87-eventemitter@5.2.9: + resolution: {integrity: sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw==} + dev: false + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: false