UNPKG

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
"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