diff --git a/packages/graphql-tag-pluck/src/index.ts b/packages/graphql-tag-pluck/src/index.ts
index 5affe6b95b3..1cdfb04f011 100644
--- a/packages/graphql-tag-pluck/src/index.ts
+++ b/packages/graphql-tag-pluck/src/index.ts
@@ -153,8 +153,21 @@ const supportedExtensions = [
];
// tslint:disable-next-line: no-implicit-dependencies
-function parseWithVue(vueTemplateCompiler: typeof import('@vue/compiler-sfc'), fileData: string) {
- const { descriptor } = vueTemplateCompiler.parse(fileData);
+function parseWithVue(
+ vueTemplateCompiler: typeof import('@vue/compiler-sfc'),
+ typescriptPackage: typeof import('typescript'),
+ fileData: string,
+ filePath: string,
+) {
+ // Calls to registerTS are idempotent, so it's safe to call it repeatedly like
+ // we are here.
+ //
+ // See https://github.com/ardatan/graphql-tools/pull/7271 for more details.
+ //
+
+ vueTemplateCompiler.registerTS(() => typescriptPackage);
+
+ const { descriptor } = vueTemplateCompiler.parse(fileData, { filename: filePath });
return descriptor.script || descriptor.scriptSetup
? vueTemplateCompiler.compileScript(descriptor, { id: Date.now().toString() }).content
@@ -168,7 +181,7 @@ function customBlockFromVue(
filePath: string,
blockType: string,
): Source | undefined {
- const { descriptor } = vueTemplateCompiler.parse(fileData);
+ const { descriptor } = vueTemplateCompiler.parse(fileData, { filename: filePath });
const block = descriptor.customBlocks.find(b => b.type === blockType);
if (block === undefined) {
@@ -232,7 +245,7 @@ export const gqlPluckFromCodeString = async (
if (options.gqlVueBlock) {
blockSource = await pluckVueFileCustomBlock(code, filePath, options.gqlVueBlock);
}
- code = await pluckVueFileScript(code);
+ code = await pluckVueFileScript(code, filePath);
} else if (fileExt === '.svelte') {
code = await pluckSvelteFileScript(code);
} else if (fileExt === '.astro') {
@@ -273,7 +286,7 @@ export const gqlPluckFromCodeStringSync = (
if (options.gqlVueBlock) {
blockSource = pluckVueFileCustomBlockSync(code, filePath, options.gqlVueBlock);
}
- code = pluckVueFileScriptSync(code);
+ code = pluckVueFileScriptSync(code, filePath);
} else if (fileExt === '.svelte') {
code = pluckSvelteFileScriptSync(code);
} else if (fileExt === '.astro') {
@@ -391,6 +404,21 @@ const MissingGlimmerCompilerError = new Error(
`),
);
+const MissingTypeScriptPackageError = new Error(
+ freeText(`
+ GraphQL template literals cannot be plucked from a Vue template code without having the "typescript" package installed.
+ Please install it and try again.
+
+ Via NPM:
+
+ $ npm install typescript
+
+ Via Yarn:
+
+ $ yarn add typescript
+ `),
+);
+
async function loadVueCompilerAsync() {
try {
// eslint-disable-next-line import/no-extraneous-dependencies
@@ -400,6 +428,15 @@ async function loadVueCompilerAsync() {
}
}
+async function loadTypeScriptPackageAsync() {
+ try {
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ return await import('typescript');
+ } catch {
+ throw MissingTypeScriptPackageError;
+ }
+}
+
function loadVueCompilerSync() {
try {
// eslint-disable-next-line import/no-extraneous-dependencies
@@ -409,18 +446,31 @@ function loadVueCompilerSync() {
}
}
-async function pluckVueFileScript(fileData: string) {
- const vueTemplateCompiler = await loadVueCompilerAsync();
- return parseWithVue(vueTemplateCompiler, fileData);
+function loadTypeScriptPackageSync() {
+ try {
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ return require('typescript');
+ } catch {
+ throw MissingTypeScriptPackageError;
+ }
}
-function pluckVueFileScriptSync(fileData: string) {
+async function pluckVueFileScript(fileData: string, filePath: string) {
+ const [typescriptPackage, vueTemplateCompiler] = await Promise.all([
+ loadTypeScriptPackageAsync(),
+ loadVueCompilerAsync(),
+ ]);
+ return parseWithVue(vueTemplateCompiler, typescriptPackage, fileData, filePath);
+}
+
+function pluckVueFileScriptSync(fileData: string, filePath: string) {
const vueTemplateCompiler = loadVueCompilerSync();
- return parseWithVue(vueTemplateCompiler, fileData);
+ const typescriptPackage = loadTypeScriptPackageSync();
+ return parseWithVue(vueTemplateCompiler, typescriptPackage, fileData, filePath);
}
async function pluckVueFileCustomBlock(fileData: string, filePath: string, blockType: string) {
- const vueTemplateCompiler = await loadVueCompilerSync();
+ const vueTemplateCompiler = await loadVueCompilerAsync();
return customBlockFromVue(vueTemplateCompiler, fileData, filePath, blockType);
}
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 568c4bfe7f7..0d9a548d90b 100644
--- a/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts
+++ b/packages/graphql-tag-pluck/tests/graphql-tag-pluck.test.ts
@@ -1,7 +1,23 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
import { runTests } from '../../testing/utils.js';
import { gqlPluckFromCodeString, gqlPluckFromCodeStringSync } from '../src/index.js';
import { freeText } from '../src/utils.js';
+// A temporary directory unique for each unit test. Cleaned up after each unit
+// test resolves.
+let tmpDir: string;
+
+beforeEach(async () => {
+ // We create temporary directories in the test directory because our test
+ // infrastructure denies writes to the host's tmp directory.
+ tmpDir = await fs.mkdtemp(path.join(__dirname, 'tmp-'));
+});
+
+afterEach(async () => {
+ await fs.rm(tmpDir, { recursive: true });
+});
+
describe('graphql-tag-pluck', () => {
runTests({
async: gqlPluckFromCodeString,
@@ -852,6 +868,59 @@ describe('graphql-tag-pluck', () => {
);
});
+ it('should pluck graphql-tag template literals from .vue 3 setup with compiler macros and imports', async () => {
+ const EXTERNAL_PROPS_SOURCE = freeText(`
+ export type ExternalProps = {
+ foo: string;
+ };
+ `);
+
+ const VUE_SFC_SOURCE = freeText(`
+
+ test
+
+
+
+ `);
+
+ // We must write the files to disk because this test is specifically
+ // ensuring that imports work in Vue SFC files with compiler macros and
+ // imports are resolved on disk by the typescript runtime.
+ //
+ // See https://github.com/ardatan/graphql-tools/pull/7271 for details.
+ await fs.writeFile(path.join(tmpDir, 'ExternalProps.ts'), EXTERNAL_PROPS_SOURCE);
+ await fs.writeFile(path.join(tmpDir, 'component.vue'), VUE_SFC_SOURCE);
+
+ const sources = await pluck(path.join(tmpDir, 'component.vue'), VUE_SFC_SOURCE);
+
+ expect(sources.map(source => source.body).join('\n\n')).toEqual(
+ freeText(`
+ query IndexQuery {
+ site {
+ siteMetadata {
+ title
+ }
+ }
+ }
+ `),
+ );
+ });
+
it('should pluck graphql-tag template literals from .vue 3 setup JavaScript file', async () => {
const sources = await pluck(
'tmp-XXXXXX.vue',