From ee0daabd6f3036bb2b63a679d5415f63c9f85056 Mon Sep 17 00:00:00 2001 From: Jascha Ephraim Date: Tue, 14 May 2024 14:42:57 -0700 Subject: [PATCH] Add option to pluck from custom Vue block (#4439) * Add option to pluck from custom Vue block * Changeset * Go * Prettier --------- Co-authored-by: Arda TANRIKULU --- .changeset/old-lobsters-fold.md | 5 ++ packages/graphql-tag-pluck/src/index.ts | 74 ++++++++++++++--- .../tests/graphql-tag-pluck.test.ts | 82 ++++++++++++++++++- .../loaders/schema/schema-from-export.spec.ts | 64 ++++++++++----- 4 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 .changeset/old-lobsters-fold.md diff --git a/.changeset/old-lobsters-fold.md b/.changeset/old-lobsters-fold.md new file mode 100644 index 00000000000..cfa1adff8d4 --- /dev/null +++ b/.changeset/old-lobsters-fold.md @@ -0,0 +1,5 @@ +--- +"@graphql-tools/graphql-tag-pluck": patch +--- + +Add option to pluck from custom Vue block diff --git a/packages/graphql-tag-pluck/src/index.ts b/packages/graphql-tag-pluck/src/index.ts index 11298ea84c3..830195824d7 100644 --- a/packages/graphql-tag-pluck/src/index.ts +++ b/packages/graphql-tag-pluck/src/index.ts @@ -91,6 +91,10 @@ export interface GraphQLTagPluckOptions { * The magic comment anchor to look for when parsing GraphQL strings. Defaults to `graphql`. */ gqlMagicComment?: string; + /** + * The name of a custom Vue block that contains raw GraphQL to be plucked. + */ + gqlVueBlock?: string; /** * Allows to use a global identifier instead of a module import. * ```js @@ -154,6 +158,23 @@ function parseWithVue(vueTemplateCompiler: typeof import('@vue/compiler-sfc'), f : ''; } +function customBlockFromVue( + // tslint:disable-next-line: no-implicit-dependencies + vueTemplateCompiler: typeof import('@vue/compiler-sfc'), + fileData: string, + filePath: string, + blockType: string, +): Source | undefined { + const { descriptor } = vueTemplateCompiler.parse(fileData); + + const block = descriptor.customBlocks.find(b => b.type === blockType); + if (block === undefined) { + return; + } + + return new Source(block.content.trim(), filePath, block.loc.start); +} + // tslint:disable-next-line: no-implicit-dependencies function parseWithSvelte(svelte2tsx: typeof import('svelte2tsx'), fileData: string) { const fileInTsx = svelte2tsx.svelte2tsx(fileData); @@ -192,7 +213,12 @@ export const gqlPluckFromCodeString = async ( validate({ code, options }); const fileExt = extractExtension(filePath); + + let blockSource; if (fileExt === '.vue') { + if (options.gqlVueBlock) { + blockSource = await pluckVueFileCustomBlock(code, filePath, options.gqlVueBlock); + } code = await pluckVueFileScript(code); } else if (fileExt === '.svelte') { code = await pluckSvelteFileScript(code); @@ -200,9 +226,13 @@ export const gqlPluckFromCodeString = async ( code = await pluckAstroFileScript(code); } - return parseCode({ code, filePath, options }).map( + const sources = parseCode({ code, filePath, options }).map( t => new Source(t.content, filePath, t.loc.start), ); + if (blockSource) { + sources.push(blockSource); + } + return sources; }; /** @@ -222,7 +252,12 @@ export const gqlPluckFromCodeStringSync = ( validate({ code, options }); const fileExt = extractExtension(filePath); + + let blockSource; if (fileExt === '.vue') { + if (options.gqlVueBlock) { + blockSource = pluckVueFileCustomBlockSync(code, filePath, options.gqlVueBlock); + } code = pluckVueFileScriptSync(code); } else if (fileExt === '.svelte') { code = pluckSvelteFileScriptSync(code); @@ -230,9 +265,13 @@ export const gqlPluckFromCodeStringSync = ( code = pluckAstroFileScriptSync(code); } - return parseCode({ code, filePath, options }).map( + const sources = parseCode({ code, filePath, options }).map( t => new Source(t.content, filePath, t.loc.start), ); + if (blockSource) { + sources.push(blockSource); + } + return sources; }; export function parseCode({ @@ -320,31 +359,44 @@ const MissingAstroCompilerError = new Error( `), ); -async function pluckVueFileScript(fileData: string) { - let vueTemplateCompiler: typeof import('@vue/compiler-sfc'); +async function loadVueCompilerSync() { try { // eslint-disable-next-line import/no-extraneous-dependencies - vueTemplateCompiler = await import('@vue/compiler-sfc'); + return await import('@vue/compiler-sfc'); } catch (e: any) { throw MissingVueTemplateCompilerError; } - - return parseWithVue(vueTemplateCompiler, fileData); } -function pluckVueFileScriptSync(fileData: string) { - let vueTemplateCompiler: typeof import('@vue/compiler-sfc'); - +function loadVueCompilerAsync() { try { // eslint-disable-next-line import/no-extraneous-dependencies - vueTemplateCompiler = require('@vue/compiler-sfc'); + return require('@vue/compiler-sfc'); } catch (e: any) { throw MissingVueTemplateCompilerError; } +} + +async function pluckVueFileScript(fileData: string) { + const vueTemplateCompiler = await loadVueCompilerSync(); + return parseWithVue(vueTemplateCompiler, fileData); +} +function pluckVueFileScriptSync(fileData: string) { + const vueTemplateCompiler = loadVueCompilerAsync(); return parseWithVue(vueTemplateCompiler, fileData); } +async function pluckVueFileCustomBlock(fileData: string, filePath: string, blockType: string) { + const vueTemplateCompiler = await loadVueCompilerSync(); + return customBlockFromVue(vueTemplateCompiler, fileData, filePath, blockType); +} + +function pluckVueFileCustomBlockSync(fileData: string, filePath: string, blockType: string) { + const vueTemplateCompiler = loadVueCompilerAsync(); + return customBlockFromVue(vueTemplateCompiler, fileData, filePath, blockType); +} + async function pluckSvelteFileScript(fileData: string) { let svelte2tsx: typeof import('svelte2tsx'); try { diff --git a/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts b/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts index b25c99a7152..e67f78d8c5a 100644 --- a/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts +++ b/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts @@ -268,7 +268,7 @@ describe('graphql-tag-pluck', () => { ); }); - it.only("should pluck graphql-tag template literals from .ts that use 'using' keyword", async () => { + it("should pluck graphql-tag template literals from .ts that use 'using' keyword", async () => { const sources = await pluck( 'tmp-XXXXXX.ts', freeText(` @@ -1986,6 +1986,86 @@ describe('graphql-tag-pluck', () => { ); }); + it('should be able to specify a custom Vue block to pluck from', async () => { + const sources = await pluck( + 'tmp-XXXXXX.vue', + freeText(` + + + + + + + + query CustomBlockQuery { + site { + siteMetadata { + title + } + } + } + + `), + { + gqlVueBlock: 'graphql', + }, + ); + + expect(sources.map(source => source.body).join('\n\n')).toEqual( + freeText(` + query IndexQuery { + site { + siteMetadata { + title + } + } + } + + query CustomBlockQuery { + site { + siteMetadata { + title + } + } + } + `), + ); + }); + it('should be able to specify the package name of which the GraphQL identifier should be imported from', async () => { const sources = await pluck( 'tmp-XXXXXX.js', diff --git a/packages/load/tests/loaders/schema/schema-from-export.spec.ts b/packages/load/tests/loaders/schema/schema-from-export.spec.ts index 9335c8ae664..88d64bd273c 100644 --- a/packages/load/tests/loaders/schema/schema-from-export.spec.ts +++ b/packages/load/tests/loaders/schema/schema-from-export.spec.ts @@ -21,16 +21,24 @@ describe('Schema From Export', () => { sync: loadSchemaSync, })((load, mode) => { test('should load the schema correctly from module.exports', async () => { - const result = await load('./tests/loaders/schema/test-files/loaders/module-exports.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/module-exports.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); }); test('should load the schema (with extend) correctly from module.exports', async () => { - const result = await load('./tests/loaders/schema/test-files/schema-dir/with-extend.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/with-extend.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); const QueryType = result.getQueryType(); assertNonMaybe(QueryType); @@ -38,36 +46,52 @@ describe('Schema From Export', () => { }); test('should load the schema correctly from variable export', async () => { - const result = await load('./tests/loaders/schema/test-files/loaders/schema-export.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/schema-export.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); }); test('should load the schema correctly from default export', async () => { - const result = await load('./tests/loaders/schema/test-files/loaders/default-export.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/default-export.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); }); if (mode === 'async') { test('should load the schema correctly from promise export', async () => { - const result = await load('./tests/loaders/schema/test-files/loaders/promise-export.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/promise-export.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); }); test('should load the schema correctly from promise export', async () => { - const result = await load('./tests/loaders/schema/test-files/loaders/promise-export.js', { - loaders: [new CodeFileLoader()], - }); + const result = await load( + '../../../../loaders/code-file/tests/test-files/loaders/promise-export.js', + { + loaders: [new CodeFileLoader()], + cwd: __dirname, + }, + ); expect(isSchema(result)).toBeTruthy(); }); } - test.only('should work with extensions (without schema definition)', async () => { + test('should work with extensions (without schema definition)', async () => { const schemaPath = './tests/loaders/schema/test-files/schema-dir/extensions/export-schema.js'; const schema = await load(schemaPath, { loaders: [new CodeFileLoader()], @@ -80,7 +104,7 @@ describe('Schema From Export', () => { expect(queryFields).toContain('bar'); }); - test.only('should work with extensions (with schema definition)', async () => { + test('should work with extensions (with schema definition)', async () => { const schemaPath = './tests/loaders/schema/test-files/schema-dir/extensions/export-schema-with-def.js'; const schema = await load(schemaPath, {