@quilted/rollup 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "access": "public",
7
7
  "@quilted/registry": "https://registry.npmjs.org"
8
8
  },
9
- "version": "0.1.1",
9
+ "version": "0.1.3",
10
10
  "engines": {
11
11
  "node": ">=14.0.0"
12
12
  },
@@ -27,8 +27,17 @@
27
27
  "types": "./build/typescript/index.d.ts",
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
- "@rollup/plugin-replace": "^5.0.0",
31
- "dotenv": "^16.0.0"
30
+ "@rollup/plugin-replace": "^5.0.3",
31
+ "@rollup/plugin-commonjs": "^25.0.5",
32
+ "@rollup/plugin-json": "^6.0.1",
33
+ "@rollup/plugin-node-resolve": "^15.2.3",
34
+ "@quilted/graphql": "^2.0.0",
35
+ "dotenv": "^16.0.0",
36
+ "esbuild": "^0.19.4",
37
+ "graphql": "^16.8.0",
38
+ "rollup-plugin-esbuild": "^6.1.0",
39
+ "rollup-plugin-node-externals": "^6.1.2",
40
+ "rollup-plugin-visualizer": "^5.9.0"
32
41
  },
33
42
  "peerDependencies": {
34
43
  "rollup": "^4.0.0"
package/source/app.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type {Plugin} from 'rollup';
1
+ import * as path from 'path';
2
+
3
+ import type {GetManualChunk, Plugin} from 'rollup';
2
4
 
3
5
  import {
4
6
  MAGIC_MODULE_ENTRY,
@@ -9,6 +11,7 @@ import {
9
11
  import type {MagicModuleEnvOptions} from './env.ts';
10
12
 
11
13
  import {multiline} from './shared/strings.ts';
14
+ import {getNodePlugins, rollupPluginsToArray} from './shared/rollup.ts';
12
15
  import {createMagicModulePlugin} from './shared/magic-module.ts';
13
16
 
14
17
  export interface AppOptions {
@@ -24,7 +27,7 @@ export interface AppOptions {
24
27
  *
25
28
  * @example './App.tsx'
26
29
  */
27
- entry?: string;
30
+ app?: string;
28
31
 
29
32
  /**
30
33
  * Whether to include GraphQL-related code transformations.
@@ -41,20 +44,63 @@ export interface AppOptions {
41
44
  env?: MagicModuleEnvOptions;
42
45
  }
43
46
 
44
- export function quiltApp({env, entry}: AppOptions = {}) {
47
+ export interface AppBrowserOptions extends AppOptions {
48
+ /**
49
+ * Customizes the magic `quilt:module/browser` entry module.
50
+ */
51
+ module?: AppBrowserModuleOptions;
52
+
53
+ /**
54
+ * Customizes the assets created for your application.
55
+ */
56
+ assets?: AppBrowserAssetsOptions;
57
+ }
58
+
59
+ export interface AppBrowserModuleOptions {
60
+ /**
61
+ * Whether the app should use hydration or client-side rendering.
62
+ *
63
+ * @default true
64
+ */
65
+ hydrate?: boolean;
66
+
67
+ /**
68
+ * The CSS selector to render or hydrate the application into.
69
+ *
70
+ * @default '#app'
71
+ */
72
+ selector?: string;
73
+ }
74
+
75
+ export interface AppBrowserAssetsOptions {
76
+ /**
77
+ * Whether to minify assets created by Quilt.
78
+ *
79
+ * @default true
80
+ */
81
+ minify?: boolean;
82
+ }
83
+
84
+ export function quiltAppBrowser({
85
+ app,
86
+ env,
87
+ assets,
88
+ module,
89
+ graphql = true,
90
+ }: AppBrowserOptions = {}) {
45
91
  return {
46
- name: '@quilted/app',
92
+ name: '@quilted/app/browser',
47
93
  async options(originalOptions) {
48
- const newPlugins = [
49
- ...(Array.isArray(originalOptions.plugins)
50
- ? originalOptions.plugins
51
- : originalOptions.plugins
52
- ? [originalOptions.plugins]
53
- : []),
54
- ];
55
-
94
+ const newPlugins = rollupPluginsToArray(originalOptions.plugins);
56
95
  const newOptions = {...originalOptions, plugins: newPlugins};
57
96
 
97
+ const [{visualizer}, nodePlugins] = await Promise.all([
98
+ import('rollup-plugin-visualizer'),
99
+ getNodePlugins(),
100
+ ]);
101
+
102
+ newPlugins.push(...nodePlugins);
103
+
58
104
  if (env) {
59
105
  const {magicModuleEnv, replaceProcessEnv} = await import('./env.ts');
60
106
 
@@ -67,49 +113,53 @@ export function quiltApp({env, entry}: AppOptions = {}) {
67
113
  }
68
114
  }
69
115
 
70
- if (entry) {
71
- newPlugins.push(magicModuleAppComponent({entry}));
116
+ if (app) {
117
+ newPlugins.push(magicModuleAppComponent({entry: app}));
72
118
  }
73
119
 
74
- return newOptions;
75
- },
76
- } satisfies Plugin;
77
- }
120
+ newPlugins.push(magicModuleAppBrowserEntry(module));
78
121
 
79
- export interface AppBrowserOptions {
80
- /**
81
- * Whether the app should use hydration or client-side rendering.
82
- */
83
- hydrate?: boolean;
84
-
85
- /**
86
- * The CSS selector to render or hydrate the application into.
87
- */
88
- selector?: string;
89
- }
122
+ if (graphql) {
123
+ const {graphql} = await import('./graphql.ts');
124
+ newPlugins.push(
125
+ graphql({manifest: path.resolve(`manifests/graphql.json`)}),
126
+ );
127
+ }
90
128
 
91
- export function quiltAppBrowser(options: AppBrowserOptions = {}) {
92
- return {
93
- name: '@quilted/app/browser',
94
- options(originalOptions) {
95
- const newPlugins = [
96
- ...(Array.isArray(originalOptions.plugins)
97
- ? originalOptions.plugins
98
- : originalOptions.plugins
99
- ? [originalOptions.plugins]
100
- : []),
101
- ];
129
+ const minify = assets?.minify ?? true;
102
130
 
103
- const newOptions = {...originalOptions, plugins: newPlugins};
131
+ if (minify) {
132
+ const {minify} = await import('rollup-plugin-esbuild');
133
+ newPlugins.push(minify());
134
+ }
104
135
 
105
- newPlugins.push(magicModuleAppBrowserEntry(options));
136
+ newPlugins.push(
137
+ visualizer({
138
+ template: 'treemap',
139
+ open: false,
140
+ brotliSize: true,
141
+ filename: path.resolve(`reports/bundle-visualizer.html`),
142
+ }),
143
+ );
106
144
 
107
145
  return newOptions;
108
146
  },
147
+ outputOptions(originalOptions) {
148
+ return {
149
+ ...originalOptions,
150
+ // format: isESM ? 'esm' : 'systemjs',
151
+ format: 'esm',
152
+ dir: path.resolve(`build/assets`),
153
+ entryFileNames: `app.[hash].js`,
154
+ assetFileNames: `[name].[hash].[ext]`,
155
+ chunkFileNames: `[name].[hash].js`,
156
+ manualChunks: createManualChunksSorter(),
157
+ };
158
+ },
109
159
  } satisfies Plugin;
110
160
  }
111
161
 
112
- export interface AppServerOptions {
162
+ export interface AppServerOptions extends AppOptions {
113
163
  /**
114
164
  * The entry module for the server of this app. This module must export a
115
165
  * `RequestRouter` object as its default export, which will be wrapped in
@@ -118,29 +168,60 @@ export interface AppServerOptions {
118
168
  entry?: string;
119
169
  }
120
170
 
121
- export function quiltAppServer(options: AppServerOptions = {}) {
171
+ export function quiltAppServer({
172
+ app,
173
+ env,
174
+ graphql,
175
+ entry,
176
+ }: AppServerOptions = {}) {
122
177
  return {
123
178
  name: '@quilted/app/server',
124
179
  async options(originalOptions) {
125
- const newPlugins = [
126
- ...(Array.isArray(originalOptions.plugins)
127
- ? originalOptions.plugins
128
- : originalOptions.plugins
129
- ? [originalOptions.plugins]
130
- : []),
131
- ];
132
-
133
- const [{magicModuleRequestRouterEntry}] = await Promise.all([
180
+ const newPlugins = rollupPluginsToArray(originalOptions.plugins);
181
+ const newOptions = {...originalOptions, plugins: newPlugins};
182
+
183
+ const [{magicModuleRequestRouterEntry}, nodePlugins] = await Promise.all([
134
184
  import('./request-router.ts'),
185
+ getNodePlugins(),
135
186
  ]);
136
187
 
137
- const newOptions = {...originalOptions, plugins: newPlugins};
188
+ newPlugins.push(...nodePlugins);
189
+
190
+ if (env) {
191
+ const {magicModuleEnv, replaceProcessEnv} = await import('./env.ts');
192
+
193
+ if (typeof env === 'boolean') {
194
+ newPlugins.push(replaceProcessEnv({mode: 'production'}));
195
+ newPlugins.push(magicModuleEnv({mode: 'production'}));
196
+ } else {
197
+ newPlugins.push(replaceProcessEnv({mode: env.mode ?? 'production'}));
198
+ newPlugins.push(magicModuleEnv({mode: 'production', ...env}));
199
+ }
200
+ }
201
+
202
+ if (app) {
203
+ newPlugins.push(magicModuleAppComponent({entry: app}));
204
+ }
138
205
 
139
206
  newPlugins.push(magicModuleRequestRouterEntry());
140
- newPlugins.push(magicModuleAppRequestRouter(options));
207
+ newPlugins.push(magicModuleAppRequestRouter({entry}));
208
+
209
+ if (graphql) {
210
+ const {graphql} = await import('./graphql.ts');
211
+ newPlugins.push(graphql({manifest: false}));
212
+ }
141
213
 
142
214
  return newOptions;
143
215
  },
216
+ outputOptions(originalOptions) {
217
+ return {
218
+ ...originalOptions,
219
+ // format,
220
+ format: 'esm',
221
+ dir: path.resolve(`build/server`),
222
+ entryFileNames: 'server.js',
223
+ };
224
+ },
144
225
  } satisfies Plugin;
145
226
  }
146
227
 
@@ -196,7 +277,7 @@ export function magicModuleAppRequestRouter({
196
277
  export function magicModuleAppBrowserEntry({
197
278
  hydrate = true,
198
279
  selector = '#app',
199
- }: AppBrowserOptions = {}) {
280
+ }: AppBrowserModuleOptions = {}) {
200
281
  return createMagicModulePlugin({
201
282
  name: '@quilted/magic-module/app-browser-entry',
202
283
  module: MAGIC_MODULE_ENTRY,
@@ -223,3 +304,103 @@ export function magicModuleAppBrowserEntry({
223
304
  },
224
305
  });
225
306
  }
307
+
308
+ const FRAMEWORK_CHUNK_NAME = 'framework';
309
+ const POLYFILLS_CHUNK_NAME = 'polyfills';
310
+ const VENDOR_CHUNK_NAME = 'vendor';
311
+ const INTERNALS_CHUNK_NAME = 'internals';
312
+ const SHARED_CHUNK_NAME = 'shared';
313
+ const PACKAGES_CHUNK_NAME = 'packages';
314
+ const GLOBAL_CHUNK_NAME = 'global';
315
+ const FRAMEWORK_TEST_STRINGS: (string | RegExp)[] = [
316
+ '/node_modules/preact/',
317
+ '/node_modules/react/',
318
+ '/node_modules/js-cookie/',
319
+ '/node_modules/@quilted/quilt/',
320
+ '/node_modules/@preact/signals/',
321
+ '/node_modules/@preact/signals-core/',
322
+ // TODO I should turn this into an allowlist
323
+ /node_modules[/]@quilted[/](?!react-query|swr)/,
324
+ ];
325
+
326
+ const POLYFILL_TEST_STRINGS = [
327
+ '/node_modules/@quilted/polyfills/',
328
+ '/node_modules/core-js/',
329
+ '/node_modules/whatwg-fetch/',
330
+ '/node_modules/regenerator-runtime/',
331
+ '/node_modules/abort-controller/',
332
+ ];
333
+
334
+ const INTERNALS_TEST_STRINGS = [
335
+ '\x00commonjsHelpers.js',
336
+ '/node_modules/@babel/runtime/',
337
+ ];
338
+
339
+ // When building from source, quilt packages are not in node_modules,
340
+ // so we instead add their repo paths to the list of framework test strings.
341
+ if (process.env.QUILT_FROM_SOURCE) {
342
+ FRAMEWORK_TEST_STRINGS.push('/quilt/packages/');
343
+ }
344
+
345
+ // Inspired by Vite: https://github.com/vitejs/vite/blob/c69f83615292953d40f07b1178d1ed1d72abe695/packages/vite/source/node/build.ts#L567
346
+ function createManualChunksSorter(): GetManualChunk {
347
+ // TODO: make this more configurable, and make it so that we bundle more intelligently
348
+ // for split entries
349
+ const packagesPath = path.resolve('packages') + path.sep;
350
+ const globalPath = path.resolve('global') + path.sep;
351
+ const sharedPath = path.resolve('shared') + path.sep;
352
+
353
+ return (id, {getModuleInfo}) => {
354
+ if (INTERNALS_TEST_STRINGS.some((test) => id.includes(test))) {
355
+ return INTERNALS_CHUNK_NAME;
356
+ }
357
+
358
+ if (
359
+ FRAMEWORK_TEST_STRINGS.some((test) =>
360
+ typeof test === 'string' ? id.includes(test) : test.test(id),
361
+ )
362
+ ) {
363
+ return FRAMEWORK_CHUNK_NAME;
364
+ }
365
+
366
+ if (POLYFILL_TEST_STRINGS.some((test) => id.includes(test))) {
367
+ return POLYFILLS_CHUNK_NAME;
368
+ }
369
+
370
+ let bundleBaseName: string | undefined;
371
+ let relativeId: string | undefined;
372
+
373
+ if (id.includes('/node_modules/')) {
374
+ const moduleInfo = getModuleInfo(id);
375
+
376
+ // If the only dependency is another vendor, let Rollup handle the naming
377
+ if (moduleInfo == null) return;
378
+ if (
379
+ moduleInfo.importers.length > 0 &&
380
+ moduleInfo.importers.every((importer) =>
381
+ importer.includes('/node_modules/'),
382
+ )
383
+ ) {
384
+ return;
385
+ }
386
+
387
+ bundleBaseName = VENDOR_CHUNK_NAME;
388
+ relativeId = id.replace(/^.*[/]node_modules[/]/, '');
389
+ } else if (id.startsWith(packagesPath)) {
390
+ bundleBaseName = PACKAGES_CHUNK_NAME;
391
+ relativeId = id.replace(packagesPath, '');
392
+ } else if (id.startsWith(globalPath)) {
393
+ bundleBaseName = GLOBAL_CHUNK_NAME;
394
+ relativeId = id.replace(globalPath, '');
395
+ } else if (id.startsWith(sharedPath)) {
396
+ bundleBaseName = SHARED_CHUNK_NAME;
397
+ relativeId = id.replace(sharedPath, '');
398
+ }
399
+
400
+ if (bundleBaseName == null || relativeId == null) {
401
+ return;
402
+ }
403
+
404
+ return `${bundleBaseName}-${relativeId.split(path.sep)[0]?.split('.')[0]}`;
405
+ };
406
+ }
@@ -0,0 +1,283 @@
1
+ import {createHash} from 'crypto';
2
+
3
+ import {print, parse} from 'graphql';
4
+ import type {
5
+ DocumentNode,
6
+ TypedQueryDocumentNode,
7
+ DefinitionNode,
8
+ SelectionSetNode,
9
+ ExecutableDefinitionNode,
10
+ OperationDefinitionNode,
11
+ SelectionNode,
12
+ Location,
13
+ } from 'graphql';
14
+ import type {GraphQLOperation} from '@quilted/graphql';
15
+
16
+ const IMPORT_REGEX = /^#import\s+['"]([^'"]*)['"];?[\s\n]*/gm;
17
+ const DEFAULT_NAME = 'Operation';
18
+
19
+ export interface EnhancedDocumentNode<
20
+ Data = Record<string, any>,
21
+ Variables = Record<string, any>,
22
+ > extends TypedQueryDocumentNode<Data, Variables> {
23
+ readonly id: string;
24
+ }
25
+
26
+ export function cleanGraphQLDocument<
27
+ Data = Record<string, any>,
28
+ Variables = Record<string, any>,
29
+ >(
30
+ document: DocumentNode | TypedQueryDocumentNode<Data, Variables>,
31
+ {removeUnused = true}: {removeUnused?: boolean | {exclude: Set<string>}} = {},
32
+ ): EnhancedDocumentNode<Data, Variables> {
33
+ if (removeUnused) {
34
+ removeUnusedDefinitions(document, {
35
+ exclude: removeUnused === true ? new Set() : removeUnused.exclude,
36
+ });
37
+ }
38
+
39
+ for (const definition of document.definitions) {
40
+ addTypename(definition);
41
+ }
42
+
43
+ const normalizedSource = minifyGraphQLSource(print(document));
44
+ const normalizedDocument = parse(normalizedSource);
45
+
46
+ for (const definition of normalizedDocument.definitions) {
47
+ stripLoc(definition);
48
+ }
49
+
50
+ // This ID is a hash of the full file contents that are part of the document,
51
+ // including other documents that are injected in, but excluding any unused
52
+ // fragments. This is useful for things like persisted queries.
53
+ const id = createHash('sha256').update(normalizedSource).digest('hex');
54
+
55
+ Reflect.defineProperty(normalizedDocument, 'id', {
56
+ value: id,
57
+ enumerable: true,
58
+ writable: false,
59
+ configurable: false,
60
+ });
61
+
62
+ Reflect.defineProperty(normalizedDocument, 'loc', {
63
+ value: stripDocumentLoc(normalizedDocument.loc),
64
+ enumerable: true,
65
+ writable: false,
66
+ configurable: false,
67
+ });
68
+
69
+ return normalizedDocument as any;
70
+ }
71
+
72
+ export function extractGraphQLImports(rawSource: string) {
73
+ const imports = new Set<string>();
74
+
75
+ const source = rawSource.replace(IMPORT_REGEX, (_, imported) => {
76
+ imports.add(imported);
77
+ return '';
78
+ });
79
+
80
+ return {imports: [...imports], source};
81
+ }
82
+
83
+ export function toGraphQLOperation<Data = unknown, Variables = unknown>(
84
+ documentOrSource: EnhancedDocumentNode<Data, Variables> | string,
85
+ ): GraphQLOperation<Data, Variables> {
86
+ const document =
87
+ typeof documentOrSource === 'string'
88
+ ? cleanGraphQLDocument(parse(documentOrSource))
89
+ : documentOrSource;
90
+
91
+ return {
92
+ id: document.id,
93
+ name: operationNameForDocument(document),
94
+ source: document.loc!.source.body,
95
+ };
96
+ }
97
+
98
+ function operationNameForDocument(document: DocumentNode) {
99
+ return document.definitions.find(
100
+ (definition): definition is OperationDefinitionNode =>
101
+ definition.kind === 'OperationDefinition',
102
+ )?.name?.value;
103
+ }
104
+
105
+ function removeUnusedDefinitions(
106
+ document: DocumentNode,
107
+ {exclude}: {exclude: Set<string>},
108
+ ) {
109
+ const usedDefinitions = new Set<DefinitionNode>();
110
+ const dependencies = definitionDependencies(document.definitions);
111
+
112
+ const markAsUsed = (definition: DefinitionNode) => {
113
+ if (usedDefinitions.has(definition)) {
114
+ return;
115
+ }
116
+
117
+ usedDefinitions.add(definition);
118
+
119
+ for (const dependency of dependencies.get(definition) || []) {
120
+ markAsUsed(dependency);
121
+ }
122
+ };
123
+
124
+ for (const definition of document.definitions) {
125
+ if (definition.kind === 'FragmentDefinition') {
126
+ if (exclude.has(definition.name.value)) {
127
+ markAsUsed(definition);
128
+ }
129
+ } else {
130
+ markAsUsed(definition);
131
+ }
132
+ }
133
+
134
+ (document as any).definitions = [...usedDefinitions];
135
+ }
136
+
137
+ function definitionDependencies(definitions: readonly DefinitionNode[]) {
138
+ const executableDefinitions: ExecutableDefinitionNode[] = definitions.filter(
139
+ (definition) =>
140
+ definition.kind === 'OperationDefinition' ||
141
+ definition.kind === 'FragmentDefinition',
142
+ ) as any[];
143
+
144
+ const definitionsByName = new Map(
145
+ executableDefinitions.map<[string, DefinitionNode]>((definition) => [
146
+ definition.name ? definition.name.value : DEFAULT_NAME,
147
+ definition,
148
+ ]),
149
+ );
150
+
151
+ return new Map(
152
+ executableDefinitions.map<[DefinitionNode, DefinitionNode[]]>(
153
+ (executableNode) => [
154
+ executableNode,
155
+ [...collectUsedFragmentSpreads(executableNode, new Set())].map(
156
+ (usedFragment) => {
157
+ const definition = definitionsByName.get(usedFragment);
158
+
159
+ if (definition == null) {
160
+ throw new Error(
161
+ `You attempted to use the fragment '${usedFragment}' (in '${
162
+ executableNode.name ? executableNode.name.value : DEFAULT_NAME
163
+ }'), but it does not exist. Maybe you forgot to import it from another document?`,
164
+ );
165
+ }
166
+
167
+ return definition;
168
+ },
169
+ ),
170
+ ],
171
+ ),
172
+ );
173
+ }
174
+
175
+ const TYPENAME_FIELD = {
176
+ kind: 'Field',
177
+ alias: null,
178
+ name: {kind: 'Name', value: '__typename'},
179
+ };
180
+
181
+ function addTypename(definition: DefinitionNode) {
182
+ for (const {selections} of selectionSetsForDefinition(definition)) {
183
+ const hasTypename = selections.some(
184
+ (selection) =>
185
+ selection.kind === 'Field' && selection.name.value === '__typename',
186
+ );
187
+
188
+ if (!hasTypename) {
189
+ (selections as any[]).push(TYPENAME_FIELD);
190
+ }
191
+ }
192
+ }
193
+
194
+ function collectUsedFragmentSpreads(
195
+ definition: DefinitionNode,
196
+ usedSpreads: Set<string>,
197
+ ) {
198
+ for (const selection of selectionsForDefinition(definition)) {
199
+ if (selection.kind === 'FragmentSpread') {
200
+ usedSpreads.add(selection.name.value);
201
+ }
202
+ }
203
+
204
+ return usedSpreads;
205
+ }
206
+
207
+ function selectionsForDefinition(
208
+ definition: DefinitionNode,
209
+ ): IterableIterator<SelectionNode> {
210
+ if (!('selectionSet' in definition) || definition.selectionSet == null) {
211
+ return [][Symbol.iterator]();
212
+ }
213
+
214
+ return selectionsForSelectionSet(definition.selectionSet);
215
+ }
216
+
217
+ function* selectionSetsForDefinition(
218
+ definition: DefinitionNode,
219
+ ): IterableIterator<SelectionSetNode> {
220
+ if (!('selectionSet' in definition) || definition.selectionSet == null) {
221
+ return [][Symbol.iterator]();
222
+ }
223
+
224
+ if (definition.kind !== 'OperationDefinition') {
225
+ yield definition.selectionSet;
226
+ }
227
+
228
+ for (const nestedSelection of selectionsForDefinition(definition)) {
229
+ if (
230
+ 'selectionSet' in nestedSelection &&
231
+ nestedSelection.selectionSet != null
232
+ ) {
233
+ yield nestedSelection.selectionSet;
234
+ }
235
+ }
236
+ }
237
+
238
+ function* selectionsForSelectionSet({
239
+ selections,
240
+ }: SelectionSetNode): IterableIterator<SelectionNode> {
241
+ for (const selection of selections) {
242
+ yield selection;
243
+
244
+ if ('selectionSet' in selection && selection.selectionSet != null) {
245
+ yield* selectionsForSelectionSet(selection.selectionSet);
246
+ }
247
+ }
248
+ }
249
+
250
+ type Writable<T> = {-readonly [K in keyof T]: T[K]};
251
+
252
+ function stripDocumentLoc(loc?: Location) {
253
+ const normalizedLoc: Partial<Writable<Location>> = {...loc};
254
+ delete normalizedLoc.endToken;
255
+ delete normalizedLoc.startToken;
256
+ return normalizedLoc;
257
+ }
258
+
259
+ function stripLoc(value: unknown) {
260
+ if (Array.isArray(value)) {
261
+ value.forEach(stripLoc);
262
+ } else if (typeof value === 'object') {
263
+ if (value == null) {
264
+ return;
265
+ }
266
+
267
+ if ('loc' in value) {
268
+ delete (value as {loc: unknown}).loc;
269
+ }
270
+
271
+ for (const key of Object.keys(value)) {
272
+ stripLoc((value as any)[key]);
273
+ }
274
+ }
275
+ }
276
+
277
+ export function minifyGraphQLSource(source: string) {
278
+ return source
279
+ .replace(/#.*/g, '')
280
+ .replace(/\\n/g, ' ')
281
+ .replace(/\s\s+/g, ' ')
282
+ .replace(/\s*({|}|\(|\)|\.|:|,)\s*/g, '$1');
283
+ }