forked from broccolijs/broccoli-plugin
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
151 lines (127 loc) · 5.08 KB
/
index.js
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
'use strict'
module.exports = Plugin
function Plugin(inputNodes, options) {
if (!(this instanceof Plugin)) throw new TypeError('Missing `new` operator')
// Remember current call stack (minus "Error" line)
this._instantiationStack = (new Error).stack.replace(/[^\n]*\n/, '')
options = options || {}
if (options.name != null) {
this._name = options.name
} else if (this.constructor && this.constructor.name != null) {
this._name = this.constructor.name
} else {
this._name = 'Plugin'
}
this._annotation = options.annotation
var label = this._name + (this._annotation != null ? ' (' + this._annotation + ')' : '')
if (!Array.isArray(inputNodes)) throw new TypeError(label + ': Expected an array of input nodes (input trees), got ' + inputNodes)
for (var i = 0; i < inputNodes.length; i++) {
if (!isPossibleNode(inputNodes[i])) {
throw new TypeError(label + ': Expected Broccoli node, got ' + inputNodes[i] + ' for inputNodes[' + i + ']')
}
}
this._baseConstructorCalled = true
this._inputNodes = inputNodes
this._persistentOutput = !!options.persistentOutput
this._needsCache = (options.needsCache != null) ? !!options.needsCache : true
this._checkOverrides()
}
Plugin.prototype._checkOverrides = function() {
if (typeof this.rebuild === 'function') {
throw new Error('For compatibility, plugins must not define a plugin.rebuild() function')
}
if (this.read !== Plugin.prototype.read) {
throw new Error('For compatibility, plugins must not define a plugin.read() function')
}
if (this.cleanup !== Plugin.prototype.cleanup) {
throw new Error('For compatibility, plugins must not define a plugin.cleanup() function')
}
}
// For future extensibility, we version the API using feature flags
Plugin.prototype.__broccoliFeatures__ = Object.freeze({
persistentOutputFlag: true,
sourceDirectories: true,
needsCacheFlag: true
})
// The Broccoli builder calls plugin.__broccoliGetInfo__
Plugin.prototype.__broccoliGetInfo__ = function(builderFeatures) {
this.builderFeatures = this._checkBuilderFeatures(builderFeatures)
if (!this._baseConstructorCalled) throw new Error('Plugin subclasses must call the superclass constructor: Plugin.call(this, inputNodes)')
var nodeInfo = {
nodeType: 'transform',
inputNodes: this._inputNodes,
setup: this._setup.bind(this),
getCallbackObject: this.getCallbackObject.bind(this), // .build, indirectly
instantiationStack: this._instantiationStack,
name: this._name,
annotation: this._annotation,
persistentOutput: this._persistentOutput,
needsCache: this._needsCache
}
// Go backwards in time, removing properties from nodeInfo if they are not
// supported by the builder. Add new features at the top.
if (!this.builderFeatures.needsCacheFlag) {
delete nodeInfo.needsCache
}
return nodeInfo
}
Plugin.prototype._checkBuilderFeatures = function(builderFeatures) {
if (builderFeatures == null) builderFeatures = this.__broccoliFeatures__
if (!builderFeatures.persistentOutputFlag || !builderFeatures.sourceDirectories) {
// No builder in the wild implements less than these.
throw new Error('Minimum builderFeatures required: { persistentOutputFlag: true, sourceDirectories: true }')
}
return builderFeatures
}
Plugin.prototype._setup = function(builderFeatures, options) {
builderFeatures = this._checkBuilderFeatures(builderFeatures)
this._builderFeatures = builderFeatures
this.inputPaths = options.inputPaths
this.outputPath = options.outputPath
if (!this.builderFeatures.needsCacheFlag) {
this.cachePath = this._needsCache ? options.cachePath : undefined
} else {
this.cachePath = options.cachePath
}
}
Plugin.prototype.toString = function() {
return '[' + this._name
+ (this._annotation != null ? ': ' + this._annotation : '')
+ ']'
}
// Return obj on which the builder will call obj.build() repeatedly
//
// This indirection allows subclasses like broccoli-caching-writer to hook
// into calls from the builder, by returning { build: someFunction }
Plugin.prototype.getCallbackObject = function() {
return this
}
Plugin.prototype.build = function() {
throw new Error('Plugin subclasses must implement a .build() function')
}
// Compatibility code so plugins can run on old, .read-based Broccoli:
Plugin.prototype.read = function(readTree) {
if (this._readCompat == null) {
try {
this._initializeReadCompat() // call this.__broccoliGetInfo__()
} catch (err) {
// Prevent trying to initialize again on next .read
this._readCompat = false
// Remember error so we can throw it on all subsequent .read calls
this._readCompatError = err
}
}
if (this._readCompatError != null) throw this._readCompatError
return this._readCompat.read(readTree)
}
Plugin.prototype.cleanup = function() {
if (this._readCompat) return this._readCompat.cleanup()
}
Plugin.prototype._initializeReadCompat = function() {
var ReadCompat = require('./read_compat')
this._readCompat = new ReadCompat(this)
}
function isPossibleNode(node) {
return typeof node === 'string' ||
(node !== null && typeof node === 'object')
}