UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

563 lines (448 loc) 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Emitter = void 0; var _index = require("../values/index.js"); var _generator = require("../utils/generator.js"); var _invariant = _interopRequireDefault(require("../invariant.js")); var _types = require("./types.js"); var _ResidualFunctions = require("./ResidualFunctions.js"); var _utils = require("../react/utils.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * 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. */ // The emitter keeps track of a stack of what's currently being emitted. // There are two kinds of interesting dependencies the emitter is dealing with: // 1. Value dependencies: // If an emission task depends on the result of another emission task which // is still currently being emitted, then the emission task must be performed later, // once the dependency is available. // To this end, the emitter maintains the `_activeValues` and `_waitingForValues` datastructures. // 2. Generator dependencies: // For each generator, there's a corresponding "body", i.e. a stream of babel statements // that the emitter is appending to. // There's always a "current" body that is currently being emitted to. // There's also a distinguished `mainBody` to which all statements get directly or indirectly appended. // If there are multiple generators/bodies involved, then they form a stack. // Nested bodies are usually composed into an instruction emitted to the outer body. // For example, two nested generators may yield the then and else-branch of an `if` statement. // When an emission is supposed to target a body that is the current body, i.e. when it sits // lower on the stack, then the emission task gets delayed until the next emission task on // the lower body entry is finished. // To this end, the emitter maintains the `_activeGeneratorStack` and `_waitingForBodies` datastructures. class Emitter { constructor(residualFunctions, referencedDeclaredValues, conditionalFeasibility, derivedIds) { this._mainBody = { type: "MainGenerator", parentBody: undefined, entries: [], done: false }; this._waitingForValues = new Map(); this._waitingForBodies = new Map(); this._body = this._mainBody; this._residualFunctions = residualFunctions; this._activeStack = []; this._activeValues = new Set(); this._activeGeneratorStack = [this._mainBody]; this._finalized = false; let mustWaitForValue = val => { if (this.cannotDeclare()) return false; if (this.hasBeenDeclared(val)) return false; let activeOptimizedFunction = this.getActiveOptimizedFunction(); if (activeOptimizedFunction === undefined) return true; let optimizedFunctionWhereValueWasDeclared = referencedDeclaredValues.get(val); return optimizedFunctionWhereValueWasDeclared === activeOptimizedFunction; }; this._getReasonToWaitForDependenciesCallbacks = { onActive: val => val, // cyclic dependency; we need to wait until this value has finished emitting onFunction: val => { // Functions are currently handled in a special way --- they are all defined ahead of time. Thus, we never have to wait for functions. this._residualFunctions.addFunctionUsage(val, this.getBodyReference()); return undefined; }, onAbstractValueWithIdentifier: val => derivedIds.has(val.getIdentifier()) && mustWaitForValue(val) ? val : undefined, onIntrinsicDerivedObject: val => mustWaitForValue(val) ? val : undefined }; this._conditionalFeasibility = conditionalFeasibility; } // Begin to emit something. Such sessions can be nested. // The dependency indicates what is being emitted; until this emission ends, other parties might have to wait for the dependency. // The targetBody is a wrapper that holds the sequence of statements that are going to be emitted. // If isChild, then we are starting a new emitting session as a branch off the previously active emitting session. beginEmitting(dependency, targetBody, isChild = false) { (0, _invariant.default)(!this._finalized); (0, _invariant.default)(targetBody.type === "OptimizedFunction" === !!targetBody.optimizedFunction); this._activeStack.push(dependency); if (dependency instanceof _index.Value) { (0, _invariant.default)(!this._activeValues.has(dependency)); this._activeValues.add(dependency); } else if (dependency instanceof _generator.Generator) { (0, _invariant.default)(!this._activeGeneratorStack.includes(targetBody)); this._activeGeneratorStack.push(targetBody); } if (isChild) { targetBody.parentBody = this._body; targetBody.nestingLevel = (this._body.nestingLevel || 0) + 1; } let oldBody = this._body; this._body = targetBody; return oldBody; } emit(statement) { (0, _invariant.default)(!this._finalized); this._body.entries.push(statement); this._processCurrentBody(); } finalizeCurrentBody() { (0, _invariant.default)(!this._finalized); this._processCurrentBody(); } // End to emit something. The parameters dependency and isChild must match a previous call to beginEmitting. // oldBody should be the value returned by the previous matching beginEmitting call. // valuesToProcess is filled with values that have been newly declared since the last corresponding beginEmitting call; // other values not yet have been emitted as they might be waiting for valuesToProcess; // processValues(valuesToProcess) should be called once the returned body has been embedded in the outer context. endEmitting(dependency, oldBody, valuesToProcess, isChild = false) { (0, _invariant.default)(!this._finalized); let lastDependency = this._activeStack.pop(); (0, _invariant.default)(dependency === lastDependency); if (dependency instanceof _index.Value) { (0, _invariant.default)(this._activeValues.has(dependency)); this._activeValues.delete(dependency); this._processValue(dependency); } else if (dependency instanceof _generator.Generator) { (0, _invariant.default)(this._isEmittingActiveGenerator()); this._activeGeneratorStack.pop(); } let lastBody = this._body; this._body = oldBody; if (isChild) { (0, _invariant.default)(lastBody.parentBody === oldBody); (0, _invariant.default)((lastBody.nestingLevel || 0) > 0); (0, _invariant.default)(!lastBody.done); lastBody.done = true; // When we are done processing a body, we can propogate all declared abstract values // to its parent, possibly unlocking further processing... if (lastBody.declaredValues) { let anyPropagated = true; for (let b = lastBody; b.done && b.parentBody !== undefined && anyPropagated; b = b.parentBody) { anyPropagated = false; let parentDeclaredValues = b.parentBody.declaredValues; if (parentDeclaredValues === undefined) b.parentBody.declaredValues = parentDeclaredValues = new Map(); (0, _invariant.default)(b.declaredValues); for (let [key, value] of b.declaredValues) { if (!parentDeclaredValues.has(key)) { parentDeclaredValues.set(key, value); if (valuesToProcess !== undefined) valuesToProcess.add(key); anyPropagated = true; } } } } } return lastBody; } processValues(valuesToProcess) { for (let value of valuesToProcess) this._processValue(value); } finalize() { (0, _invariant.default)(!this._finalized); (0, _invariant.default)(this._activeGeneratorStack.length === 1); (0, _invariant.default)(this._activeGeneratorStack[0] === this._body); (0, _invariant.default)(this._body === this._mainBody); this._processCurrentBody(); this._activeGeneratorStack.pop(); this._finalized = true; (0, _invariant.default)(this._waitingForBodies.size === 0); (0, _invariant.default)(this._waitingForValues.size === 0); (0, _invariant.default)(this._activeStack.length === 0); (0, _invariant.default)(this._activeValues.size === 0); (0, _invariant.default)(this._activeGeneratorStack.length === 0); } /** * Emitter is emitting in two modes: * 1. Emitting to entries in current active generator * 2. Emitting to body of another scope(generator or residual function) * This function checks the first condition above. */ _isEmittingActiveGenerator() { (0, _invariant.default)(this._activeGeneratorStack.length > 0); return this._activeGeneratorStack[this._activeGeneratorStack.length - 1] === this._body; } _isGeneratorBody(body) { return body.type === "MainGenerator" || body.type === "Generator" || body.type === "OptimizedFunction"; } _processCurrentBody() { if (!this._isEmittingActiveGenerator() || this._body.processing) { return; } let a = this._waitingForBodies.get(this._body); if (a === undefined) return; this._body.processing = true; while (a.length > 0) { let { dependencies, func } = a.shift(); this.emitNowOrAfterWaitingForDependencies(dependencies, func, this._body); } this._waitingForBodies.delete(this._body); this._body.processing = false; } _processValue(value) { let a = this._waitingForValues.get(value); if (a === undefined) return; let currentBody = this._body; while (a.length > 0) { let { body, dependencies, func } = a.shift(); // If body is not generator body no need to wait for it. if (this._isGeneratorBody(body) && body !== currentBody) { this._emitAfterWaitingForGeneratorBody(body, dependencies, func); } else { this.emitNowOrAfterWaitingForDependencies(dependencies, func, body); } } this._waitingForValues.delete(value); } // Find the first ancestor in input generator body stack that is in current active stack. // It can always find one because the bottom one in the stack is the main generator. _getFirstAncestorGeneratorWithActiveBody(bodyStack) { const activeBody = bodyStack.slice().reverse().find(body => this._activeGeneratorStack.includes(body)); (0, _invariant.default)(activeBody); return activeBody; } // Serialization of a statement related to a value MUST be delayed if // the creation of the value's identity requires the availability of either: // 1. a value that is also currently being serialized // (tracked by `_activeValues`). // 2. a time-dependent value that is declared by some generator entry // that has not yet been processed // (tracked by `declaredValues` in bodies) getReasonToWaitForDependencies(dependencies) { return this.dependenciesVisitor(dependencies, this._getReasonToWaitForDependenciesCallbacks); } // Visitor of dependencies that require delaying serialization dependenciesVisitor(dependencies, callbacks) { (0, _invariant.default)(!this._finalized); let result; let recurse = value => this.dependenciesVisitor(value, callbacks); if (Array.isArray(dependencies)) { let values = dependencies; for (let value of values) { result = recurse(value); if (result !== undefined) return result; } return undefined; } let val = dependencies; if (this._activeValues.has(val)) { // If a value is active and it's a function, then we still shouldn't wait on it. if (val instanceof _index.FunctionValue && !(val instanceof _index.BoundFunctionValue)) { // We ran into a function value. result = callbacks.onFunction ? callbacks.onFunction(val) : undefined; return result; } // We ran into a cyclic dependency, where the value we are dependending on is still in the process of being emitted. result = callbacks.onActive ? callbacks.onActive(val) : undefined; if (result !== undefined) return result; } if (val instanceof _index.BoundFunctionValue) { result = recurse(val.$BoundTargetFunction); if (result !== undefined) return result; result = recurse(val.$BoundThis); if (result !== undefined) return result; result = recurse(val.$BoundArguments); if (result !== undefined) return result; } else if (val instanceof _index.FunctionValue) { // We ran into a function value. result = callbacks.onFunction ? callbacks.onFunction(val) : undefined; if (result !== undefined) return result; } else if (val instanceof _index.AbstractValue) { if (val.hasIdentifier()) { // We ran into an abstract value that might have to be declared. result = callbacks.onAbstractValueWithIdentifier ? callbacks.onAbstractValueWithIdentifier(val) : undefined; if (result !== undefined) return result; } let argsToRecurse; if (val.kind === "conditional") { let cf = this._conditionalFeasibility.get(val); (0, _invariant.default)(cf !== undefined); argsToRecurse = []; if (cf.t && cf.f) argsToRecurse.push(val.args[0]); if (cf.t) argsToRecurse.push(val.args[1]); if (cf.f) argsToRecurse.push(val.args[2]); } else argsToRecurse = val.args; result = recurse(argsToRecurse); if (result !== undefined) return result; } else if (val instanceof _index.ProxyValue) { result = recurse(val.$ProxyTarget); if (result !== undefined) return result; result = recurse(val.$ProxyHandler); if (result !== undefined) return result; } else if (val instanceof _index.SymbolValue) { if (val.$Description instanceof _index.Value) { result = recurse(val.$Description); if (result !== undefined) return result; } } else if (val instanceof _index.ObjectValue && _index.ObjectValue.isIntrinsicDerivedObject(val)) { result = callbacks.onIntrinsicDerivedObject ? callbacks.onIntrinsicDerivedObject(val) : undefined; if (result !== undefined) return result; } else if (val instanceof _index.ObjectValue) { let kind = val.getKind(); switch (kind) { case "Object": let proto = val.$Prototype; if (proto instanceof _index.ObjectValue && // if this is falsy, prototype chain might be cyclic proto.usesOrdinaryObjectInternalPrototypeMethods()) { result = recurse(val.$Prototype); if (result !== undefined) return result; } break; case "Date": (0, _invariant.default)(val.$DateValue !== undefined); result = recurse(val.$DateValue); if (result !== undefined) return result; break; case "ReactElement": let realm = val.$Realm; let type = (0, _utils.getProperty)(realm, val, "type"); let props = (0, _utils.getProperty)(realm, val, "props"); let key = (0, _utils.getProperty)(realm, val, "key"); let ref = (0, _utils.getProperty)(realm, val, "ref"); result = recurse(type); if (result !== undefined) return result; result = recurse(props); if (result !== undefined) return result; result = recurse(key); if (result !== undefined) return result; result = recurse(ref); if (result !== undefined) return result; break; default: break; } } return undefined; } // Wait for a known-to-be active value if a condition is met. getReasonToWaitForActiveValue(value, condition) { (0, _invariant.default)(!this._finalized); (0, _invariant.default)(this._activeValues.has(value)); return condition ? value : undefined; } emitAfterWaiting(delayReason, dependencies, func, targetBody) { if (delayReason === undefined && this._isGeneratorBody(targetBody)) { delayReason = targetBody; } if (delayReason === undefined || delayReason === this._body) { if (targetBody === this._body) { // Emit into current body. func(); } else { (0, _invariant.default)(!this._isGeneratorBody(targetBody)); // TODO: Check if effects really don't matter here, // since we are going to emit something in an out-of-band body // that might depend on applied effects. const oldBody = this.beginEmitting(targetBody.type, targetBody); func(); this.endEmitting(targetBody.type, oldBody); } } else { (0, _invariant.default)(delayReason !== undefined); if (delayReason instanceof _index.Value) { this._emitAfterWaitingForValue(delayReason, dependencies, targetBody, func); } else if (this._isGeneratorBody(delayReason)) { // delayReason is a generator body. this._emitAfterWaitingForGeneratorBody(delayReason, dependencies, func); } else { // Unknown delay reason. (0, _invariant.default)(false); } } } _emitAfterWaitingForValue(reason, dependencies, targetBody, func) { (0, _invariant.default)(!this._finalized); (0, _invariant.default)(!(reason instanceof _index.AbstractValue && this.hasBeenDeclared(reason)) || this._activeValues.has(reason)); let a = this._waitingForValues.get(reason); if (a === undefined) this._waitingForValues.set(reason, a = []); a.push({ body: targetBody, dependencies, func }); } _emitAfterWaitingForGeneratorBody(reason, dependencies, func) { (0, _invariant.default)(this._isGeneratorBody(reason)); (0, _invariant.default)(!this._finalized); (0, _invariant.default)(this._activeGeneratorStack.includes(reason)); let b = this._waitingForBodies.get(reason); if (b === undefined) { this._waitingForBodies.set(reason, b = []); } b.push({ dependencies, func }); } emitNowOrAfterWaitingForDependencies(dependencies, func, targetBody) { this.emitAfterWaiting(this.getReasonToWaitForDependencies(dependencies), dependencies, func, targetBody); } declare(value) { (0, _invariant.default)(!this._finalized); (0, _invariant.default)(!this._activeValues.has(value)); (0, _invariant.default)(value instanceof _index.ObjectValue || value.hasIdentifier()); (0, _invariant.default)(this._isEmittingActiveGenerator()); (0, _invariant.default)(!this.cannotDeclare()); (0, _invariant.default)(!this._body.done); if (this._body.declaredValues === undefined) this._body.declaredValues = new Map(); this._body.declaredValues.set(value, this._body); this._processValue(value); } getActiveOptimizedFunction() { // Whether we are directly or indirectly emitting to an optimized function for (let b = this._body; b !== undefined; b = b.parentBody) if (b.type === "OptimizedFunction") return b.optimizedFunction; return undefined; } cannotDeclare() { // Bodies of the following types will never contain any (temporal) abstract value declarations. return this._body.type === "DelayInitializations" || this._body.type === "LazyObjectInitializer"; } hasBeenDeclared(value) { return this.getDeclarationBody(value) !== undefined; } getDeclarationBody(value) { for (let b = this._body; b !== undefined; b = b.parentBody) { if (b.declaredValues !== undefined && b.declaredValues.has(value)) { return b; } } return undefined; } declaredCount() { let declaredValues = this._body.declaredValues; return declaredValues === undefined ? 0 : declaredValues.size; } getBody() { return this._body; } isCurrentBodyOffspringOf(targetBody) { let currentBody = this._body; while (currentBody !== undefined) { if (currentBody === targetBody) { return true; } currentBody = currentBody.parentBody; } return false; } getBodyReference() { (0, _invariant.default)(!this._finalized); return new _types.BodyReference(this._body, this._body.entries.length); } } exports.Emitter = Emitter; //# sourceMappingURL=Emitter.js.map