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