-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
Copy pathexecuteLoadable.ts
93 lines (85 loc) · 3.26 KB
/
executeLoadable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { logger } from '@storybook/client-logger';
import { Path, ModuleExports } from '@storybook/store';
import { Loadable, RequireContext, LoaderFunction } from './types';
/**
* Executes a Loadable (function that returns exports or require context(s))
* and returns a map of filename => module exports
*
* @param loadable Loadable
* @returns Map<Path, ModuleExports>
*/
export function executeLoadable(loadable: Loadable) {
let reqs = null;
// todo discuss / improve type check
if (Array.isArray(loadable)) {
reqs = loadable;
} else if ((loadable as RequireContext).keys) {
reqs = [loadable as RequireContext];
}
let exportsMap = new Map<Path, ModuleExports>();
if (reqs) {
reqs.forEach((req) => {
req.keys().forEach((filename: string) => {
try {
const fileExports = req(filename) as ModuleExports;
exportsMap.set(
typeof req.resolve === 'function' ? req.resolve(filename) : filename,
fileExports
);
} catch (error) {
const errorString =
error.message && error.stack ? `${error.message}\n ${error.stack}` : error.toString();
logger.error(`Unexpected error while loading ${filename}: ${errorString}`);
}
});
});
} else {
const exported = (loadable as LoaderFunction)();
if (Array.isArray(exported) && exported.every((obj) => obj.default != null)) {
exportsMap = new Map(
exported.map((fileExports, index) => [`exports-map-${index}`, fileExports])
);
} else if (exported) {
logger.warn(
`Loader function passed to 'configure' should return void or an array of module exports that all contain a 'default' export. Received: ${JSON.stringify(
exported
)}`
);
}
}
return exportsMap;
}
/**
* Executes a Loadable (function that returns exports or require context(s))
* and compares it's output to the last time it was run (as stored on a node module)
*
* @param loadable Loadable
* @param m NodeModule
* @returns { added: Map<Path, ModuleExports>, removed: Map<Path, ModuleExports> }
*/
export function executeLoadableForChanges(loadable: Loadable, m?: NodeModule) {
let lastExportsMap: ReturnType<typeof executeLoadable> =
m?.hot?.data?.lastExportsMap || new Map();
if (m?.hot?.dispose) {
m.hot.accept();
m.hot.dispose((data) => {
// eslint-disable-next-line no-param-reassign
data.lastExportsMap = lastExportsMap;
});
}
const exportsMap = executeLoadable(loadable);
const added = new Map<Path, ModuleExports>();
Array.from(exportsMap.entries())
// Ignore files that do not have a default export
.filter(([, fileExports]) => !!fileExports.default)
// Ignore exports that are equal (by reference) to last time, this means the file hasn't changed
.filter(([fileName, fileExports]) => lastExportsMap.get(fileName) !== fileExports)
.forEach(([fileName, fileExports]) => added.set(fileName, fileExports));
const removed = new Map<Path, ModuleExports>();
Array.from(lastExportsMap.keys())
.filter((fileName) => !exportsMap.has(fileName))
.forEach((fileName) => removed.set(fileName, lastExportsMap.get(fileName)));
// Save the value for the dispose() call above
lastExportsMap = exportsMap;
return { added, removed };
}