Skip to content

Commit 9ad4418

Browse files
committed
[compiler] Add support for commonjs
We previously always generated import statements for any modules that had to be required, notably the `import {c} from 'react/compiler-runtime'` for the memo cache function. However, this obviously doesn't work when the source is using commonjs. Now we check the sourceType of the module and generate require() statements if the source type is 'script'. I initially explored using https://babeljs.io/docs/babel-helper-module-imports, but the API design was unfortunately not flexible enough for our use-case. Specifically, our pipeline is as follows: * Compile individual functions. Generate candidate imports, pre-allocating the local names for those imports. * If the file is compiled successfully, actually add the imports to the program. Ie we need to pre-allocate identifier names for the imports before we add them to the program — but that isn't supported by babel-helper-module-imports. So instead we generate our own require() calls if the sourceType is script.
1 parent 58d1791 commit 9ad4418

File tree

5 files changed

+104
-8
lines changed

5 files changed

+104
-8
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export function addImportsToProgram(
240240
programContext: ProgramContext,
241241
): void {
242242
const existingImports = getExistingImports(path);
243-
const stmts: Array<t.ImportDeclaration> = [];
243+
const stmts: Array<t.ImportDeclaration | t.VariableDeclaration> = [];
244244
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
245245
a.localeCompare(b),
246246
);
@@ -303,9 +303,29 @@ export function addImportsToProgram(
303303
if (maybeExistingImports != null) {
304304
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
305305
} else {
306-
stmts.push(
307-
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
308-
);
306+
if (path.node.sourceType === 'module') {
307+
stmts.push(
308+
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
309+
);
310+
} else {
311+
stmts.push(
312+
t.variableDeclaration('const', [
313+
t.variableDeclarator(
314+
t.objectPattern(
315+
sortedImport.map(specifier => {
316+
return t.objectProperty(
317+
t.identifier(specifier.imported),
318+
t.identifier(specifier.name),
319+
);
320+
}),
321+
),
322+
t.callExpression(t.identifier('require'), [
323+
t.stringLiteral(moduleName),
324+
]),
325+
),
326+
]),
327+
);
328+
}
309329
}
310330
}
311331
path.unshiftContainer('body', stmts);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @script
6+
const React = require('react');
7+
8+
function Component(props) {
9+
return <div>{props.name}</div>;
10+
}
11+
12+
// To work with snap evaluator
13+
exports = {
14+
FIXTURE_ENTRYPOINT: {
15+
fn: Component,
16+
params: [{name: 'React Compiler'}],
17+
},
18+
};
19+
20+
```
21+
22+
## Code
23+
24+
```javascript
25+
const { c: _c } = require("react/compiler-runtime"); // @script
26+
const React = require("react");
27+
28+
function Component(props) {
29+
const $ = _c(2);
30+
let t0;
31+
if ($[0] !== props.name) {
32+
t0 = <div>{props.name}</div>;
33+
$[0] = props.name;
34+
$[1] = t0;
35+
} else {
36+
t0 = $[1];
37+
}
38+
return t0;
39+
}
40+
41+
// To work with snap evaluator
42+
exports = {
43+
FIXTURE_ENTRYPOINT: {
44+
fn: Component,
45+
params: [{ name: "React Compiler" }],
46+
},
47+
};
48+
49+
```
50+
51+
### Eval output
52+
(kind: ok) <div>React Compiler</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @script
2+
const React = require('react');
3+
4+
function Component(props) {
5+
return <div>{props.name}</div>;
6+
}
7+
8+
// To work with snap evaluator
9+
exports = {
10+
FIXTURE_ENTRYPOINT: {
11+
fn: Component,
12+
params: [{name: 'React Compiler'}],
13+
},
14+
};

compiler/packages/snap/src/compiler.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ import prettier from 'prettier';
3131
import SproutTodoFilter from './SproutTodoFilter';
3232
import {isExpectError} from './fixture-utils';
3333
import {makeSharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider';
34+
3435
export function parseLanguage(source: string): 'flow' | 'typescript' {
3536
return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript';
3637
}
3738

39+
export function parseSourceType(source: string): 'script' | 'module' {
40+
return source.indexOf('@script') !== -1 ? 'script' : 'module';
41+
}
42+
3843
/**
3944
* Parse react compiler plugin + environment options from test fixture. Note
4045
* that although this primarily uses `Environment:parseConfigPragma`, it also
@@ -98,21 +103,22 @@ export function parseInput(
98103
input: string,
99104
filename: string,
100105
language: 'flow' | 'typescript',
106+
sourceType: 'module' | 'script',
101107
): BabelCore.types.File {
102108
// Extract the first line to quickly check for custom test directives
103109
if (language === 'flow') {
104110
return HermesParser.parse(input, {
105111
babel: true,
106112
flow: 'all',
107113
sourceFilename: filename,
108-
sourceType: 'module',
114+
sourceType,
109115
enableExperimentalComponentSyntax: true,
110116
});
111117
} else {
112118
return BabelParser.parse(input, {
113119
sourceFilename: filename,
114120
plugins: ['typescript', 'jsx'],
115-
sourceType: 'module',
121+
sourceType,
116122
});
117123
}
118124
}
@@ -221,11 +227,12 @@ export async function transformFixtureInput(
221227
const firstLine = input.substring(0, input.indexOf('\n'));
222228

223229
const language = parseLanguage(firstLine);
230+
const sourceType = parseSourceType(firstLine);
224231
// Preserve file extension as it determines typescript's babel transform
225232
// mode (e.g. stripping types, parsing rules for brackets)
226233
const filename =
227234
path.basename(fixturePath) + (language === 'typescript' ? '.ts' : '');
228-
const inputAst = parseInput(input, filename, language);
235+
const inputAst = parseInput(input, filename, language, sourceType);
229236
// Give babel transforms an absolute path as relative paths get prefixed
230237
// with `cwd`, which is different across machines
231238
const virtualFilepath = '/' + filename;

compiler/packages/snap/src/sprout/evaluator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,10 @@ export function doEval(source: string): EvaluatorResult {
298298
return {
299299
kind: 'UnexpectedError',
300300
value:
301-
'Unexpected error during eval, possible syntax error?\n' + e.message,
301+
'Unexpected error during eval, possible syntax error?\n' +
302+
e.message +
303+
'\n\nsource:\n' +
304+
source,
302305
logs,
303306
};
304307
} finally {

0 commit comments

Comments
 (0)