Skip to content

Commit 6d4f85b

Browse files
authored
[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
1 parent 121acae commit 6d4f85b

13 files changed

+255
-107
lines changed

packages/react-fresh/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -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

+13
Original file line numberDiff line numberDiff line change
@@ -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

+7
Original file line numberDiff line numberDiff line change
@@ -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

+7
Original file line numberDiff line numberDiff line change
@@ -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

+30
Original file line numberDiff line numberDiff line change
@@ -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

+13
Original file line numberDiff line numberDiff line change
@@ -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;
Original file line numberDiff line numberDiff line change
@@ -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+
}
Original file line numberDiff line numberDiff line change
@@ -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-dom/src/__tests__/ReactFresh-test.internal.js renamed to packages/react-fresh/src/__tests__/ReactFresh-test.js

+10-98
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,16 @@
1313

1414
let React;
1515
let ReactDOM;
16+
let ReactFreshRuntime;
1617
let Scheduler;
1718
let act;
19+
let lastRoot;
1820

1921
describe('ReactFresh', () => {
2022
let container;
21-
let familiesByID;
22-
let familiesByType;
23-
let newFamilies;
24-
let updatedFamilies;
25-
let performHotReload;
26-
let signaturesByType;
23+
let scheduleHotUpdate;
2724

2825
beforeEach(() => {
29-
let scheduleHotUpdate;
30-
let lastRoot;
3126
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
3227
supportsFiber: true,
3328
inject: injected => {
@@ -42,126 +37,43 @@ describe('ReactFresh', () => {
4237
jest.resetModules();
4338
React = require('react');
4439
ReactDOM = require('react-dom');
40+
ReactFreshRuntime = require('react-fresh/runtime');
4541
Scheduler = require('scheduler');
4642
act = require('react-dom/test-utils').act;
4743
container = document.createElement('div');
4844
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-
}
6345
});
6446

6547
afterEach(() => {
6648
document.body.removeChild(container);
6749
});
6850

6951
function prepare(version) {
70-
newFamilies = new Set();
71-
updatedFamilies = new Set();
72-
signaturesByType = new Map();
7352
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-
8553
return Component;
8654
}
8755

8856
function render(version, props) {
89-
const Component = prepare(version);
57+
const Component = version();
9058
act(() => {
9159
ReactDOM.render(<Component {...props} />, container);
9260
});
9361
return Component;
9462
}
9563

9664
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();
10165
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);
12368
return Component;
12469
}
12570

12671
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);
16173
}
16274

163-
function __signature__(type, signature) {
164-
signaturesByType.set(type, signature);
75+
function __signature__(type, id) {
76+
ReactFreshRuntime.setSignature(type, id);
16577
}
16678

16779
it('can preserve state for compatible types', () => {
Original file line numberDiff line numberDiff line change
@@ -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+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ReactFreshBabelPlugin hello world 1`] = `"hello();"`;

0 commit comments

Comments
 (0)