create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
603 lines (501 loc) • 17 kB
JavaScript
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
"use strict";
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr)))
return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (
var _i = arr[Symbol.iterator](), _s;
!(_n = (_s = _i.next()).done);
_n = true
) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _toConsumableArray(arr) {
return (
_arrayWithoutHoles(arr) ||
_iterableToArray(arr) ||
_unsupportedIterableToArray(arr) ||
_nonIterableSpread()
);
}
function _nonIterableSpread() {
throw new TypeError(
"Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter))
return Array.from(iter);
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly)
symbols = symbols.filter(function(sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function(key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function(key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key)
);
});
}
}
return target;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const chalk = require("chalk");
const logToConsole = require("./logToConsole");
const path = require("path");
const reporting = require("./reporting");
const throttle = require("lodash.throttle");
const _require = require("metro-core"),
AmbiguousModuleResolutionError = _require.AmbiguousModuleResolutionError;
const GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT =
"The global cache is now disabled because %s";
const DARK_BLOCK_CHAR = "\u2593";
const LIGHT_BLOCK_CHAR = "\u2591";
const MAX_PROGRESS_BAR_CHAR_WIDTH = 16;
/**
* We try to print useful information to the terminal for interactive builds.
* This implements the `Reporter` interface from the './reporting' module.
*/
class TerminalReporter {
/**
* The bundle builds for which we are actively maintaining the status on the
* terminal, ie. showing a progress bar. There can be several bundles being
* built at the same time.
*/
constructor(terminal) {
this._activeBundles = new Map();
this._scheduleUpdateBundleProgress = throttle(data => {
this.update(
_objectSpread(
_objectSpread({}, data),
{},
{
type: "bundle_transform_progressed_throttled"
}
)
);
}, 100);
this.terminal = terminal;
}
/**
* Construct a message that represents the progress of a
* single bundle build, for example:
*
* BUNDLE path/to/bundle.js ▓▓▓▓▓░░░░░░░░░░░ 36.6% (4790/7922)
*/
_getBundleStatusMessage(_ref, phase) {
let _ref$bundleDetails = _ref.bundleDetails,
entryFile = _ref$bundleDetails.entryFile,
bundleType = _ref$bundleDetails.bundleType,
runtimeBytecodeVersion = _ref$bundleDetails.runtimeBytecodeVersion,
transformedFileCount = _ref.transformedFileCount,
totalFileCount = _ref.totalFileCount,
ratio = _ref.ratio;
if (runtimeBytecodeVersion) {
bundleType = "bytecodebundle";
}
const localPath = path.relative(".", entryFile);
const filledBar = Math.floor(ratio * MAX_PROGRESS_BAR_CHAR_WIDTH);
const bundleTypeColor =
phase === "done"
? chalk.green
: phase === "failed"
? chalk.red
: chalk.yellow;
const progress =
phase === "in_progress"
? chalk.green.bgGreen(DARK_BLOCK_CHAR.repeat(filledBar)) +
chalk.bgWhite.white(
LIGHT_BLOCK_CHAR.repeat(MAX_PROGRESS_BAR_CHAR_WIDTH - filledBar)
) +
chalk.bold(` ${(100 * ratio).toFixed(1)}% `) +
chalk.dim(`(${transformedFileCount}/${totalFileCount})`)
: "";
return (
bundleTypeColor.inverse.bold(` ${bundleType.toUpperCase()} `) +
chalk.reset.dim(` ${path.dirname(localPath)}/`) +
chalk.bold(path.basename(localPath)) +
" " +
progress +
"\n"
);
}
_logCacheDisabled(reason) {
const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT;
switch (reason) {
case "too_many_errors":
reporting.logWarning(
this.terminal,
format,
"it has been failing too many times."
);
break;
case "too_many_misses":
reporting.logWarning(
this.terminal,
format,
"it has been missing too many consecutive keys."
);
break;
}
}
_logBundleBuildDone(buildID) {
const progress = this._activeBundles.get(buildID);
if (progress != null) {
const msg = this._getBundleStatusMessage(
_objectSpread(
_objectSpread({}, progress),
{},
{
ratio: 1,
transformedFileCount: progress.totalFileCount
}
),
"done"
);
this.terminal.log(msg);
this._activeBundles.delete(buildID);
}
}
_logBundleBuildFailed(buildID) {
const progress = this._activeBundles.get(buildID);
if (progress != null) {
const msg = this._getBundleStatusMessage(progress, "failed");
this.terminal.log(msg);
}
}
_logInitializing(port, hasReducedPerformance) {
const logo = [
" ",
" ####### ",
" ################ ",
" ######### ######### ",
" ######### ########## ",
" ######### ###### ######### ",
" ########################################## ",
" ##### ##################### ##### ",
" ##### ############## ##### ",
" ##### ### ###### ### ##### ",
" ##### ####### ####### ##### ",
" ##### ########### ########### ##### ",
" ##### ########################## ##### ",
" ##### ########################## ##### ",
" ##### ###################### ###### ",
" ###### ############# ####### ",
" ######### #### ######### ",
" ######### ######### ",
" ######### ######### ",
" ######### ",
" ",
" "
];
const color = hasReducedPerformance ? chalk.red : chalk.blue;
this.terminal.log(color(logo.join("\n")));
}
_logInitializingFailed(port, error) {
if (error.code === "EADDRINUSE") {
this.terminal.log(
chalk.bgRed.bold(" ERROR "),
chalk.red("Metro can't listen on port", chalk.bold(String(port)))
);
this.terminal.log(
"Most likely another process is already using this port"
);
this.terminal.log("Run the following command to find out which process:");
this.terminal.log("\n ", chalk.bold("lsof -i :" + port), "\n");
this.terminal.log("Then, you can either shut down the other process:");
this.terminal.log("\n ", chalk.bold("kill -9 <PID>"), "\n");
this.terminal.log("or run Metro on different port.");
} else {
this.terminal.log(chalk.bgRed.bold(" ERROR "), chalk.red(error.message));
const errorAttributes = JSON.stringify(error);
if (errorAttributes !== "{}") {
this.terminal.log(chalk.red(errorAttributes));
}
this.terminal.log(chalk.red(error.stack));
}
}
/**
* This function is only concerned with logging and should not do state
* or terminal status updates.
*/
_log(event) {
switch (event.type) {
case "initialize_started":
this._logInitializing(event.port, event.hasReducedPerformance);
break;
case "initialize_failed":
this._logInitializingFailed(event.port, event.error);
break;
case "bundle_build_done":
this._logBundleBuildDone(event.buildID);
break;
case "bundle_build_failed":
this._logBundleBuildFailed(event.buildID);
break;
case "bundling_error":
this._logBundlingError(event.error);
break;
case "global_cache_disabled":
this._logCacheDisabled(event.reason);
break;
case "transform_cache_reset":
reporting.logWarning(this.terminal, "the transform cache was reset.");
break;
case "worker_stdout_chunk":
this._logWorkerChunk("stdout", event.chunk);
break;
case "worker_stderr_chunk":
this._logWorkerChunk("stderr", event.chunk);
break;
case "hmr_client_error":
this._logHmrClientError(event.error);
break;
case "client_log":
logToConsole.apply(
void 0,
[this.terminal, event.level].concat(_toConsumableArray(event.data))
);
break;
case "dep_graph_loading":
const color = event.hasReducedPerformance ? chalk.red : chalk.blue;
this.terminal.log(
color.bold(" Welcome to Metro!\n") +
chalk.dim(" Fast - Scalable - Integrated\n\n")
);
if (event.hasReducedPerformance) {
this.terminal.log(
chalk.red(
"Metro is operating with reduced performance.\n" +
"Please fix the problem above and restart Metro.\n\n"
)
);
}
break;
}
}
/**
* We do not want to log the whole stacktrace for bundling error, because
* these are operational errors, not programming errors, and the stacktrace
* is not actionable to end users.
*/
_logBundlingError(error) {
if (error instanceof AmbiguousModuleResolutionError) {
const he = error.hasteError;
const message =
"ambiguous resolution: module `" +
`${error.fromModulePath}\` tries to require \`${he.hasteName}\`, ` +
"but there are several files providing this module. You can delete " +
"or fix them: \n\n" +
Object.keys(he.duplicatesSet)
.sort()
.map(dupFilePath => ` * \`${dupFilePath}\`\n`)
.join("");
reporting.logError(this.terminal, message);
return;
}
let message = error.message; // Do not log the stack trace for SyntaxError (because it will always be in
// the parser, which is not helpful).
if (!(error instanceof SyntaxError)) {
if (error.snippet == null && error.stack != null) {
message = error.stack;
}
}
if (error.filename && !message.includes(error.filename)) {
message += ` [${error.filename}]`;
}
if (error.snippet != null) {
message += "\n" + error.snippet;
}
reporting.logError(this.terminal, message);
}
_logWorkerChunk(origin, chunk) {
const lines = chunk.split("\n");
if (lines.length >= 1 && lines[lines.length - 1] === "") {
lines.splice(lines.length - 1, 1);
}
lines.forEach(line => {
this.terminal.log(`transform[${origin}]: ${line}`);
});
}
/**
* We use Math.pow(ratio, 2) to as a conservative measure of progress because
* we know the `totalCount` is going to progressively increase as well. We
* also prevent the ratio from going backwards.
*/
_updateBundleProgress(_ref2) {
let buildID = _ref2.buildID,
transformedFileCount = _ref2.transformedFileCount,
totalFileCount = _ref2.totalFileCount;
const currentProgress = this._activeBundles.get(buildID);
if (currentProgress == null) {
return;
}
const rawRatio = transformedFileCount / totalFileCount;
const conservativeRatio = Math.pow(rawRatio, 2);
const ratio = Math.max(conservativeRatio, currentProgress.ratio);
Object.assign(currentProgress, {
ratio,
transformedFileCount,
totalFileCount
});
}
/**
* This function is exclusively concerned with updating the internal state.
* No logging or status updates should be done at this point.
*/
_updateState(event) {
switch (event.type) {
case "bundle_build_done":
case "bundle_build_failed":
this._activeBundles.delete(event.buildID);
break;
case "bundle_build_started":
const bundleProgress = {
bundleDetails: event.bundleDetails,
transformedFileCount: 0,
totalFileCount: 1,
ratio: 0
};
this._activeBundles.set(event.buildID, bundleProgress);
break;
case "bundle_transform_progressed":
if (event.totalFileCount === event.transformedFileCount) {
this._scheduleUpdateBundleProgress.cancel();
this._updateBundleProgress(event);
} else {
this._scheduleUpdateBundleProgress(event);
}
break;
case "bundle_transform_progressed_throttled":
this._updateBundleProgress(event);
break;
}
}
/**
* Return a status message that is always consistent with the current state
* of the application. Having this single function ensures we don't have
* different callsites overriding each other status messages.
*/
_getStatusMessage() {
return Array.from(this._activeBundles.entries())
.map(_ref3 => {
let _ref4 = _slicedToArray(_ref3, 2),
_ = _ref4[0],
progress = _ref4[1];
return this._getBundleStatusMessage(progress, "in_progress");
})
.filter(str => str != null)
.join("\n");
}
_logHmrClientError(e) {
reporting.logError(
this.terminal,
"A WebSocket client got a connection error. Please reload your device " +
"to get HMR working again: %s",
e
);
}
/**
* Single entry point for reporting events. That allows us to implement the
* corresponding JSON reporter easily and have a consistent reporting.
*/
update(event) {
this._log(event);
this._updateState(event);
this.terminal.status(this._getStatusMessage());
}
}
module.exports = TerminalReporter;