UNPKG

prepack

Version:

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

406 lines (383 loc) 18.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Emitter = undefined; var _index = require("../values/index.js"); var _generator = require("../utils/generator.js"); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _types = require("./types.js"); var _ResidualFunctions = require("./ResidualFunctions.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // 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. // Type used to configure callbacks from the dependenciesVisitor of the Emitter. class Emitter { constructor(residualFunctions, referencedDeclaredValues) { 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; 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 => { // If the value hasn't been declared yet, then we should wait for it. if (!this.cannotDeclare() && !this.hasBeenDeclared(val) && (!this.emittingToAdditionalFunction() || referencedDeclaredValues.get(val) !== undefined)) return val;else return undefined; } }; } // Contains all the active generator bodies in stack order. // 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, _invariant2.default)(!this._finalized); this._activeStack.push(dependency); if (dependency instanceof _index.Value) { (0, _invariant2.default)(!this._activeValues.has(dependency)); this._activeValues.add(dependency); } else if (dependency instanceof _generator.Generator) { (0, _invariant2.default)(!this._activeGeneratorStack.includes(targetBody)); this._activeGeneratorStack.push(targetBody); } if (isChild) { (0, _invariant2.default)(targetBody.type === "Generator" || targetBody.type === "ConditionalAssignmentBranch"); targetBody.parentBody = this._body; targetBody.nestingLevel = (this._body.nestingLevel || 0) + 1; } let oldBody = this._body; this._body = targetBody; return oldBody; } emit(statement) { (0, _invariant2.default)(!this._finalized); this._body.entries.push(statement); 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. endEmitting(dependency, oldBody, isChild = false) { (0, _invariant2.default)(!this._finalized); let lastDependency = this._activeStack.pop(); (0, _invariant2.default)(dependency === lastDependency); if (dependency instanceof _index.Value) { (0, _invariant2.default)(this._activeValues.has(dependency)); this._activeValues.delete(dependency); this._processValue(dependency); } else if (dependency instanceof _generator.Generator) { (0, _invariant2.default)(this._isEmittingActiveGenerator()); this._activeGeneratorStack.pop(); } let lastBody = this._body; this._body = oldBody; if (isChild) { (0, _invariant2.default)(lastBody.parentBody === oldBody); (0, _invariant2.default)((lastBody.nestingLevel || 0) > 0); (0, _invariant2.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.declaredAbstractValues) { let anyPropagated = true; for (let b = lastBody; b.done && b.parentBody !== undefined && anyPropagated; b = b.parentBody) { anyPropagated = false; let parentDeclaredAbstractValues = b.parentBody.declaredAbstractValues; if (parentDeclaredAbstractValues === undefined) b.parentBody.declaredAbstractValues = parentDeclaredAbstractValues = new Map(); (0, _invariant2.default)(b.declaredAbstractValues); for (let [key, value] of b.declaredAbstractValues) { if (!parentDeclaredAbstractValues.has(key)) { parentDeclaredAbstractValues.set(key, value); this._processValue(key); anyPropagated = true; } } } } } return lastBody; } finalize() { (0, _invariant2.default)(!this._finalized); (0, _invariant2.default)(this._activeGeneratorStack.length === 1); (0, _invariant2.default)(this._activeGeneratorStack[0] === this._body); (0, _invariant2.default)(this._body === this._mainBody); this._processCurrentBody(); this._activeGeneratorStack.pop(); this._finalized = true; (0, _invariant2.default)(this._waitingForBodies.size === 0); (0, _invariant2.default)(this._waitingForValues.size === 0); (0, _invariant2.default)(this._activeStack.length === 0); (0, _invariant2.default)(this._activeValues.size === 0); (0, _invariant2.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, _invariant2.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 === "AdditionalFunction"; } _processCurrentBody() { if (!this._isEmittingActiveGenerator()) { return; } let a = this._waitingForBodies.get(this._body); if (a === undefined) return; while (a.length > 0) { let { dependencies, func } = a.shift(); this.emitNowOrAfterWaitingForDependencies(dependencies, func); } this._waitingForBodies.delete(this._body); } _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, _invariant2.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 `declaredAbstractValues` in bodies) getReasonToWaitForDependencies(dependencies) { return this.dependenciesVisitor(dependencies, this._getReasonToWaitForDependenciesCallbacks); } // Visitor of dependencies that require delaying serialization dependenciesVisitor(dependencies, callbacks) { (0, _invariant2.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)) { // 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; } result = recurse(val.args); 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) { let kind = val.getKind(); switch (kind) { case "Object": let proto = val.$Prototype; if (proto instanceof _index.ObjectValue) { result = recurse(val.$Prototype); if (result !== undefined) return result; } break; case "Date": (0, _invariant2.default)(val.$DateValue !== undefined); result = recurse(val.$DateValue); 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, _invariant2.default)(!this._finalized); (0, _invariant2.default)(this._activeValues.has(value)); return condition ? value : undefined; } _shouldEmitWithoutWaiting(delayReason, targetBody) { /** * We can directly emit without waiting if: * 1. No delayReason * 2. delayReason is a generator body while the target body we are not emitting into is not a generator body. */ return !delayReason || !(delayReason instanceof _index.Value) && this._isGeneratorBody(delayReason) && targetBody !== undefined && !this._isGeneratorBody(targetBody); } emitAfterWaiting(delayReason, dependencies, func, targetBody) { if (this._shouldEmitWithoutWaiting(delayReason, targetBody)) { if (targetBody === undefined || targetBody === this._body) { // Emit into current body. func(); } else { (0, _invariant2.default)(!this._isGeneratorBody(targetBody)); const oldBody = this.beginEmitting(targetBody.type, targetBody); func(); this.endEmitting(targetBody.type, oldBody); } } else { (0, _invariant2.default)(delayReason !== undefined); if (delayReason instanceof _index.Value) { this._emitAfterWaitingForValue(delayReason, dependencies, targetBody === undefined ? this._body : targetBody, func); } else if (this._isGeneratorBody(delayReason)) { // delayReason is a generator body. this._emitAfterWaitingForGeneratorBody(delayReason, dependencies, func); } else { // Unknown delay reason. (0, _invariant2.default)(false); } } } _emitAfterWaitingForValue(reason, dependencies, targetBody, func) { (0, _invariant2.default)(!this._finalized); (0, _invariant2.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, _invariant2.default)(this._isGeneratorBody(reason)); (0, _invariant2.default)(!this._finalized); (0, _invariant2.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) { (0, _invariant2.default)(!this._finalized); this.emitAfterWaiting(this.getReasonToWaitForDependencies(dependencies), dependencies, func, targetBody); } declare(value) { (0, _invariant2.default)(!this._finalized); (0, _invariant2.default)(!this._activeValues.has(value)); (0, _invariant2.default)(value.hasIdentifier()); (0, _invariant2.default)(this._isEmittingActiveGenerator()); (0, _invariant2.default)(!this.cannotDeclare()); (0, _invariant2.default)(!this._body.done); if (this._body.declaredAbstractValues === undefined) this._body.declaredAbstractValues = new Map(); this._body.declaredAbstractValues.set(value, this._body); this._processValue(value); } emittingToAdditionalFunction() { // Whether we are directly or indirectly emitting to an additional function for (let b = this._body; b !== undefined; b = b.parentBody) if (b.type === "AdditionalFunction") return true; return false; } 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.declaredAbstractValues !== undefined && b.declaredAbstractValues.has(value)) { return b; } return undefined; } declaredCount() { let declaredAbstractValues = this._body.declaredAbstractValues; return declaredAbstractValues === undefined ? 0 : declaredAbstractValues.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, _invariant2.default)(!this._finalized); return new _types.BodyReference(this._body, this._body.entries.length); } } exports.Emitter = Emitter; /** * 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. */ //# sourceMappingURL=Emitter.js.map