prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
450 lines (337 loc) • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getInitialProps = getInitialProps;
exports.getInitialContext = getInitialContext;
exports.createSimpleClassInstance = createSimpleClassInstance;
exports.createClassInstanceForFirstRenderOnly = createClassInstanceForFirstRenderOnly;
exports.createClassInstance = createClassInstance;
exports.evaluateClassConstructor = evaluateClassConstructor;
exports.applyGetDerivedStateFromProps = applyGetDerivedStateFromProps;
var _realm = require("../realm.js");
var _index = require("../values/index.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _utils = require("./utils.js");
var _errors = require("./errors.js");
var _index2 = require("../methods/index.js");
var _singletons = require("../singletons.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _errors2 = require("../errors.js");
var _ShapeInformation = require("../utils/ShapeInformation.js");
var _descriptors = require("../descriptors.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
const lifecycleMethods = new Set(["componentWillUnmount", "componentDidMount", "componentWillMount", "componentDidUpdate", "componentWillUpdate", "componentDidCatch", "componentWillReceiveProps"]);
const whitelistedProperties = new Set(["props", "context", "refs", "setState"]);
function getInitialProps(realm, componentType, {
modelString
}) {
let componentModel = modelString !== undefined ? JSON.parse(modelString) : undefined;
let shape = _ShapeInformation.ShapeInformation.createForReactComponentProps(componentModel);
let propsName = null;
if (componentType !== null) {
if ((0, _utils.valueIsClassComponent)(realm, componentType)) {
propsName = "this.props";
} else {
let formalParameters;
if (componentType instanceof _index.BoundFunctionValue) {
(0, _invariant.default)(componentType.$BoundTargetFunction instanceof _index.ECMAScriptSourceFunctionValue);
formalParameters = componentType.$BoundTargetFunction.$FormalParameters;
} else {
formalParameters = componentType.$FormalParameters;
} // otherwise it's a functional component, where the first paramater of the function is "props" (if it exists)
if (formalParameters.length > 0) {
let firstParam = formalParameters[0];
if (t.isIdentifier(firstParam)) {
propsName = firstParam.name;
}
}
}
}
let abstractPropsObject = _index.AbstractValue.createAbstractObject(realm, propsName || "props", shape);
(0, _invariant.default)(abstractPropsObject instanceof _index.AbstractObjectValue);
(0, _utils.flagPropsWithNoPartialKeyOrRef)(realm, abstractPropsObject);
abstractPropsObject.makeFinal();
return abstractPropsObject;
}
function getInitialContext(realm, componentType) {
let contextName = null;
if ((0, _utils.valueIsClassComponent)(realm, componentType)) {
// it's a class component, so we need to check the type on for context of the component prototype
let superTypeParameters = componentType.$SuperTypeParameters;
contextName = "this.context";
if (superTypeParameters !== undefined) {
throw new _errors.ExpectedBailOut("context on class components not yet supported");
}
} else {
let formalParameters;
if (componentType instanceof _index.BoundFunctionValue) {
(0, _invariant.default)(componentType.$BoundTargetFunction instanceof _index.ECMAScriptSourceFunctionValue);
formalParameters = componentType.$BoundTargetFunction.$FormalParameters;
} else {
formalParameters = componentType.$FormalParameters;
} // otherwise it's a functional component, where the second paramater of the function is "context" (if it exists)
if (formalParameters.length > 1) {
let secondParam = formalParameters[1];
if (t.isIdentifier(secondParam)) {
contextName = secondParam.name;
}
}
}
let value = _index.AbstractValue.createAbstractObject(realm, contextName || "context");
return value;
}
function visitClassMethodAstForThisUsage(realm, method) {
let formalParameters = method.$FormalParameters;
let code = method.$ECMAScriptCode;
(0, _traverse.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), {
ThisExpression(path) {
let parentNode = path.parentPath.node;
if (!t.isMemberExpression(parentNode)) {
throw new _errors.SimpleClassBailOut(`possible leakage of independent "this" reference found`);
}
}
}, null, {});
}
function createSimpleClassInstance(realm, componentType, props, context) {
let componentPrototype = (0, _index2.Get)(realm, componentType, "prototype");
(0, _invariant.default)(componentPrototype instanceof _index.ObjectValue); // create an instance object and disable serialization as we don't want to output the internals we set below
let instance = new _index.ObjectValue(realm, componentPrototype, "this", true);
let allowedPropertyAccess = new Set(["props", "context"]);
for (let [name] of componentPrototype.properties) {
if (lifecycleMethods.has(name)) {
// this error will result in the simple class falling back to a complex class
throw new _errors.SimpleClassBailOut("lifecycle methods are not supported on simple classes");
} else if (name !== "constructor") {
allowedPropertyAccess.add(name);
let method = (0, _index2.Get)(realm, componentPrototype, name);
if (method instanceof _index.ECMAScriptSourceFunctionValue) {
visitClassMethodAstForThisUsage(realm, method);
}
_singletons.Properties.Set(realm, instance, name, method, true);
}
} // assign props
_singletons.Properties.Set(realm, instance, "props", props, true); // assign context
_singletons.Properties.Set(realm, instance, "context", context, true); // as this object is simple, we want to check if any access to anything other than
// "this.props" or "this.context" or methods on the class occur
let $GetOwnProperty = instance.$GetOwnProperty;
instance.$GetOwnProperty = P => {
if (!allowedPropertyAccess.has(P)) {
// this error will result in the simple class falling back to a complex class
throw new _errors.SimpleClassBailOut("access to basic class instance properties is not supported on simple classes");
}
return $GetOwnProperty.call(instance, P);
}; // enable serialization to support simple instance variables properties
instance.refuseSerialization = false; // return the instance
return instance;
}
function deeplyApplyInstancePrototypeProperties(realm, instance, componentPrototype, classMetadata) {
let {
instanceProperties,
instanceSymbols
} = classMetadata;
let proto = componentPrototype.$Prototype;
if (proto instanceof _index.ObjectValue && proto !== realm.intrinsics.ObjectPrototype) {
deeplyApplyInstancePrototypeProperties(realm, instance, proto, classMetadata);
}
for (let [name] of componentPrototype.properties) {
// ensure we don't set properties that were defined on the instance
if (name !== "constructor" && !instanceProperties.has(name)) {
_singletons.Properties.Set(realm, instance, name, (0, _index2.Get)(realm, componentPrototype, name), true);
}
}
for (let [symbol] of componentPrototype.symbols) {
// ensure we don't set symbols that were defined on the instance
if (!instanceSymbols.has(symbol)) {
_singletons.Properties.Set(realm, instance, symbol, (0, _index2.Get)(realm, componentPrototype, symbol), true);
}
}
}
function createClassInstanceForFirstRenderOnly(realm, componentType, props, context, evaluatedNode) {
let instance = (0, _utils.getValueFromFunctionCall)(realm, componentType, realm.intrinsics.undefined, [props, context], true);
let objectAssign = (0, _index2.Get)(realm, realm.intrinsics.Object, "assign");
(0, _invariant.default)(objectAssign instanceof _index.ECMAScriptFunctionValue);
let objectAssignCall = objectAssign.$Call;
(0, _invariant.default)(objectAssignCall !== undefined);
(0, _invariant.default)(instance instanceof _index.ObjectValue);
instance.refuseSerialization = true; // assign props
_singletons.Properties.Set(realm, instance, "props", props, true); // assign context
_singletons.Properties.Set(realm, instance, "context", context, true);
let state = (0, _index2.Get)(realm, instance, "state");
if (state instanceof _index.AbstractObjectValue || state instanceof _index.ObjectValue) {
state.makeFinal();
} // assign a mocked setState
let setState = new _index.NativeFunctionValue(realm, undefined, `setState`, 1, (_context, [stateToUpdate, callback]) => {
(0, _invariant.default)(instance instanceof _index.ObjectValue);
let prevState = (0, _index2.Get)(realm, instance, "state");
(0, _invariant.default)(prevState instanceof _index.ObjectValue);
if (stateToUpdate instanceof _index.ECMAScriptSourceFunctionValue && stateToUpdate.$Call) {
stateToUpdate = stateToUpdate.$Call(instance, [prevState]);
}
if (stateToUpdate instanceof _index.ObjectValue) {
let newState = new _index.ObjectValue(realm, realm.intrinsics.ObjectPrototype);
objectAssignCall(realm.intrinsics.undefined, [newState, prevState]);
newState.makeFinal();
for (let [key, binding] of stateToUpdate.properties) {
if (binding && binding.descriptor) {
(0, _invariant.default)(binding.descriptor instanceof _descriptors.PropertyDescriptor);
if (binding.descriptor.enumerable) {
let value = (0, _utils.getProperty)(realm, stateToUpdate, key);
(0, _utils.hardModifyReactObjectPropertyBinding)(realm, newState, key, value);
}
}
}
_singletons.Properties.Set(realm, instance, "state", newState, true);
}
if (callback instanceof _index.ECMAScriptSourceFunctionValue && callback.$Call) {
callback.$Call(instance, []);
}
return realm.intrinsics.undefined;
});
setState.intrinsicName = "(function() {})";
_singletons.Properties.Set(realm, instance, "setState", setState, true);
instance.refuseSerialization = false;
return instance;
}
function createClassInstance(realm, componentType, props, context, classMetadata) {
let componentPrototype = (0, _index2.Get)(realm, componentType, "prototype");
(0, _invariant.default)(componentPrototype instanceof _index.ObjectValue); // create an instance object and disable serialization as we don't want to output the internals we set below
let instance = new _index.ObjectValue(realm, componentPrototype, "this", true);
deeplyApplyInstancePrototypeProperties(realm, instance, componentPrototype, classMetadata); // assign refs
_singletons.Properties.Set(realm, instance, "refs", _index.AbstractValue.createAbstractObject(realm, "this.refs"), true); // assign props
_singletons.Properties.Set(realm, instance, "props", props, true); // assign context
_singletons.Properties.Set(realm, instance, "context", context, true); // enable serialization to support simple instance variables properties
instance.refuseSerialization = false; // return the instance in an abstract object
let value = _index.AbstractValue.createAbstractObject(realm, "this", instance);
(0, _invariant.default)(value instanceof _index.AbstractObjectValue);
return value;
}
function evaluateClassConstructor(realm, constructorFunc, props, context) {
let instanceProperties = new Set();
let instanceSymbols = new Set();
realm.evaluatePure(() => realm.evaluateForEffects(() => {
let instance = (0, _index2.Construct)(realm, constructorFunc, [props, context]);
(0, _invariant.default)(instance instanceof _index.ObjectValue);
for (let [propertyName] of instance.properties) {
if (!whitelistedProperties.has(propertyName)) {
instanceProperties.add(propertyName);
}
}
for (let [symbol] of instance.symbols) {
instanceSymbols.add(symbol);
}
return instance;
},
/*state*/
null, `react component constructor: ${constructorFunc.getName()}`),
/*bubbles*/
true,
/*reportSideEffectFunc*/
null);
return {
instanceProperties,
instanceSymbols
};
}
function applyGetDerivedStateFromProps(realm, getDerivedStateFromProps, instance, props) {
let prevState = (0, _index2.Get)(realm, instance, "state");
let getDerivedStateFromPropsCall = getDerivedStateFromProps.$Call;
(0, _invariant.default)(getDerivedStateFromPropsCall !== undefined);
let partialState = getDerivedStateFromPropsCall(realm.intrinsics.null, [props, prevState]);
const deriveState = state => {
let objectAssign = (0, _index2.Get)(realm, realm.intrinsics.Object, "assign");
(0, _invariant.default)(objectAssign instanceof _index.ECMAScriptFunctionValue);
let objectAssignCall = objectAssign.$Call;
(0, _invariant.default)(objectAssignCall !== undefined);
if (state instanceof _index.AbstractValue && !(state instanceof _index.AbstractObjectValue)) {
const kind = state.kind;
if (kind === "conditional") {
let condition = state.args[0];
let a = deriveState(state.args[1]);
let b = deriveState(state.args[2]);
(0, _invariant.default)(condition instanceof _index.AbstractValue);
if (a === null && b === null) {
return null;
} else if (a === null) {
(0, _invariant.default)(b instanceof _index.Value);
return _index.AbstractValue.createFromConditionalOp(realm, condition, realm.intrinsics.false, b);
} else if (b === null) {
(0, _invariant.default)(a instanceof _index.Value);
return _index.AbstractValue.createFromConditionalOp(realm, condition, a, realm.intrinsics.false);
} else {
(0, _invariant.default)(a instanceof _index.Value);
(0, _invariant.default)(b instanceof _index.Value);
return _index.AbstractValue.createFromConditionalOp(realm, condition, a, b);
}
} else if (kind === "||" || kind === "&&") {
let a = deriveState(state.args[0]);
let b = deriveState(state.args[1]);
if (b === null) {
(0, _invariant.default)(a instanceof _index.Value);
return _index.AbstractValue.createFromLogicalOp(realm, kind, a, realm.intrinsics.false);
} else {
(0, _invariant.default)(a instanceof _index.Value);
(0, _invariant.default)(b instanceof _index.Value);
return _index.AbstractValue.createFromLogicalOp(realm, kind, a, b);
}
} else {
(0, _invariant.default)(state.args !== undefined, "TODO: unknown abstract value passed to deriveState"); // as the value is completely abstract, we need to add a bunch of
// operations to be emitted to ensure we do the right thing at runtime
let a = _index.AbstractValue.createFromBinaryOp(realm, "!==", state, realm.intrinsics.null);
let b = _index.AbstractValue.createFromBinaryOp(realm, "!==", state, realm.intrinsics.undefined);
let c = _index.AbstractValue.createFromLogicalOp(realm, "&&", a, b);
(0, _invariant.default)(c instanceof _index.AbstractValue);
let newState = new _index.ObjectValue(realm, realm.intrinsics.ObjectPrototype);
let preludeGenerator = realm.preludeGenerator;
(0, _invariant.default)(preludeGenerator !== undefined); // we cannot use the standard Object.assign as partial state
// is not simple. however, given getDerivedStateFromProps is
// meant to be pure, we can assume that there are no getters on
// the partial abstract state
_index.AbstractValue.createTemporalObjectAssign(realm, newState, [prevState, state]);
newState.makeSimple();
newState.makePartial();
newState.makeFinal();
let conditional = _index.AbstractValue.createFromLogicalOp(realm, "&&", c, newState);
return conditional;
}
} else if (state !== realm.intrinsics.null && state !== realm.intrinsics.undefined) {
let newState = new _index.ObjectValue(realm, realm.intrinsics.ObjectPrototype);
try {
objectAssignCall(realm.intrinsics.undefined, [newState, prevState, state]);
} catch (e) {
if (realm.isInPureScope() && e instanceof _errors2.FatalError) {
let preludeGenerator = realm.preludeGenerator;
(0, _invariant.default)(preludeGenerator !== undefined);
_index.AbstractValue.createTemporalObjectAssign(realm, newState, [prevState, state]);
newState.makeSimple();
newState.makePartial();
return newState;
}
throw e;
}
newState.makeFinal();
return newState;
} else {
return null;
}
};
let newState = deriveState(partialState);
if (newState !== null) {
if (newState instanceof _index.AbstractValue) {
newState = _index.AbstractValue.createFromLogicalOp(realm, "||", newState, prevState);
}
(0, _invariant.default)(newState instanceof _index.Value);
_singletons.Properties.Set(realm, instance, "state", newState, true);
}
}
//# sourceMappingURL=components.js.map