-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
feat: async data loader #6137
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
0254f5d
chore: refactor unfinished
ClarkXia 35ea322
feat: support create router
ClarkXia 89e9e74
refactor: render mode
ClarkXia 3787d34
fix: code splitting false
ClarkXia ff2f114
feat: add location for icestark
ClarkXia 62a5376
chore: remove console
ClarkXia bb0f5f4
test: examples
ClarkXia c5b1a05
fix: dataloader is undefined
ClarkXia 7b3dd4f
fix: test
ClarkXia bb2ce26
fix: test case
ClarkXia e77a94c
fix: test case
ClarkXia 7530846
fix: types
ClarkXia 6e2e859
fix: test case
ClarkXia d7509af
fix: lock
ClarkXia a6bbef1
feat: async data loader
chenjun1011 7b9f089
Merge branch 'release/next' into refactor/react-router
ClarkXia e1eb822
fix: update lock
ClarkXia 7612aef
Merge branch 'release/next' into refactor/react-router
ClarkXia 2df375f
fix: hydration
ClarkXia 7bf894f
fix: router
ClarkXia 4a9bb5c
fix: router
ClarkXia 3adb0f6
chore: log
ClarkXia afb8428
fix: hmr
ClarkXia 86d8927
fix: test
ClarkXia 40fcdd9
fix: test
ClarkXia 0241fc5
fix: conflict
chenjun1011 3d4f08d
feat: await
chenjun1011 646e1b7
fix: await component
chenjun1011 3db58eb
fix: lint
chenjun1011 d254e5b
refactor: type
chenjun1011 6f43074
fix: type
chenjun1011 ae3a240
fix: app data loader
chenjun1011 4f62e85
fix: conflict
chenjun1011 0344c5e
fix: test
chenjun1011 58bc374
fix: test
chenjun1011 5c9fda7
test: async data
chenjun1011 b9f3ff0
test: async data
chenjun1011 2efed45
docs: async data loader
chenjun1011 7aad1bf
fix: lint
chenjun1011 beedbe8
fix: conflict
chenjun1011 e1e900c
refactor: loader config
chenjun1011 970b94d
fix: test
chenjun1011 3b7fcb9
fix: compat with old useage
chenjun1011 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
52
examples/with-data-loader/src/pages/with-defer-loaders.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 如果不走 defineDataLoader,那就是数组嵌套了? const dataLoader = [[() => {}, {}], {defer: ture}] 感觉还是对象会比较清晰 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
}; | ||
} | ||
|
||
/** | ||
|
@@ -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') { | ||
|
@@ -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, | ||
}); | ||
} | ||
}); | ||
|
@@ -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, | ||
|
@@ -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); | ||
}, | ||
}; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
空文件 RenderWrapper