2018-02-23 04:46:50 +03:00
|
|
|
/**
|
|
|
|
* 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");
|
2018-03-15 02:18:19 +03:00
|
|
|
let { readFile, writeFile, existsSync } = require("fs");
|
2018-02-23 04:46:50 +03:00
|
|
|
let { promisify } = require("util");
|
|
|
|
let readFileAsync = promisify(readFile);
|
|
|
|
let writeFileAsync = promisify(writeFile);
|
2018-03-15 02:18:19 +03:00
|
|
|
let chalk = require("chalk");
|
2018-03-15 16:37:05 +03:00
|
|
|
let { Linter } = require("eslint");
|
2018-05-23 01:29:28 +03:00
|
|
|
let lintConfig = require("./lint-config");
|
2018-02-23 04:46:50 +03:00
|
|
|
|
2018-03-04 13:07:37 +03:00
|
|
|
let errorsCaptured = [];
|
|
|
|
|
2018-03-21 16:40:52 +03:00
|
|
|
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":
|
2018-06-13 11:00:44 +03:00
|
|
|
console.error(`Error: ${msg}`);
|
2018-03-21 16:40:52 +03:00
|
|
|
return "Fail";
|
|
|
|
default:
|
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 04:46:50 +03:00
|
|
|
let prepackOptions = {
|
2018-03-04 13:07:37 +03:00
|
|
|
errorHandler: diag => {
|
2018-03-15 02:18:19 +03:00
|
|
|
if (diag.severity === "Information") {
|
|
|
|
console.log(diag.message);
|
|
|
|
return "Recover";
|
|
|
|
}
|
2018-03-21 16:40:52 +03:00
|
|
|
errorsCaptured.push(diag);
|
2018-03-15 02:18:19 +03:00
|
|
|
if (diag.severity !== "Warning") {
|
2018-03-21 21:23:09 +03:00
|
|
|
if (diag.errorCode === "PP0025") {
|
2018-05-26 04:15:52 +03:00
|
|
|
// recover from `unable to evaluate "key" and "ref" on a ReactElement
|
2018-03-21 21:23:09 +03:00
|
|
|
return "Recover";
|
|
|
|
}
|
2018-03-07 01:09:03 +03:00
|
|
|
return "Fail";
|
|
|
|
}
|
|
|
|
return "Recover";
|
2018-03-04 13:07:37 +03:00
|
|
|
},
|
2018-02-23 04:46:50 +03:00
|
|
|
compatibility: "fb-www",
|
|
|
|
internalDebug: true,
|
|
|
|
serialize: true,
|
|
|
|
uniqueSuffix: "",
|
|
|
|
maxStackDepth: 100,
|
|
|
|
reactEnabled: true,
|
|
|
|
reactOutput: "jsx",
|
2018-03-15 02:18:19 +03:00
|
|
|
reactVerbose: true,
|
2018-05-31 00:30:56 +03:00
|
|
|
reactOptimizeNestedFunctions: false,
|
2018-02-23 04:46:50 +03:00
|
|
|
inlineExpressions: true,
|
2018-04-12 21:24:04 +03:00
|
|
|
invariantLevel: 0,
|
Adds k limiting counter to simplification logic
Summary:
Release notes: adds an abstract value limiting counter
Following some discussion I had the other night with hermanventer in regards to having a counter for Prepack abstract logic computation, so rather than hang the compilation process, we could throw and recover using the pure logic.
This PR adds a "k-limiting" counter to `AbstractValue`'s as a method `_checkAbstractValueImpliesCounter `. When we try and resolve abstract conditions on a single AbstractValue and we reach the limit, a `CompilerDiagnostic` along with a `FatalError` gets thrown. Locally this works well for the React compiler input.
I only call `_checkAbstractValueImpliesCounter ` from `implies` right now and that might not be the best place, maybe it should be checked in other places too, but `implies` seemed the good place as it was in all stackframes when debugging.
This also adds a new option called `abstractValueImpliesCounter ` which is based as an option to Realm. By default this feature is not turned on when the option `abstractValueImpliesCounter ` value is `0` (which it has been set to). Maybe we should enable this by default and set it to a sensible level?
**Note:**
I tried using the `timeout` feature, but it didn't work and wasn't too reliable in general because it would always fire in places where I didn't care for "time taken" but rather the depth on a certain feature of computation – in this case, abstract logic simplification.
Enabling the timeout causes many other parts of compilation to break unless I explicitly set the timeout to `0` through all the React reconciliation work, which kind of defeats the purpose of it (the React reconciler work can take minutes to deal with on very large, expensive trees, and this is totally expected).
Furthermore, we should avoid using non-deterministic features like `feature` in such cases as they can skew test results.
Closes https://github.com/facebook/prepack/pull/1607
Differential Revision: D7312616
Pulled By: trueadm
fbshipit-source-id: 8763df09b3182af4a496dec42d507aff1bb2cc8d
2018-03-17 03:25:19 +03:00
|
|
|
abstractValueImpliesMax: 1000,
|
2018-02-23 04:46:50 +03:00
|
|
|
};
|
|
|
|
let inputPath = path.resolve("fb-www/input.js");
|
|
|
|
let outputPath = path.resolve("fb-www/output.js");
|
2018-03-15 02:18:19 +03:00
|
|
|
let componentsListPath = path.resolve("fb-www/components.txt");
|
|
|
|
let components = new Map();
|
|
|
|
let startTime = Date.now();
|
|
|
|
let uniqueEvaluatedComponents = 0;
|
2018-02-23 04:46:50 +03:00
|
|
|
|
|
|
|
function compileSource(source) {
|
2018-03-04 13:07:37 +03:00
|
|
|
let serialized;
|
|
|
|
try {
|
2018-03-16 21:41:50 +03:00
|
|
|
serialized = prepackSources([{ filePath: inputPath, fileContents: source, sourceMapContents: "" }], prepackOptions);
|
2018-03-04 13:07:37 +03:00
|
|
|
} catch (e) {
|
2018-03-21 16:40:52 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Diagnostics Log ===`)}\n`);
|
|
|
|
errorsCaptured.forEach(error => printError(error));
|
2018-03-04 13:07:37 +03:00
|
|
|
throw e;
|
|
|
|
}
|
2018-02-23 04:46:50 +03:00
|
|
|
return {
|
|
|
|
stats: serialized.reactStatistics,
|
|
|
|
code: serialized.code,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-03-15 02:18:19 +03:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:37:05 +03:00
|
|
|
function lintCompiledSource(source) {
|
|
|
|
let linter = new Linter();
|
2018-05-23 01:29:28 +03:00
|
|
|
let errors = linter.verify(source, lintConfig);
|
2018-03-15 16:37:05 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 04:46:50 +03:00
|
|
|
async function compileFile() {
|
|
|
|
let source = await readFileAsync(inputPath, "utf8");
|
|
|
|
let { stats, code } = await compileSource(source);
|
|
|
|
await writeFileAsync(outputPath, code);
|
2018-03-15 16:37:05 +03:00
|
|
|
lintCompiledSource(code);
|
2018-03-19 21:22:15 +03:00
|
|
|
// $FlowFixMe: no idea what this is about
|
2018-02-23 04:46:50 +03:00
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
2018-03-01 02:03:14 +03:00
|
|
|
function printReactEvaluationGraph(evaluatedRootNode, depth) {
|
|
|
|
if (Array.isArray(evaluatedRootNode)) {
|
|
|
|
for (let child of evaluatedRootNode) {
|
|
|
|
printReactEvaluationGraph(child, depth);
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-08 03:55:51 +03:00
|
|
|
let status = evaluatedRootNode.status.toLowerCase();
|
|
|
|
let message = evaluatedRootNode.message !== "" ? `: ${evaluatedRootNode.message}` : "";
|
2018-03-15 02:18:19 +03:00
|
|
|
let name = evaluatedRootNode.name;
|
|
|
|
let line;
|
|
|
|
if (status === "inlined") {
|
|
|
|
line = `${chalk.gray(`-`)} ${chalk.green(name)} ${chalk.gray(`(${status + message})`)}`;
|
2018-05-10 17:27:27 +03:00
|
|
|
} else if (status === "unknown_type" || status === "bail-out" || status === "fatal") {
|
2018-03-15 02:18:19 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2018-03-01 02:03:14 +03:00
|
|
|
console.log(line.padStart(line.length + depth));
|
|
|
|
printReactEvaluationGraph(evaluatedRootNode.children, depth + 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 02:18:19 +03:00
|
|
|
readComponentsList()
|
|
|
|
.then(compileFile)
|
2018-02-23 04:46:50 +03:00
|
|
|
.then(result => {
|
2018-03-15 02:18:19 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Compilation Complete ===`)}\n`);
|
|
|
|
console.log(chalk.bold(`Evaluated Tree:`));
|
2018-03-01 02:03:14 +03:00
|
|
|
printReactEvaluationGraph(result.evaluatedRootNodes, 0);
|
2018-03-15 02:18:19 +03:00
|
|
|
|
|
|
|
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}`);
|
2018-03-22 20:12:54 +03:00
|
|
|
console.log(`${chalk.gray(`Optimized Nested Closures`)}: ${result.optimizedNestedClosures}`);
|
2018-03-15 02:18:19 +03:00
|
|
|
|
|
|
|
let timeTaken = Math.floor((Date.now() - startTime) / 1000);
|
|
|
|
console.log(`${chalk.gray(`Compile time`)}: ${timeTaken}s\n`);
|
2018-03-21 21:23:09 +03:00
|
|
|
// warning about ref and keys
|
|
|
|
console.log(`Warning: the build assumes that ref and key aren't being spread.`);
|
2018-02-23 04:46:50 +03:00
|
|
|
})
|
|
|
|
.catch(e => {
|
2018-03-21 16:40:52 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Compilation Failed ===`)}\n`);
|
2018-05-10 17:27:27 +03:00
|
|
|
if (e.__isReconcilerFatalError) {
|
|
|
|
console.error(e.message + "\n");
|
|
|
|
printReactEvaluationGraph(e.evaluatedNode, 0);
|
|
|
|
} else {
|
|
|
|
console.error(e.nativeStack || e.stack);
|
|
|
|
}
|
2018-02-23 04:46:50 +03:00
|
|
|
process.exit(1);
|
|
|
|
});
|