mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[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
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# react-fresh
|
||||
|
||||
This is an experimental package for hot reloading.
|
||||
|
||||
**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
|
||||
|
||||
**Use it at your own risk.**
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactFreshBabelPlugin = require('./src/ReactFreshBabelPlugin');
|
||||
|
||||
// This is hacky but makes it work with both Rollup and Jest.
|
||||
module.exports = ReactFreshBabelPlugin.default || ReactFreshBabelPlugin;
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-fresh-babel.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-fresh-babel.development.js');
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-fresh-runtime.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-fresh-runtime.development.js');
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "react-fresh",
|
||||
"private": true,
|
||||
"description": "React is a JavaScript library for building user interfaces.",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://reactjs.org/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"babel.js",
|
||||
"runtime.js",
|
||||
"build-info.json",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
],
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactFreshRuntime = require('./src/ReactFreshRuntime');
|
||||
|
||||
// This is hacky but makes it work with both Rollup and Jest.
|
||||
module.exports = ReactFreshRuntime.default || ReactFreshRuntime;
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO
|
||||
export default function(babel) {
|
||||
return {
|
||||
visitor: {},
|
||||
};
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {
|
||||
Family,
|
||||
HotUpdate,
|
||||
} from 'react-reconciler/src/ReactFiberHotReloading';
|
||||
|
||||
import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
// We never remove these associations.
|
||||
// It's OK to reference families, but use WeakMap/Set for types.
|
||||
const allFamiliesByID: Map<string, Family> = new Map();
|
||||
const allTypes: WeakSet<any> = new WeakSet();
|
||||
const allSignaturesByType: WeakMap<any, string> = new WeakMap();
|
||||
// This WeakMap is read by React, so we only put families
|
||||
// that have actually been edited here. This keeps checks fast.
|
||||
const familiesByType: WeakMap<any, Family> = new WeakMap();
|
||||
|
||||
// This is cleared on every prepareUpdate() call.
|
||||
// It is an array of [Family, NextType] tuples.
|
||||
let pendingUpdates: Array<[Family, any]> = [];
|
||||
|
||||
export function prepareUpdate(): HotUpdate {
|
||||
const staleFamilies = new Set();
|
||||
const updatedFamilies = new Set();
|
||||
|
||||
const updates = pendingUpdates;
|
||||
pendingUpdates = [];
|
||||
updates.forEach(([family, nextType]) => {
|
||||
// Now that we got a real edit, we can create associations
|
||||
// that will be read by the React reconciler.
|
||||
const prevType = family.current;
|
||||
familiesByType.set(prevType, family);
|
||||
familiesByType.set(nextType, family);
|
||||
family.current = nextType;
|
||||
|
||||
// Determine whether this should be a re-render or a re-mount.
|
||||
const prevSignature = allSignaturesByType.get(prevType);
|
||||
const nextSignature = allSignaturesByType.get(nextType);
|
||||
if (prevSignature !== nextSignature) {
|
||||
staleFamilies.add(family);
|
||||
} else {
|
||||
updatedFamilies.add(family);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
familiesByType,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
};
|
||||
}
|
||||
|
||||
export function register(type: any, id: string): void {
|
||||
if (type === null) {
|
||||
return;
|
||||
}
|
||||
if (typeof type !== 'function' && typeof type !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// This can happen in an edge case, e.g. if we register
|
||||
// return value of a HOC but it returns a cached component.
|
||||
// Ignore anything but the first registration for each type.
|
||||
if (allTypes.has(type)) {
|
||||
return;
|
||||
}
|
||||
allTypes.add(type);
|
||||
|
||||
// Create family or remember to update it.
|
||||
// None of this bookkeeping affects reconciliation
|
||||
// until the first prepareUpdate() call above.
|
||||
let family = allFamiliesByID.get(id);
|
||||
if (family === undefined) {
|
||||
family = {current: type};
|
||||
allFamiliesByID.set(id, family);
|
||||
} else {
|
||||
pendingUpdates.push([family, type]);
|
||||
}
|
||||
|
||||
// Visit inner types because we might not have registered them.
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
register(type.render, id + '$render');
|
||||
break;
|
||||
case REACT_MEMO_TYPE:
|
||||
register(type.type, id + '$type');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setSignature(type: any, signature: string): void {
|
||||
allSignaturesByType.set(type, signature);
|
||||
}
|
||||
+10
-98
@@ -13,21 +13,16 @@
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactFreshRuntime;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let lastRoot;
|
||||
|
||||
describe('ReactFresh', () => {
|
||||
let container;
|
||||
let familiesByID;
|
||||
let familiesByType;
|
||||
let newFamilies;
|
||||
let updatedFamilies;
|
||||
let performHotReload;
|
||||
let signaturesByType;
|
||||
let scheduleHotUpdate;
|
||||
|
||||
beforeEach(() => {
|
||||
let scheduleHotUpdate;
|
||||
let lastRoot;
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
supportsFiber: true,
|
||||
inject: injected => {
|
||||
@@ -42,24 +37,11 @@ describe('ReactFresh', () => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactFreshRuntime = require('react-fresh/runtime');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('react-dom/test-utils').act;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
familiesByID = new Map();
|
||||
familiesByType = new WeakMap();
|
||||
|
||||
if (__DEV__) {
|
||||
performHotReload = function(staleFamilies) {
|
||||
scheduleHotUpdate({
|
||||
root: lastRoot,
|
||||
familiesByType,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -67,26 +49,12 @@ describe('ReactFresh', () => {
|
||||
});
|
||||
|
||||
function prepare(version) {
|
||||
newFamilies = new Set();
|
||||
updatedFamilies = new Set();
|
||||
signaturesByType = new Map();
|
||||
const Component = version();
|
||||
|
||||
// Fill in the signatures.
|
||||
for (let family of newFamilies) {
|
||||
const latestSignature = signaturesByType.get(family.currentType) || null;
|
||||
family.currentSignature = latestSignature;
|
||||
}
|
||||
|
||||
newFamilies = null;
|
||||
updatedFamilies = null;
|
||||
signaturesByType = null;
|
||||
|
||||
return Component;
|
||||
}
|
||||
|
||||
function render(version, props) {
|
||||
const Component = prepare(version);
|
||||
const Component = version();
|
||||
act(() => {
|
||||
ReactDOM.render(<Component {...props} />, container);
|
||||
});
|
||||
@@ -94,74 +62,18 @@ describe('ReactFresh', () => {
|
||||
}
|
||||
|
||||
function patch(version) {
|
||||
// Will be filled in by __register__ calls in user code.
|
||||
newFamilies = new Set();
|
||||
updatedFamilies = new Set();
|
||||
signaturesByType = new Map();
|
||||
const Component = version();
|
||||
|
||||
// Fill in the signatures.
|
||||
for (let family of newFamilies) {
|
||||
const latestSignature = signaturesByType.get(family.currentType) || null;
|
||||
family.currentSignature = latestSignature;
|
||||
}
|
||||
// Now that all registration and signatures are collected,
|
||||
// find which registrations changed their signatures since last time.
|
||||
const staleFamilies = new Set();
|
||||
for (let family of updatedFamilies) {
|
||||
const latestSignature = signaturesByType.get(family.currentType) || null;
|
||||
if (family.currentSignature !== latestSignature) {
|
||||
family.currentSignature = latestSignature;
|
||||
staleFamilies.add(family);
|
||||
}
|
||||
}
|
||||
|
||||
performHotReload(staleFamilies);
|
||||
newFamilies = null;
|
||||
updatedFamilies = null;
|
||||
signaturesByType = null;
|
||||
const hotUpdate = ReactFreshRuntime.prepareUpdate();
|
||||
scheduleHotUpdate(lastRoot, hotUpdate);
|
||||
return Component;
|
||||
}
|
||||
|
||||
function __register__(type, id) {
|
||||
if (familiesByType.has(type)) {
|
||||
return;
|
||||
}
|
||||
let family = familiesByID.get(id);
|
||||
let isNew = false;
|
||||
if (family === undefined) {
|
||||
isNew = true;
|
||||
family = {currentType: type, currentSignature: null};
|
||||
familiesByID.set(id, family);
|
||||
}
|
||||
const prevType = family.currentType;
|
||||
if (isNew) {
|
||||
// The first time a type is registered, we don't need
|
||||
// any special reconciliation logic. So we won't add it to the map.
|
||||
// Instead, this will happen the firt time it is edited.
|
||||
newFamilies.add(family);
|
||||
} else {
|
||||
family.currentType = type;
|
||||
// Point both previous and next types to this family.
|
||||
familiesByType.set(prevType, family);
|
||||
familiesByType.set(type, family);
|
||||
updatedFamilies.add(family);
|
||||
}
|
||||
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
switch (type.$$typeof) {
|
||||
case Symbol.for('react.forward_ref'):
|
||||
__register__(type.render, id + '$render');
|
||||
break;
|
||||
case Symbol.for('react.memo'):
|
||||
__register__(type.type, id + '$type');
|
||||
break;
|
||||
}
|
||||
}
|
||||
ReactFreshRuntime.register(type, id);
|
||||
}
|
||||
|
||||
function __signature__(type, signature) {
|
||||
signaturesByType.set(type, signature);
|
||||
function __signature__(type, id) {
|
||||
ReactFreshRuntime.setSignature(type, id);
|
||||
}
|
||||
|
||||
it('can preserve state for compatible types', () => {
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let babel = require('babel-core');
|
||||
let freshPlugin = require('react-fresh/babel');
|
||||
|
||||
function transform(input, options = {}) {
|
||||
return babel.transform(input, {
|
||||
plugins: [[freshPlugin]],
|
||||
}).code;
|
||||
}
|
||||
|
||||
describe('ReactFreshBabelPlugin', () => {
|
||||
it('hello world', () => {
|
||||
expect(transform(`hello()`)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactFreshBabelPlugin hello world 1`] = `"hello();"`;
|
||||
+7
-9
@@ -29,14 +29,12 @@ import {
|
||||
REACT_LAZY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
type Family = {|
|
||||
currentType: any,
|
||||
currentSignature: null | string,
|
||||
export type Family = {|
|
||||
current: any,
|
||||
|};
|
||||
|
||||
type HotUpdate = {|
|
||||
export type HotUpdate = {|
|
||||
familiesByType: WeakMap<any, Family>,
|
||||
root: FiberRoot,
|
||||
staleFamilies: Set<Family>,
|
||||
updatedFamilies: Set<Family>,
|
||||
|};
|
||||
@@ -56,7 +54,7 @@ export function resolveFunctionForHotReloading(type: any): any {
|
||||
return type;
|
||||
}
|
||||
// Use the latest known implementation.
|
||||
return family.currentType;
|
||||
return family.current;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
@@ -94,7 +92,7 @@ export function resolveForwardRefForHotReloading(type: any): any {
|
||||
return type;
|
||||
}
|
||||
// Use the latest known implementation.
|
||||
return family.currentType;
|
||||
return family.current;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
@@ -194,12 +192,12 @@ export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
export function scheduleHotUpdate(hotUpdate: HotUpdate): void {
|
||||
export function scheduleHotUpdate(root: FiberRoot, hotUpdate: HotUpdate): void {
|
||||
if (__DEV__) {
|
||||
// TODO: warn if its identity changes over time?
|
||||
familiesByType = hotUpdate.familiesByType;
|
||||
|
||||
const {root, staleFamilies, updatedFamilies} = hotUpdate;
|
||||
const {staleFamilies, updatedFamilies} = hotUpdate;
|
||||
flushPassiveEffects();
|
||||
flushSync(() => {
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
|
||||
@@ -453,6 +453,23 @@ const bundles = [
|
||||
global: 'ESLintPluginReactHooks',
|
||||
externals: [],
|
||||
},
|
||||
|
||||
/******* React Fresh *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'react-fresh/babel',
|
||||
global: 'ReactFreshBabelPlugin',
|
||||
externals: [],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'react-fresh/runtime',
|
||||
global: 'ReactFreshRuntime',
|
||||
externals: [],
|
||||
},
|
||||
|
||||
{
|
||||
bundleTypes: [
|
||||
FB_WWW_DEV,
|
||||
|
||||
Reference in New Issue
Block a user