@isograph/babel-plugin 0.3.0 → 0.4.0

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.
@@ -0,0 +1,97 @@
1
+ import { transform } from '@babel/core';
2
+ import { describe, expect, test, vi } from 'vitest';
3
+ import plugin from './BabelPluginIsograph';
4
+
5
+ // @ts-ignore
6
+ async function mock(mockedUri, stub) {
7
+ const { Module } = await import('module');
8
+ const path = await import('path');
9
+ // @ts-ignore
10
+ Module._load_original = Module._load;
11
+ // @ts-ignore
12
+ Module._load = (uri, parent) => {
13
+ if (uri === mockedUri) return stub(path);
14
+ // @ts-ignore
15
+ return Module._load_original(uri, parent);
16
+ };
17
+ }
18
+
19
+ // In order to test `require`
20
+ vi.hoisted(
21
+ () =>
22
+ // @ts-ignore
23
+ void mock('cosmiconfig', (path) => () => ({
24
+ searchSync: () => ({
25
+ config: {
26
+ project_root: './src/components',
27
+ schema: './backend/schema.graphql',
28
+ schema_extensions: ['./backend/schema-extension.graphql'],
29
+ options: {
30
+ module: 'esmodule',
31
+ },
32
+ },
33
+ filepath: `${path.resolve('.')}/isograph.config.json`,
34
+ }),
35
+ })),
36
+ );
37
+
38
+ describe('Babel plugin Isograph', () => {
39
+ const transformerOpts = {
40
+ babelrc: false,
41
+ filename: './src/components/Home/Header/File.ts',
42
+ plugins: [[plugin, {}]],
43
+ };
44
+
45
+ test('should return an identity for non called iso function', () => {
46
+ const code = `
47
+ export const HomeRoute = iso(\`
48
+ field Query.HomeRoute @component {
49
+ pets {
50
+ id
51
+ }
52
+ }
53
+ \`);
54
+ `;
55
+
56
+ const result = transform(code, transformerOpts) ?? { code: '' };
57
+
58
+ expect(result.code).toMatchInlineSnapshot(
59
+ `"export const HomeRoute = x => x;"`,
60
+ );
61
+ });
62
+
63
+ test('should preserve function call when iso applied', () => {
64
+ const code = `
65
+ export const HomeRoute = iso(\`
66
+ field Query.HomeRoute @component {
67
+ pets {
68
+ id
69
+ }
70
+ }
71
+ \`)(function HomeRouteComponent() {
72
+ return 'Render';
73
+ });
74
+ `;
75
+
76
+ const result = transform(code, transformerOpts) ?? { code: '' };
77
+
78
+ expect(result.code).toMatchInlineSnapshot(`
79
+ "export const HomeRoute = function HomeRouteComponent() {
80
+ return 'Render';
81
+ };"
82
+ `);
83
+ });
84
+
85
+ test('should transform the iso function to a require call', () => {
86
+ const code = `function test() { const a=iso(\`entrypoint Query.HomeRoute\`); }`;
87
+
88
+ const result = transform(code, transformerOpts) ?? { code: '' };
89
+
90
+ expect(result.code).toMatchInlineSnapshot(`
91
+ "import _HomeRoute from "../../__isograph/Query/HomeRoute/entrypoint.ts";
92
+ function test() {
93
+ const a = _HomeRoute;
94
+ }"
95
+ `);
96
+ });
97
+ });
package/compileTag.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { addDefault } = require('@babel/helper-module-imports');
3
4
  const pathModule = require('path');
4
5
  const os = require('os');
5
6
 
@@ -12,16 +13,28 @@ const os = require('os');
12
13
  function compileTag(t, path, config) {
13
14
  const callee = path.node.callee;
14
15
  if (t.isIdentifier(callee) && callee.name === 'iso' && path.node.arguments) {
15
- const { keyword, type, field } = getTypeAndField(path);
16
+ const { keyword, parentObjectEntityName, selectableName } =
17
+ getParentObjectEntityNameAndSelectableName(path);
16
18
  if (keyword === 'entrypoint') {
17
19
  // This throws if the tag is invalid
18
- compileImportStatement(t, path, type, field, 'entrypoint', config);
19
- } else if (keyword === 'field') {
20
- if (
21
- t.isCallExpression(path.parentPath.node) &&
22
- path.parentPath.node.arguments.length === 1
23
- ) {
24
- path.parentPath.replaceWith(path.parentPath.node.arguments[0]);
20
+ compileImportStatement(
21
+ t,
22
+ path,
23
+ parentObjectEntityName,
24
+ selectableName,
25
+ 'entrypoint',
26
+ config,
27
+ );
28
+ } else if (keyword === 'field' || keyword === 'pointer') {
29
+ if (t.isCallExpression(path.parentPath.node)) {
30
+ const firstArg = path.parentPath.node.arguments[0];
31
+ if (path.parentPath.node.arguments.length === 1 && firstArg != null) {
32
+ path.parentPath.replaceWith(firstArg);
33
+ } else {
34
+ throw new Error(
35
+ 'Invalid iso tag usage. The iso function should be passed at most one argument.',
36
+ );
37
+ }
25
38
  } else {
26
39
  path.replaceWith(
27
40
  t.arrowFunctionExpression([t.identifier('x')], t.identifier('x')),
@@ -29,54 +42,61 @@ function compileTag(t, path, config) {
29
42
  }
30
43
  } else {
31
44
  throw new Error(
32
- "Invalid iso tag usage. Expected 'entrypoint' or 'field'.",
45
+ "Invalid iso tag usage. Expected 'entrypoint', 'field' or 'pointer'.",
33
46
  );
34
47
  }
35
48
  }
36
49
  return false;
37
50
  }
38
51
 
39
- const typeAndFieldRegex = new RegExp(
40
- '\\s*(entrypoint|field)\\s*([^\\.\\s]+)\\.([^\\s\\(]+)',
52
+ const parentObjectEntityNameAndSelectableNameRegex = new RegExp(
53
+ '\\s*(entrypoint|field|pointer)\\s*([^\\.\\s]+)\\.([^\\s\\(]+)',
41
54
  'm',
42
55
  );
43
56
 
44
57
  /**
45
58
  * @param {babel.NodePath<babel.types.CallExpression>} path
46
- * */
47
- function getTypeAndField(path) {
48
- if (path.node.arguments.length !== 1) {
59
+ **/
60
+ function getParentObjectEntityNameAndSelectableName(path) {
61
+ const firstArg = path.node.arguments[0];
62
+ if (path.node.arguments.length !== 1 || firstArg == null) {
49
63
  throw new Error(
50
64
  `BabelPluginIsograph: Iso invocation require one parameter, found ${path.node.arguments.length}`,
51
65
  );
52
66
  }
53
67
 
54
- if (path.node.arguments[0].type !== 'TemplateLiteral') {
68
+ if (firstArg.type !== 'TemplateLiteral') {
55
69
  throw new Error(
56
70
  'BabelPluginIsograph: Only template literals are allowed in iso fragments.',
57
71
  );
58
72
  }
59
73
 
60
- const quasis = path.node.arguments[0].quasis;
61
- if (quasis.length !== 1) {
74
+ const quasis = firstArg.quasis;
75
+ const firstQuasi = quasis[0];
76
+ if (quasis.length !== 1 || firstQuasi == null) {
62
77
  throw new Error(
63
78
  'BabelPluginIsograph: Substitutions are not allowed in iso fragments.',
64
79
  );
65
80
  }
66
81
 
67
- const content = quasis[0].value.raw;
68
- const typeAndField = typeAndFieldRegex.exec(content);
82
+ const content = firstQuasi.value.raw;
83
+ const typeAndField =
84
+ parentObjectEntityNameAndSelectableNameRegex.exec(content);
69
85
 
70
86
  const keyword = typeAndField?.[1];
71
- const type = typeAndField?.[2];
72
- const field = typeAndField?.[3];
87
+ const parentObjectEntityName = typeAndField?.[2];
88
+ const selectableName = typeAndField?.[3];
73
89
 
74
- if (keyword == null || type == null || field == null) {
90
+ if (
91
+ keyword == null ||
92
+ parentObjectEntityName == null ||
93
+ selectableName == null
94
+ ) {
75
95
  throw new Error(
76
96
  'Malformed iso literal. I hope the iso compiler failed to accept this literal!',
77
97
  );
78
98
  }
79
- return { keyword, type, field };
99
+ return { keyword, parentObjectEntityName, selectableName };
80
100
  }
81
101
 
82
102
  /**
@@ -95,6 +115,7 @@ function compileImportStatement(t, path, type, field, artifactType, config) {
95
115
  cwd,
96
116
  config.config['artifact_directory'] ?? config.config['project_root'],
97
117
  );
118
+ const module = config.config['options']?.['module'];
98
119
 
99
120
  const fileToArtifactDir = pathModule.relative(folder, artifactDirectory);
100
121
  const artifactDirToArtifact = `/__isograph/${type}/${field}/${artifactType}.ts`;
@@ -115,14 +136,29 @@ function compileImportStatement(t, path, type, field, artifactType, config) {
115
136
  fileToArtifact = '.' + fileToArtifact;
116
137
  }
117
138
 
118
- path.replaceWith(
119
- t.memberExpression(
120
- t.callExpression(t.identifier('require'), [
121
- t.stringLiteral(fileToArtifact),
122
- ]),
123
- t.identifier('default'),
124
- ),
125
- );
139
+ if (module === 'esmodule') {
140
+ const program = path.scope.getProgramParent();
141
+ const imports = /** @type {Map<string, string>} */ (
142
+ program.data['imports'] ??= new Map()
143
+ );
144
+
145
+ let id = imports.get(fileToArtifact);
146
+ if (id == null) {
147
+ id = addDefault(path, fileToArtifact, { nameHint: field }).name;
148
+ imports.set(fileToArtifact, id);
149
+ }
150
+
151
+ path.replaceWith(t.identifier(id));
152
+ } else {
153
+ path.replaceWith(
154
+ t.memberExpression(
155
+ t.callExpression(t.identifier('require'), [
156
+ t.stringLiteral(fileToArtifact),
157
+ ]),
158
+ t.identifier('default'),
159
+ ),
160
+ );
161
+ }
126
162
  }
127
163
 
128
164
  module.exports = compileTag;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@isograph/babel-plugin",
3
3
  "description": "A Babel plugin for use with Isograph applications.",
4
4
  "homepage": "https://isograph.dev",
5
- "version": "0.3.0",
5
+ "version": "0.4.0",
6
6
  "keywords": [
7
7
  "graphql",
8
8
  "isograph",
@@ -16,9 +16,12 @@
16
16
  "directory": "libs/isograph-babel-plugin"
17
17
  },
18
18
  "scripts": {
19
- "tsc": "tsc"
19
+ "tsc": "tsc",
20
+ "tsc-force": "tsc --build --clean && tsc --build --force",
21
+ "test": "vitest run"
20
22
  },
21
23
  "dependencies": {
24
+ "@babel/helper-module-imports": "^7.0.0",
22
25
  "babel-plugin-macros": "^2.0.0",
23
26
  "cosmiconfig": "^5.0.5",
24
27
  "graphql": "15.3.0"
@@ -27,6 +30,7 @@
27
30
  "@babel/core": "^7.20.0",
28
31
  "@babel/types": "^7.25.6",
29
32
  "@types/babel__core": "^7.20.5",
33
+ "@types/babel__helper-module-imports": "^7.18.3",
30
34
  "@types/cosmiconfig": "^5.0.3",
31
35
  "prettier": "2.8.8",
32
36
  "prettier-plugin-hermes-parser": "0.16.0"
package/stub.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type cosmiconfig from 'cosmiconfig';
2
+
2
3
  declare module 'cosmiconfig' {
3
4
  export const loadJson: cosmiconfig.LoaderEntry;
4
5
  }
package/tsconfig.json CHANGED
@@ -3,8 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "allowJs": true,
5
5
  "checkJs": true,
6
- "noEmit": true,
7
- "lib": ["es2017", "DOM"]
6
+ "noEmit": true
8
7
  },
9
8
  "include": ["./**/*.ts", "./**/*.js"]
10
9
  }