Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: async data loader #6137

Merged
merged 43 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0254f5d
chore: refactor unfinished
ClarkXia Mar 24, 2023
35ea322
feat: support create router
ClarkXia Mar 27, 2023
89e9e74
refactor: render mode
ClarkXia Mar 29, 2023
3787d34
fix: code splitting false
ClarkXia Mar 29, 2023
ff2f114
feat: add location for icestark
ClarkXia Mar 30, 2023
62a5376
chore: remove console
ClarkXia Mar 30, 2023
bb0f5f4
test: examples
ClarkXia Mar 30, 2023
c5b1a05
fix: dataloader is undefined
ClarkXia Mar 30, 2023
7b3dd4f
fix: test
ClarkXia Mar 31, 2023
bb2ce26
fix: test case
ClarkXia Mar 31, 2023
e77a94c
fix: test case
ClarkXia Mar 31, 2023
7530846
fix: types
ClarkXia Mar 31, 2023
6e2e859
fix: test case
ClarkXia Apr 6, 2023
d7509af
fix: lock
ClarkXia Apr 6, 2023
a6bbef1
feat: async data loader
chenjun1011 Apr 6, 2023
7b9f089
Merge branch 'release/next' into refactor/react-router
ClarkXia Apr 6, 2023
e1eb822
fix: update lock
ClarkXia Apr 6, 2023
7612aef
Merge branch 'release/next' into refactor/react-router
ClarkXia Apr 6, 2023
2df375f
fix: hydration
ClarkXia Apr 6, 2023
7bf894f
fix: router
ClarkXia Apr 6, 2023
4a9bb5c
fix: router
ClarkXia Apr 6, 2023
3adb0f6
chore: log
ClarkXia Apr 6, 2023
afb8428
fix: hmr
ClarkXia Apr 7, 2023
86d8927
fix: test
ClarkXia Apr 7, 2023
40fcdd9
fix: test
ClarkXia Apr 7, 2023
0241fc5
fix: conflict
chenjun1011 Apr 10, 2023
3d4f08d
feat: await
chenjun1011 Apr 10, 2023
646e1b7
fix: await component
chenjun1011 Apr 10, 2023
3db58eb
fix: lint
chenjun1011 Apr 11, 2023
d254e5b
refactor: type
chenjun1011 Apr 11, 2023
6f43074
fix: type
chenjun1011 Apr 11, 2023
ae3a240
fix: app data loader
chenjun1011 Apr 11, 2023
4f62e85
fix: conflict
chenjun1011 Apr 17, 2023
0344c5e
fix: test
chenjun1011 Apr 17, 2023
58bc374
fix: test
chenjun1011 Apr 17, 2023
5c9fda7
test: async data
chenjun1011 Apr 18, 2023
b9f3ff0
test: async data
chenjun1011 Apr 18, 2023
2efed45
docs: async data loader
chenjun1011 Apr 18, 2023
7aad1bf
fix: lint
chenjun1011 Apr 18, 2023
beedbe8
fix: conflict
chenjun1011 Apr 22, 2023
e1e900c
refactor: loader config
chenjun1011 Apr 22, 2023
970b94d
fix: test
chenjun1011 Apr 24, 2023
3b7fcb9
fix: compat with old useage
chenjun1011 Apr 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions examples/with-data-loader/src/pages/with-defer-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useData, defineDataLoader, Await } from 'ice';
import styles from './index.module.css';

export default function Home() {
const data = useData();

return (
<>
<h2 className={styles.title}>With dataLoader</h2>
<Await resolve={data} fallback={<p>Loading item info...</p>} errorElement={<p>Error loading!</p>}>
{(itemInfo) => {
return <p>Item id is <span id="itemId">{itemInfo.id}</span></p>;
}}
</Await>
</>
);
}

export function pageConfig() {
return {
title: 'Home',
};
}

export const dataLoader = defineDataLoader(async () => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1233,
});
}, 100);
});
return await promise;
}, { defer: true });

52 changes: 52 additions & 0 deletions examples/with-data-loader/src/pages/with-defer-loaders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useData, defineDataLoader, Await } from 'ice';
import styles from './index.module.css';

export default function Home() {
const data = useData();

return (
<>
<h2 className={styles.title}>With dataLoader</h2>
<Await resolve={data[0]} fallback={<p>Loading item info...</p>} errorElement={<p>Error loading!</p>}>
{(itemInfo) => {
return <p>Item id is {itemInfo.id}</p>;
}}
</Await>
<Await resolve={data[1]} fallback={<p>Loading item info...</p>} errorElement={<p>Error loading!</p>}>
{(itemInfo) => {
return <p>Item price is {itemInfo.price}</p>;
}}
</Await>
</>
);
}

export function pageConfig() {
return {
title: 'Home',
};
}

export const dataLoader = defineDataLoader([
async () => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1233,
});
}, 100);
});
return await promise;
},
async () => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve({
price: 9.99,
});
}, 2000);
});
return await promise;
},
], { defer: true });

45 changes: 45 additions & 0 deletions examples/with-data-loader/src/pages/with-ssr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useData, defineDataLoader, defineServerDataLoader, Await } from 'ice';
import styles from './index.module.css';

export default function Home() {
const data = useData();

return (
<>
<h2 className={styles.title}>With dataLoader</h2>
<Await resolve={data} fallback={<p>Loading item info...</p>} errorElement={<p>Error loading!</p>}>
{(itemInfo) => {
return <p>Item id is {itemInfo.id}</p>;
}}
</Await>
</>
);
}

export function pageConfig() {
return {
title: 'Home',
};
}

export const dataLoader = defineDataLoader(async () => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1233,
});
}, 100);
});
return await promise;
}, { defer: true });

export const serverDataLoader = defineServerDataLoader(async () => {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1233,
});
}, 100);
});
return await promise;
});
1 change: 1 addition & 0 deletions packages/ice/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const RUNTIME_EXPORTS = [
'ClientOnly',
'withSuspense',
'useSuspenseData',
'Await',
'defineDataLoader',
'defineServerDataLoader',
'defineStaticDataLoader',
Expand Down
16 changes: 11 additions & 5 deletions packages/runtime/src/appData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext)
return await globalLoader.getData('__app');
}

if (appExport?.dataLoader) {
return await appExport.dataLoader(requestContext);
const appDataLoaderConfig = appExport?.dataLoader;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

空文件 RenderWrapper


if (!appDataLoaderConfig) {
return null;
}

const loader = appExport?.dataLoader;
let loader;

if (!loader) return null;
if (typeof appDataLoaderConfig === 'function' || Array.isArray(appDataLoaderConfig)) {
loader = appDataLoaderConfig;
} else {
loader = appDataLoaderConfig.loader;
}

await callDataLoader(loader, requestContext);
return await callDataLoader(loader, requestContext);
}

export {
Expand Down
86 changes: 39 additions & 47 deletions packages/runtime/src/dataLoader.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { RequestContext, RenderMode, DataLoaderConfig, 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;
}

interface CachedResult {
value: any;
status: string;
}

interface LoaderOptions {
interface Options {
fetcher: Function;
runtimeModules: RuntimeModules['statics'];
appExport: AppExport;
Expand All @@ -20,16 +22,24 @@ export interface LoadRoutesDataOptions {
renderMode: RenderMode;
}

export function defineDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig {
return dataLoaderConfig;
export function defineDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果不走 defineDataLoader,那就是数组嵌套了?

const dataLoader = [[() => {}, {}], {defer: ture}]

感觉还是对象会比较清晰

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已调整

return {
loader: dataLoader,
options,
};
}

export function defineServerDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig {
return dataLoaderConfig;
export function defineServerDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
return {
loader: dataLoader,
options,
};
}

export function defineStaticDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig {
return dataLoaderConfig;
export function defineStaticDataLoader(dataLoader: Loader): DataLoaderConfig {
return {
loader: dataLoader,
};
}

/**
Expand Down Expand Up @@ -124,12 +134,13 @@ 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);
});
return Promise.all(loaders);

return loaders;
}

if (typeof dataLoader === 'object') {
Expand All @@ -156,23 +167,22 @@ function loadInitialDataInClient(loaders: Loaders) {
if (dataFromSSR) {
cache.set(renderMode === 'SSG' ? `${id}_ssg` : id, {
value: dataFromSSR,
status: 'RESOLVED',
});

if (renderMode === 'SSR') {
return;
}
}

const dataLoader = loaders[id];
const dataLoaderConfig = loaders[id];

if (dataLoader) {
if (dataLoaderConfig) {
const requestContext = getRequestContext(window.location);
const loader = callDataLoader(dataLoader, requestContext);
const { loader } = dataLoaderConfig;
const promise = callDataLoader(loader, requestContext);

cache.set(id, {
value: loader,
status: 'LOADING',
value: promise,
});
}
});
Expand All @@ -183,7 +193,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: Options) {
const {
fetcher,
runtimeModules,
Expand All @@ -208,57 +218,39 @@ async function init(dataloaderConfig: Loaders, options: LoaderOptions) {
}

try {
loadInitialDataInClient(dataloaderConfig);
loadInitialDataInClient(loaders);
} catch (error) {
console.error('Load initial data error: ', error);
}

(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];
const dataLoaderConfig = loaders[id];

// No data loader.
if (!dataLoader) {
if (!dataLoaderConfig) {
return null;
}

// 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);
const { loader } = dataLoaderConfig;
return callDataLoader(loader, requestContext);
},
};
}
Expand Down
8 changes: 5 additions & 3 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
RouteWrapper,
RenderMode,
DistType,
DataLoaderConfig,
Loader,
RouteWrapperConfig,
} from './types.js';
import Runtime from './runtime.js';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -92,6 +92,8 @@ export {
withSuspense,
useSuspenseData,

Await,

createRouteLoader,
WrapRouteComponent,
RouteErrorComponent,
Expand All @@ -109,7 +111,7 @@ export type {
RouteWrapper,
RenderMode,
DistType,
DataLoaderConfig,
Loader,
RunClientAppOptions,
MetaType,
TitleType,
Expand Down
Loading