1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | const path_1 = __importDefault(require("path"));
|
6 | const events_1 = require("events");
|
7 | const watcher_adapter_1 = __importDefault(require("./watcher_adapter"));
|
8 | const logger = require('heimdalljs-logger')('broccoli:watcher');
|
9 |
|
10 |
|
11 |
|
12 | class Watcher extends events_1.EventEmitter {
|
13 | constructor(builder, watchedNodes, options = {}) {
|
14 | super();
|
15 | this.options = options;
|
16 | if (this.options.debounce == null) {
|
17 | this.options.debounce = 100;
|
18 | }
|
19 | this._currentBuild = Promise.resolve();
|
20 | this.builder = builder;
|
21 | this.watcherAdapter =
|
22 | this.options.watcherAdapter || new watcher_adapter_1.default(watchedNodes, this.options.saneOptions);
|
23 | this._rebuildScheduled = false;
|
24 | this._ready = false;
|
25 | this._quittingPromise = null;
|
26 | this._lifetime = null;
|
27 | this._changedFiles = [];
|
28 | }
|
29 | get currentBuild() {
|
30 | return this._currentBuild;
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | _updateCurrentBuild(promise) {
|
39 | this._currentBuild = promise;
|
40 | promise.catch(() => {
|
41 | |
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | });
|
51 | }
|
52 | start() {
|
53 | if (this._lifetime != null) {
|
54 | throw new Error('Watcher.prototype.start() must not be called more than once');
|
55 | }
|
56 | this._lifetime = {};
|
57 | let lifetime = this._lifetime;
|
58 | lifetime.promise = new Promise((resolve, reject) => {
|
59 | lifetime.resolve = resolve;
|
60 | lifetime.reject = reject;
|
61 | });
|
62 | this.watcherAdapter.on('change', this._change.bind(this));
|
63 | this.watcherAdapter.on('error', this._error.bind(this));
|
64 | this._updateCurrentBuild((async () => {
|
65 | try {
|
66 | await this.watcherAdapter.watch();
|
67 | logger.debug('ready');
|
68 | this._ready = true;
|
69 | }
|
70 | catch (e) {
|
71 | this._error(e);
|
72 | }
|
73 | await this._build();
|
74 | })());
|
75 | return this._lifetime.promise;
|
76 | }
|
77 | async _change(event, filePath, root) {
|
78 | this._changedFiles.push(path_1.default.join(root, filePath));
|
79 | if (!this._ready) {
|
80 | logger.debug('change', 'ignored: before ready');
|
81 | return;
|
82 | }
|
83 | if (this._rebuildScheduled) {
|
84 | logger.debug('change', 'ignored: rebuild scheduled already');
|
85 | return;
|
86 | }
|
87 | logger.debug('change', event, filePath, root);
|
88 | this.emit('change', event, filePath, root);
|
89 | this._rebuildScheduled = true;
|
90 | try {
|
91 |
|
92 | await this.currentBuild;
|
93 | }
|
94 | catch (e) {
|
95 | |
96 |
|
97 |
|
98 | }
|
99 | if (this._quitting) {
|
100 | this._updateCurrentBuild(Promise.resolve());
|
101 | return;
|
102 | }
|
103 | this._updateCurrentBuild(new Promise((resolve, reject) => {
|
104 | logger.debug('debounce');
|
105 | this.emit('debounce');
|
106 | setTimeout(() => {
|
107 |
|
108 |
|
109 | try {
|
110 | this._rebuildScheduled = false;
|
111 | resolve(this._build(path_1.default.join(root, filePath)));
|
112 | }
|
113 | catch (e) {
|
114 | reject(e);
|
115 | }
|
116 | }, this.options.debounce);
|
117 | }));
|
118 | }
|
119 | _build(filePath) {
|
120 | logger.debug('buildStart');
|
121 | this.emit('buildStart');
|
122 | const start = process.hrtime();
|
123 |
|
124 | let annotation = {
|
125 | type: filePath ? 'rebuild' : 'initial',
|
126 | reason: 'watcher',
|
127 | primaryFile: filePath,
|
128 | changedFiles: this._changedFiles,
|
129 | };
|
130 | const buildPromise = this.builder.build(null, annotation);
|
131 |
|
132 |
|
133 |
|
134 | buildPromise.then((results = {}) => {
|
135 | const end = process.hrtime(start);
|
136 | logger.debug('Build execution time: %ds %dms', end[0], Math.round(end[1] / 1e6));
|
137 | logger.debug('buildSuccess');
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | results.filePath = filePath;
|
144 | this._changedFiles = [];
|
145 | this.emit('buildSuccess', results);
|
146 | }, (err) => {
|
147 | this._changedFiles = [];
|
148 | logger.debug('buildFailure');
|
149 | this.emit('buildFailure', err);
|
150 | });
|
151 | return buildPromise;
|
152 | }
|
153 | async _error(err) {
|
154 | if (this._quittingPromise) {
|
155 | logger.debug('error', 'ignored: already quitting');
|
156 | return this._quittingPromise;
|
157 | }
|
158 | logger.debug('error', err);
|
159 | this.emit('error', err);
|
160 | try {
|
161 | await this._quit();
|
162 | }
|
163 | catch (e) {
|
164 |
|
165 | }
|
166 | if (this._lifetime && typeof this._lifetime.reject === 'function') {
|
167 | this._lifetime.reject(err);
|
168 | }
|
169 | }
|
170 | quit() {
|
171 | if (this._quittingPromise) {
|
172 | logger.debug('quit', 'ignored: already quitting');
|
173 | return this._quittingPromise;
|
174 | }
|
175 | let quitting = this._quit();
|
176 | if (this._lifetime && typeof this._lifetime.resolve === 'function') {
|
177 | this._lifetime.resolve(quitting);
|
178 | return this._lifetime.promise;
|
179 | }
|
180 | else {
|
181 | return quitting;
|
182 | }
|
183 | }
|
184 | _quit() {
|
185 | logger.debug('quitStart');
|
186 | this.emit('quitStart');
|
187 | this._quittingPromise = (async () => {
|
188 | try {
|
189 | await this.watcherAdapter.quit();
|
190 | }
|
191 | finally {
|
192 | try {
|
193 | await this.currentBuild;
|
194 | }
|
195 | catch (e) {
|
196 |
|
197 | }
|
198 | logger.debug('quitEnd');
|
199 | this.emit('quitEnd');
|
200 | }
|
201 | })();
|
202 | return this._quittingPromise;
|
203 | }
|
204 | }
|
205 | module.exports = Watcher;
|
206 |
|
\ | No newline at end of file |