Mirror: Modular GraphQL.js import paths without the hassle.
1const fs = require('fs');
2const path = require('path');
3
4const { rollup } = require('rollup');
5
6const cwd = process.cwd();
7const basepath = path.resolve(cwd, 'node_modules/graphql/');
8
9function generateImportMapPlugin(opts = {}) {
10 const maxDepth = opts.maxDepth || 2;
11 const filename = opts.filename || 'import-map.json';
12 const map = new Map();
13
14 const resolveFile = (from, to) => {
15 return path.join(from, to);
16 };
17
18 const resolveFromMap = (id, name, depth = 0) => {
19 const exports = map.get(id);
20 if (!exports || !exports.has(name)) return null;
21
22 const declaration = exports.get(name);
23 if (depth >= maxDepth || declaration.from === id) {
24 return declaration;
25 }
26
27 return resolveFromMap(declaration.from, declaration.local, depth + 1) || declaration;
28 };
29
30 return {
31 name: 'generate-import-map',
32 transform(code, id) {
33 const relative = path.relative(basepath, id);
34 const dirname = path.dirname(relative);
35 const exports = new Map();
36
37 this.parse(code)
38 .body.filter((x) => x.type === 'ExportNamedDeclaration')
39 .forEach((node) => {
40 const source = node.source ? resolveFile(dirname, node.source.value) : relative;
41
42 node.specifiers.forEach((specifier) => {
43 exports.set(specifier.exported.name, {
44 local: specifier.local.name,
45 from: source,
46 });
47 });
48
49 if (node.declaration) {
50 (node.declaration.declarations || [node.declaration]).forEach((declaration) => {
51 if (declaration && declaration.id) {
52 const { name } = declaration.id;
53 exports.set(declaration.id.name, {
54 local: name,
55 from: source,
56 });
57 }
58 });
59 }
60 });
61
62 map.set(relative, exports);
63 return null;
64 },
65 renderChunk(_code, chunk) {
66 const id = chunk.facadeModuleId;
67 const relative = path.relative(basepath, id);
68
69 if (chunk.isEntry) {
70 const importMap = chunk.exports.reduce((acc, name) => {
71 const declaration = resolveFromMap(relative, name);
72 if (declaration) {
73 const dirname = path.join('graphql/', path.dirname(declaration.from));
74 const filename = path.basename(declaration.from, '.mjs');
75
76 acc[name] = {
77 local: declaration.local,
78 from: path.join(dirname, filename),
79 };
80 }
81
82 return acc;
83 }, {});
84
85 this.emitFile({
86 type: 'asset',
87 filename,
88 name: filename,
89 source: JSON.stringify(importMap, null, 2),
90 });
91 }
92 },
93 };
94}
95
96(async () => {
97 const bundle = await rollup({
98 input: path.join(basepath, 'index.mjs'),
99 external: (id) => !/^\.{0,2}\//.test(id),
100 preserveModules: true,
101 plugins: [
102 require('@rollup/plugin-node-resolve').nodeResolve(),
103 generateImportMapPlugin({
104 filename: 'import-map.json',
105 }),
106 ],
107 });
108
109 const { output } = await bundle.generate({});
110
111 fs.writeFileSync(
112 path.resolve(cwd, 'import-map.json'),
113 output.find((asset) => asset.type === 'asset').source
114 );
115})().catch((err) => {
116 console.error(`${err.name}: ${err.message}`);
117 process.exit(1);
118});