From 0254f5d66600fc88475361ed80572e4620be71e8 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 24 Mar 2023 14:55:01 +0800 Subject: [PATCH 01/38] chore: refactor unfinished --- examples/basic-project/src/pages/layout.tsx | 38 --- packages/ice/package.json | 2 +- packages/ice/src/routes.ts | 14 +- .../ice/templates/core/entry.server.ts.ejs | 6 +- packages/ice/templates/core/routes.ts.ejs | 1 + packages/runtime/package.json | 2 +- packages/runtime/src/RouteContext.ts | 11 +- packages/runtime/src/index.server.ts | 2 +- packages/runtime/src/index.ts | 6 +- packages/runtime/src/requestContext.ts | 3 +- packages/runtime/src/routes.tsx | 11 +- packages/runtime/src/runClientApp.tsx | 34 +-- packages/runtime/src/runServerApp.tsx | 82 ++++-- packages/runtime/src/types.ts | 1 + pnpm-lock.yaml | 242 +++--------------- 15 files changed, 150 insertions(+), 305 deletions(-) delete mode 100644 examples/basic-project/src/pages/layout.tsx diff --git a/examples/basic-project/src/pages/layout.tsx b/examples/basic-project/src/pages/layout.tsx deleted file mode 100644 index 618740508c..0000000000 --- a/examples/basic-project/src/pages/layout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Outlet, useData, useConfig, definePageConfig, defineDataLoader } from 'ice'; - -export default function Layout() { - const data = useData(); - const config = useConfig(); - - console.log('render Layout', 'data', data, 'config', config); - - return ( -
-

ICE 3.0 Layout

- -
- ); -} - - -export const pageConfig = definePageConfig(() => { - return { - title: 'Layout', - meta: [ - { - name: 'layout-color', - content: '#f00', - }, - ], - }; -}); - -export const dataLoader = defineDataLoader(() => { - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - layout: true, - }); - }, 1 * 100); - }); -}); diff --git a/packages/ice/package.json b/packages/ice/package.json index c3c2971285..c596e7f5ad 100644 --- a/packages/ice/package.json +++ b/packages/ice/package.json @@ -83,7 +83,7 @@ "esbuild": "^0.16.5", "jest": "^29.0.2", "react": "^18.2.0", - "react-router": "^6.8.2", + "react-router": "^6.9.0", "unplugin": "^0.9.0", "webpack": "^5.76.2", "webpack-dev-server": "^4.7.4" diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index a91eef510d..3adece6e08 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -68,8 +68,18 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la } const routeProperties: string[] = [ `path: '${formatPath(routePath || '')}',`, - `load: () => ${loadStatement},`, - `componentName: '${componentName}',`, + `async lazy() { + const module = await ${loadStatement}; + return { + Component: module.default, + loader: async (location, requestContext) => { + const data = module.dataLoader ? await callDataLoader(module.dataLoader, getRequestContext(location || window.location, requestContext)) : null; + const pageConfig = module.pageConfig ? module.pageConfig(data) : {}; + return { data, pageConfig }; + }, + }; + },`, + // `componentName: '${componentName}',`, `index: ${index},`, `id: '${id}',`, 'exact: true,', diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 9625ba7450..3f0cf62202 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -47,7 +47,7 @@ interface RenderOptions { export async function renderToHTML(requestContext, options: RenderOptions = {}) { const { renderMode = 'SSR' } = options; setRuntimeEnv(renderMode); - + const mergedOptions = mergeOptions(options); return await runtime.renderToHTML(requestContext, mergedOptions); } @@ -55,7 +55,7 @@ export async function renderToHTML(requestContext, options: RenderOptions = {}) export async function renderToResponse(requestContext, options: RenderOptions = {}) { const { renderMode = 'SSR' } = options; setRuntimeEnv(renderMode); - + const mergedOptions = mergeOptions(options); return runtime.renderToResponse(requestContext, mergedOptions); } @@ -98,4 +98,4 @@ function mergeOptions(options) { }, <% } -%> }; -} \ No newline at end of file +} diff --git a/packages/ice/templates/core/routes.ts.ejs b/packages/ice/templates/core/routes.ts.ejs index f15765f6e9..6d976cf618 100644 --- a/packages/ice/templates/core/routes.ts.ejs +++ b/packages/ice/templates/core/routes.ts.ejs @@ -1,3 +1,4 @@ +import { callDataLoader, getRequestContext } from '@ice/runtime'; <%- routeImports.length ? routeImports.join('\n') + '\n\n' : ''; -%> export default [ <%- routeDefination %> diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 93c3845759..6197d1c6da 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -46,7 +46,7 @@ "fs-extra": "^10.0.0", "history": "^5.3.0", "htmlparser2": "^8.0.1", - "react-router-dom": "^6.8.2" + "react-router-dom": "^6.9.0" }, "peerDependencies": { "react": "^18.1.0", diff --git a/packages/runtime/src/RouteContext.ts b/packages/runtime/src/RouteContext.ts index 113b506001..c082f4922d 100644 --- a/packages/runtime/src/RouteContext.ts +++ b/packages/runtime/src/RouteContext.ts @@ -1,12 +1,14 @@ import * as React from 'react'; +import { useLoaderData } from 'react-router-dom'; import type { RouteData, RouteConfig } from './types.js'; const DataContext = React.createContext(undefined); DataContext.displayName = 'Data'; function useData(): T { - const value = React.useContext(DataContext); - return value; + const data = useLoaderData(); + console.log('data ==>', data); + return (data as any).data; } const DataProvider = DataContext.Provider; @@ -14,8 +16,9 @@ const ConfigContext = React.createContext | undefined>(undefine ConfigContext.displayName = 'Config'; function useConfig(): RouteConfig { - const value = React.useContext(ConfigContext); - return value; + const data = useLoaderData(); + console.log('pageConfig ==>', data); + return (data as any).pageConfig; } const ConfigProvider = ConfigContext.Provider; diff --git a/packages/runtime/src/index.server.ts b/packages/runtime/src/index.server.ts index 7a8dcf67bd..e1390daf38 100644 --- a/packages/runtime/src/index.server.ts +++ b/packages/runtime/src/index.server.ts @@ -1,2 +1,2 @@ export { renderToResponse, renderToHTML, renderToEntry } from './runServerApp.js'; -export * from './index.js'; \ No newline at end of file +export * from './index.js'; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index cd654f60dc..4128991859 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -42,7 +42,8 @@ import type { DataType, MainType, } from './Document.js'; -import dataLoader, { defineDataLoader, defineServerDataLoader, defineStaticDataLoader } from './dataLoader.js'; +import dataLoader, { defineDataLoader, defineServerDataLoader, defineStaticDataLoader, callDataLoader } from './dataLoader.js'; +import getRequestContext from './requestContext.js'; import AppRouter from './AppRouter.js'; import AppErrorBoundary from './AppErrorBoundary.js'; import getAppConfig, { defineAppConfig } from './appConfig.js'; @@ -76,7 +77,10 @@ export { Scripts, Data, Main, + // API for data-loader. dataLoader, + callDataLoader, + getRequestContext, // react-router-dom API Link, Outlet, diff --git a/packages/runtime/src/requestContext.ts b/packages/runtime/src/requestContext.ts index 81ccf643d1..b120249a52 100644 --- a/packages/runtime/src/requestContext.ts +++ b/packages/runtime/src/requestContext.ts @@ -9,11 +9,12 @@ interface Location { * context for getData both in server and client side. */ export default function getRequestContext(location: Location, serverContext: ServerContext = {}): RequestContext { + console.log('loaction ==>', location); const { pathname, search } = location; const query = parseSearch(search); const requestContext: RequestContext = { - ...serverContext, + ...(serverContext || {}), pathname, query, }; diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index fba5e0b00f..326c8da219 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -6,7 +6,7 @@ import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; -type RouteModule = Pick; +type RouteModule = Pick; export function getRoutesPath(routes: RouteItem[], parentPath = ''): string[] { let paths = []; @@ -23,7 +23,8 @@ export function getRoutesPath(routes: RouteItem[], parentPath = ''): string[] { } export async function loadRouteModule(route: RouteModule, routeModulesCache: RouteModules) { - const { id, load } = route; + console.log('routerouteroute ==>', route); + const { id, load, lazy } = route; if ( typeof window !== 'undefined' && // Don't use module cache and should load again in ssr. Ref: https://github.com/ice-lab/ice-next/issues/82 id in routeModulesCache @@ -32,7 +33,8 @@ export async function loadRouteModule(route: RouteModule, routeModulesCache: Rou } try { - const routeModule = await load(); + const routeModule = lazy ? await lazy() : await load(); + console.log('routeModule ==>', routeModule); routeModulesCache[id] = routeModule; return routeModule; } catch (error) { @@ -168,7 +170,8 @@ export function createRouteElements( export function RouteComponent({ id }: { id: string }) { // get current route component from latest routeModules const { routeModules } = useAppContext(); - const { default: Component } = routeModules[id] || {}; + // @ts-ignore + const { Component } = routeModules[id] || {}; if (process.env.NODE_ENV === 'development') { if (!Component) { throw new Error( diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 2e21f39307..bcace17d3d 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -2,6 +2,7 @@ import React, { useLayoutEffect, useEffect, useState } from 'react'; import * as ReactDOM from 'react-dom/client'; import { createHashHistory, createBrowserHistory, createMemoryHistory } from 'history'; import type { HashHistory, BrowserHistory, Action, Location, MemoryHistory } from 'history'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import type { AppContext, WindowContext, AppExport, RouteItem, AppRouterProps, RoutesData, RoutesConfig, RouteWrapperConfig, RuntimeModules, RouteMatch, RouteModules, AppConfig, AssetsManifest, @@ -99,33 +100,13 @@ export default async function runClientApp(options: RunClientAppOptions) { appData = await getAppData(app, requestContext); } - const matches = matchRoutes( - routes, - memoryRouter ? routePath : history.location, - formattedBasename, - ); - const routeModules = await loadRouteModules(matches.map(({ route: { id, load } }) => ({ id, load }))); - if (Object.keys(routeModules).length === 0) { - // Log route info for debug. - console.warn('Routes:', routes, 'Basename:', formattedBasename); - } - if (!routesData) { - routesData = await loadRoutesData(matches, requestContext, routeModules, { - ssg: renderMode === 'SSG', - }); - } - - if (!routesConfig) { - routesConfig = getRoutesConfig(matches, routesData, routeModules); - } - if (hydrate && !downgrade && !documentOnly) { runtime.setRender((container, element) => { return ReactDOM.hydrateRoot(container, element); }); } // Reset app context after app context is updated. - runtime.setAppContext({ ...appContext, matches, routeModules, routesData, routesConfig, appData }); + runtime.setAppContext({ ...appContext, appData }); if (runtimeModules.commons) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } @@ -140,7 +121,7 @@ interface RenderOptions { async function render({ history, runtime }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, appData } = appContext; + const { appConfig, appData, routes } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const RouteWrappers = runtime.getWrappers(); @@ -154,17 +135,14 @@ async function render({ history, runtime }: RenderOptions) { document.body.appendChild(root); console.warn(`Root node #${rootId} is not found, current root is automatically created by the framework.`); } + // @ts-ignore + const router = createBrowserRouter(routes); return appRender( root, - + Loading...

} />
, ); diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 2af7b47c86..92805c527e 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { Action, parsePath } from 'history'; import type { Location } from 'history'; +import { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs'; import type { AppContext, RouteItem, ServerContext, AppExport, AssetsManifest, @@ -19,7 +20,7 @@ import { AppContextProvider } from './AppContext.js'; import { AppDataProvider, getAppData } from './AppData.js'; import getAppConfig from './appConfig.js'; import { DocumentContextProvider } from './Document.js'; -import { loadRouteModules, loadRoutesData, getRoutesConfig } from './routes.js'; +import { loadRouteModules, loadRoutesData, getRoutesConfig, RouteComponent } from './routes.js'; import { pipeToString, renderToNodeStream } from './server/streamRender.js'; import { createStaticNavigator } from './server/navigator.js'; import type { NodeWritablePiper } from './server/streamRender.js'; @@ -243,12 +244,29 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio } else if (!matches.length) { return render404(); } - + console.log(matches); try { - const routeModules = await loadRouteModules(matches.map(({ route: { id, load } }) => ({ id, load }))); - const routesData = await loadRoutesData(matches, requestContext, routeModules, { renderMode }); - const routesConfig = getRoutesConfig(matches, routesData, routeModules); - runtime.setAppContext({ ...appContext, routeModules, routesData, routesConfig, routePath, matches, appData }); + const routeModules = await loadRouteModules(matches.map(({ route: { id, load, lazy } }) => ({ id, load, lazy }))); + const routesData = {}; + const routesConfig = {}; + const loaderData = {}; + for (const routeId in routeModules) { + const { loader } = routeModules[routeId]; + if (loader) { + const { data, pageConfig } = await loader(location, requestContext); + loaderData[routeId] = { + data, + pageConfig, + }; + routesData[routeId] = data; + routesConfig[routeId] = pageConfig; + } + } + console.log('initialdata ==>', routesData, routesConfig); + // const routesData = await loadRoutesData(matches, requestContext, routeModules, { renderMode }); + // const routesConfig = getRoutesConfig(matches, routesData, routeModules); + // @ts-ignore loaderData + runtime.setAppContext({ ...appContext, routeModules, loaderData, routesData, routesConfig, routePath, matches, appData }); if (runtimeModules.commons) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } @@ -283,6 +301,27 @@ interface RenderServerEntry { renderOptions: RenderOptions; } +function createServerRoutes(routes, routeModules) { + return routes.map((route) => { + let dataRoute = { + element: , + id: route.id, + index: route.index, + path: route.path, + }; + + if (route?.children?.length > 0) { + let children = createServerRoutes( + routes.children, + routeModules, + ); + // @ts-ignore + dataRoute.children = children; + } + return dataRoute; + }); +} + /** * Render App by SSR. */ @@ -296,21 +335,32 @@ async function renderServerEntry( ): Promise { const { Document } = renderOptions; const appContext = runtime.getAppContext(); - const { appData, routePath } = appContext; + // @ts-ignore + const { appData, routePath, routeModules, loaderData } = appContext; const staticNavigator = createStaticNavigator(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const RouteWrappers = runtime.getWrappers(); const AppRouter = runtime.getAppRouter(); - + let routes = createServerRoutes( + appContext.routes, + routeModules, + ); + const context = { + matches, + basemane: appContext.basename, + location, + loaderData, + }; + const router = createStaticRouter(routes, context); + console.log('router ==>', router); const documentContext = { - main: , + main: ( + + ), }; const element = ( diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 552e8ed91c..33e071cbfb 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -131,6 +131,7 @@ export interface RouteItem { exact?: boolean; strict?: boolean; load?: () => Promise; + lazy?: () => Promise; children?: RouteItem[]; layout?: boolean; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7e4f47312..9353a2d7fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1021,7 +1021,7 @@ importers: open: ^8.4.0 path-to-regexp: ^6.2.0 react: ^18.2.0 - react-router: ^6.8.2 + react-router: ^6.9.0 regenerator-runtime: ^0.13.0 resolve.exports: ^1.1.0 sass: ^1.50.0 @@ -1079,7 +1079,7 @@ importers: esbuild: 0.16.17 jest: 29.5.0 react: 18.2.0 - react-router: 6.8.2_react@18.2.0 + react-router: 6.9.0_react@18.2.0 sass: 1.58.3 unplugin: 0.9.6 webpack: 5.76.2_esbuild@0.16.17 @@ -1418,7 +1418,7 @@ importers: htmlparser2: ^8.0.1 react: ^18.0.0 react-dom: ^18.0.0 - react-router-dom: ^6.8.2 + react-router-dom: ^6.9.0 regenerator-runtime: ^0.13.9 dependencies: '@ice/jsx-runtime': link:../jsx-runtime @@ -1426,7 +1426,7 @@ importers: fs-extra: 10.1.0 history: 5.3.0 htmlparser2: 8.0.1 - react-router-dom: 6.8.2_biqbaboplfbrettd7655fr4n2y + react-router-dom: 6.9.0_biqbaboplfbrettd7655fr4n2y devDependencies: '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -5145,42 +5145,6 @@ packages: - ts-node dev: true - /@ice/route-manifest/1.1.0: - resolution: {integrity: sha512-nZsXbK510qTosaTa4ZaOXwZdsVqF1xec4y5idKEI9SU4pwYvR7L9p9laixIKJUjI39zr5CBrSZYaMu4p6XwzMQ==} - dependencies: - minimatch: 5.1.6 - dev: true - - /@ice/runtime/1.1.4: - resolution: {integrity: sha512-m4jT03hoQY6SXsBWovTM6SX1wG8056hUGrViDVu5n0PteqnLVlWw2+8Mh+3X6W3aY/e6e996YinTLu5R/nUHZg==} - peerDependencies: - react: ^18.1.0 - react-dom: ^18.1.0 - dependencies: - '@ice/jsx-runtime': 0.2.0 - ejs: 3.1.8 - fs-extra: 10.1.0 - history: 5.3.0 - htmlparser2: 8.0.1 - react-router-dom: 6.8.2 - dev: true - - /@ice/runtime/1.1.4_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-m4jT03hoQY6SXsBWovTM6SX1wG8056hUGrViDVu5n0PteqnLVlWw2+8Mh+3X6W3aY/e6e996YinTLu5R/nUHZg==} - peerDependencies: - react: ^18.1.0 - react-dom: ^18.1.0 - dependencies: - '@ice/jsx-runtime': 0.2.0_react@18.2.0 - ejs: 3.1.8 - fs-extra: 10.1.0 - history: 5.3.0 - htmlparser2: 8.0.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-router-dom: 6.8.2_biqbaboplfbrettd7655fr4n2y - dev: true - /@ice/sandbox/1.1.4: resolution: {integrity: sha512-MEVF0Ze3McKDutnFiUAhUoc+WwOFxITVBgSSHmbGpKtWbXJX9kUVlx3VsEVJvdqU3O1kiBNx6zE1sFMjKPRTIQ==} dev: false @@ -5217,43 +5181,18 @@ packages: - react-native dev: false - /@ice/swc-plugin-keep-export/0.1.4: - resolution: {integrity: sha512-fOc09KALmL2zJK1xNGTEt/C27mXL7NVn/v1eRjjuM4uer+qmWIxYXIa9dpfTX5ZUn8zXhrKH8lGdczoKHCzyQQ==} - dev: true - /@ice/swc-plugin-keep-export/0.1.5-0: resolution: {integrity: sha512-huUXmRHLL5nz6x9gxSFwsHn8EAKveGFaFqW0o3rce+aSFBxAWFM/f6NyFbQzYCF+NNMn5YDgQUOz5wtiYcHlwg==} dev: false - /@ice/swc-plugin-node-transform/0.1.0-5: - resolution: {integrity: sha512-YCZUQwS4r9kjF2RDaPsChP+SEfWHecq2uqwCkZ4+akjL0hZBxbxkN0kXCRMV2O0rNDSBfMpUUdBeAcrCuFuxHw==} - dev: true - /@ice/swc-plugin-node-transform/0.1.1-1: resolution: {integrity: sha512-eqgqor4w1uqysDskVL82LEoQPk00Q5fZIk3YWVQGHgCwOmu/kDH4Gx0XLT3hUo+u6la8rF9AtPHMmVQ8w79tRQ==} dev: false - /@ice/swc-plugin-remove-export/0.1.2: - resolution: {integrity: sha512-HPeYj+z1ylaD5fJkSqyJ+eXbrHiCdy/t/t56uyf20aqsAyx12EiHVnfV4blW31DSWhFt/veAUXYzbaJ8b9KLOQ==} - dev: true - /@ice/swc-plugin-remove-export/0.1.3-0: resolution: {integrity: sha512-fLa+qSmK3iGqk8FdUHMkjv5oTvwiQIkLTVLs3o4uQbphfsRJE1eWam0IHA/vFs7WzoE3m3pwMcDv3LUOAhAysA==} dev: false - /@ice/webpack-config/1.0.10: - resolution: {integrity: sha512-hhpeHXTnmwLtJBKVXKyTeTY7xSb+tFCpR/HvphRgYvDghuz8BXjcw4x0PCXOALujVzlbW7n+wcIcxfnYe8DMBw==} - dependencies: - '@ice/bundles': 0.1.6 - '@rollup/pluginutils': 4.2.1 - browserslist: 4.21.5 - consola: 2.15.3 - fast-glob: 3.2.12 - process: 0.11.10 - transitivePeerDependencies: - - supports-color - dev: true - /@iceworks/generate-project/2.0.2: resolution: {integrity: sha512-t7/uHl5kM71o+xyR+FnaPsgyFqhFQm89TdqPahM4Kv/ubdKDknFVUYLio1khMDGY8Ops0ahn/+KM+gFnHEKSQw==} engines: {node: '>=12.20.0'} @@ -6102,6 +6041,11 @@ packages: /@remix-run/router/1.3.3: resolution: {integrity: sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==} engines: {node: '>=14'} + dev: false + + /@remix-run/router/1.4.0: + resolution: {integrity: sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==} + engines: {node: '>=14'} /@rollup/plugin-alias/3.1.9_rollup@2.79.1: resolution: {integrity: sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==} @@ -6465,15 +6409,6 @@ packages: transitivePeerDependencies: - supports-color - /@swc/core-darwin-arm64/1.3.19: - resolution: {integrity: sha512-6xLtmXzS4nNWGQkajbiAjGXspUJfxS2IWoGQ16J9nfOFdttKyoIG5o5+mxUfKeg5bXw9cI+r675kN/irx3z7MQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@swc/core-darwin-arm64/1.3.41: resolution: {integrity: sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==} engines: {node: '>=10'} @@ -6482,15 +6417,6 @@ packages: requiresBuild: true optional: true - /@swc/core-darwin-x64/1.3.19: - resolution: {integrity: sha512-qCDQcngYBeWrsNS1kcBslRD0dahKcYKaUUWRC9yHpRcs3SRvnSpJyWQR4y9RCdO9YNmixJ9+5+zPD9qcgL7jBw==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@swc/core-darwin-x64/1.3.41: resolution: {integrity: sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==} engines: {node: '>=10'} @@ -6499,15 +6425,6 @@ packages: requiresBuild: true optional: true - /@swc/core-linux-arm-gnueabihf/1.3.19: - resolution: {integrity: sha512-ufbKW6Lhii1+kVCXnsHgqYIpRvXhPjdhMudfP4KKVgJtT6TsdEIr+KRAQIBHLjRUsTKA2DLsGEpu9jfjwFiNEg==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@swc/core-linux-arm-gnueabihf/1.3.41: resolution: {integrity: sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==} engines: {node: '>=10'} @@ -6516,15 +6433,6 @@ packages: requiresBuild: true optional: true - /@swc/core-linux-arm64-gnu/1.3.19: - resolution: {integrity: sha512-HHhqLRZv9Ss8orJrlEP4XRcLuqLDwFtGgbtHU8kyWBmQEtK42uT18Pf5RJBo5sPJHY8m5EO8C8y3hIbGmKtLyg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@swc/core-linux-arm64-gnu/1.3.41: resolution: {integrity: sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==} engines: {node: '>=10'} @@ -6533,15 +6441,6 @@ packages: requiresBuild: true optional: true - /@swc/core-linux-arm64-musl/1.3.19: - resolution: {integrity: sha512-vipnF3C6T1368uHQqz8RpdszWxxGh0X8VBK3TdTOSWvI/duNZtZXEOZlB2Nh9w+u09umVw0MsJhvg86Aon39mA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@swc/core-linux-arm64-musl/1.3.41: resolution: {integrity: sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==} engines: {node: '>=10'} @@ -6550,15 +6449,6 @@ packages: requiresBuild: true optional: true - /@swc/core-linux-x64-gnu/1.3.19: - resolution: {integrity: sha512-dUbq8mnIqBhU7OppfY3ncOvl26691WFGxd97QtnnlfMZrKnaofKFMIxE9sTHOLSbBo16AylnEMiwa45w2UWDEg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@swc/core-linux-x64-gnu/1.3.41: resolution: {integrity: sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==} engines: {node: '>=10'} @@ -6567,15 +6457,6 @@ packages: requiresBuild: true optional: true - /@swc/core-linux-x64-musl/1.3.19: - resolution: {integrity: sha512-RiVZrlkNGcj9jZyjF7YFOW3fj9fWPC25AYkknLpWxAmLQcp1piAWj+aSixmMWUC4QJau78VZzcm+kRgIOECALw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@swc/core-linux-x64-musl/1.3.41: resolution: {integrity: sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==} engines: {node: '>=10'} @@ -6584,15 +6465,6 @@ packages: requiresBuild: true optional: true - /@swc/core-win32-arm64-msvc/1.3.19: - resolution: {integrity: sha512-r2U6GC+go2iiLx5JBZIJswYFiMv0yOsm+pgE1srVvAc8dP02320t9yh0Uj4Sr2hDipTWJ33Y5PMZwEsZSfBVbQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@swc/core-win32-arm64-msvc/1.3.41: resolution: {integrity: sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==} engines: {node: '>=10'} @@ -6601,15 +6473,6 @@ packages: requiresBuild: true optional: true - /@swc/core-win32-ia32-msvc/1.3.19: - resolution: {integrity: sha512-SPpESDa4vr0PRvUiqXSi8oZSTmkDOGrZ/pSiLD7ISgjsQ5RQMbPkuEK0ztWljim87q2fO0bGVVhyaVYxdOVS1A==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@swc/core-win32-ia32-msvc/1.3.41: resolution: {integrity: sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==} engines: {node: '>=10'} @@ -6618,15 +6481,6 @@ packages: requiresBuild: true optional: true - /@swc/core-win32-x64-msvc/1.3.19: - resolution: {integrity: sha512-0X5HqFC1wQlheOQDZeF6KNOSURZKkGISNK3aTSmTq9g7dDJ/kTcVjsdKbu2rK4ibCnlC9IS0cLK9FpROnsVPwA==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@swc/core-win32-x64-msvc/1.3.41: resolution: {integrity: sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==} engines: {node: '>=10'} @@ -6635,24 +6489,6 @@ packages: requiresBuild: true optional: true - /@swc/core/1.3.19: - resolution: {integrity: sha512-KiXUv2vpmOaGhoLCN9Rw7Crsfq1YmOR2ZbajiqNAh/iu0d3CKn5JZhLRs6S7nCk78cwFFac2obQfTWPePLUe/g==} - engines: {node: '>=10'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@swc/core-darwin-arm64': 1.3.19 - '@swc/core-darwin-x64': 1.3.19 - '@swc/core-linux-arm-gnueabihf': 1.3.19 - '@swc/core-linux-arm64-gnu': 1.3.19 - '@swc/core-linux-arm64-musl': 1.3.19 - '@swc/core-linux-x64-gnu': 1.3.19 - '@swc/core-linux-x64-musl': 1.3.19 - '@swc/core-win32-arm64-msvc': 1.3.19 - '@swc/core-win32-ia32-msvc': 1.3.19 - '@swc/core-win32-x64-msvc': 1.3.19 - dev: true - /@swc/core/1.3.41: resolution: {integrity: sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==} engines: {node: '>=10'} @@ -6673,6 +6509,7 @@ packages: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: tslib: 2.5.0 + dev: false /@szmarczak/http-timer/1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} @@ -7366,6 +7203,7 @@ packages: /@uni/env/1.1.0: resolution: {integrity: sha512-2GVgUzxIaO2vGElXEuc45+I7L6Jbw8inLDDFuC0K4htjKtPmYywKSE6oDhvmdAXb4GCOH8hmxECYtAh1rjsgoQ==} + dev: false /@use-gesture/core/10.2.20: resolution: {integrity: sha512-4lFhHc8so4yIHkBEs641DnEsBxPyhJ5GEjB4PURFDH4p/FcZriH6w99knZgI63zN/MBFfylMyb8+PDuj6RIXKQ==} @@ -8024,6 +7862,7 @@ packages: /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -9249,11 +9088,6 @@ packages: requiresBuild: true dev: false - /core-js/3.26.0: - resolution: {integrity: sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==} - requiresBuild: true - dev: true - /core-js/3.29.1: resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==} requiresBuild: true @@ -10182,10 +10016,12 @@ packages: /dotenv-expand/8.0.3: resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} engines: {node: '>=12'} + dev: false /dotenv/16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} + dev: false /dts-bundle/0.7.3: resolution: {integrity: sha512-EEAEuPRk8QyKhoN90NHTh+spSQujkkvOnKWUfuzpmC/fgryiWopL1SegSktx0UsoPfNidIGVDN7/AXpBDBv0WQ==} @@ -10227,6 +10063,7 @@ packages: hasBin: true dependencies: jake: 10.8.5 + dev: false /electron-to-chromium/1.4.322: resolution: {integrity: sha512-KovjizNC9XB7dno/2GjxX8VS0SlfPpCjtyoKft+bCO+UfD8bFy16hY4Sh9s0h9BDxbRH2U0zX5VBjpM1LTcNlg==} @@ -11241,6 +11078,7 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.0 + dev: false /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -11539,6 +11377,7 @@ packages: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: minimatch: 5.1.6 + dev: false /filesize/8.0.7: resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} @@ -13209,6 +13048,7 @@ packages: chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 + dev: false /jest-changed-files/28.1.3: resolution: {integrity: sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==} @@ -14263,6 +14103,7 @@ packages: /jsonc-parser/3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false /jsonfile/4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -15007,6 +14848,7 @@ packages: pathe: 1.1.0 pkg-types: 1.0.2 ufo: 1.1.1 + dev: false /moment/2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} @@ -15578,6 +15420,7 @@ packages: /path-to-regexp/6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: false /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -15585,6 +15428,7 @@ packages: /pathe/1.1.0: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + dev: false /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -15646,6 +15490,7 @@ packages: jsonc-parser: 3.2.0 mlly: 1.1.1 pathe: 1.1.0 + dev: false /pkg-up/3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} @@ -16926,6 +16771,7 @@ packages: /process/0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + dev: false /progress/2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} @@ -17933,28 +17779,18 @@ packages: tiny-invariant: 1.3.1 tiny-warning: 1.0.3 - /react-router-dom/6.8.2: - resolution: {integrity: sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.3.3 - react-router: 6.8.2 - dev: true - - /react-router-dom/6.8.2_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==} + /react-router-dom/6.9.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.3.3 + '@remix-run/router': 1.4.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - react-router: 6.8.2_react@18.2.0 + react-router: 6.9.0_react@18.2.0 + dev: false /react-router/5.3.4_react@17.0.2: resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==} @@ -17972,22 +17808,13 @@ packages: tiny-invariant: 1.3.1 tiny-warning: 1.0.3 - /react-router/6.8.2: - resolution: {integrity: sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - dependencies: - '@remix-run/router': 1.3.3 - dev: true - - /react-router/6.8.2_react@18.2.0: - resolution: {integrity: sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==} + /react-router/6.9.0_react@18.2.0: + resolution: {integrity: sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.3.3 + '@remix-run/router': 1.4.0 react: 18.2.0 /react-textarea-autosize/8.4.0_h7fc2el62uaa77gho3xhys6ola: @@ -18449,6 +18276,7 @@ packages: hasBin: true dependencies: glob: 7.2.3 + dev: false /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -19323,6 +19151,7 @@ packages: dependencies: '@babel/runtime': 7.21.0 universal-env: 3.3.3 + dev: false /stylehacks/5.1.1_postcss@8.4.12: resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} @@ -19582,6 +19411,7 @@ packages: dependencies: mkdirp: 0.5.6 rimraf: 2.6.3 + dev: false /term-size/2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -20087,6 +19917,7 @@ packages: /ufo/1.1.1: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} + dev: false /uglify-js/3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} @@ -20226,6 +20057,7 @@ packages: engines: {npm: '>=3.0.0'} dependencies: '@uni/env': 1.1.0 + dev: false /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} From 35ea322201c76c4efc0393053676bd88fcaa86fc Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Mon, 27 Mar 2023 17:25:59 +0800 Subject: [PATCH 02/38] feat: support create router --- examples/basic-project/ice.config.mts | 1 + packages/ice/package.json | 2 +- packages/ice/src/routes.ts | 18 +- .../ice/templates/core/entry.client.tsx.ejs | 4 +- .../ice/templates/core/entry.server.ts.ejs | 4 +- packages/ice/templates/core/routes.ts.ejs | 9 +- packages/runtime/package.json | 3 +- packages/runtime/src/App.tsx | 63 ------ packages/runtime/src/Document.tsx | 26 ++- packages/runtime/src/RouteWrapper.tsx | 10 +- packages/runtime/src/dataLoader.ts | 19 +- packages/runtime/src/history.ts | 4 +- packages/runtime/src/index.ts | 5 +- packages/runtime/src/requestContext.ts | 3 +- packages/runtime/src/routes.tsx | 198 +++++------------ packages/runtime/src/routesConfig.ts | 34 +-- packages/runtime/src/runClientApp.tsx | 209 ++---------------- packages/runtime/src/runServerApp.tsx | 45 ++-- packages/runtime/src/single-router.tsx | 2 +- packages/runtime/src/types.ts | 44 ++-- pnpm-lock.yaml | 6 +- 21 files changed, 202 insertions(+), 507 deletions(-) delete mode 100644 packages/runtime/src/App.tsx diff --git a/examples/basic-project/ice.config.mts b/examples/basic-project/ice.config.mts index 8baac7c3c8..4a61a033dc 100644 --- a/examples/basic-project/ice.config.mts +++ b/examples/basic-project/ice.config.mts @@ -4,6 +4,7 @@ import customPlugin from './plugin'; export default defineConfig(() => ({ ssr: true, + ssg: false, publicPath: '/', polyfill: 'entry', syntaxFeatures: { diff --git a/packages/ice/package.json b/packages/ice/package.json index c596e7f5ad..72fa6e179c 100644 --- a/packages/ice/package.json +++ b/packages/ice/package.json @@ -83,7 +83,7 @@ "esbuild": "^0.16.5", "jest": "^29.0.2", "react": "^18.2.0", - "react-router": "^6.9.0", + "react-router": "6.9.0", "unplugin": "^0.9.0", "webpack": "^5.76.2", "webpack-dev-server": "^4.7.4" diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index 3adece6e08..1a4c9f3730 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -69,17 +69,19 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la const routeProperties: string[] = [ `path: '${formatPath(routePath || '')}',`, `async lazy() { - const module = await ${loadStatement}; + const componentModule = await ${loadStatement}; + const loader = createRouteLoader({ + routeId: '${id}', + requestContext, + renderMode, + module: componentModule, + }); return { - Component: module.default, - loader: async (location, requestContext) => { - const data = module.dataLoader ? await callDataLoader(module.dataLoader, getRequestContext(location || window.location, requestContext)) : null; - const pageConfig = module.pageConfig ? module.pageConfig(data) : {}; - return { data, pageConfig }; - }, + Component: componentModule.default, + loader, }; },`, - // `componentName: '${componentName}',`, + `componentName: '${componentName}',`, `index: ${index},`, `id: '${id}',`, 'exact: true,', diff --git a/packages/ice/templates/core/entry.client.tsx.ejs b/packages/ice/templates/core/entry.client.tsx.ejs index 24f76211ac..55d5071527 100644 --- a/packages/ice/templates/core/entry.client.tsx.ejs +++ b/packages/ice/templates/core/entry.client.tsx.ejs @@ -4,7 +4,7 @@ import { runClientApp, getAppConfig } from '<%- iceRuntimePath %>'; import { commons, statics } from './runtimeModules'; import * as app from '@/app'; <% if (enableRoutes) { -%> -import routes from './routes'; +import createRoutes from './routes'; <% } -%> <%- runtimeOptions.imports %> <% if(dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%> @@ -23,7 +23,7 @@ const render = (customOptions = {}) => { commons, statics, }, - <% if (enableRoutes) { %>routes,<% } %> + <% if (enableRoutes) { %>createRoutes,<% } %> basename: getRouterBasename(), hydrate: <%- hydrate %>, memoryRouter: <%- memoryRouter || false %>, diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 3f0cf62202..4ea5769093 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -8,7 +8,7 @@ import Document from '@/document'; import type { RenderMode, DistType } from '@ice/runtime'; // @ts-ignore import assetsManifest from 'virtual:assets-manifest.json'; -import routes from './routes'; +import createRoutes from './routes'; import routesConfig from './routes-config.bundle.mjs'; <% if(dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%> <%- runtimeOptions.imports %> @@ -80,7 +80,7 @@ function mergeOptions(options) { return { app, assetsManifest, - routes, + createRoutes, runtimeModules, Document, serverOnlyBasename, diff --git a/packages/ice/templates/core/routes.ts.ejs b/packages/ice/templates/core/routes.ts.ejs index 6d976cf618..cbba09c485 100644 --- a/packages/ice/templates/core/routes.ts.ejs +++ b/packages/ice/templates/core/routes.ts.ejs @@ -1,5 +1,8 @@ -import { callDataLoader, getRequestContext } from '@ice/runtime'; +import { createRouteLoader } from '@ice/runtime'; <%- routeImports.length ? routeImports.join('\n') + '\n\n' : ''; -%> -export default [ +export default ({ + requestContext, + renderMode, +}) => ([ <%- routeDefination %> -]; +]); diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 6197d1c6da..9cf6ac535b 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -42,11 +42,12 @@ "sideEffects": false, "dependencies": { "@ice/jsx-runtime": "^0.2.0", + "@remix-run/router": "1.4.0", "ejs": "^3.1.6", "fs-extra": "^10.0.0", "history": "^5.3.0", "htmlparser2": "^8.0.1", - "react-router-dom": "^6.9.0" + "react-router-dom": "6.9.0" }, "peerDependencies": { "react": "^18.1.0", diff --git a/packages/runtime/src/App.tsx b/packages/runtime/src/App.tsx deleted file mode 100644 index 897ca2b1ff..0000000000 --- a/packages/runtime/src/App.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useMemo } from 'react'; -import type { Action, Location } from 'history'; -import type { Navigator } from 'react-router-dom'; -import type { RouteWrapperConfig, AppRouterProps } from './types.js'; -import AppErrorBoundary from './AppErrorBoundary.js'; -import { useAppContext } from './AppContext.js'; -import { createRouteElements } from './routes.js'; - -interface Props { - action: Action; - location: Location; - navigator: Navigator; - static?: boolean; - RouteWrappers: RouteWrapperConfig[]; - AppRouter: React.ComponentType; -} - -export default function App(props: Props) { - const { - location, - action, - navigator, - static: staticProp = false, - AppRouter, - RouteWrappers, - } = props; - - const { appConfig, routes: originRoutes, basename } = useAppContext(); - const { strict, errorBoundary } = appConfig.app; - const StrictMode = strict ? React.StrictMode : React.Fragment; - - if (!originRoutes || originRoutes.length === 0) { - throw new Error('Please add routes(like pages/index.tsx) to your app.'); - } - - const routes = useMemo( - () => createRouteElements(originRoutes, RouteWrappers), - // `originRoutes` and `RouteWrappers` will not be changed - // eslint-disable-next-line react-hooks/exhaustive-deps - [], - ); - - const ErrorBoundary = errorBoundary ? AppErrorBoundary : React.Fragment; - - let element: React.ReactNode = ( - - ); - - return ( - - - {element} - - - ); -} diff --git a/packages/runtime/src/Document.tsx b/packages/runtime/src/Document.tsx index 71c122bc67..b4736c0c36 100644 --- a/packages/runtime/src/Document.tsx +++ b/packages/runtime/src/Document.tsx @@ -27,8 +27,8 @@ interface MetaProps extends React.HTMLAttributes{ export type MetaType = (props: MetaProps) => JSX.Element; export const Meta: MetaType = (props: MetaProps) => { - const { matches, routesConfig } = useAppContext(); - const meta = getMeta(matches, routesConfig); + const { matches, loaderData } = useAppContext(); + const meta = getMeta(matches, loaderData); const { MetaElement = 'meta', } = props; @@ -48,8 +48,8 @@ interface TitleProps extends React.HTMLAttributes{ export type TitleType = (props: TitleProps) => JSX.Element; export const Title: TitleType = (props: TitleProps) => { - const { matches, routesConfig } = useAppContext(); - const title = getTitle(matches, routesConfig); + const { matches, loaderData } = useAppContext(); + const title = getTitle(matches, loaderData); const { TitleElement = 'title', ...rest @@ -67,13 +67,13 @@ interface LinksProps extends React.LinkHTMLAttributes{ export type LinksType = (props: LinksProps) => JSX.Element; export const Links: LinksType = (props: LinksProps) => { - const { routesConfig, matches, assetsManifest } = useAppContext(); + const { loaderData, matches, assetsManifest } = useAppContext(); const { LinkElement = 'link', ...rest } = props; - const routeLinks = getLinks(matches, routesConfig); + const routeLinks = getLinks(matches, loaderData); const pageAssets = getPageAssets(matches, assetsManifest); const entryAssets = getEntryAssets(assetsManifest); const styles = entryAssets.concat(pageAssets).filter(path => path.indexOf('.css') > -1); @@ -97,18 +97,21 @@ interface ScriptsProps extends React.ScriptHTMLAttributes{ export type ScriptsType = (props: ScriptsProps) => JSX.Element; export const Scripts: ScriptsType = (props: ScriptsProps) => { - const { routesConfig, matches, assetsManifest } = useAppContext(); + const { loaderData, matches, assetsManifest } = useAppContext(); + const { ScriptElement = 'script', ...rest } = props; - const routeScripts = getScripts(matches, routesConfig); + const routeScripts = getScripts(matches, loaderData); + console.log('document matches', matches); + console.log('document loaderData', loaderData); const pageAssets = getPageAssets(matches, assetsManifest); const entryAssets = getEntryAssets(assetsManifest); // Page assets need to be load before entry assets, so when call dynamic import won't cause duplicate js chunk loaded. let scripts = pageAssets.concat(entryAssets).filter(path => path.indexOf('.js') > -1); - + console.log('scripts ==>', assetsManifest, scripts); if (assetsManifest.dataLoader) { scripts.unshift(`${assetsManifest.publicPath}${assetsManifest.dataLoader}`); } @@ -146,7 +149,7 @@ export type DataType = (props: DataProps) => JSX.Element; // use app context separately export const Data: DataType = (props: DataProps) => { - const { routesData, documentOnly, matches, routesConfig, downgrade, renderMode, serverData } = useAppContext(); + const { documentOnly, matches, downgrade, renderMode, serverData, loaderData } = useAppContext(); const appData = useAppData(); const { ScriptElement = 'script', @@ -156,8 +159,7 @@ export const Data: DataType = (props: DataProps) => { const routePath = getCurrentRoutePath(matches); const windowContext: WindowContext = { appData, - routesData, - routesConfig, + loaderData, routePath, downgrade, matchedIds, diff --git a/packages/runtime/src/RouteWrapper.tsx b/packages/runtime/src/RouteWrapper.tsx index 04137aba61..07cfaf2755 100644 --- a/packages/runtime/src/RouteWrapper.tsx +++ b/packages/runtime/src/RouteWrapper.tsx @@ -12,7 +12,7 @@ interface Props { export default function RouteWrapper(props: Props) { const { wrappers = [], id, isLayout } = props; - const { routesData, routesConfig } = useAppContext(); + const { loaderData } = useAppContext(); // layout should only be wrapped by Wrapper with `layout: true` const filtered = isLayout ? wrappers.filter(wrapper => wrapper.layout === true) : wrappers; const RouteWrappers = filtered.map(item => item.Wrapper); @@ -29,11 +29,5 @@ export default function RouteWrapper(props: Props) { element = props.children; } - return ( - - - {element} - - - ); + return element; } diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index d46ff6bb9e..c2632f6dfe 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -1,5 +1,6 @@ import type { DataLoaderConfig, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; import getRequestContext from './requestContext.js'; +import type { RequestContext, RenderMode } from './types.js'; interface Loaders { [routeId: string]: DataLoaderConfig; @@ -17,8 +18,7 @@ interface LoaderOptions { } export interface LoadRoutesDataOptions { - ssg?: boolean; - forceRequest?: boolean; + renderMode: RenderMode; } export function defineDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig { @@ -125,7 +125,7 @@ export function loadDataByCustomFetcher(config: StaticDataLoader) { /** * Handle for different dataLoader. */ -export function callDataLoader(dataLoader: DataLoaderConfig, requestContext): DataLoaderResult { +export function callDataLoader(dataLoader: DataLoaderConfig, requestContext: RequestContext): DataLoaderResult { if (Array.isArray(dataLoader)) { const loaders = dataLoader.map(loader => { return typeof loader === 'object' ? loadDataByCustomFetcher(loader) : loader(requestContext); @@ -220,14 +220,13 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { // first render for ssg use data from build time. // second render for ssg will use data from data loader. - if (options?.ssg) { - result = cache.get(`${id}_ssg`); - } else { - result = cache.get(id); - } + const cacheKey = `${id}${options?.renderMode === 'SSG' ? '_ssg' : ''}`; + result = cache.get(cacheKey); + // Always fetch new data after cache is been used. + cache.delete(cacheKey); // Already send data request. - if (result && !options?.forceRequest) { + if (result) { const { status, value } = result; if (status === 'RESOLVED') { @@ -267,4 +266,4 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { export default { init, -}; \ No newline at end of file +}; diff --git a/packages/runtime/src/history.ts b/packages/runtime/src/history.ts index b9615c7458..3702d97f32 100644 --- a/packages/runtime/src/history.ts +++ b/packages/runtime/src/history.ts @@ -1,4 +1,4 @@ -import type { History } from 'history'; +import type { History } from '@remix-run/router'; // Value of history will be modified after render Router. let routerHistory: History | null = null; @@ -10,4 +10,4 @@ function setHistory(customHistory: History) { export { routerHistory, setHistory, -}; \ No newline at end of file +}; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 4128991859..a5ddf4a139 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -20,7 +20,6 @@ import type { RouteWrapperConfig, } from './types.js'; import Runtime from './runtime.js'; -import App from './App.js'; import runClientApp from './runClientApp.js'; import type { RunClientAppOptions } from './runClientApp.js'; import { useAppContext, AppContextProvider } from './AppContext.js'; @@ -52,12 +51,12 @@ import KeepAliveOutlet from './KeepAliveOutlet.js'; import ClientOnly from './ClientOnly.js'; import useMounted from './useMounted.js'; import { withSuspense, useSuspenseData } from './Suspense.js'; +import { createRouteLoader } from './routes.js'; export { getAppConfig, defineAppConfig, Runtime, - App, runClientApp, AppContextProvider, useAppContext, @@ -97,6 +96,8 @@ export { withSuspense, useSuspenseData, + + createRouteLoader, }; export type { diff --git a/packages/runtime/src/requestContext.ts b/packages/runtime/src/requestContext.ts index b120249a52..04734b7b09 100644 --- a/packages/runtime/src/requestContext.ts +++ b/packages/runtime/src/requestContext.ts @@ -1,6 +1,6 @@ import type { ServerContext, RequestContext } from './types.js'; -interface Location { +export interface Location { pathname: string; search: string; } @@ -9,7 +9,6 @@ interface Location { * context for getData both in server and client side. */ export default function getRequestContext(location: Location, serverContext: ServerContext = {}): RequestContext { - console.log('loaction ==>', location); const { pathname, search } = location; const query = parseSearch(search); diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 326c8da219..555a3d9971 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import type { RouteObject } from 'react-router-dom'; -import { RouteComponent } from './types.js'; -import type { RouteItem, RouteModules, RouteWrapperConfig, RouteMatch, RequestContext, RoutesConfig, RoutesData, RenderMode } from './types.js'; +import type { RouteItem, RouteModules, RouteWrapperConfig, RenderMode, DataLoaderConfig } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; +import type { RequestContext, ComponentModule } from './types.js'; +import { updateRoutesConfig } from './routesConfig.js'; type RouteModule = Pick; @@ -22,8 +22,7 @@ export function getRoutesPath(routes: RouteItem[], parentPath = ''): string[] { return paths.map(str => str.replace('//', '/')); } -export async function loadRouteModule(route: RouteModule, routeModulesCache: RouteModules) { - console.log('routerouteroute ==>', route); +export async function loadRouteModule(route: RouteModule, routeModulesCache = {}) { const { id, load, lazy } = route; if ( typeof window !== 'undefined' && // Don't use module cache and should load again in ssr. Ref: https://github.com/ice-lab/ice-next/issues/82 @@ -33,8 +32,8 @@ export async function loadRouteModule(route: RouteModule, routeModulesCache: Rou } try { + // Function load will return route module when lazy loaded is disabled. const routeModule = lazy ? await lazy() : await load(); - console.log('routeModule ==>', routeModule); routeModulesCache[id] = routeModule; return routeModule; } catch (error) { @@ -52,125 +51,25 @@ export async function loadRouteModules(routes: RouteModule[], originRouteModules return routeModules; } -export interface LoadRoutesDataOptions { - renderMode?: RenderMode; - ssg?: boolean; - forceRequest?: boolean; -} - -/** -* get data for the matched routes. -*/ -export async function loadRoutesData( - matches: RouteMatch[], - requestContext: RequestContext, - routeModules: RouteModules, - options?: LoadRoutesDataOptions, -): Promise { - const { renderMode } = options || {}; - const routesData: RoutesData = {}; - - const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; - const globalLoader = hasGlobalLoader ? (window as any).__ICE_DATA_LOADER__ : null; - - await Promise.all( - matches.map(async (match) => { - const { id } = match.route; - - if (globalLoader) { - routesData[id] = await globalLoader.getData(id, options); - return; - } - - const routeModule = routeModules[id]; - const { dataLoader, serverDataLoader, staticDataLoader } = routeModule ?? {}; - - let loader; - - // SSG -> getStaticData - // SSR -> getServerData || getData - // CSR -> getData - if (renderMode === 'SSG') { - loader = staticDataLoader; - } else if (renderMode === 'SSR') { - loader = serverDataLoader || dataLoader; - } else { - loader = dataLoader; - } - - if (loader) { - routesData[id] = await callDataLoader(loader, requestContext); - } - }), +// Wrap route component with runtime wrappers. +export function wrapRouteComponent(options: { + routeId: string; + isLayout?: boolean; + RouteComponent: React.ComponentType; + RouteWrappers?: RouteWrapperConfig[]; +}) { + const { routeId, isLayout, RouteComponent, RouteWrappers } = options; + return ( + + + ); - - return routesData; -} - -/** - * Get page config for matched routes. - */ -export function getRoutesConfig( - matches: RouteMatch[], - routesData: RoutesData, - routeModules: RouteModules, -): RoutesConfig { - const routesConfig: RoutesConfig = {}; - - matches.forEach(async (match) => { - const { id } = match.route; - const routeModule = routeModules[id]; - - if (typeof routeModule === 'object') { - const { pageConfig } = routeModule; - const data = routesData[id]; - if (pageConfig) { - const value = pageConfig({ data }); - routesConfig[id] = value; - } - } else { - routesConfig[id] = {}; - } - }); - - return routesConfig; -} - -/** - * Create elements in routes which will be consumed by react-router-dom - */ -export function createRouteElements( - routes: RouteItem[], - RouteWrappers?: RouteWrapperConfig[], -) { - return routes.map((routeItem: RouteItem) => { - let { path, children, index, id, layout, element, ...rest } = routeItem; - element = ( - - - - ); - - const route: RouteObject = { - path, - element, - index, - id, - ...rest, - }; - - if (children) { - route.children = createRouteElements(children, RouteWrappers); - } - - return route; - }); } export function RouteComponent({ id }: { id: string }) { // get current route component from latest routeModules const { routeModules } = useAppContext(); - // @ts-ignore + console.log('routeModules[id]', routeModules[id]); const { Component } = routeModules[id] || {}; if (process.env.NODE_ENV === 'development') { if (!Component) { @@ -184,28 +83,47 @@ export function RouteComponent({ id }: { id: string }) { } /** - * filter matches is new or path changed. + * Create loader function for route module. */ -export function filterMatchesToLoad(prevMatches: RouteMatch[], currentMatches: RouteMatch[]): RouteMatch[] { - let isNew = (match: RouteMatch, index: number) => { - // [a] -> [a, b] - if (!prevMatches[index]) return true; - // [a, b] -> [a, c] - return match.route.id !== prevMatches[index].route.id; - }; +interface LoaderData { + data: any; + pageConfig: any; +} - let matchPathChanged = (match: RouteMatch, index: number) => { - return ( - // param change, /users/123 -> /users/456 - prevMatches[index].pathname !== match.pathname || - // splat param changed, which is not present in match.path - // e.g. /files/images/avatar.jpg -> files/finances.xls - (prevMatches[index].route.path?.endsWith('*') && - prevMatches[index].params['*'] !== match.params['*']) - ); - }; +export interface RouteLoaderOptions { + routeId: string; + requestContext: RequestContext; + module: ComponentModule; + renderMode: RenderMode; +} - return currentMatches.filter((match, index) => { - return isNew(match, index) || matchPathChanged(match, index); - }); +export function createRouteLoader(options: RouteLoaderOptions): () => Promise { + return async () => { + const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module; + const { requestContext, renderMode, routeId } = options; + + const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; + const globalLoader = hasGlobalLoader ? (window as any).__ICE_DATA_LOADER__ : null; + let routeData: any; + if (globalLoader) { + routeData = await globalLoader.getData(routeId, { renderMode }); + } else { + let loader: DataLoaderConfig; + if (renderMode === 'SSG') { + loader = staticDataLoader; + } else if (renderMode === 'SSR') { + loader = serverDataLoader || dataLoader; + } else { + loader = dataLoader; + } + routeData = dataLoader && await callDataLoader(loader, requestContext); + } + const routeConfig = pageConfig ? pageConfig({ data: routeData }) : {}; + const loaderData = { data: routeData, pageConfig: routeConfig }; + // CSR and load next route data. + if (typeof window !== 'undefined') { + // await updateRoutesConfig(loaderData); + } + return loaderData; + }; } diff --git a/packages/runtime/src/routesConfig.ts b/packages/runtime/src/routesConfig.ts index c9f55857a2..5ed0371ead 100644 --- a/packages/runtime/src/routesConfig.ts +++ b/packages/runtime/src/routesConfig.ts @@ -1,38 +1,38 @@ -import type { RouteMatch, RoutesConfig, RouteConfig } from './types.js'; +import type { RouteMatch, LoaderDatas, LoaderData, RouteConfig } from './types.js'; export function getMeta( matches: RouteMatch[], - routesConfig: RoutesConfig, + loaderData: LoaderDatas, ): React.MetaHTMLAttributes[] { - return getMergedValue('meta', matches, routesConfig) || []; + return getMergedValue('meta', matches, loaderData) || []; } export function getLinks( matches: RouteMatch[], - routesConfig: RoutesConfig, + loaderData: LoaderDatas, ): React.LinkHTMLAttributes[] { - return getMergedValue('links', matches, routesConfig) || []; + return getMergedValue('links', matches, loaderData) || []; } export function getScripts( matches: RouteMatch[], - routesConfig: RoutesConfig, + loaderData: LoaderDatas, ): React.ScriptHTMLAttributes[] { - return getMergedValue('scripts', matches, routesConfig) || []; + return getMergedValue('scripts', matches, loaderData) || []; } -export function getTitle(matches: RouteMatch[], routesConfig: RoutesConfig): string { - return getMergedValue('title', matches, routesConfig); +export function getTitle(matches: RouteMatch[], loaderData: LoaderDatas): string { + return getMergedValue('title', matches, loaderData); } /** * merge value for each matched route */ -function getMergedValue(key: string, matches: RouteMatch[], routesConfig: RoutesConfig) { +function getMergedValue(key: string, matches: RouteMatch[], loaderData: LoaderDatas) { let result; for (let match of matches) { const routeId = match.route.id; - const data = routesConfig[routeId]; + const data = loaderData[routeId]?.pageConfig; const value = data?.[key]; if (Array.isArray(value)) { @@ -50,15 +50,17 @@ function getMergedValue(key: string, matches: RouteMatch[], routesConfig: Routes /** * update routes config to document. */ -export async function updateRoutesConfig(matches: RouteMatch[], routesConfig: RoutesConfig) { - const title = getTitle(matches, routesConfig); +export async function updateRoutesConfig(loaderData: LoaderData) { + const routeConfig = loaderData?.pageConfig; + + const title = routeConfig?.title; if (title) { document.title = title; } - const meta = getMeta(matches, routesConfig) || []; - const links = getLinks(matches, routesConfig) || []; - const scripts = getScripts(matches, routesConfig) || []; + const meta = routeConfig?.meta || []; + const links = routeConfig?.links || []; + const scripts = routeConfig?.scripts || []; await Promise.all([ updateMeta(meta), diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index bcace17d3d..6d802eaf4c 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -1,23 +1,19 @@ -import React, { useLayoutEffect, useEffect, useState } from 'react'; +import React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { createHashHistory, createBrowserHistory, createMemoryHistory } from 'history'; -import type { HashHistory, BrowserHistory, Action, Location, MemoryHistory } from 'history'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { RouterProvider } from 'react-router-dom'; +import { createRouter, createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; +import type { History } from '@remix-run/router'; import type { - AppContext, WindowContext, AppExport, RouteItem, AppRouterProps, RoutesData, RoutesConfig, - RouteWrapperConfig, RuntimeModules, RouteMatch, RouteModules, AppConfig, AssetsManifest, + AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, } from './types.js'; import { createHistory as createHistorySingle } from './single-router.js'; import { setHistory } from './history.js'; import Runtime from './runtime.js'; -import App from './App.js'; -import { AppContextProvider } from './AppContext.js'; import { AppDataProvider, getAppData } from './AppData.js'; -import { loadRouteModules, loadRoutesData, getRoutesConfig, filterMatchesToLoad, getRoutesPath } from './routes.js'; -import { updateRoutesConfig } from './routesConfig.js'; +import { getRoutesPath } from './routes.js'; +import type { RouteLoaderOptions } from './routes.js'; import getRequestContext from './requestContext.js'; import getAppConfig from './appConfig.js'; -import matchRoutes from './matchRoutes.js'; import DefaultAppRouter from './AppRouter.js'; import { setFetcher } from './dataLoader.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; @@ -25,7 +21,7 @@ import addLeadingSlash from './utils/addLeadingSlash.js'; export interface RunClientAppOptions { app: AppExport; runtimeModules: RuntimeModules; - routes?: RouteItem[]; + createRoutes?: (options: Pick) => RouteItem[]; hydrate?: boolean; basename?: string; memoryRouter?: boolean; @@ -33,12 +29,10 @@ export interface RunClientAppOptions { dataLoaderFetcher?: Function; } -type History = BrowserHistory | HashHistory | MemoryHistory; - export default async function runClientApp(options: RunClientAppOptions) { const { app, - routes, + createRoutes, runtimeModules, basename, hydrate, @@ -51,8 +45,7 @@ export default async function runClientApp(options: RunClientAppOptions) { const assetsManifest: AssetsManifest = (window as any).__ICE_ASSETS_MANIFEST__ || {}; let { appData, - routesData, - routesConfig, + loaderData, routePath, downgrade, documentOnly, @@ -62,6 +55,10 @@ export default async function runClientApp(options: RunClientAppOptions) { const formattedBasename = addLeadingSlash(basename); const requestContext = getRequestContext(window.location); const appConfig = getAppConfig(app); + const routes = createRoutes ? createRoutes({ + requestContext, + renderMode, + }) : []; const historyOptions = { memoryRouter, initialEntry: routePath, @@ -76,8 +73,7 @@ export default async function runClientApp(options: RunClientAppOptions) { routes, appConfig, appData, - routesData, - routesConfig, + loaderData, assetsManifest, basename: formattedBasename, routePath, @@ -121,11 +117,11 @@ interface RenderOptions { async function render({ history, runtime }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, appData, routes } = appContext; + const { appConfig, appData, routes, loaderData } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; - const RouteWrappers = runtime.getWrappers(); - const AppRouter = runtime.getAppRouter(); + // const RouteWrappers = runtime.getWrappers(); + // const AppRouter = runtime.getAppRouter(); const rootId = appConfig.app.rootId || 'app'; let root = document.getElementById(rootId); @@ -135,8 +131,12 @@ async function render({ history, runtime }: RenderOptions) { document.body.appendChild(root); console.warn(`Root node #${rootId} is not found, current root is automatically created by the framework.`); } - // @ts-ignore - const router = createBrowserRouter(routes); + + const router = createRouter({ + routes, + history, + hydrationData: { loaderData }, + }).initialize(); return appRender( root, @@ -148,167 +148,6 @@ async function render({ history, runtime }: RenderOptions) { ); } -interface BrowserEntryProps { - history: HashHistory | BrowserHistory | null; - appContext: AppContext; - RouteWrappers: RouteWrapperConfig[]; - AppRouter: React.ComponentType; -} - -interface HistoryState { - action: Action; - location: Location; -} - -interface RouteState { - routesData: RoutesData; - routesConfig: RoutesConfig; - matches: RouteMatch[]; - routeModules: RouteModules; -} - -function BrowserEntry({ - history, - appContext, - ...rest -}: BrowserEntryProps) { - const { - routes, - matches: originMatches, - routesData: initialRoutesData, - routesConfig: initialRoutesConfig, - routeModules: initialRouteModules, - basename, - renderMode, - } = appContext; - - const [historyState, setHistoryState] = useState({ - action: history.action, - location: history.location, - }); - const [routeState, setRouteState] = useState({ - routesData: initialRoutesData, - routesConfig: initialRoutesConfig, - matches: originMatches, - routeModules: initialRouteModules, - }); - - const { action, location } = historyState; - const { routesData, routesConfig, matches, routeModules } = routeState; - - // Listen the history change and update the state which including the latest action and location. - useLayoutEffect(() => { - if (history) { - const unlisten = history.listen(({ action, location }) => { - const currentMatches = matchRoutes(routes, location, basename); - if (!currentMatches.length) { - throw new Error(`Routes not found in location ${location.pathname}.`); - } - - loadNextPage( - currentMatches, - routeState, - ).then(({ routesData, routesConfig, routeModules }) => { - setRouteState({ - routesData, - routesConfig, - matches: currentMatches, - routeModules, - }); - setHistoryState({ - action, - location, - }); - }); - }); - - return () => unlisten(); - } - // Should add routeState to dependencies to ensure get the correct state in `history.listen`. - }, [routeState, history, basename, routes]); - - useEffect(() => { - // Rerender page use actual data for ssg. - if (renderMode === 'SSG') { - const initialContext = getRequestContext(window.location); - loadRoutesData(matches, initialContext, routeModules).then(data => { - setRouteState(r => { - return { - ...r, - routesData: data, - }; - }); - }); - } - // Trigger once after first render for SSG to update data. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // update app context for the current route. - const context = { - ...appContext, - matches, - routesData, - routesConfig, - routeModules, - }; - - return ( - - - - ); -} - -/** - * Prepare for the next pages. - * Load modules、getPageData and preLoad the custom assets. - */ -export async function loadNextPage( - currentMatches: RouteMatch[], - preRouteState: RouteState, -) { - const { - matches: preMatches, - routesData: preRoutesData, - routeModules: preRouteModules, - } = preRouteState; - - const routeModules = await loadRouteModules( - currentMatches.map(({ route: { id, load } }) => ({ id, load })), - preRouteModules, - ); - - // load data for changed route. - const initialContext = getRequestContext(window.location); - const matchesToLoad = filterMatchesToLoad(preMatches, currentMatches); - // Navigate to other router should always fetch the latest data. - const data = await loadRoutesData(matchesToLoad, initialContext, routeModules, { - forceRequest: true, - }); - - const routesData: RoutesData = {}; - // merge page data. - currentMatches.forEach(({ route }) => { - const { id } = route; - routesData[id] = data[id] || preRoutesData[id]; - }); - - const routesConfig = getRoutesConfig(currentMatches, routesData, routeModules); - await updateRoutesConfig(currentMatches, routesConfig); - - return { - routesData, - routesConfig, - routeModules, - }; -} - interface HistoryOptions { memoryRouter: boolean; initialEntry?: string; diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 92805c527e..4bf874dc1f 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -1,7 +1,7 @@ import type { ServerResponse } from 'http'; import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; -import { Action, parsePath } from 'history'; +import { parsePath } from 'react-router-dom'; import type { Location } from 'history'; import { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs'; import type { @@ -15,12 +15,12 @@ import type { AppData, } from './types.js'; import Runtime from './runtime.js'; -import App from './App.js'; import { AppContextProvider } from './AppContext.js'; import { AppDataProvider, getAppData } from './AppData.js'; import getAppConfig from './appConfig.js'; import { DocumentContextProvider } from './Document.js'; -import { loadRouteModules, loadRoutesData, getRoutesConfig, RouteComponent } from './routes.js'; +import { loadRouteModules, RouteComponent } from './routes.js'; +import type { RouteLoaderOptions } from './routes.js'; import { pipeToString, renderToNodeStream } from './server/streamRender.js'; import { createStaticNavigator } from './server/navigator.js'; import type { NodeWritablePiper } from './server/streamRender.js'; @@ -34,7 +34,7 @@ import addLeadingSlash from './utils/addLeadingSlash.js'; interface RenderOptions { app: AppExport; assetsManifest: AssetsManifest; - routes: RouteItem[]; + createRoutes: (options: Pick) => RouteItem[]; runtimeModules: RuntimeModules; Document: DocumentComponent; documentOnly?: boolean; @@ -187,7 +187,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio app, basename, serverOnlyBasename, - routes, + createRoutes, documentOnly, disableFallback, assetsManifest, @@ -201,15 +201,17 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio const requestContext = getRequestContext(location, serverContext); const appConfig = getAppConfig(app); - + const routes = createRoutes({ + requestContext, + renderMode, + }); let appData: AppData; const appContext: AppContext = { appExport: app, routes, appConfig, appData, - routesData: null, - routesConfig: null, + loaderData: {}, renderMode, assetsManifest, basename: finalBasename, @@ -234,21 +236,19 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio // HashRouter loads route modules by the CSR. if (appConfig?.router?.type === 'hash') { - return renderDocument({ matches: [], renderOptions }); + return renderDocument({ matches: [], routes, renderOptions }); } const matches = matchRoutes(routes, location, finalBasename); const routePath = getCurrentRoutePath(matches); if (documentOnly) { - return renderDocument({ matches, routePath, renderOptions }); + return renderDocument({ matches, routePath, routes, renderOptions }); } else if (!matches.length) { return render404(); } - console.log(matches); + try { const routeModules = await loadRouteModules(matches.map(({ route: { id, load, lazy } }) => ({ id, load, lazy }))); - const routesData = {}; - const routesConfig = {}; const loaderData = {}; for (const routeId in routeModules) { const { loader } = routeModules[routeId]; @@ -258,15 +258,9 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio data, pageConfig, }; - routesData[routeId] = data; - routesConfig[routeId] = pageConfig; } } - console.log('initialdata ==>', routesData, routesConfig); - // const routesData = await loadRoutesData(matches, requestContext, routeModules, { renderMode }); - // const routesConfig = getRoutesConfig(matches, routesData, routeModules); - // @ts-ignore loaderData - runtime.setAppContext({ ...appContext, routeModules, loaderData, routesData, routesConfig, routePath, matches, appData }); + runtime.setAppContext({ ...appContext, routeModules, loaderData, routePath, matches, appData }); if (runtimeModules.commons) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } @@ -282,7 +276,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio throw err; } console.error('Warning: render server entry error, downgrade to csr.', err); - return renderDocument({ matches, routePath, renderOptions, downgrade: true }); + return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true }); } } @@ -352,7 +346,6 @@ async function renderServerEntry( loaderData, }; const router = createStaticRouter(routes, context); - console.log('router ==>', router); const documentContext = { main: ( { - return renderDocument({ matches, routePath, renderOptions, downgrade: true }); + return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true }); }; return { @@ -392,6 +385,7 @@ async function renderServerEntry( interface RenderDocumentOptions { matches: RouteMatch[]; renderOptions: RenderOptions; + routes: RouteItem[]; routePath?: string; downgrade?: boolean; } @@ -405,10 +399,10 @@ function renderDocument(options: RenderDocumentOptions): RenderResult { renderOptions, routePath, downgrade, + routes, }: RenderDocumentOptions = options; const { - routes, assetsManifest, app, Document, @@ -433,8 +427,7 @@ function renderDocument(options: RenderDocumentOptions): RenderResult { assetsManifest, appConfig, appData, - routesData, - routesConfig: matchedRoutesConfig, + loaderData: {}, matches, routes, documentOnly: true, diff --git a/packages/runtime/src/single-router.tsx b/packages/runtime/src/single-router.tsx index 97b5ab2af5..bb58d8059f 100644 --- a/packages/runtime/src/single-router.tsx +++ b/packages/runtime/src/single-router.tsx @@ -3,7 +3,7 @@ * if user config `optimize.router` false */ import * as React from 'react'; -import type { History } from 'history'; +import type { History } from '@remix-run/router'; export const useRoutes = (routes) => { return <>{routes[0].element}; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 33e071cbfb..76bd710495 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -1,6 +1,6 @@ import type { IncomingMessage, ServerResponse } from 'http'; -import type { Action, InitialEntry, Location } from 'history'; -import type { ComponentType, ReactNode, PropsWithChildren } from 'react'; +import type { Action, InitialEntry, Location, AgnosticRouteObject } from '@remix-run/router'; +import type { ComponentType, PropsWithChildren } from 'react'; import type { HydrationOptions, Root } from 'react-dom/client'; import type { Navigator, Params, RouteObject } from 'react-router-dom'; @@ -71,14 +71,22 @@ export interface RoutesData { [routeId: string]: RouteData; } +export interface LoaderDatas { + [routeId: string]: LoaderData; +} + +export interface LoaderData { + data?: RouteData; + pageConfig?: RouteConfig; +} + // useAppContext export interface AppContext { appConfig: AppConfig; appData: any; serverData?: any; assetsManifest?: AssetsManifest; - routesData?: RoutesData; - routesConfig?: RoutesConfig; + loaderData?: LoaderDatas; routeModules?: RouteModules; routePath?: string; matches?: RouteMatch[]; @@ -88,13 +96,13 @@ export interface AppContext { appExport?: AppExport; basename?: string; downgrade?: boolean; - renderMode?: string; + renderMode?: RenderMode; requestContext?: RequestContext; } export type WindowContext = Pick< AppContext, - 'appData' | 'routesData' | 'routesConfig' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' + 'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' >; export type Renderer = ( @@ -113,28 +121,22 @@ export interface RequestContext extends ServerContext { query: Record; } -export interface RouteComponent { - default: ComponentType; +export type ComponentModule = { + default?: ComponentType; + Component?: ComponentType; staticDataLoader?: DataLoaderConfig; serverDataLoader?: DataLoaderConfig; dataLoader?: DataLoaderConfig; pageConfig?: PageConfig; [key: string]: any; -} +}; -export interface RouteItem { - id: string; - path: string; - element?: ReactNode; +export type RouteItem = AgnosticRouteObject & { componentName: string; - index?: boolean; - exact?: boolean; - strict?: boolean; - load?: () => Promise; - lazy?: () => Promise; - children?: RouteItem[]; + load?: () => Promise; layout?: boolean; -} + children?: RouteItem[]; +}; export type ComponentWithChildren

= ComponentType>; @@ -157,7 +159,7 @@ export type SetRender = (render: Renderer) => void; export type AddWrapper = (wrapper: RouteWrapper, forLayout?: boolean) => void; export interface RouteModules { - [routeId: string]: RouteComponent; + [routeId: string]: ComponentModule; } export interface AssetsManifest { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9353a2d7fc..1cae7ab5f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1021,7 +1021,7 @@ importers: open: ^8.4.0 path-to-regexp: ^6.2.0 react: ^18.2.0 - react-router: ^6.9.0 + react-router: 6.9.0 regenerator-runtime: ^0.13.0 resolve.exports: ^1.1.0 sass: ^1.50.0 @@ -1410,6 +1410,7 @@ importers: packages/runtime: specifiers: '@ice/jsx-runtime': ^0.2.0 + '@remix-run/router': 1.4.0 '@types/react': ^18.0.8 '@types/react-dom': ^18.0.3 ejs: ^3.1.6 @@ -1418,10 +1419,11 @@ importers: htmlparser2: ^8.0.1 react: ^18.0.0 react-dom: ^18.0.0 - react-router-dom: ^6.9.0 + react-router-dom: 6.9.0 regenerator-runtime: ^0.13.9 dependencies: '@ice/jsx-runtime': link:../jsx-runtime + '@remix-run/router': 1.4.0 ejs: 3.1.8 fs-extra: 10.1.0 history: 5.3.0 From 89e9e74fd3a4ff0102741199dcd5492395eaf8f8 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 29 Mar 2023 14:52:00 +0800 Subject: [PATCH 03/38] refactor: render mode --- .../ice/src/middlewares/renderMiddleware.ts | 1 - packages/ice/src/routes.ts | 26 ++++--- packages/ice/src/service/ServerRunner.ts | 4 - packages/ice/templates/core/routes.ts.ejs | 2 +- .../miniapp-runtime/src/app/runClientApp.tsx | 22 +----- packages/runtime/src/App.tsx | 20 +++++ packages/runtime/src/AppContext.tsx | 6 ++ packages/runtime/src/AppRouter.tsx | 34 -------- packages/runtime/src/AppWrapper.ts | 0 packages/runtime/src/ClientRouter.tsx | 30 +++++++ packages/runtime/src/Document.tsx | 6 +- packages/runtime/src/RouteContext.ts | 2 - packages/runtime/src/RouteWrapper.tsx | 3 - packages/runtime/src/ServerRouter.tsx | 51 ++++++++++++ .../runtime/src/{AppData.tsx => appData.ts} | 14 ---- packages/runtime/src/index.ts | 10 +-- packages/runtime/src/routes.tsx | 9 +-- packages/runtime/src/runClientApp.tsx | 36 ++++----- packages/runtime/src/runServerApp.tsx | 78 +++++-------------- packages/runtime/src/runtime.tsx | 7 +- packages/runtime/src/types.ts | 11 ++- 21 files changed, 180 insertions(+), 192 deletions(-) create mode 100644 packages/runtime/src/App.tsx delete mode 100644 packages/runtime/src/AppRouter.tsx create mode 100644 packages/runtime/src/AppWrapper.ts create mode 100644 packages/runtime/src/ClientRouter.tsx create mode 100644 packages/runtime/src/ServerRouter.tsx rename packages/runtime/src/{AppData.tsx => appData.ts} (70%) diff --git a/packages/ice/src/middlewares/renderMiddleware.ts b/packages/ice/src/middlewares/renderMiddleware.ts index d9c46cc02c..9aa4d77244 100644 --- a/packages/ice/src/middlewares/renderMiddleware.ts +++ b/packages/ice/src/middlewares/renderMiddleware.ts @@ -65,7 +65,6 @@ export default function createRenderMiddleware(options: Options): Middleware { req, res, }; - serverModule.renderToResponse(requestContext, { renderMode, documentOnly, diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index 1a4c9f3730..bcf89f88c0 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -69,18 +69,22 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la const routeProperties: string[] = [ `path: '${formatPath(routePath || '')}',`, `async lazy() { - const componentModule = await ${loadStatement}; - const loader = createRouteLoader({ + const componentModule = await ${loadStatement}; + const loader = createRouteLoader({ + routeId: '${id}', + requestContext, + renderMode, + module: componentModule, + }); + return { + Component: () => wrapRouteComponent({ routeId: '${id}', - requestContext, - renderMode, - module: componentModule, - }); - return { - Component: componentModule.default, - loader, - }; - },`, + isLayout: ${layout}, + RouteComponent: componentModule.default, + }), + loader, + }; + },`, `componentName: '${componentName}',`, `index: ${index},`, `id: '${id}',`, diff --git a/packages/ice/src/service/ServerRunner.ts b/packages/ice/src/service/ServerRunner.ts index b809aa60a0..7ec68bf824 100644 --- a/packages/ice/src/service/ServerRunner.ts +++ b/packages/ice/src/service/ServerRunner.ts @@ -269,10 +269,6 @@ class ServerRunner extends Runner { return { externalize: bundlePath }; } - if (id.includes('runtimeModule')) { - console.log('code ===>', await transformJsxRuntime(code)); - } - return { code: await transformJsxRuntime(code), }; diff --git a/packages/ice/templates/core/routes.ts.ejs b/packages/ice/templates/core/routes.ts.ejs index cbba09c485..5286a65fdd 100644 --- a/packages/ice/templates/core/routes.ts.ejs +++ b/packages/ice/templates/core/routes.ts.ejs @@ -1,4 +1,4 @@ -import { createRouteLoader } from '@ice/runtime'; +import { createRouteLoader, wrapRouteComponent } from '@ice/runtime'; <%- routeImports.length ? routeImports.join('\n') + '\n\n' : ''; -%> export default ({ requestContext, diff --git a/packages/miniapp-runtime/src/app/runClientApp.tsx b/packages/miniapp-runtime/src/app/runClientApp.tsx index 4248e4eee2..3954dc3ec0 100644 --- a/packages/miniapp-runtime/src/app/runClientApp.tsx +++ b/packages/miniapp-runtime/src/app/runClientApp.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { AppContext, RunClientAppOptions, } from '@ice/runtime'; -import { AppContextProvider, AppDataProvider, getAppData, getAppConfig, Runtime } from '@ice/runtime'; +import { AppContextProvider, getAppData, getAppConfig, Runtime } from '@ice/runtime'; import { eventCenter } from '../emitter/emitter.js'; import { APP_READY } from '../constants/index.js'; import App from './App.js'; @@ -48,24 +48,10 @@ async function render( render( document.getElementById('ice-container'), - + - + - , - ); -} - -interface BrowserEntryProps { - appContext: AppContext; -} - -function BrowserEntry({ - appContext, -}: BrowserEntryProps) { - return ( - - - + , ); } diff --git a/packages/runtime/src/App.tsx b/packages/runtime/src/App.tsx new file mode 100644 index 0000000000..823c27031d --- /dev/null +++ b/packages/runtime/src/App.tsx @@ -0,0 +1,20 @@ + +import React from 'react'; +import AppErrorBoundary from './AppErrorBoundary.js'; +import { useAppContext } from './AppContext.js'; + +export default function App({ children }) { + const { appConfig } = useAppContext(); + const { strict, errorBoundary } = appConfig.app; + const StrictMode = strict ? React.StrictMode : React.Fragment; + + const ErrorBoundary = errorBoundary ? AppErrorBoundary : React.Fragment; + + return ( + + + {children} + + + ); +} diff --git a/packages/runtime/src/AppContext.tsx b/packages/runtime/src/AppContext.tsx index aa09b2537b..ff05e7fdbf 100644 --- a/packages/runtime/src/AppContext.tsx +++ b/packages/runtime/src/AppContext.tsx @@ -10,9 +10,15 @@ function useAppContext() { return value; } +function useAppData() { + const value = React.useContext(Context); + return value.appData; +} + const AppContextProvider = Context.Provider; export { useAppContext, + useAppData, AppContextProvider, }; diff --git a/packages/runtime/src/AppRouter.tsx b/packages/runtime/src/AppRouter.tsx deleted file mode 100644 index 070009e457..0000000000 --- a/packages/runtime/src/AppRouter.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react'; -import type { RouteObject } from 'react-router-dom'; -import { Router, useRoutes } from 'react-router-dom'; -import type { AppRouterProps } from './types.js'; -import { Router as RouterSingle, useRoutes as useRoutesSingle } from './single-router.js'; - -const AppRouter: React.ComponentType = (props) => { - const { action, location, navigator, static: staticProps, routes, basename } = props; - const IceRouter = process.env.ICE_CORE_ROUTER === 'true' ? Router : RouterSingle; - - return ( - - - - ); -}; - -interface RoutesProps { - routes: RouteObject[]; -} - -function Routes({ routes }: RoutesProps) { - const useIceRoutes = process.env.ICE_CORE_ROUTER === 'true' ? useRoutes : useRoutesSingle; - const element = useIceRoutes(routes); - return element; -} - -export default AppRouter; diff --git a/packages/runtime/src/AppWrapper.ts b/packages/runtime/src/AppWrapper.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx new file mode 100644 index 0000000000..cc13d3c344 --- /dev/null +++ b/packages/runtime/src/ClientRouter.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { RouterProvider } from 'react-router-dom'; +import { createRouter } from '@remix-run/router'; +import type { AppRouterProps } from './types.js'; +import App from './App.js'; +import { Router } from './single-router.js'; + +function ClientRouter(props: AppRouterProps) { + const { routes, history, loaderData } = props; + let element: React.ReactNode; + if (process.env.ICE_CORE_ROUTER === 'true') { + const routerOptions = { + routes, + history, + hydrationData: loaderData ? { loaderData } : undefined, + }; + const router = createRouter(routerOptions).initialize(); + element = ; + } else { + element = ; + } + + return ( + + {element} + + ); +} + +export default ClientRouter; diff --git a/packages/runtime/src/Document.tsx b/packages/runtime/src/Document.tsx index b4736c0c36..0d32fa969a 100644 --- a/packages/runtime/src/Document.tsx +++ b/packages/runtime/src/Document.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import type { WindowContext, RouteMatch, AssetsManifest } from './types.js'; -import { useAppContext } from './AppContext.js'; -import { useAppData } from './AppData.js'; +import { useAppContext, useAppData } from './AppContext.js'; import { getMeta, getTitle, getLinks, getScripts } from './routesConfig.js'; import getCurrentRoutePath from './utils/getCurrentRoutePath.js'; @@ -105,13 +104,10 @@ export const Scripts: ScriptsType = (props: ScriptsProps) => { } = props; const routeScripts = getScripts(matches, loaderData); - console.log('document matches', matches); - console.log('document loaderData', loaderData); const pageAssets = getPageAssets(matches, assetsManifest); const entryAssets = getEntryAssets(assetsManifest); // Page assets need to be load before entry assets, so when call dynamic import won't cause duplicate js chunk loaded. let scripts = pageAssets.concat(entryAssets).filter(path => path.indexOf('.js') > -1); - console.log('scripts ==>', assetsManifest, scripts); if (assetsManifest.dataLoader) { scripts.unshift(`${assetsManifest.publicPath}${assetsManifest.dataLoader}`); } diff --git a/packages/runtime/src/RouteContext.ts b/packages/runtime/src/RouteContext.ts index c082f4922d..6f0c9f3e48 100644 --- a/packages/runtime/src/RouteContext.ts +++ b/packages/runtime/src/RouteContext.ts @@ -7,7 +7,6 @@ DataContext.displayName = 'Data'; function useData(): T { const data = useLoaderData(); - console.log('data ==>', data); return (data as any).data; } const DataProvider = DataContext.Provider; @@ -17,7 +16,6 @@ ConfigContext.displayName = 'Config'; function useConfig(): RouteConfig { const data = useLoaderData(); - console.log('pageConfig ==>', data); return (data as any).pageConfig; } const ConfigProvider = ConfigContext.Provider; diff --git a/packages/runtime/src/RouteWrapper.tsx b/packages/runtime/src/RouteWrapper.tsx index 07cfaf2755..1020c7bf1d 100644 --- a/packages/runtime/src/RouteWrapper.tsx +++ b/packages/runtime/src/RouteWrapper.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; import type { RouteWrapperConfig } from './types.js'; -import { useAppContext } from './AppContext.js'; -import { DataProvider, ConfigProvider } from './RouteContext.js'; interface Props { id: string; @@ -12,7 +10,6 @@ interface Props { export default function RouteWrapper(props: Props) { const { wrappers = [], id, isLayout } = props; - const { loaderData } = useAppContext(); // layout should only be wrapped by Wrapper with `layout: true` const filtered = isLayout ? wrappers.filter(wrapper => wrapper.layout === true) : wrappers; const RouteWrappers = filtered.map(item => item.Wrapper); diff --git a/packages/runtime/src/ServerRouter.tsx b/packages/runtime/src/ServerRouter.tsx new file mode 100644 index 0000000000..fc4abeaf0c --- /dev/null +++ b/packages/runtime/src/ServerRouter.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs'; +import type { AppRouterProps } from './types.js'; +import { RouteItem } from './types.js'; +import { useAppContext } from './AppContext.js'; +import App from './App.js'; +import { RouteComponent } from './routes.js'; + + +function createServerRoutes(routes: AppRouterProps['routes']) { + return routes.map((route) => { + let dataRoute = { + // Static Router need element or Component when matched. + element: , + id: route.id, + index: route.index, + path: route.path, + children: null, + }; + + if (route?.children?.length > 0) { + let children = createServerRoutes( + route.children, + ); + dataRoute.children = children; + } + return dataRoute; + }); +} + +function ServerRouter(props: AppRouterProps) { + const { routes, loaderData, location } = props; + const { matches, basename } = useAppContext(); + + const routerContext = { + matches, basename, loaderData, location, + }; + + const router = createStaticRouter(createServerRoutes(routes), routerContext); + + return ( + + + + ); +} + +export default ServerRouter; diff --git a/packages/runtime/src/AppData.tsx b/packages/runtime/src/appData.ts similarity index 70% rename from packages/runtime/src/AppData.tsx rename to packages/runtime/src/appData.ts index b90fd84d68..351b3a6ade 100644 --- a/packages/runtime/src/AppData.tsx +++ b/packages/runtime/src/appData.ts @@ -1,18 +1,6 @@ -import * as React from 'react'; import type { AppExport, AppData, RequestContext } from './types.js'; import { callDataLoader } from './dataLoader.js'; -const Context = React.createContext(undefined); - -Context.displayName = 'AppDataContext'; - -function useAppData (): T { - const value = React.useContext(Context); - return value; -} - -const AppDataProvider = Context.Provider; - /** * Call the getData of app config. */ @@ -37,6 +25,4 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) export { getAppData, - useAppData, - AppDataProvider, }; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index a5ddf4a139..fd19e53765 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -22,8 +22,8 @@ import type { import Runtime from './runtime.js'; import runClientApp from './runClientApp.js'; import type { RunClientAppOptions } from './runClientApp.js'; -import { useAppContext, AppContextProvider } from './AppContext.js'; -import { useAppData, AppDataProvider, getAppData } from './AppData.js'; +import { useAppContext, useAppData, AppContextProvider } from './AppContext.js'; +import { getAppData } from './appData.js'; import { useData, useConfig, DataProvider, ConfigProvider } from './RouteContext.js'; import { Meta, @@ -43,7 +43,6 @@ import type { } from './Document.js'; import dataLoader, { defineDataLoader, defineServerDataLoader, defineStaticDataLoader, callDataLoader } from './dataLoader.js'; import getRequestContext from './requestContext.js'; -import AppRouter from './AppRouter.js'; import AppErrorBoundary from './AppErrorBoundary.js'; import getAppConfig, { defineAppConfig } from './appConfig.js'; import { routerHistory as history } from './history.js'; @@ -51,7 +50,7 @@ import KeepAliveOutlet from './KeepAliveOutlet.js'; import ClientOnly from './ClientOnly.js'; import useMounted from './useMounted.js'; import { withSuspense, useSuspenseData } from './Suspense.js'; -import { createRouteLoader } from './routes.js'; +import { createRouteLoader, wrapRouteComponent } from './routes.js'; export { getAppConfig, @@ -60,7 +59,6 @@ export { runClientApp, AppContextProvider, useAppContext, - AppDataProvider, useAppData, useData, getAppData, @@ -89,7 +87,6 @@ export { history, KeepAliveOutlet, - AppRouter, AppErrorBoundary, ClientOnly, useMounted, @@ -98,6 +95,7 @@ export { useSuspenseData, createRouteLoader, + wrapRouteComponent, }; export type { diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 555a3d9971..2b82e323de 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -56,9 +56,9 @@ export function wrapRouteComponent(options: { routeId: string; isLayout?: boolean; RouteComponent: React.ComponentType; - RouteWrappers?: RouteWrapperConfig[]; }) { - const { routeId, isLayout, RouteComponent, RouteWrappers } = options; + const { routeId, isLayout, RouteComponent } = options; + const { RouteWrappers } = useAppContext(); return ( @@ -69,7 +69,6 @@ export function wrapRouteComponent(options: { export function RouteComponent({ id }: { id: string }) { // get current route component from latest routeModules const { routeModules } = useAppContext(); - console.log('routeModules[id]', routeModules[id]); const { Component } = routeModules[id] || {}; if (process.env.NODE_ENV === 'development') { if (!Component) { @@ -101,7 +100,6 @@ export function createRouteLoader(options: RouteLoaderOptions): () => Promise { const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module; const { requestContext, renderMode, routeId } = options; - const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; const globalLoader = hasGlobalLoader ? (window as any).__ICE_DATA_LOADER__ : null; let routeData: any; @@ -120,9 +118,10 @@ export function createRouteLoader(options: RouteLoaderOptions): () => Promise', loaderData); // CSR and load next route data. if (typeof window !== 'undefined') { - // await updateRoutesConfig(loaderData); + await updateRoutesConfig(loaderData); } return loaderData; }; diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 6d802eaf4c..72896e43dc 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -1,7 +1,6 @@ import React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { RouterProvider } from 'react-router-dom'; -import { createRouter, createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; +import { createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; import type { History } from '@remix-run/router'; import type { AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, @@ -9,14 +8,15 @@ import type { import { createHistory as createHistorySingle } from './single-router.js'; import { setHistory } from './history.js'; import Runtime from './runtime.js'; -import { AppDataProvider, getAppData } from './AppData.js'; +import { getAppData } from './appData.js'; import { getRoutesPath } from './routes.js'; import type { RouteLoaderOptions } from './routes.js'; import getRequestContext from './requestContext.js'; import getAppConfig from './appConfig.js'; -import DefaultAppRouter from './AppRouter.js'; +import ClientRouter from './ClientRouter.js'; import { setFetcher } from './dataLoader.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; +import { AppContextProvider } from './AppContext.js'; export interface RunClientAppOptions { app: AppExport; @@ -83,7 +83,7 @@ export default async function runClientApp(options: RunClientAppOptions) { }; const runtime = new Runtime(appContext, runtimeOptions); - runtime.setAppRouter(DefaultAppRouter); + runtime.setAppRouter(ClientRouter); // Load static module before getAppData, // so we can call request in in getAppData which provide by `plugin-request`. if (runtimeModules.statics) { @@ -96,7 +96,8 @@ export default async function runClientApp(options: RunClientAppOptions) { appData = await getAppData(app, requestContext); } - if (hydrate && !downgrade && !documentOnly) { + const needHydrate = hydrate && !downgrade && !documentOnly; + if (needHydrate) { runtime.setRender((container, element) => { return ReactDOM.hydrateRoot(container, element); }); @@ -107,21 +108,21 @@ export default async function runClientApp(options: RunClientAppOptions) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } - return render({ runtime, history }); + return render({ runtime, history, needHydrate }); } interface RenderOptions { history: History; runtime: Runtime; + needHydrate: boolean; } -async function render({ history, runtime }: RenderOptions) { +async function render({ history, runtime, needHydrate }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, appData, routes, loaderData } = appContext; + const { appConfig, loaderData, routes } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; - // const RouteWrappers = runtime.getWrappers(); - // const AppRouter = runtime.getAppRouter(); + const AppRouter = runtime.getAppRouter(); const rootId = appConfig.app.rootId || 'app'; let root = document.getElementById(rootId); @@ -131,20 +132,13 @@ async function render({ history, runtime }: RenderOptions) { document.body.appendChild(root); console.warn(`Root node #${rootId} is not found, current root is automatically created by the framework.`); } - - const router = createRouter({ - routes, - history, - hydrationData: { loaderData }, - }).initialize(); - return appRender( root, - + - Loading...

} /> + - , + , ); } diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 4bf874dc1f..b87a44cd55 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { parsePath } from 'react-router-dom'; import type { Location } from 'history'; -import { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs'; import type { AppContext, RouteItem, ServerContext, AppExport, AssetsManifest, @@ -16,18 +15,17 @@ import type { } from './types.js'; import Runtime from './runtime.js'; import { AppContextProvider } from './AppContext.js'; -import { AppDataProvider, getAppData } from './AppData.js'; +import { getAppData } from './appData.js'; import getAppConfig from './appConfig.js'; import { DocumentContextProvider } from './Document.js'; -import { loadRouteModules, RouteComponent } from './routes.js'; +import { loadRouteModules } from './routes.js'; import type { RouteLoaderOptions } from './routes.js'; import { pipeToString, renderToNodeStream } from './server/streamRender.js'; -import { createStaticNavigator } from './server/navigator.js'; import type { NodeWritablePiper } from './server/streamRender.js'; import getRequestContext from './requestContext.js'; import matchRoutes from './matchRoutes.js'; import getCurrentRoutePath from './utils/getCurrentRoutePath.js'; -import DefaultAppRouter from './AppRouter.js'; +import ServerRouter from './ServerRouter.js'; import { renderHTMLToJS } from './renderHTMLToJS.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; @@ -69,6 +67,7 @@ export async function renderToEntry( requestContext: ServerContext, renderOptions: RenderOptions, ) { + console.log('renderToEntry.tsx'); const result = await renderToHTML(requestContext, renderOptions); const { value } = result; @@ -98,6 +97,7 @@ export async function renderToHTML( requestContext: ServerContext, renderOptions: RenderOptions, ): Promise { + console.log('renderHTMLToJS.tsx'); const result = await doRender(requestContext, renderOptions); const { value } = result; @@ -220,7 +220,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio serverData, }; const runtime = new Runtime(appContext, runtimeOptions); - runtime.setAppRouter(DefaultAppRouter); + runtime.setAppRouter(ServerRouter); // Load static module before getAppData. if (runtimeModules.statics) { await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean)); @@ -294,28 +294,6 @@ interface RenderServerEntry { location: Location; renderOptions: RenderOptions; } - -function createServerRoutes(routes, routeModules) { - return routes.map((route) => { - let dataRoute = { - element: , - id: route.id, - index: route.index, - path: route.path, - }; - - if (route?.children?.length > 0) { - let children = createServerRoutes( - routes.children, - routeModules, - ); - // @ts-ignore - dataRoute.children = children; - } - return dataRoute; - }); -} - /** * Render App by SSR. */ @@ -329,43 +307,22 @@ async function renderServerEntry( ): Promise { const { Document } = renderOptions; const appContext = runtime.getAppContext(); - // @ts-ignore - const { appData, routePath, routeModules, loaderData } = appContext; - const staticNavigator = createStaticNavigator(); + const { routes, routePath, loaderData } = appContext; const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; - const RouteWrappers = runtime.getWrappers(); const AppRouter = runtime.getAppRouter(); - let routes = createServerRoutes( - appContext.routes, - routeModules, - ); - const context = { - matches, - basemane: appContext.basename, - location, - loaderData, - }; - const router = createStaticRouter(routes, context); const documentContext = { main: ( - + ), }; - const element = ( - + - - - - - + + + - + ); const pipe = renderToNodeStream(element); @@ -411,23 +368,24 @@ function renderDocument(options: RenderDocumentOptions): RenderResult { serverData, } = renderOptions; - const routesData = null; const appData = null; const appConfig = getAppConfig(app); - const matchedRoutesConfig = {}; + const loaderData = {}; matches.forEach(async (match) => { const { id } = match.route; const pageConfig = routesConfig[id]; - matchedRoutesConfig[id] = pageConfig ? pageConfig({}) : {}; + loaderData[id] = { + pageConfig: pageConfig ? pageConfig({}) : {}, + }; }); const appContext: AppContext = { assetsManifest, appConfig, appData, - loaderData: {}, + loaderData, matches, routes, documentOnly: true, diff --git a/packages/runtime/src/runtime.tsx b/packages/runtime/src/runtime.tsx index 95e1809691..a5b24aaeab 100644 --- a/packages/runtime/src/runtime.tsx +++ b/packages/runtime/src/runtime.tsx @@ -44,7 +44,12 @@ class Runtime { this.runtimeOptions = runtimeOptions; } - public getAppContext = () => this.appContext; + public getAppContext = () => { + return { + ...this.appContext, + RouteWrappers: this.RouteWrappers, + }; + }; public setAppContext = (appContext: AppContext) => { this.appContext = appContext; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 76bd710495..ae0403573a 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 { Action, InitialEntry, Location, AgnosticRouteObject } from '@remix-run/router'; +import type { InitialEntry, History, Location, AgnosticRouteObject } from '@remix-run/router'; import type { ComponentType, PropsWithChildren } from 'react'; import type { HydrationOptions, Root } from 'react-dom/client'; import type { Navigator, Params, RouteObject } from 'react-router-dom'; @@ -88,6 +88,7 @@ export interface AppContext { assetsManifest?: AssetsManifest; loaderData?: LoaderDatas; routeModules?: RouteModules; + RouteWrappers?: RouteWrapperConfig[]; routePath?: string; matches?: RouteMatch[]; routes?: RouteItem[]; @@ -218,12 +219,10 @@ export interface RuntimeModules { } export interface AppRouterProps { - action: Action; - location: Location; - navigator: Navigator; + history?: History; routes: RouteObject[]; - static?: boolean; - basename?: string; + loaderData?: LoaderDatas; + location?: Location; } export interface AppRouteProps { From 3787d3453a140872425b6bd41eae50cf43f34e50 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 29 Mar 2023 19:34:25 +0800 Subject: [PATCH 04/38] fix: code splitting false --- packages/ice/src/routes.ts | 29 ++++++------ packages/ice/templates/core/routes.ts.ejs | 2 +- .../miniapp-runtime/src/app/runClientApp.tsx | 1 - packages/runtime/src/App.tsx | 1 - packages/runtime/src/ClientRouter.tsx | 14 ++---- packages/runtime/src/Document.tsx | 3 +- .../src/{AppWrapper.ts => RenderWrapper.ts} | 0 packages/runtime/src/ServerRouter.tsx | 35 +-------------- packages/runtime/src/dataLoader.ts | 3 +- packages/runtime/src/index.ts | 4 +- packages/runtime/src/routes.tsx | 13 +++--- packages/runtime/src/runClientApp.tsx | 42 ++++++++++++++--- packages/runtime/src/runServerApp.tsx | 45 ++++++++++++++++--- packages/runtime/src/types.ts | 16 +++---- 14 files changed, 115 insertions(+), 93 deletions(-) rename packages/runtime/src/{AppWrapper.ts => RenderWrapper.ts} (100%) diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index bcf89f88c0..a4a5ff16aa 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -66,23 +66,24 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la routeImports.push(`import * as ${routeSpecifier} from '${formatPath(componentPath)}';`); loadStatement = routeSpecifier; } + const component = `Component: () => WrapRouteComponent({ + routeId: '${id}', + isLayout: ${layout}, + RouteComponent: ${lazy ? 'componentModule' : loadStatement}.default, + })`; + const loader = `loader: createRouteLoader({ + routeId: '${id}', + requestContext, + renderMode, + module: ${lazy ? 'componentModule' : loadStatement}, + })`; const routeProperties: string[] = [ `path: '${formatPath(routePath || '')}',`, `async lazy() { - const componentModule = await ${loadStatement}; - const loader = createRouteLoader({ - routeId: '${id}', - requestContext, - renderMode, - module: componentModule, - }); + ${lazy ? `const componentModule = await ${loadStatement}` : ''}; return { - Component: () => wrapRouteComponent({ - routeId: '${id}', - isLayout: ${layout}, - RouteComponent: componentModule.default, - }), - loader, + ${component}, + ${loader}, }; },`, `componentName: '${componentName}',`, @@ -90,7 +91,7 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la `id: '${id}',`, 'exact: true,', `exports: ${JSON.stringify(exports)},`, - ]; + ].filter(Boolean); if (layout) { routeProperties.push('layout: true,'); diff --git a/packages/ice/templates/core/routes.ts.ejs b/packages/ice/templates/core/routes.ts.ejs index 5286a65fdd..3e1f7cec9e 100644 --- a/packages/ice/templates/core/routes.ts.ejs +++ b/packages/ice/templates/core/routes.ts.ejs @@ -1,4 +1,4 @@ -import { createRouteLoader, wrapRouteComponent } from '@ice/runtime'; +import { createRouteLoader, WrapRouteComponent } from '@ice/runtime'; <%- routeImports.length ? routeImports.join('\n') + '\n\n' : ''; -%> export default ({ requestContext, diff --git a/packages/miniapp-runtime/src/app/runClientApp.tsx b/packages/miniapp-runtime/src/app/runClientApp.tsx index 3954dc3ec0..9a0884039b 100644 --- a/packages/miniapp-runtime/src/app/runClientApp.tsx +++ b/packages/miniapp-runtime/src/app/runClientApp.tsx @@ -42,7 +42,6 @@ async function render( runtime: Runtime, ) { const appContext = runtime.getAppContext(); - const { appData } = appContext; const render = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; diff --git a/packages/runtime/src/App.tsx b/packages/runtime/src/App.tsx index 823c27031d..effd7ef575 100644 --- a/packages/runtime/src/App.tsx +++ b/packages/runtime/src/App.tsx @@ -7,7 +7,6 @@ export default function App({ children }) { const { appConfig } = useAppContext(); const { strict, errorBoundary } = appConfig.app; const StrictMode = strict ? React.StrictMode : React.Fragment; - const ErrorBoundary = errorBoundary ? AppErrorBoundary : React.Fragment; return ( diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index cc13d3c344..04e8bee92f 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -1,25 +1,19 @@ import React from 'react'; import { RouterProvider } from 'react-router-dom'; -import { createRouter } from '@remix-run/router'; + import type { AppRouterProps } from './types.js'; import App from './App.js'; import { Router } from './single-router.js'; function ClientRouter(props: AppRouterProps) { - const { routes, history, loaderData } = props; + const { router, routes } = props; + let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { - const routerOptions = { - routes, - history, - hydrationData: loaderData ? { loaderData } : undefined, - }; - const router = createRouter(routerOptions).initialize(); - element = ; + element = loading} />; } else { element = ; } - return ( {element} diff --git a/packages/runtime/src/Document.tsx b/packages/runtime/src/Document.tsx index 0d32fa969a..299d219729 100644 --- a/packages/runtime/src/Document.tsx +++ b/packages/runtime/src/Document.tsx @@ -145,7 +145,7 @@ export type DataType = (props: DataProps) => JSX.Element; // use app context separately export const Data: DataType = (props: DataProps) => { - const { documentOnly, matches, downgrade, renderMode, serverData, loaderData } = useAppContext(); + const { documentOnly, matches, downgrade, renderMode, serverData, loaderData, revalidate } = useAppContext(); const appData = useAppData(); const { ScriptElement = 'script', @@ -162,6 +162,7 @@ export const Data: DataType = (props: DataProps) => { documentOnly, renderMode, serverData, + revalidate, }; return ( diff --git a/packages/runtime/src/AppWrapper.ts b/packages/runtime/src/RenderWrapper.ts similarity index 100% rename from packages/runtime/src/AppWrapper.ts rename to packages/runtime/src/RenderWrapper.ts diff --git a/packages/runtime/src/ServerRouter.tsx b/packages/runtime/src/ServerRouter.tsx index fc4abeaf0c..dad0dea0fa 100644 --- a/packages/runtime/src/ServerRouter.tsx +++ b/packages/runtime/src/ServerRouter.tsx @@ -1,42 +1,11 @@ import React from 'react'; -import { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs'; +import { StaticRouterProvider } from 'react-router-dom/server.mjs'; import type { AppRouterProps } from './types.js'; -import { RouteItem } from './types.js'; -import { useAppContext } from './AppContext.js'; import App from './App.js'; -import { RouteComponent } from './routes.js'; -function createServerRoutes(routes: AppRouterProps['routes']) { - return routes.map((route) => { - let dataRoute = { - // Static Router need element or Component when matched. - element: , - id: route.id, - index: route.index, - path: route.path, - children: null, - }; - - if (route?.children?.length > 0) { - let children = createServerRoutes( - route.children, - ); - dataRoute.children = children; - } - return dataRoute; - }); -} - function ServerRouter(props: AppRouterProps) { - const { routes, loaderData, location } = props; - const { matches, basename } = useAppContext(); - - const routerContext = { - matches, basename, loaderData, location, - }; - - const router = createStaticRouter(createServerRoutes(routes), routerContext); + const { router, routerContext } = props; return ( diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index c2632f6dfe..dcb80936be 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -1,6 +1,5 @@ -import type { DataLoaderConfig, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; +import type { RequestContext, RenderMode, DataLoaderConfig, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; import getRequestContext from './requestContext.js'; -import type { RequestContext, RenderMode } from './types.js'; interface Loaders { [routeId: string]: DataLoaderConfig; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index fd19e53765..48a5812db2 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -50,7 +50,7 @@ import KeepAliveOutlet from './KeepAliveOutlet.js'; import ClientOnly from './ClientOnly.js'; import useMounted from './useMounted.js'; import { withSuspense, useSuspenseData } from './Suspense.js'; -import { createRouteLoader, wrapRouteComponent } from './routes.js'; +import { createRouteLoader, WrapRouteComponent } from './routes.js'; export { getAppConfig, @@ -95,7 +95,7 @@ export { useSuspenseData, createRouteLoader, - wrapRouteComponent, + WrapRouteComponent, }; export type { diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 2b82e323de..3dd8f3f85f 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import type { RouteItem, RouteModules, RouteWrapperConfig, RenderMode, DataLoaderConfig } from './types.js'; +import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; -import type { RequestContext, ComponentModule } from './types.js'; import { updateRoutesConfig } from './routesConfig.js'; -type RouteModule = Pick; +type RouteModule = Pick; export function getRoutesPath(routes: RouteItem[], parentPath = ''): string[] { let paths = []; @@ -23,7 +22,7 @@ export function getRoutesPath(routes: RouteItem[], parentPath = ''): string[] { } export async function loadRouteModule(route: RouteModule, routeModulesCache = {}) { - const { id, load, lazy } = route; + const { id, lazy } = route; if ( typeof window !== 'undefined' && // Don't use module cache and should load again in ssr. Ref: https://github.com/ice-lab/ice-next/issues/82 id in routeModulesCache @@ -32,8 +31,7 @@ export async function loadRouteModule(route: RouteModule, routeModulesCache = {} } try { - // Function load will return route module when lazy loaded is disabled. - const routeModule = lazy ? await lazy() : await load(); + const routeModule = await lazy(); routeModulesCache[id] = routeModule; return routeModule; } catch (error) { @@ -52,7 +50,7 @@ export async function loadRouteModules(routes: RouteModule[], originRouteModules } // Wrap route component with runtime wrappers. -export function wrapRouteComponent(options: { +export function WrapRouteComponent(options: { routeId: string; isLayout?: boolean; RouteComponent: React.ComponentType; @@ -118,7 +116,6 @@ export function createRouteLoader(options: RouteLoaderOptions): () => Promise', loaderData); // CSR and load next route data. if (typeof window !== 'undefined') { await updateRoutesConfig(loaderData); diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 72896e43dc..c015ce7956 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -1,6 +1,6 @@ import React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; +import { createRouter, createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; import type { History } from '@remix-run/router'; import type { AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, @@ -51,13 +51,14 @@ export default async function runClientApp(options: RunClientAppOptions) { documentOnly, renderMode, serverData, + revalidate, } = windowContext; const formattedBasename = addLeadingSlash(basename); const requestContext = getRequestContext(window.location); const appConfig = getAppConfig(app); const routes = createRoutes ? createRoutes({ requestContext, - renderMode, + renderMode: 'CSR', }) : []; const historyOptions = { memoryRouter, @@ -80,6 +81,7 @@ export default async function runClientApp(options: RunClientAppOptions) { renderMode, requestContext, serverData, + revalidate, }; const runtime = new Runtime(appContext, runtimeOptions); @@ -98,8 +100,21 @@ export default async function runClientApp(options: RunClientAppOptions) { const needHydrate = hydrate && !downgrade && !documentOnly; if (needHydrate) { + const defaultOnRecoverableError = typeof reportError === 'function' ? reportError + : function (error: unknown) { + console['error'](error); + }; runtime.setRender((container, element) => { - return ReactDOM.hydrateRoot(container, element); + const hydrateOptions = revalidate + ? { + onRecoverableError(error: unknown) { + // Ignore this error caused by router.revalidate + if ((error as Error)?.message?.indexOf('This Suspense boundary received an update before it finished hydrating.') == -1) { + defaultOnRecoverableError(error); + } + }, + } : {}; + return ReactDOM.hydrateRoot(container, element, hydrateOptions); }); } // Reset app context after app context is updated. @@ -119,7 +134,7 @@ interface RenderOptions { async function render({ history, runtime, needHydrate }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, loaderData, routes } = appContext; + const { appConfig, loaderData, routes, revalidate } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const AppRouter = runtime.getAppRouter(); @@ -132,14 +147,29 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { document.body.appendChild(root); console.warn(`Root node #${rootId} is not found, current root is automatically created by the framework.`); } - return appRender( + const hydrationData = needHydrate ? { loaderData } : undefined; + const routerOptions = { + routes, + history, + hydrationData, + }; + // Create router before render. + const router = process.env.ICE_CORE_ROUTER === 'true' ? createRouter(routerOptions).initialize() : null; + + const renderRoot = appRender( root, - + , ); + if (revalidate) { + setTimeout(() => { + router?.revalidate(); + }); + } + return renderRoot; } interface HistoryOptions { diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index b87a44cd55..981c5042b6 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -2,7 +2,9 @@ import type { ServerResponse } from 'http'; import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { parsePath } from 'react-router-dom'; +import { createStaticRouter } from 'react-router-dom/server.mjs'; import type { Location } from 'history'; +import type { RouteObject } from 'react-router-dom'; import type { AppContext, RouteItem, ServerContext, AppExport, AssetsManifest, @@ -18,7 +20,7 @@ import { AppContextProvider } from './AppContext.js'; import { getAppData } from './appData.js'; import getAppConfig from './appConfig.js'; import { DocumentContextProvider } from './Document.js'; -import { loadRouteModules } from './routes.js'; +import { loadRouteModules, RouteComponent } from './routes.js'; import type { RouteLoaderOptions } from './routes.js'; import { pipeToString, renderToNodeStream } from './server/streamRender.js'; import type { NodeWritablePiper } from './server/streamRender.js'; @@ -181,6 +183,10 @@ async function sendResult(res: ServerResponse, result: RenderResult) { res.end(result.value); } +function needRevalidate(matchedRoutes: RouteMatch[]) { + return matchedRoutes.some(({ route }) => route.exports.includes('dataLoader') && route.exports.includes('staticDataLoader')); +} + async function doRender(serverContext: ServerContext, renderOptions: RenderOptions): Promise { const { req } = serverContext; const { @@ -248,19 +254,20 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio } try { - const routeModules = await loadRouteModules(matches.map(({ route: { id, load, lazy } }) => ({ id, load, lazy }))); + const routeModules = await loadRouteModules(matches.map(({ route: { id, lazy } }) => ({ id, lazy }))); const loaderData = {}; for (const routeId in routeModules) { const { loader } = routeModules[routeId]; if (loader) { - const { data, pageConfig } = await loader(location, requestContext); + const { data, pageConfig } = await loader(); loaderData[routeId] = { data, pageConfig, }; } } - runtime.setAppContext({ ...appContext, routeModules, loaderData, routePath, matches, appData }); + const revalidate = renderMode === 'SSG' && needRevalidate(matches); + runtime.setAppContext({ ...appContext, revalidate, routeModules, loaderData, routePath, matches, appData }); if (runtimeModules.commons) { await Promise.all(runtimeModules.commons.map(m => runtime.loadModule(m)).filter(Boolean)); } @@ -288,6 +295,28 @@ function render404(): RenderResult { }; } + +function createServerRoutes(routes: RouteObject[]) { + return routes.map((route) => { + let dataRoute = { + // Static Router need element or Component when matched. + element: , + id: route.id, + index: route.index, + path: route.path, + children: null, + }; + + if (route?.children?.length > 0) { + let children = createServerRoutes( + route.children, + ); + dataRoute.children = children; + } + return dataRoute; + }); +} + interface RenderServerEntry { runtime: Runtime; matches: RouteMatch[]; @@ -307,12 +336,16 @@ async function renderServerEntry( ): Promise { const { Document } = renderOptions; const appContext = runtime.getAppContext(); - const { routes, routePath, loaderData } = appContext; + const { routes, routePath, loaderData, basename } = appContext; const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const AppRouter = runtime.getAppRouter(); + const routerContext = { + matches, basename, loaderData, location, + }; + const router = createStaticRouter(createServerRoutes(routes), routerContext); const documentContext = { main: ( - + ), }; const element = ( diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index ae0403573a..c01510cfc4 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -1,8 +1,8 @@ import type { IncomingMessage, ServerResponse } from 'http'; -import type { InitialEntry, History, Location, AgnosticRouteObject } from '@remix-run/router'; +import type { InitialEntry, AgnosticRouteObject, Router } from '@remix-run/router'; import type { ComponentType, PropsWithChildren } from 'react'; import type { HydrationOptions, Root } from 'react-dom/client'; -import type { Navigator, Params, RouteObject } from 'react-router-dom'; +import type { Params, RouteObject } from 'react-router-dom'; type UseConfig = () => RouteConfig>; type UseData = () => RouteData; @@ -99,11 +99,12 @@ export interface AppContext { downgrade?: boolean; renderMode?: RenderMode; requestContext?: RequestContext; + revalidate?: boolean; } export type WindowContext = Pick< AppContext, - 'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' + 'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' | 'revalidate' >; export type Renderer = ( @@ -134,7 +135,7 @@ export type ComponentModule = { export type RouteItem = AgnosticRouteObject & { componentName: string; - load?: () => Promise; + exports: string[]; layout?: boolean; children?: RouteItem[]; }; @@ -219,10 +220,9 @@ export interface RuntimeModules { } export interface AppRouterProps { - history?: History; - routes: RouteObject[]; - loaderData?: LoaderDatas; - location?: Location; + routes?: RouteObject[]; + router?: Router; + routerContext?: any; } export interface AppRouteProps { From ff2f114a728c7244aca14cbbf5e32eec2fdb9356 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 30 Mar 2023 10:09:07 +0800 Subject: [PATCH 05/38] feat: add location for icestark --- packages/runtime/src/runClientApp.tsx | 2 +- packages/runtime/src/types.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index c015ce7956..8e9a86981c 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -160,7 +160,7 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { root, - + , ); diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index c01510cfc4..9d2cccff90 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, Router } from '@remix-run/router'; +import type { InitialEntry, AgnosticRouteObject, Router, Location } 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'; @@ -223,6 +223,7 @@ export interface AppRouterProps { routes?: RouteObject[]; router?: Router; routerContext?: any; + location?: Location; } export interface AppRouteProps { From 62a5376a2322ae8e74195d5b406c712dbf851baf Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 30 Mar 2023 11:01:29 +0800 Subject: [PATCH 06/38] chore: remove console --- packages/runtime/src/runServerApp.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 981c5042b6..f441d5d62e 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -99,7 +99,6 @@ export async function renderToHTML( requestContext: ServerContext, renderOptions: RenderOptions, ): Promise { - console.log('renderHTMLToJS.tsx'); const result = await doRender(requestContext, renderOptions); const { value } = result; From bb0f5f42942457e0675b7013d401a07cd76261c6 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 30 Mar 2023 11:30:26 +0800 Subject: [PATCH 07/38] test: examples --- examples/basic-project/ice.config.mts | 1 - examples/basic-project/src/pages/layout.tsx | 38 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 examples/basic-project/src/pages/layout.tsx diff --git a/examples/basic-project/ice.config.mts b/examples/basic-project/ice.config.mts index 4a61a033dc..8baac7c3c8 100644 --- a/examples/basic-project/ice.config.mts +++ b/examples/basic-project/ice.config.mts @@ -4,7 +4,6 @@ import customPlugin from './plugin'; export default defineConfig(() => ({ ssr: true, - ssg: false, publicPath: '/', polyfill: 'entry', syntaxFeatures: { diff --git a/examples/basic-project/src/pages/layout.tsx b/examples/basic-project/src/pages/layout.tsx new file mode 100644 index 0000000000..618740508c --- /dev/null +++ b/examples/basic-project/src/pages/layout.tsx @@ -0,0 +1,38 @@ +import { Outlet, useData, useConfig, definePageConfig, defineDataLoader } from 'ice'; + +export default function Layout() { + const data = useData(); + const config = useConfig(); + + console.log('render Layout', 'data', data, 'config', config); + + return ( +
+

ICE 3.0 Layout

+ +
+ ); +} + + +export const pageConfig = definePageConfig(() => { + return { + title: 'Layout', + meta: [ + { + name: 'layout-color', + content: '#f00', + }, + ], + }; +}); + +export const dataLoader = defineDataLoader(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + layout: true, + }); + }, 1 * 100); + }); +}); From c5b1a0517323c36d54c52141f20687a38c05e232 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 30 Mar 2023 15:43:07 +0800 Subject: [PATCH 08/38] fix: dataloader is undefined --- packages/runtime/src/routes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 3dd8f3f85f..18ffa47793 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -112,7 +112,7 @@ export function createRouteLoader(options: RouteLoaderOptions): () => Promise Date: Fri, 31 Mar 2023 14:09:38 +0800 Subject: [PATCH 09/38] fix: test --- tests/integration/with-antd-mobile.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/with-antd-mobile.test.ts b/tests/integration/with-antd-mobile.test.ts index 548f01c356..8c4d70e409 100644 --- a/tests/integration/with-antd-mobile.test.ts +++ b/tests/integration/with-antd-mobile.test.ts @@ -11,10 +11,9 @@ describe(`build ${example}`, () => { test('open /', async () => { await buildFixture(example); - const res = await setupBrowser({ example, disableJS: false }); + const res = await setupBrowser({ example }); page = res.page; browser = res.browser; - await page.waitForFunction('document.getElementsByTagName(\'h2\').length > 0'); expect(await page.$$text('h2')).toStrictEqual(['Counter']); }); From bb2ce263430f7955f635bb3b9fe5659c4ff2d297 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 31 Mar 2023 15:34:06 +0800 Subject: [PATCH 10/38] fix: test case --- examples/with-store/ice.config.mts | 1 + examples/with-store/src/app.tsx | 6 +++++- packages/ice/src/routes.ts | 5 ++++- .../core/{routes.ts.ejs => routes.tsx.ejs} | 2 +- packages/plugin-store/src/runtime.tsx | 10 ++++------ packages/runtime/src/RouteWrapper.tsx | 7 ++++--- packages/runtime/src/index.ts | 3 ++- packages/runtime/src/routes.tsx | 20 ++++++++++++++----- packages/runtime/src/runClientApp.tsx | 3 ++- packages/runtime/src/types.ts | 5 +++++ 10 files changed, 43 insertions(+), 19 deletions(-) rename packages/ice/templates/core/{routes.ts.ejs => routes.tsx.ejs} (63%) diff --git a/examples/with-store/ice.config.mts b/examples/with-store/ice.config.mts index 8f105b7264..d9944d8bb5 100644 --- a/examples/with-store/ice.config.mts +++ b/examples/with-store/ice.config.mts @@ -2,6 +2,7 @@ import { defineConfig } from '@ice/app'; import store from '@ice/plugin-store'; export default defineConfig(() => ({ + ssg: false, plugins: [ store({ resetPageState: true, diff --git a/examples/with-store/src/app.tsx b/examples/with-store/src/app.tsx index 87805d7ca9..a41605439a 100644 --- a/examples/with-store/src/app.tsx +++ b/examples/with-store/src/app.tsx @@ -19,4 +19,8 @@ export const dataLoader = defineDataLoader(() => { }); }); -export default defineAppConfig(() => ({})); +export default defineAppConfig(() => ({ + router: { + type: 'hash', + }, +})); diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index a4a5ff16aa..1f45ff1de6 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -69,7 +69,7 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la const component = `Component: () => WrapRouteComponent({ routeId: '${id}', isLayout: ${layout}, - RouteComponent: ${lazy ? 'componentModule' : loadStatement}.default, + routeExports: ${lazy ? 'componentModule' : loadStatement}, })`; const loader = `loader: createRouteLoader({ routeId: '${id}', @@ -82,10 +82,13 @@ export function getRoutesDefination(nestRouteManifest: NestedRouteManifest[], la `async lazy() { ${lazy ? `const componentModule = await ${loadStatement}` : ''}; return { + ${lazy ? '...componentModule' : `...${loadStatement}`}, ${component}, ${loader}, }; },`, + // Empty errorElement to avoid default ui provided by react-router. + 'ErrorBoundary: RouteErrorComponent,', `componentName: '${componentName}',`, `index: ${index},`, `id: '${id}',`, diff --git a/packages/ice/templates/core/routes.ts.ejs b/packages/ice/templates/core/routes.tsx.ejs similarity index 63% rename from packages/ice/templates/core/routes.ts.ejs rename to packages/ice/templates/core/routes.tsx.ejs index 3e1f7cec9e..16b78a77da 100644 --- a/packages/ice/templates/core/routes.ts.ejs +++ b/packages/ice/templates/core/routes.tsx.ejs @@ -1,4 +1,4 @@ -import { createRouteLoader, WrapRouteComponent } from '@ice/runtime'; +import { createRouteLoader, WrapRouteComponent, RouteErrorComponent } from '@ice/runtime'; <%- routeImports.length ? routeImports.join('\n') + '\n\n' : ''; -%> export default ({ requestContext, diff --git a/packages/plugin-store/src/runtime.tsx b/packages/plugin-store/src/runtime.tsx index e375712827..74cd1d169c 100644 --- a/packages/plugin-store/src/runtime.tsx +++ b/packages/plugin-store/src/runtime.tsx @@ -27,12 +27,10 @@ const runtime: RuntimePlugin = async ({ appContext, addWrapper, addProvider, use addProvider(StoreProvider); // Add page store . - const StoreProviderWrapper: RouteWrapper = ({ children, routeId }) => { - const { routeModules } = useAppContext(); - const routeModule = routeModules[routeId]; - if (routeModule?.[PAGE_STORE_PROVIDER]) { - const Provider = routeModule[PAGE_STORE_PROVIDER]; - const initialStates = routeModule[PAGE_STORE_INITIAL_STATES]; + const StoreProviderWrapper: RouteWrapper = ({ routeExports, children }) => { + if (routeExports?.[PAGE_STORE_PROVIDER]) { + const Provider = routeExports[PAGE_STORE_PROVIDER]; + const initialStates = routeExports[PAGE_STORE_INITIAL_STATES]; if (initialStates) { return {children}; } diff --git a/packages/runtime/src/RouteWrapper.tsx b/packages/runtime/src/RouteWrapper.tsx index 1020c7bf1d..c9629bdfad 100644 --- a/packages/runtime/src/RouteWrapper.tsx +++ b/packages/runtime/src/RouteWrapper.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; -import type { RouteWrapperConfig } from './types.js'; +import type { RouteWrapperConfig, RouteExports } from './types.js'; interface Props { id: string; isLayout?: boolean; wrappers?: RouteWrapperConfig[]; children?: React.ReactNode; + routeExports: RouteExports; } export default function RouteWrapper(props: Props) { - const { wrappers = [], id, isLayout } = props; + const { wrappers = [], id, isLayout, routeExports } = props; // layout should only be wrapped by Wrapper with `layout: true` const filtered = isLayout ? wrappers.filter(wrapper => wrapper.layout === true) : wrappers; const RouteWrappers = filtered.map(item => item.Wrapper); @@ -18,7 +19,7 @@ export default function RouteWrapper(props: Props) { if (RouteWrappers.length) { element = RouteWrappers.reduce((preElement, CurrentWrapper) => ( - + {preElement} ), props.children); diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 48a5812db2..30b05bc54a 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -50,7 +50,7 @@ import KeepAliveOutlet from './KeepAliveOutlet.js'; import ClientOnly from './ClientOnly.js'; import useMounted from './useMounted.js'; import { withSuspense, useSuspenseData } from './Suspense.js'; -import { createRouteLoader, WrapRouteComponent } from './routes.js'; +import { createRouteLoader, WrapRouteComponent, RouteErrorComponent } from './routes.js'; export { getAppConfig, @@ -96,6 +96,7 @@ export { createRouteLoader, WrapRouteComponent, + RouteErrorComponent, }; export type { diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 18ffa47793..1deb81caf8 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; +import { useRouteError } from 'react-router-dom'; +import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule, RouteExports } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; @@ -53,13 +54,13 @@ export async function loadRouteModules(routes: RouteModule[], originRouteModules export function WrapRouteComponent(options: { routeId: string; isLayout?: boolean; - RouteComponent: React.ComponentType; + routeExports: RouteExports; }) { - const { routeId, isLayout, RouteComponent } = options; + const { routeId, isLayout, routeExports } = options; const { RouteWrappers } = useAppContext(); return ( - - + + ); } @@ -79,6 +80,15 @@ export function RouteComponent({ id }: { id: string }) { return ; } +export function RouteErrorComponent() { + const error = useRouteError(); + if (error) { + // Re-throws the error so it can be caught by App Error Boundary. + throw error; + } + return <>; +} + /** * Create loader function for route module. */ diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 8e9a86981c..aa3431b0c1 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -134,7 +134,7 @@ interface RenderOptions { async function render({ history, runtime, needHydrate }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, loaderData, routes, revalidate } = appContext; + const { appConfig, loaderData, routes, revalidate, basename } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const AppRouter = runtime.getAppRouter(); @@ -149,6 +149,7 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { } const hydrationData = needHydrate ? { loaderData } : undefined; const routerOptions = { + basename, routes, history, hydrationData, diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 9d2cccff90..ac1b83b106 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -151,6 +151,11 @@ export interface RouteWrapperConfig { layout?: boolean; } +export interface RouteExports { + [key: string]: any; + default: React.ComponentType; +} + export type AppProvider = ComponentWithChildren; export type RouteWrapper = ComponentType; From e77a94c0dc86511a0ddaada7b8300e1dc65521c9 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 31 Mar 2023 17:52:52 +0800 Subject: [PATCH 11/38] fix: test case --- packages/ice/src/constant.ts | 15 +- packages/runtime/src/ClientRouter.tsx | 13 +- packages/runtime/src/RouteContext.ts | 13 +- packages/runtime/src/RouteWrapper.tsx | 4 +- packages/runtime/src/index.ts | 4 +- packages/runtime/src/router.ts | 4 +- packages/runtime/src/routes.tsx | 4 +- packages/runtime/src/runClientApp.tsx | 21 +- packages/runtime/src/single-router.tsx | 18 ++ packages/runtime/src/types.ts | 8 +- packages/runtime/tests/routes.test.tsx | 307 ++++++------------- packages/runtime/tests/runClientApp.test.tsx | 43 +-- 12 files changed, 185 insertions(+), 269 deletions(-) diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts index cf339bd8dc..caeb9b6b15 100644 --- a/packages/ice/src/constant.ts +++ b/packages/ice/src/constant.ts @@ -39,15 +39,22 @@ export const TARGETS = [ export const RUNTIME_EXPORTS = [ { - specifier: ['Link', 'Outlet', 'useParams', 'useSearchParams', 'useLocation', 'useNavigate'], + specifier: [ + 'Link', + 'Outlet', + 'useParams', + 'useSearchParams', + 'useLocation', + 'useData', + 'useConfig', + 'useNavigate', + ], source: '@ice/runtime/router', }, { specifier: [ 'defineAppConfig', 'useAppData', - 'useData', - 'useConfig', 'history', 'KeepAliveOutlet', 'useMounted', @@ -60,4 +67,4 @@ export const RUNTIME_EXPORTS = [ ], source: '@ice/runtime', }, -]; \ No newline at end of file +]; diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index 04e8bee92f..8dc1106783 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -3,16 +3,21 @@ import { RouterProvider } from 'react-router-dom'; import type { AppRouterProps } from './types.js'; import App from './App.js'; -import { Router } from './single-router.js'; +import { DataContextProvider } from './single-router.js'; function ClientRouter(props: AppRouterProps) { - const { router, routes } = props; + const { router, routes, Component, loaderData } = props; let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { - element = loading} />; + element = } />; } else { - element = ; + const routeItem = routes[0]; + element = ( + + + + ); } return ( diff --git a/packages/runtime/src/RouteContext.ts b/packages/runtime/src/RouteContext.ts index 6f0c9f3e48..9ddd8948f3 100644 --- a/packages/runtime/src/RouteContext.ts +++ b/packages/runtime/src/RouteContext.ts @@ -1,28 +1,17 @@ -import * as React from 'react'; import { useLoaderData } from 'react-router-dom'; -import type { RouteData, RouteConfig } from './types.js'; - -const DataContext = React.createContext(undefined); -DataContext.displayName = 'Data'; +import type { RouteConfig } from './types.js'; function useData(): T { const data = useLoaderData(); return (data as any).data; } -const DataProvider = DataContext.Provider; - -const ConfigContext = React.createContext | undefined>(undefined); -ConfigContext.displayName = 'Config'; function useConfig(): RouteConfig { const data = useLoaderData(); return (data as any).pageConfig; } -const ConfigProvider = ConfigContext.Provider; export { useData, - DataProvider, useConfig, - ConfigProvider, }; diff --git a/packages/runtime/src/RouteWrapper.tsx b/packages/runtime/src/RouteWrapper.tsx index c9629bdfad..adacd13289 100644 --- a/packages/runtime/src/RouteWrapper.tsx +++ b/packages/runtime/src/RouteWrapper.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import type { RouteWrapperConfig, RouteExports } from './types.js'; +import type { RouteWrapperConfig, ComponentModule } from './types.js'; interface Props { id: string; isLayout?: boolean; wrappers?: RouteWrapperConfig[]; children?: React.ReactNode; - routeExports: RouteExports; + routeExports: ComponentModule; } export default function RouteWrapper(props: Props) { diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 30b05bc54a..879bdc5f72 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -24,7 +24,7 @@ import runClientApp from './runClientApp.js'; import type { RunClientAppOptions } from './runClientApp.js'; import { useAppContext, useAppData, AppContextProvider } from './AppContext.js'; import { getAppData } from './appData.js'; -import { useData, useConfig, DataProvider, ConfigProvider } from './RouteContext.js'; +import { useData, useConfig } from './RouteContext.js'; import { Meta, Title, @@ -65,8 +65,6 @@ export { defineDataLoader, defineServerDataLoader, defineStaticDataLoader, - DataProvider, - ConfigProvider, useConfig, Meta, Title, diff --git a/packages/runtime/src/router.ts b/packages/runtime/src/router.ts index 5dc65e77c2..c86bfa1140 100644 --- a/packages/runtime/src/router.ts +++ b/packages/runtime/src/router.ts @@ -5,4 +5,6 @@ export { useSearchParams, useLocation, useNavigate, -} from 'react-router-dom'; \ No newline at end of file +} from 'react-router-dom'; + +export { useData, useConfig } from './RouteContext.js'; diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 1deb81caf8..56a7ed5b7a 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -54,7 +54,7 @@ export async function loadRouteModules(routes: RouteModule[], originRouteModules export function WrapRouteComponent(options: { routeId: string; isLayout?: boolean; - routeExports: RouteExports; + routeExports: ComponentModule; }) { const { routeId, isLayout, routeExports } = options; const { RouteWrappers } = useAppContext(); @@ -99,7 +99,7 @@ interface LoaderData { export interface RouteLoaderOptions { routeId: string; - requestContext: RequestContext; + requestContext?: RequestContext; module: ComponentModule; renderMode: RenderMode; } diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index aa3431b0c1..f889190130 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -11,6 +11,7 @@ import Runtime from './runtime.js'; import { getAppData } from './appData.js'; import { getRoutesPath } from './routes.js'; import type { RouteLoaderOptions } from './routes.js'; +import { loadRouteModule } from './routes.js'; import getRequestContext from './requestContext.js'; import getAppConfig from './appConfig.js'; import ClientRouter from './ClientRouter.js'; @@ -154,14 +155,28 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { history, hydrationData, }; + let router = null; + let singleComponent = null; + let routeData = null; // Create router before render. - const router = process.env.ICE_CORE_ROUTER === 'true' ? createRouter(routerOptions).initialize() : null; - + if (process.env.ICE_CORE_ROUTER === 'true') { + router = createRouter(routerOptions).initialize(); + } else { + const { Component, loader } = await loadRouteModule(routes[0]); + singleComponent = Component || routes[0].Component; + routeData = loader && await loader(); + } const renderRoot = appRender( root, - + , ); diff --git a/packages/runtime/src/single-router.tsx b/packages/runtime/src/single-router.tsx index bb58d8059f..ce20e796d1 100644 --- a/packages/runtime/src/single-router.tsx +++ b/packages/runtime/src/single-router.tsx @@ -4,6 +4,24 @@ */ import * as React from 'react'; import type { History } from '@remix-run/router'; +import type { LoaderData } from './types.js'; + +const Context = React.createContext(undefined); + +Context.displayName = 'DataContext'; + +export function useData(): T { + const value = React.useContext(Context); + return value.data; +} + +export function useConfig() { + const value = React.useContext(Context); + return value.pageConfig; +} + +export const DataContextProvider = Context.Provider; + export const useRoutes = (routes) => { return <>{routes[0].element}; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index ac1b83b106..16f6a6abbc 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -135,6 +135,7 @@ export type ComponentModule = { export type RouteItem = AgnosticRouteObject & { componentName: string; + Component?: ComponentType; exports: string[]; layout?: boolean; children?: RouteItem[]; @@ -151,11 +152,6 @@ export interface RouteWrapperConfig { layout?: boolean; } -export interface RouteExports { - [key: string]: any; - default: React.ComponentType; -} - export type AppProvider = ComponentWithChildren; export type RouteWrapper = ComponentType; @@ -229,6 +225,8 @@ export interface AppRouterProps { router?: Router; routerContext?: any; location?: Location; + Component?: ComponentType; + loaderData?: LoaderData; } export interface AppRouteProps { diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index ffd72c94a1..beeb2e8c91 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -5,17 +5,13 @@ import React from 'react'; import { renderToString } from 'react-dom/server'; import { expect, it, describe, beforeEach, afterEach, vi } from 'vitest'; -import type { RouteComponent as IRouteComponent } from '../src/types'; -import RouteWrapper from '../src/RouteWrapper'; import { AppContextProvider } from '../src/AppContext'; import { - filterMatchesToLoad, - createRouteElements, RouteComponent, loadRouteModules, - loadRoutesData, - getRoutesConfig, + createRouteLoader, getRoutesPath, + WrapRouteComponent, } from '../src/routes.js'; describe('routes', () => { @@ -40,17 +36,33 @@ describe('routes', () => { default: () => <>, pageConfig: () => ({ title: 'about' }), }; + const homeLazyItem = { + Component: homeItem.default, + loader: createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'CSR', + }), + }; + const aboutLazyItem = { + Component: aboutItem.default, + loader: createRouteLoader({ + routeId: 'about', + module: aboutItem, + renderMode: 'CSR', + }), + }; const routeModules = [ { id: 'home', - load: async () => { - return homeItem as IRouteComponent; + lazy: async () => { + return homeLazyItem; }, }, { id: 'about', - load: async () => { - return aboutItem as IRouteComponent; + lazy: async () => { + return aboutLazyItem; }, }, ]; @@ -61,7 +73,7 @@ describe('routes', () => {
home
, + Component: () =>
home
, }, }, }} @@ -84,12 +96,32 @@ describe('routes', () => { process.env.NODE_ENV = currentEnv; }); + it('route WrapRouteComponent', () => { + const domstring = renderToString( + // @ts-ignore +
wrapper{children}
, layout: false }] }}> +
home
}} /> +
, + ); + expect(domstring).toBe('
wrapper
home
'); + }); + + it('route WrapRouteComponent match layout', () => { + const domstring = renderToString( + // @ts-ignore +
wrapper{children}
, layout: false }] }}> +
home
}} /> +
, + ); + expect(domstring).toBe('
home
'); + }); + it('load route modules', async () => { windowSpy.mockImplementation(() => ({})); - const routeModule = await loadRouteModules(routeModules, { home: homeItem }); + const routeModule = await loadRouteModules(routeModules, {}); expect(routeModule).toStrictEqual({ - home: homeItem, - about: aboutItem, + home: homeLazyItem, + about: aboutLazyItem, }); }); @@ -97,7 +129,7 @@ describe('routes', () => { const routeModule = await loadRouteModules([{ id: 'error', // @ts-ignore - load: async () => { + lazy: async () => { throw new Error('err'); return {}; }, @@ -108,59 +140,53 @@ describe('routes', () => { }); it('load route data for SSG', async () => { - const routeModule = await loadRouteModules(routeModules); - const routesDataSSG = await loadRoutesData( - // @ts-ignore - [{ route: routeModules[0] }], - {}, - routeModule, - { - renderMode: 'SSG', - }, - ); + const routesDataSSG = await createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'SSG', + })(); expect(routesDataSSG).toStrictEqual({ - home: { + data: { type: 'getStaticData', }, + pageConfig: { + title: 'home', + }, }); }); it('load route data for SSR', async () => { - const routeModule = await loadRouteModules(routeModules); - const routesDataSSR = await loadRoutesData( - // @ts-ignore - [{ route: routeModules[0] }], - {}, - routeModule, - { - renderMode: 'SSR', - }, - ); + const routesDataSSR = await createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'SSR', + })(); expect(routesDataSSR).toStrictEqual({ - home: { + data: { type: 'getServerData', }, + pageConfig: { + title: 'home', + }, }); }); it('load route data for CSR', async () => { - const routeModule = await loadRouteModules(routeModules); - const routesDataCSR = await loadRoutesData( - // @ts-ignore - [{ route: routeModules[0] }], - {}, - routeModule, - { - renderMode: 'CSR', - }, - ); + const routesDataCSR = await createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'CSR', + })(); expect(routesDataCSR).toStrictEqual({ - home: { + data: { type: 'getData', }, + pageConfig: { + title: 'home', + }, }); }); @@ -170,180 +196,35 @@ describe('routes', () => { getData: async (id) => ({ id: `${id}_data` }), }, })); - const routesData = await loadRoutesData( - // @ts-ignore - [{ route: routeModules[0] }], - {}, - {}, - { renderMode: 'SSG' }, - ); - expect(routesData).toStrictEqual({ - home: { + const routesDataCSR = await createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'CSR', + })(); + + expect(routesDataCSR).toStrictEqual({ + data: { id: 'home_data', }, - }); - }); - - it('get routes config', async () => { - const routeModule = await loadRouteModules(routeModules); - const routesConfig = getRoutesConfig( - // @ts-ignore - [{ route: routeModules[0] }], - { home: {} }, - routeModule, - ); - expect(routesConfig).toStrictEqual({ - home: { + pageConfig: { title: 'home', }, }); }); - it('get routes config when failed get route module', async () => { - const routesConfig = getRoutesConfig( - // @ts-ignore - [{ route: routeModules[0] }], - { home: {} }, - {}, - ); - expect(routesConfig).toStrictEqual({ - home: {}, - }); - }); + it('get routes config without data', async () => { + const routesDataCSR = await createRouteLoader({ + routeId: 'about', + module: aboutItem, + renderMode: 'CSR', + })(); - it('create route element', () => { - const routeElement = createRouteElements([{ - path: '/', - id: 'home', - componentName: 'home', - }]); - expect(routeElement).toEqual([{ - componentName: 'home', - element: ( - - - - ), - id: 'home', - path: '/', - }]); - }); - - it('create route with children', () => { - const routeElement = createRouteElements([{ - path: '/', - id: 'home', - componentName: 'home', - children: [{ - path: '/about', - id: 'about', - componentName: 'about', - }], - }]); - expect(routeElement).toEqual([{ - componentName: 'home', - element: ( - - - - ), - children: [{ - componentName: 'about', - element: ( - - - - ), - id: 'about', - path: '/about', - }], - id: 'home', - path: '/', - }]); - }); - - it('filter new matches', () => { - const oldMatches = [ - { - pathname: '/', - route: { - id: '/page/layout', - }, - }, - { - pathname: '/', - route: { - id: '/page/home', - }, - }, - ]; - - const newMatches = [ - { - pathname: '/', - route: { - id: '/page/layout', - }, - }, - { - pathname: '/about', - route: { - id: '/page/about', - }, - }, - ]; - - // @ts-ignore - const matches = filterMatchesToLoad(oldMatches, newMatches); - - expect( - matches, - ).toEqual([{ - pathname: '/about', - route: { - id: '/page/about', - }, - }]); - }); - - it('filter matches with path changed', () => { - const oldMatches = [ - { - pathname: '/users/123', - route: { - id: '/users/123', - }, - }, - ]; - - const newMatches = [ - { - pathname: '/users/456', - route: { - id: '/users/456', - }, - }, - ]; - - // @ts-ignore - const matches = filterMatchesToLoad(oldMatches, newMatches); - - expect( - matches, - ).toEqual([ - { - pathname: '/users/456', - route: { - id: '/users/456', - }, + expect(routesDataCSR).toStrictEqual({ + data: undefined, + pageConfig: { + title: 'about', }, - ]); + }); }); it('get routes flatten path', () => { diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index 04b68f002e..88ef3e91f4 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -2,11 +2,12 @@ * @vitest-environment jsdom */ -import React from 'react'; +import React, { useEffect } from 'react'; import { renderToString } from 'react-dom/server'; import { expect, it, vi, describe, beforeEach, afterEach } from 'vitest'; -import runClientApp, { loadNextPage } from '../src/runClientApp'; -import { useAppData } from '../src/AppData'; +import runClientApp from '../src/runClientApp'; +import { useAppData } from '../src/AppContext'; +import { createRouteLoader } from '../src/routes'; import { useConfig, useData } from '../src/RouteContext'; describe('run client app', () => { @@ -79,22 +80,23 @@ describe('run client app', () => { addProvider(Provider); }; + const homeItem = { + default: () => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const appData = useAppData(); + return ( +
home{appData?.msg || ''}
+ ); + }, + pageConfig: () => ({ title: 'home' }), + dataLoader: async () => ({ data: 'test' }), + }; const basicRoutes = [ { id: 'home', path: '/', componentName: 'Home', - load: async () => ({ - default: () => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const appData = useAppData(); - return ( -
home{appData?.msg || ''}
- ); - }, - pageConfig: () => ({ title: 'home' }), - dataLoader: async () => ({ data: 'test' }), - }), + Component: homeItem.default, }, ]; @@ -105,9 +107,10 @@ describe('run client app', () => { return { msg: staticMsg }; }, }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime], statics: [staticRuntime] }, - hydrate: false, + hydrate: true, }); expect(domstring).toBe('
homestatic
'); }); @@ -117,10 +120,10 @@ describe('run client app', () => { ...mockData, location: new URL('http://localhost:4000/?test=1&runtime=true&baisc'), })); - await runClientApp({ app: {}, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); @@ -131,14 +134,14 @@ describe('run client app', () => { process.env.ICE_CORE_ROUTER = ''; await runClientApp({ app: {}, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); process.env.ICE_CORE_ROUTER = 'true'; expect(domstring).toBe('
home
'); }); - it('run client with wrapper', async () => { await runClientApp({ app: {}, From 7530846a24196f09ab53ee79fa11aa25492c04eb Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 31 Mar 2023 17:54:17 +0800 Subject: [PATCH 12/38] fix: types --- packages/runtime/src/routes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 56a7ed5b7a..46db33fa50 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useRouteError } from 'react-router-dom'; -import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule, RouteExports } from './types.js'; +import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; From 6e2e859786b8147af110a8499c8cbe9352370fe9 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 11:31:48 +0800 Subject: [PATCH 13/38] fix: test case --- packages/ice/package.json | 2 +- packages/miniapp-runtime/src/app/App.tsx | 2 +- packages/runtime/package.json | 7 +- packages/runtime/src/AppErrorBoundary.tsx | 2 +- packages/runtime/src/ClientRouter.tsx | 3 +- packages/runtime/src/runClientApp.tsx | 1 + packages/runtime/tests/routesConfig.test.ts | 7 +- packages/runtime/tests/runClientApp.test.tsx | 165 ++---- packages/runtime/tests/runServerApp.test.tsx | 71 ++- pnpm-lock.yaml | 517 +++++++++++++++++-- 10 files changed, 563 insertions(+), 214 deletions(-) diff --git a/packages/ice/package.json b/packages/ice/package.json index 72fa6e179c..07290a19d2 100644 --- a/packages/ice/package.json +++ b/packages/ice/package.json @@ -83,7 +83,7 @@ "esbuild": "^0.16.5", "jest": "^29.0.2", "react": "^18.2.0", - "react-router": "6.9.0", + "react-router": "6.10.0", "unplugin": "^0.9.0", "webpack": "^5.76.2", "webpack-dev-server": "^4.7.4" diff --git a/packages/miniapp-runtime/src/app/App.tsx b/packages/miniapp-runtime/src/app/App.tsx index 02cd818647..1e359cc69e 100644 --- a/packages/miniapp-runtime/src/app/App.tsx +++ b/packages/miniapp-runtime/src/app/App.tsx @@ -7,7 +7,7 @@ export default function App() { const { strict, errorBoundary } = appConfig.app; const StrictMode = strict ? React.StrictMode : React.Fragment; const ErrorBoundary = errorBoundary ? AppErrorBoundary : React.Fragment; - + console.log('ErrorBoundary', ErrorBoundary); return ( diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 9cf6ac535b..a022d0d5d8 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -37,17 +37,18 @@ "@types/react-dom": "^18.0.3", "react": "^18.0.0", "react-dom": "^18.0.0", - "regenerator-runtime": "^0.13.9" + "regenerator-runtime": "^0.13.9", + "@remix-run/web-fetch": "^4.3.3" }, "sideEffects": false, "dependencies": { "@ice/jsx-runtime": "^0.2.0", - "@remix-run/router": "1.4.0", + "@remix-run/router": "1.5.0", "ejs": "^3.1.6", "fs-extra": "^10.0.0", "history": "^5.3.0", "htmlparser2": "^8.0.1", - "react-router-dom": "6.9.0" + "react-router-dom": "6.10.0" }, "peerDependencies": { "react": "^18.1.0", diff --git a/packages/runtime/src/AppErrorBoundary.tsx b/packages/runtime/src/AppErrorBoundary.tsx index 886a1df2b2..08793e41b2 100644 --- a/packages/runtime/src/AppErrorBoundary.tsx +++ b/packages/runtime/src/AppErrorBoundary.tsx @@ -29,4 +29,4 @@ export default class AppErrorBoundary extends React.Component { return this.props.children; } -} \ No newline at end of file +} diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index 8dc1106783..a8fb607f66 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -6,13 +6,12 @@ import App from './App.js'; import { DataContextProvider } from './single-router.js'; function ClientRouter(props: AppRouterProps) { - const { router, routes, Component, loaderData } = props; + const { router, Component, loaderData } = props; let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { element = } />; } else { - const routeItem = routes[0]; element = ( diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index f889190130..f9023978eb 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -181,6 +181,7 @@ async function render({ history, runtime, needHydrate }: RenderOptions) {
, ); if (revalidate) { + // Revalidate after render for SSG while staticDataLoader and dataLoader both defined. setTimeout(() => { router?.revalidate(); }); diff --git a/packages/runtime/tests/routesConfig.test.ts b/packages/runtime/tests/routesConfig.test.ts index 4344e1ac18..49c234e369 100644 --- a/packages/runtime/tests/routesConfig.test.ts +++ b/packages/runtime/tests/routesConfig.test.ts @@ -44,7 +44,7 @@ describe('routes config', () => { it('update routes config', async () => { const routesConfig = { - home: { + pageConfig: { title: 'home', meta: [ { @@ -61,12 +61,11 @@ describe('routes config', () => { }], }, }; - // @ts-ignore - await updateRoutesConfig([{ route: { id: 'home' } }], routesConfig); + await updateRoutesConfig(routesConfig); expect(insertTags.length).toBe(1); expect(insertTags[0]?.type).toBe('meta'); expect(appendTags.length).toBe(2); expect(appendTags[0]?.type).toBe('link'); expect(appendTags[1]?.type).toBe('script'); }); -}); \ No newline at end of file +}); diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index 88ef3e91f4..90f497f312 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -2,13 +2,11 @@ * @vitest-environment jsdom */ -import React, { useEffect } from 'react'; +import React from 'react'; import { renderToString } from 'react-dom/server'; import { expect, it, vi, describe, beforeEach, afterEach } from 'vitest'; import runClientApp from '../src/runClientApp'; import { useAppData } from '../src/AppContext'; -import { createRouteLoader } from '../src/routes'; -import { useConfig, useData } from '../src/RouteContext'; describe('run client app', () => { let windowSpy; @@ -54,7 +52,9 @@ describe('run client app', () => { setRender((container, element) => { try { domstring = renderToString(element as any); - } catch (err) { } + } catch (err) { + domstring = ''; + } }); }; @@ -64,13 +64,6 @@ describe('run client app', () => { staticMsg = 'static'; }; - const wrapperRuntime = async ({ addWrapper }) => { - const RouteWrapper = ({ children }) => { - return
{children}
; - }; - addWrapper(RouteWrapper, true); - }; - const providerRuntmie = async ({ addProvider }) => { const Provider = ({ children }) => { return
{children}
; @@ -96,6 +89,7 @@ describe('run client app', () => { id: 'home', path: '/', componentName: 'Home', + // Make sure the component is loaded before render. Component: homeItem.default, }, ]; @@ -131,59 +125,51 @@ describe('run client app', () => { }); it('run client single-router', async () => { + const sigleRoutes = [ + { + id: 'home', + path: '/', + lazy: () => { + return { + Component: homeItem.default, + loader: () => {}, + }; + }, + }, + ]; process.env.ICE_CORE_ROUTER = ''; await runClientApp({ app: {}, // @ts-ignore don't need to pass params in test case. - createRoutes: () => basicRoutes, + createRoutes: () => sigleRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); process.env.ICE_CORE_ROUTER = 'true'; expect(domstring).toBe('
home
'); }); - it('run client with wrapper', async () => { - await runClientApp({ - app: {}, - routes: basicRoutes, - runtimeModules: { commons: [serverRuntime, wrapperRuntime] }, - hydrate: true, - }); - expect(domstring).toBe('
home
'); - }); it('run client with app provider', async () => { await runClientApp({ app: {}, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime, providerRuntmie] }, hydrate: true, }); expect(domstring).toBe('
home
'); }); - it('run client with empty route', async () => { - await runClientApp({ - app: {}, - routes: [], - runtimeModules: { commons: [serverRuntime] }, - hydrate: false, - }); - }); - it('run client with memory router', async () => { const routes = [...basicRoutes, { id: 'about', path: '/about', componentName: 'About', - load: async () => ({ - default: () => { - return ( -
about
- ); - }, - }), - }]; + Component: () => { + return ( +
about
+ ); + } }]; await runClientApp({ app: { default: { @@ -193,7 +179,8 @@ describe('run client app', () => { }, }, }, - routes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => routes, runtimeModules: { commons: [serverRuntime] }, hydrate: true, }); @@ -204,7 +191,8 @@ describe('run client app', () => { default: { }, }, - routes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: true, }); @@ -221,18 +209,16 @@ describe('run client app', () => { id: 'about', path: '/about', componentName: 'About', - load: async () => ({ - default: () => { - return ( -
about
- ); - }, - }), - }]; + Component: () => { + return ( +
about
+ ); + } }]; await runClientApp({ app: { }, - routes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => routes, runtimeModules: { commons: [serverRuntime] }, hydrate: true, memoryRouter: true, @@ -250,7 +236,8 @@ describe('run client app', () => { }, }, }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: true, }); @@ -266,7 +253,8 @@ describe('run client app', () => { return { msg: '-getAppData' }; }, }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); @@ -294,7 +282,8 @@ describe('run client app', () => { return { msg: 'app' }; }, }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); @@ -312,75 +301,11 @@ describe('run client app', () => { }, }, }, - routes: [{ - id: 'home', - path: '/', - componentName: 'Home', - load: async () => ({ - default: () => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const config = useConfig(); - // eslint-disable-next-line react-hooks/rules-of-hooks - const data = useData(); - return ( -
home{data?.data}{config.title}
- ); - }, - pageConfig: () => ({ title: 'home' }), - dataLoader: async () => ({ data: 'test' }), - }), - }], + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, runtimeModules: { commons: [serverRuntime] }, hydrate: false, }); - expect(domstring).toBe('
hometesthome
'); - }); - - it('load next page', async () => { - const indexPage = { - default: () => <>, - pageConfig: () => ({ title: 'index' }), - dataLoader: async () => ({ type: 'getDataIndex' }), - }; - const aboutPage = { - default: () => <>, - pageConfig: () => ({ title: 'about' }), - dataLoader: async () => ({ type: 'getDataAbout' }), - }; - const mockedModules = [ - { - id: 'index', - load: async () => { - return indexPage; - }, - }, - { - id: 'about', - load: async () => { - return aboutPage; - }, - }, - ]; - const { routesData, routesConfig, routeModules } = await loadNextPage( - // @ts-ignore - [{ route: mockedModules[0] }], - { - // @ts-ignore - matches: [{ route: mockedModules[1] }], - routesData: {}, - routeModules: {}, - }, - ); - expect(routesData).toStrictEqual({ - index: { type: 'getDataIndex' }, - }); - expect(routesConfig).toStrictEqual({ - index: { - title: 'index', - }, - }); - expect(routeModules).toStrictEqual({ - index: indexPage, - }); + expect(domstring).toBe('
home
'); }); }); diff --git a/packages/runtime/tests/runServerApp.test.tsx b/packages/runtime/tests/runServerApp.test.tsx index b5342d8f68..bfad032c4c 100644 --- a/packages/runtime/tests/runServerApp.test.tsx +++ b/packages/runtime/tests/runServerApp.test.tsx @@ -5,23 +5,32 @@ import React from 'react'; import { expect, it, describe } from 'vitest'; import { renderToHTML, renderToResponse } from '../src/runServerApp'; import { Meta, Title, Links, Main, Scripts } from '../src/Document'; +import { + createRouteLoader, +} from '../src/routes.js'; describe('run server app', () => { process.env.ICE_CORE_ROUTER = 'true'; + const homeItem = { + default: () =>
home
, + pageConfig: () => ({ title: 'home' }), + dataLoader: async () => ({ data: 'test' }), + }; const basicRoutes = [ { id: 'home', path: 'home', componentName: 'home', - load: async () => ({ - default: () => { - return ( -
home
- ); - }, - pageConfig: () => ({ title: 'home' }), - getData: async () => ({ data: 'test' }), - }), + lazy: () => { + return { + Component: homeItem.default, + loader: createRouteLoader({ + routeId: 'home', + module: homeItem, + renderMode: 'SSR', + }), + }; + }, }, ]; @@ -61,7 +70,8 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, renderMode: 'SSR', }); @@ -81,7 +91,8 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, renderMode: 'SSR', basename: '/ice', @@ -100,7 +111,8 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, renderMode: 'SSR', serverOnlyBasename: '/', @@ -120,7 +132,8 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, }); // @ts-ignore @@ -143,7 +156,8 @@ describe('run server app', () => { }, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, }); // @ts-ignore @@ -162,18 +176,24 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: [{ + // @ts-ignore don't need to pass params in test case. + createRoutes: () => [{ id: 'home', path: 'home', componentName: 'Home', - load: async () => ({ - default: () => { - throw new Error('err'); - return ( -
home
- ); - }, - }), + lazy: () => { + return { + Component: () => { + throw new Error('err'); + return ( +
home
+ ); + }, + loader: () => { + return { data: {}, pageConfig: {} }; + }, + }; + }, }], Document, }); @@ -201,7 +221,8 @@ describe('run server app', () => { app: {}, assetsManifest, runtimeModules: { commons: [] }, - routes: basicRoutes, + // @ts-ignore don't need to pass params in test case. + createRoutes: () => basicRoutes, Document, renderMode: 'SSR', routePath: '/', @@ -210,4 +231,4 @@ describe('run server app', () => { expect(!!htmlContent).toBe(true); expect(htmlContent.includes('
home=14.19.0', npm: '>=3.0.0'} + hasBin: true + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + dependencies: + '@babel/generator': 7.18.10 + '@babel/parser': 7.18.10 + '@babel/traverse': 7.18.10 + '@babel/types': 7.18.10 + '@ice/bundles': 0.1.8 + '@ice/route-manifest': 1.1.1 + '@ice/runtime': 1.1.5 + '@ice/webpack-config': 1.0.12 + '@swc/helpers': 0.4.14 + '@types/express': 4.17.17 + address: 1.2.2 + build-scripts: 2.1.0 + chalk: 4.1.2 + commander: 9.5.0 + consola: 2.15.3 + cross-spawn: 7.0.3 + detect-port: 1.5.1 + dotenv: 16.0.3 + dotenv-expand: 8.0.3 + ejs: 3.1.8 + estree-walker: 3.0.3 + fast-glob: 3.2.12 + find-up: 5.0.0 + fs-extra: 10.1.0 + micromatch: 4.0.5 + mlly: 1.1.1 + mrmime: 1.0.1 + open: 8.4.2 + path-to-regexp: 6.2.1 + regenerator-runtime: 0.13.11 + resolve.exports: 1.1.1 + semver: 7.3.8 + temp: 0.9.4 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@ice/app/3.1.6_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-ch0UwX9VJikgg+cd9Yh9KUtkdbZVzTmm1vSZU/HP3fzsLugTWkxQz1LoULIS0hAfUCvrruzvCaXqGKoLeqfHpw==} + engines: {node: '>=14.19.0', npm: '>=3.0.0'} + hasBin: true + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + dependencies: + '@babel/generator': 7.18.10 + '@babel/parser': 7.18.10 + '@babel/traverse': 7.18.10 + '@babel/types': 7.18.10 + '@ice/bundles': 0.1.8 + '@ice/route-manifest': 1.1.1 + '@ice/runtime': 1.1.5_biqbaboplfbrettd7655fr4n2y + '@ice/webpack-config': 1.0.12 + '@swc/helpers': 0.4.14 + '@types/express': 4.17.17 + address: 1.2.2 + build-scripts: 2.1.0 + chalk: 4.1.2 + commander: 9.5.0 + consola: 2.15.3 + cross-spawn: 7.0.3 + detect-port: 1.5.1 + dotenv: 16.0.3 + dotenv-expand: 8.0.3 + ejs: 3.1.8 + estree-walker: 3.0.3 + fast-glob: 3.2.12 + find-up: 5.0.0 + fs-extra: 10.1.0 + micromatch: 4.0.5 + mlly: 1.1.1 + mrmime: 1.0.1 + open: 8.4.2 + path-to-regexp: 6.2.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + regenerator-runtime: 0.13.11 + resolve.exports: 1.1.1 + semver: 7.3.8 + temp: 0.9.4 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@ice/bundles/0.1.8: + resolution: {integrity: sha512-cjaUkwcVkVO5EOdZqyFyDFYAPs5nnNXdOVRAY3CegBMOxy8K5MgoQO5xJkOMmphUSm+X0pbb0xUza1b9wR2fYA==} + dependencies: + '@ice/swc-plugin-keep-export': 0.1.4 + '@ice/swc-plugin-node-transform': 0.1.0-5 + '@ice/swc-plugin-remove-export': 0.1.2 + '@swc/core': 1.3.19 + ansi-html-community: 0.0.8 + caniuse-lite: 1.0.30001462 + chokidar: 3.5.3 + core-js: 3.29.1 + core-js-pure: 3.29.0 + error-stack-parser: 2.1.4 + esbuild: 0.16.17 + events: 3.3.0 + html-entities: 2.3.3 + jest-worker: 27.5.1 + less: 4.1.2 + postcss: 8.4.12 + react-refresh: 0.14.0 + sass: 1.50.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@ice/jsx-runtime/0.2.0: + resolution: {integrity: sha512-yUfoWloQq2mdyCTsTTWwxtx0dmiwC4Xe2QEaQCg/yFqQKz7hOFBOrju6RodMl7K0fdpj6k1UBfjLd9gPyUBAbw==} + peerDependencies: + react: ^16 || ^17 || ^18 + dependencies: + style-unit: 3.0.5 + dev: true + + /@ice/jsx-runtime/0.2.0_react@18.2.0: + resolution: {integrity: sha512-yUfoWloQq2mdyCTsTTWwxtx0dmiwC4Xe2QEaQCg/yFqQKz7hOFBOrju6RodMl7K0fdpj6k1UBfjLd9gPyUBAbw==} + peerDependencies: + react: ^16 || ^17 || ^18 + dependencies: + react: 18.2.0 + style-unit: 3.0.5 + /@ice/pkg-plugin-component/1.0.0: resolution: {integrity: sha512-Mff6Em1RwY2NOZgciQyy2NXL65u9ebHV2Jqb5746vUVEh0l7Xpokb+3djV1pKcgED4wU8pSuSQ6jj+hXd9Ht1Q==} dependencies: @@ -5147,6 +5284,56 @@ packages: - ts-node dev: true + /@ice/route-manifest/1.1.1: + resolution: {integrity: sha512-dmbSO13Dobzm7o56qTqavVG0yJQNN6+u1cG3jt3tZK9XjZgpEffdPDGkYPhRx732eR970W3J5qpYrOZvH+bTaw==} + dependencies: + minimatch: 5.1.6 + dev: true + + /@ice/runtime/1.1.5: + resolution: {integrity: sha512-QodI80xuhsPk8+q4Jprc9YXJ6xrsrtxLwBMa7zozvuo7lz0eycSahGukOnUJu7BB+ip6iBqID6q+yAxkQbMzmw==} + peerDependencies: + react: ^18.1.0 + react-dom: ^18.1.0 + dependencies: + '@ice/jsx-runtime': 0.2.0 + ejs: 3.1.8 + fs-extra: 10.1.0 + history: 5.3.0 + htmlparser2: 8.0.1 + react-router-dom: 6.10.0 + dev: true + + /@ice/runtime/1.1.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-QodI80xuhsPk8+q4Jprc9YXJ6xrsrtxLwBMa7zozvuo7lz0eycSahGukOnUJu7BB+ip6iBqID6q+yAxkQbMzmw==} + peerDependencies: + react: ^18.1.0 + react-dom: ^18.1.0 + dependencies: + '@ice/jsx-runtime': 0.2.0_react@18.2.0 + ejs: 3.1.8 + fs-extra: 10.1.0 + history: 5.3.0 + htmlparser2: 8.0.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-router-dom: 6.10.0_biqbaboplfbrettd7655fr4n2y + + /@ice/runtime/1.1.5_react@18.2.0: + resolution: {integrity: sha512-QodI80xuhsPk8+q4Jprc9YXJ6xrsrtxLwBMa7zozvuo7lz0eycSahGukOnUJu7BB+ip6iBqID6q+yAxkQbMzmw==} + peerDependencies: + react: ^18.1.0 + react-dom: ^18.1.0 + dependencies: + '@ice/jsx-runtime': 0.2.0_react@18.2.0 + ejs: 3.1.8 + fs-extra: 10.1.0 + history: 5.3.0 + htmlparser2: 8.0.1 + react: 18.2.0 + react-router-dom: 6.10.0_react@18.2.0 + dev: false + /@ice/sandbox/1.1.4: resolution: {integrity: sha512-MEVF0Ze3McKDutnFiUAhUoc+WwOFxITVBgSSHmbGpKtWbXJX9kUVlx3VsEVJvdqU3O1kiBNx6zE1sFMjKPRTIQ==} dev: false @@ -5183,18 +5370,43 @@ packages: - react-native dev: false + /@ice/swc-plugin-keep-export/0.1.4: + resolution: {integrity: sha512-fOc09KALmL2zJK1xNGTEt/C27mXL7NVn/v1eRjjuM4uer+qmWIxYXIa9dpfTX5ZUn8zXhrKH8lGdczoKHCzyQQ==} + dev: true + /@ice/swc-plugin-keep-export/0.1.5-0: resolution: {integrity: sha512-huUXmRHLL5nz6x9gxSFwsHn8EAKveGFaFqW0o3rce+aSFBxAWFM/f6NyFbQzYCF+NNMn5YDgQUOz5wtiYcHlwg==} dev: false + /@ice/swc-plugin-node-transform/0.1.0-5: + resolution: {integrity: sha512-YCZUQwS4r9kjF2RDaPsChP+SEfWHecq2uqwCkZ4+akjL0hZBxbxkN0kXCRMV2O0rNDSBfMpUUdBeAcrCuFuxHw==} + dev: true + /@ice/swc-plugin-node-transform/0.1.1-1: resolution: {integrity: sha512-eqgqor4w1uqysDskVL82LEoQPk00Q5fZIk3YWVQGHgCwOmu/kDH4Gx0XLT3hUo+u6la8rF9AtPHMmVQ8w79tRQ==} dev: false + /@ice/swc-plugin-remove-export/0.1.2: + resolution: {integrity: sha512-HPeYj+z1ylaD5fJkSqyJ+eXbrHiCdy/t/t56uyf20aqsAyx12EiHVnfV4blW31DSWhFt/veAUXYzbaJ8b9KLOQ==} + dev: true + /@ice/swc-plugin-remove-export/0.1.3-0: resolution: {integrity: sha512-fLa+qSmK3iGqk8FdUHMkjv5oTvwiQIkLTVLs3o4uQbphfsRJE1eWam0IHA/vFs7WzoE3m3pwMcDv3LUOAhAysA==} dev: false + /@ice/webpack-config/1.0.12: + resolution: {integrity: sha512-8o7E1w1virHm82bhdxsftzi+epB/avwOIzySvt5pmYc0eGdQ1hjYHYIBtnOs3Jvw6JE/DXIOf4KRqsufqqTddA==} + dependencies: + '@ice/bundles': 0.1.8 + '@rollup/pluginutils': 4.2.1 + browserslist: 4.21.5 + consola: 2.15.3 + fast-glob: 3.2.12 + process: 0.11.10 + transitivePeerDependencies: + - supports-color + dev: true + /@iceworks/generate-project/2.0.2: resolution: {integrity: sha512-t7/uHl5kM71o+xyR+FnaPsgyFqhFQm89TdqPahM4Kv/ubdKDknFVUYLio1khMDGY8Ops0ahn/+KM+gFnHEKSQw==} engines: {node: '>=12.20.0'} @@ -6045,10 +6257,42 @@ packages: engines: {node: '>=14'} dev: false - /@remix-run/router/1.4.0: - resolution: {integrity: sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==} + /@remix-run/router/1.5.0: + resolution: {integrity: sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==} engines: {node: '>=14'} + /@remix-run/web-blob/3.0.4: + resolution: {integrity: sha512-AfegzZvSSDc+LwnXV+SwROTrDtoLiPxeFW+jxgvtDAnkuCX1rrzmVJ6CzqZ1Ai0bVfmJadkG5GxtAfYclpPmgw==} + dependencies: + '@remix-run/web-stream': 1.0.3 + web-encoding: 1.1.5 + dev: true + + /@remix-run/web-fetch/4.3.3: + resolution: {integrity: sha512-DK9vA2tgsadcFPpxW4fvN198tiWpyPhwR0EYOuM4QjpDCz0G619c9RDMdyMy6a7Qb/jwiyx9SOPHWc65QAl+1g==} + engines: {node: ^10.17 || >=12.3} + dependencies: + '@remix-run/web-blob': 3.0.4 + '@remix-run/web-form-data': 3.0.4 + '@remix-run/web-stream': 1.0.3 + '@web3-storage/multipart-parser': 1.0.0 + abort-controller: 3.0.0 + data-uri-to-buffer: 3.0.1 + mrmime: 1.0.1 + dev: true + + /@remix-run/web-form-data/3.0.4: + resolution: {integrity: sha512-UMF1jg9Vu9CLOf8iHBdY74Mm3PUvMW8G/XZRJE56SxKaOFWGSWlfxfG+/a3boAgHFLTkP7K4H1PxlRugy1iQtw==} + dependencies: + web-encoding: 1.1.5 + dev: true + + /@remix-run/web-stream/1.0.3: + resolution: {integrity: sha512-wlezlJaA5NF6SsNMiwQnnAW6tnPzQ5I8qk0Y0pSohm0eHKa2FQ1QhEKLVVcDDu02TmkfHgnux0igNfeYhDOXiA==} + dependencies: + web-streams-polyfill: 3.2.1 + dev: true + /@rollup/plugin-alias/3.1.9_rollup@2.79.1: resolution: {integrity: sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==} engines: {node: '>=8.0.0'} @@ -6411,6 +6655,15 @@ packages: transitivePeerDependencies: - supports-color + /@swc/core-darwin-arm64/1.3.19: + resolution: {integrity: sha512-6xLtmXzS4nNWGQkajbiAjGXspUJfxS2IWoGQ16J9nfOFdttKyoIG5o5+mxUfKeg5bXw9cI+r675kN/irx3z7MQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@swc/core-darwin-arm64/1.3.41: resolution: {integrity: sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==} engines: {node: '>=10'} @@ -6419,6 +6672,15 @@ packages: requiresBuild: true optional: true + /@swc/core-darwin-x64/1.3.19: + resolution: {integrity: sha512-qCDQcngYBeWrsNS1kcBslRD0dahKcYKaUUWRC9yHpRcs3SRvnSpJyWQR4y9RCdO9YNmixJ9+5+zPD9qcgL7jBw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@swc/core-darwin-x64/1.3.41: resolution: {integrity: sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==} engines: {node: '>=10'} @@ -6427,6 +6689,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm-gnueabihf/1.3.19: + resolution: {integrity: sha512-ufbKW6Lhii1+kVCXnsHgqYIpRvXhPjdhMudfP4KKVgJtT6TsdEIr+KRAQIBHLjRUsTKA2DLsGEpu9jfjwFiNEg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm-gnueabihf/1.3.41: resolution: {integrity: sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==} engines: {node: '>=10'} @@ -6435,6 +6706,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-gnu/1.3.19: + resolution: {integrity: sha512-HHhqLRZv9Ss8orJrlEP4XRcLuqLDwFtGgbtHU8kyWBmQEtK42uT18Pf5RJBo5sPJHY8m5EO8C8y3hIbGmKtLyg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm64-gnu/1.3.41: resolution: {integrity: sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==} engines: {node: '>=10'} @@ -6443,6 +6723,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-musl/1.3.19: + resolution: {integrity: sha512-vipnF3C6T1368uHQqz8RpdszWxxGh0X8VBK3TdTOSWvI/duNZtZXEOZlB2Nh9w+u09umVw0MsJhvg86Aon39mA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-arm64-musl/1.3.41: resolution: {integrity: sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==} engines: {node: '>=10'} @@ -6451,6 +6740,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-gnu/1.3.19: + resolution: {integrity: sha512-dUbq8mnIqBhU7OppfY3ncOvl26691WFGxd97QtnnlfMZrKnaofKFMIxE9sTHOLSbBo16AylnEMiwa45w2UWDEg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-x64-gnu/1.3.41: resolution: {integrity: sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==} engines: {node: '>=10'} @@ -6459,6 +6757,15 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-musl/1.3.19: + resolution: {integrity: sha512-RiVZrlkNGcj9jZyjF7YFOW3fj9fWPC25AYkknLpWxAmLQcp1piAWj+aSixmMWUC4QJau78VZzcm+kRgIOECALw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@swc/core-linux-x64-musl/1.3.41: resolution: {integrity: sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==} engines: {node: '>=10'} @@ -6467,6 +6774,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-arm64-msvc/1.3.19: + resolution: {integrity: sha512-r2U6GC+go2iiLx5JBZIJswYFiMv0yOsm+pgE1srVvAc8dP02320t9yh0Uj4Sr2hDipTWJ33Y5PMZwEsZSfBVbQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-arm64-msvc/1.3.41: resolution: {integrity: sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==} engines: {node: '>=10'} @@ -6475,6 +6791,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-ia32-msvc/1.3.19: + resolution: {integrity: sha512-SPpESDa4vr0PRvUiqXSi8oZSTmkDOGrZ/pSiLD7ISgjsQ5RQMbPkuEK0ztWljim87q2fO0bGVVhyaVYxdOVS1A==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-ia32-msvc/1.3.41: resolution: {integrity: sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==} engines: {node: '>=10'} @@ -6483,6 +6808,15 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-x64-msvc/1.3.19: + resolution: {integrity: sha512-0X5HqFC1wQlheOQDZeF6KNOSURZKkGISNK3aTSmTq9g7dDJ/kTcVjsdKbu2rK4ibCnlC9IS0cLK9FpROnsVPwA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@swc/core-win32-x64-msvc/1.3.41: resolution: {integrity: sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==} engines: {node: '>=10'} @@ -6491,6 +6825,24 @@ packages: requiresBuild: true optional: true + /@swc/core/1.3.19: + resolution: {integrity: sha512-KiXUv2vpmOaGhoLCN9Rw7Crsfq1YmOR2ZbajiqNAh/iu0d3CKn5JZhLRs6S7nCk78cwFFac2obQfTWPePLUe/g==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.19 + '@swc/core-darwin-x64': 1.3.19 + '@swc/core-linux-arm-gnueabihf': 1.3.19 + '@swc/core-linux-arm64-gnu': 1.3.19 + '@swc/core-linux-arm64-musl': 1.3.19 + '@swc/core-linux-x64-gnu': 1.3.19 + '@swc/core-linux-x64-musl': 1.3.19 + '@swc/core-win32-arm64-msvc': 1.3.19 + '@swc/core-win32-ia32-msvc': 1.3.19 + '@swc/core-win32-x64-msvc': 1.3.19 + dev: true + /@swc/core/1.3.41: resolution: {integrity: sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==} engines: {node: '>=10'} @@ -6511,7 +6863,6 @@ packages: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: tslib: 2.5.0 - dev: false /@szmarczak/http-timer/1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} @@ -7205,7 +7556,6 @@ packages: /@uni/env/1.1.0: resolution: {integrity: sha512-2GVgUzxIaO2vGElXEuc45+I7L6Jbw8inLDDFuC0K4htjKtPmYywKSE6oDhvmdAXb4GCOH8hmxECYtAh1rjsgoQ==} - dev: false /@use-gesture/core/10.2.20: resolution: {integrity: sha512-4lFhHc8so4yIHkBEs641DnEsBxPyhJ5GEjB4PURFDH4p/FcZriH6w99knZgI63zN/MBFfylMyb8+PDuj6RIXKQ==} @@ -7260,6 +7610,10 @@ packages: - terser dev: true + /@web3-storage/multipart-parser/1.0.0: + resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} + dev: true + /@webassemblyjs/ast/1.11.1: resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} dependencies: @@ -7357,6 +7711,12 @@ packages: /@xtuc/long/4.2.2: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + /@zxing/text-encoding/0.9.0: + resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} + requiresBuild: true + dev: true + optional: true + /JSONStream/1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -7369,6 +7729,13 @@ packages: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} dev: true + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: true + /accepts/1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -7864,7 +8231,6 @@ packages: /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - dev: false /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -9603,6 +9969,11 @@ packages: assert-plus: 1.0.0 dev: false + /data-uri-to-buffer/3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + dev: true + /data-urls/3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -10018,12 +10389,10 @@ packages: /dotenv-expand/8.0.3: resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} engines: {node: '>=12'} - dev: false /dotenv/16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} - dev: false /dts-bundle/0.7.3: resolution: {integrity: sha512-EEAEuPRk8QyKhoN90NHTh+spSQujkkvOnKWUfuzpmC/fgryiWopL1SegSktx0UsoPfNidIGVDN7/AXpBDBv0WQ==} @@ -10065,7 +10434,6 @@ packages: hasBin: true dependencies: jake: 10.8.5 - dev: false /electron-to-chromium/1.4.322: resolution: {integrity: sha512-KovjizNC9XB7dno/2GjxX8VS0SlfPpCjtyoKft+bCO+UfD8bFy16hY4Sh9s0h9BDxbRH2U0zX5VBjpM1LTcNlg==} @@ -11080,7 +11448,6 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.0 - dev: false /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -11101,6 +11468,11 @@ packages: '@types/node': 17.0.45 require-like: 0.1.2 + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: true + /eventemitter3/4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -11379,7 +11751,6 @@ packages: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: minimatch: 5.1.6 - dev: false /filesize/8.0.7: resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} @@ -13050,7 +13421,6 @@ packages: chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 - dev: false /jest-changed-files/28.1.3: resolution: {integrity: sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==} @@ -14105,7 +14475,6 @@ packages: /jsonc-parser/3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: false /jsonfile/4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -14850,7 +15219,6 @@ packages: pathe: 1.1.0 pkg-types: 1.0.2 ufo: 1.1.1 - dev: false /moment/2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} @@ -15422,7 +15790,6 @@ packages: /path-to-regexp/6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: false /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -15430,7 +15797,6 @@ packages: /pathe/1.1.0: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} - dev: false /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -15492,7 +15858,6 @@ packages: jsonc-parser: 3.2.0 mlly: 1.1.1 pathe: 1.1.0 - dev: false /pkg-up/3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} @@ -16773,7 +17138,6 @@ packages: /process/0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - dev: false /progress/2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} @@ -17781,17 +18145,39 @@ packages: tiny-invariant: 1.3.1 tiny-warning: 1.0.3 - /react-router-dom/6.9.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==} + /react-router-dom/6.10.0: + resolution: {integrity: sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.4.0 + '@remix-run/router': 1.5.0 + react-router: 6.10.0 + dev: true + + /react-router-dom/6.10.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.5.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - react-router: 6.9.0_react@18.2.0 + react-router: 6.10.0_react@18.2.0 + + /react-router-dom/6.10.0_react@18.2.0: + resolution: {integrity: sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.5.0 + react: 18.2.0 + react-router: 6.10.0_react@18.2.0 dev: false /react-router/5.3.4_react@17.0.2: @@ -17810,13 +18196,22 @@ packages: tiny-invariant: 1.3.1 tiny-warning: 1.0.3 - /react-router/6.9.0_react@18.2.0: - resolution: {integrity: sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==} + /react-router/6.10.0: + resolution: {integrity: sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.5.0 + dev: true + + /react-router/6.10.0_react@18.2.0: + resolution: {integrity: sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.4.0 + '@remix-run/router': 1.5.0 react: 18.2.0 /react-textarea-autosize/8.4.0_h7fc2el62uaa77gho3xhys6ola: @@ -18278,7 +18673,6 @@ packages: hasBin: true dependencies: glob: 7.2.3 - dev: false /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -19153,7 +19547,6 @@ packages: dependencies: '@babel/runtime': 7.21.0 universal-env: 3.3.3 - dev: false /stylehacks/5.1.1_postcss@8.4.12: resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} @@ -19413,7 +19806,6 @@ packages: dependencies: mkdirp: 0.5.6 rimraf: 2.6.3 - dev: false /term-size/2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -19452,6 +19844,7 @@ packages: serialize-javascript: 6.0.1 terser: 5.14.2 webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia + dev: true /terser-webpack-plugin/5.3.5_ru5mwuzjydtus6p6kkdltzgpiq: resolution: {integrity: sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==} @@ -19476,7 +19869,6 @@ packages: serialize-javascript: 6.0.1 terser: 5.14.2 webpack: 5.76.2_esbuild@0.16.17 - dev: true /terser-webpack-plugin/5.3.5_webpack@5.76.2: resolution: {integrity: sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==} @@ -19919,7 +20311,6 @@ packages: /ufo/1.1.1: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} - dev: false /uglify-js/3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} @@ -20059,7 +20450,6 @@ packages: engines: {npm: '>=3.0.0'} dependencies: '@uni/env': 1.1.0 - dev: false /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -20589,9 +20979,22 @@ packages: dependencies: defaults: 1.0.4 + /web-encoding/1.1.5: + resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} + dependencies: + util: 0.12.5 + optionalDependencies: + '@zxing/text-encoding': 0.9.0 + dev: true + /web-namespaces/1.1.4: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} + /web-streams-polyfill/3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -20653,7 +21056,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.0.0 - webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia + webpack: 5.76.2_esbuild@0.16.17 /webpack-dev-server/4.11.1_debug@4.3.4+webpack@5.76.2: resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} @@ -20741,7 +21144,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia + webpack: 5.76.2_esbuild@0.16.17 webpack-dev-middleware: 5.3.3_webpack@5.76.2 ws: 8.12.1 transitivePeerDependencies: @@ -20850,7 +21253,6 @@ packages: - '@swc/core' - esbuild - uglify-js - dev: true /webpack/5.76.2_x5vjxkescd7wbsfiatu6dm4yia: resolution: {integrity: sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==} @@ -20890,6 +21292,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /webpackbar/5.0.2_webpack@5.76.2: resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==} From d7509af115915fb696c0087f3a755d1f91a38f79 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 11:35:08 +0800 Subject: [PATCH 14/38] fix: lock --- pnpm-lock.yaml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ec16e40bf..673a4cf261 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1288,7 +1288,7 @@ importers: webpack: ^5.76.2 webpack-dev-server: ^4.9.2 dependencies: - '@remix-run/router': 1.3.3 + '@remix-run/router': 1.5.0 chalk: 4.1.2 consola: 2.15.3 htmlparser2: 8.0.1 @@ -6252,11 +6252,6 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@remix-run/router/1.3.3: - resolution: {integrity: sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==} - engines: {node: '>=14'} - dev: false - /@remix-run/router/1.5.0: resolution: {integrity: sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==} engines: {node: '>=14'} @@ -19844,7 +19839,6 @@ packages: serialize-javascript: 6.0.1 terser: 5.14.2 webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia - dev: true /terser-webpack-plugin/5.3.5_ru5mwuzjydtus6p6kkdltzgpiq: resolution: {integrity: sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==} @@ -19869,6 +19863,7 @@ packages: serialize-javascript: 6.0.1 terser: 5.14.2 webpack: 5.76.2_esbuild@0.16.17 + dev: true /terser-webpack-plugin/5.3.5_webpack@5.76.2: resolution: {integrity: sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==} @@ -21056,7 +21051,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.0.0 - webpack: 5.76.2_esbuild@0.16.17 + webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia /webpack-dev-server/4.11.1_debug@4.3.4+webpack@5.76.2: resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} @@ -21144,7 +21139,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.76.2_esbuild@0.16.17 + webpack: 5.76.2_x5vjxkescd7wbsfiatu6dm4yia webpack-dev-middleware: 5.3.3_webpack@5.76.2 ws: 8.12.1 transitivePeerDependencies: @@ -21253,6 +21248,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /webpack/5.76.2_x5vjxkescd7wbsfiatu6dm4yia: resolution: {integrity: sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==} @@ -21292,7 +21288,6 @@ packages: - '@swc/core' - esbuild - uglify-js - dev: true /webpackbar/5.0.2_webpack@5.76.2: resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==} From a6bbef1ee41b8facd077fc563f51df824ca845a7 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Thu, 6 Apr 2023 11:44:54 +0800 Subject: [PATCH 15/38] feat: async data loader --- examples/with-data-loader/package.json | 3 +- .../src/pages/with-defer-loader.tsx | 50 ++++++++++++ .../src/pages/with-defer-loaders.tsx | 80 +++++++++++++++++++ .../with-data-loader/src/pages/with-ssr.tsx | 60 ++++++++++++++ packages/runtime/src/dataLoader.ts | 61 ++++++-------- packages/runtime/src/routes.tsx | 62 +++++++++++--- 6 files changed, 265 insertions(+), 51 deletions(-) create mode 100644 examples/with-data-loader/src/pages/with-defer-loader.tsx create mode 100644 examples/with-data-loader/src/pages/with-defer-loaders.tsx create mode 100644 examples/with-data-loader/src/pages/with-ssr.tsx diff --git a/examples/with-data-loader/package.json b/examples/with-data-loader/package.json index 4cdfb8420e..8ca8b75ad2 100644 --- a/examples/with-data-loader/package.json +++ b/examples/with-data-loader/package.json @@ -13,7 +13,8 @@ "@ice/app": "workspace:*", "@ice/runtime": "workspace:*", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.10.0" }, "devDependencies": { "@types/react": "^18.0.17", diff --git a/examples/with-data-loader/src/pages/with-defer-loader.tsx b/examples/with-data-loader/src/pages/with-defer-loader.tsx new file mode 100644 index 0000000000..2be2ca0838 --- /dev/null +++ b/examples/with-data-loader/src/pages/with-defer-loader.tsx @@ -0,0 +1,50 @@ +import { useData, defineDataLoader } from 'ice'; +import * as React from 'react'; +import { Await } from 'react-router-dom'; +import styles from './index.module.css'; + +export default function Home() { + const data = useData(); + + return ( + <> +

With dataLoader

+ Loading item info...

} + > + Error loading!

+ } + > + {(itemInfo) => { + return ( +

+ Item id is {itemInfo.id} +

+ ); +}} +
+
+ + ); +} + +export function pageConfig() { + return { + title: 'Home', + }; +} + +export const dataLoader = defineDataLoader(async () => { + const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + id: 1233, + }); + }, 100); + }); + return await promise; +}, { defer: true }); + diff --git a/examples/with-data-loader/src/pages/with-defer-loaders.tsx b/examples/with-data-loader/src/pages/with-defer-loaders.tsx new file mode 100644 index 0000000000..ef49c93ebd --- /dev/null +++ b/examples/with-data-loader/src/pages/with-defer-loaders.tsx @@ -0,0 +1,80 @@ +import { useData, defineDataLoader } from 'ice'; +import * as React from 'react'; +import { Await } from 'react-router-dom'; +import styles from './index.module.css'; + +export default function Home() { + const data = useData(); + + return ( + <> +

With dataLoader

+ Loading item info...

} + > + Error loading!

+ } + > + {(itemInfo) => { + return ( +

+ Item id is {itemInfo.id} +

+ ); +}} +
+
+ Loading item info...

} + > + Error loading!

+ } + > + {(itemInfo) => { + return ( +

+ Item price is {itemInfo.price} +

+ ); +}} +
+
+ + ); +} + +export function pageConfig() { + return { + title: 'Home', + }; +} + +export const dataLoader = defineDataLoader([ + async () => { + const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + id: 1233, + }); + }, 100); + }); + return await promise; + }, + async () => { + const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + price: 9.99, + }); + }, 2000); + }); + return await promise; + }, +], { defer: true }); + diff --git a/examples/with-data-loader/src/pages/with-ssr.tsx b/examples/with-data-loader/src/pages/with-ssr.tsx new file mode 100644 index 0000000000..fe75f77874 --- /dev/null +++ b/examples/with-data-loader/src/pages/with-ssr.tsx @@ -0,0 +1,60 @@ +import { useData, defineDataLoader, defineServerDataLoader } from 'ice'; +import * as React from 'react'; +import { Await } from 'react-router-dom'; +import styles from './index.module.css'; + +export default function Home() { + const data = useData(); + + return ( + <> +

With dataLoader

+ Loading item info...

} + > + Error loading!

+ } + > + {(itemInfo) => { + return ( +

+ Item id is {itemInfo.id} +

+ ); +}} +
+
+ + ); +} + +export function pageConfig() { + return { + title: 'Home', + }; +} + +export const dataLoader = defineDataLoader(async () => { + const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + id: 1233, + }); + }, 100); + }); + return await promise; +}, { defer: true }); + +export const serverDataLoader = defineServerDataLoader(async () => { + const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + id: 1233, + }); + }, 100); + }); + return await promise; +}); \ No newline at end of file diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index dcb80936be..dfea439acd 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -7,7 +7,6 @@ interface Loaders { interface CachedResult { value: any; - status: string; } interface LoaderOptions { @@ -16,20 +15,26 @@ interface LoaderOptions { appExport: AppExport; } +interface DefineOptions { + defer?: boolean; +} + +type DefineResult = [DataLoaderConfig, DefineOptions?]; + export interface LoadRoutesDataOptions { renderMode: RenderMode; } -export function defineDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig { - return dataLoaderConfig; +export function defineDataLoader(dataLoaderConfig: DataLoaderConfig, options?: DefineOptions): DefineResult { + return [dataLoaderConfig, options]; } -export function defineServerDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig { - return dataLoaderConfig; +export function defineServerDataLoader(dataLoaderConfig: DataLoaderConfig, options?: DefineOptions): DefineResult { + return [dataLoaderConfig, options]; } -export function defineStaticDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig { - return dataLoaderConfig; +export function defineStaticDataLoader(dataLoaderConfig: DataLoaderConfig): DefineResult { + return [dataLoaderConfig]; } /** @@ -129,7 +134,8 @@ export function callDataLoader(dataLoader: DataLoaderConfig, requestContext: Req const loaders = dataLoader.map(loader => { return typeof loader === 'object' ? loadDataByCustomFetcher(loader) : loader(requestContext); }); - return Promise.all(loaders); + + return loaders; } if (typeof dataLoader === 'object') { @@ -156,7 +162,6 @@ function loadInitialDataInClient(loaders: Loaders) { if (dataFromSSR) { cache.set(renderMode === 'SSG' ? `${id}_ssg` : id, { value: dataFromSSR, - status: 'RESOLVED', }); if (renderMode === 'SSR') { @@ -164,15 +169,14 @@ function loadInitialDataInClient(loaders: Loaders) { } } - const dataLoader = loaders[id]; + const dataLoaderConfig = loaders[id]; - if (dataLoader) { + if (dataLoaderConfig) { const requestContext = getRequestContext(window.location); - const loader = callDataLoader(dataLoader, requestContext); + const loader = callDataLoader(dataLoaderConfig[0], requestContext); cache.set(id, { value: loader, - status: 'LOADING', }); } }); @@ -214,38 +218,20 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { } (window as any).__ICE_DATA_LOADER__ = { - getData: async (id, options: LoadRoutesDataOptions) => { + getData: (id, options: LoadRoutesDataOptions) => { let result; - // first render for ssg use data from build time. - // second render for ssg will use data from data loader. + // First render for ssg use data from build time, second render for ssg will use data from data loader. const cacheKey = `${id}${options?.renderMode === 'SSG' ? '_ssg' : ''}`; + + // In CSR, all dataLoader is called by global data loader to avoid bundle dataLoader in page bundle duplicate. result = cache.get(cacheKey); // Always fetch new data after cache is been used. cache.delete(cacheKey); // Already send data request. if (result) { - const { status, value } = result; - - if (status === 'RESOLVED') { - return result; - } - - try { - if (Array.isArray(value)) { - return await Promise.all(value); - } - - return await value; - } catch (error) { - console.error('DataLoader: getData error.\n', error); - - return { - message: 'DataLoader: getData error.', - error, - }; - } + return result.value; } const dataLoader = dataloaderConfig[id]; @@ -256,9 +242,8 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { } // Call dataLoader. - // In CSR, all dataLoader is called by global data loader to avoid bundle dataLoader in page bundle duplicate. const requestContext = getRequestContext(window.location); - return await callDataLoader(dataLoader, requestContext); + return callDataLoader(dataLoader[0], requestContext); }, }; } diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 18ffa47793..fe9327b59e 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { defer } from 'react-router-dom'; import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; @@ -94,26 +95,63 @@ export interface RouteLoaderOptions { renderMode: RenderMode; } -export function createRouteLoader(options: RouteLoaderOptions): () => Promise { - return async () => { - const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module; - const { requestContext, renderMode, routeId } = options; +export function createRouteLoader(options: RouteLoaderOptions): () => any { + const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module; + const { requestContext, renderMode, routeId } = options; + + let loaderConfig: any; + if (renderMode === 'SSG') { + loaderConfig = staticDataLoader; + } else if (renderMode === 'SSR') { + loaderConfig = serverDataLoader || dataLoader; + } else { + loaderConfig = dataLoader; + } + + const getData = () => { const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; const globalLoader = hasGlobalLoader ? (window as any).__ICE_DATA_LOADER__ : null; let routeData: any; if (globalLoader) { - routeData = await globalLoader.getData(routeId, { renderMode }); + routeData = globalLoader.getData(routeId, { renderMode }); } else { - let loader: DataLoaderConfig; - if (renderMode === 'SSG') { - loader = staticDataLoader; - } else if (renderMode === 'SSR') { - loader = serverDataLoader || dataLoader; + routeData = loaderConfig && callDataLoader(loaderConfig[0], requestContext); + } + return routeData; + }; + + if (loaderConfig?.[1]?.defer) { + return async () => { + const promise = getData(); + + return defer({ + data: promise, + routeConfig: pageConfig ? pageConfig({}) : {}, + }); + }; + } + + return async () => { + let routeData; + const promise = getData(); + + try { + if (Array.isArray(promise)) { + routeData = await Promise.all(promise); + } else if (promise instanceof Promise) { + routeData = await promise; } else { - loader = dataLoader; + routeData = promise; } - routeData = loader && await callDataLoader(loader, requestContext); + } catch (error) { + console.error('DataLoader: getData error.\n', error); + + routeData = { + message: 'DataLoader: getData error.', + error, + }; } + const routeConfig = pageConfig ? pageConfig({ data: routeData }) : {}; const loaderData = { data: routeData, pageConfig: routeConfig }; // CSR and load next route data. From e1eb822b3a9266b19bfab363e4b01e49468f7834 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 12:13:03 +0800 Subject: [PATCH 16/38] fix: update lock --- packages/miniapp-runtime/src/app/App.tsx | 2 +- pnpm-lock.yaml | 160 ++--------------------- 2 files changed, 13 insertions(+), 149 deletions(-) diff --git a/packages/miniapp-runtime/src/app/App.tsx b/packages/miniapp-runtime/src/app/App.tsx index 1e359cc69e..02cd818647 100644 --- a/packages/miniapp-runtime/src/app/App.tsx +++ b/packages/miniapp-runtime/src/app/App.tsx @@ -7,7 +7,7 @@ export default function App() { const { strict, errorBoundary } = appConfig.app; const StrictMode = strict ? React.StrictMode : React.Fragment; const ErrorBoundary = errorBoundary ? AppErrorBoundary : React.Fragment; - console.log('ErrorBoundary', ErrorBoundary); + return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a04861b4f0..eeae601fba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1155,7 +1155,7 @@ importers: dependencies: '@ice/style-import': link:../style-import devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice packages/plugin-auth: specifiers: @@ -1165,7 +1165,7 @@ importers: '@types/react-dom': ^18.0.0 regenerator-runtime: ^0.13.9 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice '@ice/runtime': link:../runtime '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -1180,7 +1180,7 @@ importers: consola: 2.15.3 extract-css-assets-webpack-plugin: 0.2.10 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice packages/plugin-fusion: specifiers: @@ -1189,7 +1189,7 @@ importers: dependencies: '@ice/style-import': link:../style-import devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice packages/plugin-icestark: specifiers: @@ -1203,7 +1203,7 @@ importers: '@ice/stark': 2.7.5 '@ice/stark-app': 1.5.0 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice '@ice/runtime': link:../runtime '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -1231,7 +1231,7 @@ importers: babel-plugin-transform-jsx-slot: 0.1.2 babel-runtime-jsx-plus: 0.1.5 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -1265,14 +1265,14 @@ importers: regenerator-runtime: 0.11.1 sax: 1.2.4 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice webpack: 5.76.2 packages/plugin-moment-locales: specifiers: '@ice/app': ^3.1.2 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice packages/plugin-pha: specifiers: @@ -1295,7 +1295,7 @@ importers: humps: 2.0.1 lodash.clonedeep: 4.5.0 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice build-scripts: 2.1.0 esbuild: 0.16.17 webpack: 5.76.2_esbuild@0.16.17 @@ -1327,7 +1327,7 @@ importers: style-unit: 3.0.5 stylesheet-loader: 0.9.1 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice webpack: 5.76.2 packages/plugin-request: @@ -1343,7 +1343,7 @@ importers: ahooks: 3.7.5 axios: 0.27.2 devDependencies: - '@ice/app': 3.1.6 + '@ice/app': link:../ice '@ice/runtime': link:../runtime '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -1367,7 +1367,7 @@ importers: fast-glob: 3.2.12 micromatch: 4.0.5 devDependencies: - '@ice/app': 3.1.6_biqbaboplfbrettd7655fr4n2y + '@ice/app': link:../ice '@ice/runtime': link:../runtime '@types/micromatch': 4.0.2 '@types/react': 18.0.28 @@ -5105,142 +5105,6 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@ice/app/3.1.6: - resolution: {integrity: sha512-ch0UwX9VJikgg+cd9Yh9KUtkdbZVzTmm1vSZU/HP3fzsLugTWkxQz1LoULIS0hAfUCvrruzvCaXqGKoLeqfHpw==} - engines: {node: '>=14.19.0', npm: '>=3.0.0'} - hasBin: true - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - dependencies: - '@babel/generator': 7.18.10 - '@babel/parser': 7.18.10 - '@babel/traverse': 7.18.10 - '@babel/types': 7.18.10 - '@ice/bundles': 0.1.8 - '@ice/route-manifest': 1.1.1 - '@ice/runtime': 1.1.5 - '@ice/webpack-config': 1.0.12 - '@swc/helpers': 0.4.14 - '@types/express': 4.17.17 - address: 1.2.2 - build-scripts: 2.1.0 - chalk: 4.1.2 - commander: 9.5.0 - consola: 2.15.3 - cross-spawn: 7.0.3 - detect-port: 1.5.1 - dotenv: 16.0.3 - dotenv-expand: 8.0.3 - ejs: 3.1.8 - estree-walker: 3.0.3 - fast-glob: 3.2.12 - find-up: 5.0.0 - fs-extra: 10.1.0 - micromatch: 4.0.5 - mlly: 1.1.1 - mrmime: 1.0.1 - open: 8.4.2 - path-to-regexp: 6.2.1 - regenerator-runtime: 0.13.11 - resolve.exports: 1.1.1 - semver: 7.3.8 - temp: 0.9.4 - yargs-parser: 21.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@ice/app/3.1.6_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-ch0UwX9VJikgg+cd9Yh9KUtkdbZVzTmm1vSZU/HP3fzsLugTWkxQz1LoULIS0hAfUCvrruzvCaXqGKoLeqfHpw==} - engines: {node: '>=14.19.0', npm: '>=3.0.0'} - hasBin: true - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - dependencies: - '@babel/generator': 7.18.10 - '@babel/parser': 7.18.10 - '@babel/traverse': 7.18.10 - '@babel/types': 7.18.10 - '@ice/bundles': 0.1.8 - '@ice/route-manifest': 1.1.1 - '@ice/runtime': 1.1.5_biqbaboplfbrettd7655fr4n2y - '@ice/webpack-config': 1.0.12 - '@swc/helpers': 0.4.14 - '@types/express': 4.17.17 - address: 1.2.2 - build-scripts: 2.1.0 - chalk: 4.1.2 - commander: 9.5.0 - consola: 2.15.3 - cross-spawn: 7.0.3 - detect-port: 1.5.1 - dotenv: 16.0.3 - dotenv-expand: 8.0.3 - ejs: 3.1.8 - estree-walker: 3.0.3 - fast-glob: 3.2.12 - find-up: 5.0.0 - fs-extra: 10.1.0 - micromatch: 4.0.5 - mlly: 1.1.1 - mrmime: 1.0.1 - open: 8.4.2 - path-to-regexp: 6.2.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - regenerator-runtime: 0.13.11 - resolve.exports: 1.1.1 - semver: 7.3.8 - temp: 0.9.4 - yargs-parser: 21.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@ice/bundles/0.1.8: - resolution: {integrity: sha512-cjaUkwcVkVO5EOdZqyFyDFYAPs5nnNXdOVRAY3CegBMOxy8K5MgoQO5xJkOMmphUSm+X0pbb0xUza1b9wR2fYA==} - dependencies: - '@ice/swc-plugin-keep-export': 0.1.4 - '@ice/swc-plugin-node-transform': 0.1.0-5 - '@ice/swc-plugin-remove-export': 0.1.2 - '@swc/core': 1.3.19 - ansi-html-community: 0.0.8 - caniuse-lite: 1.0.30001462 - chokidar: 3.5.3 - core-js: 3.29.1 - core-js-pure: 3.29.0 - error-stack-parser: 2.1.4 - esbuild: 0.16.17 - events: 3.3.0 - html-entities: 2.3.3 - jest-worker: 27.5.1 - less: 4.1.2 - postcss: 8.4.12 - react-refresh: 0.14.0 - sass: 1.50.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@ice/jsx-runtime/0.2.0: - resolution: {integrity: sha512-yUfoWloQq2mdyCTsTTWwxtx0dmiwC4Xe2QEaQCg/yFqQKz7hOFBOrju6RodMl7K0fdpj6k1UBfjLd9gPyUBAbw==} - peerDependencies: - react: ^16 || ^17 || ^18 - dependencies: - style-unit: 3.0.5 - dev: true - - /@ice/jsx-runtime/0.2.0_react@18.2.0: - resolution: {integrity: sha512-yUfoWloQq2mdyCTsTTWwxtx0dmiwC4Xe2QEaQCg/yFqQKz7hOFBOrju6RodMl7K0fdpj6k1UBfjLd9gPyUBAbw==} - peerDependencies: - react: ^16 || ^17 || ^18 - dependencies: - react: 18.2.0 - style-unit: 3.0.5 - dev: true - /@ice/pkg-plugin-component/1.0.0: resolution: {integrity: sha512-Mff6Em1RwY2NOZgciQyy2NXL65u9ebHV2Jqb5746vUVEh0l7Xpokb+3djV1pKcgED4wU8pSuSQ6jj+hXd9Ht1Q==} dependencies: From 2df375ff363ae044ba6e07eb63a6400381abd10c Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 13:40:08 +0800 Subject: [PATCH 17/38] fix: hydration --- packages/plugin-store/src/runtime.tsx | 2 +- packages/runtime/src/ClientRouter.tsx | 2 +- packages/runtime/src/runClientApp.tsx | 17 +++++++++++++++++ packages/runtime/tests/runClientApp.test.tsx | 7 +++++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/plugin-store/src/runtime.tsx b/packages/plugin-store/src/runtime.tsx index 74cd1d169c..cfb6fc9b80 100644 --- a/packages/plugin-store/src/runtime.tsx +++ b/packages/plugin-store/src/runtime.tsx @@ -5,7 +5,7 @@ import type { StoreConfig } from './types.js'; const EXPORT_CONFIG_NAME = 'storeConfig'; -const runtime: RuntimePlugin = async ({ appContext, addWrapper, addProvider, useAppContext }, runtimeOptions) => { +const runtime: RuntimePlugin = async ({ appContext, addWrapper, addProvider }, runtimeOptions) => { const { appExport, appData } = appContext; const exported = appExport[EXPORT_CONFIG_NAME]; const storeConfig: StoreConfig = (typeof exported === 'function' ? await exported(appData) : exported) || {}; diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index a8fb607f66..eca9198a14 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -10,7 +10,7 @@ function ClientRouter(props: AppRouterProps) { let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { - element = } />; + element = ; } else { element = ( diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index f9023978eb..6a926412b6 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -18,6 +18,7 @@ import ClientRouter from './ClientRouter.js'; import { setFetcher } from './dataLoader.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; import { AppContextProvider } from './AppContext.js'; +import matchRoutes from './matchRoutes.js'; export interface RunClientAppOptions { app: AppExport; @@ -149,6 +150,22 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { console.warn(`Root node #${rootId} is not found, current root is automatically created by the framework.`); } const hydrationData = needHydrate ? { loaderData } : undefined; + if (needHydrate) { + const lazyMatches = matchRoutes(routes, history.location, basename).filter((m) => m.route.lazy); + if (lazyMatches?.length > 0) { + // Load the lazy matches and update the routes before creating your router + // so we can hydrate the SSR-rendered content synchronously. + await Promise.all( + lazyMatches.map(async (m) => { + let routeModule = await m.route.lazy(); + Object.assign(m.route, { + ...routeModule, + lazy: undefined, + }); + }), + ); + } + } const routerOptions = { basename, routes, diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index 90f497f312..a4df760fb6 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -89,8 +89,11 @@ describe('run client app', () => { id: 'home', path: '/', componentName: 'Home', - // Make sure the component is loaded before render. - Component: homeItem.default, + lazy: () => { + return { + Component: homeItem.default, + }; + }, }, ]; From 7bf894f79aa9125f99910734e771004c7bf86bc0 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 15:49:25 +0800 Subject: [PATCH 18/38] fix: router --- .../src/components/FrameworkLayout.tsx | 2 +- examples/icestark-layout/src/pages/about.tsx | 2 +- .../plugin-icestark/src/runtime/framework.tsx | 20 +++++++++++- packages/runtime/src/ClientRouter.tsx | 22 +++++++++++-- packages/runtime/src/ServerRouter.tsx | 32 +++++++++++++++++-- packages/runtime/src/runClientApp.tsx | 12 +++---- packages/runtime/src/runServerApp.tsx | 30 ++--------------- packages/runtime/src/types.ts | 3 +- packages/runtime/tests/routes.test.tsx | 14 +++++--- 9 files changed, 89 insertions(+), 48 deletions(-) diff --git a/examples/icestark-layout/src/components/FrameworkLayout.tsx b/examples/icestark-layout/src/components/FrameworkLayout.tsx index e8e34a3255..01a079d20d 100644 --- a/examples/icestark-layout/src/components/FrameworkLayout.tsx +++ b/examples/icestark-layout/src/components/FrameworkLayout.tsx @@ -13,4 +13,4 @@ export default function FrameworkLayout({ children }) {
); -} \ No newline at end of file +} diff --git a/examples/icestark-layout/src/pages/about.tsx b/examples/icestark-layout/src/pages/about.tsx index 78d823e59a..764f4847be 100644 --- a/examples/icestark-layout/src/pages/about.tsx +++ b/examples/icestark-layout/src/pages/about.tsx @@ -7,4 +7,4 @@ export default function About() { home ); -} \ No newline at end of file +} diff --git a/packages/plugin-icestark/src/runtime/framework.tsx b/packages/plugin-icestark/src/runtime/framework.tsx index c801e1bf6a..b83b81b1c9 100644 --- a/packages/plugin-icestark/src/runtime/framework.tsx +++ b/packages/plugin-icestark/src/runtime/framework.tsx @@ -4,10 +4,12 @@ import type { RuntimePlugin, AppRouterProps } from '@ice/runtime/types'; import type { RouteInfo, AppConfig } from '../types'; const { useState, useEffect } = React; + const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => { const { appExport, appData } = appContext; const OriginalRouter = getAppRouter(); const { layout, getApps, appRouter } = appExport?.icestark || {}; + if (getApps) { const FrameworkRouter = (props: AppRouterProps) => { const [routeInfo, setRouteInfo] = useState({}); @@ -62,7 +64,23 @@ const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => { path="/" location={props.location} render={() => { - return ; + const { routerContext } = props; + routerContext.routes = [ + ...routerContext.routes, + { + path: '*', + Component: () => ( + process.env.NODE_ENV === 'development' + ?
Add $.tsx to folder pages as a 404 component
+ : null + ), + }, + ]; + const routerProps = { + ...props, + routerContext, + }; + return ; }} />
diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index eca9198a14..f60cd04ee8 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -1,15 +1,33 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { RouterProvider } from 'react-router-dom'; +import { createRouter } from '@remix-run/router'; import type { AppRouterProps } from './types.js'; import App from './App.js'; import { DataContextProvider } from './single-router.js'; +// createRouter only needs to be called once. +let router: ReturnType; + function ClientRouter(props: AppRouterProps) { - const { router, Component, loaderData } = props; + const { Component, loaderData, routerContext } = props; + + useEffect(() => { + return () => { + // In case of micro app, ClientRouter will be unmounted, + // duspose router before mount again. + router.dispose(); + router = null; + }; + }, []); let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { + if (!router) { + // Do not call createRouter outside of the component, + // otherwise it cause error in the use case of micro app. + router = createRouter(routerContext).initialize(); + } element = ; } else { element = ( diff --git a/packages/runtime/src/ServerRouter.tsx b/packages/runtime/src/ServerRouter.tsx index dad0dea0fa..9d0adf012f 100644 --- a/packages/runtime/src/ServerRouter.tsx +++ b/packages/runtime/src/ServerRouter.tsx @@ -1,11 +1,39 @@ import React from 'react'; -import { StaticRouterProvider } from 'react-router-dom/server.mjs'; +import { StaticRouterProvider, createStaticRouter } from 'react-router-dom/server.mjs'; +import type { RouteObject } from 'react-router-dom'; +import { RouteComponent } from './routes.js'; import type { AppRouterProps } from './types.js'; import App from './App.js'; +let router: ReturnType = null; + +function createServerRoutes(routes: RouteObject[]) { + return routes.map((route) => { + let dataRoute = { + // Static Router need element or Component when matched. + element: , + id: route.id, + index: route.index, + path: route.path, + children: null, + }; + + if (route?.children?.length > 0) { + let children = createServerRoutes( + route.children, + ); + dataRoute.children = children; + } + return dataRoute; + }); +} function ServerRouter(props: AppRouterProps) { - const { router, routerContext } = props; + const { routerContext, routes } = props; + if (!router) { + // API createStaticRouter only needs to be called once. + router = createStaticRouter(createServerRoutes(routes), routerContext); + } return ( diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 6a926412b6..da14c41146 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -1,6 +1,6 @@ import React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { createRouter, createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; +import { createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router'; import type { History } from '@remix-run/router'; import type { AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, @@ -9,9 +9,8 @@ import { createHistory as createHistorySingle } from './single-router.js'; import { setHistory } from './history.js'; import Runtime from './runtime.js'; import { getAppData } from './appData.js'; -import { getRoutesPath } from './routes.js'; +import { getRoutesPath, loadRouteModule } from './routes.js'; import type { RouteLoaderOptions } from './routes.js'; -import { loadRouteModule } from './routes.js'; import getRequestContext from './requestContext.js'; import getAppConfig from './appConfig.js'; import ClientRouter from './ClientRouter.js'; @@ -175,10 +174,7 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { let router = null; let singleComponent = null; let routeData = null; - // Create router before render. - if (process.env.ICE_CORE_ROUTER === 'true') { - router = createRouter(routerOptions).initialize(); - } else { + if (process.env.ICE_CORE_ROUTER !== 'true') { const { Component, loader } = await loadRouteModule(routes[0]); singleComponent = Component || routes[0].Component; routeData = loader && await loader(); @@ -188,7 +184,7 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { { - let dataRoute = { - // Static Router need element or Component when matched. - element: , - id: route.id, - index: route.index, - path: route.path, - children: null, - }; - - if (route?.children?.length > 0) { - let children = createServerRoutes( - route.children, - ); - dataRoute.children = children; - } - return dataRoute; - }); -} - interface RenderServerEntry { runtime: Runtime; matches: RouteMatch[]; @@ -341,10 +317,10 @@ async function renderServerEntry( const routerContext = { matches, basename, loaderData, location, }; - const router = createStaticRouter(createServerRoutes(routes), routerContext); + const documentContext = { main: ( - + ), }; const element = ( diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index b6deb79a26..add0482189 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, Router, Location } from '@remix-run/router'; +import type { InitialEntry, AgnosticRouteObject, Location } 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'; @@ -222,7 +222,6 @@ export interface RuntimeModules { export interface AppRouterProps { routes?: RouteObject[]; - router?: Router; routerContext?: any; location?: Location; Component?: ComponentType; diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index beeb2e8c91..5012d87bcb 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -98,8 +98,11 @@ describe('routes', () => { it('route WrapRouteComponent', () => { const domstring = renderToString( - // @ts-ignore -
wrapper{children}
, layout: false }] }}> + +
wrapper{children}
, layout: false }] }} + >
home
}} />
, ); @@ -108,8 +111,11 @@ describe('routes', () => { it('route WrapRouteComponent match layout', () => { const domstring = renderToString( - // @ts-ignore -
wrapper{children}
, layout: false }] }}> + +
wrapper{children}
, layout: false }] }} + >
home
}} />
, ); From 4a9bb5c5f271c2389c81292e4cd41f8222e69aa2 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 18:05:38 +0800 Subject: [PATCH 19/38] fix: router --- packages/runtime/src/ClientRouter.tsx | 16 +++++----------- packages/runtime/src/ServerRouter.tsx | 8 ++++---- packages/runtime/src/utils/useHooks.ts | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 packages/runtime/src/utils/useHooks.ts diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index f60cd04ee8..ab9417cc0e 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -1,33 +1,27 @@ import React, { useEffect } from 'react'; import { RouterProvider } from 'react-router-dom'; import { createRouter } from '@remix-run/router'; - +import { useOnce } from './utils/useHooks.js'; import type { AppRouterProps } from './types.js'; import App from './App.js'; import { DataContextProvider } from './single-router.js'; -// createRouter only needs to be called once. -let router: ReturnType; - function ClientRouter(props: AppRouterProps) { const { Component, loaderData, routerContext } = props; - + // createRouter only needs to be called once. + const router = useOnce(() => { + return process.env.ICE_CORE_ROUTER === 'true' ? createRouter(routerContext).initialize() : {}; + }); useEffect(() => { return () => { // In case of micro app, ClientRouter will be unmounted, // duspose router before mount again. router.dispose(); - router = null; }; }, []); let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { - if (!router) { - // Do not call createRouter outside of the component, - // otherwise it cause error in the use case of micro app. - router = createRouter(routerContext).initialize(); - } element = ; } else { element = ( diff --git a/packages/runtime/src/ServerRouter.tsx b/packages/runtime/src/ServerRouter.tsx index 9d0adf012f..3c9692f80c 100644 --- a/packages/runtime/src/ServerRouter.tsx +++ b/packages/runtime/src/ServerRouter.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { StaticRouterProvider, createStaticRouter } from 'react-router-dom/server.mjs'; import type { RouteObject } from 'react-router-dom'; import { RouteComponent } from './routes.js'; +import { useOnce } from './utils/useHooks.js'; import type { AppRouterProps } from './types.js'; import App from './App.js'; @@ -30,10 +31,9 @@ function createServerRoutes(routes: RouteObject[]) { function ServerRouter(props: AppRouterProps) { const { routerContext, routes } = props; - if (!router) { - // API createStaticRouter only needs to be called once. - router = createStaticRouter(createServerRoutes(routes), routerContext); - } + const router = useOnce(() => { + return createStaticRouter(createServerRoutes(routes), routerContext); + }); return ( diff --git a/packages/runtime/src/utils/useHooks.ts b/packages/runtime/src/utils/useHooks.ts new file mode 100644 index 0000000000..dab2a85d25 --- /dev/null +++ b/packages/runtime/src/utils/useHooks.ts @@ -0,0 +1,14 @@ +import { useRef } from 'react'; + +const useOnce = (callback: Function) => { + const ref = useRef(null); + + if (!ref.current) { + ref.current = callback(); + } + return ref.current; +}; + +export { + useOnce, +}; From 3adb0f6ebfddf4c18c320b363b6306b7388362cb Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Apr 2023 18:10:57 +0800 Subject: [PATCH 20/38] chore: log --- packages/runtime/src/runServerApp.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index dfcb59c7c3..706406baf7 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -67,7 +67,6 @@ export async function renderToEntry( requestContext: ServerContext, renderOptions: RenderOptions, ) { - console.log('renderToEntry.tsx'); const result = await renderToHTML(requestContext, renderOptions); const { value } = result; @@ -98,7 +97,6 @@ export async function renderToHTML( renderOptions: RenderOptions, ): Promise { const result = await doRender(requestContext, renderOptions); - const { value } = result; if (typeof value === 'string') { From afb842804302054633712030a7c87467d065142b Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 7 Apr 2023 11:04:35 +0800 Subject: [PATCH 21/38] fix: hmr --- examples/multi-target/ice.config.mts | 1 - examples/with-ssg/src/pages/index.tsx | 2 +- packages/runtime/src/ClientRouter.tsx | 29 +++++++++++++++++++------- packages/runtime/src/ServerRouter.tsx | 9 +++----- packages/runtime/src/runClientApp.tsx | 9 +------- packages/runtime/src/utils/useHooks.ts | 14 ------------- 6 files changed, 27 insertions(+), 37 deletions(-) delete mode 100644 packages/runtime/src/utils/useHooks.ts diff --git a/examples/multi-target/ice.config.mts b/examples/multi-target/ice.config.mts index d511ba9ed4..d86560cf30 100644 --- a/examples/multi-target/ice.config.mts +++ b/examples/multi-target/ice.config.mts @@ -2,7 +2,6 @@ import { defineConfig } from '@ice/app'; export default defineConfig(() => ({ ssr: true, - ssg: true, define: { 'process.env.NODE_ENV': JSON.stringify(true), }, diff --git a/examples/with-ssg/src/pages/index.tsx b/examples/with-ssg/src/pages/index.tsx index 5df7dc3e75..fd6781a8ca 100644 --- a/examples/with-ssg/src/pages/index.tsx +++ b/examples/with-ssg/src/pages/index.tsx @@ -39,4 +39,4 @@ export const staticDataLoader = defineStaticDataLoader(() => { return { price: '0.00', }; -}); \ No newline at end of file +}); diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index ab9417cc0e..6417ef0589 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -1,24 +1,39 @@ import React, { useEffect } from 'react'; import { RouterProvider } from 'react-router-dom'; import { createRouter } from '@remix-run/router'; -import { useOnce } from './utils/useHooks.js'; import type { AppRouterProps } from './types.js'; import App from './App.js'; import { DataContextProvider } from './single-router.js'; +import { useAppContext } from './AppContext.js'; +let router: ReturnType = null; function ClientRouter(props: AppRouterProps) { const { Component, loaderData, routerContext } = props; - // createRouter only needs to be called once. - const router = useOnce(() => { - return process.env.ICE_CORE_ROUTER === 'true' ? createRouter(routerContext).initialize() : {}; - }); + const { revalidate } = useAppContext(); + + function clearRouter() { + if (router) { + router.dispose(); + router = null; + } + } + // API createRouter only needs to be called once, and create before mount. + if (process.env.ICE_CORE_ROUTER === 'true') { + // Clear router before re-create in case of hot module replacement. + clearRouter(); + router = createRouter(routerContext).initialize(); + } useEffect(() => { + if (revalidate) { + // Revalidate after render for SSG while staticDataLoader and dataLoader both defined. + router?.revalidate(); + } return () => { // In case of micro app, ClientRouter will be unmounted, // duspose router before mount again. - router.dispose(); + clearRouter(); }; - }, []); + }, [revalidate]); let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { diff --git a/packages/runtime/src/ServerRouter.tsx b/packages/runtime/src/ServerRouter.tsx index 3c9692f80c..a7fa4d43fd 100644 --- a/packages/runtime/src/ServerRouter.tsx +++ b/packages/runtime/src/ServerRouter.tsx @@ -2,12 +2,9 @@ import React from 'react'; import { StaticRouterProvider, createStaticRouter } from 'react-router-dom/server.mjs'; import type { RouteObject } from 'react-router-dom'; import { RouteComponent } from './routes.js'; -import { useOnce } from './utils/useHooks.js'; import type { AppRouterProps } from './types.js'; import App from './App.js'; -let router: ReturnType = null; - function createServerRoutes(routes: RouteObject[]) { return routes.map((route) => { let dataRoute = { @@ -31,15 +28,15 @@ function createServerRoutes(routes: RouteObject[]) { function ServerRouter(props: AppRouterProps) { const { routerContext, routes } = props; - const router = useOnce(() => { - return createStaticRouter(createServerRoutes(routes), routerContext); - }); + // Server router only be called once. + const router = createStaticRouter(createServerRoutes(routes), routerContext); return ( ); diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index da14c41146..0ca1bd3259 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -135,7 +135,7 @@ interface RenderOptions { async function render({ history, runtime, needHydrate }: RenderOptions) { const appContext = runtime.getAppContext(); - const { appConfig, loaderData, routes, revalidate, basename } = appContext; + const { appConfig, loaderData, routes, basename } = appContext; const appRender = runtime.getRender(); const AppRuntimeProvider = runtime.composeAppProvider() || React.Fragment; const AppRouter = runtime.getAppRouter(); @@ -171,7 +171,6 @@ async function render({ history, runtime, needHydrate }: RenderOptions) { history, hydrationData, }; - let router = null; let singleComponent = null; let routeData = null; if (process.env.ICE_CORE_ROUTER !== 'true') { @@ -193,12 +192,6 @@ async function render({ history, runtime, needHydrate }: RenderOptions) {
, ); - if (revalidate) { - // Revalidate after render for SSG while staticDataLoader and dataLoader both defined. - setTimeout(() => { - router?.revalidate(); - }); - } return renderRoot; } diff --git a/packages/runtime/src/utils/useHooks.ts b/packages/runtime/src/utils/useHooks.ts deleted file mode 100644 index dab2a85d25..0000000000 --- a/packages/runtime/src/utils/useHooks.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useRef } from 'react'; - -const useOnce = (callback: Function) => { - const ref = useRef(null); - - if (!ref.current) { - ref.current = callback(); - } - return ref.current; -}; - -export { - useOnce, -}; From 86d89272176cfa583a1276ae7d4ca4405d5e7d14 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 7 Apr 2023 11:33:02 +0800 Subject: [PATCH 22/38] fix: test --- examples/basic-project/ice.config.mts | 1 + packages/runtime/tests/runClientApp.test.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/examples/basic-project/ice.config.mts b/examples/basic-project/ice.config.mts index 8baac7c3c8..151f031488 100644 --- a/examples/basic-project/ice.config.mts +++ b/examples/basic-project/ice.config.mts @@ -9,6 +9,7 @@ export default defineConfig(() => ({ syntaxFeatures: { exportDefaultFrom: true, }, + codeSplitting: false, server: { onDemand: true, format: 'esm', diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index a4df760fb6..c2658b5471 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -5,18 +5,28 @@ import React from 'react'; import { renderToString } from 'react-dom/server'; import { expect, it, vi, describe, beforeEach, afterEach } from 'vitest'; +import { fetch, Request, Response } from '@remix-run/web-fetch'; import runClientApp from '../src/runClientApp'; import { useAppData } from '../src/AppContext'; describe('run client app', () => { let windowSpy; let documentSpy; + if (!globalThis.fetch) { + // @ts-expect-error + globalThis.fetch = fetch; + // @ts-expect-error + globalThis.Request = Request; + // @ts-expect-error + globalThis.Response = Response; + } const mockData = { location: new URL('http://localhost:4000/'), history: { replaceState: vi.fn(), }, addEventListener: vi.fn(), + removeEventListener: vi.fn(), }; beforeEach(() => { process.env.ICE_CORE_ROUTER = 'true'; From 40fcdd90c1216e6a2bab1458ebd697059c2ef220 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 7 Apr 2023 11:33:47 +0800 Subject: [PATCH 23/38] fix: test --- examples/basic-project/ice.config.mts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/basic-project/ice.config.mts b/examples/basic-project/ice.config.mts index 151f031488..8baac7c3c8 100644 --- a/examples/basic-project/ice.config.mts +++ b/examples/basic-project/ice.config.mts @@ -9,7 +9,6 @@ export default defineConfig(() => ({ syntaxFeatures: { exportDefaultFrom: true, }, - codeSplitting: false, server: { onDemand: true, format: 'esm', From 3d4f08dc44db4812968d689774806c98fb6de7fb Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Mon, 10 Apr 2023 10:18:43 +0800 Subject: [PATCH 24/38] feat: await --- examples/with-data-loader/package.json | 3 +- .../src/pages/with-defer-loader.tsx | 27 +++------- .../src/pages/with-defer-loaders.tsx | 50 ++++--------------- .../with-data-loader/src/pages/with-ssr.tsx | 27 +++------- packages/ice/src/constant.ts | 1 + packages/runtime/src/index.ts | 4 +- packages/runtime/src/routes.tsx | 17 ++++++- 7 files changed, 43 insertions(+), 86 deletions(-) diff --git a/examples/with-data-loader/package.json b/examples/with-data-loader/package.json index 8ca8b75ad2..4cdfb8420e 100644 --- a/examples/with-data-loader/package.json +++ b/examples/with-data-loader/package.json @@ -13,8 +13,7 @@ "@ice/app": "workspace:*", "@ice/runtime": "workspace:*", "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.10.0" + "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.0.17", diff --git a/examples/with-data-loader/src/pages/with-defer-loader.tsx b/examples/with-data-loader/src/pages/with-defer-loader.tsx index 2be2ca0838..59d20750cc 100644 --- a/examples/with-data-loader/src/pages/with-defer-loader.tsx +++ b/examples/with-data-loader/src/pages/with-defer-loader.tsx @@ -1,6 +1,4 @@ -import { useData, defineDataLoader } from 'ice'; -import * as React from 'react'; -import { Await } from 'react-router-dom'; +import { useData, defineDataLoader, Await } from 'ice'; import styles from './index.module.css'; export default function Home() { @@ -9,24 +7,11 @@ export default function Home() { return ( <>

With dataLoader

- Loading item info...

} - > - Error loading!

- } - > - {(itemInfo) => { - return ( -

- Item id is {itemInfo.id} -

- ); -}} -
-
+ Loading item info...

} errorElement={

Error loading!

}> + {(itemInfo) => { + return

Item id is {itemInfo.id}

; + }} +
); } diff --git a/examples/with-data-loader/src/pages/with-defer-loaders.tsx b/examples/with-data-loader/src/pages/with-defer-loaders.tsx index ef49c93ebd..9dbcf0e973 100644 --- a/examples/with-data-loader/src/pages/with-defer-loaders.tsx +++ b/examples/with-data-loader/src/pages/with-defer-loaders.tsx @@ -1,6 +1,4 @@ -import { useData, defineDataLoader } from 'ice'; -import * as React from 'react'; -import { Await } from 'react-router-dom'; +import { useData, defineDataLoader, Await } from 'ice'; import styles from './index.module.css'; export default function Home() { @@ -9,42 +7,16 @@ export default function Home() { return ( <>

With dataLoader

- Loading item info...

} - > - Error loading!

- } - > - {(itemInfo) => { - return ( -

- Item id is {itemInfo.id} -

- ); -}} -
-
- Loading item info...

} - > - Error loading!

- } - > - {(itemInfo) => { - return ( -

- Item price is {itemInfo.price} -

- ); -}} -
-
+ Loading item info...

} errorElement={

Error loading!

}> + {(itemInfo) => { + return

Item id is {itemInfo.id}

; + }} +
+ Loading item info...

} errorElement={

Error loading!

}> + {(itemInfo) => { + return

Item price is {itemInfo.price}

; + }} +
); } diff --git a/examples/with-data-loader/src/pages/with-ssr.tsx b/examples/with-data-loader/src/pages/with-ssr.tsx index fe75f77874..70e690f0ea 100644 --- a/examples/with-data-loader/src/pages/with-ssr.tsx +++ b/examples/with-data-loader/src/pages/with-ssr.tsx @@ -1,6 +1,4 @@ -import { useData, defineDataLoader, defineServerDataLoader } from 'ice'; -import * as React from 'react'; -import { Await } from 'react-router-dom'; +import { useData, defineDataLoader, defineServerDataLoader, Await } from 'ice'; import styles from './index.module.css'; export default function Home() { @@ -9,24 +7,11 @@ export default function Home() { return ( <>

With dataLoader

- Loading item info...

} - > - Error loading!

- } - > - {(itemInfo) => { - return ( -

- Item id is {itemInfo.id} -

- ); -}} -
-
+ Loading item info...

} errorElement={

Error loading!

}> + {(itemInfo) => { + return

Item id is {itemInfo.id}

; + }} +
); } diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts index caeb9b6b15..ecb0e9d1b8 100644 --- a/packages/ice/src/constant.ts +++ b/packages/ice/src/constant.ts @@ -61,6 +61,7 @@ export const RUNTIME_EXPORTS = [ 'ClientOnly', 'withSuspense', 'useSuspenseData', + 'Await', 'defineDataLoader', 'defineServerDataLoader', 'defineStaticDataLoader', diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 879bdc5f72..601241036b 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -50,7 +50,7 @@ import KeepAliveOutlet from './KeepAliveOutlet.js'; import ClientOnly from './ClientOnly.js'; import useMounted from './useMounted.js'; import { withSuspense, useSuspenseData } from './Suspense.js'; -import { createRouteLoader, WrapRouteComponent, RouteErrorComponent } from './routes.js'; +import { createRouteLoader, WrapRouteComponent, RouteErrorComponent, Await } from './routes.js'; export { getAppConfig, @@ -92,6 +92,8 @@ export { withSuspense, useSuspenseData, + Await, + createRouteLoader, WrapRouteComponent, RouteErrorComponent, diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 70c5b7245c..daf5b9bd20 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { defer, useRouteError } from 'react-router-dom'; +import React, { Suspense } from 'react'; +import { defer, useRouteError, Await as ReactRouterAwait } from 'react-router-dom'; import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; @@ -89,6 +89,19 @@ export function RouteErrorComponent() { return <>; } +export function Await(props) { + return ( + + + {props.children} + + + ); +} + /** * Create loader function for route module. */ From 646e1b780c5a5bbc0254a6943848a447a77dd5ab Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Mon, 10 Apr 2023 11:01:16 +0800 Subject: [PATCH 25/38] fix: await component --- packages/runtime/src/routes.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index daf5b9bd20..96ddf689bb 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -92,12 +92,12 @@ export function RouteErrorComponent() { export function Await(props) { return ( - {props.children} - + ); } From 3db58ebbad5eb66d5a17e5b6200fd4bdef8ce265 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 11 Apr 2023 09:04:54 +0800 Subject: [PATCH 26/38] fix: lint --- examples/with-data-loader/src/pages/with-defer-loader.tsx | 2 +- examples/with-data-loader/src/pages/with-defer-loaders.tsx | 4 ++-- examples/with-data-loader/src/pages/with-ssr.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/with-data-loader/src/pages/with-defer-loader.tsx b/examples/with-data-loader/src/pages/with-defer-loader.tsx index 59d20750cc..80d97f3ce4 100644 --- a/examples/with-data-loader/src/pages/with-defer-loader.tsx +++ b/examples/with-data-loader/src/pages/with-defer-loader.tsx @@ -23,7 +23,7 @@ export function pageConfig() { } export const dataLoader = defineDataLoader(async () => { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve) => { setTimeout(() => { resolve({ id: 1233, diff --git a/examples/with-data-loader/src/pages/with-defer-loaders.tsx b/examples/with-data-loader/src/pages/with-defer-loaders.tsx index 9dbcf0e973..06f7eb5cdb 100644 --- a/examples/with-data-loader/src/pages/with-defer-loaders.tsx +++ b/examples/with-data-loader/src/pages/with-defer-loaders.tsx @@ -29,7 +29,7 @@ export function pageConfig() { export const dataLoader = defineDataLoader([ async () => { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve) => { setTimeout(() => { resolve({ id: 1233, @@ -39,7 +39,7 @@ export const dataLoader = defineDataLoader([ return await promise; }, async () => { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve) => { setTimeout(() => { resolve({ price: 9.99, diff --git a/examples/with-data-loader/src/pages/with-ssr.tsx b/examples/with-data-loader/src/pages/with-ssr.tsx index 70e690f0ea..16c363547c 100644 --- a/examples/with-data-loader/src/pages/with-ssr.tsx +++ b/examples/with-data-loader/src/pages/with-ssr.tsx @@ -23,7 +23,7 @@ export function pageConfig() { } export const dataLoader = defineDataLoader(async () => { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve) => { setTimeout(() => { resolve({ id: 1233, @@ -34,7 +34,7 @@ export const dataLoader = defineDataLoader(async () => { }, { defer: true }); export const serverDataLoader = defineServerDataLoader(async () => { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve) => { setTimeout(() => { resolve({ id: 1233, From d254e5bc6c64505d2b059102c45f6c5e0ad86553 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 11 Apr 2023 09:20:51 +0800 Subject: [PATCH 27/38] refactor: type --- packages/runtime/src/dataLoader.ts | 36 ++++++++++++++++-------------- packages/runtime/src/index.ts | 4 ++-- packages/runtime/src/types.ts | 8 +++---- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index dfea439acd..2bbab257f1 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -1,4 +1,4 @@ -import type { RequestContext, RenderMode, DataLoaderConfig, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; +import type { RequestContext, RenderMode, Loader, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; import getRequestContext from './requestContext.js'; interface Loaders { @@ -15,26 +15,26 @@ interface LoaderOptions { appExport: AppExport; } -interface DefineOptions { +interface Options { defer?: boolean; } -type DefineResult = [DataLoaderConfig, DefineOptions?]; +type DataLoaderConfig = [Loader, Options?]; export interface LoadRoutesDataOptions { renderMode: RenderMode; } -export function defineDataLoader(dataLoaderConfig: DataLoaderConfig, options?: DefineOptions): DefineResult { - return [dataLoaderConfig, options]; +export function defineDataLoader(dataLoader: Loader, options?: Options): DataLoaderConfig { + return [dataLoader, options]; } -export function defineServerDataLoader(dataLoaderConfig: DataLoaderConfig, options?: DefineOptions): DefineResult { - return [dataLoaderConfig, options]; +export function defineServerDataLoader(dataLoader: Loader, options?: Options): DataLoaderConfig { + return [dataLoader, options]; } -export function defineStaticDataLoader(dataLoaderConfig: DataLoaderConfig): DefineResult { - return [dataLoaderConfig]; +export function defineStaticDataLoader(dataLoader: Loader): DataLoaderConfig { + return [dataLoader]; } /** @@ -129,7 +129,7 @@ export function loadDataByCustomFetcher(config: StaticDataLoader) { /** * Handle for different dataLoader. */ -export function callDataLoader(dataLoader: DataLoaderConfig, requestContext: RequestContext): DataLoaderResult { +export function callDataLoader(dataLoader: Loader, requestContext: RequestContext): DataLoaderResult { if (Array.isArray(dataLoader)) { const loaders = dataLoader.map(loader => { return typeof loader === 'object' ? loadDataByCustomFetcher(loader) : loader(requestContext); @@ -173,10 +173,11 @@ function loadInitialDataInClient(loaders: Loaders) { if (dataLoaderConfig) { const requestContext = getRequestContext(window.location); - const loader = callDataLoader(dataLoaderConfig[0], requestContext); + const [dataLoader] = dataLoaderConfig; + const promise = callDataLoader(dataLoader, requestContext); cache.set(id, { - value: loader, + value: promise, }); } }); @@ -187,7 +188,7 @@ function loadInitialDataInClient(loaders: Loaders) { * Load initial data and register global loader. * In order to load data, JavaScript modules, CSS and other assets in parallel. */ -async function init(dataloaderConfig: Loaders, options: LoaderOptions) { +async function init(loaders: Loaders, options: LoaderOptions) { const { fetcher, runtimeModules, @@ -212,7 +213,7 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { } try { - loadInitialDataInClient(dataloaderConfig); + loadInitialDataInClient(loaders); } catch (error) { console.error('Load initial data error: ', error); } @@ -234,16 +235,17 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) { return result.value; } - const dataLoader = dataloaderConfig[id]; + const dataLoaderConfig = loaders[id]; // No data loader. - if (!dataLoader) { + if (!dataLoaderConfig) { return null; } // Call dataLoader. const requestContext = getRequestContext(window.location); - return callDataLoader(dataLoader[0], requestContext); + const [loader] = dataLoaderConfig; + return callDataLoader(loader, requestContext); }, }; } diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 601241036b..38991adb37 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -16,7 +16,7 @@ import type { RouteWrapper, RenderMode, DistType, - DataLoaderConfig, + Loader, RouteWrapperConfig, } from './types.js'; import Runtime from './runtime.js'; @@ -111,7 +111,7 @@ export type { RouteWrapper, RenderMode, DistType, - DataLoaderConfig, + Loader, RunClientAppOptions, MetaType, TitleType, diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index add0482189..86da7f116a 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -49,7 +49,7 @@ export interface StaticDataLoader { // route.defineDataLoader // route.defineServerDataLoader // route.defineStaticDataLoader -export type DataLoaderConfig = DataLoader | StaticDataLoader | Array; +export type Loader = DataLoader | StaticDataLoader | Array; // route.pageConfig export type PageConfig = (args: { data?: RouteData }) => RouteConfig; @@ -126,9 +126,9 @@ export interface RequestContext extends ServerContext { export type ComponentModule = { default?: ComponentType; Component?: ComponentType; - staticDataLoader?: DataLoaderConfig; - serverDataLoader?: DataLoaderConfig; - dataLoader?: DataLoaderConfig; + staticDataLoader?: Loader; + serverDataLoader?: Loader; + dataLoader?: Loader; pageConfig?: PageConfig; [key: string]: any; }; From 6f4307436d697983047396245d863b88ec4105be Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 11 Apr 2023 10:00:53 +0800 Subject: [PATCH 28/38] fix: type --- packages/runtime/src/dataLoader.ts | 21 +++++----- packages/runtime/src/routes.tsx | 63 ++++++++++++++++++++---------- packages/runtime/src/types.ts | 12 ++++-- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index 2bbab257f1..db7f446a45 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -1,6 +1,9 @@ -import type { RequestContext, RenderMode, Loader, DataLoaderResult, RuntimeModules, AppExport, StaticRuntimePlugin, CommonJsRuntime, StaticDataLoader } from './types.js'; import getRequestContext from './requestContext.js'; - +import type { + RequestContext, RenderMode, AppExport, + RuntimeModules, StaticRuntimePlugin, CommonJsRuntime, + Loader, DataLoaderResult, StaticDataLoader, DataLoaderConfig, DataLoaderOptions, +} from './types.js'; interface Loaders { [routeId: string]: DataLoaderConfig; } @@ -9,27 +12,21 @@ interface CachedResult { value: any; } -interface LoaderOptions { +interface Options { fetcher: Function; runtimeModules: RuntimeModules['statics']; appExport: AppExport; } -interface Options { - defer?: boolean; -} - -type DataLoaderConfig = [Loader, Options?]; - export interface LoadRoutesDataOptions { renderMode: RenderMode; } -export function defineDataLoader(dataLoader: Loader, options?: Options): DataLoaderConfig { +export function defineDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig { return [dataLoader, options]; } -export function defineServerDataLoader(dataLoader: Loader, options?: Options): DataLoaderConfig { +export function defineServerDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig { return [dataLoader, options]; } @@ -188,7 +185,7 @@ function loadInitialDataInClient(loaders: Loaders) { * Load initial data and register global loader. * In order to load data, JavaScript modules, CSS and other assets in parallel. */ -async function init(loaders: Loaders, options: LoaderOptions) { +async function init(loaders: Loaders, options: Options) { const { fetcher, runtimeModules, diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 96ddf689bb..b867095bfb 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -1,6 +1,8 @@ import React, { Suspense } from 'react'; -import { defer, useRouteError, Await as ReactRouterAwait } from 'react-router-dom'; -import type { RouteItem, RouteModules, RenderMode, DataLoaderConfig, RequestContext, ComponentModule } from './types.js'; +import { useRouteError, defer, Await as ReactRouterAwait } from 'react-router-dom'; +// eslint-disable-next-line camelcase +import type { UNSAFE_DeferredData } from '@remix-run/router'; +import type { RouteItem, RouteModules, RenderMode, RequestContext, ComponentModule, DataLoaderConfig } from './types.js'; import RouteWrapper from './RouteWrapper.js'; import { useAppContext } from './AppContext.js'; import { callDataLoader } from './dataLoader.js'; @@ -106,8 +108,8 @@ export function Await(props) { * Create loader function for route module. */ interface LoaderData { - data: any; - pageConfig: any; + data?: any; + pageConfig?: any; } export interface RouteLoaderOptions { @@ -117,19 +119,32 @@ export interface RouteLoaderOptions { renderMode: RenderMode; } -export function createRouteLoader(options: RouteLoaderOptions): () => any { +// eslint-disable-next-line camelcase +type LoaderFunction = () => LoaderData | UNSAFE_DeferredData | Promise; + +export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module; const { requestContext, renderMode, routeId } = options; - let loaderConfig: any; + let dataLoaderConfig: DataLoaderConfig; if (renderMode === 'SSG') { - loaderConfig = staticDataLoader; + dataLoaderConfig = staticDataLoader; } else if (renderMode === 'SSR') { - loaderConfig = serverDataLoader || dataLoader; + dataLoaderConfig = serverDataLoader || dataLoader; } else { - loaderConfig = dataLoader; + dataLoaderConfig = dataLoader; + } + + if (!dataLoaderConfig) { + return () => { + return { + pageConfig: pageConfig ? pageConfig({}) : {}, + }; + }; } + const [loader, loaderOptions] = dataLoaderConfig; + const getData = () => { const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; const globalLoader = hasGlobalLoader ? (window as any).__ICE_DATA_LOADER__ : null; @@ -137,33 +152,36 @@ export function createRouteLoader(options: RouteLoaderOptions): () => any { if (globalLoader) { routeData = globalLoader.getData(routeId, { renderMode }); } else { - routeData = loaderConfig && callDataLoader(loaderConfig[0], requestContext); + routeData = callDataLoader(loader, requestContext); } return routeData; }; - if (loaderConfig?.[1]?.defer) { + // Async dataLoader. + if (loaderOptions?.defer) { return async () => { const promise = getData(); return defer({ data: promise, - routeConfig: pageConfig ? pageConfig({}) : {}, + // Call pageConfig without data. + pageConfig: pageConfig ? pageConfig({}) : {}, }); }; } + // Await dataLoader before render. return async () => { - let routeData; - const promise = getData(); + const result = getData(); + let routeData; try { - if (Array.isArray(promise)) { - routeData = await Promise.all(promise); - } else if (promise instanceof Promise) { - routeData = await promise; + if (Array.isArray(result)) { + routeData = await Promise.all(result); + } else if (result instanceof Promise) { + routeData = await result; } else { - routeData = promise; + routeData = result; } } catch (error) { console.error('DataLoader: getData error.\n', error); @@ -175,11 +193,16 @@ export function createRouteLoader(options: RouteLoaderOptions): () => any { } const routeConfig = pageConfig ? pageConfig({ data: routeData }) : {}; - const loaderData = { data: routeData, pageConfig: routeConfig }; + const loaderData = { + data: routeData, + pageConfig: routeConfig, + }; + // CSR and load next route data. if (typeof window !== 'undefined') { await updateRoutesConfig(loaderData); } + return loaderData; }; } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 86da7f116a..c36f3cb693 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -71,6 +71,12 @@ export interface RoutesData { [routeId: string]: RouteData; } +export interface DataLoaderOptions { + defer?: boolean; +} + +export type DataLoaderConfig = [Loader, DataLoaderOptions?]; + export interface LoaderDatas { [routeId: string]: LoaderData; } @@ -126,9 +132,9 @@ export interface RequestContext extends ServerContext { export type ComponentModule = { default?: ComponentType; Component?: ComponentType; - staticDataLoader?: Loader; - serverDataLoader?: Loader; - dataLoader?: Loader; + staticDataLoader?: DataLoaderConfig; + serverDataLoader?: DataLoaderConfig; + dataLoader?: DataLoaderConfig; pageConfig?: PageConfig; [key: string]: any; }; From ae3a24085061a7e2b4f9498094c8993a7d0b773e Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 11 Apr 2023 10:14:53 +0800 Subject: [PATCH 29/38] fix: app data loader --- packages/runtime/src/appData.ts | 11 +++++------ packages/runtime/src/types.ts | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/appData.ts b/packages/runtime/src/appData.ts index 351b3a6ade..9b1da08cea 100644 --- a/packages/runtime/src/appData.ts +++ b/packages/runtime/src/appData.ts @@ -12,14 +12,13 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) return await globalLoader.getData('__app'); } - if (appExport?.dataLoader) { - return await appExport.dataLoader(requestContext); - } - - const loader = appExport?.dataLoader; + const appDataLoaderConfig = appExport?.dataLoader; - if (!loader) return null; + if (!appDataLoaderConfig) { + return null; + } + const [loader] = appDataLoaderConfig; await callDataLoader(loader, requestContext); } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index c36f3cb693..96ebf4843b 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -31,7 +31,7 @@ export type RouteConfig = T & { export interface AppExport { default?: AppConfig; [key: string]: any; - dataLoader?: DataLoader; + dataLoader?: DataLoaderConfig; } export type DataLoaderResult = (Promise | RouteData) | RouteData; From 0344c5e0925c8cd2fd2d5da12188d2aead723e98 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 07:45:17 +0800 Subject: [PATCH 30/38] fix: test --- packages/runtime/src/appData.ts | 2 +- packages/runtime/tests/runClientApp.test.tsx | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/appData.ts b/packages/runtime/src/appData.ts index 9b1da08cea..cfa5fc654f 100644 --- a/packages/runtime/src/appData.ts +++ b/packages/runtime/src/appData.ts @@ -19,7 +19,7 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) } const [loader] = appDataLoaderConfig; - await callDataLoader(loader, requestContext); + return await callDataLoader(loader, requestContext); } export { diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index c2658b5471..710bb69de5 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -110,9 +110,9 @@ describe('run client app', () => { it('run with static runtime', async () => { await runClientApp({ app: { - dataLoader: async () => { + dataLoader: [async () => { return { msg: staticMsg }; - }, + }], }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, @@ -261,10 +261,10 @@ describe('run client app', () => { let executed = false; await runClientApp({ app: { - dataLoader: async () => { + dataLoader: [async () => { executed = true; return { msg: '-getAppData' }; - }, + }], }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, @@ -290,10 +290,10 @@ describe('run client app', () => { await runClientApp({ app: { - dataLoader: async () => { + dataLoader: [async () => { executed = true; return { msg: 'app' }; - }, + }], }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, From 58bc374d4f5debbec2798412332e210941060dcc Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 07:59:56 +0800 Subject: [PATCH 31/38] fix: test --- packages/runtime/tests/routes.test.tsx | 7 +++---- packages/runtime/tests/runServerApp.test.tsx | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index 5012d87bcb..b406fd30b3 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -28,9 +28,9 @@ describe('routes', () => { const homeItem = { default: () => <>, pageConfig: () => ({ title: 'home' }), - dataLoader: async () => ({ type: 'getData' }), - serverDataLoader: async () => ({ type: 'getServerData' }), - staticDataLoader: async () => ({ type: 'getStaticData' }), + dataLoader: [async () => ({ type: 'getData' })], + serverDataLoader: [async () => ({ type: 'getServerData' })], + staticDataLoader: [async () => ({ type: 'getStaticData' })], }; const aboutItem = { default: () => <>, @@ -226,7 +226,6 @@ describe('routes', () => { })(); expect(routesDataCSR).toStrictEqual({ - data: undefined, pageConfig: { title: 'about', }, diff --git a/packages/runtime/tests/runServerApp.test.tsx b/packages/runtime/tests/runServerApp.test.tsx index bfad032c4c..0810aa95f2 100644 --- a/packages/runtime/tests/runServerApp.test.tsx +++ b/packages/runtime/tests/runServerApp.test.tsx @@ -14,7 +14,7 @@ describe('run server app', () => { const homeItem = { default: () =>
home
, pageConfig: () => ({ title: 'home' }), - dataLoader: async () => ({ data: 'test' }), + dataLoader: [async () => ({ data: 'test' })], }; const basicRoutes = [ { From 5c9fda74aabc75cb7601fedd69fab1c8ff3467d5 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 08:22:42 +0800 Subject: [PATCH 32/38] test: async data --- packages/runtime/tests/routes.test.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index b406fd30b3..ad84a338b5 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -12,7 +12,9 @@ import { createRouteLoader, getRoutesPath, WrapRouteComponent, + Await, } from '../src/routes.js'; +import { useData } from '../src/singleRouter'; describe('routes', () => { let windowSpy; @@ -36,6 +38,12 @@ describe('routes', () => { default: () => <>, pageConfig: () => ({ title: 'about' }), }; + const InfoItem = { + default: () => <>, + pageConfig: () => ({ title: 'home' }), + dataLoader: [async () => ({ type: 'getAsyncData' }), { defer: true }], + }; + const homeLazyItem = { Component: homeItem.default, loader: createRouteLoader({ @@ -145,6 +153,19 @@ describe('routes', () => { }); }); + it('load async route', async () => { + const { data: deferredResult } = await createRouteLoader({ + routeId: 'home', + module: InfoItem, + })(); + + const data = await deferredResult.data; + + expect(data).toStrictEqual({ + type: 'getAsyncData', + }); + }); + it('load route data for SSG', async () => { const routesDataSSG = await createRouteLoader({ routeId: 'home', From b9f3ff0cf1e41f0f820b65214da08300b0107a28 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 08:27:41 +0800 Subject: [PATCH 33/38] test: async data --- examples/with-data-loader/src/pages/with-defer-loader.tsx | 2 +- tests/integration/with-data-loader.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/with-data-loader/src/pages/with-defer-loader.tsx b/examples/with-data-loader/src/pages/with-defer-loader.tsx index 80d97f3ce4..0396facb93 100644 --- a/examples/with-data-loader/src/pages/with-defer-loader.tsx +++ b/examples/with-data-loader/src/pages/with-defer-loader.tsx @@ -9,7 +9,7 @@ export default function Home() {

With dataLoader

Loading item info...

} errorElement={

Error loading!

}> {(itemInfo) => { - return

Item id is {itemInfo.id}

; + return

Item id is {itemInfo.id}

; }}
diff --git a/tests/integration/with-data-loader.test.ts b/tests/integration/with-data-loader.test.ts index b5ae581329..1449443f06 100644 --- a/tests/integration/with-data-loader.test.ts +++ b/tests/integration/with-data-loader.test.ts @@ -49,6 +49,14 @@ describe(`start ${example}`, () => { expect(timeStampForRouter3).not.toStrictEqual(timeStampForRouter1); }); + test('should work with deferred data loader', async () => { + await page.push('/with-defer-loader'); + await page.waitForNetworkIdle(); + const data = (await page.$$text('#itemId'))[0]; + + expect(data).toEqual('1233'); + }); + afterAll(async () => { await browser.close(); }); From 2efed45c89d3107d2381772aa4a131a96ff8c1ad Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 08:45:13 +0800 Subject: [PATCH 34/38] docs: async data loader --- website/docs/guide/basic/data-loader.md | 83 ++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/website/docs/guide/basic/data-loader.md b/website/docs/guide/basic/data-loader.md index 5c0602178f..55d688e236 100644 --- a/website/docs/guide/basic/data-loader.md +++ b/website/docs/guide/basic/data-loader.md @@ -71,6 +71,51 @@ export const dataLoader = defineDataLoader(async () => { 受小程序环境限制,通过 `dataLoader` 定义的应用级数据加载将在 `App` 的 `onLaunch` 生命周期中进行,页面级数据加载则会在 `Page` 的 `onLoad` 生命周期中,二者均会阻塞页面的 UI 渲染。如果这不是你想要的效果,请按照常规方式进行数据请求。(比如在组件首次 `useEffect` 时发起数据请求) ::: + +## 异步消费数据 + +默认情况下,页面会等待数据请求完成后,再开始渲染,在数据接口比较快的情况下,这可以避免页面的二次渲染。 + +如果数据接口较慢,也可以选择先渲染不依赖于动态数据的部分,待数据回来后,再重新渲染依赖数据的页面内容。 + +具体做法如下: +- 1. 在定义 dataLoader 时标记 defer: true +- 2. 在消费数据时,使用 Await 组件包裹依赖于数据的页面内容 + +```tsx title="src/pages/index.tsx" +import { useData, defineDataLoader, Await } from 'ice'; + +// 页面组件的 UI 实现 +export default function Home() { + const data = useData(); + + return ( + <> +
Hello ICE
+ loading...} errorElement={
Error!
} /> + { + (data) =>
{JSON.stringify(data)}
+ } +
+ + ); +}; + +// 在定义 dataLoader 时标记 defer: true +export const dataLoader = defineDataLoader(async () => { + const data = await fetch('https://example.com/api/xxx'); + return data; +}, { defer: true }); +``` + +注意: +- 当 dataLoader 被声明为异步时,useData 返回的内容不可直接消费,需由 Await 组件处理 + +Await 组件接收三个参数 +* resolve 数据请求对象 +* fallback 数据加载过程中展示的 UI +* errorElement 请求失败时展示的 UI + ## 静态 dataLoader 当开发者希望通过统一的发送函数处理静态配置以完成 `dataLoader` 时,可以通过自定义 `fetcher` 以完成发送逻辑的统一封装,在 `dataLoader` 中只需要传递一份配置即可。 @@ -186,7 +231,7 @@ export default function Home(props) { import { useData, defineDataLoader } from 'ice'; export default function Home() { - const [useInfo, itemInfo] = useData(); + const [userInfo, itemInfo] = useData(); return ( <> @@ -198,8 +243,8 @@ export default function Home() { export const dataLoader = defineDataLoader([ async () => { - const useInfo = await fetch('https://example.com/api/userInfo'); - return useInfo; + const userInfo = await fetch('https://example.com/api/userInfo'); + return userInfo; }, async (ctx) => { const itemInfo = await fetch(`https://example.com/api/itemInfo${ctx?.query?.itemId}`); @@ -209,3 +254,35 @@ export const dataLoader = defineDataLoader([ ``` 多个数据请求的情况下,`useData` 获取的数据也对应的为数组,数组元素和 `dataLoader` 中定义的数据请求的返回值一一对应。 + +如果 dataLoader 被声明为异步,消费时可以分别 Await 不同的数据,这样可以做到先返回的数据,先渲染。 + +```tsx +import { useData, defineDataLoader } from 'ice'; + +export default function Home() { + const [userInfo, itemInfo] = useData(); + + return ( + <> + + { (data) =>
Hello {data?.name}
} +
+ + { (data) =>
{JSON.stringify(data)}
} +
+ + ); +}; + +export const dataLoader = defineDataLoader([ + async () => { + const useInfo = await fetch('https://example.com/api/userInfo'); + return useInfo; + }, + async (ctx) => { + const itemInfo = await fetch(`https://example.com/api/itemInfo${ctx?.query?.itemId}`); + return itemInfo; + }, +], { defer: true }); +``` \ No newline at end of file From 7aad1bfe91ecec232a720de4fa60d6df57a66e2f Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Tue, 18 Apr 2023 20:13:00 +0800 Subject: [PATCH 35/38] fix: lint --- packages/runtime/src/RenderWrapper.ts | 0 packages/runtime/tests/routes.test.tsx | 2 -- 2 files changed, 2 deletions(-) delete mode 100644 packages/runtime/src/RenderWrapper.ts diff --git a/packages/runtime/src/RenderWrapper.ts b/packages/runtime/src/RenderWrapper.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index ad84a338b5..de51bd1f43 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -12,9 +12,7 @@ import { createRouteLoader, getRoutesPath, WrapRouteComponent, - Await, } from '../src/routes.js'; -import { useData } from '../src/singleRouter'; describe('routes', () => { let windowSpy; From e1e900c1ec240bc18858c01e76cbe4f2c8409720 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Sat, 22 Apr 2023 11:54:06 +0800 Subject: [PATCH 36/38] refactor: loader config --- packages/runtime/src/appData.ts | 2 +- packages/runtime/src/dataLoader.ts | 20 ++++++++++++++------ packages/runtime/src/routes.tsx | 2 +- packages/runtime/src/types.ts | 5 ++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/runtime/src/appData.ts b/packages/runtime/src/appData.ts index cfa5fc654f..a610505383 100644 --- a/packages/runtime/src/appData.ts +++ b/packages/runtime/src/appData.ts @@ -18,7 +18,7 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) return null; } - const [loader] = appDataLoaderConfig; + const { loader } = appDataLoaderConfig; return await callDataLoader(loader, requestContext); } diff --git a/packages/runtime/src/dataLoader.ts b/packages/runtime/src/dataLoader.ts index db7f446a45..ffb1d29134 100644 --- a/packages/runtime/src/dataLoader.ts +++ b/packages/runtime/src/dataLoader.ts @@ -23,15 +23,23 @@ export interface LoadRoutesDataOptions { } export function defineDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig { - return [dataLoader, options]; + return { + loader: dataLoader, + options, + }; } export function defineServerDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig { - return [dataLoader, options]; + return { + loader: dataLoader, + options, + }; } export function defineStaticDataLoader(dataLoader: Loader): DataLoaderConfig { - return [dataLoader]; + return { + loader: dataLoader, + }; } /** @@ -170,8 +178,8 @@ function loadInitialDataInClient(loaders: Loaders) { if (dataLoaderConfig) { const requestContext = getRequestContext(window.location); - const [dataLoader] = dataLoaderConfig; - const promise = callDataLoader(dataLoader, requestContext); + const { loader } = dataLoaderConfig; + const promise = callDataLoader(loader, requestContext); cache.set(id, { value: promise, @@ -241,7 +249,7 @@ async function init(loaders: Loaders, options: Options) { // Call dataLoader. const requestContext = getRequestContext(window.location); - const [loader] = dataLoaderConfig; + const { loader } = dataLoaderConfig; return callDataLoader(loader, requestContext); }, }; diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 8280e8b7eb..79e7fce153 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -143,7 +143,7 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { }; } - const [loader, loaderOptions] = dataLoaderConfig; + const { loader, options: loaderOptions } = dataLoaderConfig; const getData = () => { const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index b9a1b8c451..2242fd6718 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -75,7 +75,10 @@ export interface DataLoaderOptions { defer?: boolean; } -export type DataLoaderConfig = [Loader, DataLoaderOptions?]; +export interface DataLoaderConfig { + loader: Loader; + options?: DataLoaderOptions; +} export interface LoadersData { [routeId: string]: LoaderData; From 970b94d7436e441ae6779ead9802e0ee20fe19a7 Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Mon, 24 Apr 2023 08:56:14 +0800 Subject: [PATCH 37/38] fix: test --- packages/runtime/tests/routes.test.tsx | 8 ++--- packages/runtime/tests/runClientApp.test.tsx | 32 ++++++++++++-------- packages/runtime/tests/runServerApp.test.tsx | 4 ++- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/runtime/tests/routes.test.tsx b/packages/runtime/tests/routes.test.tsx index de51bd1f43..abcde3b6d6 100644 --- a/packages/runtime/tests/routes.test.tsx +++ b/packages/runtime/tests/routes.test.tsx @@ -28,9 +28,9 @@ describe('routes', () => { const homeItem = { default: () => <>, pageConfig: () => ({ title: 'home' }), - dataLoader: [async () => ({ type: 'getData' })], - serverDataLoader: [async () => ({ type: 'getServerData' })], - staticDataLoader: [async () => ({ type: 'getStaticData' })], + dataLoader: { loader: async () => ({ type: 'getData' }) }, + serverDataLoader: { loader: async () => ({ type: 'getServerData' }) }, + staticDataLoader: { loader: async () => ({ type: 'getStaticData' }) }, }; const aboutItem = { default: () => <>, @@ -39,7 +39,7 @@ describe('routes', () => { const InfoItem = { default: () => <>, pageConfig: () => ({ title: 'home' }), - dataLoader: [async () => ({ type: 'getAsyncData' }), { defer: true }], + dataLoader: { loader: async () => ({ type: 'getAsyncData' }), options: { defer: true } }, }; const homeLazyItem = { diff --git a/packages/runtime/tests/runClientApp.test.tsx b/packages/runtime/tests/runClientApp.test.tsx index 710bb69de5..63c889813e 100644 --- a/packages/runtime/tests/runClientApp.test.tsx +++ b/packages/runtime/tests/runClientApp.test.tsx @@ -92,7 +92,9 @@ describe('run client app', () => { ); }, pageConfig: () => ({ title: 'home' }), - dataLoader: async () => ({ data: 'test' }), + dataLoader: { + loader: async () => ({ data: 'test' }), + }, }; const basicRoutes = [ { @@ -110,9 +112,11 @@ describe('run client app', () => { it('run with static runtime', async () => { await runClientApp({ app: { - dataLoader: [async () => { - return { msg: staticMsg }; - }], + dataLoader: { + loader: async () => { + return { msg: staticMsg }; + }, + }, }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, @@ -261,10 +265,12 @@ describe('run client app', () => { let executed = false; await runClientApp({ app: { - dataLoader: [async () => { - executed = true; - return { msg: '-getAppData' }; - }], + dataLoader: { + loader: async () => { + executed = true; + return { msg: '-getAppData' }; + }, + }, }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, @@ -290,10 +296,12 @@ describe('run client app', () => { await runClientApp({ app: { - dataLoader: [async () => { - executed = true; - return { msg: 'app' }; - }], + dataLoader: { + loader: async () => { + executed = true; + return { msg: 'app' }; + }, + }, }, // @ts-ignore don't need to pass params in test case. createRoutes: () => basicRoutes, diff --git a/packages/runtime/tests/runServerApp.test.tsx b/packages/runtime/tests/runServerApp.test.tsx index 0810aa95f2..2ef60ee19d 100644 --- a/packages/runtime/tests/runServerApp.test.tsx +++ b/packages/runtime/tests/runServerApp.test.tsx @@ -14,7 +14,9 @@ describe('run server app', () => { const homeItem = { default: () =>
home
, pageConfig: () => ({ title: 'home' }), - dataLoader: [async () => ({ data: 'test' })], + dataLoader: { + loader: async () => ({ data: 'test' }), + }, }; const basicRoutes = [ { From 3b7fcb9e326726d14eaf57b83157e9ea126e2e4b Mon Sep 17 00:00:00 2001 From: "shuilan.cj" Date: Mon, 24 Apr 2023 19:00:21 +0800 Subject: [PATCH 38/38] fix: compat with old useage --- packages/runtime/src/appData.ts | 9 ++++++++- packages/runtime/src/routes.tsx | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/appData.ts b/packages/runtime/src/appData.ts index a610505383..57fb28e2f5 100644 --- a/packages/runtime/src/appData.ts +++ b/packages/runtime/src/appData.ts @@ -18,7 +18,14 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) return null; } - const { loader } = appDataLoaderConfig; + let loader; + + if (typeof appDataLoaderConfig === 'function' || Array.isArray(appDataLoaderConfig)) { + loader = appDataLoaderConfig; + } else { + loader = appDataLoaderConfig.loader; + } + return await callDataLoader(loader, requestContext); } diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 79e7fce153..83db91842a 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -143,7 +143,16 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { }; } - const { loader, options: loaderOptions } = dataLoaderConfig; + let loader; + let loaderOptions; + + // Compat dataLoaderConfig not return by defineDataLoader. + if (typeof dataLoaderConfig === 'function' || Array.isArray(dataLoaderConfig)) { + loader = dataLoaderConfig; + } else { + loader = dataLoaderConfig.loader; + loaderOptions = dataLoaderConfig.options; + } const getData = () => { const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__;