diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7ab7fe3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 233 + +[*.json] +indent_style = space +indent_size = 2 + +[*.ejs] +insert_final_newline = false + +[*.yml] +indent_style = space +indent_size = 2 + +[test/cases/parsing/bom/bomfile.{css,js}] +charset = utf-8-bom + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index dd6c9a6..03e0809 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,22 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion * text=auto + +# For the following file types, normalize line endings to LF on +# checkin and prevent conversion to CRLF when they are checked out +# (this is required in order to prevent newline related issues like, +# for example, after the build script is run) +.* text eol=lf +*.css text eol=lf +*.html text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.json text eol=lf +*.md text eol=lf +*.sh text eol=lf +*.txt text eol=lf +*.xml text eol=lf + +test/statsCases/* eol=lf examples/* eol=lf -bin/* eol=lf \ No newline at end of file +bin/* eol=lf diff --git a/.gitignore b/.gitignore index 017a83d..7ae5b39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,56 @@ -/node_modules -/test/fixtures -/playground/folder +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul /coverage -.DS_Store \ No newline at end of file + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +/node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# tests +/test/js +/test/browsertest/js + +# benchmark +/benchmark/js +/benchmark/fixtures + +# custom +/examples/*/js +lib/**/*.map + +# IDE +.DS_Store +.idea +.vscode + +yarn.lock \ No newline at end of file diff --git a/lib/DirectoryWatcher.ts b/lib/DirectoryWatcher.ts new file mode 100644 index 0000000..804aade --- /dev/null +++ b/lib/DirectoryWatcher.ts @@ -0,0 +1,372 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ +import { EventEmitter } from 'events'; +import async = require('async'); +import chokidar = require('chokidar'); +import fs = require('graceful-fs'); +import path = require('path'); +import watcherManager = require('./watcherManager'); +import Watcher = require('./Watcher') +import Watchpack = require('./watchpack') + +let FS_ACCURENCY = 10000; + +function withoutCase(str: string) { + return str.toLowerCase(); +} + +class DirectoryWatcher extends EventEmitter { + directories: { + [path: string]: Watcher | true + } + files: { + [path: string]: [number, number] + } + initialScan: boolean + initialScanRemoved: string[] + nestedWatching: boolean + path: string + refs: number + watcher: fs.FSWatcher + watchers: { + [path: string]: Watcher[] + } + + constructor(directoryPath: string, public options: Watchpack.WatcherOptions) { + super(); + this.path = directoryPath; + this.files = {}; + this.directories = {}; + this.watcher = chokidar.watch(directoryPath, { + ignoreInitial: true, + persistent: true, + followSymlinks: false, + depth: 0, + atomic: false, + alwaysStat: true, + ignorePermissionErrors: true, + ignored: options.ignored, + usePolling: options.poll ? true : undefined, + interval: typeof options.poll === 'number' ? options.poll : undefined + }); + this.watcher.on('add', this.onFileAdded.bind(this)); + this.watcher.on('addDir', this.onDirectoryAdded.bind(this)); + this.watcher.on('change', this.onChange.bind(this)); + this.watcher.on('unlink', this.onFileUnlinked.bind(this)); + this.watcher.on('unlinkDir', this.onDirectoryUnlinked.bind(this)); + this.watcher.on('error', this.onWatcherError.bind(this)); + this.initialScan = true; + this.nestedWatching = false; + this.initialScanRemoved = []; + this.doInitialScan(); + this.watchers = {}; + this.refs = 0; + } + + setFileTime(filePath: string, mtime: number, initial: boolean, type?: string | boolean) { + const now = Date.now(); + const old = this.files[filePath]; + + this.files[filePath] = [initial ? Math.min(now, mtime) : now, mtime]; + + // we add the fs accurency to reach the maximum possible mtime + mtime = mtime + FS_ACCURENCY; + + if (!old) { + if (mtime) { + if (this.watchers[withoutCase(filePath)]) { + this.watchers[withoutCase(filePath)].forEach(w => { + if (!initial || w.checkStartTime(mtime, initial)) { + w.emit('change', mtime); + } + }); + } + } + } + else if (!initial && mtime && type !== 'add') { + if (this.watchers[withoutCase(filePath)]) { + this.watchers[withoutCase(filePath)].forEach(w => { + w.emit('change', mtime); + }); + } + } + else if (!initial && !mtime) { + delete this.files[filePath]; + if (this.watchers[withoutCase(filePath)]) { + this.watchers[withoutCase(filePath)].forEach(w => { + w.emit('remove'); + }); + } + } + if (this.watchers[withoutCase(this.path)]) { + this.watchers[withoutCase(this.path)].forEach(w => { + if (!initial || w.checkStartTime(mtime, initial)) { + w.emit('change', filePath, mtime); + } + }); + } + } + + setDirectory(directoryPath: string, exist: boolean, initial: boolean) { + const old = this.directories[directoryPath]; + if (!old) { + if (exist) { + if (this.nestedWatching) { + this.createNestedWatcher(directoryPath); + } + else { + this.directories[directoryPath] = true; + } + } + } + else { + if (!exist) { + if (this.nestedWatching) { + (this.directories[directoryPath] as Watcher).close(); + } + delete this.directories[directoryPath]; + if (!initial && this.watchers[withoutCase(this.path)]) { + this.watchers[withoutCase(this.path)].forEach(w => { + w.emit('change', directoryPath, w.data); + }); + } + } + } + } + + createNestedWatcher(directoryPath: string) { + this.directories[directoryPath] = watcherManager.watchDirectory(directoryPath, this.options, 1); + (this.directories[directoryPath] as Watcher).on('change', (filePath: string, mtime: number) => { + if (this.watchers[withoutCase(this.path)]) { + this.watchers[withoutCase(this.path)].forEach(w => { + if (w.checkStartTime(mtime, false)) { + w.emit('change', filePath, mtime); + } + }); + } + }); + } + + setNestedWatching(flag: boolean) { + if (this.nestedWatching !== !!flag) { + this.nestedWatching = !!flag; + if (this.nestedWatching) { + Object.keys(this.directories).forEach(function (directory) { + this.createNestedWatcher(directory); + }, this); + } + else { + Object.keys(this.directories).forEach(function (directory) { + this.directories[directory].close(); + this.directories[directory] = true; + }, this); + } + } + } + + watch(filePath: string, startTime: number) { + this.watchers[withoutCase(filePath)] = this.watchers[withoutCase(filePath)] || []; + this.refs++; + const watcher = new Watcher(this, filePath, startTime); + watcher.on('closed', () => { + const idx = this.watchers[withoutCase(filePath)].indexOf(watcher); + this.watchers[withoutCase(filePath)].splice(idx, 1); + if (this.watchers[withoutCase(filePath)].length === 0) { + delete this.watchers[withoutCase(filePath)]; + if (this.path === filePath) { + this.setNestedWatching(false); + } + } + if (--this.refs <= 0) { + this.close(); + } + }); + this.watchers[withoutCase(filePath)].push(watcher); + let data: [number, number] | boolean; + if (filePath === this.path) { + this.setNestedWatching(true); + data = false; + Object.keys(this.files).forEach(function (file) { + const d = this.files[file]; + if (!data) { + data = d; + } + else { + data = [Math.max(data[0], d[0]), Math.max(data[1], d[1])]; + } + }, this); + } + else { + data = this.files[filePath]; + } + process.nextTick(() => { + if (data) { + const ts = data[0] === data[1] ? data[0] + FS_ACCURENCY : data[0]; + if (ts > startTime) { + watcher.emit('change', data[1] + FS_ACCURENCY); + } + } + else if (this.initialScan && this.initialScanRemoved.includes(filePath)) { + watcher.emit('remove'); + } + }); + return watcher; + } + + onFileAdded(filePath: string, stat: fs.Stats) { + if (filePath.indexOf(this.path) !== 0) { + return; + } + if (/[\\\/]/.test(filePath.substr(this.path.length + 1))) { + return; + } + + this.setFileTime(filePath, +stat.mtime, false, 'add'); + } + + onDirectoryAdded(directoryPath: string /*, stat */) { + if (directoryPath.indexOf(this.path) !== 0) { + return; + } + if (/[\\\/]/.test(directoryPath.substr(this.path.length + 1))) { + return; + } + this.setDirectory(directoryPath, true, false); + } + + onChange(filePath: string, stat: fs.Stats) { + if (filePath.indexOf(this.path) !== 0) { + return; + } + if (/[\\\/]/.test(filePath.substr(this.path.length + 1))) { + return; + } + const mtime = +stat.mtime; + if (FS_ACCURENCY > 1 && mtime % 1 !== 0) { + FS_ACCURENCY = 1; + } + else if (FS_ACCURENCY > 10 && mtime % 10 !== 0) { + FS_ACCURENCY = 10; + } + else if (FS_ACCURENCY > 100 && mtime % 100 !== 0) { + FS_ACCURENCY = 100; + } + else if (FS_ACCURENCY > 1000 && mtime % 1000 !== 0) { + FS_ACCURENCY = 1000; + } + else if (FS_ACCURENCY > 2000 && mtime % 2000 !== 0) { + FS_ACCURENCY = 2000; + } + this.setFileTime(filePath, mtime, false, 'change'); + } + + onFileUnlinked(filePath: string) { + if (filePath.indexOf(this.path) !== 0) { + return; + } + if (/[\\\/]/.test(filePath.substr(this.path.length + 1))) { + return; + } + this.setFileTime(filePath, null, false, 'unlink'); + if (this.initialScan) { + this.initialScanRemoved.push(filePath); + } + } + + onDirectoryUnlinked(directoryPath: string) { + if (directoryPath.indexOf(this.path) !== 0) { + return; + } + if (/[\\\/]/.test(directoryPath.substr(this.path.length + 1))) { + return; + } + this.setDirectory(directoryPath, false, false); + if (this.initialScan) { + this.initialScanRemoved.push(directoryPath); + } + } + + onWatcherError() /* err */ {} + + doInitialScan() { + fs.readdir(this.path, (err, items) => { + if (err) { + this.initialScan = false; + return; + } + async.forEach(items, (item, callback) => { + const itemPath = path.join(this.path, item); + fs.stat(itemPath, (err2, stat) => { + if (!this.initialScan) { + return; + } + if (err2) { + callback(); + return; + } + if (stat.isFile()) { + if (!this.files[itemPath]) { + this.setFileTime(itemPath, +stat.mtime, true); + } + } + else if (stat.isDirectory()) { + if (!this.directories[itemPath]) { + this.setDirectory(itemPath, true, true); + } + } + callback(); + }); + }, () => { + this.initialScan = false; + this.initialScanRemoved = null; + }); + }); + } + + getTimes(): { + [path: string]: number; + } { + const obj = {}; + let selfTime = 0; + Object.keys(this.files).forEach(function (file) { + const data = this.files[file]; + if (data[1]) { + const time = Math.max(data[0], data[1] + FS_ACCURENCY); + obj[file] = time; + if (time > selfTime) { + selfTime = time; + } + } + }, this); + if (this.nestedWatching) { + Object.keys(this.directories).forEach(function (dir) { + const w = this.directories[dir]; + const times = w.directoryWatcher.getTimes(); + Object.keys(times).forEach(file => { + const time = times[file]; + obj[file] = time; + if (time > selfTime) { + selfTime = time; + } + }); + }, this); + obj[this.path] = selfTime; + } + return obj; + } + + close() { + this.initialScan = false; + this.watcher.close(); + if (this.nestedWatching) { + Object.keys(this.directories).forEach(function (dir) { + this.directories[dir].close(); + }, this); + } + this.emit('closed'); + } +} + +export = DirectoryWatcher; diff --git a/lib/Watcher.ts b/lib/Watcher.ts new file mode 100644 index 0000000..ad7d43e --- /dev/null +++ b/lib/Watcher.ts @@ -0,0 +1,31 @@ +import { EventEmitter } from 'events' +import DirectoryWatcher = require('./DirectoryWatcher') + +class Watcher extends EventEmitter { + data: number + directoryWatcher: DirectoryWatcher + path: string + startTime: number + + constructor(directoryWatcher: DirectoryWatcher, filePath: string, startTime: number) { + super(); + this.directoryWatcher = directoryWatcher; + this.path = filePath; + this.startTime = startTime && +startTime; + this.data = 0; + } + + checkStartTime(mtime: number, initial: boolean) { + if (typeof this.startTime !== 'number') { + return !initial; + } + const startTime = this.startTime; + return startTime <= mtime; + } + + close() { + this.emit('closed'); + } +} + +export = Watcher diff --git a/lib/watcherManager.ts b/lib/watcherManager.ts new file mode 100644 index 0000000..c158c95 --- /dev/null +++ b/lib/watcherManager.ts @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ +import path = require('path'); +import DirectoryWatcher = require('./DirectoryWatcher') +import Watchpack = require('./watchpack') + +class WatcherManager { + directoryWatchers: { + [key: string]: DirectoryWatcher + } + + constructor() { + this.directoryWatchers = {}; + } + + getDirectoryWatcher(directory: string, options: Watchpack.WatcherOptions) { + const DirectoryWatcher = require('./DirectoryWatcher'); + options = options || {}; + const key = `${directory} ${JSON.stringify(options)}`; + if (!this.directoryWatchers[key]) { + this.directoryWatchers[key] = new DirectoryWatcher(directory, options); + this.directoryWatchers[key].on('closed', () => { + delete this.directoryWatchers[key]; + }); + } + return this.directoryWatchers[key]; + } + + watchFile(p: string, options: Watchpack.WatcherOptions, startTime: number) { + const directory = path.dirname(p); + return this.getDirectoryWatcher(directory, options).watch(p, startTime); + } + + watchDirectory(directory: string, options: Watchpack.WatcherOptions, startTime: number) { + return this.getDirectoryWatcher(directory, options).watch(directory, startTime); + } +} + +export = new WatcherManager(); diff --git a/lib/watchpack.ts b/lib/watchpack.ts new file mode 100644 index 0000000..eae18c4 --- /dev/null +++ b/lib/watchpack.ts @@ -0,0 +1,160 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ +import watcherManager = require('./watcherManager'); + +import { EventEmitter } from 'events' +import Watcher = require('./Watcher') +import DirectoryWatcher = require('./DirectoryWatcher') + +class Watchpack extends EventEmitter { + aggregatedChanges: string[] + aggregateTimeout: NodeJS.Timer // setTimeout id + dirWatchers: Watcher[] + fileWatchers: Watcher[] + mtimes: { + [path: string]: number + } + options: Watchpack.WatchOptions + paused: boolean + watcherOptions: Watchpack.WatcherOptions + + constructor(options: Watchpack.WatchOptions) { + super(); + if (!options) { + options = {} as any; + } + if (!options.aggregateTimeout) { + options.aggregateTimeout = 200; + } + this.options = options; + this.watcherOptions = { + ignored: options.ignored, + poll: options.poll + }; + this.fileWatchers = []; + this.dirWatchers = []; + this.mtimes = {}; + this.paused = false; + this.aggregatedChanges = []; + this.aggregateTimeout = null; + this._onTimeout = this._onTimeout.bind(this); + } + + watch(files: string[], directories: string[], startTime: number) { + this.paused = false; + const oldFileWatchers = this.fileWatchers; + const oldDirWatchers = this.dirWatchers; + this.fileWatchers = files.map(function (file) { + return this._fileWatcher(file, watcherManager.watchFile(file, this.watcherOptions, startTime)); + }, this); + this.dirWatchers = directories.map(function (dir) { + return this._dirWatcher(dir, watcherManager.watchDirectory(dir, this.watcherOptions, startTime)); + }, this); + oldFileWatchers.forEach(w => { + w.close(); + }, this); + oldDirWatchers.forEach(w => { + w.close(); + }, this); + } + + close() { + this.paused = true; + if (this.aggregateTimeout) { + clearTimeout(this.aggregateTimeout); + } + this.fileWatchers.forEach(w => { + w.close(); + }, this); + this.dirWatchers.forEach(w => { + w.close(); + }, this); + this.fileWatchers.length = 0; + this.dirWatchers.length = 0; + } + + pause() { + this.paused = true; + if (this.aggregateTimeout) { + clearTimeout(this.aggregateTimeout); + } + } + + getTimes(): { + [path: string]: number; + } { + const directoryWatchers: DirectoryWatcher[] = []; + addWatchersToArray(this.fileWatchers.concat(this.dirWatchers), directoryWatchers); + const obj = {}; + directoryWatchers.forEach(w => { + const times = w.getTimes(); + Object.keys(times).forEach(file => { + obj[file] = times[file]; + }); + }); + return obj; + } + + _fileWatcher(file: string, watcher: Watcher) { + watcher.on('change', this._onChange.bind(this, file)); + return watcher; + } + + _dirWatcher(item: string, watcher: Watcher) { + watcher.on('change', (file: string, mtime: number) => { + this._onChange(item, mtime, file); + }); + return watcher; + } + + _onChange(item: string, mtime: number, file = item) { + this.mtimes[file] = mtime; + if (this.paused) { + return; + } + this.emit('change', file, mtime); + if (this.aggregateTimeout) { + clearTimeout(this.aggregateTimeout); + } + if (!this.aggregatedChanges.includes(item)) { + this.aggregatedChanges.push(item); + } + this.aggregateTimeout = setTimeout(this._onTimeout, this.options.aggregateTimeout); + } + + _onTimeout() { + this.aggregateTimeout = null; + const changes = this.aggregatedChanges; + this.aggregatedChanges = []; + this.emit('aggregated', changes); + } +} + +declare namespace Watchpack { + interface WatcherOptions { + ignored?: string[] | string | RegExp | ((path: string)=> boolean) + poll?: boolean | number + } + + interface WatchOptions extends WatcherOptions { + aggregateTimeout?: number + } +} + +export = Watchpack; + +function addWatchersToArray(watchers: Watcher[], array: any[]) { + watchers.forEach(w => { + if (!array.includes(w.directoryWatcher)) { + array.push(w.directoryWatcher); + addWatchersToArray(Object.keys(w.directoryWatcher.directories).reduce((a, dir) => { + if (w.directoryWatcher.directories[dir] !== true) { + a.push(w.directoryWatcher.directories[dir]); + } + return a; + }, []), array); + } + }); +} diff --git a/package.json b/package.json index 7aec936..d932d69 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "travis": "npm run cover -- --report lcovonly", "lint": "eslint lib", "precover": "npm run lint", - "cover": "istanbul cover node_modules/mocha/bin/_mocha" + "cover": "istanbul cover node_modules/mocha/bin/_mocha", + "build": "tsc || true" }, "repository": { "type": "git", @@ -33,9 +34,15 @@ "istanbul": "^0.4.3", "mocha": "^2.1.0", "rimraf": "^2.2.8", - "should": "^8.3.1" + "should": "^8.3.1", + "tslint": "^4.0.0", + "typescript": "^2.0.0" }, "dependencies": { + "@types/async": "^2.0.34", + "@types/chokidar": "^1.4.29", + "@types/graceful-fs": "^2.0.29", + "@types/node": "^0.0.2", "async": "^2.1.2", "chokidar": "^1.4.3", "graceful-fs": "^4.1.2" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8dce7c8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noEmit": false, + "sourceMap": true, + "lib": [ + "es2016", + "dom" + ], + "declaration": true, + "declarationDir": "./typings", + "noImplicitAny": true, + "noImplicitThis": false, + "suppressImplicitAnyIndexErrors": true, + "pretty": true, + "noImplicitReturns": false, + "strictNullChecks": false + }, + "exclude": [ + "node_modules" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..057bbba --- /dev/null +++ b/tslint.json @@ -0,0 +1,71 @@ +{ + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "label-position": true, + "linebreak-style": [ + true, + "LF" + ], + "no-arg": true, + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, + "no-internal-module": true, + "no-trailing-whitespace": true, + "no-unsafe-finally": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-var-keyword": true, + "one-line": [ + true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": false, + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-module", + "check-separator", + "check-type" + ] + } +} diff --git a/typings/DirectoryWatcher.d.ts b/typings/DirectoryWatcher.d.ts new file mode 100644 index 0000000..f348e80 --- /dev/null +++ b/typings/DirectoryWatcher.d.ts @@ -0,0 +1,58 @@ +/// +/// +import { EventEmitter } from 'events' +import fs = require('graceful-fs'); +import Watcher = require('./Watcher'); +import Watchpack = require('./watchpack'); + +declare class DirectoryWatcher extends EventEmitter { + options: Watchpack.WatcherOptions; + directories: { + [path: string]: Watcher | true; + }; + files: { + [path: string]: [number, number]; + }; + initialScan: boolean; + initialScanRemoved: string[]; + nestedWatching: boolean; + path: string; + refs: number; + watcher: fs.FSWatcher; + watchers: { + [path: string]: Watcher[]; + }; + + constructor(directoryPath: string, options: Watchpack.WatcherOptions); + + setFileTime(filePath: string, mtime: number, initial: boolean, type?: string | boolean): void; + + setDirectory(directoryPath: string, exist: boolean, initial: boolean): void; + + createNestedWatcher(directoryPath: string): void; + + setNestedWatching(flag: boolean): void; + + watch(filePath: string, startTime: number): Watcher; + + onFileAdded(filePath: string, stat: fs.Stats): void; + + onDirectoryAdded(directoryPath: string): void; + + onChange(filePath: string, stat: fs.Stats): void; + + onFileUnlinked(filePath: string): void; + + onDirectoryUnlinked(directoryPath: string): void; + + onWatcherError(): void; + + doInitialScan(): void; + + getTimes(): { + [path: string]: number; + }; + + close(): void; +} +export = DirectoryWatcher; diff --git a/typings/Watcher.d.ts b/typings/Watcher.d.ts new file mode 100644 index 0000000..efc2304 --- /dev/null +++ b/typings/Watcher.d.ts @@ -0,0 +1,17 @@ +/// +import { EventEmitter } from 'events' +import DirectoryWatcher = require('./DirectoryWatcher'); + +declare class Watcher extends EventEmitter { + data: number; + directoryWatcher: DirectoryWatcher; + path: string; + startTime: number; + + constructor(directoryWatcher: DirectoryWatcher, filePath: string, startTime: number); + + checkStartTime(mtime: number, initial: boolean): boolean; + + close(): void; +} +export = Watcher; diff --git a/typings/watchpack.d.ts b/typings/watchpack.d.ts new file mode 100644 index 0000000..13a4533 --- /dev/null +++ b/typings/watchpack.d.ts @@ -0,0 +1,48 @@ +/// +import { EventEmitter } from 'events' +import Watcher = require('./Watcher'); + +declare class Watchpack extends EventEmitter { + aggregatedChanges: string[]; + aggregateTimeout: NodeJS.Timer; + dirWatchers: Watcher[]; + fileWatchers: Watcher[]; + mtimes: { + [path: string]: number; + }; + options: Watchpack.WatchOptions; + paused: boolean; + watcherOptions: Watchpack.WatcherOptions; + + constructor(options: Watchpack.WatchOptions); + + watch(files: string[], directories: string[], startTime: number): void; + + close(): void; + + pause(): void; + + getTimes(): { + [path: string]: number; + }; + + _fileWatcher(file: string, watcher: Watcher): Watcher; + + _dirWatcher(item: string, watcher: Watcher): Watcher; + + _onChange(item: string, mtime: number, file?: string): void; + + _onTimeout(): void; +} + +declare namespace Watchpack { + interface WatcherOptions { + ignored?: string[] | string | RegExp | ((path: string) => boolean); + poll?: boolean | number; + } + interface WatchOptions extends WatcherOptions { + aggregateTimeout?: number; + } +} + +export = Watchpack;