@quilted/rollup 0.1.2 → 0.1.4
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/CHANGELOG.md +14 -0
- package/build/cjs/app.cjs +166 -38
- package/build/cjs/graphql/transform.cjs +186 -0
- package/build/cjs/graphql.cjs +86 -0
- package/build/cjs/index.cjs +0 -1
- package/build/cjs/shared/source-code.cjs +40 -0
- package/build/esm/app.mjs +167 -38
- package/build/esm/graphql/transform.mjs +181 -0
- package/build/esm/graphql.mjs +84 -0
- package/build/esm/index.mjs +1 -1
- package/build/esm/shared/source-code.mjs +38 -0
- package/build/esnext/app.esnext +167 -38
- package/build/esnext/graphql/transform.esnext +181 -0
- package/build/esnext/graphql.esnext +84 -0
- package/build/esnext/index.esnext +1 -1
- package/build/esnext/shared/source-code.esnext +38 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/app.d.ts +101 -31
- package/build/typescript/app.d.ts.map +1 -1
- package/build/typescript/graphql/transform.d.ts +17 -0
- package/build/typescript/graphql/transform.d.ts.map +1 -0
- package/build/typescript/graphql.d.ts +6 -0
- package/build/typescript/graphql.d.ts.map +1 -0
- package/build/typescript/index.d.ts +1 -1
- package/build/typescript/index.d.ts.map +1 -1
- package/build/typescript/shared/source-code.d.ts +5 -0
- package/build/typescript/shared/source-code.d.ts.map +1 -0
- package/package.json +13 -1
- package/source/app.ts +197 -41
- package/source/graphql/transform.ts +283 -0
- package/source/graphql.ts +139 -0
- package/source/index.ts +0 -1
- package/source/shared/source-code.ts +64 -0
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.
|
|
9
|
+
"version": "0.1.4",
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=14.0.0"
|
|
12
12
|
},
|
|
@@ -27,12 +27,24 @@
|
|
|
27
27
|
"types": "./build/typescript/index.d.ts",
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@babel/core": "^7.23.0",
|
|
31
|
+
"@babel/plugin-transform-runtime": "^7.23.0",
|
|
32
|
+
"@babel/plugin-proposal-decorators": "^7.23.0",
|
|
33
|
+
"@babel/plugin-syntax-typescript": "^7.22.0",
|
|
34
|
+
"@babel/preset-env": "^7.23.0",
|
|
35
|
+
"@babel/preset-react": "^7.22.0",
|
|
36
|
+
"@babel/preset-typescript": "^7.23.0",
|
|
37
|
+
"@rollup/plugin-alias": "^5.0.1",
|
|
38
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
30
39
|
"@rollup/plugin-replace": "^5.0.3",
|
|
31
40
|
"@rollup/plugin-commonjs": "^25.0.5",
|
|
32
41
|
"@rollup/plugin-json": "^6.0.1",
|
|
33
42
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
43
|
+
"@quilted/graphql": "^2.0.0",
|
|
44
|
+
"@types/babel__preset-env": "^7.9.0",
|
|
34
45
|
"dotenv": "^16.0.0",
|
|
35
46
|
"esbuild": "^0.19.4",
|
|
47
|
+
"graphql": "^16.8.0",
|
|
36
48
|
"rollup-plugin-esbuild": "^6.1.0",
|
|
37
49
|
"rollup-plugin-node-externals": "^6.1.2",
|
|
38
50
|
"rollup-plugin-visualizer": "^5.9.0"
|
package/source/app.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
|
|
3
|
-
import type {Plugin} from 'rollup';
|
|
3
|
+
import type {GetManualChunk, Plugin} from 'rollup';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
MAGIC_MODULE_ENTRY,
|
|
@@ -27,7 +27,7 @@ export interface AppOptions {
|
|
|
27
27
|
*
|
|
28
28
|
* @example './App.tsx'
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
app?: string;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Whether to include GraphQL-related code transformations.
|
|
@@ -44,34 +44,6 @@ export interface AppOptions {
|
|
|
44
44
|
env?: MagicModuleEnvOptions;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function quiltApp({env, entry}: AppOptions = {}) {
|
|
48
|
-
return {
|
|
49
|
-
name: '@quilted/app',
|
|
50
|
-
async options(originalOptions) {
|
|
51
|
-
const newPlugins = rollupPluginsToArray(originalOptions.plugins);
|
|
52
|
-
const newOptions = {...originalOptions, plugins: newPlugins};
|
|
53
|
-
|
|
54
|
-
if (env) {
|
|
55
|
-
const {magicModuleEnv, replaceProcessEnv} = await import('./env.ts');
|
|
56
|
-
|
|
57
|
-
if (typeof env === 'boolean') {
|
|
58
|
-
newPlugins.push(replaceProcessEnv({mode: 'production'}));
|
|
59
|
-
newPlugins.push(magicModuleEnv({mode: 'production'}));
|
|
60
|
-
} else {
|
|
61
|
-
newPlugins.push(replaceProcessEnv({mode: env.mode ?? 'production'}));
|
|
62
|
-
newPlugins.push(magicModuleEnv({mode: 'production', ...env}));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (entry) {
|
|
67
|
-
newPlugins.push(magicModuleAppComponent({entry}));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return newOptions;
|
|
71
|
-
},
|
|
72
|
-
} satisfies Plugin;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
47
|
export interface AppBrowserOptions extends AppOptions {
|
|
76
48
|
/**
|
|
77
49
|
* Customizes the magic `quilt:module/browser` entry module.
|
|
@@ -110,27 +82,55 @@ export interface AppBrowserAssetsOptions {
|
|
|
110
82
|
}
|
|
111
83
|
|
|
112
84
|
export function quiltAppBrowser({
|
|
113
|
-
|
|
85
|
+
app,
|
|
114
86
|
env,
|
|
115
|
-
graphql,
|
|
116
87
|
assets,
|
|
117
88
|
module,
|
|
89
|
+
graphql = true,
|
|
118
90
|
}: AppBrowserOptions = {}) {
|
|
91
|
+
const mode =
|
|
92
|
+
(typeof env === 'object' ? env?.mode : undefined) ?? 'production';
|
|
93
|
+
|
|
119
94
|
return {
|
|
120
95
|
name: '@quilted/app/browser',
|
|
121
96
|
async options(originalOptions) {
|
|
122
97
|
const newPlugins = rollupPluginsToArray(originalOptions.plugins);
|
|
123
98
|
const newOptions = {...originalOptions, plugins: newPlugins};
|
|
124
99
|
|
|
125
|
-
const [{visualizer}, nodePlugins] = await Promise.all([
|
|
100
|
+
const [{visualizer}, {sourceCode}, nodePlugins] = await Promise.all([
|
|
126
101
|
import('rollup-plugin-visualizer'),
|
|
102
|
+
import('./shared/source-code.ts'),
|
|
127
103
|
getNodePlugins(),
|
|
128
104
|
]);
|
|
129
105
|
|
|
130
|
-
newPlugins.push(quiltApp({env, entry, graphql}));
|
|
131
106
|
newPlugins.push(...nodePlugins);
|
|
107
|
+
newPlugins.push(sourceCode({mode}));
|
|
108
|
+
|
|
109
|
+
if (env) {
|
|
110
|
+
const {magicModuleEnv, replaceProcessEnv} = await import('./env.ts');
|
|
111
|
+
|
|
112
|
+
if (typeof env === 'boolean') {
|
|
113
|
+
newPlugins.push(replaceProcessEnv({mode: 'production'}));
|
|
114
|
+
newPlugins.push(magicModuleEnv({mode: 'production'}));
|
|
115
|
+
} else {
|
|
116
|
+
newPlugins.push(replaceProcessEnv({mode: env.mode ?? 'production'}));
|
|
117
|
+
newPlugins.push(magicModuleEnv({mode: 'production', ...env}));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (app) {
|
|
122
|
+
newPlugins.push(magicModuleAppComponent({entry: app}));
|
|
123
|
+
}
|
|
124
|
+
|
|
132
125
|
newPlugins.push(magicModuleAppBrowserEntry(module));
|
|
133
126
|
|
|
127
|
+
if (graphql) {
|
|
128
|
+
const {graphql} = await import('./graphql.ts');
|
|
129
|
+
newPlugins.push(
|
|
130
|
+
graphql({manifest: path.resolve(`manifests/graphql.json`)}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
134
|
const minify = assets?.minify ?? true;
|
|
135
135
|
|
|
136
136
|
if (minify) {
|
|
@@ -143,16 +143,28 @@ export function quiltAppBrowser({
|
|
|
143
143
|
template: 'treemap',
|
|
144
144
|
open: false,
|
|
145
145
|
brotliSize: true,
|
|
146
|
-
filename: path.resolve(
|
|
146
|
+
filename: path.resolve(`reports/bundle-visualizer.html`),
|
|
147
147
|
}),
|
|
148
148
|
);
|
|
149
149
|
|
|
150
150
|
return newOptions;
|
|
151
151
|
},
|
|
152
|
+
outputOptions(originalOptions) {
|
|
153
|
+
return {
|
|
154
|
+
...originalOptions,
|
|
155
|
+
// format: isESM ? 'esm' : 'systemjs',
|
|
156
|
+
format: 'esm',
|
|
157
|
+
dir: path.resolve(`build/assets`),
|
|
158
|
+
entryFileNames: `app.[hash].js`,
|
|
159
|
+
assetFileNames: `[name].[hash].[ext]`,
|
|
160
|
+
chunkFileNames: `[name].[hash].js`,
|
|
161
|
+
manualChunks: createManualChunksSorter(),
|
|
162
|
+
};
|
|
163
|
+
},
|
|
152
164
|
} satisfies Plugin;
|
|
153
165
|
}
|
|
154
166
|
|
|
155
|
-
export interface AppServerOptions {
|
|
167
|
+
export interface AppServerOptions extends AppOptions {
|
|
156
168
|
/**
|
|
157
169
|
* The entry module for the server of this app. This module must export a
|
|
158
170
|
* `RequestRouter` object as its default export, which will be wrapped in
|
|
@@ -161,22 +173,66 @@ export interface AppServerOptions {
|
|
|
161
173
|
entry?: string;
|
|
162
174
|
}
|
|
163
175
|
|
|
164
|
-
export function quiltAppServer(
|
|
176
|
+
export function quiltAppServer({
|
|
177
|
+
app,
|
|
178
|
+
env,
|
|
179
|
+
graphql,
|
|
180
|
+
entry,
|
|
181
|
+
}: AppServerOptions = {}) {
|
|
182
|
+
const mode =
|
|
183
|
+
(typeof env === 'object' ? env?.mode : undefined) ?? 'production';
|
|
184
|
+
|
|
165
185
|
return {
|
|
166
186
|
name: '@quilted/app/server',
|
|
167
187
|
async options(originalOptions) {
|
|
168
188
|
const newPlugins = rollupPluginsToArray(originalOptions.plugins);
|
|
169
189
|
const newOptions = {...originalOptions, plugins: newPlugins};
|
|
170
190
|
|
|
171
|
-
const [{magicModuleRequestRouterEntry}] =
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
const [{magicModuleRequestRouterEntry}, {sourceCode}, nodePlugins] =
|
|
192
|
+
await Promise.all([
|
|
193
|
+
import('./request-router.ts'),
|
|
194
|
+
import('./shared/source-code.ts'),
|
|
195
|
+
getNodePlugins(),
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
newPlugins.push(...nodePlugins);
|
|
199
|
+
newPlugins.push(sourceCode({mode}));
|
|
200
|
+
|
|
201
|
+
if (env) {
|
|
202
|
+
const {magicModuleEnv, replaceProcessEnv} = await import('./env.ts');
|
|
203
|
+
|
|
204
|
+
if (typeof env === 'boolean') {
|
|
205
|
+
newPlugins.push(replaceProcessEnv({mode}));
|
|
206
|
+
newPlugins.push(magicModuleEnv({mode}));
|
|
207
|
+
} else {
|
|
208
|
+
newPlugins.push(replaceProcessEnv({mode}));
|
|
209
|
+
newPlugins.push(magicModuleEnv({mode, ...env}));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (app) {
|
|
214
|
+
newPlugins.push(magicModuleAppComponent({entry: app}));
|
|
215
|
+
}
|
|
174
216
|
|
|
175
217
|
newPlugins.push(magicModuleRequestRouterEntry());
|
|
176
|
-
newPlugins.push(magicModuleAppRequestRouter(
|
|
218
|
+
newPlugins.push(magicModuleAppRequestRouter({entry}));
|
|
219
|
+
|
|
220
|
+
if (graphql) {
|
|
221
|
+
const {graphql} = await import('./graphql.ts');
|
|
222
|
+
newPlugins.push(graphql({manifest: false}));
|
|
223
|
+
}
|
|
177
224
|
|
|
178
225
|
return newOptions;
|
|
179
226
|
},
|
|
227
|
+
outputOptions(originalOptions) {
|
|
228
|
+
return {
|
|
229
|
+
...originalOptions,
|
|
230
|
+
// format,
|
|
231
|
+
format: 'esm',
|
|
232
|
+
dir: path.resolve(`build/server`),
|
|
233
|
+
entryFileNames: 'server.js',
|
|
234
|
+
};
|
|
235
|
+
},
|
|
180
236
|
} satisfies Plugin;
|
|
181
237
|
}
|
|
182
238
|
|
|
@@ -259,3 +315,103 @@ export function magicModuleAppBrowserEntry({
|
|
|
259
315
|
},
|
|
260
316
|
});
|
|
261
317
|
}
|
|
318
|
+
|
|
319
|
+
const FRAMEWORK_CHUNK_NAME = 'framework';
|
|
320
|
+
const POLYFILLS_CHUNK_NAME = 'polyfills';
|
|
321
|
+
const VENDOR_CHUNK_NAME = 'vendor';
|
|
322
|
+
const INTERNALS_CHUNK_NAME = 'internals';
|
|
323
|
+
const SHARED_CHUNK_NAME = 'shared';
|
|
324
|
+
const PACKAGES_CHUNK_NAME = 'packages';
|
|
325
|
+
const GLOBAL_CHUNK_NAME = 'global';
|
|
326
|
+
const FRAMEWORK_TEST_STRINGS: (string | RegExp)[] = [
|
|
327
|
+
'/node_modules/preact/',
|
|
328
|
+
'/node_modules/react/',
|
|
329
|
+
'/node_modules/js-cookie/',
|
|
330
|
+
'/node_modules/@quilted/quilt/',
|
|
331
|
+
'/node_modules/@preact/signals/',
|
|
332
|
+
'/node_modules/@preact/signals-core/',
|
|
333
|
+
// TODO I should turn this into an allowlist
|
|
334
|
+
/node_modules[/]@quilted[/](?!react-query|swr)/,
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const POLYFILL_TEST_STRINGS = [
|
|
338
|
+
'/node_modules/@quilted/polyfills/',
|
|
339
|
+
'/node_modules/core-js/',
|
|
340
|
+
'/node_modules/whatwg-fetch/',
|
|
341
|
+
'/node_modules/regenerator-runtime/',
|
|
342
|
+
'/node_modules/abort-controller/',
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
const INTERNALS_TEST_STRINGS = [
|
|
346
|
+
'\x00commonjsHelpers.js',
|
|
347
|
+
'/node_modules/@babel/runtime/',
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
// When building from source, quilt packages are not in node_modules,
|
|
351
|
+
// so we instead add their repo paths to the list of framework test strings.
|
|
352
|
+
if (process.env.QUILT_FROM_SOURCE) {
|
|
353
|
+
FRAMEWORK_TEST_STRINGS.push('/quilt/packages/');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Inspired by Vite: https://github.com/vitejs/vite/blob/c69f83615292953d40f07b1178d1ed1d72abe695/packages/vite/source/node/build.ts#L567
|
|
357
|
+
function createManualChunksSorter(): GetManualChunk {
|
|
358
|
+
// TODO: make this more configurable, and make it so that we bundle more intelligently
|
|
359
|
+
// for split entries
|
|
360
|
+
const packagesPath = path.resolve('packages') + path.sep;
|
|
361
|
+
const globalPath = path.resolve('global') + path.sep;
|
|
362
|
+
const sharedPath = path.resolve('shared') + path.sep;
|
|
363
|
+
|
|
364
|
+
return (id, {getModuleInfo}) => {
|
|
365
|
+
if (INTERNALS_TEST_STRINGS.some((test) => id.includes(test))) {
|
|
366
|
+
return INTERNALS_CHUNK_NAME;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (
|
|
370
|
+
FRAMEWORK_TEST_STRINGS.some((test) =>
|
|
371
|
+
typeof test === 'string' ? id.includes(test) : test.test(id),
|
|
372
|
+
)
|
|
373
|
+
) {
|
|
374
|
+
return FRAMEWORK_CHUNK_NAME;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (POLYFILL_TEST_STRINGS.some((test) => id.includes(test))) {
|
|
378
|
+
return POLYFILLS_CHUNK_NAME;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let bundleBaseName: string | undefined;
|
|
382
|
+
let relativeId: string | undefined;
|
|
383
|
+
|
|
384
|
+
if (id.includes('/node_modules/')) {
|
|
385
|
+
const moduleInfo = getModuleInfo(id);
|
|
386
|
+
|
|
387
|
+
// If the only dependency is another vendor, let Rollup handle the naming
|
|
388
|
+
if (moduleInfo == null) return;
|
|
389
|
+
if (
|
|
390
|
+
moduleInfo.importers.length > 0 &&
|
|
391
|
+
moduleInfo.importers.every((importer) =>
|
|
392
|
+
importer.includes('/node_modules/'),
|
|
393
|
+
)
|
|
394
|
+
) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
bundleBaseName = VENDOR_CHUNK_NAME;
|
|
399
|
+
relativeId = id.replace(/^.*[/]node_modules[/]/, '');
|
|
400
|
+
} else if (id.startsWith(packagesPath)) {
|
|
401
|
+
bundleBaseName = PACKAGES_CHUNK_NAME;
|
|
402
|
+
relativeId = id.replace(packagesPath, '');
|
|
403
|
+
} else if (id.startsWith(globalPath)) {
|
|
404
|
+
bundleBaseName = GLOBAL_CHUNK_NAME;
|
|
405
|
+
relativeId = id.replace(globalPath, '');
|
|
406
|
+
} else if (id.startsWith(sharedPath)) {
|
|
407
|
+
bundleBaseName = SHARED_CHUNK_NAME;
|
|
408
|
+
relativeId = id.replace(sharedPath, '');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (bundleBaseName == null || relativeId == null) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return `${bundleBaseName}-${relativeId.split(path.sep)[0]?.split('.')[0]}`;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
@@ -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
|
+
}
|