diff --git a/.changeset/dirty-bats-fly.md b/.changeset/dirty-bats-fly.md new file mode 100644 index 0000000000..c57d60d627 --- /dev/null +++ b/.changeset/dirty-bats-fly.md @@ -0,0 +1,5 @@ +--- +'@ice/app': patch +--- + +feat: support add routes definition diff --git a/.changeset/fluffy-hounds-smoke.md b/.changeset/fluffy-hounds-smoke.md new file mode 100644 index 0000000000..d34ef0bed8 --- /dev/null +++ b/.changeset/fluffy-hounds-smoke.md @@ -0,0 +1,5 @@ +--- +'@ice/route-manifest': minor +--- + +refactor: route id generation diff --git a/.changeset/hip-balloons-brush.md b/.changeset/hip-balloons-brush.md new file mode 100644 index 0000000000..d12a0eea30 --- /dev/null +++ b/.changeset/hip-balloons-brush.md @@ -0,0 +1,5 @@ +--- +'@ice/plugin-i18n': major +--- + +feat: init plugin diff --git a/.changeset/hot-baboons-retire.md b/.changeset/hot-baboons-retire.md new file mode 100644 index 0000000000..8a20317596 --- /dev/null +++ b/.changeset/hot-baboons-retire.md @@ -0,0 +1,5 @@ +--- +'@ice/route-manifest': minor +--- + +feat: support accept one more `defineExtraRoutes` functions diff --git a/.changeset/red-gorillas-remember.md b/.changeset/red-gorillas-remember.md new file mode 100644 index 0000000000..1d630a0e84 --- /dev/null +++ b/.changeset/red-gorillas-remember.md @@ -0,0 +1,5 @@ +--- +'@ice/runtime': patch +--- + +feat: support handler response diff --git a/.changeset/tasty-spies-think.md b/.changeset/tasty-spies-think.md new file mode 100644 index 0000000000..af1f2150c2 --- /dev/null +++ b/.changeset/tasty-spies-think.md @@ -0,0 +1,5 @@ +--- +'@ice/app': patch +--- + +fix: routeSpecifier is not unique diff --git a/.gitignore b/.gitignore index 4a5e4f589e..16b66d940f 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ yalc.lock # Packages packages/*/lib/ packages/*/esm/ +packages/*/es2017/ # temp folder .ice examples/*/.ice diff --git a/examples/with-antd5/ice.config.mts b/examples/with-antd5/ice.config.mts new file mode 100644 index 0000000000..63f3c0bcab --- /dev/null +++ b/examples/with-antd5/ice.config.mts @@ -0,0 +1,5 @@ +import { defineConfig } from '@ice/app'; + +export default defineConfig(() => ({ + ssg: false, +})); diff --git a/examples/with-antd5/package.json b/examples/with-antd5/package.json new file mode 100644 index 0000000000..b4b9872217 --- /dev/null +++ b/examples/with-antd5/package.json @@ -0,0 +1,22 @@ +{ + "name": "@examples/with-antd5", + "private": true, + "version": "1.0.0", + "scripts": { + "start": "ice start", + "build": "ice build" + }, + "dependencies": { + "@ice/app": "workspace:*", + "@ice/runtime": "workspace:*", + "antd": "^5.0.0", + "dayjs": "^1.11.7", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-intl": "^6.3.2" + }, + "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.2" + } +} \ No newline at end of file diff --git a/examples/with-antd5/src/app.tsx b/examples/with-antd5/src/app.tsx new file mode 100644 index 0000000000..38e608b8f4 --- /dev/null +++ b/examples/with-antd5/src/app.tsx @@ -0,0 +1,7 @@ +import { defineAppConfig } from 'ice'; + +export default defineAppConfig(() => ({ + app: { + rootId: 'app', + }, +})); diff --git a/examples/with-antd5/src/document.tsx b/examples/with-antd5/src/document.tsx new file mode 100644 index 0000000000..1e7b99c49d --- /dev/null +++ b/examples/with-antd5/src/document.tsx @@ -0,0 +1,22 @@ +import { Meta, Title, Links, Main, Scripts } from 'ice'; + +function Document() { + return ( + + + + + + + + <Links /> + </head> + <body> + <Main /> + <Scripts /> + </body> + </html> + ); +} + +export default Document; diff --git a/examples/with-antd5/src/locales.ts b/examples/with-antd5/src/locales.ts new file mode 100644 index 0000000000..f839a418ad --- /dev/null +++ b/examples/with-antd5/src/locales.ts @@ -0,0 +1,10 @@ +export const messages: Record<string, any> = { + en: { + changeLanguageTitle: 'Change locale:', + indexTitle: 'Index', + }, + 'zh-cn': { + changeLanguageTitle: '修改语言:', + indexTitle: '首页', + }, +}; diff --git a/examples/with-antd5/src/pages/index.tsx b/examples/with-antd5/src/pages/index.tsx new file mode 100644 index 0000000000..cb885ff566 --- /dev/null +++ b/examples/with-antd5/src/pages/index.tsx @@ -0,0 +1,12 @@ +import { DatePicker, Pagination } from 'antd'; +import { FormattedMessage } from 'react-intl'; + +export default function Index() { + return ( + <div> + <h2><FormattedMessage id="indexTitle" /></h2> + <Pagination defaultCurrent={1} total={50} showSizeChanger /> + <DatePicker /> + </div> + ); +} diff --git a/examples/with-antd5/src/pages/layout.tsx b/examples/with-antd5/src/pages/layout.tsx new file mode 100644 index 0000000000..31a75d9cff --- /dev/null +++ b/examples/with-antd5/src/pages/layout.tsx @@ -0,0 +1,47 @@ +import { Outlet } from 'ice'; +import { useState } from 'react'; +import { IntlProvider, FormattedMessage } from 'react-intl'; +import { ConfigProvider, Radio } from 'antd'; +import type { RadioChangeEvent } from 'antd'; +import enUS from 'antd/locale/en_US'; +import zhCN from 'antd/locale/zh_CN'; +import type { Locale } from 'antd/es/locale'; + +import * as dayjs from 'dayjs'; +import { messages } from '@/locales'; +import 'dayjs/locale/zh-cn'; + +export default function Layout() { + const [locale, setLocale] = useState<Locale>(enUS); + + const changeLocale = (e: RadioChangeEvent) => { + const localeValue = e.target.value; + setLocale(localeValue); + if (localeValue) { + dayjs.locale('zh-cn'); + } else { + dayjs.locale('en'); + } + }; + + return ( + <main> + <IntlProvider locale={locale.locale} messages={messages[locale.locale]}> + <div style={{ marginBottom: 16 }}> + <span style={{ marginRight: 16 }}><FormattedMessage id="changeLanguageTitle" /></span> + <Radio.Group value={locale} onChange={changeLocale}> + <Radio.Button key="en" value={enUS}> + English + </Radio.Button> + <Radio.Button key="cn" value={zhCN}> + 中文 + </Radio.Button> + </Radio.Group> + </div> + <ConfigProvider locale={locale}> + <Outlet /> + </ConfigProvider> + </IntlProvider> + </main> + ); +} diff --git a/examples/with-antd5/src/typings.d.ts b/examples/with-antd5/src/typings.d.ts new file mode 100644 index 0000000000..1f6ba4ffa6 --- /dev/null +++ b/examples/with-antd5/src/typings.d.ts @@ -0,0 +1 @@ +/// <reference types="@ice/app/types" /> diff --git a/examples/with-antd5/tsconfig.json b/examples/with-antd5/tsconfig.json new file mode 100644 index 0000000000..55aebc3d90 --- /dev/null +++ b/examples/with-antd5/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext", "DOM.Iterable"], + "jsx": "react-jsx", + "moduleResolution": "node", + "strict": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"], + "ice": [".ice"] + } + }, + "include": ["src", ".ice"], +} diff --git a/examples/with-fusion/ice.config.mts b/examples/with-fusion/ice.config.mts index 46eab65bee..40cf4bdb4d 100644 --- a/examples/with-fusion/ice.config.mts +++ b/examples/with-fusion/ice.config.mts @@ -16,4 +16,5 @@ export default defineConfig({ locales: ['af'], }), ], + ssg: false, }); diff --git a/examples/with-fusion/package.json b/examples/with-fusion/package.json index 88379b2318..145c92723d 100644 --- a/examples/with-fusion/package.json +++ b/examples/with-fusion/package.json @@ -12,16 +12,17 @@ "dependencies": { "@alifd/next": "^1.25.49", "@ice/runtime": "workspace:*", + "moment": "^2.29.4", "react": "^18.0.0", "react-dom": "^18.0.0", - "moment": "^2.29.4" + "react-intl": "^6.3.2" }, "devDependencies": { - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.2", "@ice/app": "workspace:*", "@ice/plugin-css-assets-local": "workspace:*", "@ice/plugin-fusion": "workspace:*", - "@ice/plugin-moment-locales": "workspace:*" + "@ice/plugin-moment-locales": "workspace:*", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.2" } } diff --git a/examples/with-fusion/src/locales.ts b/examples/with-fusion/src/locales.ts new file mode 100644 index 0000000000..089b5f35dc --- /dev/null +++ b/examples/with-fusion/src/locales.ts @@ -0,0 +1,8 @@ +export const messages: Record<string, any> = { + en: { + buttonText: 'Button', + }, + 'zh-cn': { + buttonText: '按钮', + }, +}; diff --git a/examples/with-fusion/src/pages/index.tsx b/examples/with-fusion/src/pages/index.tsx index 354ffcc855..2ada9e2ec0 100644 --- a/examples/with-fusion/src/pages/index.tsx +++ b/examples/with-fusion/src/pages/index.tsx @@ -1,11 +1,15 @@ -import { Button } from '@alifd/next'; +import { Button, DatePicker } from '@alifd/next'; import '@alifd/next/dist/next.css'; +import { FormattedMessage } from 'react-intl'; export default function Home() { return ( <div> <h1>with fusion</h1> - <Button type="primary">Button</Button> + <DatePicker /> + <Button type="primary"> + <FormattedMessage id="buttonText" /> + </Button> </div> ); } \ No newline at end of file diff --git a/examples/with-fusion/src/pages/layout.tsx b/examples/with-fusion/src/pages/layout.tsx new file mode 100644 index 0000000000..35151701ac --- /dev/null +++ b/examples/with-fusion/src/pages/layout.tsx @@ -0,0 +1,46 @@ +import { Outlet } from 'ice'; +import { useState } from 'react'; +import { ConfigProvider, Radio } from '@alifd/next'; +import enUS from '@alifd/next/lib/locale/en-us'; +import zhCN from '@alifd/next/lib/locale/zh-cn'; +import { IntlProvider } from 'react-intl'; +import { messages } from '@/locales'; + +const localeMap = new Map([ + ['en', enUS], + ['zh-cn', zhCN], +]); + +export default function Layout() { + const [locale, setLocale] = useState('en'); + const list = [ + { + value: 'en', + label: 'English', + }, + { + value: 'zh-cn', + label: '中文', + }, + ]; + + function changeLocale(value: string) { + setLocale(value); + } + return ( + <main> + <IntlProvider locale={locale} messages={messages[locale]}> + <Radio.Group + dataSource={list} + shape="button" + value={locale} + onChange={changeLocale} + /> + <ConfigProvider locale={localeMap.get(locale)}> + <Outlet /> + </ConfigProvider> + </IntlProvider> + </main> + ); +} + diff --git a/examples/with-i18n/ice.config.mts b/examples/with-i18n/ice.config.mts new file mode 100644 index 0000000000..fcb7723878 --- /dev/null +++ b/examples/with-i18n/ice.config.mts @@ -0,0 +1,13 @@ +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US'], + defaultLocale: 'zh-CN', + autoRedirect: true, + }), + ], + ssr: true, +}); diff --git a/examples/with-i18n/package.json b/examples/with-i18n/package.json new file mode 100644 index 0000000000..d671d2d1c3 --- /dev/null +++ b/examples/with-i18n/package.json @@ -0,0 +1,26 @@ +{ + "name": "@examples/with-i18n", + "private": true, + "version": "1.0.0", + "scripts": { + "start": "ice start", + "build": "ice build", + "serve": "tsx server.mts" + }, + "dependencies": { + "@ice/runtime": "workspace:*", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-intl": "^6.3.2" + }, + "devDependencies": { + "@ice/app": "workspace:*", + "@ice/plugin-i18n": "workspace:*", + "@types/express": "^4.17.14", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.2", + "express": "^4.17.3", + "tslib": "^2.5.0", + "tsx": "^3.12.1" + } +} \ No newline at end of file diff --git a/examples/with-i18n/server.mts b/examples/with-i18n/server.mts new file mode 100644 index 0000000000..7990c499b3 --- /dev/null +++ b/examples/with-i18n/server.mts @@ -0,0 +1,27 @@ +import express from 'express'; +import { renderToHTML } from './build/server/index.mjs'; + +const app = express(); +const port = 4000; +const basename = '/app'; + +app.use(express.static('build', {})); + +app.use(async (req, res) => { + const { statusCode, statusText, headers, value: body } = await renderToHTML({ req, res }, { basename }); + res.statusCode = statusCode; + res.statusMessage = statusText; + Object.entries((headers || {}) as Record<string, string>).forEach(([name, value]) => { + res.setHeader(name, value); + }); + if (body && req.method !== 'HEAD') { + res.end(body); + } else { + res.end(); + } +}); + + +app.listen(port, () => { + console.log(`App listening on http://localhost:${port}${basename}`); +}); \ No newline at end of file diff --git a/examples/with-i18n/src/app.tsx b/examples/with-i18n/src/app.tsx new file mode 100644 index 0000000000..d0ddd0f9ba --- /dev/null +++ b/examples/with-i18n/src/app.tsx @@ -0,0 +1,12 @@ +import { defineAppConfig } from 'ice'; +import { defineI18nConfig } from '@ice/plugin-i18n/types'; + +export default defineAppConfig(() => ({ + router: { + basename: '/app', + }, +})); + +export const i18nConfig = defineI18nConfig(() => ({ + // disabledCookie: true, +})); diff --git a/examples/with-i18n/src/document.tsx b/examples/with-i18n/src/document.tsx new file mode 100644 index 0000000000..1e7b99c49d --- /dev/null +++ b/examples/with-i18n/src/document.tsx @@ -0,0 +1,22 @@ +import { Meta, Title, Links, Main, Scripts } from 'ice'; + +function Document() { + return ( + <html> + <head> + <meta charSet="utf-8" /> + <meta name="description" content="ICE Demo" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <Meta /> + <Title /> + <Links /> + </head> + <body> + <Main /> + <Scripts /> + </body> + </html> + ); +} + +export default Document; diff --git a/examples/with-i18n/src/locales.ts b/examples/with-i18n/src/locales.ts new file mode 100644 index 0000000000..ab4863cc65 --- /dev/null +++ b/examples/with-i18n/src/locales.ts @@ -0,0 +1,8 @@ +export const messages: Record<string, any> = { + 'en-US': { + buttonText: 'Normal Button', + }, + 'zh-CN': { + buttonText: '普通按钮', + }, +}; diff --git a/examples/with-i18n/src/pages/blog/a.tsx b/examples/with-i18n/src/pages/blog/a.tsx new file mode 100644 index 0000000000..0316e78bea --- /dev/null +++ b/examples/with-i18n/src/pages/blog/a.tsx @@ -0,0 +1,13 @@ +import { Link } from 'ice'; + +export default function BlogA() { + return ( + <> + <h2>Blog A</h2> + <ul> + <li><Link to="/">Index</Link></li> + <li><Link to="/blog">Blog</Link></li> + </ul> + </> + ); +} diff --git a/examples/with-i18n/src/pages/blog/index.tsx b/examples/with-i18n/src/pages/blog/index.tsx new file mode 100644 index 0000000000..ff66b3ffae --- /dev/null +++ b/examples/with-i18n/src/pages/blog/index.tsx @@ -0,0 +1,13 @@ +import { Link } from 'ice'; + +export default function Blog() { + return ( + <> + <h2>Blog</h2> + <ul> + <li><Link to="/">Index</Link></li> + <li><Link to="/blog/a">Blog A</Link></li> + </ul> + </> + ); +} diff --git a/examples/with-i18n/src/pages/index.tsx b/examples/with-i18n/src/pages/index.tsx new file mode 100644 index 0000000000..334e5da149 --- /dev/null +++ b/examples/with-i18n/src/pages/index.tsx @@ -0,0 +1,15 @@ +import { Link } from 'ice'; +import { FormattedMessage } from 'react-intl'; + +export default function Home() { + return ( + <div> + <h1>I18n Example</h1> + <Link to="/blog">Blog</Link> + <br /> + <button style={{ marginTop: 20 }} id="button"> + <FormattedMessage id="buttonText" /> + </button> + </div> + ); +} diff --git a/examples/with-i18n/src/pages/layout.tsx b/examples/with-i18n/src/pages/layout.tsx new file mode 100644 index 0000000000..7d3eab9868 --- /dev/null +++ b/examples/with-i18n/src/pages/layout.tsx @@ -0,0 +1,38 @@ +import { Outlet, useLocale, getAllLocales, getDefaultLocale, Link, useLocation } from 'ice'; +import { IntlProvider as ReactIntlProvider } from 'react-intl'; +import { messages } from '@/locales'; + +export default function Layout() { + const location = useLocation(); + const [activeLocale, setLocale] = useLocale(); + + return ( + <main> + <p><b>Current locale: </b>{activeLocale}</p> + <p><b>Default locale: </b>{getDefaultLocale()}</p> + <p><b>Configured locales: </b>{JSON.stringify(getAllLocales())}</p> + + <b>Choose language: </b> + <ul> + { + getAllLocales().map((locale: string) => { + return ( + <li key={locale}> + <Link + to={location.pathname} + onClick={() => setLocale(locale)} + // state={{ locale }} + > + {locale} + </Link> + </li> + ); + }) + } + </ul> + <ReactIntlProvider locale={activeLocale} messages={messages[activeLocale]}> + <Outlet /> + </ReactIntlProvider> + </main> + ); +} diff --git a/examples/with-i18n/tsconfig.json b/examples/with-i18n/tsconfig.json new file mode 100644 index 0000000000..26fd9ec799 --- /dev/null +++ b/examples/with-i18n/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react-jsx", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"], + "ice": [".ice"] + } + }, + "include": ["src", ".ice", "ice.config.*"], + "exclude": ["build", "public"] +} \ No newline at end of file diff --git a/examples/with-nested-routes/src/pages/about/#a.tsx b/examples/with-nested-routes/src/pages/about/#a.tsx deleted file mode 100644 index d05029d1f3..0000000000 --- a/examples/with-nested-routes/src/pages/about/#a.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function A() { - return <div>111</div>; -} \ No newline at end of file diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index 240072f1c3..7ebaa96d69 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -7,7 +7,12 @@ import type { Config } from '@ice/webpack-config/types'; import type { AppConfig } from '@ice/runtime/types'; import webpack from '@ice/bundles/compiled/webpack/index.js'; import fg from 'fast-glob'; -import type { DeclarationData, PluginData, ExtendsPluginAPI, TargetDeclarationData } from './types'; +import type { + DeclarationData, + PluginData, + ExtendsPluginAPI, + TargetDeclarationData, +} from './types/index.js'; import { DeclarationType } from './types/index.js'; import Generator from './service/runtimeGenerator.js'; import { createServerCompiler } from './service/serverCompiler.js'; @@ -19,7 +24,7 @@ import test from './commands/test.js'; import getWatchEvents from './getWatchEvents.js'; import { setEnv, updateRuntimeEnv, getCoreEnvKeys } from './utils/runtimeEnv.js'; import getRuntimeModules from './utils/getRuntimeModules.js'; -import { generateRoutesInfo, getRoutesDefination } from './routes.js'; +import { generateRoutesInfo, getRoutesDefinition } from './routes.js'; import * as config from './config.js'; import { RUNTIME_TMP_DIR, WEB, RUNTIME_EXPORTS, SERVER_ENTRY } from './constant.js'; import createSpinner from './utils/createSpinner.js'; @@ -167,6 +172,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt getRouteManifest: () => routeManifest.getNestedRoute(), getFlattenRoutes: () => routeManifest.getFlattenRoute(), getRoutesFile: () => routeManifest.getRoutesFile(), + addRoutesDefinition: routeManifest.addRoutesDefinition.bind(routeManifest), excuteServerEntry, context: { webpack, @@ -212,8 +218,9 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt const coreEnvKeys = getCoreEnvKeys(); - const routesInfo = await generateRoutesInfo(rootDir, routesConfig); + const routesInfo = await generateRoutesInfo(rootDir, routesConfig, routeManifest.getRoutesDefinitions()); routeManifest.setRoutes(routesInfo.routes); + const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('dataLoader'); const csr = !userConfig.ssr && !userConfig.ssg; @@ -230,7 +237,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt const iceRuntimePath = '@ice/runtime'; // Only when code splitting use the default strategy or set to `router`, the router will be lazy loaded. const lazy = [true, 'chunks', 'page'].includes(userConfig.codeSplitting); - const { routeImports, routeDefination } = getRoutesDefination(routesInfo.routes, lazy); + const { routeImports, routeDefinition } = getRoutesDefinition(routesInfo.routes, lazy); // add render data generator.setRenderData({ ...routesInfo, @@ -250,21 +257,25 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt jsOutput: distType.includes('javascript'), dataLoader: userConfig.dataLoader, routeImports, - routeDefination, + routeDefinition, }); dataCache.set('routes', JSON.stringify(routesInfo)); dataCache.set('hasExportAppData', hasExportAppData ? 'true' : ''); // Render exports files if route component export dataLoader / pageConfig. - renderExportsTemplate({ - ...routesInfo, - hasExportAppData, - }, generator.addRenderFile, { - rootDir, - runtimeDir: RUNTIME_TMP_DIR, - templateDir: path.join(templateDir, 'exports'), - dataLoader: Boolean(userConfig.dataLoader), - }); + renderExportsTemplate( + { + ...routesInfo, + hasExportAppData, + }, + generator.addRenderFile, + { + rootDir, + runtimeDir: RUNTIME_TMP_DIR, + templateDir: path.join(templateDir, 'exports'), + dataLoader: Boolean(userConfig.dataLoader), + }, + ); if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) { const { diff --git a/packages/ice/src/getWatchEvents.ts b/packages/ice/src/getWatchEvents.ts index a5b3e3a4d4..8bb5081d8f 100644 --- a/packages/ice/src/getWatchEvents.ts +++ b/packages/ice/src/getWatchEvents.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import type { Context } from 'build-scripts'; import type { Config } from '@ice/webpack-config/types'; import type { WatchEvent } from './types/plugin.js'; -import { generateRoutesInfo, getRoutesDefination } from './routes.js'; +import { generateRoutesInfo, getRoutesDefinition } from './routes.js'; import type Generator from './service/runtimeGenerator'; import getGlobalStyleGlobPattern from './utils/getGlobalStyleGlobPattern.js'; import renderExportsTemplate from './utils/renderExportsTemplate.js'; @@ -30,7 +30,7 @@ const getWatchEvents = (options: Options): WatchEvent[] => { async (eventName: string) => { if (eventName === 'add' || eventName === 'unlink' || eventName === 'change') { const routesRenderData = await generateRoutesInfo(rootDir, routesConfig); - const { routeImports, routeDefination } = getRoutesDefination(routesRenderData.routes, lazyRoutes); + const { routeImports, routeDefinition } = getRoutesDefinition(routesRenderData.routes, lazyRoutes); const stringifiedData = JSON.stringify(routesRenderData); if (cache.get('routes') !== stringifiedData) { cache.set('routes', stringifiedData); @@ -38,9 +38,9 @@ const getWatchEvents = (options: Options): WatchEvent[] => { if (eventName !== 'change') { // Specify the route files to re-render. generator.renderFile( - path.join(templateDir, 'routes.ts.ejs'), - path.join(rootDir, targetDir, 'routes.ts'), - { routeImports, routeDefination }, + path.join(templateDir, 'routes.tsx.ejs'), + path.join(rootDir, targetDir, 'routes.tsx'), + { routeImports, routeDefinition }, ); // Keep generate route manifest for avoid breaking change. generator.renderFile( diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index 1f45ff1de6..aae6a64850 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -1,15 +1,19 @@ import * as path from 'path'; import { formatNestedRouteManifest, generateRouteManifest } from '@ice/route-manifest'; -import type { NestedRouteManifest } from '@ice/route-manifest'; +import type { NestedRouteManifest, DefineExtraRoutes } from '@ice/route-manifest'; import type { UserConfig } from './types/userConfig.js'; import { getFileExports } from './service/analyze.js'; import formatPath from './utils/formatPath.js'; -export async function generateRoutesInfo(rootDir: string, routesConfig: UserConfig['routes'] = {}) { +export async function generateRoutesInfo( + rootDir: string, + routesConfig: UserConfig['routes'] = {}, + routesDefinitions: DefineExtraRoutes[] = [], +) { const routeManifest = generateRouteManifest( rootDir, routesConfig.ignoreFiles, - routesConfig.defineRoutes, + [routesConfig.defineRoutes, ...routesDefinitions], routesConfig.config, ); @@ -51,9 +55,9 @@ export default { }; } -export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], lazy = false, depth = 0) { +export function getRoutesDefinition(nestRouteManifest: NestedRouteManifest[], lazy = false, depth = 0) { const routeImports: string[] = []; - const routeDefination = nestRouteManifest.reduce((prev, route, currentIndex) => { + const routeDefinition = nestRouteManifest.reduce((prev, route) => { const { children, path: routePath, index, componentName, file, id, layout, exports } = route; const componentPath = id.startsWith('__') ? file : `@/pages/${file}`.replace(new RegExp(`${path.extname(file)}$`), ''); @@ -62,7 +66,7 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la if (lazy) { loadStatement = `import(/* webpackChunkName: "p_${componentName}" */ '${formatPath(componentPath)}')`; } else { - const routeSpecifier = `route_${depth}_${currentIndex}`; + const routeSpecifier = id.replace(/[./-]/g, '_').replace(/[:*]/, '$'); routeImports.push(`import * as ${routeSpecifier} from '${formatPath(componentPath)}';`); loadStatement = routeSpecifier; } @@ -100,9 +104,9 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la routeProperties.push('layout: true,'); } if (children) { - const res = getRoutesDefination(children, lazy, depth + 1); + const res = getRoutesDefinition(children, lazy, depth + 1); routeImports.push(...res.routeImports); - routeProperties.push(`children: [${res.routeDefination}]`); + routeProperties.push(`children: [${res.routeDefinition}]`); } prev += formatRoutesStr(depth, routeProperties); return prev; @@ -110,7 +114,7 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la return { routeImports, - routeDefination, + routeDefinition, }; } diff --git a/packages/ice/src/service/runtimeGenerator.ts b/packages/ice/src/service/runtimeGenerator.ts index 4878c8d79c..a8af3ace67 100644 --- a/packages/ice/src/service/runtimeGenerator.ts +++ b/packages/ice/src/service/runtimeGenerator.ts @@ -342,7 +342,7 @@ export default class Generator { const renderExt = '.ejs'; const realTargetPath = path.isAbsolute(targetPath) ? targetPath : path.join(this.rootDir, this.targetDir, targetPath); - // example: templatePath = 'routes.ts.ejs' + // example: templatePath = 'routes.tsx.ejs' const realTemplatePath = path.isAbsolute(templatePath) ? templatePath : path.join(this.templateDir, templatePath); const { ext } = path.parse(templatePath); diff --git a/packages/ice/src/types/generator.ts b/packages/ice/src/types/generator.ts index 52c92d428f..b1df8877d5 100644 --- a/packages/ice/src/types/generator.ts +++ b/packages/ice/src/types/generator.ts @@ -19,7 +19,7 @@ export interface TargetDeclarationData { declarationType?: DeclarationType; } -export type RenderData = Record<string, unknown>; +export type RenderData = Record<string, any>; type RenderDataFunction = (renderDataFunction: RenderData) => RenderData; export interface TemplateOptions { template: string; diff --git a/packages/ice/src/types/plugin.ts b/packages/ice/src/types/plugin.ts index d85bd4af54..268f59eb95 100644 --- a/packages/ice/src/types/plugin.ts +++ b/packages/ice/src/types/plugin.ts @@ -2,7 +2,7 @@ import type webpack from '@ice/bundles/compiled/webpack'; import type { _Plugin, CommandArgs, TaskConfig } from 'build-scripts'; import type { Configuration, Stats, WebpackOptionsNormalized } from '@ice/bundles/compiled/webpack'; import type { esbuild } from '@ice/bundles'; -import type { NestedRouteManifest } from '@ice/route-manifest'; +import type { DefineExtraRoutes, NestedRouteManifest } from '@ice/route-manifest'; import type { Config } from '@ice/webpack-config/types'; import type { AppConfig, AssetsManifest } from '@ice/runtime/types'; import type ServerCompileTask from '../utils/ServerCompileTask.js'; @@ -158,6 +158,7 @@ export interface ExtendsPluginAPI { getRouteManifest: () => Routes; getFlattenRoutes: () => string[]; getRoutesFile: () => string[]; + addRoutesDefinition: (defineRoutes: DefineExtraRoutes) => void; dataCache: Map<string, string>; createLogger: CreateLogger; } diff --git a/packages/ice/src/utils/routeManifest.ts b/packages/ice/src/utils/routeManifest.ts index 4057774c29..f433b9e6d6 100644 --- a/packages/ice/src/utils/routeManifest.ts +++ b/packages/ice/src/utils/routeManifest.ts @@ -1,11 +1,13 @@ -import type { NestedRouteManifest } from '@ice/route-manifest'; +import type { NestedRouteManifest, DefineExtraRoutes } from '@ice/route-manifest'; import getRoutePath, { getRoutesFile } from './getRoutePaths.js'; export default class RouteManifest { private routeManifest: NestedRouteManifest[]; + private routesDefinitions: DefineExtraRoutes[]; constructor() { this.routeManifest = null; + this.routesDefinitions = []; } getNestedRoute() { @@ -26,4 +28,12 @@ export default class RouteManifest { getRoutesFile() { return getRoutesFile(this.getNestedRoute()); } -} \ No newline at end of file + + public addRoutesDefinition(defineRoutes: DefineExtraRoutes) { + this.routesDefinitions.push(defineRoutes); + } + + public getRoutesDefinitions() { + return this.routesDefinitions; + } +} diff --git a/packages/ice/templates/core/entry.client.tsx.ejs b/packages/ice/templates/core/entry.client.tsx.ejs index 55d5071527..5c123e8b2d 100644 --- a/packages/ice/templates/core/entry.client.tsx.ejs +++ b/packages/ice/templates/core/entry.client.tsx.ejs @@ -16,7 +16,7 @@ const getRouterBasename = () => { // Otherwise chunk of route component will pack @ice/jsx-runtime and depend on framework bundle. const App = <></>; -const render = (customOptions = {}) => { +const render = (customOptions: Record<string, any> = {}) => { const appProps = { app, runtimeModules: { @@ -29,12 +29,15 @@ const render = (customOptions = {}) => { memoryRouter: <%- memoryRouter || false %>, <% if(dataLoaderImport.imports) {-%>dataLoaderFetcher,<% } -%> ...customOptions, - <% if (runtimeOptions.exports) { -%> runtimeOptions: { - <%- runtimeOptions.exports %> - ...(customOptions.runtimeOptions || {}), + <% if (runtimeOptions.exports) { -%> + <%- runtimeOptions.exports %> + <% } -%> + <% if (locals.customRuntimeOptions) { -%> + ...<%- JSON.stringify(customRuntimeOptions) %>, + <% } -%> + ...customOptions.runtimeOptions, }, - <% } -%> }; return runClientApp(appProps); }; diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 4ea5769093..24d0ac86f5 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -92,10 +92,13 @@ function mergeOptions(options) { routesConfig, distType, serverData, -<% if (runtimeOptions.exports) { -%> runtimeOptions: { + <% if (runtimeOptions.exports) { -%> <%- runtimeOptions.exports %> + <% } -%> + <% if (locals.customRuntimeOptions) { _%> + ...<%- JSON.stringify(customRuntimeOptions) %>, + <% } _%> }, -<% } -%> }; } diff --git a/packages/ice/templates/core/routes.tsx.ejs b/packages/ice/templates/core/routes.tsx.ejs index 16b78a77da..88ca61cad8 100644 --- a/packages/ice/templates/core/routes.tsx.ejs +++ b/packages/ice/templates/core/routes.tsx.ejs @@ -4,5 +4,5 @@ export default ({ requestContext, renderMode, }) => ([ - <%- routeDefination %> + <%- routeDefinition %> ]); diff --git a/packages/plugin-i18n/README.md b/packages/plugin-i18n/README.md new file mode 100644 index 0000000000..08dc3542f6 --- /dev/null +++ b/packages/plugin-i18n/README.md @@ -0,0 +1,46 @@ +# @ice/plugin-i18n + +组件功能描述 + +## Install + +```bash +$ npm i @ice/plugin-i18n --save-dev +``` + +## Usage + +```ts +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US'], + defaultLocale: 'zh-CN', + }), + ], +}); +``` + +## Options + +### `locales` + +- **type:** `string[]` + +The locales you want to support in your app. This option is required. + +### defaultLocale + +- **type:** `string` + +The default locale you want to be used when visiting a non-locale prefixed path. This option is required. + +### autoRedirect + +- **type:** `boolean` +- **default:** `true` + +Redirect to the preferred locale automatically. This option should be used with the middleware. If you deploy your application in production, you should read the [example]() for more detail. diff --git a/packages/plugin-i18n/build.config.mts b/packages/plugin-i18n/build.config.mts new file mode 100644 index 0000000000..c91fc6b44f --- /dev/null +++ b/packages/plugin-i18n/build.config.mts @@ -0,0 +1,8 @@ +import { defineConfig } from '@ice/pkg'; + +// https://pkg.ice.work/reference/config-list/ +export default defineConfig({ + transform: { + formats: ['es2017'], + }, +}); diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json new file mode 100644 index 0000000000..8978be9658 --- /dev/null +++ b/packages/plugin-i18n/package.json @@ -0,0 +1,65 @@ +{ + "name": "@ice/plugin-i18n", + "version": "0.0.0", + "description": "I18n plugin for ice.js 3.", + "files": [ + "es2017", + "!es2017/**/*.map" + ], + "type": "module", + "main": "es2017/index.js", + "module": "es2017/index.js", + "types": "es2017/index.d.ts", + "exports": { + ".": { + "types": "./es2017/index.d.ts", + "import": "./es2017/index.js", + "default": "./es2017/index.js" + }, + "./runtime": { + "types": "./es2017/runtime/index.d.ts", + "import": "./es2017/runtime/index.js", + "default": "./es2017/runtime/index.js" + }, + "./types": { + "types": "./es2017/types.d.ts", + "import": "./es2017/types.js", + "default": "./es2017/types.js" + }, + "./*": "./*" + }, + "sideEffects": false, + "scripts": { + "watch": "ice-pkg start", + "build": "ice-pkg build" + }, + "keywords": [ + "ice.js", + "i18n", + "plugin" + ], + "dependencies": { + "@ice/jsx-runtime": "^0.2.0", + "@swc/helpers": "^0.4.14", + "accept-language-parser": "^1.5.0", + "universal-cookie": "^4.0.4", + "url-join": "^5.0.0" + }, + "devDependencies": { + "@ice/pkg": "^1.0.0", + "@ice/app": "workspace:^", + "@ice/runtime": "workspace:^", + "@remix-run/router": "^1.5.0", + "@types/react": "^18.0.33", + "@types/accept-language-parser": "^1.5.3", + "webpack-dev-server": "^4.13.2" + }, + "peerDependencies": { + "@ice/app": "^3.0.0", + "@ice/runtime": "^1.0.0" + }, + "publishConfig": { + "access": "public" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/plugin-i18n/runtime.d.ts b/packages/plugin-i18n/runtime.d.ts new file mode 100644 index 0000000000..c26ea1b145 --- /dev/null +++ b/packages/plugin-i18n/runtime.d.ts @@ -0,0 +1 @@ +export * from './es2017/runtime/index'; \ No newline at end of file diff --git a/packages/plugin-i18n/src/constants.ts b/packages/plugin-i18n/src/constants.ts new file mode 100644 index 0000000000..00ad99dd39 --- /dev/null +++ b/packages/plugin-i18n/src/constants.ts @@ -0,0 +1 @@ +export const LOCALE_COOKIE_NAME = 'ice_locale'; diff --git a/packages/plugin-i18n/src/index.ts b/packages/plugin-i18n/src/index.ts new file mode 100644 index 0000000000..3a39633b0f --- /dev/null +++ b/packages/plugin-i18n/src/index.ts @@ -0,0 +1,85 @@ +import * as path from 'path'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import type { Plugin } from '@ice/app/types'; +import type { CreateLoggerReturnType } from '@ice/app'; +import type { I18nConfig } from './types.js'; + +const _require = createRequire(import.meta.url); +const _dirname = path.dirname(fileURLToPath(import.meta.url)); +const packageJSON = _require('../package.json'); +const { name: packageName } = packageJSON; + +const plugin: Plugin<I18nConfig> = (i18nConfig) => ({ + name: packageName, + setup: ({ addRoutesDefinition, generator, createLogger }) => { + const logger = createLogger('plugin-i18n'); + checkPluginOptions(i18nConfig, logger); + + const { locales, defaultLocale } = i18nConfig; + const prefixedLocales = locales.filter(locale => locale !== defaultLocale); + + const defineRoutes: Parameters<typeof addRoutesDefinition>[0] = (defineRoute, options) => { + function defineChildrenRoutes(children: any[], prefixedLocale: string) { + children.forEach(child => { + defineRoute( + child.path, + child.file, + { index: child.index }, + () => { + if (child.children) { + defineChildrenRoutes(child.children, prefixedLocale); + } + }); + }); + } + prefixedLocales.forEach(prefixedLocale => { + options.nestedRouteManifest.forEach(route => { + const newRoutePath = `${prefixedLocale}${route.path ? `/${route.path}` : ''}`; + defineRoute(newRoutePath, route.file, { index: route.index }, () => { + route.children && defineChildrenRoutes(route.children, prefixedLocale); + }); + }); + }); + }; + addRoutesDefinition(defineRoutes); + + generator.addRenderFile( + path.join(_dirname, 'templates/plugin-i18n.ts.ejs'), + 'plugin-i18n.ts', + { + defaultLocale: defaultLocale, + locales: JSON.stringify(locales), + }, + ); + generator.addExport({ + specifier: ['getDefaultLocale', 'getAllLocales'], + source: './plugin-i18n', + }); + generator.addExport({ + specifier: ['withLocale', 'useLocale'], + source: `${packageName}/runtime`, + }); + + generator.modifyRenderData((renderData) => { + renderData.customRuntimeOptions ||= {}; + (renderData.customRuntimeOptions as Record<string, any>).i18nConfig = i18nConfig; + return renderData; + }); + }, + runtime: `${packageName}/runtime`, +}); + +function checkPluginOptions(options: I18nConfig, logger: CreateLoggerReturnType) { + const { locales, defaultLocale } = options; + if (!Array.isArray(locales)) { + logger.error(`The plugin option \`locales\` type should be array but received ${typeof locales}`); + process.exit(1); + } + if (typeof defaultLocale !== 'string') { + logger.error(`The plugin option \`defaultLocale\` type should be string but received ${typeof defaultLocale}`); + process.exit(1); + } +} + +export default plugin; diff --git a/packages/plugin-i18n/src/runtime/I18nContext.tsx b/packages/plugin-i18n/src/runtime/I18nContext.tsx new file mode 100644 index 0000000000..0470680b83 --- /dev/null +++ b/packages/plugin-i18n/src/runtime/I18nContext.tsx @@ -0,0 +1,58 @@ +import type { ReactElement, SetStateAction, Dispatch } from 'react'; +import React, { createContext, useState, useContext } from 'react'; +import normalizeLocalePath from '../utils/normalizeLocalePath.js'; +import setLocaleToCookie from '../utils/setLocaleToCookie.js'; +import type { I18nConfig } from '../types.js'; + +type ContextValue = [string, Dispatch<SetStateAction<string>>]; + +interface I18nProvider { + children: ReactElement; + locales: I18nConfig['locales']; + defaultLocale: I18nConfig['defaultLocale']; + pathname: string; + disableCookie: boolean; + basename?: string; + headers?: { + [key: string]: string | string[]; + }; +} + +export const I18nContext = createContext<ContextValue>(null); + +I18nContext.displayName = 'I18nContext'; + +export function I18nProvider({ + children, + locales, + defaultLocale, + disableCookie, + pathname, + basename, +}: I18nProvider) { + const [locale, updateLocale] = useState<string>( + normalizeLocalePath({ pathname, basename, locales: locales }).pathLocale || defaultLocale, + ); + + function setLocale(locale: string) { + !disableCookie && setLocaleToCookie(locale); + updateLocale(locale); + } + + return ( + <I18nContext.Provider value={[locale, setLocale]}> + {children} + </I18nContext.Provider> + ); +} + +export function useLocale() { + return useContext(I18nContext); +} + +export function withLocale<Props>(Component: React.ComponentType<Props>) { + return (props: Props) => { + const [locale, setLocale] = useLocale(); + return <Component {...props} locale={locale} setLocale={setLocale} />; + }; +} diff --git a/packages/plugin-i18n/src/runtime/hijackHistory.tsx b/packages/plugin-i18n/src/runtime/hijackHistory.tsx new file mode 100644 index 0000000000..984284c79a --- /dev/null +++ b/packages/plugin-i18n/src/runtime/hijackHistory.tsx @@ -0,0 +1,79 @@ +import type { History, To } from '@remix-run/router'; +import urlJoin from 'url-join'; +import detectLocale from '../utils/detectLocale.js'; +import normalizeLocalePath from '../utils/normalizeLocalePath.js'; +import type { I18nConfig } from '../types.js'; + +/** + * Hijack history in order to add locale prefix to the new route path. + */ +export default function hijackHistory( + history: History, + i18nConfig: I18nConfig, + disableCookie: boolean, + basename?: string, +) { + const originHistory = { ...history }; + const { defaultLocale, locales } = i18nConfig; + + function createNewNavigate(type: 'push' | 'replace') { + return function (toPath: To, state?: Record<string, any>) { + const locale = state?.locale; + const localePrefixPathname = getLocalePrefixPathname({ + toPath, + basename, + locales, + currentLocale: locale, + defaultLocale, + disableCookie, + }); + originHistory[type](localePrefixPathname, state); + }; + } + + history.push = createNewNavigate('push'); + + history.replace = createNewNavigate('replace'); +} + +function getLocalePrefixPathname({ + toPath, + locales, + defaultLocale, + basename, + disableCookie, + currentLocale = '', +}: { + toPath: To; + locales: string[]; + defaultLocale: string; + disableCookie: boolean; + currentLocale: string; + basename?: string; +}) { + const pathname = getPathname(toPath); + const locale = disableCookie ? currentLocale : detectLocale({ + locales, + defaultLocale, + pathname, + disableCookie, + }); + const { pathname: newPathname } = normalizeLocalePath({ pathname, locales, basename }); + return urlJoin( + basename, + locale === defaultLocale ? '' : locale, + newPathname, + typeof toPath === 'string' ? '' : toPath.search, + ); +} + +function getPathname(toPath: To): string { + if (isPathnameString(toPath)) { + return toPath; + } + return toPath.pathname; +} + +function isPathnameString(pathname: To): pathname is string { + return typeof pathname === 'string'; +} diff --git a/packages/plugin-i18n/src/runtime/index.tsx b/packages/plugin-i18n/src/runtime/index.tsx new file mode 100644 index 0000000000..dee5e6a12c --- /dev/null +++ b/packages/plugin-i18n/src/runtime/index.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import type { RuntimePlugin } from '@ice/runtime/types'; +import detectLocale from '../utils/detectLocale.js'; +import type { I18nAppConfig, I18nConfig } from '../types.js'; +import getLocaleRedirectPath from '../utils/getLocaleRedirectPath.js'; +import { I18nProvider, useLocale, withLocale } from './I18nContext.js'; +import hijackHistory from './hijackHistory.js'; + +const EXPORT_NAME = 'i18nConfig'; +// Mock it to avoid ssg error and use new URL to parse url instead of url.parse. +const baseUrl = 'http://127.0.0.1'; + +const runtime: RuntimePlugin<{ i18nConfig: I18nConfig }> = async ( + { + appContext, + addProvider, + history, + addResponseHandler, + }, + runtimeOptions, +) => { + const { basename, requestContext, appExport } = appContext; + const exported = appExport[EXPORT_NAME]; + const i18nAppConfig: I18nAppConfig = Object.assign( + { disableCookie: false }, + (typeof exported === 'function' ? await exported() : exported), + ); + const disableCookie = typeof i18nAppConfig.disableCookie === 'function' + ? i18nAppConfig.disableCookie() + : i18nAppConfig.disableCookie; + + const { i18nConfig } = runtimeOptions; + const { locales, defaultLocale, autoRedirect } = i18nConfig; + + addProvider(({ children }) => { + return ( + <I18nProvider + pathname={requestContext.pathname} + locales={locales} + defaultLocale={defaultLocale} + basename={basename} + disableCookie={disableCookie} + headers={requestContext.req?.headers} + > + {children} + </I18nProvider> + ); + }); + + if (history) { + hijackHistory(history, i18nConfig, disableCookie, basename); + } + + if (autoRedirect) { + addResponseHandler((req) => { + const url = new URL(`${baseUrl}${req.url}`); + const detectedLocale = detectLocale({ + locales, + defaultLocale, + basename, + pathname: url.pathname, + headers: req.headers, + disableCookie: false, + }); + + const localeRedirectPath = getLocaleRedirectPath({ + pathname: url.pathname, + defaultLocale, + detectedLocale, + basename, + }); + if (localeRedirectPath) { + url.pathname = localeRedirectPath; + + return { + statusCode: 302, + statusText: 'Found', + headers: { + location: String(Object.assign(new URL(baseUrl), url)).replace(RegExp(`^${baseUrl}`), ''), + }, + }; + } + }); + } +}; + +export default runtime; + +export { useLocale, withLocale }; \ No newline at end of file diff --git a/packages/plugin-i18n/src/templates/plugin-i18n.ts.ejs b/packages/plugin-i18n/src/templates/plugin-i18n.ts.ejs new file mode 100644 index 0000000000..6d87f99556 --- /dev/null +++ b/packages/plugin-i18n/src/templates/plugin-i18n.ts.ejs @@ -0,0 +1,7 @@ +export function getDefaultLocale() { + return '<%= defaultLocale %>'; +} + +export function getAllLocales() { + return <%- locales %>; +} diff --git a/packages/plugin-i18n/src/types.ts b/packages/plugin-i18n/src/types.ts new file mode 100644 index 0000000000..185f58b493 --- /dev/null +++ b/packages/plugin-i18n/src/types.ts @@ -0,0 +1,32 @@ +export interface I18nConfig { + /** + * The locales which you want to support in your app. + */ + locales: string[]; + /** + * The default locale which is used when visiting a non-locale prefixed path. e.g `/home`. + */ + defaultLocale: string; + /** + * Automatically redirect to the correct path which is based on user's preferred locale. + */ + autoRedirect?: boolean; +} + +export interface I18nAppConfig { + /** + * Weather or not current application cookie is blocked(authorized). + * If it is, we will not get the locale value(ice_locale) from cookie. + * @default {false} + */ + disableCookie?: boolean | (() => boolean); +} + +export function defineI18nConfig( + configOrDefineConfig: I18nAppConfig | (() => I18nAppConfig), +): I18nAppConfig { + if (typeof configOrDefineConfig === 'function') { + return configOrDefineConfig(); + } + return configOrDefineConfig; +} diff --git a/packages/plugin-i18n/src/typings.d.ts b/packages/plugin-i18n/src/typings.d.ts new file mode 100644 index 0000000000..922f320605 --- /dev/null +++ b/packages/plugin-i18n/src/typings.d.ts @@ -0,0 +1 @@ +/// <reference types="@ice/pkg/types" /> diff --git a/packages/plugin-i18n/src/utils/detectLocale.ts b/packages/plugin-i18n/src/utils/detectLocale.ts new file mode 100644 index 0000000000..53aaa88171 --- /dev/null +++ b/packages/plugin-i18n/src/utils/detectLocale.ts @@ -0,0 +1,33 @@ +import type { I18nConfig } from '../types.js'; +import getLocaleFromCookie from './getLocaleFromCookie.js'; +import normalizeLocalePath from './normalizeLocalePath.js'; +import getPreferredLocale from './getPreferredLocale.js'; + +interface DetectLocaleParams { + locales: I18nConfig['locales']; + defaultLocale: I18nConfig['defaultLocale']; + pathname: string; + disableCookie: boolean; + basename?: string; + headers?: { + [key: string]: string | string[]; + }; +} + +export default function detectLocale({ + locales, + defaultLocale, + pathname, + basename, + disableCookie, + headers = {}, +}: DetectLocaleParams): string { + const detectedLocale = ( + normalizeLocalePath({ pathname, locales, basename }).pathLocale || + (!disableCookie && getLocaleFromCookie(locales, headers)) || + getPreferredLocale(locales, headers) || + defaultLocale + ); + + return detectedLocale; +} diff --git a/packages/plugin-i18n/src/utils/getLocaleFromCookie.ts b/packages/plugin-i18n/src/utils/getLocaleFromCookie.ts new file mode 100644 index 0000000000..fbf661e756 --- /dev/null +++ b/packages/plugin-i18n/src/utils/getLocaleFromCookie.ts @@ -0,0 +1,14 @@ +import type { I18nConfig } from 'src/types.js'; +import Cookies from 'universal-cookie'; +import { LOCALE_COOKIE_NAME } from '../constants.js'; + +export default function getLocaleFromCookie( + locales: I18nConfig['locales'], + headers: { [key: string]: string | string[] | undefined } = {}, +) { + const cookies: Cookies = new Cookies(typeof window === 'undefined' ? headers.cookie : undefined); + const iceLocale = cookies.get(LOCALE_COOKIE_NAME); + const locale = locales.find(locale => iceLocale === locale) || undefined; + + return locale; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/utils/getLocaleRedirectPath.ts b/packages/plugin-i18n/src/utils/getLocaleRedirectPath.ts new file mode 100644 index 0000000000..68c8c55dfe --- /dev/null +++ b/packages/plugin-i18n/src/utils/getLocaleRedirectPath.ts @@ -0,0 +1,24 @@ +import urlJoin from 'url-join'; +import type { I18nConfig } from '../types.js'; +import removeBasenameFromPath from './removeBasenameFromPath.js'; + +interface GetRedirectPathOptions { + pathname: string; + defaultLocale: I18nConfig['defaultLocale']; + detectedLocale: string; + basename?: string; +} + +export default function getLocaleRedirectPath({ + pathname, + defaultLocale, + detectedLocale, + basename, +}: GetRedirectPathOptions) { + const normalizedPathname = removeBasenameFromPath(pathname, basename); + const isRootPath = normalizedPathname === '/'; + + if (isRootPath && defaultLocale !== detectedLocale) { + return urlJoin(basename, detectedLocale); + } +} diff --git a/packages/plugin-i18n/src/utils/getPreferredLocale.ts b/packages/plugin-i18n/src/utils/getPreferredLocale.ts new file mode 100644 index 0000000000..50ff12b112 --- /dev/null +++ b/packages/plugin-i18n/src/utils/getPreferredLocale.ts @@ -0,0 +1,14 @@ +import { pick as acceptLanguagePick } from 'accept-language-parser'; + +/** + * Get the preferred locale by Accept-Language in request headers(Server) or window.navigator.languages(Client) + */ +export default function getPreferredLocale(locales: string[], headers: { [key: string]: string | string[] } = {}) { + if (typeof window === 'undefined') { + const acceptLanguageValue = headers?.['accept-language'] as string; + return acceptLanguagePick(locales, acceptLanguageValue); + } else { + const acceptLanguages = window.navigator.languages || []; + return acceptLanguages.find(acceptLanguage => locales.includes(acceptLanguage)); + } +} diff --git a/packages/plugin-i18n/src/utils/normalizeLocalePath.ts b/packages/plugin-i18n/src/utils/normalizeLocalePath.ts new file mode 100644 index 0000000000..08c0c06328 --- /dev/null +++ b/packages/plugin-i18n/src/utils/normalizeLocalePath.ts @@ -0,0 +1,29 @@ +import type { I18nConfig } from '../types.js'; +import removeBasenameFromPath from './removeBasenameFromPath.js'; + +interface NormalizeLocalePathOptions { + pathname: string; + locales: I18nConfig['locales']; + basename?: string; +} + +export default function normalizeLocalePath({ pathname, locales, basename }: NormalizeLocalePathOptions) { + const originPathname = removeBasenameFromPath(pathname, basename); + const subPaths = originPathname.split('/'); + let newPathname = originPathname; + let pathLocale: string | undefined; + + for (const locale of locales) { + if (subPaths[1] && subPaths[1] === locale) { + pathLocale = locale; + subPaths.splice(1, 1); + newPathname = subPaths.join('/') || '/'; + break; + } + } + + return { + pathname: newPathname, + pathLocale, + }; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/utils/removeBasenameFromPath.ts b/packages/plugin-i18n/src/utils/removeBasenameFromPath.ts new file mode 100644 index 0000000000..acb994b86f --- /dev/null +++ b/packages/plugin-i18n/src/utils/removeBasenameFromPath.ts @@ -0,0 +1,18 @@ +export default function removeBasenameFromPath(pathname: string, basename?: string) { + if (typeof basename !== 'string') { + return pathname; + } + if (basename[0] !== '/') { + // compatible with no slash. For example: ice -> /ice + basename = `/${basename}`; + } + + if (pathname.startsWith(basename)) { + pathname = pathname.substring(basename.length); + if (!pathname.startsWith('/')) { + pathname = `/${pathname || ''}`; + } + } + + return pathname; +} diff --git a/packages/plugin-i18n/src/utils/setLocaleToCookie.ts b/packages/plugin-i18n/src/utils/setLocaleToCookie.ts new file mode 100644 index 0000000000..308a3afb0f --- /dev/null +++ b/packages/plugin-i18n/src/utils/setLocaleToCookie.ts @@ -0,0 +1,10 @@ +import Cookies from 'universal-cookie'; +import { LOCALE_COOKIE_NAME } from '../constants.js'; + +/** + * NOTE: Call this function in browser. + */ +export default function setLocaleToCookie(locale: string) { + const cookies = new Cookies(); + cookies.set(LOCALE_COOKIE_NAME, locale, { path: '/' }); +} diff --git a/packages/plugin-i18n/tsconfig.json b/packages/plugin-i18n/tsconfig.json new file mode 100644 index 0000000000..67afd8c5e4 --- /dev/null +++ b/packages/plugin-i18n/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "rootDir": "src", + }, + "include": ["src"] +} diff --git a/packages/plugin-i18n/types.d.ts b/packages/plugin-i18n/types.d.ts new file mode 100644 index 0000000000..d285a37f53 --- /dev/null +++ b/packages/plugin-i18n/types.d.ts @@ -0,0 +1 @@ +export * from './es2017/types'; diff --git a/packages/plugin-pha/src/index.ts b/packages/plugin-pha/src/index.ts index 0e7fc6783e..9fdff4f83c 100644 --- a/packages/plugin-pha/src/index.ts +++ b/packages/plugin-pha/src/index.ts @@ -34,7 +34,7 @@ const plugin: Plugin<PluginOptions> = (options) => ({ const { template = true, preload = false } = options || {}; const { command, rootDir } = context; - const logger = createLogger('PHA'); + const logger = createLogger('plugin-pha'); // Get variable blows from task config. let compiler: Compiler; diff --git a/packages/plugin-store/package.json b/packages/plugin-store/package.json index b5e047ed08..9f18109173 100644 --- a/packages/plugin-store/package.json +++ b/packages/plugin-store/package.json @@ -44,8 +44,8 @@ "micromatch": "^4.0.5" }, "devDependencies": { - "@ice/app": "^3.1.2", - "@ice/runtime": "^1.1.3", + "@ice/app": "workspace:^", + "@ice/runtime": "workspace:^", "react": "^18.2.0", "react-dom": "^18.2.0", "@types/react": "^18.0.0", diff --git a/packages/route-manifest/src/index.ts b/packages/route-manifest/src/index.ts index 591b25f234..c8d62d18d3 100644 --- a/packages/route-manifest/src/index.ts +++ b/packages/route-manifest/src/index.ts @@ -2,14 +2,30 @@ import fs from 'fs'; import path from 'path'; import minimatch from 'minimatch'; -import { createComponentName, createRouteId, defineRoutes, normalizeSlashes } from './routes.js'; -import type { RouteManifest, DefineRouteFunction, NestedRouteManifest, ConfigRoute } from './routes.js'; +import { + createComponentName, + removeLastLayoutStrFromId, + createRouteId, + defineRoutes, + normalizeSlashes, + createFileId, +} from './routes.js'; +import type { + RouteManifest, + DefineRouteFunction, + NestedRouteManifest, + ConfigRoute, + DefineRoutesOptions, + DefineExtraRoutes, +} from './routes.js'; export type { RouteManifest, NestedRouteManifest, DefineRouteFunction, ConfigRoute, + DefineExtraRoutes, + DefineRoutesOptions, }; export interface RouteItem { @@ -38,7 +54,7 @@ export function isRouteModuleFile(filename: string): boolean { export function generateRouteManifest( rootDir: string, ignoreFiles: string[] = [], - defineExtraRoutes?: (defineRoute: DefineRouteFunction) => void, + defineExtraRoutesQueue?: DefineExtraRoutes[], routeConfig?: RouteItem[], ) { const srcDir = path.join(rootDir, 'src'); @@ -47,6 +63,10 @@ export function generateRouteManifest( if (fs.existsSync(path.resolve(srcDir, 'pages'))) { const conventionalRoutes = defineConventionalRoutes( rootDir, + { + routeManifest, + nestedRouteManifest: formatNestedRouteManifest(routeManifest), + }, ignoreFiles, ); @@ -59,15 +79,25 @@ export function generateRouteManifest( } } // 3. add extra routes from user config - if (defineExtraRoutes) { - const extraRoutes = defineRoutes(defineExtraRoutes); - for (const key of Object.keys(extraRoutes)) { - const route = extraRoutes[key]; - routeManifest[route.id] = { - ...route, - parentId: route.parentId || undefined, - }; - } + if (Array.isArray(defineExtraRoutesQueue)) { + defineExtraRoutesQueue.forEach((defineExtraRoutes) => { + if (defineExtraRoutes) { + const extraRoutes = defineRoutes( + defineExtraRoutes, + { + routeManifest, + nestedRouteManifest: formatNestedRouteManifest(routeManifest), + }, + ); + for (const key of Object.keys(extraRoutes)) { + const route = extraRoutes[key]; + routeManifest[route.id] = { + ...route, + parentId: route.parentId || undefined, + }; + } + } + }); } // Add routes by routes config. @@ -86,7 +116,7 @@ export function generateRouteManifest( export function parseRoute(routeItem: RouteItem, parentId?: string, parentPath?: string) { const routes = []; const { path: routePath, component, children } = routeItem; - const id = createRouteId(component); + const id = createRouteId(component, routePath, parentPath); let index; const currentPath = path.join(parentPath || '/', routePath).split(path.sep).join('/'); const isRootPath = currentPath === '/'; @@ -101,7 +131,7 @@ export function parseRoute(routeItem: RouteItem, parentId?: string, parentPath?: id, parentId, file: component, - componentName: createComponentName(id), + componentName: createComponentName(component), layout: !!children, }; routes.push(route); @@ -128,6 +158,7 @@ export function formatNestedRouteManifest(routeManifest: RouteManifest, parentId function defineConventionalRoutes( rootDir: string, + options: DefineRoutesOptions, ignoredFilePatterns?: string[], ): RouteManifest { const files: { [routeId: string]: string } = {}; @@ -143,40 +174,41 @@ function defineConventionalRoutes( } if (isRouteModuleFile(file)) { - let routeId = createRouteId(file); - files[routeId] = file; + let fileId = createFileId(file); + files[fileId] = file; return; } }, ); - const routeIds = Object.keys(files).sort(byLongestFirst); + const fileIds = Object.keys(files).sort(byLongestFirst); const uniqueRoutes = new Map<string, string>(); // 2. recurse through all routes using the public defineRoutes() API function defineNestedRoutes( defineRoute: DefineRouteFunction, + options: DefineRoutesOptions, parentId?: string, ): void { - const childRouteIds = routeIds.filter((id) => { - const parentRouteId = findParentRouteId(routeIds, id); - return parentRouteId === parentId; + const childFileIds = fileIds.filter((id) => { + const parentFileId = findParentFileId(fileIds, id); + return parentFileId === parentId; }); - for (let routeId of childRouteIds) { + for (let fileId of childFileIds) { const parentRoutePath = removeLastLayoutStrFromId(parentId) || ''; const routePath: string | undefined = createRoutePath( // parentRoutePath = 'home', routeId = 'home/me', the new routeId is 'me' // in order to escape the child route path is absolute path - routeId.slice(parentRoutePath.length + (parentRoutePath ? 1 : 0)), + fileId.slice(parentRoutePath.length + (parentRoutePath ? 1 : 0)), ); - const routeFilePath = normalizeSlashes(path.join('src', 'pages', files[routeId])); + const routeFilePath = normalizeSlashes(path.join('src', 'pages', files[fileId])); if (RegExp(`[^${validRouteChar.join('')}]+`).test(routePath)) { throw new Error(`invalid character in '${routeFilePath}'. Only support char: ${validRouteChar.join(', ')}`); } - const isIndexRoute = routeId === 'index' || routeId.endsWith('/index'); - const fullPath = createRoutePath(routeId); + const isIndexRoute = fileId === 'index' || fileId.endsWith('/index'); + const fullPath = createRoutePath(fileId); const uniqueRouteId = (fullPath || '') + (isIndexRoute ? '?index' : ''); if (uniqueRouteId) { @@ -186,33 +218,33 @@ function defineConventionalRoutes( conflicts with route ${JSON.stringify(uniqueRoutes.get(uniqueRouteId))}`, ); } else { - uniqueRoutes.set(uniqueRouteId, routeId); + uniqueRoutes.set(uniqueRouteId, fileId); } } if (isIndexRoute) { - let invalidChildRoutes = routeIds.filter( - (id) => findParentRouteId(routeIds, id) === routeId, + let invalidChildRoutes = fileIds.filter( + (id) => findParentFileId(fileIds, id) === fileId, ); if (invalidChildRoutes.length > 0) { throw new Error( - `Child routes are not allowed in index routes. Please remove child routes of ${routeId}`, + `Child routes are not allowed in index routes. Please remove child routes of ${fileId}`, ); } - defineRoute(routePath, files[routeId], { + defineRoute(routePath, files[fileId], { index: true, }); } else { - defineRoute(routePath, files[routeId], () => { - defineNestedRoutes(defineRoute, routeId); + defineRoute(routePath, files[fileId], () => { + defineNestedRoutes(defineRoute, options, fileId); }); } } } - return defineRoutes(defineNestedRoutes); + return defineRoutes(defineNestedRoutes, options); } const escapeStart = '['; @@ -284,7 +316,7 @@ export function createRoutePath(routeId: string): string | undefined { return result || undefined; } -function findParentRouteId( +function findParentFileId( routeIds: string[], childRouteId: string, ): string | undefined { @@ -315,16 +347,3 @@ function visitFiles( } } } - -/** - * remove `/layout` str if the routeId has it - * - * 'layout' -> '' - * 'About/layout' -> 'About' - * 'About/layout/index' -> 'About/layout/index' - */ -function removeLastLayoutStrFromId(id?: string) { - const layoutStrs = ['/layout', 'layout']; - const currentLayoutStr = layoutStrs.find(layoutStr => id?.endsWith(layoutStr)); - return currentLayoutStr ? id.slice(0, id.length - currentLayoutStr.length) : id; -} diff --git a/packages/route-manifest/src/routes.ts b/packages/route-manifest/src/routes.ts index d4042fc26a..b74ce1efdc 100644 --- a/packages/route-manifest/src/routes.ts +++ b/packages/route-manifest/src/routes.ts @@ -1,6 +1,6 @@ // based on https://github.com/remix-run/remix/blob/main/packages/remix-dev/config/routes.ts -import { win32 } from 'path'; +import { win32, join } from 'path'; export interface ConfigRoute { /** @@ -82,8 +82,19 @@ export interface NestedRouteManifest extends ConfigRoute { children?: ConfigRoute[]; } +export interface DefineRoutesOptions { + routeManifest: RouteManifest; + nestedRouteManifest: NestedRouteManifest[]; +} + +export type DefineExtraRoutes = ( + defineRoute: DefineRouteFunction, + options: DefineRoutesOptions, +) => void; + export function defineRoutes( - callback: (defineRoute: DefineRouteFunction) => void, + callback: (defineRoute: DefineRouteFunction, options: DefineRoutesOptions) => void, + options: DefineRoutesOptions, ) { const routes: RouteManifest = Object.create(null); const parentRoutes: ConfigRoute[] = []; @@ -108,18 +119,19 @@ export function defineRoutes( // route(path, file, options) options = optionsOrChildren || {}; } + const parentRoute = parentRoutes.length > 0 + ? parentRoutes[parentRoutes.length - 1] + : undefined; + + const id = createRouteId(file, path, parentRoute?.path, options.index); - const id = createRouteId(file); const route: ConfigRoute = { path, index: options.index ? true : undefined, id, - parentId: - parentRoutes.length > 0 - ? parentRoutes[parentRoutes.length - 1].id - : undefined, + parentId: parentRoute ? parentRoute.id : undefined, file, - componentName: createComponentName(id), + componentName: createComponentName(file), layout: id.endsWith('layout'), }; @@ -132,14 +144,27 @@ export function defineRoutes( } }; - callback(defineRoute); + callback(defineRoute, options); alreadyReturned = true; return routes; } -export function createRouteId(file: string) { +export function createRouteId( + file: string, + path?: string, + parentPath?: string, + index?: boolean, +) { + return normalizeSlashes(join( + parentPath || '', + path || (index ? '/' : ''), + stripFileExtension(file).endsWith('layout') ? 'layout' : '', + )); +} + +export function createFileId(file: string) { return normalizeSlashes(stripFileExtension(file)); } @@ -151,8 +176,21 @@ function stripFileExtension(file: string) { return file.replace(/\.[a-z0-9]+$/i, ''); } -export function createComponentName(id: string) { - return id.split('/') +export function createComponentName(file: string) { + return createFileId(file).split('/') .map((item: string) => item.toLowerCase()) .join('-'); } + +/** + * remove `/layout` str if the routeId has it + * + * 'layout' -> '' + * 'About/layout' -> 'About' + * 'About/layout/index' -> 'About/layout/index' + */ +export function removeLastLayoutStrFromId(id?: string) { + const layoutStrs = ['/layout', 'layout']; + const currentLayoutStr = layoutStrs.find(layoutStr => id?.endsWith(layoutStr)); + return currentLayoutStr ? id.slice(0, id.length - currentLayoutStr.length) : id; +} diff --git a/packages/route-manifest/tests/__snapshots__/formatNestedRouteManifest.spec.ts.snap b/packages/route-manifest/tests/__snapshots__/formatNestedRouteManifest.spec.ts.snap index b47291d7c1..fa7914c3fe 100644 --- a/packages/route-manifest/tests/__snapshots__/formatNestedRouteManifest.spec.ts.snap +++ b/packages/route-manifest/tests/__snapshots__/formatNestedRouteManifest.spec.ts.snap @@ -5,7 +5,7 @@ exports[`generateRouteManifest function > layout-routes 1`] = ` { "componentName": "blog-index", "file": "blog/index.tsx", - "id": "blog/index", + "id": "blog", "index": true, "layout": false, "parentId": undefined, @@ -14,7 +14,7 @@ exports[`generateRouteManifest function > layout-routes 1`] = ` { "componentName": "blog-$id", "file": "blog/$id.tsx", - "id": "blog/$id", + "id": "blog/:id", "index": undefined, "layout": false, "parentId": undefined, @@ -32,7 +32,7 @@ exports[`generateRouteManifest function > layout-routes 1`] = ` { "componentName": "index", "file": "index.tsx", - "id": "index", + "id": "/", "index": true, "layout": false, "parentId": undefined, diff --git a/packages/route-manifest/tests/__snapshots__/generateRouteManifest.spec.ts.snap b/packages/route-manifest/tests/__snapshots__/generateRouteManifest.spec.ts.snap index 76f5523f4e..4c81670ac1 100644 --- a/packages/route-manifest/tests/__snapshots__/generateRouteManifest.spec.ts.snap +++ b/packages/route-manifest/tests/__snapshots__/generateRouteManifest.spec.ts.snap @@ -2,19 +2,28 @@ exports[`generateRouteManifest function > basic-routes 1`] = ` { - "About/index": { + "/": { + "componentName": "index", + "file": "index.tsx", + "id": "/", + "index": true, + "layout": false, + "parentId": "layout", + "path": undefined, + }, + "About": { "componentName": "about-index", "file": "About/index.tsx", - "id": "About/index", + "id": "About", "index": true, "layout": false, "parentId": "layout", "path": "About", }, - "About/me/index": { + "About/me": { "componentName": "about-me-index", "file": "About/me/index.tsx", - "id": "About/me/index", + "id": "About/me", "index": true, "layout": false, "parentId": "layout", @@ -29,15 +38,6 @@ exports[`generateRouteManifest function > basic-routes 1`] = ` "parentId": "layout", "path": "home", }, - "index": { - "componentName": "index", - "file": "index.tsx", - "id": "index", - "index": true, - "layout": false, - "parentId": "layout", - "path": undefined, - }, "layout": { "componentName": "layout", "file": "layout.tsx", @@ -52,19 +52,28 @@ exports[`generateRouteManifest function > basic-routes 1`] = ` exports[`generateRouteManifest function > define-extra-routes 1`] = ` { - "About/index": { + "/": { + "componentName": "index", + "file": "index.tsx", + "id": "/", + "index": true, + "layout": false, + "parentId": "layout", + "path": undefined, + }, + "/about-me": { "componentName": "about-index", "file": "About/index.tsx", - "id": "About/index", + "id": "/about-me", "index": undefined, "layout": false, "parentId": undefined, "path": "/about-me", }, - "About/me/index": { + "About/me": { "componentName": "about-me-index", "file": "About/me/index.tsx", - "id": "About/me/index", + "id": "About/me", "index": true, "layout": false, "parentId": "layout", @@ -79,15 +88,6 @@ exports[`generateRouteManifest function > define-extra-routes 1`] = ` "parentId": "layout", "path": "home", }, - "index": { - "componentName": "index", - "file": "index.tsx", - "id": "index", - "index": true, - "layout": false, - "parentId": "layout", - "path": undefined, - }, "layout": { "componentName": "layout", "file": "layout.tsx", @@ -102,10 +102,10 @@ exports[`generateRouteManifest function > define-extra-routes 1`] = ` exports[`generateRouteManifest function > doc-delimeters-routes 1`] = ` { - "home.news": { + "home/news": { "componentName": "home.news", "file": "home.news.tsx", - "id": "home.news", + "id": "home/news", "index": undefined, "layout": false, "parentId": "layout", @@ -125,6 +125,15 @@ exports[`generateRouteManifest function > doc-delimeters-routes 1`] = ` exports[`generateRouteManifest function > dynamic-routes 1`] = ` { + "/": { + "componentName": "index", + "file": "index.tsx", + "id": "/", + "index": true, + "layout": false, + "parentId": undefined, + "path": undefined, + }, "about": { "componentName": "about", "file": "about.tsx", @@ -134,51 +143,42 @@ exports[`generateRouteManifest function > dynamic-routes 1`] = ` "parentId": undefined, "path": "about", }, - "blog/$id": { - "componentName": "blog-$id", - "file": "blog/$id.tsx", - "id": "blog/$id", - "index": undefined, - "layout": false, - "parentId": undefined, - "path": "blog/:id", - }, - "blog/index": { + "blog": { "componentName": "blog-index", "file": "blog/index.tsx", - "id": "blog/index", + "id": "blog", "index": true, "layout": false, "parentId": undefined, "path": "blog", }, - "index": { - "componentName": "index", - "file": "index.tsx", - "id": "index", - "index": true, + "blog/:id": { + "componentName": "blog-$id", + "file": "blog/$id.tsx", + "id": "blog/:id", + "index": undefined, "layout": false, "parentId": undefined, - "path": undefined, + "path": "blog/:id", }, } `; exports[`generateRouteManifest function > escape-routes 1`] = ` { - "1[.pdf]": { + "1.pdf": { "componentName": "1[.pdf]", "file": "1[.pdf].tsx", - "id": "1[.pdf]", + "id": "1.pdf", "index": undefined, "layout": false, "parentId": undefined, "path": "1.pdf", }, - "[index]": { + "index": { "componentName": "[index]", "file": "[index].tsx", - "id": "[index]", + "id": "index", "index": undefined, "layout": false, "parentId": undefined, @@ -189,10 +189,19 @@ exports[`generateRouteManifest function > escape-routes 1`] = ` exports[`generateRouteManifest function > ignore-routes 1`] = ` { - "About/me/index": { + "/": { + "componentName": "index", + "file": "index.tsx", + "id": "/", + "index": true, + "layout": false, + "parentId": "layout", + "path": undefined, + }, + "About/me": { "componentName": "about-me-index", "file": "About/me/index.tsx", - "id": "About/me/index", + "id": "About/me", "index": true, "layout": false, "parentId": "layout", @@ -207,15 +216,6 @@ exports[`generateRouteManifest function > ignore-routes 1`] = ` "parentId": "layout", "path": "home", }, - "index": { - "componentName": "index", - "file": "index.tsx", - "id": "index", - "index": true, - "layout": false, - "parentId": "layout", - "path": undefined, - }, "layout": { "componentName": "layout", "file": "layout.tsx", @@ -230,6 +230,15 @@ exports[`generateRouteManifest function > ignore-routes 1`] = ` exports[`generateRouteManifest function > layout-routes 1`] = ` { + "/": { + "componentName": "index", + "file": "index.tsx", + "id": "/", + "index": true, + "layout": false, + "parentId": "layout", + "path": undefined, + }, "about": { "componentName": "about", "file": "about.tsx", @@ -239,24 +248,24 @@ exports[`generateRouteManifest function > layout-routes 1`] = ` "parentId": "layout", "path": "about", }, - "blog/$id": { - "componentName": "blog-$id", - "file": "blog/$id.tsx", - "id": "blog/$id", - "index": undefined, - "layout": false, - "parentId": "blog/layout", - "path": ":id", - }, - "blog/index": { + "blog/": { "componentName": "blog-index", "file": "blog/index.tsx", - "id": "blog/index", + "id": "blog/", "index": true, "layout": false, "parentId": "blog/layout", "path": undefined, }, + "blog/:id": { + "componentName": "blog-$id", + "file": "blog/$id.tsx", + "id": "blog/:id", + "index": undefined, + "layout": false, + "parentId": "blog/layout", + "path": ":id", + }, "blog/layout": { "componentName": "blog-layout", "file": "blog/layout.tsx", @@ -266,42 +275,24 @@ exports[`generateRouteManifest function > layout-routes 1`] = ` "parentId": "layout", "path": "blog", }, - "home/index": { + "home/": { "componentName": "home-index", "file": "home/index.tsx", - "id": "home/index", + "id": "home/", "index": true, "layout": false, "parentId": "home/layout", "path": undefined, }, "home/layout": { - "componentName": "home-layout", - "file": "home/layout.tsx", - "id": "home/layout", - "index": undefined, - "layout": true, - "parentId": "layout", - "path": "home", - }, - "home/layout/index": { "componentName": "home-layout-index", "file": "home/layout/index.tsx", - "id": "home/layout/index", + "id": "home/layout", "index": true, - "layout": false, + "layout": true, "parentId": "home/layout", "path": "layout", }, - "index": { - "componentName": "index", - "file": "index.tsx", - "id": "index", - "index": true, - "layout": false, - "parentId": "layout", - "path": undefined, - }, "layout": { "componentName": "layout", "file": "layout.tsx", @@ -325,10 +316,10 @@ exports[`generateRouteManifest function > nested-routes 1`] = ` "parentId": undefined, "path": "a/b/c", }, - "d.e.f": { + "d/e/f": { "componentName": "d.e.f", "file": "d.e.f.tsx", - "id": "d.e.f", + "id": "d/e/f", "index": undefined, "layout": false, "parentId": undefined, @@ -339,10 +330,10 @@ exports[`generateRouteManifest function > nested-routes 1`] = ` exports[`generateRouteManifest function > splat-routes 1`] = ` { - "$": { + "*": { "componentName": "$", "file": "$.tsx", - "id": "$", + "id": "*", "index": undefined, "layout": false, "parentId": "layout", diff --git a/packages/route-manifest/tests/__snapshots__/parseRoute.spec.ts.snap b/packages/route-manifest/tests/__snapshots__/parseRoute.spec.ts.snap index 64a09399e9..0ed2631be3 100644 --- a/packages/route-manifest/tests/__snapshots__/parseRoute.spec.ts.snap +++ b/packages/route-manifest/tests/__snapshots__/parseRoute.spec.ts.snap @@ -5,7 +5,7 @@ exports[`parseRoute function > basic-routes 1`] = ` { "componentName": "index", "file": "index.tsx", - "id": "index", + "id": "/", "index": true, "layout": false, "parentId": undefined, @@ -19,7 +19,7 @@ exports[`parseRoute function > nested layout 1`] = ` { "componentName": "layout", "file": "layout.tsx", - "id": "layout", + "id": "/layout", "index": undefined, "layout": true, "parentId": undefined, @@ -28,37 +28,37 @@ exports[`parseRoute function > nested layout 1`] = ` { "componentName": "home", "file": "home.tsx", - "id": "home", + "id": "/home", "index": undefined, "layout": true, - "parentId": "layout", + "parentId": "/layout", "path": "home", }, { "componentName": "home1", "file": "home1.tsx", - "id": "home1", + "id": "/home/1", "index": undefined, "layout": false, - "parentId": "home", + "parentId": "/home", "path": "1", }, { "componentName": "home2", "file": "home2.tsx", - "id": "home2", + "id": "/home/2", "index": undefined, "layout": false, - "parentId": "home", + "parentId": "/home", "path": "2", }, { "componentName": "about", "file": "about.tsx", - "id": "about", + "id": "/about", "index": undefined, "layout": false, - "parentId": "layout", + "parentId": "/layout", "path": "about", }, ] @@ -69,7 +69,7 @@ exports[`parseRoute function > with layout 1`] = ` { "componentName": "layout", "file": "layout.tsx", - "id": "layout", + "id": "/layout", "index": undefined, "layout": true, "parentId": undefined, @@ -78,28 +78,28 @@ exports[`parseRoute function > with layout 1`] = ` { "componentName": "home", "file": "home.tsx", - "id": "home", + "id": "/home", "index": undefined, "layout": false, - "parentId": "layout", + "parentId": "/layout", "path": "home", }, { "componentName": "about", "file": "about.tsx", - "id": "about", + "id": "/about", "index": undefined, "layout": false, - "parentId": "layout", + "parentId": "/layout", "path": "about", }, { "componentName": "index", "file": "index.tsx", - "id": "index", + "id": "/", "index": true, "layout": false, - "parentId": "layout", + "parentId": "/layout", "path": "/", }, ] diff --git a/packages/route-manifest/tests/generateRouteManifest.spec.ts b/packages/route-manifest/tests/generateRouteManifest.spec.ts index 36c7ff4672..d42ac858b5 100644 --- a/packages/route-manifest/tests/generateRouteManifest.spec.ts +++ b/packages/route-manifest/tests/generateRouteManifest.spec.ts @@ -45,9 +45,9 @@ describe('generateRouteManifest function', () => { const routeManifest = generateRouteManifest( path.join(fixturesDir, 'basic-routes'), ['About/index.tsx'], - (defineRoute) => { + [(defineRoute) => { defineRoute('/about-me', 'About/index.tsx'); - }, + }], ); expect(routeManifest).toMatchSnapshot(); }); diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 706406baf7..303b2e0417 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -1,4 +1,4 @@ -import type { ServerResponse } from 'http'; +import type { ServerResponse, IncomingMessage } from 'http'; import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { parsePath } from 'react-router-dom'; @@ -55,9 +55,11 @@ interface Piper { pipe: NodeWritablePiper; fallback: Function; } -interface RenderResult { +interface Response { statusCode?: number; + statusText?: string; value?: string | Piper; + headers?: Record<string, string>; } /** @@ -95,11 +97,11 @@ export async function renderToEntry( export async function renderToHTML( requestContext: ServerContext, renderOptions: RenderOptions, -): Promise<RenderResult> { +): Promise<Response> { const result = await doRender(requestContext, renderOptions); const { value } = result; - if (typeof value === 'string') { + if (typeof value === 'string' || typeof value === 'undefined') { return result; } @@ -110,6 +112,9 @@ export async function renderToHTML( return { value: entryStr, + headers: { + 'Content-Type': 'text/html; charset=utf-8', + }, statusCode: 200, }; } catch (error) { @@ -127,13 +132,13 @@ export async function renderToHTML( * Render and send the result to ServerResponse. */ export async function renderToResponse(requestContext: ServerContext, renderOptions: RenderOptions) { - const { res } = requestContext; + const { req, res } = requestContext; const result = await doRender(requestContext, renderOptions); const { value } = result; - if (typeof value === 'string') { - sendResult(res, result); + if (typeof value === 'string' || typeof value === 'undefined') { + sendResponse(req, res, result); } else { const { pipe, fallback } = value; @@ -152,7 +157,7 @@ export async function renderToResponse(requestContext: ServerContext, renderOpti console.error('PipeToResponse onShellError, downgrade to CSR.'); console.error(err); const result = await fallback(); - sendResult(res, result); + sendResponse(req, res, result); resolve(); }, onError: async (err) => { @@ -169,21 +174,29 @@ export async function renderToResponse(requestContext: ServerContext, renderOpti } } -/** - * Send string result to ServerResponse. - */ -async function sendResult(res: ServerResponse, result: RenderResult) { - res.statusCode = result.statusCode; - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end(result.value); +async function sendResponse( + req: IncomingMessage, + res: ServerResponse, + response: Response, +) { + res.statusCode = response.statusCode; + res.statusMessage = response.statusText; + Object.entries(response.headers || {}).forEach(([name, value]) => { + res.setHeader(name, value); + }); + if (response.value && req.method !== 'HEAD') { + res.end(response.value); + } else { + res.end(); + } } function needRevalidate(matchedRoutes: RouteMatch[]) { return matchedRoutes.some(({ route }) => route.exports.includes('dataLoader') && route.exports.includes('staticDataLoader')); } -async function doRender(serverContext: ServerContext, renderOptions: RenderOptions): Promise<RenderResult> { - const { req } = serverContext; +async function doRender(serverContext: ServerContext, renderOptions: RenderOptions): Promise<Response> { + const { req, res } = serverContext; const { app, basename, @@ -226,6 +239,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio if (runtimeModules.statics) { await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean)); } + // don't need to execute getAppData in CSR if (!documentOnly) { try { @@ -245,7 +259,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio if (documentOnly) { return renderDocument({ matches, routePath, routes, renderOptions }); } else if (!matches.length) { - return render404(); + return handleNotFoundResponse(); } try { @@ -266,6 +280,31 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio if (runtimeModules.commons) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } + /** + Plugin may register response handlers, for example: + ``` + addResponseHandler((req) => { + if (redirect) { + return { + statusCode: 302, + statusText: 'Found', + headers: { + location: '/redirect', + }, + }; + } + }); + ``` + */ + const responseHandlers = runtime.getResponseHandlers(); + for (const responseHandler of responseHandlers) { + if (typeof responseHandler === 'function') { + const response = await responseHandler(req, res); + if (response) { + return response as Response; + } + } + } return await renderServerEntry({ runtime, @@ -283,9 +322,9 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio } // https://github.com/ice-lab/ice-next/issues/133 -function render404(): RenderResult { +function handleNotFoundResponse(): Response { return { - value: 'Not Found', + statusText: 'Not Found', statusCode: 404, }; } @@ -306,7 +345,7 @@ async function renderServerEntry( location, renderOptions, }: RenderServerEntry, -): Promise<RenderResult> { +): Promise<Response> { const { Document } = renderOptions; const appContext = runtime.getAppContext(); const { routes, routePath, loaderData, basename } = appContext; @@ -356,7 +395,7 @@ interface RenderDocumentOptions { /** * Render Document for CSR. */ -function renderDocument(options: RenderDocumentOptions): RenderResult { +function renderDocument(options: RenderDocumentOptions): Response { const { matches, renderOptions, @@ -415,6 +454,9 @@ function renderDocument(options: RenderDocumentOptions): RenderResult { return { value: `<!DOCTYPE html>${htmlStr}`, + headers: { + 'Content-Type': 'text/html; charset=utf-8', + }, statusCode: 200, }; } diff --git a/packages/runtime/src/runtime.tsx b/packages/runtime/src/runtime.tsx index a5b24aaeab..e12636a3c5 100644 --- a/packages/runtime/src/runtime.tsx +++ b/packages/runtime/src/runtime.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import type { ComponentType } from 'react'; +import { routerHistory as history } from './history.js'; import type { Renderer, AppContext, @@ -15,6 +16,7 @@ import type { SetRender, AppRouterProps, ComponentWithChildren, + ResponseHandler, } from './types.js'; import { useData, useConfig } from './RouteContext.js'; import { useAppContext } from './AppContext.js'; @@ -32,6 +34,8 @@ class Runtime { private render: Renderer; + private responseHandlers: ResponseHandler[]; + public constructor(appContext: AppContext, runtimeOptions?: Record<string, any>) { this.AppProvider = []; this.appContext = appContext; @@ -42,6 +46,7 @@ class Runtime { }; this.RouteWrappers = []; this.runtimeOptions = runtimeOptions; + this.responseHandlers = []; } public getAppContext = () => { @@ -66,6 +71,8 @@ class Runtime { public async loadModule(module: RuntimePlugin | StaticRuntimePlugin | CommonJsRuntime) { let runtimeAPI: RuntimeAPI = { addProvider: this.addProvider, + addResponseHandler: this.addResponseHandler, + getResponseHandlers: this.getResponseHandlers, getAppRouter: this.getAppRouter, setRender: this.setRender, addWrapper: this.addWrapper, @@ -74,6 +81,7 @@ class Runtime { useData, useConfig, useAppContext, + history, }; const runtimeModule = ((module as CommonJsRuntime).default || module) as RuntimePlugin; @@ -113,6 +121,14 @@ class Runtime { public setAppRouter: SetAppRouter = (AppRouter) => { this.AppRouter = AppRouter; }; + + public addResponseHandler = (handler: ResponseHandler) => { + this.responseHandlers.push(handler); + }; + + public getResponseHandlers = () => { + return this.responseHandlers; + }; } export default Runtime; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 138fcd5468..c1250b114c 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -1,5 +1,5 @@ import type { IncomingMessage, ServerResponse } from 'http'; -import type { InitialEntry, AgnosticRouteObject, Location } from '@remix-run/router'; +import type { InitialEntry, AgnosticRouteObject, Location, History } from '@remix-run/router'; import type { ComponentType, PropsWithChildren } from 'react'; import type { HydrationOptions, Root } from 'react-dom/client'; import type { Params, RouteObject } from 'react-router-dom'; @@ -154,12 +154,18 @@ export interface RouteWrapperConfig { export type AppProvider = ComponentWithChildren<any>; export type RouteWrapper = ComponentType<any>; +export type ResponseHandler = ( + req: IncomingMessage, + res: ServerResponse, +) => any | Promise<any>; export type SetAppRouter = (AppRouter: ComponentType<AppRouterProps>) => void; export type GetAppRouter = () => AppProvider; export type AddProvider = (Provider: AppProvider) => void; export type SetRender = (render: Renderer) => void; export type AddWrapper = (wrapper: RouteWrapper, forLayout?: boolean) => void; +export type AddResponseHandler = (handler: ResponseHandler) => void; +export type GetResponseHandlers = () => ResponseHandler[]; export interface RouteModules { [routeId: string]: ComponentModule; @@ -183,12 +189,15 @@ export interface RuntimeAPI { setAppRouter?: SetAppRouter; getAppRouter: GetAppRouter; addProvider: AddProvider; + addResponseHandler: AddResponseHandler; + getResponseHandlers: GetResponseHandlers; setRender: SetRender; addWrapper: AddWrapper; appContext: AppContext; useData: UseData; useConfig: UseConfig; useAppContext: UseAppContext; + history: History; } export interface StaticRuntimeAPI { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1dcaf5c4f4..fd7d4fd3e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -475,6 +475,29 @@ importers: '@types/react': 18.0.28 '@types/react-dom': 18.0.11 + examples/with-antd5: + specifiers: + '@ice/app': workspace:* + '@ice/runtime': workspace:* + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.2 + antd: ^5.0.0 + dayjs: ^1.11.7 + react: ^18.0.0 + react-dom: ^18.0.0 + react-intl: ^6.3.2 + dependencies: + '@ice/app': link:../../packages/ice + '@ice/runtime': link:../../packages/runtime + antd: 5.4.1_biqbaboplfbrettd7655fr4n2y + dayjs: 1.11.7 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-intl: 6.3.2_react@18.2.0 + devDependencies: + '@types/react': 18.0.34 + '@types/react-dom': 18.0.11 + examples/with-auth: specifiers: '@ice/app': workspace:* @@ -541,12 +564,14 @@ importers: moment: ^2.29.4 react: ^18.0.0 react-dom: ^18.0.0 + react-intl: ^6.3.2 dependencies: '@alifd/next': 1.26.2_jb42yyeu5qxbfieyxjks6malva '@ice/runtime': link:../../packages/runtime moment: 2.29.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + react-intl: 6.3.2_react@18.2.0 devDependencies: '@ice/app': link:../../packages/ice '@ice/plugin-css-assets-local': link:../../packages/plugin-css-assets-local @@ -555,6 +580,35 @@ importers: '@types/react': 18.0.28 '@types/react-dom': 18.0.11 + examples/with-i18n: + specifiers: + '@ice/app': workspace:* + '@ice/plugin-i18n': workspace:* + '@ice/runtime': workspace:* + '@types/express': ^4.17.14 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.2 + express: ^4.17.3 + react: ^18.0.0 + react-dom: ^18.0.0 + react-intl: ^6.3.2 + tslib: ^2.5.0 + tsx: ^3.12.1 + dependencies: + '@ice/runtime': link:../../packages/runtime + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-intl: 6.3.2_react@18.2.0 + devDependencies: + '@ice/app': link:../../packages/ice + '@ice/plugin-i18n': link:../../packages/plugin-i18n + '@types/express': 4.17.17 + '@types/react': 18.0.34 + '@types/react-dom': 18.0.11 + express: 4.18.2 + tslib: 2.5.0 + tsx: 3.12.3 + examples/with-jest: specifiers: '@ice/app': workspace:* @@ -1191,6 +1245,35 @@ importers: devDependencies: '@ice/app': link:../ice + packages/plugin-i18n: + specifiers: + '@ice/app': workspace:^ + '@ice/jsx-runtime': ^0.2.0 + '@ice/pkg': ^1.0.0 + '@ice/runtime': workspace:^ + '@remix-run/router': ^1.5.0 + '@swc/helpers': ^0.4.14 + '@types/accept-language-parser': ^1.5.3 + '@types/react': ^18.0.33 + accept-language-parser: ^1.5.0 + universal-cookie: ^4.0.4 + url-join: ^5.0.0 + webpack-dev-server: ^4.13.2 + dependencies: + '@ice/jsx-runtime': link:../jsx-runtime + '@swc/helpers': 0.4.14 + accept-language-parser: 1.5.0 + universal-cookie: 4.0.4 + url-join: 5.0.0 + devDependencies: + '@ice/app': link:../ice + '@ice/pkg': 1.5.5 + '@ice/runtime': link:../runtime + '@remix-run/router': 1.5.0 + '@types/accept-language-parser': 1.5.3 + '@types/react': 18.0.34 + webpack-dev-server: 4.13.2 + packages/plugin-icestark: specifiers: '@ice/app': ^3.1.5 @@ -1351,8 +1434,8 @@ importers: packages/plugin-store: specifiers: - '@ice/app': ^3.1.2 - '@ice/runtime': ^1.1.3 + '@ice/app': workspace:^ + '@ice/runtime': workspace:^ '@ice/store': ^2.0.3 '@types/micromatch': ^4.0.2 '@types/react': ^18.0.0 @@ -1717,6 +1800,29 @@ packages: '@ctrl/tinycolor': 3.6.0 dev: false + /@ant-design/colors/7.0.0: + resolution: {integrity: sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==} + dependencies: + '@ctrl/tinycolor': 3.6.0 + dev: false + + /@ant-design/cssinjs/1.8.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-pOQJV9H9viB6qB9u7hkpKEOIQGx4dd8zjpwzF1v8YNwjffbZTlyUNQYln56gwpFF7SFskpYpnSfgoqTK4sFE/Q==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.3.2 + csstype: 3.1.1 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + stylis: 4.1.3 + dev: false + /@ant-design/icons-svg/4.2.1: resolution: {integrity: sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==} dev: false @@ -1737,6 +1843,22 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@ant-design/icons/5.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-ZyF4ksXCcdtwA/1PLlnFLcF/q8/MhwxXhKHh4oCHDA4Ip+ZzAHoICtyp4wZWfiCVDP0yuz3HsjyvuldHFb3wjA==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@ant-design/colors': 7.0.0 + '@ant-design/icons-svg': 4.2.1 + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@ant-design/react-slick/0.29.2_react@18.2.0: resolution: {integrity: sha512-kgjtKmkGHa19FW21lHnAfyyH9AAoh35pBdcJ53rHmQ3O+cfFHGHnUbj/HFrRNJ5vIts09FKJVAD8RpaC+RaWfA==} peerDependencies: @@ -1750,6 +1872,19 @@ packages: resize-observer-polyfill: 1.5.1 dev: false + /@ant-design/react-slick/1.0.0_react@18.2.0: + resolution: {integrity: sha512-OKxZsn8TAf8fYxP79rDXgLs9zvKMTslK6dJ4iLhDXOujUqC5zJPBRszyrcEHXcMPOm1Sgk40JgyF3yiL/Swd7w==} + peerDependencies: + react: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + json2mq: 0.2.0 + react: 18.2.0 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.0 + dev: false + /@applint/commitlint-config/1.0.2: resolution: {integrity: sha512-Je7RbW+7sQSLRq3Axfv8rPvO3SBRSgJVeYhNLisMuBDMNx/VWoFV4aX7ozZW5MUmvdFv8fVc1TNtyi5MfNflmg==} dependencies: @@ -2054,7 +2189,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.18.10 /@babel/helper-plugin-utils/7.10.4: resolution: {integrity: sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==} @@ -3769,7 +3904,7 @@ packages: wait-on: 6.0.1_debug@4.3.4 webpack: 5.76.2 webpack-bundle-analyzer: 4.8.0 - webpack-dev-server: 4.11.1_debug@4.3.4+webpack@5.76.2 + webpack-dev-server: 4.13.2_debug@4.3.4+webpack@5.76.2 webpack-merge: 5.8.0 webpackbar: 5.0.2_webpack@5.76.2 transitivePeerDependencies: @@ -3869,7 +4004,7 @@ packages: wait-on: 6.0.1 webpack: 5.76.2 webpack-bundle-analyzer: 4.8.0 - webpack-dev-server: 4.11.1_webpack@5.76.2 + webpack-dev-server: 4.13.2_webpack@5.76.2 webpack-merge: 5.8.0 webpackbar: 5.0.2_webpack@5.76.2 transitivePeerDependencies: @@ -3913,8 +4048,8 @@ packages: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 dependencies: - '@babel/parser': 7.21.2 - '@babel/traverse': 7.21.2 + '@babel/parser': 7.18.10 + '@babel/traverse': 7.18.10 '@docusaurus/logger': 2.3.1 '@docusaurus/utils': 2.3.1_@docusaurus+types@2.3.1 '@mdx-js/mdx': 1.6.22 @@ -3949,7 +4084,7 @@ packages: '@docusaurus/react-loadable': 5.5.2_react@17.0.2 '@docusaurus/types': 2.3.1_sfoxds7t5ydpegc3knd667wn6m '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 '@types/react-router-config': 5.0.6 '@types/react-router-dom': 5.3.3 react: 17.0.2 @@ -4335,7 +4470,7 @@ packages: peerDependencies: react: '*' dependencies: - '@types/react': 18.0.28 + '@types/react': 18.0.34 prop-types: 15.8.1 react: 17.0.2 @@ -4405,7 +4540,7 @@ packages: '@docusaurus/plugin-content-pages': 2.3.1_jgxnvbe4faw3ohf4h6p42qq6oy '@docusaurus/utils': 2.3.1_@docusaurus+types@2.3.1 '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 '@types/react-router-config': 5.0.6 clsx: 1.2.1 parse-numeric-range: 1.3.0 @@ -4494,7 +4629,7 @@ packages: react-dom: ^16.8.4 || ^17.0.0 dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 commander: 5.1.0 joi: 17.8.3 react: 17.0.2 @@ -4623,6 +4758,14 @@ packages: - webpack-cli dev: false + /@emotion/hash/0.8.0: + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + dev: false + + /@emotion/unitless/0.7.5: + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + dev: false + /@esbuild-kit/cjs-loader/2.4.2: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} dependencies: @@ -5077,6 +5220,73 @@ packages: '@floating-ui/core': 1.2.2 dev: false + /@formatjs/ecma402-abstract/1.14.3: + resolution: {integrity: sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==} + dependencies: + '@formatjs/intl-localematcher': 0.2.32 + tslib: 2.5.0 + dev: false + + /@formatjs/fast-memoize/2.0.1: + resolution: {integrity: sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==} + dependencies: + tslib: 2.5.0 + dev: false + + /@formatjs/icu-messageformat-parser/2.3.0: + resolution: {integrity: sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==} + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/icu-skeleton-parser': 1.3.18 + tslib: 2.5.0 + dev: false + + /@formatjs/icu-skeleton-parser/1.3.18: + resolution: {integrity: sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==} + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + tslib: 2.5.0 + dev: false + + /@formatjs/intl-displaynames/6.2.6: + resolution: {integrity: sha512-scf5AQTk9EjpvPhboo5sizVOvidTdMOnajv9z+0cejvl7JNl9bl/aMrNBgC72UH+bP3l45usPUKAGskV6sNIrA==} + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/intl-localematcher': 0.2.32 + tslib: 2.5.0 + dev: false + + /@formatjs/intl-listformat/7.1.9: + resolution: {integrity: sha512-5YikxwRqRXTVWVujhswDOTCq6gs+m9IcNbNZLa6FLtyBStAjEsuE2vAU+lPsbz9ZTST57D5fodjIh2JXT6sMWQ==} + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/intl-localematcher': 0.2.32 + tslib: 2.5.0 + dev: false + + /@formatjs/intl-localematcher/0.2.32: + resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==} + dependencies: + tslib: 2.5.0 + dev: false + + /@formatjs/intl/2.6.9: + resolution: {integrity: sha512-EtcMZ9O24YSASu/jGOaTQtArx7XROjlKiO4KmkxJ/3EyAQLCr5hrS+KKvNud0a7GIwBucOb3IFrZ7WiSm2A/Cw==} + peerDependencies: + typescript: ^4.7 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/fast-memoize': 2.0.1 + '@formatjs/icu-messageformat-parser': 2.3.0 + '@formatjs/intl-displaynames': 6.2.6 + '@formatjs/intl-listformat': 7.1.9 + intl-messageformat: 10.3.3 + tslib: 2.5.0 + dev: false + /@hapi/hoek/9.3.0: resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -5149,6 +5359,48 @@ packages: - ts-node dev: true + /@ice/pkg/1.5.5: + resolution: {integrity: sha512-0BIfv6Uzs2wpHv7RmFwz+kWfoJLfx0yJrQyh3yqy+F6TZWxTwrqQmX+5yRmgqK5f7lGGhYfMMVNWjRSCw5MHPQ==} + engines: {node: '>=16.14.0'} + hasBin: true + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/core': 7.21.0 + '@babel/parser': 7.18.10 + '@babel/preset-react': 7.18.6_@babel+core@7.21.0 + '@babel/preset-typescript': 7.21.0_@babel+core@7.21.0 + '@rollup/plugin-commonjs': 21.1.0_rollup@2.79.1 + '@rollup/plugin-image': 3.0.2_rollup@2.79.1 + '@rollup/plugin-json': 4.1.0_rollup@2.79.1 + '@rollup/plugin-node-resolve': 13.3.0_rollup@2.79.1 + '@rollup/plugin-replace': 5.0.2_rollup@2.79.1 + '@rollup/pluginutils': 4.2.1 + '@swc/core': 1.3.32 + acorn: 8.8.2 + autoprefixer: 10.4.13_postcss@8.4.21 + build-scripts: 2.1.0 + cac: 6.7.14 + chokidar: 3.5.3 + consola: 2.15.3 + debug: 4.3.4 + deepmerge: 4.3.0 + escape-string-regexp: 5.0.0 + fs-extra: 10.1.0 + globby: 11.1.0 + gzip-size: 7.0.0 + lodash.merge: 4.6.2 + magic-string: 0.25.9 + picocolors: 1.0.0 + postcss: 8.4.21 + rollup: 2.79.1 + rollup-plugin-styles: 4.0.0_rollup@2.79.1 + rollup-plugin-visualizer: 5.9.0_rollup@2.79.1 + tsc-alias: 1.8.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@ice/sandbox/1.1.4: resolution: {integrity: sha512-MEVF0Ze3McKDutnFiUAhUoc+WwOFxITVBgSSHmbGpKtWbXJX9kUVlx3VsEVJvdqU3O1kiBNx6zE1sFMjKPRTIQ==} dev: false @@ -5967,6 +6219,18 @@ packages: /@polka/url/1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + /@rc-component/context/1.3.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-6QdaCJ7Wn5UZLJs15IEfqy4Ru3OaL5ctqpQYWd5rlfV9wwzrzdt6+kgAQZV/qdB0MUPN4nhyBfRembQCIvBf+w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + rc-util: 5.29.3_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@rc-component/mini-decimal/1.0.1: resolution: {integrity: sha512-9N8nRk0oKj1qJzANKl+n9eNSMUGsZtjwNuDCiZ/KA+dt1fE3zq5x2XxclRcAbOIXnZcJ53ozP2Pa60gyELXagA==} engines: {node: '>=8.x'} @@ -5974,6 +6238,20 @@ packages: '@babel/runtime': 7.21.0 dev: false + /@rc-component/mutate-observer/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-okqRJSfNisXdI6CUeOLZC5ukBW/8kir2Ii4PJiKpUt+3+uS7dxwJUMxsUZquxA1rQuL8YcEmKVp/TCnR+yUdZA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@rc-component/portal/1.1.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-tbXM9SB1r5FOuZjRCljERFByFiEUcMmCWMXLog/NmgCzlAzreXyf23Vei3ZpSMxSMavzPnhCovfZjZdmxS3d1w==} engines: {node: '>=8.x'} @@ -5988,6 +6266,40 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@rc-component/tour/1.8.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-rrRGioHTLQlGca27G2+lw7QpRb3uuMYCUIJjj31/B44VCJS0P2tqYhOgtzvWQmaLMlWH3ZlpzotkKX13NT4XEA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/portal': 1.1.0_biqbaboplfbrettd7655fr4n2y + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@rc-component/trigger/1.8.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-O9d4Tlg/FiCUlkQ+aAUUO5KmrBbj4XYq6qYfZE/hvNHzIepHqwLGx8H/d+1fG13dVPq70nGDf5ha9PQ96YRMVg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/portal': 1.1.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-align: 4.0.15_biqbaboplfbrettd7655fr4n2y + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-resize-observer: 1.3.1_biqbaboplfbrettd7655fr4n2y + rc-util: 5.29.3_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@react-spring/animated/9.6.1_react@18.2.0: resolution: {integrity: sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==} peerDependencies: @@ -6104,6 +6416,29 @@ packages: rollup: 2.79.1 dev: true + /@rollup/plugin-image/3.0.2_rollup@2.79.1: + resolution: {integrity: sha512-eGVrD6lummWH5ENo9LWX3JY62uBb9okUNQ2htXkugrG6WjACrMUVhWvss+0wW3fwJWmFYpoEny3yL4spEdh15g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@2.79.1 + mini-svg-data-uri: 1.4.4 + rollup: 2.79.1 + dev: true + + /@rollup/plugin-json/4.1.0_rollup@2.79.1: + resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + rollup: 2.79.1 + dev: true + /@rollup/plugin-node-resolve/13.3.0_rollup@2.79.1: resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==} engines: {node: '>= 10.0.0'} @@ -6119,6 +6454,20 @@ packages: rollup: 2.79.1 dev: true + /@rollup/plugin-replace/5.0.2_rollup@2.79.1: + resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@2.79.1 + magic-string: 0.27.0 + rollup: 2.79.1 + dev: true + /@rollup/pluginutils/3.1.0_rollup@2.79.1: resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} @@ -6138,12 +6487,27 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 - /@sideway/address/4.1.4: - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + /@rollup/pluginutils/5.0.2_rollup@2.79.1: + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - '@hapi/hoek': 9.3.0 - - /@sideway/formula/3.0.1: + '@types/estree': 1.0.0 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: true + + /@sideway/address/4.1.4: + resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@sideway/formula/3.0.1: resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} /@sideway/pinpoint/2.0.0: @@ -6448,6 +6812,15 @@ packages: requiresBuild: true optional: true + /@swc/core-darwin-arm64/1.3.32: + resolution: {integrity: sha512-o19bhlxuUgjUElm6i+QhXgZ0vD6BebiB/gQpK3en5aAwhOvinwr4sah3GqFXsQzz/prKVDuMkj9SW6F/Ug5hgg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@swc/core-darwin-x64/1.3.19: resolution: {integrity: sha512-qCDQcngYBeWrsNS1kcBslRD0dahKcYKaUUWRC9yHpRcs3SRvnSpJyWQR4y9RCdO9YNmixJ9+5+zPD9qcgL7jBw==} engines: {node: '>=10'} @@ -6456,6 +6829,15 @@ packages: requiresBuild: true optional: true + /@swc/core-darwin-x64/1.3.32: + resolution: {integrity: sha512-hVEGd+v5Afh+YekGADOGKwhuS4/AXk91nLuk7pmhWkk8ceQ1cfmah90kXjIXUlCe2G172MLRfHNWlZxr29E/Og==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm-gnueabihf/1.3.19: resolution: {integrity: sha512-ufbKW6Lhii1+kVCXnsHgqYIpRvXhPjdhMudfP4KKVgJtT6TsdEIr+KRAQIBHLjRUsTKA2DLsGEpu9jfjwFiNEg==} engines: {node: '>=10'} @@ -6464,6 +6846,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm-gnueabihf/1.3.32: + resolution: {integrity: sha512-5X01WqI9EbJ69oHAOGlI08YqvEIXMfT/mCJ1UWDQBb21xWRE2W1yFAAeuqOLtiagLrXjPv/UKQ0S2gyWQR5AXQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm64-gnu/1.3.19: resolution: {integrity: sha512-HHhqLRZv9Ss8orJrlEP4XRcLuqLDwFtGgbtHU8kyWBmQEtK42uT18Pf5RJBo5sPJHY8m5EO8C8y3hIbGmKtLyg==} engines: {node: '>=10'} @@ -6472,6 +6863,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-gnu/1.3.32: + resolution: {integrity: sha512-PTJ6oPiutkNBg+m22bUUPa4tNuMmsgpSnsnv2wnWVOgK0lhvQT6bAPTUXDq/8peVAgR/SlpP2Ht8TRRqYMRjRQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm64-musl/1.3.19: resolution: {integrity: sha512-vipnF3C6T1368uHQqz8RpdszWxxGh0X8VBK3TdTOSWvI/duNZtZXEOZlB2Nh9w+u09umVw0MsJhvg86Aon39mA==} engines: {node: '>=10'} @@ -6480,6 +6880,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-musl/1.3.32: + resolution: {integrity: sha512-lG0VOuYNPWOCJ99Aza69cTljjeft/wuRQeYFF8d+1xCQS/OT7gnbgi7BOz39uSHIPTBqfzdIsuvzdKlp9QydrQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-x64-gnu/1.3.19: resolution: {integrity: sha512-dUbq8mnIqBhU7OppfY3ncOvl26691WFGxd97QtnnlfMZrKnaofKFMIxE9sTHOLSbBo16AylnEMiwa45w2UWDEg==} engines: {node: '>=10'} @@ -6488,6 +6897,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-gnu/1.3.32: + resolution: {integrity: sha512-ecqtSWX4NBrs7Ji2VX3fDWeqUfrbLlYqBuufAziCM27xMxwlAVgmyGQk4FYgoQ3SAUAu3XFH87+3Q7uWm2X7xg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-x64-musl/1.3.19: resolution: {integrity: sha512-RiVZrlkNGcj9jZyjF7YFOW3fj9fWPC25AYkknLpWxAmLQcp1piAWj+aSixmMWUC4QJau78VZzcm+kRgIOECALw==} engines: {node: '>=10'} @@ -6496,6 +6914,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-musl/1.3.32: + resolution: {integrity: sha512-rl3dMcUuENVkpk5NGW/LXovjK0+JFm4GWPjy4NM3Q5cPvhBpGwSeLZlR+zAw9K0fdGoIXiayRTTfENrQwwsH+g==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-arm64-msvc/1.3.19: resolution: {integrity: sha512-r2U6GC+go2iiLx5JBZIJswYFiMv0yOsm+pgE1srVvAc8dP02320t9yh0Uj4Sr2hDipTWJ33Y5PMZwEsZSfBVbQ==} engines: {node: '>=10'} @@ -6504,6 +6931,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-arm64-msvc/1.3.32: + resolution: {integrity: sha512-VlybAZp8DcS66CH1LDnfp9zdwbPlnGXREtHDMHaBfK9+80AWVTg+zn0tCYz+HfcrRONqxbudwOUIPj+dwl/8jw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-ia32-msvc/1.3.19: resolution: {integrity: sha512-SPpESDa4vr0PRvUiqXSi8oZSTmkDOGrZ/pSiLD7ISgjsQ5RQMbPkuEK0ztWljim87q2fO0bGVVhyaVYxdOVS1A==} engines: {node: '>=10'} @@ -6512,6 +6948,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-ia32-msvc/1.3.32: + resolution: {integrity: sha512-MEUMdpUFIQ+RD+K/iHhHKfu0TFNj9VXwIxT5hmPeqyboKo095CoFEFBJ0sHG04IGlnu8T9i+uE2Pi18qUEbFug==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-x64-msvc/1.3.19: resolution: {integrity: sha512-0X5HqFC1wQlheOQDZeF6KNOSURZKkGISNK3aTSmTq9g7dDJ/kTcVjsdKbu2rK4ibCnlC9IS0cLK9FpROnsVPwA==} engines: {node: '>=10'} @@ -6520,6 +6965,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-x64-msvc/1.3.32: + resolution: {integrity: sha512-DPMoneNFQco7SqmVVOUv1Vn53YmoImEfrAPMY9KrqQzgfzqNTuL2JvfxUqfAxwQ6pEKYAdyKJvZ483rIhgG9XQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core/1.3.19: resolution: {integrity: sha512-KiXUv2vpmOaGhoLCN9Rw7Crsfq1YmOR2ZbajiqNAh/iu0d3CKn5JZhLRs6S7nCk78cwFFac2obQfTWPePLUe/g==} engines: {node: '>=10'} @@ -6537,6 +6991,23 @@ packages: '@swc/core-win32-ia32-msvc': 1.3.19 '@swc/core-win32-x64-msvc': 1.3.19 + /@swc/core/1.3.32: + resolution: {integrity: sha512-Yx/n1j+uUkcqlJAW8IRg8Qymgkdow6NHJZPFShiR0YiaYq2sXY+JHmvh16O6GkL91Y+gTlDUS7uVgDz50czJUQ==} + engines: {node: '>=10'} + requiresBuild: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.32 + '@swc/core-darwin-x64': 1.3.32 + '@swc/core-linux-arm-gnueabihf': 1.3.32 + '@swc/core-linux-arm64-gnu': 1.3.32 + '@swc/core-linux-arm64-musl': 1.3.32 + '@swc/core-linux-x64-gnu': 1.3.32 + '@swc/core-linux-x64-musl': 1.3.32 + '@swc/core-win32-arm64-msvc': 1.3.32 + '@swc/core-win32-ia32-msvc': 1.3.32 + '@swc/core-win32-x64-msvc': 1.3.32 + dev: true + /@swc/helpers/0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: @@ -6617,6 +7088,10 @@ packages: resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} dev: true + /@types/accept-language-parser/1.5.3: + resolution: {integrity: sha512-S8oM29O6nnRC3/+rwYV7GBYIIgNIZ52PCxqBG7OuItq9oATnYWy8FfeLKwvq5F7pIYjeeBSCI7y+l+Z9UEQpVQ==} + dev: true + /@types/aria-query/5.0.1: resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} dev: true @@ -6654,7 +7129,7 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.14.6 + '@types/node': 17.0.45 /@types/bonjour/3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} @@ -6684,7 +7159,11 @@ packages: /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.14.6 + '@types/node': 17.0.45 + + /@types/cookie/0.3.3: + resolution: {integrity: sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==} + dev: false /@types/cross-spawn/6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} @@ -6692,6 +7171,15 @@ packages: '@types/node': 18.14.6 dev: true + /@types/cssnano/5.1.0_postcss@8.4.21: + resolution: {integrity: sha512-ikR+18UpFGgvaWSur4og6SJYF/6QEYHXvrIt36dp81p1MG3cAPTYDMBJGeyWa3LCnqEbgNMHKRb+FP0NrXtoWQ==} + deprecated: This is a stub types definition. cssnano provides its own type definitions, so you do not need this installed. + dependencies: + cssnano: 5.1.15_postcss@8.4.21 + transitivePeerDependencies: + - postcss + dev: true + /@types/detect-indent/0.1.30: resolution: {integrity: sha512-AUmj9JHuHTD94slY1WR1VulFxRGC6D1pcNCN0MCulKFyiihvV/28lLS8oRHgfmc2Cxq954J8Vmosa8qzm7PLGQ==} dev: true @@ -6732,7 +7220,7 @@ packages: /@types/express-serve-static-core/4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 18.14.6 + '@types/node': 17.0.45 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 @@ -6781,7 +7269,7 @@ packages: /@types/hoist-non-react-statics/3.3.1: resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} dependencies: - '@types/react': 18.0.28 + '@types/react': 18.0.34 hoist-non-react-statics: 3.3.2 dev: false @@ -6954,7 +7442,7 @@ packages: resolution: {integrity: sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==} dependencies: '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.0.28 + '@types/react': 18.0.34 hoist-non-react-statics: 3.3.2 redux: 4.2.1 dev: false @@ -6963,21 +7451,21 @@ packages: resolution: {integrity: sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 '@types/react-router': 5.1.20 /@types/react-router-dom/5.3.3: resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 '@types/react-router': 5.1.20 /@types/react-router/5.1.20: resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.0.28 + '@types/react': 18.0.34 /@types/react/17.0.53: resolution: {integrity: sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==} @@ -6992,6 +7480,14 @@ packages: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.2 csstype: 3.1.1 + dev: true + + /@types/react/18.0.34: + resolution: {integrity: sha512-NO1UO8941541CJl1BeOXi8a9dNKFK09Gnru5ZJqkm4Q3/WoQJtHvmwt0VX0SB9YCEwe7TfSSxDuaNmx6H2BAIQ==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -7033,7 +7529,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 18.14.6 + '@types/node': 17.0.45 /@types/sockjs/0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} @@ -7416,6 +7912,10 @@ packages: event-target-shim: 5.0.1 dev: true + /accept-language-parser/1.5.0: + resolution: {integrity: sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw==} + dev: false + /accepts/1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -7754,6 +8254,68 @@ packages: scroll-into-view-if-needed: 2.2.31 dev: false + /antd/5.4.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-10bZn2fj2ihhBSrwTzhnuFvhLdK1LTVs2GScqhEIDHh+s93Zl7dF27ZNSvth3BUoozoLFjifSVtBbU0dCsd5lw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@ant-design/colors': 7.0.0 + '@ant-design/cssinjs': 1.8.1_biqbaboplfbrettd7655fr4n2y + '@ant-design/icons': 5.0.1_biqbaboplfbrettd7655fr4n2y + '@ant-design/react-slick': 1.0.0_react@18.2.0 + '@babel/runtime': 7.21.0 + '@ctrl/tinycolor': 3.6.0 + '@rc-component/mutate-observer': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@rc-component/tour': 1.8.0_biqbaboplfbrettd7655fr4n2y + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.7 + qrcode.react: 3.1.0_react@18.2.0 + rc-cascader: 3.10.1_biqbaboplfbrettd7655fr4n2y + rc-checkbox: 3.0.0_biqbaboplfbrettd7655fr4n2y + rc-collapse: 3.5.2_biqbaboplfbrettd7655fr4n2y + rc-dialog: 9.1.0_biqbaboplfbrettd7655fr4n2y + rc-drawer: 6.1.3_biqbaboplfbrettd7655fr4n2y + rc-dropdown: 4.0.1_biqbaboplfbrettd7655fr4n2y + rc-field-form: 1.29.2_biqbaboplfbrettd7655fr4n2y + rc-image: 5.16.0_biqbaboplfbrettd7655fr4n2y + rc-input: 1.0.4_biqbaboplfbrettd7655fr4n2y + rc-input-number: 7.4.2_biqbaboplfbrettd7655fr4n2y + rc-mentions: 2.2.0_biqbaboplfbrettd7655fr4n2y + rc-menu: 9.8.4_biqbaboplfbrettd7655fr4n2y + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-notification: 5.0.3_biqbaboplfbrettd7655fr4n2y + rc-pagination: 3.3.1_biqbaboplfbrettd7655fr4n2y + rc-picker: 3.5.1_mlnkrlbros4rghcauwy625gk7y + rc-progress: 3.4.1_biqbaboplfbrettd7655fr4n2y + rc-rate: 2.10.0_biqbaboplfbrettd7655fr4n2y + rc-resize-observer: 1.3.1_biqbaboplfbrettd7655fr4n2y + rc-segmented: 2.1.2_biqbaboplfbrettd7655fr4n2y + rc-select: 14.4.3_biqbaboplfbrettd7655fr4n2y + rc-slider: 10.1.1_biqbaboplfbrettd7655fr4n2y + rc-steps: 6.0.0_biqbaboplfbrettd7655fr4n2y + rc-switch: 4.0.0_biqbaboplfbrettd7655fr4n2y + rc-table: 7.31.1_biqbaboplfbrettd7655fr4n2y + rc-tabs: 12.5.7_biqbaboplfbrettd7655fr4n2y + rc-textarea: 1.2.2_biqbaboplfbrettd7655fr4n2y + rc-tooltip: 6.0.1_biqbaboplfbrettd7655fr4n2y + rc-tree: 5.7.2_biqbaboplfbrettd7655fr4n2y + rc-tree-select: 5.8.0_biqbaboplfbrettd7655fr4n2y + rc-trigger: 5.3.4_biqbaboplfbrettd7655fr4n2y + rc-upload: 4.3.4_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + scroll-into-view-if-needed: 3.0.10 + throttle-debounce: 5.0.0 + transitivePeerDependencies: + - date-fns + - luxon + - moment + dev: false + /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -8976,6 +9538,10 @@ packages: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} dev: false + /compute-scroll-into-view/3.0.3: + resolution: {integrity: sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==} + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -9073,6 +9639,11 @@ packages: /cookie-signature/1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + /cookie/0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + /cookie/0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -9724,7 +10295,6 @@ packages: /decode-uri-component/0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} - dev: false /decompress-response/3.3.0: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} @@ -11448,6 +12018,11 @@ packages: dependencies: to-regex-range: 5.0.1 + /filter-obj/1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + dev: true + /finalhandler/1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} @@ -12644,6 +13219,15 @@ packages: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} dev: false + /intl-messageformat/10.3.3: + resolution: {integrity: sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==} + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/fast-memoize': 2.0.1 + '@formatjs/icu-messageformat-parser': 2.3.0 + tslib: 2.5.0 + dev: false + /invariant/2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -14255,6 +14839,12 @@ packages: dependencies: package-json: 6.5.0 + /launch-editor/2.6.0: + resolution: {integrity: sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==} + dependencies: + picocolors: 1.0.0 + shell-quote: 1.8.0 + /less-loader/10.2.0_less@4.1.2+webpack@5.76.2: resolution: {integrity: sha512-AV5KHWvCezW27GT90WATaDnfXBv99llDbtaj4bshq6DvAihMdNjaPDcUMa6EXKLRF+P2opFenJp89BXg91XLYg==} engines: {node: '>= 12.13.0'} @@ -14802,6 +15392,11 @@ packages: schema-utils: 4.0.0 webpack: 5.76.2 + /mini-svg-data-uri/1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + dev: true + /miniapp-history/0.1.7: resolution: {integrity: sha512-q/+f8ncjeyDvPahMLEeknvJiKcVwZLVNDm3tNeB4o8sxJxoQbHIaStJ9SpQkbdhJn971kmoUQyH8aH26O7OvIw==} dependencies: @@ -14935,6 +15530,11 @@ packages: /mute-stream/0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + /mylas/2.1.13: + resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} + engines: {node: '>=12.0.0'} + dev: true + /nanoid/3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -15557,6 +16157,12 @@ packages: dependencies: find-up: 3.0.0 + /plimit-lit/1.5.0: + resolution: {integrity: sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==} + dependencies: + queue-lit: 1.5.0 + dev: true + /portfinder/1.0.32: resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} engines: {node: '>= 0.12.0'} @@ -16973,6 +17579,14 @@ packages: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + /qrcode.react/3.1.0_react@18.2.0: + resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /qs/6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -16984,9 +17598,23 @@ packages: engines: {node: '>=0.6'} dev: false + /query-string/7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + dev: true + /querystringify/2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + /queue-lit/1.5.0: + resolution: {integrity: sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA==} + dev: true + /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -17120,6 +17748,22 @@ packages: resize-observer-polyfill: 1.5.1 dev: false + /rc-cascader/3.10.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-tImBYEAqLlIZ+jnRmfQQEm5gOXa09N9aGV9AKxriXlCvsNEfdZMIRyY0p74sEZIUn0ycXHo8VcOlqsgLcgFknQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + array-tree-filter: 2.1.0 + classnames: 2.3.2 + rc-select: 14.4.3_biqbaboplfbrettd7655fr4n2y + rc-tree: 5.7.2_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-cascader/3.7.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-SFtGpwmYN7RaWEAGTS4Rkc62ZV/qmQGg/tajr/7mfIkleuu8ro9Hlk6J+aA0x1YS4zlaZBtTcSaXM01QMiEV/A==} peerDependencies: @@ -17148,6 +17792,19 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-checkbox/3.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-tOEs1+wWDUei7DuP2EsJCZfam5vxMjKTCGcZdXVgsiOcNszc41Esycbo31P0/jFwUAPmd5oPYFWkcnFUCTLZxA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-collapse/3.4.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-jpTwLgJzkhAgp2Wpi3xmbTbbYExg6fkptL67Uu5LCRVEj6wqmy0DHTjjeynsjOLsppHGHu41t1ELntZ0lEvS/Q==} peerDependencies: @@ -17163,6 +17820,20 @@ packages: shallowequal: 1.1.0 dev: false + /rc-collapse/3.5.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-/TNiT3DW1t3sUCiVD/DPUYooJZ3BLA93/2rZsB3eM2bGJCCla2X9D2E4tgm7LGMQGy5Atb2lMUn2FQuvQNvavQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-dialog/9.0.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-s3U+24xWUuB6Bn2Lk/Qt6rufy+uT+QvWkiFhNBcO9APLxcFFczWamaq7x9h8SCuhfc1nHcW4y8NbMsnAjNnWyg==} peerDependencies: @@ -17178,6 +17849,21 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-dialog/9.1.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-5ry+JABAWEbaKyYsmITtrJbZbJys8CtMyzV8Xn4LYuXMeUx5XVHNyJRoqLFE4AzBuXXzOWeaC49cg+XkxK6kHA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/portal': 1.1.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-drawer/6.1.3_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-AvHisO90A+xMLMKBw2zs89HxjWxusM2BUABlgK60RhweIHF8W/wk0hSOrxBlUXoA9r1F+10na3g6GZ97y1qDZA==} peerDependencies: @@ -17221,6 +17907,20 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-field-form/1.29.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-gXNkthHMUjJ7gDKYmD/lJWJrpMqAjiEPQE4QmlOuZoiHF51LybCL/y+iAmLXpdEjPfJ41WtZBH5hZMUEnEnHXA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + async-validator: 4.2.5 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-image/5.13.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-iZTOmw5eWo2+gcrJMMcnd7SsxVHl3w5xlyCgsULUdJhJbnuI8i/AL0tVOsE7aLn9VfOh1qgDT3mC2G75/c7mqg==} peerDependencies: @@ -17237,24 +17937,27 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-input-number/7.3.11_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==} + /rc-image/5.16.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-11DOye57IgTXh2yTsmxFNynZJG3tdx8RZnnaqb38eYWrBPPyhVHIuURxyiSZ8B68lEUAggR7SBA0Zb95KP/CyQ==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.21.0 + '@rc-component/portal': 1.1.0_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 + rc-dialog: 9.1.0_biqbaboplfbrettd7655fr4n2y + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 dev: false - /rc-input/0.1.4_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==} + /rc-input-number/7.3.11_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==} peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' + react: '>=16.9.0' + react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.21.0 classnames: 2.3.2 @@ -17263,8 +17966,48 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-mentions/1.13.1_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==} + /rc-input-number/7.4.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-yGturTw7WGP+M1GbJ+UTAO7L4buxeW6oilhL9Sq3DezsRS8/9qec4UiXUbeoiX9bzvRXH11JvgskBtxSp4YSNg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/mini-decimal': 1.0.1 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /rc-input/0.1.4_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /rc-input/1.0.4_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-clY4oneVHRtKHYf/HCxT/MO+4BGzCIywSNLosXWOm7fcQAS0jQW7n0an8Raa8JMB8kpxc8m28p7SNwFZmlMj6g==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /rc-mentions/1.13.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -17279,6 +18022,23 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-mentions/2.2.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-R7ncCldr02uKgJBBPlXdtnOGQIjZ9C3uoIMi4fabU3CPFdmefYlNF6QM4u2AzgcGt8V0KkoHTN5T6HPdUpet8g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-input: 1.0.4_biqbaboplfbrettd7655fr4n2y + rc-menu: 9.8.4_biqbaboplfbrettd7655fr4n2y + rc-textarea: 1.2.2_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-menu/9.8.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-EahOJVjLuEnJsThoPN+mGnVm431RzVzDLZWHRS/YnXTQULa7OsgdJa/Y7qXxc3Z5sz8mgT6xYtgpmBXLxrZFaQ==} peerDependencies: @@ -17295,6 +18055,22 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-menu/9.8.4_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-lmw2j8I2fhdIzHmC9ajfImfckt0WDb2KVJJBBRIsxPEw2kGkEfjLMUoB1NgiNT/Q5cC8PdjGOGQjHJIJMwyNMw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-overflow: 1.2.8_biqbaboplfbrettd7655fr4n2y + rc-trigger: 5.3.4_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-motion/2.6.3_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-xFLkes3/7VL/J+ah9jJruEW/Akbx5F6jVa2wG5o/ApGKQKSOd5FR3rseHLL9+xtJg4PmCwo6/1tqhDO/T+jFHA==} peerDependencies: @@ -17323,6 +18099,21 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-notification/5.0.3_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-+wHbHu6RiTNtsZYx42WxWA+tC5m0qyKvJAauO4/6LIEyJspK8fRlFQz+OCFgFwGuNs3cOdo9tLs+cPfztSZwbQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-overflow/1.2.8_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==} peerDependencies: @@ -17349,6 +18140,18 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-pagination/3.3.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-eI4dSeB3OrFxll7KzWa3ZH63LV2tHxt0AUmZmDwuI6vc3CK5lZhaKUYq0fRowb5586hN+L26j5WZoSz9cwEfjg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-picker/2.7.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-oZH6FZ3j4iuBxHB4NvQ6ABRsS2If/Kpty1YFFsji7/aej6ruGmfM7WnJWQ88AoPfpJ++ya5z+nVEA8yCRYGKyw==} engines: {node: '>=8.x'} @@ -17368,6 +18171,35 @@ packages: shallowequal: 1.1.0 dev: false + /rc-picker/3.5.1_mlnkrlbros4rghcauwy625gk7y: + resolution: {integrity: sha512-T/rqhB2IVU014k14x713JGzHCUT56YEYsGkUT8vBVOANdoRCe18oN/8zdeWYB/7mQRTTHJ1vCSPnxOowjLyN8Q==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + dayjs: 1.11.7 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-progress/3.4.1_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw==} peerDependencies: @@ -17381,6 +18213,20 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-rate/2.10.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-TCjEpKPeN1m0EnGDDbb1KyxjNTJRzoReiPdtbrBJEey4Ryf/UGOQ6vqmz2yC6DJdYVDVUoZPdoz043ryh0t/nQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-rate/2.9.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-SaiZFyN8pe0Fgphv8t3+kidlej+cq/EALkAJAc3A0w0XcPaH2L1aggM8bhe1u6GAGuQNAoFvTLjw4qLPGRKV5g==} engines: {node: '>=8.x'} @@ -17441,6 +18287,24 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-select/14.4.3_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-qoz4gNqm3SN+4dYKSCRiRkxKSEEdbS3jC6gdFYoYwEjDZ9sdQFo5jHlfQbF+hhai01HOoj1Hf8Gq6tpUvU+Gmw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-motion: 2.6.3_biqbaboplfbrettd7655fr4n2y + rc-overflow: 1.2.8_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + rc-virtual-list: 3.4.13_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-slider/10.0.1_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==} engines: {node: '>=8.x'} @@ -17456,6 +18320,20 @@ packages: shallowequal: 1.1.0 dev: false + /rc-slider/10.1.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-steps/5.0.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-9TgRvnVYirdhbV0C3syJFj9EhCRqoJAsxt4i1rED5o8/ZcSv5TLIYyo4H8MCjLPvbe2R+oBAm/IYBEtC+OS1Rw==} engines: {node: '>=8.x'} @@ -17470,6 +18348,20 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-steps/6.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-+KfMZIty40mYCQSDvYbZ1jwnuObLauTiIskT1hL4FFOBHP6ZOr8LK0m143yD3kEN5XKHSEX1DIwCj3AYZpoeNQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-switch/3.2.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==} peerDependencies: @@ -17483,6 +18375,19 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-switch/4.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-IfrYC99vN0gKaTyjQdqYuADU0eH00SAFHg3jOp8HrmUpJruhV1SohJzrCbPqPraZeX/6X/QKkdLfkdnUub05WA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-table/7.26.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ==} engines: {node: '>=8.x'} @@ -17499,6 +18404,22 @@ packages: shallowequal: 1.1.0 dev: false + /rc-table/7.31.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-KZPi35aGpv2VaL1Jbc58FBJo063HtKyVjhOFWX4AkBV7tjHHQokMdUoua5E+GPJh6QZUpK/a8PjKa9IZzPLIEA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/context': 1.3.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + rc-resize-observer: 1.3.1_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-tabs/12.5.7_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-i9gY2TcwCNmBM+bXCDDTvb6mnRYIDkkNm+UGoIqrLOFnRRbAqjsSf+tgyvzhBvbK8XcSrMhzKKLaOMbGyND8YA==} engines: {node: '>=8.x'} @@ -17532,6 +18453,21 @@ packages: shallowequal: 1.1.0 dev: false + /rc-textarea/1.2.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-S9fkiek5VezfwJe2McEs/NH63xgnnZ4iDh6a8n01mIfzyNJj0HkS0Uz6boyR3/eONYjmKaqhrpuJJuEClRDEBw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-input: 1.0.4_biqbaboplfbrettd7655fr4n2y + rc-resize-observer: 1.3.1_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-tooltip/5.2.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg==} peerDependencies: @@ -17545,6 +18481,19 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-tooltip/6.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-MdvPlsD1fDSxKp9+HjXrc/CxLmA/s11QYIh1R7aExxfodKP7CZA++DG1AjrW80F8IUdHYcR43HAm0Y2BYPelHA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + '@rc-component/trigger': 1.8.0_biqbaboplfbrettd7655fr4n2y + classnames: 2.3.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-tree-select/5.5.5_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-k2av7jF6tW9bIO4mQhaVdV4kJ1c54oxV3/hHVU+oD251Gb5JN+m1RbJFTMf1o0rAFqkvto33rxMdpafaGKQRJw==} peerDependencies: @@ -17560,6 +18509,21 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /rc-tree-select/5.8.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-NozrkVLR8k3cpx8R5/YFmJMptgOacR5zEQHZGMQg31bD6jEgGiJeOn2cGRI6x0Xdyvi1CSqCbUsIoqiej74wzw==} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.21.0 + classnames: 2.3.2 + rc-select: 14.4.3_biqbaboplfbrettd7655fr4n2y + rc-tree: 5.7.2_biqbaboplfbrettd7655fr4n2y + rc-util: 5.28.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /rc-tree/5.7.2_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-nmnL6qLnfwVckO5zoqKL2I9UhwDqzyCtjITQCkwhimyz1zfuFkG5ZPIXpzD/Guzso94qQA/QrMsvzic5W6QDjg==} engines: {node: '>=10.x'} @@ -17617,6 +18581,18 @@ packages: react-is: 16.13.1 dev: false + /rc-util/5.29.3_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-wX6ZwQTzY2v7phJBquN4mSEIFR0E0qumlENx0zjENtDvoVSq2s7cR95UidKRO1hOHfDsecsfM9D1gO4Kebs7fA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 16.13.1 + dev: false + /rc-virtual-list/3.4.13_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-cPOVDmcNM7rH6ANotanMDilW/55XnFPw0Jh/GQYtrzZSy3AmWvCnqVNyNC/pgg3lfVmX2994dlzAhuUrd4jG7w==} engines: {node: '>=8.x'} @@ -17730,6 +18706,28 @@ packages: react-fast-compare: 3.2.0 shallowequal: 1.1.0 + /react-intl/6.3.2_react@18.2.0: + resolution: {integrity: sha512-NT03zOHRAFGcZdTx4cXcVKZtnWBOM6RfLPK8Q67eA+Ba+pHdYb+cmrahncqAnevZKgO1r/nEauiVFKwQeudLIw==} + peerDependencies: + react: ^16.6.0 || 17 || 18 + typescript: ^4.7 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@formatjs/ecma402-abstract': 1.14.3 + '@formatjs/icu-messageformat-parser': 2.3.0 + '@formatjs/intl': 2.6.9 + '@formatjs/intl-displaynames': 6.2.6 + '@formatjs/intl-listformat': 7.1.9 + '@types/hoist-non-react-statics': 3.3.1 + '@types/react': 18.0.34 + hoist-non-react-statics: 3.3.2 + intl-messageformat: 10.3.3 + react: 18.2.0 + tslib: 2.5.0 + dev: false + /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -18367,6 +19365,50 @@ packages: - ts-node dev: true + /rollup-plugin-styles/4.0.0_rollup@2.79.1: + resolution: {integrity: sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + rollup: ^2.63.0 + dependencies: + '@rollup/pluginutils': 4.2.1 + '@types/cssnano': 5.1.0_postcss@8.4.21 + cosmiconfig: 7.1.0 + cssnano: 5.1.15_postcss@8.4.21 + fs-extra: 10.1.0 + icss-utils: 5.1.0_postcss@8.4.21 + mime-types: 2.1.35 + p-queue: 6.6.2 + postcss: 8.4.21 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.21 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.21 + postcss-modules-scope: 3.0.0_postcss@8.4.21 + postcss-modules-values: 4.0.0_postcss@8.4.21 + postcss-value-parser: 4.2.0 + query-string: 7.1.3 + resolve: 1.22.1 + rollup: 2.79.1 + source-map-js: 1.0.2 + tslib: 2.5.0 + dev: true + + /rollup-plugin-visualizer/5.9.0_rollup@2.79.1: + resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x + peerDependenciesMeta: + rollup: + optional: true + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + rollup: 2.79.1 + source-map: 0.7.4 + yargs: 17.7.1 + dev: true + /rollup-pluginutils/2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} dependencies: @@ -18563,6 +19605,12 @@ packages: compute-scroll-into-view: 1.0.20 dev: false + /scroll-into-view-if-needed/3.0.10: + resolution: {integrity: sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==} + dependencies: + compute-scroll-into-view: 3.0.3 + dev: false + /section-matter/1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -18950,6 +19998,11 @@ packages: webpack: 5.76.2 dev: true + /split-on-first/1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + dev: true + /split2/3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: @@ -19036,6 +20089,11 @@ packages: mixme: 0.5.5 dev: true + /strict-uri-encode/2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + dev: true + /string-argv/0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -19302,6 +20360,10 @@ packages: loader-utils: 1.4.2 dev: false + /stylis/4.1.3: + resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} + dev: false + /supports-color/2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -19618,6 +20680,11 @@ packages: /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + /throttle-debounce/5.0.0: + resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==} + engines: {node: '>=12.22'} + dev: false + /through/2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -19828,6 +20895,18 @@ packages: yn: 3.1.1 dev: true + /tsc-alias/1.8.5: + resolution: {integrity: sha512-Y3ka0olwSRdbHPyX5kXhYY2aoBKuT53DFdeY+PpQUR4hg5M/b8eIRmC8dL4FBdd0wT366iWc6iDUUGe6QwI7mg==} + hasBin: true + dependencies: + chokidar: 3.5.3 + commander: 9.5.0 + globby: 11.1.0 + mylas: 2.1.13 + normalize-path: 3.0.0 + plimit-lit: 1.5.0 + dev: true + /tsconfig-paths/3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -20111,6 +21190,13 @@ packages: unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 + /universal-cookie/4.0.4: + resolution: {integrity: sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==} + dependencies: + '@types/cookie': 0.3.3 + cookie: 0.4.2 + dev: false + /universal-env/3.3.3: resolution: {integrity: sha512-4ZyITvWhtcurCEA66Cb7jcd4zpEiAAo91wSwbEscbiu033pIsC2yjgT8LYyasFgsst6jZHD1gtVoSyYcL8oH1Q==} engines: {npm: '>=3.0.0'} @@ -20219,6 +21305,11 @@ packages: /url-join/4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + /url-join/5.0.0: + resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /url-loader/4.1.1_35ful32yo3wjb53le3l6xb5doy: resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} engines: {node: '>= 10.13.0'} @@ -20712,6 +21803,19 @@ packages: - bufferutil - utf-8-validate + /webpack-dev-middleware/5.3.3: + resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + colorette: 2.0.19 + memfs: 3.4.13 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.0.0 + dev: true + /webpack-dev-middleware/5.3.3_webpack@5.76.2: resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} engines: {node: '>= 12.13.0'} @@ -20725,7 +21829,7 @@ packages: schema-utils: 4.0.0 webpack: 5.76.2_i6ic2mvdiaf3c6z2npjtaj5kuu - /webpack-dev-server/4.11.1_debug@4.3.4+webpack@5.76.2: + /webpack-dev-server/4.11.1_webpack@5.76.2: resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} engines: {node: '>= 12.13.0'} hasBin: true @@ -20735,6 +21839,106 @@ packages: peerDependenciesMeta: webpack-cli: optional: true + dependencies: + '@types/bonjour': 3.5.10 + '@types/connect-history-api-fallback': 1.3.5 + '@types/express': 4.17.17 + '@types/serve-index': 1.9.1 + '@types/serve-static': 1.15.1 + '@types/sockjs': 0.3.33 + '@types/ws': 8.5.4 + ansi-html-community: 0.0.8 + bonjour-service: 1.1.0 + chokidar: 3.5.3 + colorette: 2.0.19 + compression: 1.7.4 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.18.2 + graceful-fs: 4.2.10 + html-entities: 2.3.3 + http-proxy-middleware: 2.0.6_@types+express@4.17.17 + ipaddr.js: 2.0.1 + open: 8.4.2 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.0.0 + selfsigned: 2.1.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack: 5.76.2_i6ic2mvdiaf3c6z2npjtaj5kuu + webpack-dev-middleware: 5.3.3_webpack@5.76.2 + ws: 8.12.1 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: true + + /webpack-dev-server/4.13.2: + resolution: {integrity: sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + dependencies: + '@types/bonjour': 3.5.10 + '@types/connect-history-api-fallback': 1.3.5 + '@types/express': 4.17.17 + '@types/serve-index': 1.9.1 + '@types/serve-static': 1.15.1 + '@types/sockjs': 0.3.33 + '@types/ws': 8.5.4 + ansi-html-community: 0.0.8 + bonjour-service: 1.1.0 + chokidar: 3.5.3 + colorette: 2.0.19 + compression: 1.7.4 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.18.2 + graceful-fs: 4.2.10 + html-entities: 2.3.3 + http-proxy-middleware: 2.0.6_@types+express@4.17.17 + ipaddr.js: 2.0.1 + launch-editor: 2.6.0 + open: 8.4.2 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.0.0 + selfsigned: 2.1.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 5.3.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: true + + /webpack-dev-server/4.13.2_debug@4.3.4+webpack@5.76.2: + resolution: {integrity: sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true dependencies: '@types/bonjour': 3.5.10 '@types/connect-history-api-fallback': 1.3.5 @@ -20755,6 +21959,7 @@ packages: html-entities: 2.3.3 http-proxy-middleware: 2.0.6_cdocoejotnspksifechgljubnq ipaddr.js: 2.0.1 + launch-editor: 2.6.0 open: 8.4.2 p-retry: 4.6.2 rimraf: 3.0.2 @@ -20765,7 +21970,7 @@ packages: spdy: 4.0.2 webpack: 5.76.2 webpack-dev-middleware: 5.3.3_webpack@5.76.2 - ws: 8.12.1 + ws: 8.13.0 transitivePeerDependencies: - bufferutil - debug @@ -20773,14 +21978,16 @@ packages: - utf-8-validate dev: false - /webpack-dev-server/4.11.1_webpack@5.76.2: - resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} + /webpack-dev-server/4.13.2_webpack@5.76.2: + resolution: {integrity: sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==} engines: {node: '>= 12.13.0'} hasBin: true peerDependencies: webpack: ^4.37.0 || ^5.0.0 webpack-cli: '*' peerDependenciesMeta: + webpack: + optional: true webpack-cli: optional: true dependencies: @@ -20803,6 +22010,7 @@ packages: html-entities: 2.3.3 http-proxy-middleware: 2.0.6_@types+express@4.17.17 ipaddr.js: 2.0.1 + launch-editor: 2.6.0 open: 8.4.2 p-retry: 4.6.2 rimraf: 3.0.2 @@ -20811,9 +22019,9 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.76.2_i6ic2mvdiaf3c6z2npjtaj5kuu + webpack: 5.76.2 webpack-dev-middleware: 5.3.3_webpack@5.76.2 - ws: 8.12.1 + ws: 8.13.0 transitivePeerDependencies: - bufferutil - debug @@ -21165,6 +22373,19 @@ packages: optional: true utf-8-validate: optional: true + dev: true + + /ws/8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true /xdg-basedir/4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} diff --git a/tests/integration/with-i18n.test.ts b/tests/integration/with-i18n.test.ts new file mode 100644 index 0000000000..0014372a82 --- /dev/null +++ b/tests/integration/with-i18n.test.ts @@ -0,0 +1,44 @@ +import * as path from 'path'; +import glob from 'glob'; +import { expect, test, describe, afterAll } from 'vitest'; +import { buildFixture, setupBrowser } from '../utils/build'; +import type { Page } from '../utils/browser'; +import type Browser from '../utils/browser'; + +const example = 'with-i18n'; + +describe(`build ${example}`, () => { + let page: Page; + let browser: Browser; + + test('generate html files with locales', async () => { + await buildFixture(example); + const res = await setupBrowser({ example, disableJS: false }); + page = res.page; + browser = res.browser; + const distDir = path.join(__dirname, `../../examples/${example}/build`); + const htmlFiles = glob.sync('**/*.html', { cwd: distDir }); + + expect(htmlFiles).toEqual([ + 'blog.html', + 'blog/a.html', + 'en-US.html', + 'en-US/blog.html', + 'en-US/blog/a.html', + 'index.html', + ]); + }); + + test('visit / page and get the zh-CN locale page', async () => { + expect(await page.$$text('#button')).toStrictEqual(['普通按钮']); + }); + + test('visit /en-US page and get the en-US locale page', async () => { + await page.push('/en-US.html'); + expect(await page.$$text('#button')).toStrictEqual(['Normal Button']); + }); + + afterAll(async () => { + await browser.close(); + }); +}); \ No newline at end of file diff --git a/website/docs/guide/advanced/i18n.md b/website/docs/guide/advanced/i18n.md index 3f5573ea1e..da53204c40 100644 --- a/website/docs/guide/advanced/i18n.md +++ b/website/docs/guide/advanced/i18n.md @@ -1,6 +1,380 @@ --- title: 国际化 -hide: true --- -@TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +ice.js 官方提供 i18n 国际化插件,支持在应用快速开启国际化能力。核心特性包括: + +1. 支持自动处理和生成国际化路由 +2. 完美支持 SSR 和 SSG,以获得更好的 SEO 优化 +3. 支持自动重定向到偏好语言对应的页面 +4. 不耦合任何一个 i18n 库(流行的 React i18n 库有 [react-intl](https://formatjs.io/docs/getting-started/installation/)、[react-i18next](https://react.i18next.com/) 等),你可以选择任一国际化的库来为你的应用设置国际化 + +<details open> + <summary>使用国际化插件的示例</summary> + <ul> + <li> + <a href="https://github.com/alibaba/ice/tree/master/examples/with-i18n" target="_blank" rel="noopener noreferrer"> + with-i18n + </a> + </li> + </ul> +</details> + +:::tip + +如果应用不需要使用国际化路由,你可以参考以下例子来让你的项目支持国际化: + +- [with-antd5](https://github.com/alibaba/ice/tree/master/examples/with-antd5) +- [with-fusion](https://github.com/alibaba/ice/tree/master/examples/with-fusion) + +::: + +## 快速开始 + +首先,我们需要在终端执行以下命令安装插件: + +```bash +$ npm i @ice/plugin-i18n -D +``` + +然后在 `ice.config.mts` 中添加插件和选项: + +```ts +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US', 'de'], + defaultLocale: 'zh-CN', + }), + ], +}); +``` + +上面的 `en-US` 和 `zh-CN` 是国际化语言的缩写,它们均遵循标准的 [UTS 语言标识符](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers)。比如: + +- `zh-CN`:中文(中国) +- `zh-HK`:中文(香港) +- `en-US`:英文(美国) +- `de`: 德文 + +## 国际化路由 + +国际化路由是指在页面路由地址中包含了当前页面的语言,一个国际化路由对应一个语言。 + +假设现在插件的选项配置是: + +```ts +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US', 'nl-NL'], + defaultLocale: 'zh-CN', + }), + ], +}); +``` + +假设我们有一个页面 `src/pages/home.tsx`,那么将会一一对应自动生成以下的路由: + +- `/home`:显示 `zh-CN` 语言,默认语言对应的路由不包含语言前缀 +- `/en-US/home`:显示 `en-US` 语言 +- `/nl-NL/home`:显示 `nl-NL` 语言 + +访问不同的路由,将会显示该语言对应页面内容。 + +## 获取语言信息 + +### `getLocales()` + +`getAllLocales()` 用于获取当前应用支持的所有语言。 + +```ts +import { getAllLocales } from 'ice'; + +console.log(getAllLocales()); // ['zh-CN', 'en-US'] +``` + +### `getDefaultLocale()` + +`getDefaultLocale()` 用于获取应用配置的默认语言。 + +```ts +import { getDefaultLocale } from 'ice'; + +console.log(getDefaultLocale()); // 'zh-CN' +``` + +### `useLocale()` + +在 Function 组件中使用 `useLocale()` Hook API,它的返回值是一个数组,包含两个值: + +1. 当前页面的语言 +2. 一个 set 函数用于更新当前页面的语言。注意,默认情况下调用此 set 函数时候,同时会更新 Cookie 中 `ice_locale` 的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。 + + +```tsx +import { useLocale } from 'ice'; + +export default function Home() { + const [locale, setLocale] = useLocale(); + + console.log('locale: ', locale); // 'en-US' + return ( + <> + {/* 切换语言为 zh-CN */} + <div onClick={() => setLocale('zh-CN')}>Set zh-CN</div> + </> + ) +} +``` + +### `withLocale()` + +使用 `withLocale()` 方法包裹的 Class 组件,组件的 Props 会包含 `locale` 和 `setLocale()` 函数,可以查看和修改当前页面的语言。注意,默认情况下调用 `setLocale()`,会更新 Cookie 中 `ice_locale` 的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。 + +```tsx +import { withLocale } from 'ice'; + +function Home({ locale, setLocale }) { + console.log('locale: ', locale); // 'en-US' + return ( + <> + {/* 切换语言为 zh-CN */} + <div onClick={() => setLocale('zh-CN')}>Set zh-CN</div> + </> + ) +} + +export default withLocale(Home); +``` + +## 切换语言 + +推荐使用 `setLocale()` 方法配合 `<Link>` 组件或者 `useNavigate()` 方法进行语言切换: + +<Tabs> +<TabItem value="a" label="使用 <Link />"> + +```tsx +import { useLocale, getAllLocales, Link, useLocation } from 'ice'; + +export default function Layout() { + const location = useLocation(); + const [activeLocale, setLocale] = useLocale(); + + return ( + <main> + <p><b>Current locale: </b>{activeLocale}</p> + + <b>Choose language: </b> + <ul> + { + getAllLocales().map((locale: string) => { + return ( + <li key={locale}> + <Link + to={location.pathname} + onClick={() => setLocale(locale)} + > + {locale} + </Link> + </li> + ); + }) + } + </ul> + </main> + ); +} +``` + +</TabItem> +<TabItem value="b" label="使用 useNavigate()"> + +```tsx +import { useLocale, useNavigate, useLocation } from 'ice'; + +export default function Layout() { + const [, setLocale] = useLocale(); + const location = useLocation(); + const navigate = useNavigate(); + const switchToZHCN = () => { + setLocale('zh-CN'); + navigate(location.pathname); + } + return ( + <main> + <div onClick={switchToZHCN}> + 点我切换到中文 + </div> + </main> + ); +} +``` + +</TabItem> +</Tabs> + +## 路由自动重定向 + +路由自动重定向是指,如果当前访问的页面是根路由(`/`),将会根据当前语言环境自动跳转到对应的国际化路由。 + +默认情况下,路由自动重定向的功能是关闭的。如果需要开启,则需要加入以下内容: + +```diff +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US', 'de'], + defaultLocale: 'zh-CN', ++ autoRedirect: true, + }), + ], +}); +``` +其中,语言环境的识别顺序如下: + +- `CSR`:cookie 中 `ice_locale` 的值 > `window.navigator.language` > `defaultLocale` +- `SSR`:cookie 中 `ice_locale` 的值 > `Request Header` 中的 `Accept-Language` > `defaultLocale` + +在部署阶段,路由自动重定向的功能需要配合 Node 中间件使用才能生效。比如: + +```ts +import express from 'express'; +import { renderToHTML } from './build/server/index.mjs'; + +const app = express(); + +app.use(express.static('build', {})); + +app.use(async (req, res) => { + const { statusCode, statusText, headers, value: body } = await renderToHTML({ req, res }); + res.statusCode = statusCode; + res.statusMessage = statusText; + Object.entries((headers || {}) as Record<string, string>).forEach(([name, value]) => { + res.setHeader(name, value); + }); + if (body && req.method !== 'HEAD') { + res.end(body); + } else { + res.end(); + } +}); +``` + +## 禁用 Cookie + +在上面的章节中提到,用户设置的偏好语言是存放在 Cookie 中的 `ice_locale`,调用 `setLocale()` 时会更新到 Cookie 中,并且路由重定向和路由跳转的时候依赖 `ice_locale` 的值。 + +假设有这么一个场景,用户拒绝接受 Cookie,为了保护隐私,这样就不能把偏好语言写到 Cookie 中了。因此需要做以下的配置来禁用 Cookie: + +```ts title="src/app.ts" +import { defineI18nConfig } from '@ice/plugin-i18n/types'; + +export const i18nConfig = defineI18nConfig(() => ({ + // 可以是一个 function + disabledCookie: () => { + if (import.meta.renderer === 'client') { + return window.localStorage.getItem('acceptCookie') === 'yes'; + } + return false; + }, + // 也可以是 boolean 值 + // disabledCookie: true, +})); +``` + +这样,就禁用掉了 Cookie 的写入了。在切换语言的时候需要在 `state` 对象中显式传入即将要切换的新语言的值: + +```tsx +import { Link, useLocale } from 'ice'; + +export default function Home() { + const [, setLocale] = useLocale(); + return ( + <> + <Link + to="/" + onClick={() => setLocale('zh-CN')} + state={{ locale: 'zh-CN' }} + > + 切换到 zh-CN + </Link> + </> + ) +} +``` + +## SSG + +在开启 SSG 功能后,将根据配置的 `locales` 的值,在 `build` 阶段会生成不同语言对应的 HTML。 + +比如我们有以下的目录结构,包含 `about` 和 `index` 两个页面: + +```md +├── src/pages +| ├── about.tsx +| └── index.tsx +``` + +假如插件的配置是: + +```ts +import { defineConfig } from '@ice/app'; +import i18n from '@ice/plugin-i18n'; + +export default defineConfig({ + plugins: [ + i18n({ + locales: ['zh-CN', 'en-US'], + defaultLocale: 'zh-CN', + }), + ], +}); +``` + +那么将会生成 4 个 HTML 文件: + +```md +├── build +| ├── about +| | └── index.html +| ├── en-US +| | ├── about +| | | └── index.html +| | └── index.html +| ├── index.html +``` + +## 插件选项 + +### `locales` + +- **类型:**`string[]` + +用于声明该应用支持的语言。 + +### `defaultLocale` + +- **类型:**`string` + +声明该应用默认的语言。需要注意的是, `locales` 数组必须包含 `defaultLocale` 的值。 + +### `autoRedirect` + +- **类型:**`boolean` +- **默认值:**`false` + +默认不会自动重定向到用户偏好语言对应的页面。如果设置为 `true`,在生产环境下,一般需要配合 Node 中间件一起使用才能生效。[详见](#路由自动重定向)