mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-08-16 18:10:33 +03:00
Summary: Release notes: the side-effect detection system within pure scopes has been improved This PR revamps the entire `evaluatePure` system. The old system had so many small issues with it was the cause of many edge-cases that were hard to patch up. Specifically, the PR makes the following changes: - `realm.evaluatePure` -> `realm.evaluateWithPureScope` and now can only be used a single instance. Nesting further `realm.evaluateWithPureScope` calls will trigger an invariant and is strictly forbidden. Furthermore, it now only takes a single argument – the function you're wrapped around. - All places that used pure scope wrappers have been updated to conditionally enable it depending if the `realm.isInPureScope()` returns `true` or `false`. - `realm.evaluateFunctionForPureEffects` has been added, this works like `evaluateForEffects` except it requires a root function and a callback for side-effects. The callback for side-effects works like the old callback that was `evaluatePure`. - `realm.evaluateFunctionForPureEffectsInGlobalEnv` has been added a convenience wrapper around `realm.evaluateFunctionForPureEffects`. - When we leak bindings, we no longer set their value to `undefined` or `realm.intrinsics.undefined`. We now set the value to a special "leaked abstract", specifically – `realm.intrinsics.__leakedValue` – like `topValue` and `bottomValue`. Unsurprisingly, this now fixes a host of bugs that existed before. Including fixes for https://github.com/facebook/prepack/issues/2598, https://github.com/facebook/prepack/issues/2579, https://github.com/facebook/prepack/issues/2446, https://github.com/facebook/prepack/issues/2599 and probably many other issues too. The logic for detection of side-effects works very differently from before but after speaking to sebmarkbage last week, he pointed me in this direction to track side-effects rather than how we did it before. We now find side-effects from a given set of effects, rather than in an ad-hoc manor as mutations occur on objects/bindings. This PR requires https://github.com/facebook/prepack/pull/2596 as a dependency as it re-uses the logic. Closes https://github.com/facebook/prepack/pull/2587. Fixes https://github.com/facebook/prepack/issues/2598. Fixes https://github.com/facebook/prepack/issues/2579. Fixes https://github.com/facebook/prepack/issues/2446. Fixes https://github.com/facebook/prepack/issues/2599. Pull Request resolved: https://github.com/facebook/prepack/pull/2600 Differential Revision: D10368159 Pulled By: trueadm fbshipit-source-id: ded248f5cfd8648913cae9b9c697d723a82c59ab
217 lines
7.0 KiB
JavaScript
217 lines
7.0 KiB
JavaScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
/* @flow */
|
|
|
|
// NOTE:
|
|
// put the input fb-www file in ${root}/fb-www/input.js
|
|
// the compiled file will be saved to ${root}/fb-www/output.js
|
|
|
|
let prepackSources = require("../lib/prepack-node.js").prepackSources;
|
|
let path = require("path");
|
|
let { readFile, writeFile, existsSync } = require("fs");
|
|
let { promisify } = require("util");
|
|
let readFileAsync = promisify(readFile);
|
|
let writeFileAsync = promisify(writeFile);
|
|
let chalk = require("chalk");
|
|
let { Linter } = require("eslint");
|
|
let lintConfig = require("./lint-config");
|
|
|
|
let errorsCaptured = [];
|
|
|
|
function printError(error) {
|
|
let msg = `${error.errorCode}: ${error.message}`;
|
|
if (error.location) {
|
|
let loc_start = error.location.start;
|
|
let loc_end = error.location.end;
|
|
msg += ` at ${loc_start.line}:${loc_start.column} to ${loc_end.line}:${loc_end.column}`;
|
|
}
|
|
switch (error.severity) {
|
|
case "Information":
|
|
console.log(`Info: ${msg}`);
|
|
return "Recover";
|
|
case "Warning":
|
|
console.warn(`Warn: ${msg}`);
|
|
return "Recover";
|
|
case "RecoverableError":
|
|
case "FatalError":
|
|
console.error(`Error: ${msg}`);
|
|
return "Fail";
|
|
default:
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
let prepackOptions = {
|
|
errorHandler: diag => {
|
|
if (diag.severity === "Information") {
|
|
console.log(diag.message);
|
|
return "Recover";
|
|
}
|
|
errorsCaptured.push(diag);
|
|
if (diag.severity !== "Warning") {
|
|
if (diag.errorCode === "PP0025") {
|
|
// recover from `unable to evaluate "key" and "ref" on a ReactElement
|
|
return "Recover";
|
|
}
|
|
if (diag.errorCode === "PP0021") {
|
|
// recover from "Possible throw inside try/catch is not yet supported"
|
|
return "Recover";
|
|
}
|
|
return "Fail";
|
|
}
|
|
return "Recover";
|
|
},
|
|
compatibility: "fb-www",
|
|
internalDebug: true,
|
|
serialize: true,
|
|
uniqueSuffix: "",
|
|
maxStackDepth: 200,
|
|
instantRender: false,
|
|
reactEnabled: true,
|
|
reactOutput: "jsx",
|
|
reactVerbose: true,
|
|
reactFailOnUnsupportedSideEffects: true,
|
|
reactOptimizeNestedFunctions: true,
|
|
arrayNestedOptimizedFunctionsEnabled: true,
|
|
inlineExpressions: true,
|
|
invariantLevel: 0,
|
|
abstractValueImpliesMax: 1000,
|
|
stripFlow: true,
|
|
};
|
|
let inputPath = path.resolve("fb-www/input.js");
|
|
let outputPath = path.resolve("fb-www/output.js");
|
|
let componentsListPath = path.resolve("fb-www/components.txt");
|
|
let components = new Map();
|
|
let startTime = Date.now();
|
|
let uniqueEvaluatedComponents = 0;
|
|
|
|
function compileSource(source) {
|
|
let serialized;
|
|
try {
|
|
serialized = prepackSources([{ filePath: inputPath, fileContents: source, sourceMapContents: "" }], prepackOptions);
|
|
} catch (e) {
|
|
console.log(`\n${chalk.inverse(`=== Diagnostics Log ===`)}\n`);
|
|
errorsCaptured.forEach(error => printError(error));
|
|
throw e;
|
|
}
|
|
return {
|
|
stats: serialized.reactStatistics,
|
|
code: serialized.code,
|
|
};
|
|
}
|
|
|
|
async function readComponentsList() {
|
|
if (existsSync(componentsListPath)) {
|
|
let componentsList = await readFileAsync(componentsListPath, "utf8");
|
|
let componentNames = componentsList.split("\n");
|
|
|
|
for (let componentName of componentNames) {
|
|
components.set(componentName, "missing");
|
|
}
|
|
}
|
|
}
|
|
|
|
function lintCompiledSource(source) {
|
|
let linter = new Linter();
|
|
let errors = linter.verify(source, lintConfig);
|
|
if (errors.length > 0) {
|
|
console.log(`\n${chalk.inverse(`=== Validation Failed ===`)}\n`);
|
|
for (let error of errors) {
|
|
console.log(`${chalk.red(error.message)} ${chalk.gray(`(${error.line}:${error.column})`)}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function compileFile() {
|
|
let source = await readFileAsync(inputPath, "utf8");
|
|
let { stats, code } = await compileSource(source);
|
|
await writeFileAsync(outputPath, code);
|
|
lintCompiledSource(code);
|
|
// $FlowFixMe: no idea what this is about
|
|
return stats;
|
|
}
|
|
|
|
function printReactEvaluationGraph(evaluatedRootNode, depth) {
|
|
if (Array.isArray(evaluatedRootNode)) {
|
|
for (let child of evaluatedRootNode) {
|
|
printReactEvaluationGraph(child, depth);
|
|
}
|
|
} else {
|
|
let status = evaluatedRootNode.status.toLowerCase();
|
|
let message = evaluatedRootNode.message !== "" ? `: ${evaluatedRootNode.message}` : "";
|
|
let name = evaluatedRootNode.name;
|
|
let line;
|
|
if (status === "inlined") {
|
|
line = `${chalk.gray(`-`)} ${chalk.green(name)} ${chalk.gray(`(${status + message})`)}`;
|
|
} else if (status === "unknown_type" || status === "bail-out" || status === "fatal") {
|
|
line = `${chalk.gray(`-`)} ${chalk.red(name)} ${chalk.gray(`(${status + message})`)}`;
|
|
} else {
|
|
line = `${chalk.gray(`-`)} ${chalk.yellow(name)} ${chalk.gray(`(${status + message})`)}`;
|
|
}
|
|
if (components.has(name)) {
|
|
let currentStatus = components.get(name);
|
|
|
|
if (currentStatus === "missing") {
|
|
uniqueEvaluatedComponents++;
|
|
currentStatus = [currentStatus];
|
|
components.set(name, currentStatus);
|
|
} else if (Array.isArray(currentStatus)) {
|
|
currentStatus.push(status);
|
|
}
|
|
}
|
|
console.log(line.padStart(line.length + depth));
|
|
printReactEvaluationGraph(evaluatedRootNode.children, depth + 2);
|
|
}
|
|
}
|
|
|
|
readComponentsList()
|
|
.then(compileFile)
|
|
.then(result => {
|
|
console.log(`\n${chalk.inverse(`=== Compilation Complete ===`)}\n`);
|
|
console.log(chalk.bold(`Evaluated Tree:`));
|
|
printReactEvaluationGraph(result.evaluatedRootNodes, 0);
|
|
|
|
if (components.size > 0) {
|
|
console.log(`\n${chalk.inverse(`=== Status ===`)}\n`);
|
|
for (let [componentName, status] of components) {
|
|
if (status === "missing") {
|
|
console.log(`${chalk.red(`✖`)} ${componentName}`);
|
|
} else {
|
|
console.log(`${chalk.green(`✔`)} ${componentName}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`\n${chalk.inverse(`=== Summary ===`)}\n`);
|
|
if (components.size > 0) {
|
|
console.log(`${chalk.gray(`Optimized Components`)}: ${uniqueEvaluatedComponents}/${components.size}`);
|
|
}
|
|
console.log(`${chalk.gray(`Optimized Nodes`)}: ${result.componentsEvaluated}`);
|
|
console.log(`${chalk.gray(`Inlined Nodes`)}: ${result.inlinedComponents}`);
|
|
console.log(`${chalk.gray(`Optimized Trees`)}: ${result.optimizedTrees}`);
|
|
console.log(`${chalk.gray(`Optimized Nested Closures`)}: ${result.optimizedNestedClosures}`);
|
|
|
|
let timeTaken = Math.floor((Date.now() - startTime) / 1000);
|
|
console.log(`${chalk.gray(`Compile time`)}: ${timeTaken}s\n`);
|
|
// warning about ref and keys
|
|
console.log(`Warning: the build assumes that ref and key aren't being spread.`);
|
|
})
|
|
.catch(e => {
|
|
console.log(`\n${chalk.inverse(`=== Compilation Failed ===`)}\n`);
|
|
if (e.__isReconcilerFatalError) {
|
|
console.error(e.message + "\n");
|
|
printReactEvaluationGraph(e.evaluatedNode, 0);
|
|
} else {
|
|
console.error(e.nativeStack || e.stack);
|
|
}
|
|
process.exit(1);
|
|
});
|