@samitouri / QOS-React-2 / commits / 6d4f85b611

[Fresh] Set up infra for runtime and Babel plugin (#15698)

* Add a stub for React Fresh Babel plugin package * Move ReactFresh-test into ReactFresh top level directory * Add a stub for React Fresh Runtime entry point * Extract Fresh runtime from tests into its entry point

Dan Abramov committed May 21, 2019 at 21:54 UTC 6d4f85b611cd7bac40db44854bab06776ce30c96
13 files changed +255 -107
packages/react-fresh/README.md new
+7
@@ -0,0 +1,7 @@
1 +# react-fresh
2 +
3 +This is an experimental package for hot reloading.
4 +
5 +**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6 +
7 +**Use it at your own risk.**
packages/react-fresh/babel.js new
+13
@@ -0,0 +1,13 @@
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 +
8 +'use strict';
9 +
10 +const ReactFreshBabelPlugin = require('./src/ReactFreshBabelPlugin');
11 +
12 +// This is hacky but makes it work with both Rollup and Jest.
13 +module.exports = ReactFreshBabelPlugin.default || ReactFreshBabelPlugin;
packages/react-fresh/npm/babel.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-fresh-babel.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-fresh-babel.development.js');
7 +}
packages/react-fresh/npm/runtime.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-fresh-runtime.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-fresh-runtime.development.js');
7 +}
packages/react-fresh/package.json new
+30
@@ -0,0 +1,30 @@
1 +{
2 + "name": "react-fresh",
3 + "private": true,
4 + "description": "React is a JavaScript library for building user interfaces.",
5 + "keywords": [
6 + "react"
7 + ],
8 + "version": "0.1.0",
9 + "homepage": "https://reactjs.org/",
10 + "bugs": "https://github.com/facebook/react/issues",
11 + "license": "MIT",
12 + "files": [
13 + "LICENSE",
14 + "README.md",
15 + "babel.js",
16 + "runtime.js",
17 + "build-info.json",
18 + "cjs/",
19 + "umd/"
20 + ],
21 + "main": "index.js",
22 + "repository": {
23 + "type": "git",
24 + "url": "https://github.com/facebook/react.git",
25 + "directory": "packages/react"
26 + },
27 + "engines": {
28 + "node": ">=0.10.0"
29 + }
30 +}
packages/react-fresh/runtime.js new
+13
@@ -0,0 +1,13 @@
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 +
8 +'use strict';
9 +
10 +const ReactFreshRuntime = require('./src/ReactFreshRuntime');
11 +
12 +// This is hacky but makes it work with both Rollup and Jest.
13 +module.exports = ReactFreshRuntime.default || ReactFreshRuntime;
packages/react-fresh/src/ReactFreshBabelPlugin.js new
+15
@@ -0,0 +1,15 @@
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 +
8 +'use strict';
9 +
10 +// TODO
11 +export default function(babel) {
12 + return {
13 + visitor: {},
14 + };
15 +}
packages/react-fresh/src/ReactFreshRuntime.js new
+103
@@ -0,0 +1,103 @@
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 + * @flow
8 + */
9 +
10 +import type {
11 + Family,
12 + HotUpdate,
13 +} from 'react-reconciler/src/ReactFiberHotReloading';
14 +
15 +import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
16 +
17 +// We never remove these associations.
18 +// It's OK to reference families, but use WeakMap/Set for types.
19 +const allFamiliesByID: Map<string, Family> = new Map();
20 +const allTypes: WeakSet<any> = new WeakSet();
21 +const allSignaturesByType: WeakMap<any, string> = new WeakMap();
22 +// This WeakMap is read by React, so we only put families
23 +// that have actually been edited here. This keeps checks fast.
24 +const familiesByType: WeakMap<any, Family> = new WeakMap();
25 +
26 +// This is cleared on every prepareUpdate() call.
27 +// It is an array of [Family, NextType] tuples.
28 +let pendingUpdates: Array<[Family, any]> = [];
29 +
30 +export function prepareUpdate(): HotUpdate {
31 + const staleFamilies = new Set();
32 + const updatedFamilies = new Set();
33 +
34 + const updates = pendingUpdates;
35 + pendingUpdates = [];
36 + updates.forEach(([family, nextType]) => {
37 + // Now that we got a real edit, we can create associations
38 + // that will be read by the React reconciler.
39 + const prevType = family.current;
40 + familiesByType.set(prevType, family);
41 + familiesByType.set(nextType, family);
42 + family.current = nextType;
43 +
44 + // Determine whether this should be a re-render or a re-mount.
45 + const prevSignature = allSignaturesByType.get(prevType);
46 + const nextSignature = allSignaturesByType.get(nextType);
47 + if (prevSignature !== nextSignature) {
48 + staleFamilies.add(family);
49 + } else {
50 + updatedFamilies.add(family);
51 + }
52 + });
53 +
54 + return {
55 + familiesByType,
56 + updatedFamilies,
57 + staleFamilies,
58 + };
59 +}
60 +
61 +export function register(type: any, id: string): void {
62 + if (type === null) {
63 + return;
64 + }
65 + if (typeof type !== 'function' && typeof type !== 'object') {
66 + return;
67 + }
68 +
69 + // This can happen in an edge case, e.g. if we register
70 + // return value of a HOC but it returns a cached component.
71 + // Ignore anything but the first registration for each type.
72 + if (allTypes.has(type)) {
73 + return;
74 + }
75 + allTypes.add(type);
76 +
77 + // Create family or remember to update it.
78 + // None of this bookkeeping affects reconciliation
79 + // until the first prepareUpdate() call above.
80 + let family = allFamiliesByID.get(id);
81 + if (family === undefined) {
82 + family = {current: type};
83 + allFamiliesByID.set(id, family);
84 + } else {
85 + pendingUpdates.push([family, type]);
86 + }
87 +
88 + // Visit inner types because we might not have registered them.
89 + if (typeof type === 'object' && type !== null) {
90 + switch (type.$$typeof) {
91 + case REACT_FORWARD_REF_TYPE:
92 + register(type.render, id + '$render');
93 + break;
94 + case REACT_MEMO_TYPE:
95 + register(type.type, id + '$type');
96 + break;
97 + }
98 + }
99 +}
100 +
101 +export function setSignature(type: any, signature: string): void {
102 + allSignaturesByType.set(type, signature);
103 +}
packages/react-fresh/src/__tests__/ReactFresh-test.js renamed
+10 -98
@@ -13,21 +13,16 @@
13
14 let React;
15 let ReactDOM;
16 +let ReactFreshRuntime;
17 let Scheduler;
18 let act;
19 +let lastRoot;
20
21 describe('ReactFresh', () => {
22 let container;
21 - let familiesByID;
22 - let familiesByType;
23 - let newFamilies;
24 - let updatedFamilies;
25 - let performHotReload;
26 - let signaturesByType;
23 + let scheduleHotUpdate;
24
25 beforeEach(() => {
29 - let scheduleHotUpdate;
30 - let lastRoot;
26 global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
27 supportsFiber: true,
28 inject: injected => {
@@ -42,24 +37,11 @@ describe('ReactFresh', () => {
37 jest.resetModules();
38 React = require('react');
39 ReactDOM = require('react-dom');
40 + ReactFreshRuntime = require('react-fresh/runtime');
41 Scheduler = require('scheduler');
42 act = require('react-dom/test-utils').act;
43 container = document.createElement('div');
44 document.body.appendChild(container);
49 -
50 - familiesByID = new Map();
51 - familiesByType = new WeakMap();
52 -
53 - if (__DEV__) {
54 - performHotReload = function(staleFamilies) {
55 - scheduleHotUpdate({
56 - root: lastRoot,
57 - familiesByType,
58 - updatedFamilies,
59 - staleFamilies,
60 - });
61 - };
62 - }
45 });
46
47 afterEach(() => {
@@ -67,26 +49,12 @@ describe('ReactFresh', () => {
49 });
50
51 function prepare(version) {
70 - newFamilies = new Set();
71 - updatedFamilies = new Set();
72 - signaturesByType = new Map();
52 const Component = version();
74 -
75 - // Fill in the signatures.
76 - for (let family of newFamilies) {
77 - const latestSignature = signaturesByType.get(family.currentType) || null;
78 - family.currentSignature = latestSignature;
79 - }
80 -
81 - newFamilies = null;
82 - updatedFamilies = null;
83 - signaturesByType = null;
84 -
53 return Component;
54 }
55
56 function render(version, props) {
89 - const Component = prepare(version);
57 + const Component = version();
58 act(() => {
59 ReactDOM.render(<Component {...props} />, container);
60 });
@@ -94,74 +62,18 @@ describe('ReactFresh', () => {
62 }
63
64 function patch(version) {
97 - // Will be filled in by __register__ calls in user code.
98 - newFamilies = new Set();
99 - updatedFamilies = new Set();
100 - signaturesByType = new Map();
65 const Component = version();
102 -
103 - // Fill in the signatures.
104 - for (let family of newFamilies) {
105 - const latestSignature = signaturesByType.get(family.currentType) || null;
106 - family.currentSignature = latestSignature;
107 - }
108 - // Now that all registration and signatures are collected,
109 - // find which registrations changed their signatures since last time.
110 - const staleFamilies = new Set();
111 - for (let family of updatedFamilies) {
112 - const latestSignature = signaturesByType.get(family.currentType) || null;
113 - if (family.currentSignature !== latestSignature) {
114 - family.currentSignature = latestSignature;
115 - staleFamilies.add(family);
116 - }
117 - }
118 -
119 - performHotReload(staleFamilies);
120 - newFamilies = null;
121 - updatedFamilies = null;
122 - signaturesByType = null;
66 + const hotUpdate = ReactFreshRuntime.prepareUpdate();
67 + scheduleHotUpdate(lastRoot, hotUpdate);
68 return Component;
69 }
70
71 function __register__(type, id) {
127 - if (familiesByType.has(type)) {
128 - return;
129 - }
130 - let family = familiesByID.get(id);
131 - let isNew = false;
132 - if (family === undefined) {
133 - isNew = true;
134 - family = {currentType: type, currentSignature: null};
135 - familiesByID.set(id, family);
136 - }
137 - const prevType = family.currentType;
138 - if (isNew) {
139 - // The first time a type is registered, we don't need
140 - // any special reconciliation logic. So we won't add it to the map.
141 - // Instead, this will happen the firt time it is edited.
142 - newFamilies.add(family);
143 - } else {
144 - family.currentType = type;
145 - // Point both previous and next types to this family.
146 - familiesByType.set(prevType, family);
147 - familiesByType.set(type, family);
148 - updatedFamilies.add(family);
149 - }
150 -
151 - if (typeof type === 'object' && type !== null) {
152 - switch (type.$$typeof) {
153 - case Symbol.for('react.forward_ref'):
154 - __register__(type.render, id + '$render');
155 - break;
156 - case Symbol.for('react.memo'):
157 - __register__(type.type, id + '$type');
158 - break;
159 - }
160 - }
72 + ReactFreshRuntime.register(type, id);
73 }
74
163 - function __signature__(type, signature) {
164 - signaturesByType.set(type, signature);
75 + function __signature__(type, id) {
76 + ReactFreshRuntime.setSignature(type, id);
77 }
78
79 it('can preserve state for compatible types', () => {
packages/react-fresh/src/__tests__/ReactFreshBabelPlugin-test.js new
+23
@@ -0,0 +1,23 @@
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 +
8 +'use strict';
9 +
10 +let babel = require('babel-core');
11 +let freshPlugin = require('react-fresh/babel');
12 +
13 +function transform(input, options = {}) {
14 + return babel.transform(input, {
15 + plugins: [[freshPlugin]],
16 + }).code;
17 +}
18 +
19 +describe('ReactFreshBabelPlugin', () => {
20 + it('hello world', () => {
21 + expect(transform(`hello()`)).toMatchSnapshot();
22 + });
23 +});
packages/react-fresh/src/__tests__/__snapshots__/ReactFreshBabelPlugin-test.js.snap new
+3
@@ -0,0 +1,3 @@
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`ReactFreshBabelPlugin hello world 1`] = `"hello();"`;
packages/react-reconciler/src/ReactFiberHotReloading.js
+7 -9
@@ -29,14 +29,12 @@ import {
29 REACT_LAZY_TYPE,
30 } from 'shared/ReactSymbols';
31
32 -type Family = {|
33 - currentType: any,
34 - currentSignature: null | string,
32 +export type Family = {|
33 + current: any,
34 |};
35
37 -type HotUpdate = {|
36 +export type HotUpdate = {|
37 familiesByType: WeakMap<any, Family>,
39 - root: FiberRoot,
38 staleFamilies: Set<Family>,
39 updatedFamilies: Set<Family>,
40 |};
@@ -56,7 +54,7 @@ export function resolveFunctionForHotReloading(type: any): any {
54 return type;
55 }
56 // Use the latest known implementation.
59 - return family.currentType;
57 + return family.current;
58 } else {
59 return type;
60 }
@@ -94,7 +92,7 @@ export function resolveForwardRefForHotReloading(type: any): any {
92 return type;
93 }
94 // Use the latest known implementation.
97 - return family.currentType;
95 + return family.current;
96 } else {
97 return type;
98 }
@@ -194,12 +192,12 @@ export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
192 }
193 }
194
197 -export function scheduleHotUpdate(hotUpdate: HotUpdate): void {
195 +export function scheduleHotUpdate(root: FiberRoot, hotUpdate: HotUpdate): void {
196 if (__DEV__) {
197 // TODO: warn if its identity changes over time?
198 familiesByType = hotUpdate.familiesByType;
199
202 - const {root, staleFamilies, updatedFamilies} = hotUpdate;
200 + const {staleFamilies, updatedFamilies} = hotUpdate;
201 flushPassiveEffects();
202 flushSync(() => {
203 scheduleFibersWithFamiliesRecursively(
scripts/rollup/bundles.js
+17
@@ -453,6 +453,23 @@ const bundles = [
453 global: 'ESLintPluginReactHooks',
454 externals: [],
455 },
456 +
457 + /******* React Fresh *******/
458 + {
459 + bundleTypes: [NODE_DEV, NODE_PROD],
460 + moduleType: ISOMORPHIC,
461 + entry: 'react-fresh/babel',
462 + global: 'ReactFreshBabelPlugin',
463 + externals: [],
464 + },
465 + {
466 + bundleTypes: [NODE_DEV, NODE_PROD],
467 + moduleType: ISOMORPHIC,
468 + entry: 'react-fresh/runtime',
469 + global: 'ReactFreshRuntime',
470 + externals: [],
471 + },
472 +
473 {
474 bundleTypes: [
475 FB_WWW_DEV,