Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.

Commit fde5992

Browse files
n1ru4lasterikxalloydependabot-preview[bot]sibelius
authored
feat: full esm support for code artifacts (transform require statements emitted from relay-compiler) (#269)
* wip: collect inline require statements and convert them to top level imports * add tests (#1) * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.3 [skip ci] * chore: bump typescript from 4.1.4 to 4.1.5 (#268) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](microsoft/TypeScript@v4.1.4...v4.1.5) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.4 [skip ci] * chore: bump @types/node from 14.14.25 to 14.14.28 Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.25 to 14.14.28. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot-preview[bot] <[email protected]> * fix: revert breaking changes introduced in 13.0.3 (#274) * Revert "refactor: use factory functions instead of deprecated functions" This reverts commit 812d17e. * Revert "refactor: remove deprecated typescript function calls in favor of the factory; replace @ts-ignores with proper code (microsoft/TypeScript#40263 (comment))" This reverts commit 55c58be. * Revert "refactor: some of the statements are redundant. It is not necessary to write to write only properties." This reverts commit 2cbd1de. * Revert "refactor: address all the typescript deprecations by using the factory" This reverts commit 72e0b4a. * chore: run CI github action on pull request * fix: typo * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.5 [skip ci] * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.6 [skip ci] * feat: include .d.ts types in release (#309) * feat: drop typescript 3 and Node.js 10 support (#275) * refactor: address all the typescript deprecations by using the factory * refactor: some of the statements are redundant. It is not necessary to write to write only properties. * refactor: remove deprecated typescript function calls in favor of the factory; replace @ts-ignores with proper code (microsoft/TypeScript#40263 (comment)) * refactor: use factory functions instead of deprecated functions * feat: bump peerDependencies version * dps: upgrade to latest typescript version * chore: replace rm with rimraf for cross platform support * chore: replace fixture tests with inline snapshot tests * docs: add notice about minimum TypeScript version. * chore: drop node 10 support * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.7 [skip ci] * Update CHANGELOG.md [skip ci] * Bump version to: 13.0.8 [skip ci] * feat: replace require calls by static (top level) or dynamic imports based on ts compiler options * feat: add tests for require call replacement logic * chore: add comment * fix: parse contents of tsconfig file into compiler options Converts strings to enum values etc. * fix: remove support for dynamic imports * fix: import default Co-authored-by: Eloy Durn <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Laurin Quast <[email protected]> Co-authored-by: Sibelius Seraphini <[email protected]> Co-authored-by: Tim Griesser <[email protected]> Co-authored-by: Erik Müller <[email protected]> * please the holy linter * chore(docs): mention es2015 module syntax of generated code * chore: remove duplicate import Co-authored-by: Erik Müller <[email protected]> Co-authored-by: Eloy Durn <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Sibelius Seraphini <[email protected]> Co-authored-by: Tim Griesser <[email protected]> Co-authored-by: Erik Müller <[email protected]> Co-authored-by: Erik Müller <[email protected]>
1 parent 76d6e70 commit fde5992

9 files changed

+262
-85
lines changed

README.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ to instruct `babel-plugin-relay` to use in your `.babelrc`:
4242

4343
### TypeScript
4444

45-
Also be sure to configure the TypeScript compiler to transpile to `es2015`
46-
modules and leave transpilation to `commonjs` modules up to Babel with the
47-
following `tsconfig.json` settings:
45+
Also be sure to configure the TypeScript compiler to transpile to `ES2015`
46+
modules (or higher) and leave transpilation to `CommonJS` modules (if required)
47+
up to Babel with the following `tsconfig.json` settings:
4848

49-
```json
49+
```json5
5050
{
5151
"compilerOptions": {
52-
"target": "es2015",
53-
"module": "es2015"
52+
"module": "ES2015", // ES2015 or higher
53+
"target": "ES2020" // best use the highest target setting compatible with your Babel setup
5454
}
5555
}
5656
```
@@ -68,13 +68,13 @@ react_relay_1.createFragmentContainer(
6868
);
6969
```
7070

71-
and this makes it impossible for `babel-plugin-relay` to find the locations
71+
which makes it impossible for `babel-plugin-relay` to find the locations
7272
where the `graphql` function is being used.
7373

74-
Note that this does mean you need to configure Babel to transform the ES module
75-
`import` and `export` statements, by using the
76-
[`babel-plugin-transform-es2015-modules-commonjs`](https://babeljs.io/docs/plugins/transform-es2015-modules-commonjs/)
77-
transform plugin, if you’re not already.
74+
*The generated code uses ES2015 module syntax if `module` is set to ES2015 or
75+
higher in your `tsconfig.json`. Note that the `eagerESModules` option from
76+
`relay-compiler` has no effect on the generated code if `module` is ES2015 or
77+
higher.*
7878

7979
## Problems
8080

src/FindGraphQLTags.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as util from "util";
33

44
import {
55
GraphQLTag,
6-
GraphQLTagFinder
6+
GraphQLTagFinder,
77
} from "relay-compiler/lib/language/RelayLanguagePluginInterface";
88

99
function isCreateContainerFunction(
@@ -65,7 +65,7 @@ function visit(node: ts.Node, addGraphQLTag: (tag: GraphQLTag) => void): void {
6565
break;
6666
}
6767
if (ts.isObjectLiteralExpression(fragmentSpec)) {
68-
fragmentSpec.properties.forEach(prop => {
68+
fragmentSpec.properties.forEach((prop) => {
6969
invariant(
7070
ts.isPropertyAssignment(prop) &&
7171
prop.questionToken == null &&
@@ -90,7 +90,7 @@ function visit(node: ts.Node, addGraphQLTag: (tag: GraphQLTag) => void): void {
9090
addGraphQLTag({
9191
keyName: (propAssignment.name as ts.Identifier).text,
9292
template: getGraphQLText(taggedTemplate),
93-
sourceLocationOffset: getSourceLocationOffset(taggedTemplate)
93+
sourceLocationOffset: getSourceLocationOffset(taggedTemplate),
9494
});
9595
});
9696
} else {
@@ -111,7 +111,7 @@ function visit(node: ts.Node, addGraphQLTag: (tag: GraphQLTag) => void): void {
111111
addGraphQLTag({
112112
keyName: null,
113113
template: getGraphQLText(taggedTemplate),
114-
sourceLocationOffset: getSourceLocationOffset(taggedTemplate)
114+
sourceLocationOffset: getSourceLocationOffset(taggedTemplate),
115115
});
116116
}
117117
// Visit remaining arguments
@@ -130,7 +130,7 @@ function visit(node: ts.Node, addGraphQLTag: (tag: GraphQLTag) => void): void {
130130
addGraphQLTag({
131131
keyName: null,
132132
template: getGraphQLText(taggedTemplate),
133-
sourceLocationOffset: getSourceLocationOffset(taggedTemplate)
133+
sourceLocationOffset: getSourceLocationOffset(taggedTemplate),
134134
});
135135
}
136136
}
@@ -167,7 +167,7 @@ function getSourceLocationOffset(quasi: ts.TaggedTemplateExpression) {
167167
const loc = quasi.getSourceFile().getLineAndCharacterOfPosition(pos);
168168
return {
169169
line: loc.line + 1,
170-
column: loc.character + 1
170+
column: loc.character + 1,
171171
};
172172
}
173173

@@ -180,6 +180,6 @@ function invariant(condition: boolean, msg: string, ...args: any[]) {
180180
export const find: GraphQLTagFinder = (text, filePath) => {
181181
const result: GraphQLTag[] = [];
182182
const ast = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true);
183-
visit(ast, tag => result.push(tag));
183+
visit(ast, (tag) => result.push(tag));
184184
return result;
185185
};

src/formatGeneratedModule.ts

+75-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,63 @@ import { FormatModule } from "relay-compiler";
22
import * as ts from "typescript";
33
import addAnyTypeCast from "./addAnyTypeCast";
44

5+
const createRequireRegex = () => /require\('(.*)'\)/g;
6+
7+
function getModuleName(path: string) {
8+
const [moduleName] = path.replace("./", "").split(".");
9+
return moduleName;
10+
}
11+
12+
// collects all require calls and converts them top-level imports
13+
const requireToImport = (content: string): string => {
14+
const requireRegex = createRequireRegex();
15+
16+
// collect all require paths (unique)
17+
const requirePaths = new Set<string>();
18+
while (true) {
19+
const res = requireRegex.exec(content);
20+
if (res === null) {
21+
break;
22+
}
23+
requirePaths.add(res[1]);
24+
}
25+
// replace all require paths
26+
Array.from(requirePaths).forEach((requirePath) => {
27+
content = content.replace(
28+
`require('${requirePath}')`,
29+
getModuleName(requirePath)
30+
);
31+
});
32+
// create top-level imports
33+
const topLevelImports = Array.from(requirePaths)
34+
.sort()
35+
.map(
36+
(requirePath) =>
37+
`import ${getModuleName(requirePath)} from "${requirePath.replace(
38+
".ts",
39+
""
40+
)}";`
41+
);
42+
// add top-level imports
43+
content = `${topLevelImports.join("\n")}
44+
${content}`;
45+
return content;
46+
};
47+
48+
type FormatContentOptions = {
49+
replaceRequire: boolean;
50+
};
51+
52+
function formatContent(
53+
rawContent: string,
54+
options: FormatContentOptions
55+
): string {
56+
if (!options.replaceRequire) {
57+
return rawContent;
58+
}
59+
return requireToImport(rawContent);
60+
}
61+
562
export const formatterFactory = (
663
compilerOptions: ts.CompilerOptions = {}
764
): FormatModule => ({
@@ -11,27 +68,35 @@ export const formatterFactory = (
1168
concreteText,
1269
typeText,
1370
hash,
14-
sourceHash
71+
sourceHash,
1572
}) => {
73+
const { noImplicitAny, module = -1 } = compilerOptions;
74+
1675
const documentTypeImport = documentType
1776
? `import { ${documentType} } from "relay-runtime";`
1877
: "";
1978
const docTextComment = docText ? "\n/*\n" + docText.trim() + "\n*/\n" : "";
20-
let nodeStatement = `const node: ${documentType ||
21-
"never"} = ${concreteText};`;
22-
if (compilerOptions.noImplicitAny) {
79+
let nodeStatement = `const node: ${
80+
documentType || "never"
81+
} = ${concreteText};`;
82+
if (noImplicitAny) {
2383
nodeStatement = addAnyTypeCast(nodeStatement).trim();
2484
}
25-
return `/* tslint:disable */
26-
/* eslint-disable */
27-
// @ts-nocheck
28-
${hash ? `/* ${hash} */\n` : ""}
29-
${documentTypeImport}
30-
${typeText || ""}
85+
const rawContent = `${typeText || ""}
3186
3287
${docTextComment}
3388
${nodeStatement}
3489
(node as any).hash = '${sourceHash}';
3590
export default node;
3691
`;
92+
93+
const content = `/* tslint:disable */
94+
/* eslint-disable */
95+
// @ts-nocheck
96+
${hash ? `/* ${hash} */\n` : ""}
97+
${documentTypeImport}
98+
${formatContent(rawContent, {
99+
replaceRequire: module >= ts.ModuleKind.ES2015,
100+
})}`;
101+
return content;
37102
};

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export default function plugin(): PluginInterface {
1010
outputExtension: "ts",
1111
findGraphQLTags: find,
1212
formatModule: formatterFactory(loadCompilerOptions()),
13-
typeGenerator: TypeScriptGenerator
13+
typeGenerator: TypeScriptGenerator,
1414
};
1515
}

src/loadCompilerOptions.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ export const loadCompilerOptions = (): ts.CompilerOptions => {
55
if (!configFileName) {
66
return {};
77
}
8-
const result = ts.readConfigFile(configFileName, ts.sys.readFile);
9-
if (result.error) {
8+
const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
9+
if (configFile.error) {
1010
return {};
1111
}
12-
return result.config.compilerOptions;
12+
// parse config file contents (to convert strings to enum values etc.)
13+
const parsedConfig = ts.parseJsonConfigFileContent(
14+
configFile.config,
15+
ts.sys,
16+
"./"
17+
);
18+
if (parsedConfig.errors.length > 0) {
19+
return {};
20+
}
21+
return parsedConfig.options;
1322
};

test/FindGraphQLTags-test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ describe("FindGraphQLTags", () => {
2626
name
2727
}
2828
`,
29-
sourceLocationOffset: { line: 5, column: 16 }
30-
}
29+
sourceLocationOffset: { line: 5, column: 16 },
30+
},
3131
]);
3232
});
3333

@@ -38,8 +38,8 @@ describe("FindGraphQLTags", () => {
3838
{
3939
keyName: null,
4040
template: `fragment TestModule_artist on Artist {name}`,
41-
sourceLocationOffset: { line: 1, column: 8 }
42-
}
41+
sourceLocationOffset: { line: 1, column: 8 },
42+
},
4343
]);
4444
});
4545
// TODO: Cover all cases where tags are extracted

0 commit comments

Comments
 (0)