metro-bundler: Terminal: remove global state

Reviewed By: cpojer

Differential Revision: D5155075

fbshipit-source-id: 1d64bdd0ae13087aca620b65892832e3a1229c4a
This commit is contained in:
Jean Lauliac 2017-06-01 02:57:56 -07:00 committed by Facebook Github Bot
parent 1f3140c496
commit 365c1bfcf9
9 changed files with 74 additions and 67 deletions

View File

@ -13,6 +13,7 @@
const log = require('../util/log').out('bundle');
const Server = require('../../packager/src/Server');
const Terminal = require('../../packager/src/lib/TerminalClass');
const TerminalReporter = require('../../packager/src/lib/TerminalReporter');
const TransformCaching = require('../../packager/src/lib/TransformCaching');
@ -74,9 +75,13 @@ function buildBundle(
: config.getTransformModulePath();
const providesModuleNodeModules =
typeof config.getProvidesModuleNodeModules === 'function' ? config.getProvidesModuleNodeModules() :
defaultProvidesModuleNodeModules;
typeof config.getProvidesModuleNodeModules === 'function'
? config.getProvidesModuleNodeModules()
: defaultProvidesModuleNodeModules;
/* $FlowFixMe: Flow is wrong, Node.js docs specify that process.stdout is an
* instance of a net.Socket (a local socket, not network). */
const terminal = new Terminal(process.stdout);
const options = {
assetExts: defaultAssetExts.concat(assetExts),
blacklistRE: config.getBlacklistRE(),
@ -90,7 +95,7 @@ function buildBundle(
projectRoots: config.getProjectRoots(),
providesModuleNodeModules: providesModuleNodeModules,
resetCache: args.resetCache,
reporter: new TerminalReporter(),
reporter: new TerminalReporter(terminal),
sourceExts: defaultSourceExts.concat(sourceExts),
transformCache: TransformCaching.useTempDir(),
transformModulePath: transformModulePath,

View File

@ -15,6 +15,7 @@
require('../../setupBabel')();
const InspectorProxy = require('./util/inspectorProxy.js');
const ReactPackager = require('../../packager');
const Terminal = require('../../packager/src/lib/TerminalClass');
const attachHMRServer = require('./util/attachHMRServer');
const connect = require('connect');
@ -138,6 +139,9 @@ function getPackagerServer(args, config) {
LogReporter = require('../../packager/src/lib/TerminalReporter');
}
/* $FlowFixMe: Flow is wrong, Node.js docs specify that process.stdout is an
* instance of a net.Socket (a local socket, not network). */
const terminal = new Terminal(process.stdout);
return ReactPackager.createServer({
assetExts: defaultAssetExts.concat(args.assetExts),
blacklistRE: config.getBlacklistRE(),
@ -151,7 +155,7 @@ function getPackagerServer(args, config) {
postMinifyProcess: config.postMinifyProcess,
projectRoots: args.projectRoots,
providesModuleNodeModules: providesModuleNodeModules,
reporter: new LogReporter(),
reporter: new LogReporter(terminal),
resetCache: args.resetCache,
sourceExts: defaultSourceExts.concat(args.sourceExts),
transformModulePath: transformModulePath,

View File

@ -21,7 +21,6 @@ const mime = require('mime-types');
const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
const path = require('path');
const symbolicate = require('./symbolicate');
const terminal = require('../lib/terminal');
const url = require('url');
const debug = require('debug')('RNP:Server');
@ -396,7 +395,8 @@ class Server {
e => {
res.writeHead(500);
res.end('Internal Error');
terminal.log(e.stack); // eslint-disable-line no-console-disallow
// FIXME: $FlowFixMe: that's a hack, doesn't work with JSON-mode output
this._reporter.terminal && this._reporter.terminal.log(e.stack);
}
);
} else {

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -84,7 +85,6 @@ function getTTYStream(stream: net$Socket): ?tty.WriteStream {
* single responsibility of handling status messages.
*/
class Terminal {
_logLines: Array<string>;
_nextStatusStr: string;
_scheduleUpdate: () => void;
@ -120,7 +120,10 @@ class Terminal {
});
this._logLines = [];
if (ttyStream != null) {
this._nextStatusStr = chunkString(this._nextStatusStr, ttyStream.columns).join('\n');
this._nextStatusStr = chunkString(
this._nextStatusStr,
ttyStream.columns,
).join('\n');
_stream.write(this._nextStatusStr);
}
this._statusStr = this._nextStatusStr;
@ -158,25 +161,6 @@ class Terminal {
this.log(this._nextStatusStr);
this._nextStatusStr = '';
}
}
/**
* On the same pattern as node.js `console` module, we export the stdout-based
* terminal at the top-level, but provide access to the Terminal class as a
* field (so it can be used, for instance, with stderr).
*/
class GlobalTerminal extends Terminal {
Terminal: Class<Terminal>;
constructor() {
/* $FlowFixMe: Flow is wrong, Node.js docs specify that process.stdout is an
* instance of a net.Socket (a local socket, not network). */
super(process.stdout);
this.Terminal = Terminal;
}
}
module.exports = new GlobalTerminal();
module.exports = Terminal;

View File

@ -15,10 +15,10 @@ const chalk = require('chalk');
const formatBanner = require('./formatBanner');
const path = require('path');
const reporting = require('./reporting');
const terminal = require('./terminal');
const throttle = require('lodash/throttle');
const util = require('util');
import type Terminal from './TerminalClass';
import type {ReportableEvent, GlobalCacheDisabledReason} from './reporting';
const DEP_GRAPH_MESSAGE = 'Loading dependency graph';
@ -72,12 +72,15 @@ class TerminalReporter {
totalFileCount: number,
}) => void;
constructor() {
+terminal: Terminal;
constructor(terminal: Terminal) {
this._dependencyGraphHasLoaded = false;
this._activeBundles = new Map();
this._scheduleUpdateBundleProgress = throttle(data => {
this.update({...data, type: 'bundle_transform_progressed_throttled'});
}, 100);
(this: any).terminal = terminal;
}
/**
@ -106,10 +109,18 @@ class TerminalReporter {
const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT;
switch (reason) {
case 'too_many_errors':
reporting.logWarning(terminal, format, 'it has been failing too many times.');
reporting.logWarning(
this.terminal,
format,
'it has been failing too many times.',
);
break;
case 'too_many_misses':
reporting.logWarning(terminal, format, 'it has been missing too many consecutive keys.');
reporting.logWarning(
this.terminal,
format,
'it has been missing too many consecutive keys.',
);
break;
}
}
@ -122,7 +133,7 @@ class TerminalReporter {
ratio: 1,
transformedFileCount: progress.totalFileCount,
}, 'done');
terminal.log(msg);
this.terminal.log(msg);
}
}
@ -130,12 +141,12 @@ class TerminalReporter {
const progress = this._activeBundles.get(buildID);
if (progress != null) {
const msg = this._getBundleStatusMessage(progress, 'failed');
terminal.log(msg);
this.terminal.log(msg);
}
}
_logPackagerInitializing(port: number, projectRoots: $ReadOnlyArray<string>) {
terminal.log(
this.terminal.log(
formatBanner(
'Running packager on port ' +
port +
@ -152,7 +163,7 @@ class TerminalReporter {
)
);
terminal.log(
this.terminal.log(
'Looking for JS files in\n ',
chalk.dim(projectRoots.join('\n ')),
'\n'
@ -161,23 +172,23 @@ class TerminalReporter {
_logPackagerInitializingFailed(port: number, error: Error) {
if (error.code === 'EADDRINUSE') {
terminal.log(
this.terminal.log(
chalk.bgRed.bold(' ERROR '),
chalk.red("Packager can't listen on port", chalk.bold(port))
);
terminal.log('Most likely another process is already using this port');
terminal.log('Run the following command to find out which process:');
terminal.log('\n ', chalk.bold('lsof -i :' + port), '\n');
terminal.log('Then, you can either shut down the other process:');
terminal.log('\n ', chalk.bold('kill -9 <PID>'), '\n');
terminal.log('or run packager on different 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 packager on different port.');
} else {
terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message));
this.terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message));
const errorAttributes = JSON.stringify(error);
if (errorAttributes !== '{}') {
terminal.log(chalk.red(errorAttributes));
this.terminal.log(chalk.red(errorAttributes));
}
terminal.log(chalk.red(error.stack));
this.terminal.log(chalk.red(error.stack));
}
}
@ -191,7 +202,7 @@ class TerminalReporter {
this._logPackagerInitializing(event.port, event.projectRoots);
break;
case 'initialize_packager_done':
terminal.log('\nReact packager ready.\n');
this.terminal.log('\nReact packager ready.\n');
break;
case 'initialize_packager_failed':
this._logPackagerInitializingFailed(event.port, event.error);
@ -206,13 +217,13 @@ class TerminalReporter {
this._logBundlingError(event.error);
break;
case 'dep_graph_loaded':
terminal.log(`${DEP_GRAPH_MESSAGE}, done.`);
this.terminal.log(`${DEP_GRAPH_MESSAGE}, done.`);
break;
case 'global_cache_disabled':
this._logCacheDisabled(event.reason);
break;
case 'transform_cache_reset':
reporting.logWarning(terminal, 'the transform cache was reset.');
reporting.logWarning(this.terminal, 'the transform cache was reset.');
break;
case 'worker_stdout_chunk':
this._logWorkerChunk('stdout', event.chunk);
@ -230,7 +241,7 @@ class TerminalReporter {
*/
_logBundlingError(error: Error) {
const str = JSON.stringify(error.message);
reporting.logError(terminal, 'bundling failed: %s', str);
reporting.logError(this.terminal, 'bundling failed: %s', str);
}
_logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) {
@ -239,7 +250,7 @@ class TerminalReporter {
lines.splice(lines.length - 1, 1);
}
lines.forEach(line => {
terminal.log(`transform[${origin}]: ${line}`);
this.terminal.log(`transform[${origin}]: ${line}`);
});
}
@ -335,7 +346,7 @@ class TerminalReporter {
update(event: TerminalReportableEvent) {
this._log(event);
this._updateState(event);
terminal.status(this._getStatusMessage());
this.terminal.status(this._getStatusMessage());
}
}

View File

@ -19,7 +19,6 @@ const invariant = require('fbjs/lib/invariant');
const mkdirp = require('mkdirp');
const path = require('path');
const rimraf = require('rimraf');
const terminal = require('../lib/terminal');
const writeFileAtomicSync = require('write-file-atomic').sync;
import type {Options as WorkerOptions} from '../JSTransformer/worker';
@ -226,7 +225,7 @@ class FileBasedCache {
lastCollected == null ||
Date.now() - lastCollected > GARBAGE_COLLECTION_PERIOD
) {
this._collectSyncNoThrow();
this._collectSyncNoThrow(options.reporter);
}
}
@ -241,15 +240,19 @@ class FileBasedCache {
* We want to avoid preventing tool use if the cleanup fails for some reason,
* but still provide some chance for people to report/fix things.
*/
_collectSyncNoThrow() {
_collectSyncNoThrow(reporter: Reporter) {
try {
this._collectCacheIfOldSync();
} catch (error) {
terminal.log(error.stack);
terminal.log(
'Error: Cleaning up the cache folder failed. Continuing anyway.',
);
terminal.log('The cache folder is: %s', this._rootPath);
// FIXME: $FlowFixMe: this is a hack, only works for TerminalReporter
const {terminal} = reporter;
if (terminal != null) {
terminal.log(error.stack);
terminal.log(
'Error: Cleaning up the cache folder failed. Continuing anyway.',
);
terminal.log('The cache folder is: %s', this._rootPath);
}
}
this._lastCollected = Date.now();
}

View File

@ -9,7 +9,7 @@
'use strict';
jest.dontMock('../terminal').dontMock('lodash/throttle');
jest.dontMock('../TerminalClass').dontMock('lodash/throttle');
jest.mock('readline', () => ({
moveCursor: (stream, dx, dy) => {
@ -27,14 +27,14 @@ jest.mock('readline', () => ({
},
}));
describe('terminal', () => {
describe('Terminal', () => {
beforeEach(() => {
jest.resetModules();
});
function prepare(isTTY) {
const {Terminal} = require('../terminal');
const Terminal = require('../TerminalClass');
const lines = 10;
const columns = 10;
const stream = Object.create(

View File

@ -88,7 +88,7 @@ describe('TransformCaching.FileBasedCache', () => {
const {result} = args;
const cachedResult = transformCache.readSync({
...args,
cacheOptions: {resetCache: false},
cacheOptions: {reporter: {}, resetCache: false},
});
expect(cachedResult.result).toEqual(result);
});
@ -121,7 +121,7 @@ describe('TransformCaching.FileBasedCache', () => {
const {result} = args;
const cachedResult = transformCache.readSync({
...args,
cacheOptions: {resetCache: false},
cacheOptions: {reporter: {}, resetCache: false},
});
expect(cachedResult.result).toEqual(result);
});
@ -129,7 +129,7 @@ describe('TransformCaching.FileBasedCache', () => {
allCases.forEach(entry => {
const cachedResult = transformCache.readSync({
...argsFor(entry),
cacheOptions: {resetCache: false},
cacheOptions: {reporter: {}, resetCache: false},
});
expect(cachedResult.result).toBeNull();
expect(cachedResult.outdatedDependencies).toEqual(['foo', 'bar']);

View File

@ -14,7 +14,7 @@
const chalk = require('chalk');
const util = require('util');
import type {Terminal} from './terminal';
import type Terminal from './TerminalClass';
export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses';