Add view config babel plugin

Summary:
This diff adds a babel plugin for the generated view configs which will inline them in the file instead of needing to check the view configs in (fb only)

The way it works is:
- babel reads the code
- looks for type alias `CodegenNativeComponent` in `*NativeComponet.js` files
- run the flow parser on the file source to create a schema
- run the schema into codegen to get the view config source code
- inject the generated source code back into the NativeComponent.js file
- remove the original export
- profit

After this diff we will remove the `js1 build viewconfigs` command and the checked-in NativeViewConfig.js files

Note: since this plugin is not published to open source, for now OSS will continue using the `requireNativeComponent` function

Reviewed By: cpojer

Differential Revision: D15516062

fbshipit-source-id: a8efb077773e04fd9753a7036682eeaae9175e09
This commit is contained in:
Rick Hanlon
2019-06-07 12:31:36 -07:00
committed by Facebook Github Bot
parent 886fb501bd
commit efec97f2be
14 changed files with 298 additions and 23 deletions
@@ -10,17 +10,13 @@
'use strict';
import type {
BubblingEvent,
WithDefault,
CodegenNativeComponent,
} from '../../Types/CodegenTypes';
const requireNativeComponent = require('../../ReactNative/requireNativeComponent');
import type {BubblingEvent, WithDefault} from '../../Types/CodegenTypes';
import type {NativeComponent} from '../../Renderer/shims/ReactNative';
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
import type {ViewProps} from '../View/ViewPropTypes';
const requireNativeComponent = require('../../ReactNative/requireNativeComponent');
type NativeProps = $ReadOnly<{|
...ViewProps,
@@ -48,13 +44,10 @@ type NativeProps = $ReadOnly<{|
refreshing: WithDefault<boolean, false>,
|}>;
type PullToRefreshViewType = CodegenNativeComponent<
'PullToRefreshView',
NativeProps,
>;
// TODO: Switch this over to require('./PullToRefreshNativeViewConfig')
// TODO: Switch this over to CodegenNativeComponent
// once the native components are renamed in paper and fabric
type PullToRefreshViewType = Class<NativeComponent<NativeProps>>;
module.exports = ((requireNativeComponent(
'RCTRefreshControl',
): any): PullToRefreshViewType);
@@ -0,0 +1,23 @@
load("@fbsource//tools/build_defs/third_party:yarn_defs.bzl", "yarn_workspace")
yarn_workspace(
name = "yarn-workspace",
srcs = glob(
["**/*.js"],
exclude = [
"**/__fixtures__/**",
"**/__flowtests__/**",
"**/__mocks__/**",
"**/__server_snapshot_tests__/**",
"**/__tests__/**",
"**/node_modules/**",
"**/node_modules/.bin/**",
"**/.*",
"**/.*/**",
"**/.*/.*",
"**/*.xcodeproj/**",
"**/*.xcworkspace/**",
],
),
visibility = ["PUBLIC"],
)
@@ -0,0 +1,54 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const NOT_A_NATIVE_COMPONENT = `
const requireNativeComponent = require('requireNativeComponent');
export default 'Not a view config'
`;
const FULL_NATIVE_COMPONENT = `
const requireNativeComponent = require('requireNativeComponent');
import type {
BubblingEvent,
DirectEvent,
WithDefault,
CodegenNativeComponent,
} from 'CodegenFlowtypes';
import type {ViewProps} from 'ViewPropTypes';
type ModuleProps = $ReadOnly<{|
...ViewProps,
// Props
boolean_default_true_optional_both?: ?WithDefault<boolean, true>,
// Events
onDirectEventDefinedInlineNull: (event: DirectEvent<null>) => void,
onBubblingEventDefinedInlineNull: (event: BubblingEvent<null>) => void,
|}>;
type Options = {
interfaceOnly: true,
isDeprecatedPaperComponentNameRCT: true,
};
type ModuleType = CodegenNativeComponent<'Module', ModuleProps, Options>;
module.exports = ((requireNativeComponent('RCTModule'): any): ModuleType);
`;
module.exports = {
'NotANativeComponent.js': NOT_A_NATIVE_COMPONENT,
'FullNativeComponent.js': FULL_NATIVE_COMPONENT,
};
@@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Babel plugin inline view configs can inline config for FullNativeComponent.js 1`] = `
"const requireNativeComponent = require('requireNativeComponent');
import type { BubblingEvent, DirectEvent, WithDefault, CodegenNativeComponent } from 'CodegenFlowtypes';
import type { ViewProps } from 'ViewPropTypes';
type ModuleProps = $ReadOnly<{| ...ViewProps,
// Props
boolean_default_true_optional_both?: ?WithDefault<boolean, true>,
// Events
onDirectEventDefinedInlineNull: (event: DirectEvent<null>) => void,
onBubblingEventDefinedInlineNull: (event: BubblingEvent<null>) => void,
|}>;
type Options = {
interfaceOnly: true,
isDeprecatedPaperComponentNameRCT: true,
};
type ModuleType = CodegenNativeComponent<'Module', ModuleProps, Options>;
const registerGeneratedViewConfig = require('registerGeneratedViewConfig');
const ModuleViewConfig = {
uiViewClassName: 'RCTModule',
bubblingEventTypes: {
topBubblingEventDefinedInlineNull: {
phasedRegistrationNames: {
captured: 'onBubblingEventDefinedInlineNullCapture',
bubbled: 'onBubblingEventDefinedInlineNull'
}
}
},
directEventTypes: {
topDirectEventDefinedInlineNull: {
registrationName: 'onDirectEventDefinedInlineNull'
}
},
validAttributes: {
boolean_default_true_optional_both: true,
onDirectEventDefinedInlineNull: true,
onBubblingEventDefinedInlineNull: true
}
};
registerGeneratedViewConfig('RCTModule', ModuleViewConfig);
module.exports = 'RCTModule'; // RCT prefix present for paper support"
`;
exports[`Babel plugin inline view configs can inline config for NotANativeComponent.js 1`] = `
"const requireNativeComponent = require('requireNativeComponent');
export default 'Not a view config';"
`;
@@ -0,0 +1,32 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+react_native
* @format
*/
'use strict';
const {transform: babelTransform} = require('@babel/core');
const fixtures = require('../__test_fixtures__/fixtures.js');
function transform(filename) {
return babelTransform(fixtures[filename], {
plugins: [require('@babel/plugin-syntax-flow'), require('../index')],
babelrc: false,
filename,
}).code;
}
describe('Babel plugin inline view configs', () => {
Object.keys(fixtures)
.sort()
.forEach(fixtureName => {
it(`can inline config for ${fixtureName}`, () => {
expect(transform(fixtureName)).toMatchSnapshot();
});
});
});
@@ -0,0 +1,59 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {parseString} = require('react-native-codegen/src/parsers/flow');
const RNCodegen = require('react-native-codegen/src/generators/RNCodegen');
const path = require('path');
function generateViewConfig(filename, code) {
const schema = parseString(code);
const libraryName = path
.basename(filename)
.replace(/NativeComponent\.js$/, '');
return RNCodegen.generateViewConfig({
schema,
libraryName,
});
}
module.exports = function(context) {
return {
pre(state) {
this.code = state.code;
this.filename = state.opts.filename;
this.inserted = false;
},
visitor: {
TypeAlias(nodePath, state) {
if (
!this.inserted &&
nodePath.node.right &&
nodePath.node.right.type === 'GenericTypeAnnotation' &&
nodePath.node.right.id.name === 'CodegenNativeComponent'
) {
const code = generateViewConfig(this.filename, this.code);
// Remove the original export
nodePath.parentPath.traverse({
MemberExpression(exportPath) {
if (exportPath.node.property.name === 'exports') {
exportPath.parentPath.remove();
}
},
});
nodePath.insertAfter(context.parse(code).program.body);
this.inserted = true;
}
},
},
};
};
@@ -0,0 +1,16 @@
{
"version": "0.0.0",
"name": "babel-plugin-inline-view-configs",
"description": "Babel plugin to inline view configs for React Native",
"repository": {
"type": "git",
"url": "git@github.com:facebook/react-native.git"
},
"dependencies": {
"react-native-codegen": "*"
},
"devDependencies": {
"@babel/core": "^7.0.0"
},
"license": "MIT"
}
+23
View File
@@ -2,6 +2,7 @@ load("@fbsource//tools/build_defs:default_platform_defs.bzl", "ANDROID", "APPLE"
load("@fbsource//tools/build_defs:fb_native_wrapper.bzl", "fb_native")
load("@fbsource//tools/build_defs:fb_xplat_cxx_binary.bzl", "fb_xplat_cxx_binary")
load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "rn_xplat_cxx_library")
load("@fbsource//tools/build_defs/third_party:yarn_defs.bzl", "yarn_workspace")
load("@fbsource//xplat/js/react-native-github/packages/react-native-codegen:DEFS.bzl", "rn_codegen_test")
fb_native.sh_binary(
@@ -173,3 +174,25 @@ rn_xplat_cxx_library(
":generated_components-TWO_COMPONENTS_SAME_FILE",
],
)
yarn_workspace(
name = "yarn-workspace",
srcs = glob(
["**/*.js"],
exclude = [
"**/__fixtures__/**",
"**/__flowtests__/**",
"**/__mocks__/**",
"**/__server_snapshot_tests__/**",
"**/__tests__/**",
"**/node_modules/**",
"**/node_modules/.bin/**",
"**/.*",
"**/.*/**",
"**/.*/.*",
"**/*.xcodeproj/**",
"**/*.xcworkspace/**",
],
),
visibility = ["PUBLIC"],
)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"version": "0.0.1",
"name": "react-native-codgen",
"name": "react-native-codegen",
"description": "⚛️ Code generation tools for React Native",
"repository": {
"type": "git",
+4 -1
View File
@@ -14,7 +14,10 @@ const FlowParser = require('../../parsers/flow');
function parseFiles(files: Array<string>) {
files.forEach(filename => {
console.log(filename, JSON.stringify(FlowParser.parse(filename), null, 2));
console.log(
filename,
JSON.stringify(FlowParser.parseFile(filename), null, 2),
);
});
}
@@ -33,7 +33,7 @@ function generateFilesWithResults(
return files.reduce((aggregated, filename) => {
const schema =
config.parser === 'flow'
? FlowParser.parse(filename)
? FlowParser.parseFile(filename)
: SchemaParser.parse(filename);
if (schema && schema.modules) {
const libraryName = path
@@ -118,4 +118,18 @@ module.exports = {
return writeMapToFiles(filesToUpdate, outputDirectory);
},
generateViewConfig({libraryName, schema}: Options): string {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};
@@ -22,7 +22,7 @@ describe('RN Codegen Flow Parser', () => {
.sort()
.forEach(fixtureName => {
it(`can generate fixture ${fixtureName}`, () => {
expect(FlowParser.parse(fixtureName)).toMatchSnapshot();
expect(FlowParser.parseFile(fixtureName)).toMatchSnapshot();
});
});
});
+11 -5
View File
@@ -71,8 +71,7 @@ function getPropProperties(propsTypeName, types) {
}
}
function parseFileAst(filename: string) {
const contents = fs.readFileSync(filename, 'utf8');
function processString(contents: string) {
const ast = flowParser.parse(contents);
const types = getTypes(ast);
@@ -96,10 +95,17 @@ function parseFileAst(filename: string) {
};
}
function parse(filename: string): ?SchemaType {
return buildSchema(parseFileAst(filename));
function parseFile(filename: string): ?SchemaType {
const contents = fs.readFileSync(filename, 'utf8');
return buildSchema(processString(contents));
}
function parseString(contents: string): ?SchemaType {
return buildSchema(processString(contents));
}
module.exports = {
parse,
parseFile,
parseString,
};