mirror of
https://github.com/status-im/metro.git
synced 2025-03-02 03:30:53 +00:00
packager: make output simpler, more legible
Reviewed By: cpojer Differential Revision: D4339417 fbshipit-source-id: f174ee11bc220de5e8da1d8227e9a9ceb5319e8d
This commit is contained in:
parent
0410c6f714
commit
379daeb0d1
12
react-packager/index.js
vendored
12
react-packager/index.js
vendored
@ -57,12 +57,22 @@ function createServer(options) {
|
||||
|
||||
options = Object.assign({}, options);
|
||||
delete options.verbose;
|
||||
if (options.reporter == null) {
|
||||
// It's unsound to set-up the reporter here, but this allows backward
|
||||
// compatibility.
|
||||
var TerminalReporter = require('./src/lib/TerminalReporter');
|
||||
options.reporter = new TerminalReporter();
|
||||
}
|
||||
var Server = require('./src/Server');
|
||||
return new Server(options);
|
||||
}
|
||||
|
||||
function createNonPersistentServer(options) {
|
||||
Logger.disablePrinting();
|
||||
if (options.reporter == null) {
|
||||
// It's unsound to set-up the reporter here, but this allows backward
|
||||
// compatibility.
|
||||
options.reporter = require('./src/lib/reporting').nullReporter;
|
||||
}
|
||||
options.watch = !options.nonPersistent;
|
||||
return createServer(options);
|
||||
}
|
||||
|
37
react-packager/src/Bundler/index.js
vendored
37
react-packager/src/Bundler/index.js
vendored
@ -39,6 +39,7 @@ import type AssetServer from '../AssetServer';
|
||||
import type Module from '../node-haste/Module';
|
||||
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
|
||||
import type {Options as TransformOptions} from '../JSTransformer/worker/worker';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
|
||||
export type GetTransformOptions<T> = (
|
||||
string,
|
||||
@ -54,7 +55,6 @@ const {
|
||||
createActionStartEntry,
|
||||
createActionEndEntry,
|
||||
log,
|
||||
print,
|
||||
} = require('../Logger');
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
@ -113,6 +113,9 @@ const validateOpts = declareOpts({
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
reporter: {
|
||||
type: 'object',
|
||||
},
|
||||
});
|
||||
|
||||
const assetPropertyBlacklist = new Set([
|
||||
@ -122,21 +125,22 @@ const assetPropertyBlacklist = new Set([
|
||||
]);
|
||||
|
||||
type Options = {
|
||||
projectRoots: Array<string>,
|
||||
allowBundleUpdates: boolean,
|
||||
assetExts: Array<string>,
|
||||
assetServer: AssetServer,
|
||||
blacklistRE: RegExp,
|
||||
moduleFormat: string,
|
||||
polyfillModuleNames: Array<string>,
|
||||
cacheVersion: string,
|
||||
extraNodeModules: {},
|
||||
getTransformOptions?: GetTransformOptions<*>,
|
||||
moduleFormat: string,
|
||||
platforms: Array<string>,
|
||||
polyfillModuleNames: Array<string>,
|
||||
projectRoots: Array<string>,
|
||||
reporter: Reporter,
|
||||
resetCache: boolean,
|
||||
transformModulePath: string,
|
||||
getTransformOptions?: GetTransformOptions<*>,
|
||||
extraNodeModules: {},
|
||||
assetExts: Array<string>,
|
||||
platforms: Array<string>,
|
||||
watch: boolean,
|
||||
assetServer: AssetServer,
|
||||
transformTimeoutInterval: ?number,
|
||||
allowBundleUpdates: boolean,
|
||||
watch: boolean,
|
||||
};
|
||||
|
||||
class Bundler {
|
||||
@ -204,20 +208,21 @@ class Bundler {
|
||||
blacklistRE: opts.blacklistRE,
|
||||
cache: this._cache,
|
||||
extraNodeModules: opts.extraNodeModules,
|
||||
watch: opts.watch,
|
||||
minifyCode: this._transformer.minify,
|
||||
moduleFormat: opts.moduleFormat,
|
||||
platforms: opts.platforms,
|
||||
polyfillModuleNames: opts.polyfillModuleNames,
|
||||
projectRoots: opts.projectRoots,
|
||||
reporter: options.reporter,
|
||||
resetCache: opts.resetCache,
|
||||
transformCacheKey,
|
||||
transformCode:
|
||||
(module, code, transformCodeOptions) => this._transformer.transformFile(
|
||||
module.path,
|
||||
code,
|
||||
transformCodeOptions,
|
||||
),
|
||||
transformCacheKey,
|
||||
watch: opts.watch,
|
||||
});
|
||||
|
||||
this._projectRoots = opts.projectRoots;
|
||||
@ -401,11 +406,11 @@ class Bundler {
|
||||
onProgress = noop,
|
||||
}: *) {
|
||||
const transformingFilesLogEntry =
|
||||
print(log(createActionStartEntry({
|
||||
log(createActionStartEntry({
|
||||
action_name: 'Transforming files',
|
||||
entry_point: entryFile,
|
||||
environment: dev ? 'dev' : 'prod',
|
||||
})));
|
||||
}));
|
||||
|
||||
const modulesByName = Object.create(null);
|
||||
|
||||
@ -425,7 +430,7 @@ class Bundler {
|
||||
return Promise.resolve(resolutionResponse).then(response => {
|
||||
bundle.setRamGroups(response.transformOptions.transform.ramGroups);
|
||||
|
||||
print(log(createActionEndEntry(transformingFilesLogEntry)));
|
||||
log(createActionEndEntry(transformingFilesLogEntry));
|
||||
onResolutionResponse(response);
|
||||
|
||||
// get entry file complete path (`entryFile` is relative to roots)
|
||||
|
@ -17,7 +17,6 @@ const {
|
||||
createEntry,
|
||||
createActionStartEntry,
|
||||
createActionEndEntry,
|
||||
enablePrinting,
|
||||
} = require('../');
|
||||
|
||||
describe('Logger', () => {
|
||||
@ -29,7 +28,6 @@ describe('Logger', () => {
|
||||
|
||||
afterEach(() => {
|
||||
console.log = originalConsoleLog;
|
||||
enablePrinting();
|
||||
});
|
||||
|
||||
it('creates simple log entries', () => {
|
||||
|
73
react-packager/src/Logger/index.js
vendored
73
react-packager/src/Logger/index.js
vendored
@ -7,14 +7,12 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const os = require('os');
|
||||
const pkgjson = require('../../../package.json');
|
||||
const terminal = require('../lib/terminal');
|
||||
|
||||
const {EventEmitter} = require('events');
|
||||
|
||||
@ -24,17 +22,6 @@ import type {
|
||||
LogEntry,
|
||||
} from './Types';
|
||||
|
||||
const DATE_LOCALE_OPTIONS = {
|
||||
day: '2-digit',
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
month: '2-digit',
|
||||
second: '2-digit',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
let PRINT_LOG_ENTRIES = true;
|
||||
const log_session = `${os.hostname()}-${Date.now()}`;
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
@ -93,68 +80,10 @@ function log(logEntry: LogEntry): LogEntry {
|
||||
return logEntry;
|
||||
}
|
||||
|
||||
function print(
|
||||
logEntry: LogEntry,
|
||||
printFields?: Array<string> = [],
|
||||
): LogEntry {
|
||||
if (!PRINT_LOG_ENTRIES) {
|
||||
return logEntry;
|
||||
}
|
||||
const {
|
||||
log_entry_label: logEntryLabel,
|
||||
action_phase: actionPhase,
|
||||
duration_ms: duration,
|
||||
} = logEntry;
|
||||
|
||||
const timeStamp = new Date().toLocaleString(undefined, DATE_LOCALE_OPTIONS);
|
||||
let logEntryString;
|
||||
|
||||
switch (actionPhase) {
|
||||
case 'start':
|
||||
logEntryString = chalk.dim(`[${timeStamp}] <START> ${logEntryLabel}`);
|
||||
break;
|
||||
case 'end':
|
||||
logEntryString = chalk.dim(`[${timeStamp}] <END> ${logEntryLabel}`) +
|
||||
chalk.cyan(` (${+duration}ms)`);
|
||||
break;
|
||||
default:
|
||||
logEntryString = chalk.dim(`[${timeStamp}] ${logEntryLabel}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (printFields.length) {
|
||||
const indent = ' '.repeat(timeStamp.length + 11);
|
||||
|
||||
for (const field of printFields) {
|
||||
const value = logEntry[field];
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
logEntryString += chalk.dim(`\n${indent}${field}: ${value.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console-disallow
|
||||
terminal.log(logEntryString);
|
||||
|
||||
return logEntry;
|
||||
}
|
||||
|
||||
function enablePrinting(): void {
|
||||
PRINT_LOG_ENTRIES = true;
|
||||
}
|
||||
|
||||
function disablePrinting(): void {
|
||||
PRINT_LOG_ENTRIES = false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
on,
|
||||
createEntry,
|
||||
createActionStartEntry,
|
||||
createActionEndEntry,
|
||||
log,
|
||||
print,
|
||||
enablePrinting,
|
||||
disablePrinting,
|
||||
};
|
||||
|
32
react-packager/src/Resolver/index.js
vendored
32
react-packager/src/Resolver/index.js
vendored
@ -21,6 +21,7 @@ import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionRes
|
||||
import type Module from '../node-haste/Module';
|
||||
import type {SourceMap} from '../lib/SourceMap';
|
||||
import type {Options as TransformOptions} from '../JSTransformer/worker/worker';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
@ -71,6 +72,9 @@ const validateOpts = declareOpts({
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
reporter: {
|
||||
type: 'object',
|
||||
},
|
||||
});
|
||||
|
||||
const getDependenciesValidateOpts = declareOpts({
|
||||
@ -99,30 +103,34 @@ class Resolver {
|
||||
Promise<{code: string, map: SourceMap}>;
|
||||
_polyfillModuleNames: Array<string>;
|
||||
|
||||
constructor(options: {resetCache: boolean}) {
|
||||
constructor(options: {
|
||||
reporter: Reporter,
|
||||
resetCache: boolean,
|
||||
}) {
|
||||
const opts = validateOpts(options);
|
||||
|
||||
this._depGraph = new DependencyGraph({
|
||||
roots: opts.projectRoots,
|
||||
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
|
||||
assetExts: opts.assetExts,
|
||||
cache: opts.cache,
|
||||
extraNodeModules: opts.extraNodeModules,
|
||||
ignoreFilePath: function(filepath) {
|
||||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
|
||||
},
|
||||
providesModuleNodeModules: defaults.providesModuleNodeModules,
|
||||
platforms: opts.platforms,
|
||||
preferNativePlatform: true,
|
||||
watch: opts.watch,
|
||||
cache: opts.cache,
|
||||
transformCode: opts.transformCode,
|
||||
transformCacheKey: opts.transformCacheKey,
|
||||
extraNodeModules: opts.extraNodeModules,
|
||||
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
|
||||
resetCache: options.resetCache,
|
||||
moduleOptions: {
|
||||
cacheTransformResults: true,
|
||||
resetCache: options.resetCache,
|
||||
},
|
||||
platforms: opts.platforms,
|
||||
preferNativePlatform: true,
|
||||
providesModuleNodeModules: defaults.providesModuleNodeModules,
|
||||
reporter: options.reporter,
|
||||
resetCache: options.resetCache,
|
||||
roots: opts.projectRoots,
|
||||
transformCacheKey: opts.transformCacheKey,
|
||||
transformCode: opts.transformCode,
|
||||
watch: opts.watch,
|
||||
});
|
||||
|
||||
this._minifyCode = opts.minifyCode;
|
||||
|
@ -39,7 +39,8 @@ describe('processRequest', () => {
|
||||
projectRoots: ['root'],
|
||||
blacklistRE: null,
|
||||
cacheVersion: null,
|
||||
polyfillModuleNames: null
|
||||
polyfillModuleNames: null,
|
||||
reporter: require('../../lib/reporting').nullReporter,
|
||||
};
|
||||
|
||||
const makeRequest = (reqHandler, requrl, reqOptions) => new Promise(resolve =>
|
||||
|
101
react-packager/src/Server/index.js
vendored
101
react-packager/src/Server/index.js
vendored
@ -22,7 +22,6 @@ const defaults = require('../../../defaults');
|
||||
const mime = require('mime-types');
|
||||
const path = require('path');
|
||||
const terminal = require('../lib/terminal');
|
||||
const throttle = require('lodash/throttle');
|
||||
const url = require('url');
|
||||
|
||||
const debug = require('debug')('ReactNativePackager:Server');
|
||||
@ -32,12 +31,12 @@ import type {Stats} from 'fs';
|
||||
import type {IncomingMessage, ServerResponse} from 'http';
|
||||
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
|
||||
import type Bundle from '../Bundler/Bundle';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
|
||||
const {
|
||||
createActionStartEntry,
|
||||
createActionEndEntry,
|
||||
log,
|
||||
print,
|
||||
} = require('../Logger');
|
||||
|
||||
function debounceAndBatch(fn, delay) {
|
||||
@ -109,6 +108,9 @@ const validateOpts = declareOpts({
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
reporter: {
|
||||
type: 'object',
|
||||
},
|
||||
});
|
||||
|
||||
const bundleOpts = declareOpts({
|
||||
@ -223,12 +225,17 @@ class Server {
|
||||
_bundler: Bundler;
|
||||
_debouncedFileChangeHandler: (filePath: string) => mixed;
|
||||
_hmrFileChangeListener: (type: string, filePath: string) => mixed;
|
||||
_reporter: Reporter;
|
||||
|
||||
constructor(options: {watch?: boolean}) {
|
||||
constructor(options: {
|
||||
reporter: Reporter,
|
||||
watch?: boolean,
|
||||
}) {
|
||||
const opts = this._opts = validateOpts(options);
|
||||
const processFileChange =
|
||||
({type, filePath, stat}) => this.onFileChange(type, filePath, stat);
|
||||
|
||||
this._reporter = options.reporter;
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._bundles = Object.create(null);
|
||||
this._changeWatchers = [];
|
||||
@ -243,6 +250,7 @@ class Server {
|
||||
bundlerOpts.assetServer = this._assetServer;
|
||||
bundlerOpts.allowBundleUpdates = options.watch;
|
||||
bundlerOpts.watch = options.watch;
|
||||
bundlerOpts.reporter = options.reporter;
|
||||
this._bundler = new Bundler(bundlerOpts);
|
||||
|
||||
// changes to the haste map can affect resolution of files in the bundle
|
||||
@ -514,10 +522,10 @@ class Server {
|
||||
const assetPath: string = urlObj.pathname.match(/^\/assets\/(.+)$/);
|
||||
|
||||
const processingAssetRequestLogEntry =
|
||||
print(log(createActionStartEntry({
|
||||
log(createActionStartEntry({
|
||||
action_name: 'Processing asset request',
|
||||
asset: assetPath[1],
|
||||
})), ['asset']);
|
||||
}));
|
||||
|
||||
/* $FlowFixMe: query may be empty for invalid URLs */
|
||||
this._assetServer.get(assetPath[1], urlObj.query.platform)
|
||||
@ -530,7 +538,7 @@ class Server {
|
||||
}
|
||||
res.end(this._rangeRequestMiddleware(req, res, data, assetPath));
|
||||
process.nextTick(() => {
|
||||
print(log(createActionEndEntry(processingAssetRequestLogEntry)), ['asset']);
|
||||
log(createActionEndEntry(processingAssetRequestLogEntry));
|
||||
});
|
||||
},
|
||||
error => {
|
||||
@ -565,10 +573,10 @@ class Server {
|
||||
if (outdated.size) {
|
||||
|
||||
const updatingExistingBundleLogEntry =
|
||||
print(log(createActionStartEntry({
|
||||
log(createActionStartEntry({
|
||||
action_name: 'Updating existing bundle',
|
||||
outdated_modules: outdated.size,
|
||||
})), ['outdated_modules']);
|
||||
}));
|
||||
|
||||
debug('Attempt to update existing bundle');
|
||||
|
||||
@ -632,10 +640,7 @@ class Server {
|
||||
|
||||
bundle.invalidateSource();
|
||||
|
||||
print(
|
||||
log(createActionEndEntry(updatingExistingBundleLogEntry)),
|
||||
['outdated_modules'],
|
||||
);
|
||||
log(createActionEndEntry(updatingExistingBundleLogEntry));
|
||||
|
||||
debug('Successfully updated existing bundle');
|
||||
return bundle;
|
||||
@ -689,21 +694,32 @@ class Server {
|
||||
}
|
||||
|
||||
const options = this._getOptionsFromUrl(req.url);
|
||||
this._reporter.update({
|
||||
type: 'bundle_requested',
|
||||
entryFilePath: options.entryFile,
|
||||
});
|
||||
const requestingBundleLogEntry =
|
||||
print(log(createActionStartEntry({
|
||||
log(createActionStartEntry({
|
||||
action_name: 'Requesting bundle',
|
||||
bundle_url: req.url,
|
||||
entry_point: options.entryFile,
|
||||
})), ['bundle_url']);
|
||||
}));
|
||||
|
||||
let updateTTYProgressMessage = () => {};
|
||||
if (process.stdout.isTTY && !this._opts.silent) {
|
||||
updateTTYProgressMessage = startTTYProgressMessage();
|
||||
let reportProgress = () => {};
|
||||
if (!this._opts.silent) {
|
||||
reportProgress = (transformedFileCount, totalFileCount) => {
|
||||
this._reporter.update({
|
||||
type: 'bundle_transform_progressed',
|
||||
entryFilePath: options.entryFile,
|
||||
transformedFileCount,
|
||||
totalFileCount,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const mres = MultipartResponse.wrap(req, res);
|
||||
options.onProgress = (done, total) => {
|
||||
updateTTYProgressMessage(done, total);
|
||||
reportProgress(done, total);
|
||||
mres.writeChunk({'Content-Type': 'application/json'}, JSON.stringify({done, total}));
|
||||
};
|
||||
|
||||
@ -711,6 +727,10 @@ class Server {
|
||||
const building = this._useCachedOrUpdateOrCreateBundle(options);
|
||||
building.then(
|
||||
p => {
|
||||
this._reporter.update({
|
||||
type: 'bundle_built',
|
||||
entryFilePath: options.entryFile,
|
||||
});
|
||||
if (requestType === 'bundle') {
|
||||
debug('Generating source code');
|
||||
const bundleSource = p.getSource({
|
||||
@ -731,7 +751,7 @@ class Server {
|
||||
mres.end(bundleSource);
|
||||
}
|
||||
debug('Finished response');
|
||||
print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
|
||||
log(createActionEndEntry(requestingBundleLogEntry));
|
||||
} else if (requestType === 'map') {
|
||||
let sourceMap = p.getSourceMap({
|
||||
minify: options.minify,
|
||||
@ -744,12 +764,12 @@ class Server {
|
||||
|
||||
mres.setHeader('Content-Type', 'application/json');
|
||||
mres.end(sourceMap);
|
||||
print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
|
||||
log(createActionEndEntry(requestingBundleLogEntry));
|
||||
} else if (requestType === 'assets') {
|
||||
const assetsList = JSON.stringify(p.getAssets());
|
||||
mres.setHeader('Content-Type', 'application/json');
|
||||
mres.end(assetsList);
|
||||
print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
|
||||
log(createActionEndEntry(requestingBundleLogEntry));
|
||||
}
|
||||
},
|
||||
error => this._handleError(mres, this.optionsHash(options), error)
|
||||
@ -762,7 +782,7 @@ class Server {
|
||||
|
||||
_symbolicate(req: IncomingMessage, res: ServerResponse) {
|
||||
const symbolicatingLogEntry =
|
||||
print(log(createActionStartEntry('Symbolicating')));
|
||||
log(createActionStartEntry('Symbolicating'));
|
||||
|
||||
/* $FlowFixMe: where is `rowBody` defined? Is it added by
|
||||
* the `connect` framework? */
|
||||
@ -815,7 +835,7 @@ class Server {
|
||||
stack => {
|
||||
res.end(JSON.stringify({stack: stack}));
|
||||
process.nextTick(() => {
|
||||
print(log(createActionEndEntry(symbolicatingLogEntry)));
|
||||
log(createActionEndEntry(symbolicatingLogEntry));
|
||||
});
|
||||
},
|
||||
error => {
|
||||
@ -950,41 +970,6 @@ class Server {
|
||||
}
|
||||
}
|
||||
|
||||
function getProgressBar(ratio: number, length: number) {
|
||||
const blockCount = Math.floor(ratio * length);
|
||||
return (
|
||||
'\u2593'.repeat(blockCount) +
|
||||
'\u2591'.repeat(length - blockCount)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function startTTYProgressMessage(
|
||||
): (doneCount: number, totalCount: number) => void {
|
||||
let currentRatio = 0;
|
||||
const updateMessage = (doneCount, totalCount) => {
|
||||
const isDone = doneCount === totalCount;
|
||||
const conservativeRatio = Math.pow(doneCount / totalCount, 2);
|
||||
currentRatio = Math.max(conservativeRatio, currentRatio);
|
||||
terminal.status(
|
||||
'Transforming files %s%s% (%s/%s)%s',
|
||||
isDone ? '' : getProgressBar(currentRatio, 20) + ' ',
|
||||
(100 * currentRatio).toFixed(1),
|
||||
doneCount,
|
||||
totalCount,
|
||||
isDone ? ', done.' : '...',
|
||||
);
|
||||
if (isDone) {
|
||||
terminal.persistStatus();
|
||||
}
|
||||
};
|
||||
return throttle(updateMessage, 200);
|
||||
}
|
||||
|
||||
function contentsEqual(array: Array<mixed>, set: Set<mixed>): boolean {
|
||||
return array.length === set.size && array.every(set.has, set);
|
||||
}
|
||||
|
246
react-packager/src/lib/TerminalReporter.js
vendored
Normal file
246
react-packager/src/lib/TerminalReporter.js
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Copyright (c) 2015-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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const path = require('path');
|
||||
const reporting = require('./reporting');
|
||||
const terminal = require('./terminal');
|
||||
const throttle = require('lodash/throttle');
|
||||
const util = require('util');
|
||||
|
||||
import type {ReportableEvent, GlobalCacheDisabledReason} from './reporting';
|
||||
|
||||
const DEP_GRAPH_MESSAGE = 'Loading dependency graph';
|
||||
const GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT =
|
||||
'The global cache is now disabled because %s';
|
||||
|
||||
type BundleProgress = {
|
||||
transformedFileCount: number,
|
||||
totalFileCount: number,
|
||||
ratio: number,
|
||||
};
|
||||
|
||||
const DARK_BLOCK_CHAR = '\u2593';
|
||||
const LIGHT_BLOCK_CHAR = '\u2591';
|
||||
const PACKAGE_EMOJI = '\uD83D\uDCE6';
|
||||
|
||||
function getProgressBar(ratio: number, length: number) {
|
||||
const blockCount = Math.floor(ratio * length);
|
||||
return (
|
||||
DARK_BLOCK_CHAR.repeat(blockCount) +
|
||||
LIGHT_BLOCK_CHAR.repeat(length - blockCount)
|
||||
);
|
||||
}
|
||||
|
||||
type TerminalReportableEvent = ReportableEvent | {
|
||||
type: 'bundle_transform_progressed_throttled',
|
||||
entryFilePath: string,
|
||||
transformedFileCount: number,
|
||||
totalFileCount: number,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
_activeBundles: Map<string, BundleProgress>;
|
||||
|
||||
_dependencyGraphHasLoaded: boolean;
|
||||
_scheduleUpdateBundleProgress: (data: {
|
||||
entryFilePath: string,
|
||||
transformedFileCount: number,
|
||||
totalFileCount: number,
|
||||
}) => void;
|
||||
|
||||
constructor() {
|
||||
this._dependencyGraphHasLoaded = false;
|
||||
this._activeBundles = new Map();
|
||||
this._scheduleUpdateBundleProgress = throttle((data) => {
|
||||
this.update({...data, type: 'bundle_transform_progressed_throttled'});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a message looking like this:
|
||||
*
|
||||
* Transforming files |#### | 34.2% (324/945)...
|
||||
*
|
||||
*/
|
||||
_getFileTransformMessage(
|
||||
{totalFileCount, transformedFileCount, ratio}: BundleProgress,
|
||||
build: 'in_progress' | 'done',
|
||||
): string {
|
||||
if (build === 'done' && totalFileCount === 0) {
|
||||
return 'All files are already up-to-date.';
|
||||
}
|
||||
return util.format(
|
||||
'Transforming files %s%s% (%s/%s)%s',
|
||||
build === 'done' ? '' : getProgressBar(ratio, 30) + ' ',
|
||||
(100 * ratio).toFixed(1),
|
||||
transformedFileCount,
|
||||
totalFileCount,
|
||||
build === 'done' ? ', done.' : '...',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a message that represent the progress of a single bundle build.
|
||||
*/
|
||||
_getBundleStatusMessage(
|
||||
entryFilePath: string,
|
||||
progress: BundleProgress,
|
||||
build: 'in_progress' | 'done',
|
||||
): string {
|
||||
const localPath = path.relative('.', entryFilePath);
|
||||
return [
|
||||
chalk.underline(`Bundling ${PACKAGE_EMOJI} \`${localPath}\``),
|
||||
this._getFileTransformMessage(progress, build),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
_logCacheDisabled(reason: GlobalCacheDisabledReason): void {
|
||||
const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT;
|
||||
switch (reason) {
|
||||
case 'too_many_errors':
|
||||
reporting.logWarning(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.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is only concerned with logging and should not do state
|
||||
* or terminal status updates.
|
||||
*/
|
||||
_log(event: TerminalReportableEvent): void {
|
||||
switch (event.type) {
|
||||
case 'bundle_built':
|
||||
const progress = this._activeBundles.get(event.entryFilePath);
|
||||
if (progress != null) {
|
||||
terminal.log(
|
||||
this._getBundleStatusMessage(event.entryFilePath, progress, 'done'),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'dep_graph_loaded':
|
||||
terminal.log(`${DEP_GRAPH_MESSAGE}, done.`);
|
||||
break;
|
||||
case 'global_cache_error':
|
||||
reporting.logWarning(terminal, 'The global cache failed: %s', event.error.stack);
|
||||
break;
|
||||
case 'global_cache_disabled':
|
||||
this._logCacheDisabled(event.reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
{entryFilePath, transformedFileCount, totalFileCount}: {
|
||||
entryFilePath: string,
|
||||
transformedFileCount: number,
|
||||
totalFileCount: number,
|
||||
},
|
||||
) {
|
||||
const currentProgress = this._activeBundles.get(entryFilePath);
|
||||
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: TerminalReportableEvent): void {
|
||||
switch (event.type) {
|
||||
case 'bundle_requested':
|
||||
this._activeBundles.set(event.entryFilePath, {
|
||||
transformedFileCount: 0,
|
||||
totalFileCount: 0,
|
||||
ratio: 0,
|
||||
});
|
||||
break;
|
||||
case 'bundle_transform_progressed':
|
||||
this._scheduleUpdateBundleProgress(event);
|
||||
break;
|
||||
case 'bundle_transform_progressed_throttled':
|
||||
this._updateBundleProgress(event);
|
||||
break;
|
||||
case 'bundle_built':
|
||||
this._activeBundles.delete(event.entryFilePath);
|
||||
break;
|
||||
case 'dep_graph_loading':
|
||||
this._dependencyGraphHasLoaded = false;
|
||||
break;
|
||||
case 'dep_graph_loaded':
|
||||
this._dependencyGraphHasLoaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_getDepGraphStatusMessage(): ?string {
|
||||
if (!this._dependencyGraphHasLoaded) {
|
||||
return `${DEP_GRAPH_MESSAGE}...`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(): string {
|
||||
return [
|
||||
this._getDepGraphStatusMessage(),
|
||||
].concat(Array.from(this._activeBundles.entries()).map(
|
||||
([entryFilePath, progress]) =>
|
||||
this._getBundleStatusMessage(entryFilePath, progress, 'in_progress'),
|
||||
)).filter(str => str != null).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything that happens goes through the same 3 steps. This makes the
|
||||
* output more reliable and consistent, because no matter what additional.
|
||||
*/
|
||||
update(event: TerminalReportableEvent) {
|
||||
this._log(event);
|
||||
this._updateState(event);
|
||||
terminal.status(this._getStatusMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = TerminalReporter;
|
90
react-packager/src/lib/reporting.js
vendored
Normal file
90
react-packager/src/lib/reporting.js
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2015-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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const util = require('util');
|
||||
|
||||
import type {Terminal} from './terminal';
|
||||
|
||||
export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses';
|
||||
|
||||
/**
|
||||
* A tagged union of all the actions that may happen and we may want to
|
||||
* report to the tool user.
|
||||
*/
|
||||
export type ReportableEvent = {
|
||||
type: 'dep_graph_loading',
|
||||
} | {
|
||||
type: 'dep_graph_loaded',
|
||||
} | {
|
||||
type: 'bundle_requested',
|
||||
entryFilePath: string,
|
||||
} | {
|
||||
type: 'bundle_transform_progressed',
|
||||
entryFilePath: string,
|
||||
transformedFileCount: number,
|
||||
totalFileCount: number,
|
||||
} | {
|
||||
type: 'bundle_built',
|
||||
entryFilePath: string,
|
||||
} | {
|
||||
type: 'global_cache_error',
|
||||
error: Error,
|
||||
} | {
|
||||
type: 'global_cache_disabled',
|
||||
reason: GlobalCacheDisabledReason,
|
||||
};
|
||||
|
||||
/**
|
||||
* Code across the application takes a reporter as an option and calls the
|
||||
* update whenever one of the ReportableEvent happens. Code does not directly
|
||||
* write to the standard output, because a build would be:
|
||||
*
|
||||
* 1. ad-hoc, embedded into another tool, in which case we do not want to
|
||||
* pollute that tool's own output. The tool is free to present the
|
||||
* warnings/progress we generate any way they want, by specifing a custom
|
||||
* reporter.
|
||||
* 2. run as a background process from another tool, in which case we want
|
||||
* to expose updates in a way that is easily machine-readable, for example
|
||||
* a JSON-stream. We don't want to pollute it with textual messages.
|
||||
*
|
||||
* We centralize terminal reporting into a single place because we want the
|
||||
* output to be robust and consistent. The most common reporter is
|
||||
* TerminalReporter, that should be the only place in the application should
|
||||
* access the `terminal` module (nor the `console`).
|
||||
*/
|
||||
export type Reporter = {
|
||||
update(event: ReportableEvent): void,
|
||||
};
|
||||
|
||||
/**
|
||||
* A standard way to log a warning to the terminal. This should not be called
|
||||
* from some arbitrary packager logic, only from the reporters. Instead of
|
||||
* calling this, add a new type of ReportableEvent instead, and implement a
|
||||
* proper handler in the reporter(s).
|
||||
*/
|
||||
function logWarning(terminal: Terminal, format: string, ...args: Array<mixed>): void {
|
||||
const str = util.format(format, ...args);
|
||||
terminal.log('%s: %s', chalk.yellow('warning'), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reporter that does nothing. Errors and warnings will be swallowed, that
|
||||
* is generally not what you want.
|
||||
*/
|
||||
const nullReporter: Reporter = {update() {}};
|
||||
|
||||
module.exports = {
|
||||
logWarning,
|
||||
nullReporter,
|
||||
};
|
4
react-packager/src/lib/terminal.js
vendored
4
react-packager/src/lib/terminal.js
vendored
@ -126,9 +126,9 @@ class GlobalTerminal extends Terminal {
|
||||
Terminal: Class<Terminal>;
|
||||
|
||||
constructor() {
|
||||
/* $FlowFixMe: Flow is wrong, Node.js docs specify that process.stderr is an
|
||||
/* $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.stderr);
|
||||
super(process.stdout);
|
||||
this.Terminal = Terminal;
|
||||
}
|
||||
|
||||
|
31
react-packager/src/node-haste/Module.js
vendored
31
react-packager/src/node-haste/Module.js
vendored
@ -14,20 +14,19 @@
|
||||
const GlobalTransformCache = require('../lib/GlobalTransformCache');
|
||||
const TransformCache = require('../lib/TransformCache');
|
||||
|
||||
const chalk = require('chalk');
|
||||
const crypto = require('crypto');
|
||||
const docblock = require('./DependencyGraph/docblock');
|
||||
const fs = require('fs');
|
||||
const invariant = require('invariant');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const jsonStableStringify = require('json-stable-stringify');
|
||||
const terminal = require('../lib/terminal');
|
||||
|
||||
const {join: joinPath, relative: relativePath, extname} = require('path');
|
||||
|
||||
import type {TransformedCode, Options as TransformOptions} from '../JSTransformer/worker/worker';
|
||||
import type {SourceMap} from '../lib/SourceMap';
|
||||
import type {ReadTransformProps} from '../lib/TransformCache';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
import type Cache from './Cache';
|
||||
import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers';
|
||||
import type ModuleCache from './ModuleCache';
|
||||
@ -65,6 +64,7 @@ export type ConstructorArgs = {
|
||||
transformCacheKey: ?string,
|
||||
depGraphHelpers: DependencyGraphHelpers,
|
||||
options: Options,
|
||||
reporter: Reporter,
|
||||
};
|
||||
|
||||
class Module {
|
||||
@ -78,6 +78,7 @@ class Module {
|
||||
_transformCacheKey: ?string;
|
||||
_depGraphHelpers: DependencyGraphHelpers;
|
||||
_options: Options;
|
||||
_reporter: Reporter;
|
||||
|
||||
_docBlock: Promise<{id?: string, moduleDocBlock: {[key: string]: mixed}}>;
|
||||
_readSourceCodePromise: Promise<string>;
|
||||
@ -93,6 +94,7 @@ class Module {
|
||||
transformCode,
|
||||
transformCacheKey,
|
||||
depGraphHelpers,
|
||||
reporter,
|
||||
options,
|
||||
}: ConstructorArgs) {
|
||||
if (!isAbsolutePath(file)) {
|
||||
@ -112,6 +114,7 @@ class Module {
|
||||
);
|
||||
this._depGraphHelpers = depGraphHelpers;
|
||||
this._options = options || {};
|
||||
this._reporter = reporter;
|
||||
|
||||
this._readPromises = new Map();
|
||||
}
|
||||
@ -276,25 +279,25 @@ class Module {
|
||||
}
|
||||
globalCache.fetch(cacheProps, (globalCacheError, globalCachedResult) => {
|
||||
if (globalCacheError != null && Module._globalCacheRetries > 0) {
|
||||
terminal.log(chalk.red(
|
||||
'Warning: the global cache failed with error:',
|
||||
));
|
||||
terminal.log(chalk.red(globalCacheError.stack));
|
||||
this._reporter.update({
|
||||
type: 'global_cache_error',
|
||||
error: globalCacheError,
|
||||
});
|
||||
Module._globalCacheRetries--;
|
||||
if (Module._globalCacheRetries <= 0) {
|
||||
terminal.log(chalk.red(
|
||||
'No more retries, the global cache will be disabled for the ' +
|
||||
'remainder of the transformation.',
|
||||
));
|
||||
this._reporter.update({
|
||||
type: 'global_cache_disabled',
|
||||
reason: 'too_many_errors',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (globalCachedResult == null) {
|
||||
--Module._globalCacheMaxMisses;
|
||||
if (Module._globalCacheMaxMisses === 0) {
|
||||
terminal.log(
|
||||
'warning: global cache is now disabled because it ' +
|
||||
'has been missing too many consecutive keys.',
|
||||
);
|
||||
this._reporter.update({
|
||||
type: 'global_cache_disabled',
|
||||
reason: 'too_many_misses',
|
||||
});
|
||||
}
|
||||
this._transformAndStoreCodeGlobally(cacheProps, globalCache, callback);
|
||||
return;
|
||||
|
6
react-packager/src/node-haste/ModuleCache.js
vendored
6
react-packager/src/node-haste/ModuleCache.js
vendored
@ -16,6 +16,7 @@ const Module = require('./Module');
|
||||
const Package = require('./Package');
|
||||
const Polyfill = require('./Polyfill');
|
||||
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
import type Cache from './Cache';
|
||||
import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers';
|
||||
import type {
|
||||
@ -38,6 +39,7 @@ class ModuleCache {
|
||||
_platforms: mixed;
|
||||
_transformCacheKey: string;
|
||||
_transformCode: TransformCode;
|
||||
_reporter: Reporter;
|
||||
|
||||
constructor({
|
||||
assetDependencies,
|
||||
@ -48,6 +50,7 @@ class ModuleCache {
|
||||
moduleOptions,
|
||||
transformCacheKey,
|
||||
transformCode,
|
||||
reporter,
|
||||
}: {
|
||||
assetDependencies: mixed,
|
||||
cache: Cache,
|
||||
@ -56,6 +59,7 @@ class ModuleCache {
|
||||
moduleOptions: ModuleOptions,
|
||||
transformCacheKey: string,
|
||||
transformCode: TransformCode,
|
||||
reporter: Reporter,
|
||||
}, platforms: mixed) {
|
||||
this._assetDependencies = assetDependencies;
|
||||
this._getClosestPackage = getClosestPackage;
|
||||
@ -68,6 +72,7 @@ class ModuleCache {
|
||||
this._platforms = platforms;
|
||||
this._transformCacheKey = transformCacheKey;
|
||||
this._transformCode = transformCode;
|
||||
this._reporter = reporter;
|
||||
}
|
||||
|
||||
getModule(filePath: string) {
|
||||
@ -80,6 +85,7 @@ class ModuleCache {
|
||||
transformCacheKey: this._transformCacheKey,
|
||||
depGraphHelpers: this._depGraphHelpers,
|
||||
options: this._moduleOptions,
|
||||
reporter: this._reporter,
|
||||
});
|
||||
}
|
||||
return this._moduleCache[filePath];
|
||||
|
@ -127,6 +127,7 @@ describe('DependencyGraph', function() {
|
||||
});
|
||||
},
|
||||
transformCacheKey,
|
||||
reporter: require('../../lib/reporting').nullReporter,
|
||||
};
|
||||
});
|
||||
|
||||
|
17
react-packager/src/node-haste/index.js
vendored
17
react-packager/src/node-haste/index.js
vendored
@ -35,10 +35,10 @@ const {
|
||||
createActionEndEntry,
|
||||
createActionStartEntry,
|
||||
log,
|
||||
print,
|
||||
} = require('../Logger');
|
||||
|
||||
import type {Options as TransformOptions} from '../JSTransformer/worker/worker';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
import type {
|
||||
Options as ModuleOptions,
|
||||
TransformCode,
|
||||
@ -76,6 +76,7 @@ class DependencyGraph {
|
||||
_hasteMapError: ?Error;
|
||||
_helpers: DependencyGraphHelpers;
|
||||
_moduleCache: ModuleCache;
|
||||
_reporter: Reporter;
|
||||
|
||||
_loading: Promise<mixed>;
|
||||
|
||||
@ -100,6 +101,7 @@ class DependencyGraph {
|
||||
transformCode,
|
||||
useWatchman,
|
||||
watch,
|
||||
reporter,
|
||||
}: {
|
||||
assetDependencies: mixed,
|
||||
assetExts: Array<string>,
|
||||
@ -121,6 +123,7 @@ class DependencyGraph {
|
||||
transformCode: TransformCode,
|
||||
useWatchman?: ?boolean,
|
||||
watch: boolean,
|
||||
reporter: Reporter,
|
||||
}) {
|
||||
this._opts = {
|
||||
assetExts: assetExts || [],
|
||||
@ -145,6 +148,7 @@ class DependencyGraph {
|
||||
watch: !!watch,
|
||||
};
|
||||
|
||||
this._reporter = reporter;
|
||||
this._cache = cache;
|
||||
this._assetDependencies = assetDependencies;
|
||||
this._helpers = new DependencyGraphHelpers(this._opts);
|
||||
@ -174,7 +178,8 @@ class DependencyGraph {
|
||||
});
|
||||
|
||||
const initializingPackagerLogEntry =
|
||||
print(log(createActionStartEntry('Initializing Packager')));
|
||||
log(createActionStartEntry('Initializing Packager'));
|
||||
this._reporter.update({type: 'dep_graph_loading'});
|
||||
this._loading = this._haste.build().then(({hasteFS}) => {
|
||||
this._hasteFS = hasteFS;
|
||||
const hasteFSFiles = hasteFS.getAllFiles();
|
||||
@ -186,6 +191,7 @@ class DependencyGraph {
|
||||
depGraphHelpers: this._helpers,
|
||||
assetDependencies: this._assetDependencies,
|
||||
moduleOptions: this._opts.moduleOptions,
|
||||
reporter: this._reporter,
|
||||
getClosestPackage: filePath => {
|
||||
let {dir, root} = path.parse(filePath);
|
||||
do {
|
||||
@ -216,12 +222,13 @@ class DependencyGraph {
|
||||
});
|
||||
|
||||
const buildingHasteMapLogEntry =
|
||||
print(log(createActionStartEntry('Building Haste Map')));
|
||||
log(createActionStartEntry('Building Haste Map'));
|
||||
|
||||
return this._hasteMap.build().then(
|
||||
map => {
|
||||
print(log(createActionEndEntry(buildingHasteMapLogEntry)));
|
||||
print(log(createActionEndEntry(initializingPackagerLogEntry)));
|
||||
log(createActionEndEntry(buildingHasteMapLogEntry));
|
||||
log(createActionEndEntry(initializingPackagerLogEntry));
|
||||
this._reporter.update({type: 'dep_graph_loaded'});
|
||||
return map;
|
||||
},
|
||||
err => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user