@samitouri / QOS-React-2 / commits / 393924879f

[Fresh] Babel plugin now handles HOCs + add integration tests (#15724)

* Refactor component search to prepare for deeper traversals * Register HOCs with intermediate results * Register components that are used as JSX types * Add integration test skeleton The integration test combines testing runtime together with the Babel plugin. It's a bit harder to debug because multiple things can go wrong, but it helps us build confidence that specific scenarios work well. * Add HOC integration test and fix conflict with JSX transform * Infer usage from createElement too This helps us avoid dependency on the plugin order. * Remove outdated comments * Wrap tests in __DEV__ * Support export default hoc(...) for anonymous functions * Fix test indentation * Fix comment typo * Use named function for test as this case is more important

Dan Abramov committed May 25, 2019 at 00:45 UTC 393924879fbba20e43d9de76f9fb4f01c945cefa
5 files changed +683 -34
packages/react-fresh/src/ReactFreshBabelPlugin.js
+184 -33
@@ -32,25 +32,164 @@ export default function(babel) {
32 return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z';
33 }
34
35 - function isComponentish(node) {
35 + function findInnerComponents(inferredName, path, callback) {
36 + const node = path.node;
37 switch (node.type) {
37 - case 'FunctionDeclaration':
38 - return node.id !== null && isComponentishName(node.id.name);
39 - case 'VariableDeclarator':
40 - return (
41 - isComponentishName(node.id.name) &&
42 - node.init !== null &&
43 - (node.init.type === 'FunctionExpression' ||
44 - (node.init.type === 'ArrowFunctionExpression' &&
45 - node.init.body.type !== 'ArrowFunctionExpression'))
38 + case 'FunctionDeclaration': {
39 + // function Foo() {}
40 + // export function Foo() {}
41 + // export default function Foo() {}
42 + callback(inferredName, node.id, null);
43 + return true;
44 + }
45 + case 'ArrowFunctionExpression': {
46 + if (node.body.type === 'ArrowFunctionExpression') {
47 + return false;
48 + }
49 + // let Foo = () => {}
50 + // export default hoc1(hoc2(() => {}))
51 + callback(inferredName, node, path);
52 + return true;
53 + }
54 + case 'FunctionExpression': {
55 + // let Foo = function() {}
56 + // const Foo = hoc1(forwardRef(function renderFoo() {}))
57 + // export default memo(function() {})
58 + callback(inferredName, node, path);
59 + return true;
60 + }
61 + case 'CallExpression': {
62 + const argsPath = path.get('arguments');
63 + if (argsPath === undefined || argsPath.length === 0) {
64 + return false;
65 + }
66 + const calleePath = path.get('callee');
67 + switch (calleePath.node.type) {
68 + case 'MemberExpression':
69 + case 'Identifier': {
70 + const calleeSource = calleePath.getSource();
71 + const firstArgPath = argsPath[0];
72 + const innerName = inferredName + '$' + calleeSource;
73 + const foundInside = findInnerComponents(
74 + innerName,
75 + firstArgPath,
76 + callback,
77 + );
78 + if (!foundInside) {
79 + return false;
80 + }
81 + // const Foo = hoc1(hoc2(() => {}))
82 + // export default memo(React.forwardRef(function() {}))
83 + callback(inferredName, node, path);
84 + return true;
85 + }
86 + default: {
87 + return false;
88 + }
89 + }
90 + }
91 + case 'VariableDeclarator': {
92 + const init = node.init;
93 + if (init === null) {
94 + return false;
95 + }
96 + const name = node.id.name;
97 + if (!isComponentishName(name)) {
98 + return false;
99 + }
100 + if (init.type === 'Identifier' || init.type === 'MemberExpression') {
101 + return false;
102 + }
103 + const initPath = path.get('init');
104 + const foundInside = findInnerComponents(
105 + inferredName,
106 + initPath,
107 + callback,
108 );
47 - default:
48 - return false;
109 + if (foundInside) {
110 + return true;
111 + }
112 + // See if this identifier is used in JSX. Then it's a component.
113 + const binding = path.scope.getBinding(name);
114 + if (binding === undefined) {
115 + return;
116 + }
117 + let isLikelyUsedAsType = false;
118 + const referencePaths = binding.referencePaths;
119 + for (let i = 0; i < referencePaths.length; i++) {
120 + const ref = referencePaths[i];
121 + if (
122 + ref.node.type !== 'JSXIdentifier' &&
123 + ref.node.type !== 'Identifier'
124 + ) {
125 + continue;
126 + }
127 + const refParent = ref.parent;
128 + if (refParent.type === 'JSXOpeningElement') {
129 + isLikelyUsedAsType = true;
130 + } else if (refParent.type === 'CallExpression') {
131 + const callee = refParent.callee;
132 + let fnName;
133 + switch (callee.type) {
134 + case 'Identifier':
135 + fnName = callee.name;
136 + break;
137 + case 'MemberExpression':
138 + fnName = callee.property.name;
139 + break;
140 + }
141 + switch (fnName) {
142 + case 'createElement':
143 + case 'jsx':
144 + case 'jsxDEV':
145 + case 'jsxs':
146 + isLikelyUsedAsType = true;
147 + break;
148 + }
149 + }
150 + if (isLikelyUsedAsType) {
151 + // const X = ... + later <X />
152 + callback(inferredName, init, initPath);
153 + return true;
154 + }
155 + }
156 + }
157 }
158 + return false;
159 }
160
161 return {
162 visitor: {
163 + ExportDefaultDeclaration(path) {
164 + const node = path.node;
165 + const decl = node.declaration;
166 + const declPath = path.get('declaration');
167 + if (decl.type !== 'CallExpression') {
168 + // For now, we only support possible HOC calls here.
169 + // Named function declarations are handled in FunctionDeclaration.
170 + // Anonymous direct exports like export default function() {}
171 + // are currently ignored.
172 + return;
173 + }
174 + // This code path handles nested cases like:
175 + // export default memo(() => {})
176 + // In those cases it is more plausible people will omit names
177 + // so they're worth handling despite possible false positives.
178 + // More importantly, it handles the named case:
179 + // export default memo(function Named() {})
180 + const inferredName = '%default%';
181 + const programPath = path.parentPath;
182 + findInnerComponents(
183 + inferredName,
184 + declPath,
185 + (persistentID, targetExpr, targetPath) => {
186 + const handle = createRegistration(programPath, persistentID);
187 + targetPath.replaceWith(
188 + t.assignmentExpression('=', handle, targetExpr),
189 + );
190 + },
191 + );
192 + },
193 FunctionDeclaration(path) {
194 let programPath;
195 let insertAfterPath;
@@ -60,6 +199,9 @@ export default function(babel) {
199 programPath = path.parentPath;
200 break;
201 case 'ExportNamedDeclaration':
202 + insertAfterPath = path.parentPath;
203 + programPath = insertAfterPath.parentPath;
204 + break;
205 case 'ExportDefaultDeclaration':
206 insertAfterPath = path.parentPath;
207 programPath = insertAfterPath.parentPath;
@@ -67,17 +209,25 @@ export default function(babel) {
209 default:
210 return;
211 }
70 - const maybeComponent = path.node;
71 - if (!isComponentish(maybeComponent)) {
212 + const id = path.node.id;
213 + if (id === null) {
214 + // We don't currently handle anonymous default exports.
215 return;
216 }
74 - const functionName = path.node.id.name;
75 - const handle = createRegistration(programPath, functionName);
76 - insertAfterPath.insertAfter(
77 - t.expressionStatement(
78 - t.assignmentExpression('=', handle, path.node.id),
79 - ),
80 - );
217 + const inferredName = id.name;
218 + if (!isComponentishName(inferredName)) {
219 + return;
220 + }
221 + // export function Named() {}
222 + // function Named() {}
223 + findInnerComponents(inferredName, path, (persistentID, targetExpr) => {
224 + const handle = createRegistration(programPath, persistentID);
225 + insertAfterPath.insertAfter(
226 + t.expressionStatement(
227 + t.assignmentExpression('=', handle, targetExpr),
228 + ),
229 + );
230 + });
231 },
232 VariableDeclaration(path) {
233 let programPath;
@@ -92,20 +242,21 @@ export default function(babel) {
242 default:
243 return;
244 }
95 - const declPath = path.get('declarations');
96 - if (declPath.length !== 1) {
245 + const declPaths = path.get('declarations');
246 + if (declPaths.length !== 1) {
247 return;
248 }
99 - const firstDeclPath = declPath[0];
100 - const maybeComponent = firstDeclPath.node;
101 - if (!isComponentish(maybeComponent)) {
102 - return;
103 - }
104 - const functionName = maybeComponent.id.name;
105 - const initPath = firstDeclPath.get('init');
106 - const handle = createRegistration(programPath, functionName);
107 - initPath.replaceWith(
108 - t.assignmentExpression('=', handle, initPath.node),
249 + const declPath = declPaths[0];
250 + const inferredName = declPath.node.id.name;
251 + findInnerComponents(
252 + inferredName,
253 + declPath,
254 + (persistentID, targetExpr, targetPath) => {
255 + const handle = createRegistration(programPath, persistentID);
256 + targetPath.replaceWith(
257 + t.assignmentExpression('=', handle, targetExpr),
258 + );
259 + },
260 );
261 },
262 Program: {
packages/react-fresh/src/__tests__/ReactFresh-test.js
+1 -1
@@ -16,10 +16,10 @@ let ReactDOM;
16 let ReactFreshRuntime;
17 let Scheduler;
18 let act;
19 -let lastRoot;
19
20 describe('ReactFresh', () => {
21 let container;
22 + let lastRoot;
23 let scheduleHotUpdate;
24
25 beforeEach(() => {
packages/react-fresh/src/__tests__/ReactFreshBabelPlugin-test.js
+113
@@ -186,4 +186,117 @@ describe('ReactFreshBabelPlugin', () => {
186 `),
187 ).toMatchSnapshot();
188 });
189 +
190 + it('registers likely HOCs with inline functions', () => {
191 + expect(
192 + transform(`
193 + const A = forwardRef(function() {
194 + return <h1>Foo</h1>;
195 + });
196 + const B = memo(React.forwardRef(() => {
197 + return <h1>Foo</h1>;
198 + }));
199 + export default React.memo(forwardRef((props, ref) => {
200 + return <h1>Foo</h1>;
201 + }));
202 + `),
203 + ).toMatchSnapshot();
204 + expect(
205 + transform(`
206 + export default React.memo(forwardRef(function (props, ref) {
207 + return <h1>Foo</h1>;
208 + }));
209 + `),
210 + ).toMatchSnapshot();
211 + expect(
212 + transform(`
213 + export default React.memo(forwardRef(function Named(props, ref) {
214 + return <h1>Foo</h1>;
215 + }));
216 + `),
217 + ).toMatchSnapshot();
218 + });
219 +
220 + it('ignores higher-order functions that are not HOCs', () => {
221 + expect(
222 + transform(`
223 + const throttledAlert = throttle(function() {
224 + alert('Hi');
225 + });
226 + const TooComplex = (function() { return hello })(() => {});
227 + if (cond) {
228 + const Foo = thing(() => {});
229 + }
230 + `),
231 + ).toMatchSnapshot();
232 + });
233 +
234 + it('registers identifiers used in JSX at definition site', () => {
235 + // When in doubt, register variables that were used in JSX.
236 + // Foo, Header, and B get registered.
237 + // A doesn't get registered because it's not declared locally.
238 + // Alias doesn't get registered because its definition is just an identifier.
239 + expect(
240 + transform(`
241 + import A from './A';
242 + import Store from './Store';
243 +
244 + Store.subscribe();
245 +
246 + const Header = styled.div\`color: red\`
247 + const Factory = funny.factory\`\`;
248 +
249 + let Alias1 = A;
250 + let Alias2 = A.Foo;
251 + const Dict = {};
252 +
253 + function Foo() {
254 + return (
255 + <div><A /><B /><Alias1 /><Alias2 /><Header /><Dict.X /></div>
256 + );
257 + }
258 +
259 + const B = hoc(A);
260 + const NotAComponent = wow(A);
261 + `),
262 + ).toMatchSnapshot();
263 + });
264 +
265 + it('registers identifiers used in React.createElement at definition site', () => {
266 + // When in doubt, register variables that were used in JSX.
267 + // Foo, Header, and B get registered.
268 + // A doesn't get registered because it's not declared locally.
269 + // Alias doesn't get registered because its definition is just an identifier.
270 + expect(
271 + transform(`
272 + import A from './A';
273 + import Store from './Store';
274 +
275 + Store.subscribe();
276 +
277 + const Header = styled.div\`color: red\`
278 + const Factory = funny.factory\`\`;
279 +
280 + let Alias1 = A;
281 + let Alias2 = A.Foo;
282 + const Dict = {};
283 +
284 + function Foo() {
285 + return [
286 + React.createElement(A),
287 + React.createElement(B),
288 + React.createElement(Alias1),
289 + React.createElement(Alias2),
290 + jsx(Header),
291 + React.createElement(Dict.X),
292 + ];
293 + }
294 +
295 + React.createContext(Store);
296 +
297 + const B = hoc(A);
298 + const NotAComponent = wow(A);
299 + `),
300 + ).toMatchSnapshot();
301 + });
302 });
packages/react-fresh/src/__tests__/ReactFreshIntegration-test.js new
+247
@@ -0,0 +1,247 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +/* eslint-disable no-for-of-loops/no-for-of-loops */
11 +
12 +'use strict';
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactFreshRuntime;
17 +let act;
18 +
19 +let babel = require('babel-core');
20 +let freshPlugin = require('react-fresh/babel');
21 +
22 +describe('ReactFreshIntegration', () => {
23 + let container;
24 + let lastRoot;
25 + let scheduleHotUpdate;
26 +
27 + beforeEach(() => {
28 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
29 + supportsFiber: true,
30 + inject: injected => {
31 + scheduleHotUpdate = injected.scheduleHotUpdate;
32 + },
33 + onCommitFiberRoot: (id, root) => {
34 + lastRoot = root;
35 + },
36 + onCommitFiberUnmount: () => {},
37 + };
38 +
39 + jest.resetModules();
40 + React = require('react');
41 + ReactDOM = require('react-dom');
42 + ReactFreshRuntime = require('react-fresh/runtime');
43 + act = require('react-dom/test-utils').act;
44 + container = document.createElement('div');
45 + document.body.appendChild(container);
46 + });
47 +
48 + afterEach(() => {
49 + document.body.removeChild(container);
50 + });
51 +
52 + function execute(source) {
53 + const compiled = babel.transform(source, {
54 + babelrc: false,
55 + presets: ['react'],
56 + plugins: [freshPlugin, 'transform-es2015-modules-commonjs'],
57 + }).code;
58 + const exportsObj = {};
59 + // eslint-disable-next-line no-new-func
60 + new Function('React', 'exports', '__register__', compiled)(
61 + React,
62 + exportsObj,
63 + __register__,
64 + );
65 + return exportsObj.default;
66 + }
67 +
68 + function render(source) {
69 + const Component = execute(source);
70 + act(() => {
71 + ReactDOM.render(<Component />, container);
72 + });
73 + }
74 +
75 + function patch(source) {
76 + execute(source);
77 + const hotUpdate = ReactFreshRuntime.prepareUpdate();
78 + scheduleHotUpdate(lastRoot, hotUpdate);
79 + }
80 +
81 + function __register__(type, id) {
82 + ReactFreshRuntime.register(type, id);
83 + }
84 +
85 + it('reloads function declarations', () => {
86 + if (__DEV__) {
87 + render(`
88 + function Parent() {
89 + return <Child prop="A" />;
90 + };
91 +
92 + function Child({prop}) {
93 + return <h1>{prop}1</h1>;
94 + };
95 +
96 + export default Parent;
97 + `);
98 + const el = container.firstChild;
99 + expect(el.textContent).toBe('A1');
100 + patch(`
101 + function Parent() {
102 + return <Child prop="B" />;
103 + };
104 +
105 + function Child({prop}) {
106 + return <h1>{prop}2</h1>;
107 + };
108 +
109 + export default Parent;
110 + `);
111 + expect(container.firstChild).toBe(el);
112 + expect(el.textContent).toBe('B2');
113 + }
114 + });
115 +
116 + it('reloads arrow functions', () => {
117 + if (__DEV__) {
118 + render(`
119 + const Parent = () => {
120 + return <Child prop="A" />;
121 + };
122 +
123 + const Child = ({prop}) => {
124 + return <h1>{prop}1</h1>;
125 + };
126 +
127 + export default Parent;
128 + `);
129 + const el = container.firstChild;
130 + expect(el.textContent).toBe('A1');
131 + patch(`
132 + const Parent = () => {
133 + return <Child prop="B" />;
134 + };
135 +
136 + const Child = ({prop}) => {
137 + return <h1>{prop}2</h1>;
138 + };
139 +
140 + export default Parent;
141 + `);
142 + expect(container.firstChild).toBe(el);
143 + expect(el.textContent).toBe('B2');
144 + }
145 + });
146 +
147 + it('reloads a combination of memo and forwardRef', () => {
148 + if (__DEV__) {
149 + render(`
150 + const {memo} = React;
151 +
152 + const Parent = memo(React.forwardRef(function (props, ref) {
153 + return <Child prop="A" ref={ref} />;
154 + }));
155 +
156 + const Child = React.memo(({prop}) => {
157 + return <h1>{prop}1</h1>;
158 + });
159 +
160 + export default React.memo(Parent);
161 + `);
162 + const el = container.firstChild;
163 + expect(el.textContent).toBe('A1');
164 + patch(`
165 + const {memo} = React;
166 +
167 + const Parent = memo(React.forwardRef(function (props, ref) {
168 + return <Child prop="B" ref={ref} />;
169 + }));
170 +
171 + const Child = React.memo(({prop}) => {
172 + return <h1>{prop}2</h1>;
173 + });
174 +
175 + export default React.memo(Parent);
176 + `);
177 + expect(container.firstChild).toBe(el);
178 + expect(el.textContent).toBe('B2');
179 + }
180 + });
181 +
182 + it('reloads default export with named memo', () => {
183 + if (__DEV__) {
184 + render(`
185 + const {memo} = React;
186 +
187 + const Child = React.memo(({prop}) => {
188 + return <h1>{prop}1</h1>;
189 + });
190 +
191 + export default memo(React.forwardRef(function Parent(props, ref) {
192 + return <Child prop="A" ref={ref} />;
193 + }));
194 + `);
195 + const el = container.firstChild;
196 + expect(el.textContent).toBe('A1');
197 + patch(`
198 + const {memo} = React;
199 +
200 + const Child = React.memo(({prop}) => {
201 + return <h1>{prop}2</h1>;
202 + });
203 +
204 + export default memo(React.forwardRef(function Parent(props, ref) {
205 + return <Child prop="B" ref={ref} />;
206 + }));
207 + `);
208 + expect(container.firstChild).toBe(el);
209 + expect(el.textContent).toBe('B2');
210 + }
211 + });
212 +
213 + it('reloads HOCs if they return functions', () => {
214 + if (__DEV__) {
215 + render(`
216 + function hoc(letter) {
217 + return function() {
218 + return <h1>{letter}1</h1>;
219 + }
220 + }
221 +
222 + export default function Parent() {
223 + return <Child />;
224 + }
225 +
226 + const Child = hoc('A');
227 + `);
228 + const el = container.firstChild;
229 + expect(el.textContent).toBe('A1');
230 + patch(`
231 + function hoc(letter) {
232 + return function() {
233 + return <h1>{letter}2</h1>;
234 + }
235 + }
236 +
237 + export default function Parent() {
238 + return React.createElement(Child);
239 + }
240 +
241 + const Child = hoc('B');
242 + `);
243 + expect(container.firstChild).toBe(el);
244 + expect(el.textContent).toBe('B2');
245 + }
246 + });
247 +});
packages/react-fresh/src/__tests__/__snapshots__/ReactFreshBabelPlugin-test.js.snap
+138
@@ -33,6 +33,19 @@ let D = bar && (() => {
33 });"
34 `;
35
36 +exports[`ReactFreshBabelPlugin ignores higher-order functions that are not HOCs 1`] = `
37 +"
38 +const throttledAlert = throttle(function () {
39 + alert('Hi');
40 +});
41 +const TooComplex = function () {
42 + return hello;
43 +}(() => {});
44 +if (cond) {
45 + const Foo = thing(() => {});
46 +}"
47 +`;
48 +
49 exports[`ReactFreshBabelPlugin ignores unnamed function declarations 1`] = `
50 "
51 export default function () {}"
@@ -45,6 +58,131 @@ function hello() {
58 }"
59 `;
60
61 +exports[`ReactFreshBabelPlugin registers identifiers used in JSX at definition site 1`] = `
62 +"
63 +import A from './A';
64 +import Store from './Store';
65 +
66 +Store.subscribe();
67 +
68 +const Header = _c = styled.div\`color: red\`;
69 +const Factory = funny.factory\`\`;
70 +
71 +let Alias1 = A;
72 +let Alias2 = A.Foo;
73 +const Dict = {};
74 +
75 +function Foo() {
76 + return <div><A /><B /><Alias1 /><Alias2 /><Header /><Dict.X /></div>;
77 +}
78 +
79 +_c2 = Foo;
80 +const B = _c3 = hoc(A);
81 +const NotAComponent = wow(A);
82 +
83 +var _c, _c2, _c3;
84 +
85 +__register__(_c, 'Header');
86 +
87 +__register__(_c2, 'Foo');
88 +
89 +__register__(_c3, 'B');"
90 +`;
91 +
92 +exports[`ReactFreshBabelPlugin registers identifiers used in React.createElement at definition site 1`] = `
93 +"
94 +import A from './A';
95 +import Store from './Store';
96 +
97 +Store.subscribe();
98 +
99 +const Header = _c = styled.div\`color: red\`;
100 +const Factory = funny.factory\`\`;
101 +
102 +let Alias1 = A;
103 +let Alias2 = A.Foo;
104 +const Dict = {};
105 +
106 +function Foo() {
107 + return [React.createElement(A), React.createElement(B), React.createElement(Alias1), React.createElement(Alias2), jsx(Header), React.createElement(Dict.X)];
108 +}
109 +
110 +_c2 = Foo;
111 +React.createContext(Store);
112 +
113 +const B = _c3 = hoc(A);
114 +const NotAComponent = wow(A);
115 +
116 +var _c, _c2, _c3;
117 +
118 +__register__(_c, 'Header');
119 +
120 +__register__(_c2, 'Foo');
121 +
122 +__register__(_c3, 'B');"
123 +`;
124 +
125 +exports[`ReactFreshBabelPlugin registers likely HOCs with inline functions 1`] = `
126 +"
127 +const A = _c2 = forwardRef(_c = function () {
128 + return <h1>Foo</h1>;
129 +});
130 +const B = _c5 = memo(_c4 = React.forwardRef(_c3 = () => {
131 + return <h1>Foo</h1>;
132 +}));
133 +export default _c8 = React.memo(_c7 = forwardRef(_c6 = (props, ref) => {
134 + return <h1>Foo</h1>;
135 +}));
136 +
137 +var _c, _c2, _c3, _c4, _c5, _c6, _c7, _c8;
138 +
139 +__register__(_c, \\"A$forwardRef\\");
140 +
141 +__register__(_c2, \\"A\\");
142 +
143 +__register__(_c3, \\"B$memo$React.forwardRef\\");
144 +
145 +__register__(_c4, \\"B$memo\\");
146 +
147 +__register__(_c5, \\"B\\");
148 +
149 +__register__(_c6, \\"%default%$React.memo$forwardRef\\");
150 +
151 +__register__(_c7, \\"%default%$React.memo\\");
152 +
153 +__register__(_c8, \\"%default%\\");"
154 +`;
155 +
156 +exports[`ReactFreshBabelPlugin registers likely HOCs with inline functions 2`] = `
157 +"
158 +export default _c3 = React.memo(_c2 = forwardRef(_c = function (props, ref) {
159 + return <h1>Foo</h1>;
160 +}));
161 +
162 +var _c, _c2, _c3;
163 +
164 +__register__(_c, \\"%default%$React.memo$forwardRef\\");
165 +
166 +__register__(_c2, \\"%default%$React.memo\\");
167 +
168 +__register__(_c3, \\"%default%\\");"
169 +`;
170 +
171 +exports[`ReactFreshBabelPlugin registers likely HOCs with inline functions 3`] = `
172 +"
173 +export default _c3 = React.memo(_c2 = forwardRef(_c = function Named(props, ref) {
174 + return <h1>Foo</h1>;
175 +}));
176 +
177 +var _c, _c2, _c3;
178 +
179 +__register__(_c, \\"%default%$React.memo$forwardRef\\");
180 +
181 +__register__(_c2, \\"%default%$React.memo\\");
182 +
183 +__register__(_c3, \\"%default%\\");"
184 +`;
185 +
186 exports[`ReactFreshBabelPlugin registers top-level exported function declarations 1`] = `
187 "
188 export function Hello() {