/
graphquire.js
193 lines (168 loc) · 6.41 KB
/
graphquire.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: true undef: true es5: true node: true devel: true
forin: true latedef: false globalstrict: true */
/*global define: true */
'use strict';
var path = require('path')
var fs = require('fs')
var http = require('http')
var https = require('https')
var url = require('url')
var COMMENT_PATTERN = /(\/\*[\s\S]*?\*\/)|((^|\n)[^('|"|\n)]*\/\/[^\n]*)/g
var REQUIRE_PATTERN = /require\s*\(['"]([\w\W]*?)['"]\s*\)/g
var GET_METADATA = exports.GET_METADATA = 0
var GOT_METADATA = exports.GOT_MODULE = 1
var GET_MODULE = exports.GET_MODULE = 2
var READ_FILE = exports.READ_FILE = 3
var FETCH_URL = exports.FETCH_URL = 4
var GOT_MODULE = exports.GOT_MODULE = 5
function extractDependencies(source) {
var dependency, dependencies = []
// Removing comments to avoid capturing commented require statements.
source = String(source).replace(COMMENT_PATTERN, '')
// Push each found dependency into array.
while ((dependency = REQUIRE_PATTERN.exec(source)))
dependencies.push(dependency[1])
return dependencies
}
function isPackageLocation(uri) { return path.basename(uri) === "package.json" }
function isURI(uri) {
return 0 === uri.indexOf('http:') || 0 === uri.indexOf('https:')
}
function normalizePackageLocation(uri) {
return isPackageLocation(uri) ? uri :
uri + (uri[uri.length - 1] === "/" ? "" : "/") + "package.json"
}
function isRelative(id) { return id && id.charAt(0) === '.' }
function normalizeURI(uri) { return path.extname(uri) ? uri : uri + '.js' }
function resolveID(id, base) {
var path, paths, last
if (!isRelative(id)) return id
paths = id.split('/')
base = base ? base.split('/') : [ '.' ]
if (base.length > 1) base.pop()
while ((path = paths.shift())) {
if (path === '..') {
if (base.length && base[base.length - 1] !== '..') {
if (base.pop() === '.') base.push(path)
} else base.push(path)
} else if (path !== '.') {
base.push(path)
}
}
if (base[base.length - 1].substr(-1) === '.') base.push('')
return base.join('/')
}
function resolve(id, base) {
return normalizeURI(isURI(id) ? id : resolveID(id, base))
}
function readURL(uri, callback) {
var options = url.parse(uri)
options.path = options.pathname
options.followRedirect = true
options.maxRedirects = 2
var get = options.protocol === 'http:' ? http.get : https.get
var buffer = ''
get(options, function onResponse(response) {
response.on('error', callback)
response.on('data', function onData(chunk) { buffer += chunk })
response.on('end', function onEnd() {
callback(null, buffer, true)
})
}).on('error', callback)
}
exports.readURL = readURL
function getSource(graph, module, onComplete, onProgress) {
var path = graph.resolvePath(module.id)
var uri = graph.resolveURI(module.id)
if (path) {
if (onProgress) onProgress(READ_FILE, module.path)
fs.readFile(path, function onRead(error, buffer) {
if (!error || error.code !== 'ENOENT' || !uri)
return onComplete(error, buffer)
if (onProgress) onProgress(FETCH_URL, uri)
readURL(uri, onComplete)
})
} else {
module.isNative = true
return onComplete(null, module)
}
}
function getDependency(graph, requirer, next, onProgress, dependencyID) {
var id, module;
id = resolve(dependencyID, requirer.id)
id = requirer.requirements[dependencyID] = id
// If module is already loaded or is being fetched we just go next.
if ((module = graph.modules[id]))
return next(null, graph, module, next)
// Otherwise we create module and start resolving it's dependencies
module = graph.modules[id] = { id: id }
resolveRequirements(graph, module, next, onProgress)
}
function Next(total, onComplete, onProgress) {
var current = 0
return function next(error, graph, module) {
if (error) return onComplete(error)
if (++ current === total)
onComplete(error, graph, module)
}
}
function resolveRequirements(graph, module, onComplete, onProgress) {
if (onProgress) onProgress(GET_MODULE, module)
getSource(graph, module, function onSource(error, source, isRemote) {
var dependencies, resolved = 0
if (error) return onComplete(error)
if (onProgress) onProgress(GOT_MODULE, module)
if (isRemote && graph.includesSource) module.source = source
// Extracting module dependencies by analyzing it's source.
dependencies = extractDependencies(source)
// If module has no dependencies we call callback and return immediately.
if (!dependencies.length)
return onComplete(error, graph, module)
// If we got this far we know module has dependencies, so we create it's
// requirements map.
module.requirements = {}
// Creating dependency tracker which we will call after each dependency is
// resolved. Tracker will call `callback` once all the dependencies of this
// module will be resolved.
var next = Next(dependencies.length, onComplete)
dependencies.forEach(getDependency.bind(null, graph, module, next, onProgress))
}, onProgress)
}
exports.resolveRequirements = resolveRequirements
function getMetadata(location, callback) {
var read = isURI(location) ? readURL : fs.readFile
read(location, callback)
}
exports.getMetadata = getMetadata
function getGraph(options, onComplete, onProgress) {
var location = normalizePackageLocation(options.location)
var graph = {
path: isURI(location) ? './' : location,
uri: isURI(location) ? location : './',
cachePath: options.cachePath || './',
includesSource: options.includeSource || false,
escape: options.escape || false,
resolvePath: function resolvePath(id) {
var root = path.dirname(graph.path)
return isURI(id) ?
path.join(root, graph.cachePath,
graph.escape ? id.replace(/:/, encodeURIComponent) : id)
: isRelative(id) ? path.join(root, id) : null
},
resolveURI: function resolveURI(id) {
return isURI(id) ? id : isRelative(id) ? resolve(id, graph.uri) : null
}
}
if (onProgress) onProgress(GET_METADATA, location)
getMetadata(location, function onMetadata(error, content) {
if (error) return onComplete(error)
graph.metadata = JSON.parse(String(content))
graph.modules = {}
if (onProgress) onProgress(GOT_METADATA, graph.metadata)
var main = { id: graph.metadata.main || "./index.js" }
graph.modules[main.id] = main
resolveRequirements(graph, main, onComplete, onProgress)
})
}
exports.getGraph = getGraph