mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook Github Bot
parent
886fb501bd
commit
efec97f2be
@@ -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"
|
||||
}
|
||||
@@ -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,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
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user