# 四.plugin

new NodeEnvironmentPlugin().apply(compiler)
1

NodeEnvironmentPlugin.js

class NodeEnvironmentPlugin {
  apply(compiler) {
    compiler.inputFileSystem = new CachedInputFileSystem(
      new NodeJsInputFileSystem(),
      60000
    )
    const inputFileSystem = compiler.inputFileSystem
    compiler.outputFileSystem = new NodeOutputFileSystem()
    compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem)
    compiler.plugin("before-run", (compiler, callback) => {
      if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge()
      callback()
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function CachedInputFileSystem(fileSystem, duration) {
  this.fileSystem = fileSystem
  this._statStorage = new Storage(duration)
  this._readdirStorage = new Storage(duration)
  this._readFileStorage = new Storage(duration)
  this._readJsonStorage = new Storage(duration)
  this._readlinkStorage = new Storage(duration)

  this._stat = this.fileSystem.stat
    ? this.fileSystem.stat.bind(this.fileSystem)
    : null
  if (!this._stat) this.stat = null

  this._statSync = this.fileSystem.statSync
    ? this.fileSystem.statSync.bind(this.fileSystem)
    : null
  if (!this._statSync) this.statSync = null

  this._readdir = this.fileSystem.readdir
    ? this.fileSystem.readdir.bind(this.fileSystem)
    : null
  if (!this._readdir) this.readdir = null

  this._readdirSync = this.fileSystem.readdirSync
    ? this.fileSystem.readdirSync.bind(this.fileSystem)
    : null
  if (!this._readdirSync) this.readdirSync = null

  this._readFile = this.fileSystem.readFile
    ? this.fileSystem.readFile.bind(this.fileSystem)
    : null
  if (!this._readFile) this.readFile = null

  this._readFileSync = this.fileSystem.readFileSync
    ? this.fileSystem.readFileSync.bind(this.fileSystem)
    : null
  if (!this._readFileSync) this.readFileSync = null

  if (this.fileSystem.readJson) {
    this._readJson = this.fileSystem.readJson.bind(this.fileSystem)
  } else if (this.readFile) {
    this._readJson = function(path, callback) {
      this.readFile(path, function(err, buffer) {
        if (err) return callback(err)
        try {
          var data = JSON.parse(buffer.toString("utf-8"))
        } catch (e) {
          return callback(e)
        }
        callback(null, data)
      })
    }.bind(this)
  } else {
    this.readJson = null
  }
  if (this.fileSystem.readJsonSync) {
    this._readJsonSync = this.fileSystem.readJsonSync.bind(this.fileSystem)
  } else if (this.readFileSync) {
    this._readJsonSync = function(path) {
      var buffer = this.readFileSync(path)
      var data = JSON.parse(buffer.toString("utf-8"))
      return data
    }.bind(this)
  } else {
    this.readJsonSync = null
  }

  this._readlink = this.fileSystem.readlink
    ? this.fileSystem.readlink.bind(this.fileSystem)
    : null
  if (!this._readlink) this.readlink = null

  this._readlinkSync = this.fileSystem.readlinkSync
    ? this.fileSystem.readlinkSync.bind(this.fileSystem)
    : null
  if (!this._readlinkSync) this.readlinkSync = null
}
module.exports = CachedInputFileSystem

CachedInputFileSystem.prototype.stat = function(path, callback) {
  this._statStorage.provide(path, this._stat, callback)
}

CachedInputFileSystem.prototype.readdir = function(path, callback) {
  this._readdirStorage.provide(path, this._readdir, callback)
}

CachedInputFileSystem.prototype.readFile = function(path, callback) {
  this._readFileStorage.provide(path, this._readFile, callback)
}

CachedInputFileSystem.prototype.readJson = function(path, callback) {
  this._readJsonStorage.provide(path, this._readJson, callback)
}

CachedInputFileSystem.prototype.readlink = function(path, callback) {
  this._readlinkStorage.provide(path, this._readlink, callback)
}

CachedInputFileSystem.prototype.statSync = function(path) {
  return this._statStorage.provideSync(path, this._statSync)
}

CachedInputFileSystem.prototype.readdirSync = function(path) {
  return this._readdirStorage.provideSync(path, this._readdirSync)
}

CachedInputFileSystem.prototype.readFileSync = function(path) {
  return this._readFileStorage.provideSync(path, this._readFileSync)
}

CachedInputFileSystem.prototype.readJsonSync = function(path) {
  return this._readJsonStorage.provideSync(path, this._readJsonSync)
}

CachedInputFileSystem.prototype.readlinkSync = function(path) {
  return this._readlinkStorage.provideSync(path, this._readlinkSync)
}

CachedInputFileSystem.prototype.purge = function(what) {
  this._statStorage.purge(what)
  this._readdirStorage.purge(what)
  this._readFileStorage.purge(what)
  this._readlinkStorage.purge(what)
  this._readJsonStorage.purge(what)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
function NodeJsInputFileSystem() {}
module.exports = NodeJsInputFileSystem

NodeJsInputFileSystem.prototype.stat = fs.stat.bind(fs)
NodeJsInputFileSystem.prototype.readdir = function readdir(path, callback) {
  fs.readdir(path, function(err, files) {
    callback(
      err,
      files &&
        files.map(function(file) {
          return file.normalize ? file.normalize("NFC") : file
        })
    )
  })
}
NodeJsInputFileSystem.prototype.readFile = fs.readFile.bind(fs)
NodeJsInputFileSystem.prototype.readlink = fs.readlink.bind(fs)

NodeJsInputFileSystem.prototype.statSync = fs.statSync.bind(fs)
NodeJsInputFileSystem.prototype.readdirSync = function readdirSync(path) {
  var files = fs.readdirSync(path)
  return (
    files &&
    files.map(function(file) {
      return file.normalize ? file.normalize("NFC") : file
    })
  )
}
NodeJsInputFileSystem.prototype.readFileSync = fs.readFileSync.bind(fs)
NodeJsInputFileSystem.prototype.readlinkSync = fs.readlinkSync.bind(fs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class NodeOutputFileSystem {
  constructor() {
    this.mkdirp = mkdirp
    this.mkdir = fs.mkdir.bind(fs)
    this.rmdir = fs.rmdir.bind(fs)
    this.unlink = fs.unlink.bind(fs)
    this.writeFile = fs.writeFile.bind(fs)
    this.join = path.join.bind(path)
  }
}
1
2
3
4
5
6
7
8
9
10
class NodeWatchFileSystem {
	constructor(inputFileSystem) {
		this.inputFileSystem = inputFileSystem;
		this.watcherOptions = {
			aggregateTimeout: 0
		};
		this.watcher = new Watchpack(this.watcherOptions);
	}

	watch(files, dirs, missing, startTime, options, callback, callbackUndelayed) {
		if(!Array.isArray(files))
			throw new Error("Invalid arguments: 'files'");
		if(!Array.isArray(dirs))
			throw new Error("Invalid arguments: 'dirs'");
		if(!Array.isArray(missing))
			throw new Error("Invalid arguments: 'missing'");
		if(typeof callback !== "function")
			throw new Error("Invalid arguments: 'callback'");
		if(typeof startTime !== "number" && startTime)
			throw new Error("Invalid arguments: 'startTime'");
		if(typeof options !== "object")
			throw new Error("Invalid arguments: 'options'");
		if(typeof callbackUndelayed !== "function" && callbackUndelayed)
			throw new Error("Invalid arguments: 'callbackUndelayed'");
		const oldWatcher = this.watcher;
		this.watcher = new Watchpack(options);

		if(callbackUndelayed)
			this.watcher.once("change", callbackUndelayed);

		this.watcher.once("aggregated", (changes, removals) => {
			changes = changes.concat(removals);
			if(this.inputFileSystem && this.inputFileSystem.purge) {
				this.inputFileSystem.purge(changes);
			}
			const times = this.watcher.getTimes();
			callback(null,
				changes.filter(file => files.indexOf(file) >= 0).sort(),
				changes.filter(file => dirs.indexOf(file) >= 0).sort(),
				changes.filter(file => missing.indexOf(file) >= 0).sort(), times, times);
		});

		this.watcher.watch(files.concat(missing), dirs, startTime);

		if(oldWatcher) {
			oldWatcher.close();
		}
		return {
			close: () => {
				if(this.watcher) {
					this.watcher.close();
					this.watcher = null;
				}
			},
			pause: () => {
				if(this.watcher) {
					this.watcher.pause();
				}
			}
		};
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62