Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Transformer should uses getFragmentDependenciesForAST #1233

Merged
merged 1 commit into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 24 additions & 1 deletion e2e/webpack-specs/transform.js
Expand Up @@ -4,7 +4,7 @@ const { execSync } = require('child_process');
const webpack = require('webpack');
const { print } = require('graphql/language');

async function run() {
async function specWithoutGlobalFragments() {
const config = require('../../project-fixtures/transformation-prj/webpack.config.js');
const compiler = webpack({ ...config, mode: 'production' });
const stats = await new Promise((res, rej) => {
Expand All @@ -17,6 +17,29 @@ async function run() {
const distFilePath = path.resolve(stats.toJson().outputPath, 'main.js');
const result = execSync(`node ${distFilePath}`);
assert.equal(typeof print(JSON.parse(result.toString())), 'string');
assert(print(JSON.parse(result.toString())).indexOf('MyQuery') !== -1);
assert(print(JSON.parse(result.toString())).indexOf('fragment FragmentLeaf') !== -1);
}

async function specWithGlobalFragments() {
const config = require('../../project-fixtures/transformation-global-fag-prj/webpack.config.js');
const compiler = webpack({ ...config, mode: 'production' });
const stats = await new Promise((res, rej) => {
compiler.run((err, stats) => {
if (err) return rej(err);
return res(stats);
});
});
assert(!stats.hasErrors());
const distFilePath = path.resolve(stats.toJson().outputPath, 'main.js');
const result = execSync(`node ${distFilePath}`);
assert(print(JSON.parse(result.toString())).indexOf('MyQuery') !== -1);
assert(print(JSON.parse(result.toString())).indexOf('fragment FragmentLeaf') !== -1);
}

async function run() {
await specWithoutGlobalFragments();
await specWithGlobalFragments();
}

module.exports = run;
1 change: 1 addition & 0 deletions project-fixtures/transformation-global-fag-prj/.gitignore
@@ -0,0 +1 @@
dist/
@@ -0,0 +1,7 @@
import gql from './tag';

const fragmentLeaf = gql`
fragment FragmentLeaf on Query {
hello
}
`;
@@ -0,0 +1,8 @@
import gql from './tag';

const fragmentNode = gql`
fragment FragmentNode on Query {
bye
...FragmentLeaf
}
`;
11 changes: 11 additions & 0 deletions project-fixtures/transformation-global-fag-prj/query.ts
@@ -0,0 +1,11 @@
import gql from './tag';
import { fragmentNode } from './fragment-node';

const query = gql`
query MyQuery {
__typename
...FragmentNode
}
`;

console.log(JSON.stringify(query, null, 2));
4 changes: 4 additions & 0 deletions project-fixtures/transformation-global-fag-prj/schema.graphql
@@ -0,0 +1,4 @@
type Query {
hello: String!
bye: String!
}
4 changes: 4 additions & 0 deletions project-fixtures/transformation-global-fag-prj/tag.ts
@@ -0,0 +1,4 @@
export default function gql(literals: TemplateStringsArray, ...args: unknown[]) {
// dummy impl
return '';
}
16 changes: 16 additions & 0 deletions project-fixtures/transformation-global-fag-prj/tsconfig.json
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"plugins": [
{
"name": "ts-graphql-plugin",
"tag": "gql",
"schema": "schema.graphql",
"enabledGlobalFragments": true
}
]
}
}
35 changes: 35 additions & 0 deletions project-fixtures/transformation-global-fag-prj/webpack.config.js
@@ -0,0 +1,35 @@
const path = require('path');

const TsGraphQLPlugin = require('../../webpack');

const tsgqlPlugin = new TsGraphQLPlugin({ tsconfigPath: __dirname });

module.exports = {
resolve: {
extensions: ['.ts', '.js'],
},
entry: {
main: path.resolve(__dirname, 'query.ts'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: () => ({
before: [tsgqlPlugin.getTransformer()],
}),
},
},
],
},
plugins: [tsgqlPlugin],
devtool: false,
};
27 changes: 20 additions & 7 deletions src/transformer/transformer-host.ts
@@ -1,11 +1,14 @@
import ts from 'typescript';
import { DocumentNode } from 'graphql';
import { Kind, type DocumentNode, FragmentDefinitionNode } from 'graphql';
import { getFragmentDependenciesForAST } from 'graphql-language-service';
import { Analyzer, AnalyzerFactory, ExtractFileResult } from '../analyzer';
import { getTransformer, DocumentTransformer } from './transformer';
import { parseTagConfig } from '../ts-ast-util';
import { cloneFragmentMap, getFragmentNamesInDocument } from '../gql-ast-util';

class DocumentNodeRegistory {
protected readonly _map = new Map<string, Map<number, DocumentNode>>();
private _externalFragmentMap = new Map<string, FragmentDefinitionNode>();

constructor() {}

Expand All @@ -16,10 +19,20 @@ class DocumentNodeRegistory {
getDocumentNode(templateNode: ts.TemplateExpression | ts.NoSubstitutionTemplateLiteral) {
const positionMap = this._map.get(templateNode.getSourceFile().fileName);
if (!positionMap) return;
return positionMap.get(templateNode.getStart());
const docNode = positionMap.get(templateNode.getStart());
if (!docNode) return;
const externalFragments = getFragmentDependenciesForAST(
docNode,
cloneFragmentMap(this._externalFragmentMap, getFragmentNamesInDocument(docNode)),
);
return {
kind: Kind.DOCUMENT,
definitions: [...docNode.definitions, ...externalFragments],
} satisfies DocumentNode;
}

update(extractedResults: ExtractFileResult[]) {
update(extractedResults: ExtractFileResult[], externalFragmentMap: Map<string, FragmentDefinitionNode>) {
this._externalFragmentMap = externalFragmentMap;
extractedResults.forEach(result => {
if (!result.documentNode) return;
let positionMap = this._map.get(result.fileName);
Expand Down Expand Up @@ -55,8 +68,8 @@ export class TransformerHost {
}

loadProject() {
const [, { fileEntries }] = this._analyzer.extract();
this._documentNodeRegistory.update(fileEntries);
const [, { fileEntries, globalFragments }] = this._analyzer.extract();
this._documentNodeRegistory.update(fileEntries, globalFragments.definitionMap);
}

updateFiles(fileNameList: string[]) {
Expand All @@ -71,10 +84,10 @@ export class TransformerHost {
// other-opened-file.ts: declare `query { ...X }` importing fragment from changed-file.ts
//
// In the above case, the transformed output of other-opened-file.ts should have GraphQL docuemnt corresponding to `fragment X on Query { fieldA } query { ...X }`
const [, { fileEntries }] = this._analyzer.extract([
const [, { fileEntries, globalFragments }] = this._analyzer.extract([
...new Set([...fileNameList, ...this._documentNodeRegistory.getFiles()]),
]);
this._documentNodeRegistory.update(fileEntries);
this._documentNodeRegistory.update(fileEntries, globalFragments.definitionMap);
}

getTransformer({
Expand Down