mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[compiler] Add support for commonjs (#34589)
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.
This commit is contained in:
@@ -240,7 +240,7 @@ export function addImportsToProgram(
|
||||
programContext: ProgramContext,
|
||||
): void {
|
||||
const existingImports = getExistingImports(path);
|
||||
const stmts: Array<t.ImportDeclaration> = [];
|
||||
const stmts: Array<t.ImportDeclaration | t.VariableDeclaration> = [];
|
||||
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
);
|
||||
@@ -303,9 +303,29 @@ export function addImportsToProgram(
|
||||
if (maybeExistingImports != null) {
|
||||
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
|
||||
} else {
|
||||
stmts.push(
|
||||
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
|
||||
);
|
||||
if (path.node.sourceType === 'module') {
|
||||
stmts.push(
|
||||
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
|
||||
);
|
||||
} else {
|
||||
stmts.push(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.objectPattern(
|
||||
sortedImport.map(specifier => {
|
||||
return t.objectProperty(
|
||||
t.identifier(specifier.imported),
|
||||
t.identifier(specifier.name),
|
||||
);
|
||||
}),
|
||||
),
|
||||
t.callExpression(t.identifier('require'), [
|
||||
t.stringLiteral(moduleName),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
path.unshiftContainer('body', stmts);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @script
|
||||
const React = require('react');
|
||||
|
||||
function Component(props) {
|
||||
return <div>{props.name}</div>;
|
||||
}
|
||||
|
||||
// To work with snap evaluator
|
||||
exports = {
|
||||
FIXTURE_ENTRYPOINT: {
|
||||
fn: Component,
|
||||
params: [{name: 'React Compiler'}],
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
const { c: _c } = require("react/compiler-runtime"); // @script
|
||||
const React = require("react");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.name) {
|
||||
t0 = <div>{props.name}</div>;
|
||||
$[0] = props.name;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
// To work with snap evaluator
|
||||
exports = {
|
||||
FIXTURE_ENTRYPOINT: {
|
||||
fn: Component,
|
||||
params: [{ name: "React Compiler" }],
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>React Compiler</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
// @script
|
||||
const React = require('react');
|
||||
|
||||
function Component(props) {
|
||||
return <div>{props.name}</div>;
|
||||
}
|
||||
|
||||
// To work with snap evaluator
|
||||
exports = {
|
||||
FIXTURE_ENTRYPOINT: {
|
||||
fn: Component,
|
||||
params: [{name: 'React Compiler'}],
|
||||
},
|
||||
};
|
||||
@@ -31,10 +31,15 @@ import prettier from 'prettier';
|
||||
import SproutTodoFilter from './SproutTodoFilter';
|
||||
import {isExpectError} from './fixture-utils';
|
||||
import {makeSharedRuntimeTypeProvider} from './sprout/shared-runtime-type-provider';
|
||||
|
||||
export function parseLanguage(source: string): 'flow' | 'typescript' {
|
||||
return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript';
|
||||
}
|
||||
|
||||
export function parseSourceType(source: string): 'script' | 'module' {
|
||||
return source.indexOf('@script') !== -1 ? 'script' : 'module';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse react compiler plugin + environment options from test fixture. Note
|
||||
* that although this primarily uses `Environment:parseConfigPragma`, it also
|
||||
@@ -98,6 +103,7 @@ export function parseInput(
|
||||
input: string,
|
||||
filename: string,
|
||||
language: 'flow' | 'typescript',
|
||||
sourceType: 'module' | 'script',
|
||||
): BabelCore.types.File {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
if (language === 'flow') {
|
||||
@@ -105,14 +111,14 @@ export function parseInput(
|
||||
babel: true,
|
||||
flow: 'all',
|
||||
sourceFilename: filename,
|
||||
sourceType: 'module',
|
||||
sourceType,
|
||||
enableExperimentalComponentSyntax: true,
|
||||
});
|
||||
} else {
|
||||
return BabelParser.parse(input, {
|
||||
sourceFilename: filename,
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
sourceType,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -221,11 +227,12 @@ export async function transformFixtureInput(
|
||||
const firstLine = input.substring(0, input.indexOf('\n'));
|
||||
|
||||
const language = parseLanguage(firstLine);
|
||||
const sourceType = parseSourceType(firstLine);
|
||||
// Preserve file extension as it determines typescript's babel transform
|
||||
// mode (e.g. stripping types, parsing rules for brackets)
|
||||
const filename =
|
||||
path.basename(fixturePath) + (language === 'typescript' ? '.ts' : '');
|
||||
const inputAst = parseInput(input, filename, language);
|
||||
const inputAst = parseInput(input, filename, language, sourceType);
|
||||
// Give babel transforms an absolute path as relative paths get prefixed
|
||||
// with `cwd`, which is different across machines
|
||||
const virtualFilepath = '/' + filename;
|
||||
|
||||
@@ -298,7 +298,10 @@ export function doEval(source: string): EvaluatorResult {
|
||||
return {
|
||||
kind: 'UnexpectedError',
|
||||
value:
|
||||
'Unexpected error during eval, possible syntax error?\n' + e.message,
|
||||
'Unexpected error during eval, possible syntax error?\n' +
|
||||
e.message +
|
||||
'\n\nsource:\n' +
|
||||
source,
|
||||
logs,
|
||||
};
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user