-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18432 from storybookjs/tom/sb-347-sequence-import…
…ing-stories-in-parallel
- Loading branch information
Showing
12 changed files
with
181 additions
and
17 deletions.
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
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
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,121 @@ | ||
import { importPipeline } from './importPipeline'; | ||
|
||
const createGate = (): [Promise<any | undefined>, (_?: any) => void] => { | ||
let openGate = (_?: any) => {}; | ||
const gate = new Promise<any | undefined>((resolve) => { | ||
openGate = resolve; | ||
}); | ||
return [gate, openGate]; | ||
}; | ||
|
||
it('passes through to passed importFn on serial calls', async () => { | ||
const pipeline = importPipeline(); | ||
const importFn = jest.fn(); | ||
|
||
importFn.mockResolvedValueOnce('r1'); | ||
expect(await pipeline(() => importFn('i1'))).toEqual('r1'); | ||
expect(importFn).toHaveBeenCalledTimes(1); | ||
expect(importFn).toHaveBeenCalledWith('i1'); | ||
|
||
importFn.mockResolvedValueOnce('r2'); | ||
expect(await pipeline(() => importFn('i2'))).toEqual('r2'); | ||
expect(importFn).toHaveBeenCalledTimes(2); | ||
expect(importFn).toHaveBeenCalledWith('i2'); | ||
}); | ||
|
||
it('blocks on parallel calls', async () => { | ||
const pipeline = importPipeline(); | ||
const [firstGate, openFirstGate] = createGate(); | ||
const importFn = jest | ||
.fn() | ||
.mockImplementationOnce(() => firstGate) | ||
.mockResolvedValueOnce('r2'); | ||
|
||
const firstPromise = pipeline(() => importFn('i1')); | ||
|
||
// We need to await promise resolution to get the block setup | ||
await new Promise((r) => r(null)); | ||
|
||
const secondPromise = pipeline(() => importFn('i2')); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(1); | ||
expect(importFn).toHaveBeenCalledWith('i1'); | ||
openFirstGate('r1'); | ||
expect(await firstPromise).toEqual('r1'); | ||
|
||
// We need to await promise resolution to get past the block | ||
await new Promise((r) => r(null)); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(2); | ||
expect(importFn).toHaveBeenCalledWith('i2'); | ||
expect(await secondPromise).toEqual('r2'); | ||
}); | ||
|
||
it('dispatches all queued calls on opening', async () => { | ||
const pipeline = importPipeline(); | ||
const [firstGate, openFirstGate] = createGate(); | ||
const importFn = jest | ||
.fn() | ||
.mockImplementationOnce(() => firstGate) | ||
.mockResolvedValueOnce('r2') | ||
.mockResolvedValueOnce('r3'); | ||
|
||
const firstPromise = pipeline(() => importFn('i1')); | ||
|
||
// We need to await promise resolution to get the block setup | ||
await new Promise((r) => r(null)); | ||
const secondPromise = pipeline(() => importFn('i2')); | ||
|
||
// We need to await promise resolution to get the block setup | ||
await new Promise((r) => r(null)); | ||
const thirdPromise = pipeline(() => importFn('i3')); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(1); | ||
expect(importFn).toHaveBeenCalledWith('i1'); | ||
openFirstGate('r1'); | ||
expect(await firstPromise).toEqual('r1'); | ||
|
||
// We need to await promise resolution to get past the block | ||
await new Promise((r) => r(null)); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(3); | ||
expect(importFn).toHaveBeenCalledWith('i2'); | ||
expect(importFn).toHaveBeenCalledWith('i3'); | ||
expect(await secondPromise).toEqual('r2'); | ||
expect(await thirdPromise).toEqual('r3'); | ||
}); | ||
|
||
it('blocks sequentially on parallel calls', async () => { | ||
const pipeline = importPipeline(); | ||
const [firstGate, openFirstGate] = createGate(); | ||
const [secondGate, openSecondGate] = createGate(); | ||
const importFn = jest | ||
.fn() | ||
.mockImplementationOnce(() => firstGate) | ||
.mockImplementationOnce(() => secondGate) | ||
.mockResolvedValueOnce('r3'); | ||
|
||
const firstPromise = pipeline(() => importFn('i1')); | ||
|
||
// We need to await promise resolution to get the block setup | ||
await new Promise((r) => r(null)); | ||
const secondPromise = pipeline(() => importFn('i2')); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(1); | ||
expect(importFn).toHaveBeenCalledWith('i1'); | ||
openFirstGate('r1'); | ||
expect(await firstPromise).toEqual('r1'); | ||
|
||
// We need to await promise resolution to get past the block, and set up the new one | ||
await new Promise((r) => r(null)); | ||
const thirdPromise = pipeline(() => importFn('i3')); | ||
|
||
expect(importFn).toHaveBeenCalledTimes(2); | ||
expect(importFn).toHaveBeenCalledWith('i2'); | ||
openSecondGate('r2'); | ||
expect(await secondPromise).toEqual('r2'); | ||
|
||
// We need to await promise resolution to get past the block | ||
await new Promise((r) => r(null)); | ||
expect(await thirdPromise).toEqual('r3'); | ||
}); |
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,22 @@ | ||
type ModuleExports = Record<string, any>; | ||
|
||
// If an import is in flight when another import arrives, block it until the first completes. | ||
// This is to avoid a situation where webpack kicks off a second compilation whilst the | ||
// first is still completing, cf: https://github.com/webpack/webpack/issues/15541#issuecomment-1143138832 | ||
// Note the way this code works if N futher `import()`s occur while the first is in flight, | ||
// they will all be kicked off in the same tick and not block each other. This is by design, | ||
// Webpack can handle multiple invalidations simutaneously, just not in quick succession. | ||
|
||
export function importPipeline() { | ||
let importGate: Promise<void> = Promise.resolve(); | ||
|
||
return async (importFn: () => Promise<ModuleExports>) => { | ||
await importGate; | ||
|
||
const moduleExportsPromise = importFn(); | ||
importGate = importGate.then(async () => { | ||
await moduleExportsPromise; | ||
}); | ||
return moduleExportsPromise; | ||
}; | ||
} |
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
5 changes: 3 additions & 2 deletions
5
...n/src/utils/__tests__/to-importFn.test.ts → lib/core-webpack/src/to-importFn.test.ts
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
5 changes: 3 additions & 2 deletions
5
...tils/__tests__/to-require-context.test.ts → ...re-webpack/src/to-require-context.test.ts
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
4 changes: 2 additions & 2 deletions
4
...re-common/src/utils/to-require-context.ts → lib/core-webpack/src/to-require-context.ts
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