UNPKG

ember-cli

Version:

Command line tool for developing ambitious ember.js apps

175 lines (140 loc) 5.2 kB
'use strict'; const chalk = require('chalk'); const logger = require('heimdalljs-logger')('ember-cli:watcher'); const CoreObject = require('core-object'); const serveURL = require('../utilities/get-serve-url'); const printSlowTrees = require('broccoli-slow-trees'); const eventTypeNormalization = { add: 'added', delete: 'deleted', change: 'changed', }; const ConstructedFromBuilder = Symbol('Watcher.build'); module.exports = class Watcher extends CoreObject { constructor(_options, build) { if (build !== ConstructedFromBuilder) { throw new Error('instantiate Watcher with (await Watcher.build()).watcher, not new Watcher()'); } super(_options); this.verbose = true; this.serving = _options.serving; } static async build(_options) { let watcher = new this(_options, ConstructedFromBuilder); await watcher.setupBroccoliWatcher(_options); // This indirection is because Watcher instances are themselves spec // noncompliant thennables (see the then() method) so returning watcher // directly will interfere with `await Watcher.build()` return { watcher }; } async setupBroccoliWatcher() { let options = this.buildOptions(); logger.info('initialize %o', options); this.watcher = this.watcher || (await this.constructBroccoliWatcher(options)); this.setupBroccoliChangeEvent(); this.watcher.on('buildStart', this._setupBroccoliWatcherBuild.bind(this)); this.watcher.on('buildSuccess', this.didChange.bind(this)); // build errors arriving via watcher this.watcher.on('buildFailure', this.didError.bind(this)); // watcher specific errors this.watcher.on('error', this.didError.bind(this)); this.serveURL = serveURL; } async constructBroccoliWatcher(options) { const { Watcher } = require('broccoli'); await this.builder.ensureBroccoliBuilder(); const { watchedSourceNodeWrappers } = this.builder.builder; let watcher = new Watcher(this.builder, watchedSourceNodeWrappers, { saneOptions: options, ignored: this.ignored }); watcher.start(); return watcher; } setupBroccoliChangeEvent() { // This is to keep backwards compatibility with broccoli-sane-watcher. // https://github.com/ember-cli/broccoli-sane-watcher/blob/48860/index.js#L158 if (this.verbose) { this.watcher.on('change', (event, filePath) => { this.ui.writeLine(`file ${eventTypeNormalization[event]} ${filePath}`); }); } } async _setupBroccoliWatcherBuild() { try { const hash = await this.watcher.currentBuild; hash.graph.__heimdall__.remove(); } catch (e) { if (e !== null && typeof e === 'object' && e.isBuilderError === true) { // e must be a builder error which we can safely ignore. // // Builder errors are expected failures which are handled and presented // to the user else-where. To prevent double reporting faliures to the // user, it is important that we ignore them here as this functions // single responsibilty is to reset the himedall state after each build. // // exists to prune heimdall data after a build. Those errors are // reported elsewhere. // } else { // this error is unexpected, we must re-throw as we cannot be sure it // has been handled else-where throw e; } } } _totalTime(hash) { const sumNodes = (node, cb) => { let total = 0; node.visitPreOrder((node) => { total += cb(node); }); return total; }; return sumNodes(hash.graph.__heimdall__, (node) => node.stats.time.self); } didError(error) { logger.info('didError %o', error); this.ui.writeError(error); } didChange(results) { logger.info('didChange %o', results); results.totalTime = this._totalTime(results); let totalTime = results.totalTime / 1e6; let message = chalk.green(`Build successful (${Math.round(totalTime)}ms)`); this.ui.writeLine(''); if (this.serving) { message += ` – Serving on ${this.serveURL(this.options, this.options.project)}`; } this.ui.writeLine(message); if (this.verbose) { printSlowTrees(results.graph.__heimdall__); } } buildOptions() { let watcher = this.options && this.options.watcher; if (watcher && ['polling', 'watchman', 'node', 'events'].indexOf(watcher) === -1) { throw new Error(`Unknown watcher type --watcher=[polling|watchman|node|events] but was: ${watcher}`); } return { verbose: this.verbose, poll: watcher === 'polling', watchman: watcher === 'watchman' || watcher === 'events', node: watcher === 'node', }; } then() { return this.watcher.currentBuild.then.apply(this.watcher.currentBuild, arguments); } on() { const args = arguments; if (args[0] === 'change' && !this.watchedDir) { args[0] = 'buildSuccess'; } this.watcher.on.apply(this.watcher, args); } off() { const args = arguments; if (args[0] === 'change' && !this.watchedDir) { args[0] = 'buildSuccess'; } this.watcher.off.apply(this.watcher, args); } };