Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
1import * as path from 'path';
2import { promises as fs } from 'fs';
3
4import resolve from '@rollup/plugin-node-resolve';
5import replace from '@rollup/plugin-replace';
6import terser from '@rollup/plugin-terser';
7import { babel } from '@rollup/plugin-babel';
8
9import babelModularGraphQL from 'babel-plugin-modular-graphql';
10import babelTransformComputedProps from '../babel/transformComputedProps.mjs';
11import babelTransformDevAssert from '../babel/transformDevAssert.mjs';
12import babelTransformObjectFreeze from '../babel/transformObjectFreeze.mjs';
13
14import { packageMetadata, version } from './packageMetadata.mjs';
15import { generateImportMap } from './importMap.mjs';
16
17const cwd = process.cwd();
18const graphqlModule = path.posix.join(cwd, 'node_modules/graphql/');
19const virtualModule = path.posix.join(cwd, 'virtual/');
20const aliasModule = path.posix.join(cwd, 'alias/');
21
22const EXTERNAL = 'graphql';
23const externalModules = ['dns', 'fs', 'path', 'url'];
24const externalPredicate = new RegExp(`^(${externalModules.join('|')})($|/)`);
25
26function manualChunks(id, utils) {
27 let chunk;
28 if (id.startsWith(graphqlModule)) {
29 chunk = id.slice(graphqlModule.length);
30 } else if (id.startsWith(virtualModule)) {
31 chunk = id.slice(virtualModule.length);
32 } else if (id.startsWith(aliasModule)) {
33 chunk = id.slice(aliasModule.length);
34 }
35
36 if (chunk) {
37 return chunk.replace(/\.m?js$/, '');
38 }
39
40 const { importers } = utils.getModuleInfo(id);
41 return importers.length === 1 ? manualChunks(importers[0], utils) : 'shared';
42}
43
44function buildPlugin() {
45 const exports = {};
46 return {
47 async buildStart(options) {
48 const importMap = await generateImportMap();
49
50 for (const key in importMap) {
51 const { from, local } = importMap[key];
52 if (/\/jsutils\//g.test(from)) continue;
53
54 const name = from.replace(/^graphql\//, '');
55 exports[name] = (exports[name] || '') + `export { ${key} } from '${EXTERNAL}'\n`;
56
57 const parts = name.split('/');
58 for (let i = parts.length - 1; i > 0; i--) {
59 const name = `${parts.slice(0, i).join('/')}/index`;
60 const from = `./${parts.slice(i).join('/')}`;
61 if (from !== './index')
62 exports[name] = (exports[name] || '') + `export { ${local} } from '${from}'\n`;
63 }
64
65 const index = `export { ${local} } from './${name}'\n`;
66 exports.index = (exports.index || '') + index;
67 }
68
69 if (typeof options.input !== 'object') options.input = {};
70
71 for (const key in exports) {
72 options.input[key] = path.posix.join('./virtual', key);
73 }
74 },
75
76 async load(id) {
77 if (!id.startsWith(virtualModule)) return null;
78 const entry = path.posix.relative(virtualModule, id).replace(/\.m?js$/, '');
79 if (entry === 'version') return version;
80 return exports[entry] || null;
81 },
82
83 async resolveId(source, importer) {
84 if (!source.startsWith('.') && !source.startsWith('virtual/')) return null;
85
86 const target = path.posix.join(importer ? path.posix.dirname(importer) : cwd, source);
87
88 const virtualEntry = path.posix.relative(virtualModule, target);
89 if (!virtualEntry.startsWith('../')) {
90 const aliasSource = path.posix.join(aliasModule, virtualEntry);
91 const alias = await this.resolve(aliasSource, undefined, {
92 skipSelf: true,
93 });
94 return alias || target;
95 }
96
97 const graphqlEntry = path.posix.relative(graphqlModule, target);
98 if (!graphqlEntry.startsWith('../')) {
99 const aliasSource = path.posix.join(aliasModule, graphqlEntry);
100 const alias = await this.resolve(aliasSource, undefined, {
101 skipSelf: true,
102 });
103 return alias || target;
104 }
105
106 return null;
107 },
108
109 async renderStart() {
110 this.emitFile({
111 type: 'asset',
112 fileName: 'package.json',
113 source: packageMetadata,
114 });
115
116 this.emitFile({
117 type: 'asset',
118 fileName: 'README.md',
119 source: await fs.readFile('README.md'),
120 });
121
122 this.emitFile({
123 type: 'asset',
124 fileName: 'LICENSE',
125 source: await fs.readFile('./LICENSE.md'),
126 });
127 },
128
129 async renderChunk(_code, { fileName }) {
130 const name = fileName.replace(/\.m?js$/, '');
131
132 const getContents = async extension => {
133 try {
134 const name = fileName.replace(/\.m?js$/, '');
135 const contents = await fs.readFile(path.join(graphqlModule, name + extension));
136 return contents;
137 } catch (_error) {
138 return null;
139 }
140 };
141
142 const dts = await getContents('.d.ts');
143 const flow = await getContents('.js.flow');
144
145 if (dts) {
146 this.emitFile({
147 type: 'asset',
148 fileName: name + '.d.ts',
149 source: dts,
150 });
151 }
152
153 if (flow) {
154 this.emitFile({
155 type: 'asset',
156 fileName: name + '.js.flow',
157 source: flow,
158 });
159 }
160
161 return null;
162 },
163 };
164}
165
166export default {
167 input: {},
168 external(id) {
169 return externalPredicate.test(id);
170 },
171 treeshake: {
172 unknownGlobalSideEffects: false,
173 tryCatchDeoptimization: false,
174 moduleSideEffects: false,
175 },
176 plugins: [
177 buildPlugin(),
178
179 resolve({
180 extensions: ['.mjs', '.js'],
181 mainFields: ['module', 'browser', 'main'],
182 preferBuiltins: false,
183 browser: true,
184 }),
185
186 babel({
187 babelrc: false,
188 babelHelpers: 'bundled',
189 presets: [],
190 plugins: [
191 babelTransformDevAssert,
192 babelTransformObjectFreeze,
193 babelTransformComputedProps,
194 babelModularGraphQL,
195 ],
196 }),
197
198 replace({
199 preventAssignment: true,
200 values: {
201 'process.env.NODE_ENV': JSON.stringify('production'),
202 },
203 }),
204
205 terser({
206 warnings: true,
207 ecma: 2016,
208 keep_fnames: true,
209 compress: {
210 module: true,
211 pure_getters: true,
212 toplevel: true,
213 booleans_as_integers: false,
214 keep_fnames: true,
215 keep_fargs: true,
216 if_return: false,
217 ie8: false,
218 sequences: false,
219 loops: false,
220 conditionals: false,
221 join_vars: false,
222 },
223 mangle: false,
224 output: {
225 beautify: true,
226 braces: true,
227 indent_level: 2,
228 },
229 }),
230 ],
231
232 treeshake: 'smallest',
233 shimMissingExports: false,
234 preserveEntrySignatures: 'allow-extension',
235 preserveSymlinks: true,
236
237 output: [
238 {
239 chunkFileNames: '[name].js',
240 entryFileNames: '[name].js',
241 dir: './dist',
242 exports: 'named',
243 format: 'cjs',
244 minifyInternalExports: false,
245 hoistTransitiveImports: false,
246 manualChunks,
247 },
248 {
249 chunkFileNames: '[name].mjs',
250 entryFileNames: '[name].mjs',
251 dir: './dist',
252 exports: 'named',
253 format: 'esm',
254 minifyInternalExports: false,
255 hoistTransitiveImports: false,
256 manualChunks,
257 },
258 ],
259};