Skip to content

Commit

Permalink
feat: dynamic API (#6831)
Browse files Browse the repository at this point in the history
* chore: save

* feat: basic dynamic

* feat: export from ice/runtime

* test: with-dynamic

* test(with-dynamic): name export

* feat: use useMounted

* chore: cmt

* chore: up lock file

* chore: use universal-env

* fix: ci

* Revert "chore: use universal-env"

This reverts commit 98f5dff.

* chore: optimize logic
  • Loading branch information
HomyeeKing authored Mar 20, 2024
1 parent 8275f13 commit 25c7584
Show file tree
Hide file tree
Showing 22 changed files with 361 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/with-dynamic/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chrome 55
5 changes: 5 additions & 0 deletions examples/with-dynamic/ice.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from '@ice/app';

export default defineConfig(() => ({
ssr: true,
}));
23 changes: 23 additions & 0 deletions examples/with-dynamic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@examples/with-dynamic",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "ice start",
"build": "ice build"
},
"description": "ICE example with dynamic",
"author": "ICE Team",
"license": "MIT",
"dependencies": {
"@ice/app": "workspace:*",
"@ice/runtime": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tslib": "^2.4.0"
},
"devDependencies": {
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6"
}
}
6 changes: 6 additions & 0 deletions examples/with-dynamic/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
app: {
rootId: 'app',
type: 'browser',
},
};
6 changes: 6 additions & 0 deletions examples/with-dynamic/src/components/nonssr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default (props) => {
window.addEventListener('load', () => {
console.log('load');
});
return <div>{props.text}</div>;
};
7 changes: 7 additions & 0 deletions examples/with-dynamic/src/components/normal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return <div>normal text</div>;
};

export function NameExportComp() {
return <div>name exported</div>;
}
22 changes: 22 additions & 0 deletions examples/with-dynamic/src/document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, Title, Links, Main, Scripts } from 'ice';

function Document() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="description" content="ICE 3 Example for plugin request." />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Title />
<Links />
</head>
<body>
<Main />
<Scripts />
</body>
</html>
);
}

export default Document;
10 changes: 10 additions & 0 deletions examples/with-dynamic/src/pages/nonssr/no-ssr-fallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { dynamic } from '@ice/runtime';

const NonSSR = dynamic(() => import('@/components/nonssr'), {
ssr: false,
fallback: () => <div>fallback</div>,
});

export default () => {
return <NonSSR text={'hello world'} />;
};
9 changes: 9 additions & 0 deletions examples/with-dynamic/src/pages/nonssr/no-ssr-no-fallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { dynamic } from '@ice/runtime';

const NonSSR = dynamic(() => import('@/components/nonssr'), {
ssr: false,
});

export default () => {
return <NonSSR text={'hello world'} />;
};
7 changes: 7 additions & 0 deletions examples/with-dynamic/src/pages/nonssr/ssr-no-fallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { dynamic } from '@ice/runtime';

const NonSSR = dynamic(() => import('@/components/nonssr'));

export default () => {
return <NonSSR text={'hello world'} />;
};
5 changes: 5 additions & 0 deletions examples/with-dynamic/src/pages/nonssr/without-dynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import NonSsr from '@/components/nonssr';

export default () => {
return <NonSsr text={'without dynamic'} />;
};
9 changes: 9 additions & 0 deletions examples/with-dynamic/src/pages/normal/bare-import.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { dynamic } from '@ice/runtime';

const Normal = dynamic(import('../../components/normal'), {
fallback: () => <div>bare import fallback</div>,
});

export default () => {
return <Normal />;
};
9 changes: 9 additions & 0 deletions examples/with-dynamic/src/pages/normal/basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { dynamic } from '@ice/runtime';

const Normal = dynamic(() => import('../../components/normal'), {
fallback: () => <div>normal fallback</div>,
});

export default () => {
return <Normal />;
};
13 changes: 13 additions & 0 deletions examples/with-dynamic/src/pages/normal/name-export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { dynamic } from '@ice/runtime';

const Normal = dynamic(
import('../../components/normal').then((mod) => {
return {
default: mod.NameExportComp,
};
}),
);

export default () => {
return <Normal />;
};
1 change: 1 addition & 0 deletions examples/with-dynamic/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@ice/app/types" />
44 changes: 44 additions & 0 deletions examples/with-dynamic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"compileOnSave": false,
"buildOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"module": "esnext",
"target": "es6",
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": [
"es6",
"dom"
],
"sourceMap": true,
"allowJs": true,
"rootDir": "./",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
"@/*": [
"./src/*"
],
"ice": [
".ice"
]
}
},
"include": [
"src",
".ice", "src/pages/with-dynamic/.tsx",
],
"exclude": [
"build",
"public"
]
}
1 change: 1 addition & 0 deletions packages/ice/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const RUNTIME_EXPORTS = [
'Await',
'usePageLifecycle',
'unstable_useDocumentData',
'dynamic',
],
alias: {
usePublicAppContext: 'useAppContext',
Expand Down
55 changes: 55 additions & 0 deletions packages/runtime/src/dynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ReactNode } from 'react';
import React, { Suspense, lazy } from 'react';
import useMounted from './useMounted.js';

const isServer = import.meta.renderer === 'server';

type ComponentModule<P = {}> = { default: React.ComponentType<P> };

export type LoaderComponent<P = {}> = Promise<React.ComponentType<P> | ComponentModule<P>>;

export type Loader<P = {}> = (() => LoaderComponent<P>) | LoaderComponent<P>;

export interface DynamicOptions {
/** @default true */
ssr?: boolean;
/** the fallback UI to render before the actual is loaded */
fallback?: () => ReactNode;
}

// Normalize loader to return the module as form { default: Component } for `React.lazy`.
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) {
return { default: (mod as ComponentModule<P>)?.default || mod };
}

const DefaultFallback = () => null;

export function dynamic<P = {}>(loader: Loader<P>, option?: DynamicOptions) {
const { ssr = true, fallback = DefaultFallback } = option || {};
let realLoader;
// convert dynamic(import('xxx')) to dynamic(() => import('xxx'))
if (loader instanceof Promise) {
realLoader = () => loader;
} else if (typeof loader === 'function') {
realLoader = loader;
}
if (!realLoader) return DefaultFallback;
const Fallback = fallback;

if (!ssr && isServer) {
return () => <Fallback />;
}

const LazyComp = lazy(() => realLoader().then(convertModule));
return (props) => {
const hasMounted = useMounted();

return ssr || hasMounted ? (
<Suspense fallback={<Fallback />}>
<LazyComp {...props} />
</Suspense>
) : (
<Fallback />
);
};
}
3 changes: 2 additions & 1 deletion packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import useMounted from './useMounted.js';
import usePageLifecycle from './usePageLifecycle.js';
import { withSuspense, useSuspenseData } from './Suspense.js';
import { createRouteLoader, WrapRouteComponent, RouteErrorComponent, Await } from './routes.js';

import { dynamic } from './dynamic.js';
function useAppContext() {
console.warn('import { useAppContext } from \'@ice/runtime\'; is deprecated, please use import { useAppContext } from \'ice\'; instead.');
return useInternalAppContext();
Expand Down Expand Up @@ -117,6 +117,7 @@ export {
callDataLoader,
getRequestContext,
history,
dynamic,

useActive,
KeepAliveOutlet,
Expand Down
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 25c7584

Please sign in to comment.