Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
1import * as path from 'path';
2import { nodeResolve } from '@rollup/plugin-node-resolve';
3import { rollup } from 'rollup';
4
5/** Generates a map of exports from a given graphql package to list of import locations. */
6async function traceImports(moduleName) {
7 const basepath = path.resolve(process.cwd(), 'node_modules/', moduleName);
8 const exportMap = {};
9
10 const resolveFile = (to, relative = '.') => {
11 const dirname = path.join('graphql/', relative, path.dirname(to));
12 const filename = path.basename(to, '.mjs');
13 return path.join(dirname, filename);
14 };
15
16 const bundle = await rollup({
17 // This contains all top-level "sub-modules" of graphql too, since not all exports of
18 // them may be exposed in the main index.mjs file.
19 input: {
20 graphql: path.join(basepath, 'index.mjs'),
21 'graphql/error': path.join(basepath, 'error/index.mjs'),
22 'graphql/execution': path.join(basepath, 'execution/index.mjs'),
23 'graphql/language': path.join(basepath, 'language/index.mjs'),
24 'graphql/subscription': path.join(basepath, 'subscription/index.mjs'),
25 'graphql/type': path.join(basepath, 'type/index.mjs'),
26 'graphql/utilities': path.join(basepath, 'utilities/index.mjs'),
27 'graphql/validation': path.join(basepath, 'validation/index.mjs'),
28 },
29 shimMissingExports: false,
30 preserveEntrySignatures: 'allow-extension',
31 preserveSymlinks: true,
32 external: id => !/^\.{0,2}\//.test(id),
33 plugins: [
34 nodeResolve(),
35 {
36 transform(code, id) {
37 const relative = path.relative(basepath, id);
38 const dirname = path.dirname(relative);
39 const exports = {};
40
41 this.parse(code)
42 .body.filter(x => x.type === 'ExportNamedDeclaration')
43 .forEach(node => {
44 const from = node.source
45 ? resolveFile(node.source.value, dirname)
46 : resolveFile(relative);
47
48 node.specifiers.forEach(specifier => {
49 const { name: local } = specifier.exported;
50 exports[local] = { local, from };
51 });
52
53 if (node.declaration) {
54 (node.declaration.declarations || [node.declaration]).forEach(declaration => {
55 if (declaration && declaration.id) {
56 const { name: local } = declaration.id;
57 exports[local] = { local, from };
58 }
59 });
60 }
61 });
62
63 exportMap[resolveFile(relative)] = exports;
64 return null;
65 },
66 },
67 ],
68 });
69
70 await bundle.generate({});
71 return exportMap;
72}
73
74function isDeclarationEqual(a, b) {
75 return a.local === b.local && a.from === b.from;
76}
77
78function mergeTraces(traces) {
79 const trace = {};
80
81 // Iterate over all known filenames in all traces
82 const ids = new Set(
83 traces.map(trace => Object.keys(trace)).reduce((acc, names) => acc.concat(names), [])
84 );
85 for (const id of ids) {
86 // Each file must exist in all traces
87 if (!traces.every(trace => !!trace[id])) continue;
88
89 const exports = {};
90
91 // Iterate over all known exports in each trace's set of exports for this file
92 const exportNames = new Set(
93 traces.map(trace => Object.keys(trace[id])).reduce((acc, names) => acc.concat(names), [])
94 );
95 for (const name of exportNames) {
96 // Each export must exist in all traces
97 if (traces.every(trace => !!trace[id][name])) {
98 // Collect known declarations and deduplicate
99 exports[name] = traces
100 .map(trace => trace[id][name])
101 .filter((val, index, all) => {
102 const firstIndex = all.findIndex(item => isDeclarationEqual(item, val));
103 return firstIndex === index;
104 });
105 }
106 }
107
108 if (Object.keys(exports).length) trace[id] = exports;
109 }
110
111 // For a given declaration, find the first deepest one that works for the trace
112 // NOTE: This doesn't find the absolute deepest one, since it assumes that each
113 // export only has one functional trace
114 const resolveDeclaration = declaration => {
115 const declarations = trace[declaration.from];
116 if (!declarations || !declarations[declaration.local]) return null;
117 for (const childDeclaration of declarations[declaration.local]) {
118 if (childDeclaration.from === declaration.from) continue;
119 const resolved = resolveDeclaration(childDeclaration);
120 if (resolved && resolved.from !== declaration.from) return resolved;
121 }
122
123 return declaration;
124 };
125
126 // Resolve all known (and consistent) exports to a common, deepest declaration
127 const ROOT_MODULE = 'graphql/index';
128 const exports = {};
129 for (const local in trace[ROOT_MODULE])
130 exports[local] = resolveDeclaration({ local, from: ROOT_MODULE });
131 return exports;
132}
133
134export async function generateImportMap() {
135 return mergeTraces(await Promise.all([traceImports('graphql'), traceImports('graphql15')]));
136}