[4mRunning "mochacov:cov" (mochacov) task[24m
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | fu = require('./fileUtil'); | |
4 | ||
5 | /** | |
6 | * Creates a list of all of the resources for the current module. | |
7 | * | |
8 | * Context state: module | |
9 | * | |
10 | * Plugin Calls: | |
11 | * moduleResources | |
12 | * fileFilter | |
13 | * resourceList | |
14 | */ | |
15 | 1 | exports.loadResources = function(context, callback) { |
16 | 311 | var plugins = context.plugins; |
17 | ||
18 | 311 | function filterResource(resource) { |
19 | 535 | if (_.isString(resource)) { |
20 | 161 | resource = { src: resource }; |
21 | } | |
22 | ||
23 | 535 | if (exports.filterResource(resource, context)) { |
24 | 454 | return resource; |
25 | } | |
26 | } | |
27 | ||
28 | 311 | plugins.moduleResources(context, function(err, files) { |
29 | 311 | if (err) { |
30 | 0 | return callback(err); |
31 | } | |
32 | ||
33 | 311 | var fileFilter = plugins.fileFilter(context) || /.*/; |
34 | 311 | fu.fileList(files, fileFilter, function(err, files) { |
35 | 311 | if (err) { |
36 | 0 | callback(err); |
37 | 0 | return; |
38 | } | |
39 | ||
40 | 311 | async.map(files, function(resource, callback) { |
41 | 488 | var resourceContext = context.clone(); |
42 | 488 | resourceContext.resource = resource; |
43 | 488 | plugins.resourceList(resourceContext, callback); |
44 | }, | |
45 | function(err, resources) { | |
46 | 311 | resources = _.flatten(resources); |
47 | 311 | resources = _.map(resources, filterResource); |
48 | 846 | resources = _.filter(resources, function(resource) { return resource; }); |
49 | 311 | callback(err, resources); |
50 | }); | |
51 | }); | |
52 | }); | |
53 | }; | |
54 | ||
55 | /** | |
56 | * Filters a given resource for platform constraints, if specified. | |
57 | */ | |
58 | 1 | exports.filterResource = function(resource, context) { |
59 | 875 | function check(value, singular, plural) { |
60 | 2377 | if (typeof singular !== 'undefined') { |
61 | 172 | return singular.not ? singular.not !== value : singular === value; |
62 | 2205 | } else if (plural) { |
63 | 73 | var ret = (plural.not || plural).reduce(function(found, filePlatform) { |
64 | 105 | return found || filePlatform === value; |
65 | }, false); | |
66 | 73 | return plural.not ? !ret : ret; |
67 | } | |
68 | 2132 | return true; |
69 | } | |
70 | ||
71 | 875 | function checkResource(resource) { |
72 | 879 | return check(context.platform, resource.platform, resource.platforms) |
73 | && check(context.package, resource.package, resource.packages) | |
74 | && check(!!context.combined, resource.combined); | |
75 | } | |
76 | 875 | return checkResource(resource) |
77 | && (!resource.originalResource || checkResource(resource.originalResource)); | |
78 | }; | |
79 | ||
80 | ||
81 | /** | |
82 | * Runs a set of resources through the resource plugin. | |
83 | * | |
84 | * Context state: module | |
85 | * | |
86 | * Plugin Calls: | |
87 | * resource | |
88 | */ | |
89 | 1 | exports.processResources = function(resources, context, callback) { |
90 | 313 | var plugins = context.plugins; |
91 | ||
92 | 313 | async.map(resources, function(resource, callback) { |
93 | 432 | var resourceContext = context.clone(); |
94 | 432 | resourceContext.resource = resource; |
95 | 432 | plugins.resource(resourceContext, function(err, newResource) { |
96 | 432 | if (newResource && newResource !== resource) { |
97 | 97 | newResource.originalResource = resource; |
98 | } | |
99 | ||
100 | 432 | callback(err, newResource); |
101 | }); | |
102 | }, | |
103 | function(err, resources) { | |
104 | 313 | if (err) { |
105 | 0 | return callback(err); |
106 | } | |
107 | ||
108 | 745 | callback(err, resources.filter(function(resource) { return resource; })); |
109 | }); | |
110 | }; | |
111 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | cp = require('child_process'); | |
3 | ||
4 | 1 | var numCPUs = require('os').cpus().length; |
5 | ||
6 | 1 | exports = module.exports = function(module, options) { |
7 | 2 | this.module = module; |
8 | 2 | this.options = _.defaults({workers: numCPUs, keepAlive: 100}, options); |
9 | 2 | this.workers = []; |
10 | 2 | this.queue = []; |
11 | 2 | this.cullTimeout = undefined; |
12 | }; | |
13 | 1 | exports.prototype.sendAll = function(message) { |
14 | 233 | _.each(this.workers, function(worker) { |
15 | 455 | worker.process.send(message); |
16 | }); | |
17 | }; | |
18 | 1 | exports.prototype.send = function(message, callback) { |
19 | 31 | var self = this; |
20 | ||
21 | 31 | callback = callback || function() {}; |
22 | ||
23 | 31 | function fork() { |
24 | 12 | worker = { |
25 | callback: false, | |
26 | process: cp.fork(self.module) | |
27 | }; | |
28 | 12 | worker.process.on('message', function(msg) { |
29 | 31 | if (msg.log) { |
30 | 0 | return console.log(msg.log); |
31 | } | |
32 | ||
33 | 31 | var callback = worker.callback; |
34 | 31 | worker.callback = undefined; |
35 | ||
36 | 31 | if (self.queue.length) { |
37 | 0 | var queued = self.queue.shift(); |
38 | 0 | self.send(queued.message, queued.callback); |
39 | } else { | |
40 | // Check for process culling | |
41 | 31 | worker.cullTimeout = setTimeout(function() { |
42 | // Kill | |
43 | 12 | worker.process.kill(); |
44 | 12 | self.workers = _.without(self.workers, worker); |
45 | }, self.options.keepAlive); | |
46 | } | |
47 | ||
48 | // Dispatch | |
49 | 31 | process.nextTick(function() { |
50 | 31 | callback(msg.err, msg.data); |
51 | }); | |
52 | }); | |
53 | 12 | self.workers.push(worker); |
54 | } | |
55 | ||
56 | 85 | var worker = _.find(this.workers, function(worker) { return !worker.callback; }); |
57 | 31 | if (!worker) { |
58 | 12 | if (this.workers.length < this.options.workers) { |
59 | // Fork! | |
60 | 12 | fork(); |
61 | } else { | |
62 | // Queue! | |
63 | 0 | self.queue.push({message: message, callback: callback}); |
64 | 0 | return; |
65 | } | |
66 | } | |
67 | ||
68 | 31 | clearTimeout(worker.cullTimeout); |
69 | 31 | worker.callback = callback; |
70 | 31 | worker.process.send(message); |
71 | }; | |
72 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | fu = require('./fileUtil'), | |
3 | path = require('path'), | |
4 | vm = require('vm'); | |
5 | ||
6 | /** | |
7 | * Reads the RAW JSON for a lumbar config file. | |
8 | */ | |
9 | 1 | exports.readConfig = function(lumbarFile) { |
10 | 33 | try { |
11 | 33 | var data = '(' + fu.readFileSync(lumbarFile) + ')'; |
12 | ||
13 | // Yes this is totally unsafe, but we don't want the strictness of pure JSON for our | |
14 | // config files and if you are running an untrusted lumbar file you already have concerns. | |
15 | 33 | return vm.runInThisContext(data, lumbarFile); |
16 | } catch (err) { | |
17 | 0 | var line; |
18 | 0 | try { |
19 | 0 | var esprima = require('esprima'); |
20 | 0 | console.log(err.stack, esprima.parse(data)); |
21 | } catch (err) { | |
22 | 0 | if (err.lineNumber) { |
23 | 0 | line = ':' + err.lineNumber; |
24 | } | |
25 | } | |
26 | 0 | throw new Error('Failed to load config ' + lumbarFile + line + ': ' + err); |
27 | } | |
28 | }; | |
29 | ||
30 | /** | |
31 | * | |
32 | * @name load | |
33 | * @function This function loads the lumbar JSON file, and returns | |
34 | * helper methods associated with accessing its specific data. | |
35 | * @param {string} lumbarFile the location of the lumbar file. | |
36 | * @return {Object} | |
37 | */ | |
38 | 1 | exports.load = function(lumbarFile) { |
39 | 27 | fu.lookupPath(''); |
40 | ||
41 | 27 | var config = exports.readConfig(lumbarFile); |
42 | 27 | fu.lookupPath(path.dirname(lumbarFile)); |
43 | ||
44 | 27 | return exports.create(config); |
45 | }; | |
46 | ||
47 | 1 | exports.create = function(config) { |
48 | 125 | var packageList, moduleList; |
49 | ||
50 | 125 | function loadPackageList() { |
51 | 125 | if (!config.packages) { |
52 | 113 | config.packages = { web: { name: '' } }; |
53 | } | |
54 | ||
55 | 125 | packageList = _.keys(config.packages); |
56 | } | |
57 | 125 | function loadModuleList() { |
58 | 125 | if (!config.modules) { |
59 | 1 | throw new Error('No modules object defined'); |
60 | } | |
61 | 124 | moduleList = _.keys(config.modules); |
62 | } | |
63 | ||
64 | 125 | loadPackageList(); |
65 | 125 | loadModuleList(); |
66 | ||
67 | 124 | return { |
68 | /** @typedef {Object} The raw lumbar file as a JSON object. */ | |
69 | attributes: config, | |
70 | loadPrefix: function() { | |
71 | 46 | return config.loadPrefix || ''; |
72 | }, | |
73 | ||
74 | /** | |
75 | * | |
76 | * @name packageList | |
77 | * @function This function returns the list of packages found | |
78 | * in the lumbar file. | |
79 | * @return {Array.<Object>} array of package(s). | |
80 | */ | |
81 | packageList: function() { | |
82 | 27 | return packageList; |
83 | }, | |
84 | ||
85 | /** | |
86 | * | |
87 | * @name combineModules | |
88 | * @function This functions checks to see if the package, pkg, | |
89 | * is going to combine all its modules or not. | |
90 | * @param {string} pkg the name of the package | |
91 | * @return {boolean} is this package destined to be combined? | |
92 | */ | |
93 | combineModules: function(pkg) { | |
94 | 1809 | if (config && config.packages && config.packages[pkg]) { |
95 | 1553 | return config.packages[pkg].combine; |
96 | } | |
97 | 256 | return false; |
98 | }, | |
99 | platformList: function(pkg) { | |
100 | 86 | if (!pkg) { |
101 | 52 | return config.platforms || ['']; |
102 | } else { | |
103 | 34 | if (config.packages[pkg]) { |
104 | 34 | return config.packages[pkg].platforms || this.platformList(); |
105 | } | |
106 | 0 | return this.platformList(); |
107 | } | |
108 | }, | |
109 | ||
110 | moduleList: function(pkg) { | |
111 | 234 | return (config.packages[pkg] || {}).modules || _.keys(config.modules); |
112 | }, | |
113 | ||
114 | module: function(name) { | |
115 | 480 | var ret = config.modules[name]; |
116 | 480 | if (ret) { |
117 | 478 | ret.name = name; |
118 | } | |
119 | 480 | return ret; |
120 | }, | |
121 | isAppModule: function(module) { | |
122 | 73 | var app = config.application; |
123 | 73 | return (app && app.module) === (module.name || module); |
124 | }, | |
125 | scopedAppModuleName: function(module) { | |
126 | 43 | var app = config.application; |
127 | 43 | if (this.isAppModule(module)) { |
128 | 4 | return 'module.exports'; |
129 | } else { | |
130 | 39 | var app = config.application; |
131 | 39 | return app && app.name; |
132 | } | |
133 | }, | |
134 | ||
135 | routeList: function(module) { | |
136 | 22 | return config.modules[module].routes; |
137 | }, | |
138 | ||
139 | serialize: function() { | |
140 | 1 | function objectClone(object) { |
141 | 13 | var clone = object; |
142 | ||
143 | 13 | if (object && object.serialize) { |
144 | // Allow specialized objects to handle themselves | |
145 | 0 | clone = object.serialize(); |
146 | 13 | } else if (_.isArray(object)) { |
147 | 1 | clone = _.map(object, objectClone); |
148 | 12 | } else if (_.isObject(object)) { |
149 | 7 | clone = {}; |
150 | 7 | _.each(object, function(value, name) { |
151 | 10 | clone[name] = objectClone(value); |
152 | }); | |
153 | } | |
154 | ||
155 | // Collapse simple resources | |
156 | 13 | if (clone.src && _.keys(clone).length === 1) { |
157 | 0 | clone = clone.src; |
158 | } | |
159 | ||
160 | 13 | return clone; |
161 | } | |
162 | ||
163 | 1 | return objectClone(this.attributes); |
164 | } | |
165 | }; | |
166 | }; | |
167 | ||
168 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | fs = require('fs'), | |
4 | fu = require('./fileUtil'); | |
5 | ||
6 | 1 | function Context(options, config, plugins, libraries, event) { |
7 | 2057 | this._package = options.package; |
8 | 2057 | this._platform = options.platform; |
9 | 2057 | this._plugins = plugins; |
10 | 2057 | this.mode = options.mode; |
11 | 2057 | this.module = options.module; |
12 | 2057 | this.fileConfig = options.fileConfig; |
13 | 2057 | this.resource = options.resource; |
14 | 2057 | this.config = config; |
15 | 2057 | this.libraries = libraries || options.libraries; |
16 | 2057 | this.event = event || options.event; |
17 | } | |
18 | 1 | Context.prototype = { |
19 | fileUtil: fu, | |
20 | ||
21 | clone: function(options) { | |
22 | 1935 | var ret = new Context(this, this.config); |
23 | 1935 | ret.parent = this; |
24 | 1935 | var prototype = Object.keys(Context.prototype); |
25 | 1935 | for (var name in this) { |
26 | 60098 | if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) { |
27 | 34943 | ret[name] = this[name]; |
28 | } | |
29 | } | |
30 | 1935 | if (options) { |
31 | 255 | _.extend(ret, options); |
32 | 255 | ret._package = options.package || this._package; |
33 | 255 | ret._platform = options.platform || this._platform; |
34 | } | |
35 | 1935 | return ret; |
36 | }, | |
37 | ||
38 | fileNamesForModule: function(mode, moduleName, callback) { | |
39 | 83 | var context = this.clone(); |
40 | 83 | context.mode = mode; |
41 | 83 | context.module = moduleName && this.config.module(moduleName); |
42 | 83 | if (moduleName && !context.module) { |
43 | 2 | return callback(new Error('Unknown module "' + moduleName + '"')); |
44 | } | |
45 | ||
46 | 81 | this.plugins.outputConfigs(context, function(err, configs) { |
47 | 81 | if (err) { |
48 | 0 | return callback(err); |
49 | } | |
50 | ||
51 | 81 | async.map(configs, function(config, callback) { |
52 | 107 | var fileContext = context.clone(); |
53 | 107 | fileContext.fileConfig = config; |
54 | 107 | fileContext._plugins.fileName(fileContext, function(err, fileName) { |
55 | 107 | config.fileName = fileName; |
56 | 107 | callback(err, config); |
57 | }); | |
58 | }, | |
59 | callback); | |
60 | }); | |
61 | }, | |
62 | ||
63 | loadResource: function(resource, callback) { | |
64 | 412 | if (!callback) { |
65 | // if only single param, assume as callback and resource from context | |
66 | 0 | resource = this.resource; |
67 | 0 | callback = resource; |
68 | } | |
69 | ||
70 | 412 | var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src}; |
71 | ||
72 | 412 | function loaded(err, data) { |
73 | /*jshint eqnull: true */ | |
74 | 412 | if (err) { |
75 | 2 | var json = ''; |
76 | 2 | try { |
77 | // Output JSON for the resource... but protect ourselves from a failure masking a failure | |
78 | 2 | resource = _.clone(resource); |
79 | 2 | delete resource.library; |
80 | 2 | delete resource.enoent; |
81 | 2 | json = '\n\t' + JSON.stringify(resource); |
82 | } catch (err) { /* NOP */ } | |
83 | ||
84 | 2 | var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err)); |
85 | 2 | errorWrapper.source = err; |
86 | 2 | errorWrapper.code = err.code; |
87 | 2 | callback(errorWrapper); |
88 | 2 | return; |
89 | } | |
90 | 410 | fileInfo.inputs = data.inputs; |
91 | 410 | fileInfo.generated = data.generated; |
92 | 410 | fileInfo.noSeparator = data.noSeparator; |
93 | 410 | fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings; |
94 | 410 | fileInfo.content = data.data != null ? data.data : data; |
95 | ||
96 | // Ensure that we dump off the stack | |
97 | 410 | _.defer(function() { |
98 | 410 | callback(err, fileInfo); |
99 | }); | |
100 | } | |
101 | ||
102 | 412 | if (typeof resource === 'function') { |
103 | 193 | resource(this, loaded); |
104 | 219 | } else if (resource.src) { |
105 | // Assume a file page, attempt to load | |
106 | 206 | fu.readFile(resource.src, loaded); |
107 | } else { | |
108 | 13 | loaded(undefined, {data: '', noSeparator: true, inputs: resource.dir ? [resource.dir] : []}); |
109 | } | |
110 | ||
111 | 412 | return fileInfo; |
112 | }, | |
113 | ||
114 | outputFile: function(writer, callback) { | |
115 | 139 | var context = this; |
116 | 139 | context.plugins.file(context, function(err) { |
117 | 139 | if (err) { |
118 | 0 | return callback(err); |
119 | } | |
120 | ||
121 | 139 | context.plugins.fileName(context, function(err, fileName) { |
122 | 139 | if (err) { |
123 | 0 | return callback(err); |
124 | } | |
125 | ||
126 | 139 | context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension; |
127 | 139 | context.fileName = context.outdir + '/' + context.buildPath; |
128 | 139 | writer(function(err, data) { |
129 | 139 | data = _.defaults({ |
130 | fileConfig: context.fileConfig, | |
131 | platform: context.platform, | |
132 | package: context.package, | |
133 | mode: context.mode | |
134 | }, data); | |
135 | ||
136 | 139 | if (err) { |
137 | 1 | fs.unlink(context.fileName); |
138 | } else { | |
139 | 138 | context.event.emit('output', data); |
140 | } | |
141 | 139 | context.fileCache = undefined; |
142 | 139 | callback(err, data); |
143 | }); | |
144 | }); | |
145 | }); | |
146 | }, | |
147 | ||
148 | get description() { | |
149 | 0 | var ret = 'package:' + this.package + '_platform:' + this.platform; |
150 | 0 | if (this.mode) { |
151 | 0 | ret += '_mode:' + this.mode; |
152 | } | |
153 | 0 | if (this.fileName) { |
154 | 0 | ret += '_config:' + this.fileName; |
155 | } | |
156 | 0 | if (this.module) { |
157 | 0 | ret += '_module:' + (this.module.name || this.module); |
158 | } | |
159 | 0 | if (this.resource) { |
160 | // TODO : Anything better for this? | |
161 | 0 | ret += '_resource:' + (this.resource.src || this.resource); |
162 | } | |
163 | 0 | return ret; |
164 | }, | |
165 | ||
166 | 1228 | get plugins() { return this._plugins; }, |
167 | ||
168 | 4963 | get package() { return this._package; }, |
169 | 3496 | get platform() { return this._platform; }, |
170 | get platformPath() { | |
171 | 151 | return this.platform ? this.platform + '/' : ''; |
172 | }, | |
173 | ||
174 | get combined() { | |
175 | 1809 | return this.config.combineModules(this.package); |
176 | }, | |
177 | get baseName() { | |
178 | 220 | if (!this.combined) { |
179 | 152 | return this.module.name; |
180 | } else { | |
181 | 68 | return (this.config.attributes.packages[this.package] || {}).name || this.package; |
182 | } | |
183 | }, | |
184 | ||
185 | get resources() { | |
186 | 282 | if (this.parent) { |
187 | 0 | return this.parent.resources; |
188 | } else { | |
189 | 282 | return this._resources; |
190 | } | |
191 | }, | |
192 | set resources(value) { | |
193 | 340 | if (this.parent) { |
194 | 304 | delete this.parent; |
195 | } | |
196 | 340 | this._resources = value; |
197 | } | |
198 | }; | |
199 | ||
200 | 1 | module.exports = Context; |
201 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | EventEmitter = require('events').EventEmitter, | |
3 | fs = require('fs'), | |
4 | path = require('path'), | |
5 | handlebars = require('handlebars'); | |
6 | ||
7 | 1 | const EMFILE_RETRY = 250; |
8 | ||
9 | 1 | var fileCache = {}; |
10 | ||
11 | 1 | exports = module.exports = new EventEmitter(); |
12 | ||
13 | 1 | function cacheRead(path, exec, callback) { |
14 | 348 | path = exports.resolvePath(path); |
15 | ||
16 | 348 | var cache = fileCache[path]; |
17 | 348 | if (cache) { |
18 | 192 | if (cache.data) { |
19 | 132 | callback(undefined, cache); |
20 | } else { | |
21 | 60 | cache.pending.push(callback); |
22 | } | |
23 | 192 | return; |
24 | } | |
25 | ||
26 | 156 | cache = fileCache[path] = { |
27 | pending: [callback], | |
28 | artifacts: {} | |
29 | }; | |
30 | ||
31 | 156 | exec(path, function _callback(err, data) { |
32 | 156 | if (err && err.code === 'EMFILE') { |
33 | 0 | setTimeout(exec.bind(this, path, _callback), EMFILE_RETRY); |
34 | } else { | |
35 | 156 | if (err) { |
36 | 2 | delete fileCache[path]; |
37 | } | |
38 | ||
39 | 156 | cache.data = data; |
40 | 156 | cache.pending.forEach(function(callback) { |
41 | 216 | callback(err, cache); |
42 | }); | |
43 | 156 | exports.emit('cache:set', path); |
44 | } | |
45 | }); | |
46 | } | |
47 | ||
48 | 1 | exports.resetCache = function(filePath) { |
49 | 233 | filePath = filePath && path.normalize(filePath); |
50 | 233 | exports.emit('cache:reset', filePath); |
51 | ||
52 | 233 | if (filePath) { |
53 | 171 | filePath = exports.resolvePath(filePath); |
54 | 171 | delete fileCache[filePath]; |
55 | } else { | |
56 | 62 | fileCache = {}; |
57 | } | |
58 | }; | |
59 | ||
60 | 1 | var lookupPath; |
61 | 1 | exports.resolvePath = function(pathName) { |
62 | // Poormans path.resolve. We aren't able to use the bundled path.resolve due to | |
63 | // it throwing sync EMFILE errors without a type to key on. | |
64 | 1255 | if (lookupPath |
65 | && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1) | |
66 | && pathName.indexOf(lookupPath) !== 0) { | |
67 | 847 | return lookupPath + pathName; |
68 | } else { | |
69 | 408 | return pathName; |
70 | } | |
71 | }; | |
72 | 1 | exports.makeRelative = function(pathName) { |
73 | 561 | if (pathName.indexOf(lookupPath) === 0) { |
74 | 543 | return pathName.substring(lookupPath.length); |
75 | } else { | |
76 | 18 | return pathName; |
77 | } | |
78 | }; | |
79 | ||
80 | 1 | exports.lookupPath = function(pathName) { |
81 | 135 | if (pathName !== undefined) { |
82 | 83 | lookupPath = pathName; |
83 | 83 | if (lookupPath && !/\/$/.test(lookupPath)) { |
84 | 36 | lookupPath += '/'; |
85 | } | |
86 | } | |
87 | 135 | return lookupPath; |
88 | }; | |
89 | ||
90 | 1 | exports.stat = function(file, callback) { |
91 | 796 | fs.stat(file, function(err, stat) { |
92 | 796 | if (err && err.code === 'EMFILE') { |
93 | 0 | setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY); |
94 | } else { | |
95 | 796 | callback(err, stat); |
96 | } | |
97 | }); | |
98 | }; | |
99 | ||
100 | 1 | exports.readFileSync = function(file) { |
101 | 33 | return fs.readFileSync(exports.resolvePath(file)); |
102 | }; | |
103 | 1 | exports.readFile = function(file, callback) { |
104 | 230 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
105 | 230 | callback(err, cache && cache.data); |
106 | }); | |
107 | }; | |
108 | 1 | exports.readFileArtifact = function(file, name, callback) { |
109 | 58 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
110 | 58 | var artifacts = cache.artifacts; |
111 | 58 | callback(err, {data: cache.data, artifact: artifacts[name]}); |
112 | }); | |
113 | }; | |
114 | 1 | exports.setFileArtifact = function(path, name, artifact) { |
115 | 24 | path = exports.resolvePath(path); |
116 | ||
117 | 24 | var cache = fileCache[path]; |
118 | 24 | if (cache) { |
119 | 24 | cache.artifacts[name] = artifact; |
120 | } | |
121 | }; | |
122 | ||
123 | 1 | exports.readdir = function(dir, callback) { |
124 | 60 | cacheRead(dir, fs.readdir.bind(fs), function(err, cache) { |
125 | 60 | callback(err, cache && cache.data); |
126 | }); | |
127 | }; | |
128 | ||
129 | 1 | exports.ensureDirs = function(pathname, callback) { |
130 | 239 | var dirname = path.dirname(pathname); |
131 | 239 | exports.stat(dirname, function(err) { |
132 | 239 | if (err && err.code === 'ENOENT') { |
133 | // If we don't exist, check to see if our parent exists before trying to create ourselves | |
134 | 48 | exports.ensureDirs(dirname, function() { |
135 | 48 | fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) { |
136 | 48 | if (err && err.code === 'EMFILE') { |
137 | 0 | setTimeout(fs.mkdir.bind(fs, dirname, parseInt('0755', 8), _callback), EMFILE_RETRY); |
138 | } else { | |
139 | // Off to the races... and we lost. | |
140 | 48 | callback(err && err.code === 'EEXIST' ? undefined : err); |
141 | } | |
142 | }); | |
143 | }); | |
144 | } else { | |
145 | 191 | callback(); |
146 | } | |
147 | }); | |
148 | }; | |
149 | ||
150 | 1 | exports.writeFile = function(file, data, callback) { |
151 | 133 | exports.resetCache(file); |
152 | ||
153 | 133 | exports.ensureDirs(file, function(err) { |
154 | 133 | if (err) { |
155 | 0 | return callback(err); |
156 | } | |
157 | ||
158 | 133 | fs.writeFile(file, data, 'utf8', function _callback(err) { |
159 | 133 | if (err && err.code === 'EMFILE') { |
160 | 0 | setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY); |
161 | } else { | |
162 | 133 | callback(err); |
163 | } | |
164 | }); | |
165 | }); | |
166 | }; | |
167 | ||
168 | /** | |
169 | * Takes a given input and returns the files that are represented. | |
170 | * | |
171 | * pathname may be: | |
172 | * a resource object | |
173 | * a path on the file system | |
174 | * an array of resources | |
175 | */ | |
176 | 1 | exports.fileList = function(pathname, extension, callback, dirList, resource, srcDir) { |
177 | 848 | if (_.isFunction(extension)) { |
178 | 5 | callback = extension; |
179 | 5 | extension = /.*/; |
180 | } | |
181 | ||
182 | 848 | if (_.isArray(pathname)) { |
183 | 293 | var files = pathname; |
184 | 293 | pathname = ''; |
185 | 293 | if (!files.length) { |
186 | 119 | return callback(undefined, []); |
187 | } | |
188 | 174 | return handleFiles(false, undefined, _.uniq(files)); |
189 | 555 | } else if (!dirList) { |
190 | 395 | if (pathname.src) { |
191 | 0 | resource = resource || pathname; |
192 | 0 | pathname = pathname.src; |
193 | } | |
194 | ||
195 | 395 | pathname = exports.resolvePath(pathname); |
196 | } | |
197 | 555 | if (resource && resource.src) { |
198 | 183 | resource = _.clone(resource); |
199 | 183 | delete resource.src; |
200 | } | |
201 | ||
202 | 555 | function handleFiles(dirname, err, files, srcDir) { |
203 | 231 | if (err) { |
204 | 0 | return callback(err); |
205 | } | |
206 | ||
207 | 231 | var ret = [], |
208 | count = 0, | |
209 | expected = files.length, | |
210 | prefix = pathname ? pathname.replace(/\/$/, '') + '/' : ''; | |
211 | ||
212 | 231 | function complete(files, index) { |
213 | 592 | count++; |
214 | ||
215 | 592 | ret[index] = files; |
216 | ||
217 | 592 | if (count === expected) { |
218 | 230 | ret = _.flatten(ret); |
219 | ||
220 | 230 | if (srcDir) { |
221 | 56 | ret = ret.map(function(file) { |
222 | 123 | if (!file.src && !file.dir) { |
223 | 0 | file = { src: file }; |
224 | } | |
225 | 123 | file.srcDir = srcDir; |
226 | 123 | return file; |
227 | }); | |
228 | } | |
229 | ||
230 | 230 | if (dirname) { |
231 | 56 | ret.push(_.defaults({dir: dirname}, resource)); |
232 | 56 | ret = ret.sort(function(a, b) { |
233 | 240 | return (a.dir || a.src || a).localeCompare(b.dir || b.src || b); |
234 | }); | |
235 | } | |
236 | ||
237 | 230 | callback(undefined, ret); |
238 | } | |
239 | } | |
240 | ||
241 | 231 | if (!files.length) { |
242 | 1 | callback(undefined, []); |
243 | } | |
244 | ||
245 | 231 | files.forEach(function(file, index) { |
246 | 592 | var fileResource = resource; |
247 | 592 | if (file.src) { |
248 | 183 | fileResource = resource || file; |
249 | 183 | file = file.src; |
250 | 409 | } else if (_.isObject(file)) { |
251 | 64 | complete(file, index); |
252 | 64 | return; |
253 | } | |
254 | ||
255 | 528 | exports.fileList(prefix + file, extension, function(err, files) { |
256 | 528 | if (err) { |
257 | 0 | callback(err); |
258 | 0 | return; |
259 | } | |
260 | ||
261 | 528 | complete(files, index); |
262 | }, dirname, fileResource, srcDir); | |
263 | }); | |
264 | } | |
265 | ||
266 | 555 | exports.stat(pathname, function(err, stat) { |
267 | 555 | if (err) { |
268 | 47 | if (err.code === 'ENOENT') { |
269 | 47 | callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]); |
270 | } else { | |
271 | 0 | callback(err); |
272 | } | |
273 | 47 | return; |
274 | } | |
275 | ||
276 | 508 | if (stat.isDirectory()) { |
277 | 57 | exports.readdir(pathname, function(err, files) { |
278 | 57 | var _pathname = exports.makeRelative(pathname); |
279 | 57 | handleFiles(_pathname, undefined, files, srcDir || _pathname); |
280 | }); | |
281 | } else { | |
282 | 451 | pathname = exports.makeRelative(pathname); |
283 | ||
284 | 451 | var basename = path.basename(pathname), |
285 | namePasses = basename[0] !== '.' && basename !== 'vendor' && (!dirList || extension.test(pathname)), | |
286 | ret = []; | |
287 | 451 | if (namePasses) { |
288 | 393 | if (resource) { |
289 | 170 | ret = [ _.defaults({src: pathname, srcDir: srcDir}, resource) ]; |
290 | 223 | } else if (srcDir) { |
291 | 70 | ret = [ { src: pathname, srcDir: srcDir } ]; |
292 | } else { | |
293 | 153 | ret = [ pathname ]; |
294 | } | |
295 | } | |
296 | 451 | callback(undefined, ret); |
297 | } | |
298 | }); | |
299 | }; | |
300 | ||
301 | //accepts a template string or a filename ending in .handlebars | |
302 | 1 | exports.loadTemplate = function(template, splitOnDelimiter, callback) { |
303 | 40 | function compile(templateStr, callback) { |
304 | 29 | try { |
305 | 29 | if (splitOnDelimiter) { |
306 | 19 | callback(null, templateStr.split(splitOnDelimiter).map(function(bit) { |
307 | 38 | return handlebars.compile(bit); |
308 | })); | |
309 | } else { | |
310 | 10 | callback(null, handlebars.compile(templateStr)); |
311 | } | |
312 | } catch (e) { | |
313 | 0 | callback(e); |
314 | } | |
315 | } | |
316 | 40 | if (template.match(/\.handlebars$/)) { |
317 | 19 | exports.readFileArtifact(template, 'template', function(err, data) { |
318 | 19 | if (err) { |
319 | 1 | return callback(err); |
320 | } | |
321 | ||
322 | 18 | if (data.artifact) { |
323 | 10 | callback(undefined, data.artifact); |
324 | } else { | |
325 | 8 | compile(data.data.toString(), function(err, data) { |
326 | 8 | if (!err) { |
327 | 8 | exports.setFileArtifact(template, 'template', data); |
328 | } | |
329 | 8 | callback(err, data); |
330 | }); | |
331 | } | |
332 | }); | |
333 | } else { | |
334 | 21 | compile(template, callback); |
335 | } | |
336 | }; | |
337 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | FileMap = require('./util/file-map'), | |
4 | fu = require('./fileUtil'), | |
5 | ChildPool = require('./child-pool'); | |
6 | ||
7 | 1 | var uglify = new ChildPool(__dirname + '/uglify-worker'); |
8 | ||
9 | 1 | exports.combine = function(context, files, output, minimize, noSeparator, callback) { |
10 | ||
11 | 107 | function outputIfCompleted() { |
12 | 369 | if (completed >= files.length) { |
13 | 106 | var lastEl, |
14 | map = new FileMap(output), | |
15 | warnings = [], | |
16 | ||
17 | tasks = []; | |
18 | ||
19 | 106 | _.each(content, function(el) { |
20 | 369 | var content = el.content.toString(); |
21 | ||
22 | 369 | if (!noSeparator && (!lastEl || !lastEl.noSeparator) && map.content()) { |
23 | 114 | map.add(undefined, '\n;;\n'); |
24 | } | |
25 | ||
26 | 369 | map.add(el.generated ? undefined : el.name, content, el); |
27 | ||
28 | 369 | lastEl = el; |
29 | }, ''); | |
30 | ||
31 | 106 | var inputs = []; |
32 | 106 | content.forEach(function(el) { |
33 | 369 | if (el.inputs) { |
34 | 93 | inputs.push.apply(inputs, el.inputs); |
35 | 276 | } else if (el.name) { |
36 | 178 | inputs.push(el.name); |
37 | } | |
38 | }); | |
39 | 106 | inputs = _.unique(inputs); |
40 | ||
41 | // "Serialize" the data in the map | |
42 | 106 | tasks.push(function(callback) { |
43 | 106 | callback(undefined, map.content()); |
44 | }); | |
45 | ||
46 | // Minimize the content if flagged | |
47 | 106 | if (minimize) { |
48 | 0 | var uglifyConfig = context.config.attributes.uglify || {}; |
49 | ||
50 | 0 | tasks.push(function(data, callback) { |
51 | 0 | uglify.send({ |
52 | output: output, | |
53 | data: data, | |
54 | compressorOptions: uglifyConfig.compressor, | |
55 | manglerOptions: uglifyConfig.mangler, | |
56 | outputOptions: uglifyConfig.output, | |
57 | sourceMap: context.options.sourceMap ? map.sourceMap() : undefined | |
58 | }, | |
59 | function(err, data) { | |
60 | 0 | if (err) { |
61 | 0 | return callback(err); |
62 | } | |
63 | ||
64 | 0 | _.each(data.warnings, function(msg) { |
65 | 0 | var match = /(.*?)\s*\[.*:(\d+),(\d+)/.exec(msg); |
66 | 0 | if (match) { |
67 | 0 | var msg = match[1], |
68 | line = parseInt(match[2], 10), | |
69 | column = match[3], | |
70 | context = map.context(line, column); | |
71 | ||
72 | 0 | if (context && (!context.fileContext || !context.fileContext.ignoreWarnings)) { |
73 | 0 | context.msg = msg; |
74 | 0 | warnings.push(context); |
75 | } | |
76 | } else { | |
77 | 0 | warnings.push({msg: msg}); |
78 | } | |
79 | }); | |
80 | ||
81 | 0 | if (data.sourceMap) { |
82 | // Remap the sourcemap output for the point that it is actually used for output | |
83 | // We need to restore the source map here as uglify will remove the original | |
84 | // Declaration | |
85 | 0 | map.sourceMap = function() { return data.sourceMap; }; |
86 | } | |
87 | ||
88 | 0 | callback(err, data.data); |
89 | }); | |
90 | }); | |
91 | } | |
92 | ||
93 | // Output the source map if requested | |
94 | 106 | var sourceMap = context.options.sourceMap; |
95 | 106 | if (sourceMap) { |
96 | 0 | var inlineSourceMap = sourceMap === true; |
97 | ||
98 | 0 | tasks.push(function(data, callback) { |
99 | 0 | map.writeSourceMap({ |
100 | mapDestination: !inlineSourceMap && (sourceMap + '/' + context.buildPath), | |
101 | outputSource: inlineSourceMap, | |
102 | callback: function(err) { | |
103 | 0 | if (inlineSourceMap) { |
104 | 0 | data += '\n' + map.sourceMapToken(); |
105 | } | |
106 | 0 | callback(err, data); |
107 | } | |
108 | }); | |
109 | }); | |
110 | } | |
111 | ||
112 | // Output step | |
113 | 106 | tasks.push(function(data, callback) { |
114 | 106 | fu.writeFile(output, data, callback); |
115 | }); | |
116 | ||
117 | // Excute everything and return to the caller | |
118 | 106 | async.waterfall(tasks, function(err) { |
119 | 106 | if (err) { |
120 | 0 | callback(new Error('Combined output "' + output + '" failed\n\t' + err)); |
121 | 0 | return; |
122 | } | |
123 | ||
124 | 106 | callback(undefined, { |
125 | fileName: output, | |
126 | inputs: inputs, | |
127 | warnings: warnings | |
128 | }); | |
129 | }); | |
130 | } | |
131 | } | |
132 | 107 | var completed = 0, |
133 | content = []; | |
134 | ||
135 | 107 | files.forEach(function(resource) { |
136 | 370 | var fileInfo = context.loadResource(resource, function(err) { |
137 | 370 | if (err) { |
138 | 1 | callback(err); |
139 | 1 | return; |
140 | } | |
141 | ||
142 | 369 | completed++; |
143 | 369 | outputIfCompleted(); |
144 | }); | |
145 | 370 | content.push(fileInfo); |
146 | }); | |
147 | }; | |
148 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | config = require('./config'), | |
4 | fs = require('fs'), | |
5 | fu = require('./fileUtil'), | |
6 | path = require('path'); | |
7 | ||
8 | 1 | function Libraries(options) { |
9 | 118 | this.options = options; |
10 | 118 | this.mixins = []; |
11 | 118 | this.configs = []; |
12 | } | |
13 | ||
14 | 1 | Libraries.prototype.initialize = function(context, callback) { |
15 | 122 | this.mixins = []; |
16 | 122 | this.originalConfig = _.clone(context.config.attributes); |
17 | ||
18 | 122 | var commandLineLibraries = this.options.libraries || [], |
19 | configLibraries = context.config.attributes.libraries || context.config.attributes.mixins || [], | |
20 | ||
21 | allLibraries = commandLineLibraries.concat(configLibraries); | |
22 | ||
23 | 122 | delete context.config.attributes.mixins; |
24 | 122 | async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback); |
25 | }; | |
26 | 1 | Libraries.prototype.load = function(context, libraryConfig, callback) { |
27 | // Allow mixins to be passed directly | |
28 | 67 | var root = libraryConfig.root, |
29 | configPath, | |
30 | self = this; | |
31 | ||
32 | // Or as a file reference | |
33 | 67 | if (!_.isObject(libraryConfig)) { |
34 | 6 | root = root || libraryConfig; |
35 | ||
36 | // If we have a dir then pull lumbar.json from that | |
37 | 6 | try { |
38 | 6 | var stat = fs.statSync(fu.resolvePath(libraryConfig)); |
39 | 6 | if (stat.isDirectory()) { |
40 | 3 | libraryConfig = libraryConfig + '/lumbar.json'; |
41 | 3 | } else if (root === libraryConfig) { |
42 | // If we are a file the root should be the file's directory unless explicitly passed | |
43 | 3 | root = path.dirname(root); |
44 | } | |
45 | } catch (err) { | |
46 | 0 | return callback(err); |
47 | } | |
48 | ||
49 | 6 | configPath = fu.resolvePath(libraryConfig); |
50 | 6 | libraryConfig = config.readConfig(configPath); |
51 | } | |
52 | ||
53 | // To make things easy force root to be a dir | |
54 | 67 | if (root && !/\/$/.test(root)) { |
55 | 24 | root = root + '/'; |
56 | } | |
57 | ||
58 | 67 | if (!libraryConfig.name) { |
59 | 4 | return callback(new Error('Mixin with root "' + root + '" is missing a name.')); |
60 | } | |
61 | ||
62 | 63 | var mixins = libraryConfig.mixins, |
63 | toRegister = {}; | |
64 | 63 | delete libraryConfig.mixins; |
65 | ||
66 | 63 | function mapMixin(mixin, name) { |
67 | // Only register once, giving priority to an explicitly defined mixin | |
68 | 43 | if (!toRegister[name]) { |
69 | 42 | toRegister[name] = { |
70 | serialize: function() { | |
71 | 0 | return {name: this.name, library: this.parent.name}; |
72 | }, | |
73 | attributes: mixin, | |
74 | parent: libraryConfig, | |
75 | root: root | |
76 | }; | |
77 | } | |
78 | } | |
79 | ||
80 | // Read each of the mixins that are defined in the config | |
81 | 63 | _.each(mixins, mapMixin, this); |
82 | ||
83 | // Make mixin modules accessible as normal mixins as well | |
84 | 63 | _.each(libraryConfig.modules, mapMixin, this); |
85 | ||
86 | // After we've pulled everything in register | |
87 | 63 | _.each(toRegister, function(mixin, name) { |
88 | 42 | this.mixins[name] = this.mixins[name] || []; |
89 | 42 | var list = this.mixins[name]; |
90 | 42 | list.push(mixin); |
91 | }, this); | |
92 | ||
93 | // Run all of the plugins that are concerned with this. | |
94 | 63 | libraryConfig.root = root; |
95 | 63 | libraryConfig.path = configPath; |
96 | 63 | context.loadedLibrary = libraryConfig; |
97 | 63 | context.plugins.loadMixin(context, function(err) { |
98 | 63 | delete libraryConfig.root; |
99 | ||
100 | // And then splat everything else into our config | |
101 | 63 | _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path')); |
102 | ||
103 | 63 | libraryConfig.serialize = function() { |
104 | 0 | return { library: this.name }; |
105 | }; | |
106 | ||
107 | 63 | libraryConfig.root = root; |
108 | 63 | self.configs.push(libraryConfig); |
109 | ||
110 | 63 | callback(err); |
111 | }); | |
112 | }; | |
113 | ||
114 | 1 | Libraries.prototype.findDecl = function(mixins, mixinName) { |
115 | 9 | if (!mixinName) { |
116 | 0 | mixinName = {name: mixinName}; |
117 | } | |
118 | ||
119 | 9 | return _.find(mixins, function(mixinDecl) { |
120 | 11 | return (mixinDecl.name || mixinDecl) === mixinName.name |
121 | && (!mixinDecl.library || mixinDecl.library === mixinName.library); | |
122 | }); | |
123 | }; | |
124 | ||
125 | 1 | Libraries.prototype.moduleMixins = function(module) { |
126 | // Perform any nested mixin lookup | |
127 | 282 | var mixins = _.clone(module.mixins || []); |
128 | 282 | for (var i = 0, len = mixins.length; i < len; i++) { |
129 | 62 | var firstInclude = mixins[i], |
130 | mixin = this.getMixin(firstInclude), | |
131 | added = [i, 0]; | |
132 | ||
133 | 60 | if (!mixin) { |
134 | 0 | throw new Error('Unable to find mixin "' + firstInclude + '"'); |
135 | } | |
136 | ||
137 | // Check if we need to include any modules that this defined | |
138 | 60 | _.each(mixin.attributes.mixins, function(mixinInclude) { |
139 | 4 | if (!this.findDecl(mixins, mixinInclude)) { |
140 | 4 | added.push(mixinInclude); |
141 | } | |
142 | }, this); | |
143 | ||
144 | // If we've found any new mixins insert them at the current spot and iterate | |
145 | // over those items | |
146 | 60 | if (added.length > 2) { |
147 | 4 | mixins.splice.apply(mixins, added); |
148 | 4 | i--; |
149 | } | |
150 | } | |
151 | ||
152 | // Extend the module with each of the mixins content, giving priority to the module | |
153 | 280 | return _.map(mixins.reverse(), function(mixin) { |
154 | 60 | var mixinConfig = mixin.name && mixin, |
155 | name = mixin; | |
156 | 60 | if (mixinConfig) { |
157 | 20 | mixinConfig = _.clone(mixinConfig); |
158 | 20 | delete mixinConfig.library; |
159 | 20 | delete mixinConfig.container; |
160 | } | |
161 | 60 | mixin = _.extend( |
162 | {}, | |
163 | this.getMixin(name), | |
164 | mixinConfig); | |
165 | 60 | if (!mixin.attributes) { |
166 | 0 | throw new Error('Mixin "' + name.name || name + '" is not defined.'); |
167 | } | |
168 | ||
169 | // Save a distinct instance of the config for resource extension | |
170 | 60 | if (mixinConfig) { |
171 | 20 | mixinConfig = _.clone(mixinConfig); |
172 | 20 | delete mixinConfig.overrides; |
173 | 20 | delete mixinConfig.name; |
174 | } | |
175 | ||
176 | 60 | return { |
177 | library: mixin, | |
178 | mixinConfig: mixinConfig | |
179 | }; | |
180 | }, this); | |
181 | }; | |
182 | ||
183 | 1 | Libraries.prototype.mapFiles = function(value, library, config) { |
184 | 157 | var files = _.map(value, function(resource) { |
185 | 240 | return this.mapFile(resource, library, config); |
186 | }, this); | |
187 | 395 | files = _.filter(files, function(file) { return file; }); |
188 | ||
189 | 156 | return files; |
190 | }; | |
191 | 1 | Libraries.prototype.mapFile = function(resource, library, config) { |
192 | // If explicitly declared the resource library takes precedence | |
193 | 240 | if (_.isString(resource.library || resource.mixin)) { |
194 | 3 | library = this.getConfig(resource.library || resource.mixin); |
195 | 3 | if (!library) { |
196 | 1 | throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found'); |
197 | } | |
198 | 2 | delete resource.mixin; |
199 | } | |
200 | ||
201 | // If no mixin was defined on either side then return the identity | |
202 | 239 | if (!library) { |
203 | 172 | return resource; |
204 | } | |
205 | ||
206 | 67 | if (_.isString(resource)) { |
207 | 56 | resource = {src: resource}; |
208 | } else { | |
209 | 11 | resource = _.clone(resource); |
210 | } | |
211 | ||
212 | 67 | var src = resource.src || resource.dir; |
213 | ||
214 | // Include any config information such as env or platform that may have been | |
215 | // specified on the library settings | |
216 | 67 | _.extend(resource, config); |
217 | ||
218 | 67 | if (src) { |
219 | 65 | var librarySrc = (library.root || '') + src; |
220 | ||
221 | 65 | var override = library.overrides && library.overrides[src]; |
222 | 65 | if (override) { |
223 | 6 | resource.originalSrc = librarySrc; |
224 | 6 | librarySrc = _.isString(override) ? override : src; |
225 | 59 | } else if (override === false) { |
226 | 1 | return; |
227 | } | |
228 | ||
229 | 64 | if (resource.src) { |
230 | 63 | resource.src = librarySrc; |
231 | 1 | } else if (resource.dir) { |
232 | 1 | resource.dir = librarySrc; |
233 | } | |
234 | } | |
235 | ||
236 | 66 | resource.library = library; |
237 | 66 | return resource; |
238 | }; | |
239 | ||
240 | 1 | Libraries.prototype.getMixin = function(name) { |
241 | 122 | var mixins = (this.mixins && this.mixins[name.name || name]) || [], |
242 | library = name.library || name.container; | |
243 | 122 | if (mixins.length > 1 && !library) { |
244 | 1 | throw new Error( |
245 | 'Duplicate mixins found for "' + (name.name || name) + '"' | |
246 | + _.map(mixins, function(mixin) { | |
247 | 2 | return ' parent: "' + mixin.parent.name + '"'; |
248 | }).join('')); | |
249 | } | |
250 | ||
251 | 121 | if (library) { |
252 | 9 | if (name.name === undefined) { |
253 | 0 | var found = _.find(this.configs, function(config) { |
254 | 0 | return config.name === library; |
255 | }); | |
256 | 0 | if (!found) { |
257 | 0 | throw new Error('Unable to find library "' + library + '"'); |
258 | } | |
259 | 0 | return found; |
260 | } | |
261 | ||
262 | 9 | var found = _.find(mixins, function(mixin) { |
263 | 17 | return mixin.parent.name === library; |
264 | }); | |
265 | 9 | if (found) { |
266 | 8 | return found; |
267 | } else { | |
268 | 1 | throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"'); |
269 | } | |
270 | 112 | } else if (mixins.length === 1) { |
271 | 112 | return mixins[0]; |
272 | } | |
273 | }; | |
274 | 1 | Libraries.prototype.getConfig = function(name) { |
275 | 9 | return _.find(this.configs, function(config) { return config.name === name; }); |
276 | }; | |
277 | ||
278 | 1 | Libraries.prototype.resolvePath = function(name, mixin) { |
279 | 43 | if (!mixin) { |
280 | 36 | return name; |
281 | } | |
282 | ||
283 | 7 | var override = mixin.overrides && mixin.overrides[name]; |
284 | 7 | if (override) { |
285 | 2 | return _.isString(override) ? override : name; |
286 | } | |
287 | ||
288 | 5 | return mixin.root + name; |
289 | }; | |
290 | ||
291 | 1 | Libraries.prototype.mergeHash = function(hashName, input, mixin, output) { |
292 | 60 | if (mixin[hashName]) { |
293 | // Close the value to make sure that we are not overriding anything | |
294 | 10 | if (!output[hashName] || output[hashName] === input[hashName]) { |
295 | 8 | output[hashName] = _.clone(input[hashName] || {}); |
296 | } | |
297 | 10 | _.each(mixin[hashName], function(value, key) { |
298 | 16 | if (!input[hashName] || !(key in input[hashName])) { |
299 | 12 | output[hashName][key] = value; |
300 | } | |
301 | }); | |
302 | 10 | return true; |
303 | } | |
304 | }; | |
305 | 1 | Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) { |
306 | 32 | if (mixinData[fieldName]) { |
307 | 8 | mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]]; |
308 | ||
309 | 8 | var configData = input[fieldName] || []; |
310 | 8 | if (!output[fieldName] || configData === output[fieldName]) { |
311 | 6 | output[fieldName] = _.clone(configData); |
312 | } | |
313 | 8 | if (!_.isArray(configData)) { |
314 | 2 | configData = [configData]; |
315 | } | |
316 | 8 | if (!_.isArray(output[fieldName])) { |
317 | 1 | output[fieldName] = [output[fieldName]]; |
318 | } | |
319 | ||
320 | // Insert point is at the start of the upstream list, which we are | |
321 | // assuming occurs at length postions from the end. | |
322 | 8 | _.each(mixinData, function(value) { |
323 | //Make the include relative to the mixin | |
324 | 11 | value = (library.root || '') + value; |
325 | ||
326 | 11 | output[fieldName].splice( |
327 | output[fieldName].length - configData.length, | |
328 | 0, | |
329 | {src: value, library: library}); | |
330 | }); | |
331 | ||
332 | 8 | return true; |
333 | } | |
334 | }; | |
335 | ||
336 | 1 | module.exports = Libraries; |
337 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | build = require('./build'), | |
4 | combine = require('./jsCombine'), | |
5 | configLoader = require('./config'), | |
6 | Context = require('./context'), | |
7 | EventEmitter = require('events').EventEmitter, | |
8 | fs = require('fs'), | |
9 | fu = require('./fileUtil'), | |
10 | Libraries = require('./libraries'), | |
11 | plugin = require('./plugin'), | |
12 | WatchManager = require('./watch-manager'); | |
13 | ||
14 | 1 | exports.build = build; |
15 | 1 | exports.fileUtil = fu; |
16 | 1 | exports.plugin = plugin.plugin; |
17 | 1 | exports.combine = combine.combine; |
18 | 1 | exports.config = configLoader; |
19 | ||
20 | /** | |
21 | * | |
22 | * @name init | |
23 | * @function This function initializes a Lumbar instance | |
24 | * @param {string} lumbarFile The lumbarFile is the main | |
25 | * file. Its responsible to define all the platforms, | |
26 | * packages, modules, and templates for Lumbar to use. | |
27 | * @param {Object} options supports the following options: | |
28 | * packageConfigFile (string): name of the package config file. | |
29 | * outdir (string): path to directory of where to output the files. | |
30 | * minimize (boolean): Should we minimize the files? | |
31 | * @return {Object.<Function>} | |
32 | */ | |
33 | 1 | exports.init = function(lumbarFile, options) { |
34 | 23 | var outdir = options.outdir, |
35 | plugins = plugin.create(options), | |
36 | libraries = new Libraries(options), | |
37 | config, | |
38 | configCache, | |
39 | context; | |
40 | ||
41 | 23 | function logError(err) { |
42 | 52 | if (err) { |
43 | 1 | event.emit('error', err); |
44 | } | |
45 | } | |
46 | ||
47 | 23 | function loadConfig(path, callback) { |
48 | 27 | try { |
49 | 27 | fu.resetCache(); |
50 | ||
51 | 27 | configCache = {}; |
52 | 27 | config = configLoader.load(path); |
53 | 27 | plugins.initialize(config); |
54 | ||
55 | 27 | config.outdir = options.outdir = outdir || config.attributes.output; |
56 | ||
57 | 27 | context = new Context(options, config, plugins, libraries, event); |
58 | 27 | context.options = options; |
59 | 27 | context.configCache = configCache; |
60 | ||
61 | 27 | libraries.initialize(context, function(err) { |
62 | 27 | if (err) { |
63 | 0 | return callback(err); |
64 | } | |
65 | ||
66 | 27 | plugins.loadConfig(context, function(err) { |
67 | 27 | if (err) { |
68 | 0 | return callback(err); |
69 | } | |
70 | ||
71 | 27 | if (options.verbose) { |
72 | 0 | event.emit('log', 'Finalized config ' + JSON.stringify(context.config.serialize(), undefined, 2)); |
73 | } | |
74 | ||
75 | // Ensure that we have the proper build output | |
76 | 27 | if (!config.outdir) { |
77 | 0 | return callback(new Error('Output must be defined on the command line or config file.')); |
78 | } | |
79 | 27 | context.outdir = config.outdir; |
80 | ||
81 | 27 | fu.ensureDirs(config.outdir + '/.', function() { |
82 | 27 | var stat = fs.statSync(config.outdir); |
83 | 27 | if (!stat.isDirectory()) { |
84 | 0 | callback(new Error('Output must be a directory')); |
85 | } else { | |
86 | 27 | callback(); |
87 | } | |
88 | }); | |
89 | }); | |
90 | }); | |
91 | } catch (err) { | |
92 | 0 | callback(err); |
93 | } | |
94 | } | |
95 | ||
96 | 23 | function buildPackages(packageName, modules, callback) { |
97 | 31 | if (!callback) { |
98 | 11 | callback = modules; |
99 | 11 | modules = undefined; |
100 | } | |
101 | ||
102 | // Allow a string or a list as modules input | |
103 | 31 | if (!_.isArray(modules)) { |
104 | 31 | modules = [modules]; |
105 | 0 | } else if (!modules.length) { |
106 | // Special case empty array input to build all | |
107 | 0 | modules = [undefined]; |
108 | } | |
109 | ||
110 | 31 | var options = {}; |
111 | 31 | if (typeof packageName === 'object') { |
112 | 0 | options = packageName; |
113 | 0 | packageName = options.package; |
114 | } | |
115 | ||
116 | 31 | var packageNames = packageName ? [packageName] : config.packageList(), |
117 | contexts = []; | |
118 | ||
119 | 31 | packageNames.forEach(function(pkg) { |
120 | 34 | modules.forEach(function(module) { |
121 | 34 | options.package = pkg; |
122 | 34 | options.module = module || undefined; // '' -> undefined |
123 | ||
124 | 34 | var platforms = config.platformList(pkg); |
125 | 34 | platforms.forEach(function(platform) { |
126 | 45 | options.platform = platform; |
127 | ||
128 | 45 | var newContext = context.clone(options); |
129 | 45 | contexts.push(newContext); |
130 | }); | |
131 | }); | |
132 | }); | |
133 | ||
134 | 31 | async.forEach(contexts, buildPlatform, callback); |
135 | } | |
136 | 23 | function buildPlatform(context, callback) { |
137 | 77 | var modes = context.mode ? [context.mode] : plugins.modes(); |
138 | ||
139 | 77 | async.forEach(modes, function(mode, callback) { |
140 | 168 | buildMode(mode, context, callback); |
141 | }, | |
142 | callback); | |
143 | } | |
144 | 23 | function buildMode(mode, context, callback) { |
145 | 168 | var modules = context.module ? [context.module] : config.moduleList(context.package); |
146 | ||
147 | 168 | context = context.clone(); |
148 | 168 | context.mode = mode; |
149 | 168 | context.modeCache = {}; |
150 | ||
151 | 168 | if (context.fileConfig) { |
152 | 20 | processConfig(context.fileConfig, callback); |
153 | } else { | |
154 | 148 | plugins.outputConfigs(context, function(err, configs) { |
155 | 148 | if (err) { |
156 | 0 | return callback(err); |
157 | } | |
158 | 148 | async.forEach(configs, processConfig, callback); |
159 | }); | |
160 | } | |
161 | ||
162 | 168 | function processConfig(fileConfig, callback) { |
163 | 178 | var fileContext = context.clone(true); |
164 | 178 | fileContext.fileConfig = fileConfig; |
165 | 178 | fileContext.resources = []; |
166 | 178 | fileContext.combineResources = {}; |
167 | 178 | fileContext.fileCache = fileContext.combined ? {} : undefined; |
168 | ||
169 | 178 | async.forEach(modules, function(module, callback) { |
170 | 262 | var moduleContext = fileContext.clone(); |
171 | 262 | moduleContext.module = module; |
172 | ||
173 | 262 | buildModule(moduleContext, callback); |
174 | }, | |
175 | function(err) { | |
176 | 178 | if (err) { |
177 | 1 | return callback(err); |
178 | } | |
179 | ||
180 | 177 | plugins.modeComplete(fileContext, callback); |
181 | }); | |
182 | } | |
183 | } | |
184 | 23 | function buildModule(context, callback) { |
185 | 262 | var module = config.module(context.module); |
186 | 262 | if (!module) { |
187 | 0 | return callback(new Error('Unable to find module "' + context.module + '"')); |
188 | } | |
189 | ||
190 | 262 | context.module = module; |
191 | 262 | context.fileCache = context.combined ? context.fileCache : {}; |
192 | 262 | context.moduleCache = {}; |
193 | ||
194 | 262 | var resource = context.resource; |
195 | 262 | if (resource) { |
196 | 6 | resource = resource.originalResource || resource; |
197 | 6 | processResources([resource]); |
198 | } else { | |
199 | // Load all resources associated with this module | |
200 | 256 | build.loadResources(context, function(err, resources) { |
201 | 256 | if (err) { |
202 | 0 | return callback(err); |
203 | } | |
204 | 256 | processResources(resources); |
205 | }); | |
206 | } | |
207 | ||
208 | 262 | function processResources(resources) { |
209 | 262 | build.processResources(resources, context, function(err, resources) { |
210 | 262 | if (err) { |
211 | 0 | return callback(err); |
212 | } | |
213 | ||
214 | 262 | context.moduleResources = resources; |
215 | 262 | plugins.module(context, callback); |
216 | }); | |
217 | } | |
218 | } | |
219 | ||
220 | 23 | var event = new EventEmitter(), |
221 | watch; | |
222 | ||
223 | 23 | function watchOutputHandler(status) { |
224 | 96 | if (!watch) { |
225 | // We've been cleaned up but residuals may still exist, do nothing on this exec | |
226 | 12 | return; |
227 | } | |
228 | ||
229 | 84 | if (status.fileConfig.isPrimary) { |
230 | 31 | delete status.fileConfig; |
231 | 53 | } else if (status.fileConfig.isPrimary === false) { |
232 | // This config is directly linked to another meaning we don't want to watch on it as | |
233 | // it will be rebuilt. | |
234 | 12 | return; |
235 | } | |
236 | ||
237 | 72 | var originalConfig = config; |
238 | 72 | watch.moduleOutput(status, function() { |
239 | 32 | if (config !== originalConfig) { |
240 | // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch) | |
241 | 0 | return; |
242 | } | |
243 | ||
244 | 32 | buildPlatform(context.clone(status), logError); |
245 | }); | |
246 | } | |
247 | ||
248 | 23 | return _.extend(event, { |
249 | config: function() { | |
250 | 0 | return config; |
251 | }, | |
252 | ||
253 | use: function(plugin) { | |
254 | 0 | plugins.use(plugin); |
255 | }, | |
256 | /** | |
257 | * | |
258 | * @name build | |
259 | * @function This function builds out the package(s). | |
260 | * @param {string} packageName the name of the package listed under | |
261 | * 'packages' from the lumbarFile passed in during the call to init(). | |
262 | * @param {Function} callback the node process Function | |
263 | */ | |
264 | build: function(packageName, modules, callback) { | |
265 | 11 | loadConfig(lumbarFile, function(err) { |
266 | 11 | if (err) { |
267 | 0 | if (!callback) { |
268 | 0 | throw err; |
269 | } | |
270 | 0 | return callback(err); |
271 | } | |
272 | ||
273 | 11 | buildPackages(packageName, modules, callback); |
274 | }); | |
275 | }, | |
276 | watch: function(packageName, modules, callback) { | |
277 | 16 | if (!fs.watch) { |
278 | 0 | throw new Error('Watch requires fs.watch, introduced in Node v0.6.0'); |
279 | } | |
280 | ||
281 | 16 | watch = new WatchManager(); |
282 | 16 | watch.on('watch-change', function(info) { |
283 | 40 | event.emit('watch-change', info); |
284 | }); | |
285 | ||
286 | 16 | var self = this; |
287 | 16 | loadConfig(lumbarFile, function(err) { |
288 | 16 | if (err) { |
289 | 0 | logError(err); |
290 | } | |
291 | ||
292 | 16 | if (!callback) { |
293 | 16 | callback = modules; |
294 | 16 | modules = undefined; |
295 | } | |
296 | ||
297 | // Watch for changes in the config file | |
298 | 20 | var mixinPaths = _.filter(_.pluck(libraries.configs, 'path'), function(path) { return path; }); |
299 | 16 | watch.configFile(lumbarFile, mixinPaths, function() { |
300 | 4 | config = undefined; |
301 | 4 | self.watch(packageName, callback); |
302 | }); | |
303 | ||
304 | // If we have errored do not exec everything as it could be in an indeterminate state | |
305 | 16 | if (err) { |
306 | 0 | return; |
307 | } | |
308 | ||
309 | // Watch the individual components | |
310 | 16 | event.removeListener('output', watchOutputHandler); |
311 | 16 | event.on('output', watchOutputHandler); |
312 | ||
313 | // Actual build everything | |
314 | 16 | var packages = packageName ? [packageName] : config.packageList(); |
315 | 16 | packages.forEach(function(name) { |
316 | 20 | buildPackages(name, modules, logError); |
317 | }); | |
318 | }); | |
319 | }, | |
320 | unwatch: function() { | |
321 | 12 | event.removeListener('output', watchOutputHandler); |
322 | 12 | if (watch) { |
323 | 12 | watch.removeAllListeners(); |
324 | 12 | watch.reset(); |
325 | 12 | watch = undefined; |
326 | } | |
327 | } | |
328 | }); | |
329 | }; | |
330 |
Line | Hits | Source |
---|---|---|
1 | // Copyright Joyent, Inc. and other Node contributors. | |
2 | // | |
3 | // Permission is hereby granted, free of charge, to any person obtaining a | |
4 | // copy of this software and associated documentation files (the | |
5 | // "Software"), to deal in the Software without restriction, including | |
6 | // without limitation the rights to use, copy, modify, merge, publish, | |
7 | // distribute, sublicense, and/or sell copies of the Software, and to permit | |
8 | // persons to whom the Software is furnished to do so, subject to the | |
9 | // following conditions: | |
10 | // | |
11 | // The above copyright notice and this permission notice shall be included | |
12 | // in all copies or substantial portions of the Software. | |
13 | // | |
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | ||
22 | 1 | var path = require('path'); |
23 | ||
24 | // path.relative(from, to) | |
25 | // posix version | |
26 | 1 | exports.relative = function(from, to) { |
27 | 28 | from = path.resolve(from).substr(1); |
28 | 28 | to = path.resolve(to).substr(1); |
29 | ||
30 | 28 | function trim(arr) { |
31 | 56 | var start = 0; |
32 | 56 | for (; start < arr.length; start++) { |
33 | 56 | if (arr[start] !== '') { |
34 | 56 | break; |
35 | } | |
36 | } | |
37 | ||
38 | 56 | var end = arr.length - 1; |
39 | 56 | for (; end >= 0; end--) { |
40 | 56 | if (arr[end] !== '') { |
41 | 56 | break; |
42 | } | |
43 | } | |
44 | ||
45 | 56 | if (start > end) { |
46 | 0 | return []; |
47 | } | |
48 | 56 | return arr.slice(start, end - start + 1); |
49 | } | |
50 | ||
51 | 28 | var fromParts = trim(from.split('/')); |
52 | 28 | var toParts = trim(to.split('/')); |
53 | ||
54 | 28 | var length = Math.min(fromParts.length, toParts.length); |
55 | 28 | var samePartsLength = length; |
56 | 28 | for (var i = 0; i < length; i++) { |
57 | 156 | if (fromParts[i] !== toParts[i]) { |
58 | 0 | samePartsLength = i; |
59 | 0 | break; |
60 | } | |
61 | } | |
62 | ||
63 | 28 | var outputParts = []; |
64 | 28 | for (var i = samePartsLength; i < fromParts.length; i++) { |
65 | 2 | outputParts.push('..'); |
66 | } | |
67 | ||
68 | 28 | outputParts = outputParts.concat(toParts.slice(samePartsLength)); |
69 | ||
70 | 28 | return outputParts.join('/'); |
71 | }; | |
72 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | path = require('path'); | |
3 | 1 | const corePlugins = [ |
4 | 'mixin', | |
5 | 'styles-output', 'scripts-output', 'static-output', | |
6 | 'scope', 'router', 'template', 'inline-styles', | |
7 | 'coffee-script', 'stylus', 'handlebars', | |
8 | 'module-map', 'package-config', 'stylus-config', | |
9 | 'update-externals', | |
10 | 'inline-styles-resources', 'styles', 'scripts', 'static' | |
11 | ]; | |
12 | 1 | var fileUtils = require("/Users/kpdecker/dev/walmart/lumbar/lib/./fileUtil"); |
13 | ||
14 | 1 | var globalPlugins = {}; |
15 | ||
16 | 1 | exports.plugin = function(name, plugin) { |
17 | 20 | globalPlugins[name] = plugin; |
18 | 20 | plugin.id = name; |
19 | }; | |
20 | ||
21 | 1 | exports.plugin('module-map', require('./plugins/module-map')); |
22 | 1 | exports.plugin('package-config', require('./plugins/package-config')); |
23 | 1 | exports.plugin('router', require('./plugins/router')); |
24 | 1 | exports.plugin('scope', require('./plugins/scope')); |
25 | 1 | exports.plugin('stylus', require('./plugins/stylus')); |
26 | 1 | exports.plugin('stylus-config', require('./plugins/stylus-config')); |
27 | 1 | exports.plugin('coffee-script', require('./plugins/coffee-script')); |
28 | 1 | exports.plugin('handlebars', require('./plugins/handlebars')); |
29 | 1 | exports.plugin('inline-styles', require('./plugins/inline-styles')); |
30 | 1 | exports.plugin('inline-styles-resources', require('./plugins/inline-styles-resources')); |
31 | 1 | exports.plugin('mixin', require('./plugins/mixin')); |
32 | 1 | exports.plugin('update-externals', require('./plugins/update-externals')); |
33 | 1 | exports.plugin('template', require('./plugins/template')); |
34 | 1 | exports.plugin('styles', require('./plugins/styles.js')); |
35 | 1 | exports.plugin('server-scripts', require('./plugins/server-scripts.js')); |
36 | 1 | exports.plugin('scripts', require('./plugins/scripts.js')); |
37 | 1 | exports.plugin('static', require('./plugins/static.js')); |
38 | 1 | exports.plugin('styles-output', require('./plugins/styles-output.js')); |
39 | 1 | exports.plugin('scripts-output', require('./plugins/scripts-output.js')); |
40 | 1 | exports.plugin('static-output', require('./plugins/static-output.js')); |
41 | ||
42 | 1 | exports.create = function(options) { |
43 | 118 | var plugins; |
44 | 118 | var modes; // all registered modes |
45 | 118 | var pluginModes; // map of modes and plugins scoped to the mode |
46 | 118 | var modeAll; // plugins that are scoped to all modes |
47 | ||
48 | 118 | function runPlugins(context, methodName, complete, failOver, noMode) { |
49 | 2894 | var len = 0, |
50 | pluginMode = pluginModes[context.mode] || []; | |
51 | ||
52 | 2894 | return (function next(complete) { |
53 | /*jshint boss:true */ | |
54 | 8135 | var plugin; |
55 | 8135 | while (plugin = plugins[len++]) { |
56 | // if plugin shouldn't work with current mode, go to next | |
57 | 53520 | if (!noMode |
58 | && (!context.mode || pluginMode.indexOf(plugin) < 0) | |
59 | && modeAll.indexOf(plugin) < 0) { | |
60 | 23689 | continue; |
61 | } | |
62 | ||
63 | 29831 | var method = plugin[methodName]; |
64 | 29831 | if (method) { |
65 | 5902 | if (complete) { |
66 | 5771 | process.nextTick(function() { |
67 | 5771 | method.call(plugin, context, next, complete); |
68 | }); | |
69 | 5771 | return; |
70 | } else { | |
71 | 131 | return method.call(plugin, context, next, complete); |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | // We're done, send data back | |
77 | 2233 | if (complete) { |
78 | // async | |
79 | // Clear out our stack under async mode to try to keep the stack somewhat sane. | |
80 | 2053 | process.nextTick(function() { |
81 | 2053 | complete(undefined, failOver && failOver()); |
82 | }); | |
83 | } else { | |
84 | // sync | |
85 | 180 | return failOver && failOver(); |
86 | } | |
87 | })(complete); | |
88 | } | |
89 | ||
90 | 118 | function registerPlugin(plugin) { |
91 | 2272 | var _plugin = globalPlugins[plugin] || plugin; |
92 | ||
93 | 2272 | var mode = _plugin.mode; |
94 | 2272 | if (mode) { |
95 | 2153 | if (_.isString(mode)) { |
96 | 1670 | mode = [mode]; |
97 | } | |
98 | 2153 | _.each(mode, function(_mode) { |
99 | 2636 | if (mode === 'all') { |
100 | // allow plugins to contribute new modes and participate in all modes | |
101 | 0 | modeAll.push(_plugin); |
102 | } else { | |
103 | 2636 | if (modes.indexOf(_mode) < 0) { |
104 | 364 | modes.push(_mode); |
105 | 364 | pluginModes[_mode] = []; |
106 | } | |
107 | 2636 | pluginModes[_mode].push(_plugin); |
108 | } | |
109 | }); | |
110 | } else { | |
111 | 119 | modeAll.push(_plugin); |
112 | } | |
113 | 2272 | plugins.push(_plugin); |
114 | 2272 | plugins.sort(function(a, b) { |
115 | 30078 | return (a.priority || 50) - (b.priority || 50); |
116 | }); | |
117 | } | |
118 | ||
119 | 118 | return { |
120 | get: function(name) { | |
121 | // Find the plugin with this id, if one exists | |
122 | 67 | var plugin = plugins.reduce(function(plugin, left) { |
123 | 1206 | return plugin.id === name ? plugin : left; |
124 | }); | |
125 | ||
126 | // If the plugin was not found do not return the last item in the reduce | |
127 | 67 | if (plugin.id === name) { |
128 | 65 | return plugin; |
129 | } | |
130 | }, | |
131 | use: function(plugin) { | |
132 | 11 | if (plugin.path || (_.isString(plugin) && !globalPlugins[plugin])) { |
133 | 1 | var pluginPath = plugin.path || plugin; |
134 | 1 | var options = plugin.options; |
135 | 1 | try { |
136 | 1 | plugin = require(pluginPath); |
137 | } catch (e) { | |
138 | 1 | plugin = require(path.resolve(process.cwd(), fileUtils.lookupPath()) + '/node_modules/' + pluginPath); |
139 | } | |
140 | 1 | if ('function' === typeof plugin) { |
141 | 1 | plugin = plugin(options); |
142 | } | |
143 | } | |
144 | 11 | registerPlugin(plugin); |
145 | }, | |
146 | ||
147 | initialize: function(config) { | |
148 | // reset | |
149 | 122 | plugins = []; |
150 | 122 | modes = []; // all registered modes |
151 | 122 | pluginModes = {}; // map of modes and plugins scoped to the mode |
152 | 122 | modeAll = []; // plugins that are scoped to all modes |
153 | ||
154 | // load the core plugins | |
155 | 122 | if (!options.ignoreCorePlugins) { |
156 | 119 | corePlugins.forEach(registerPlugin); |
157 | } | |
158 | ||
159 | 122 | var self = this; |
160 | 122 | function plugin(plugins) { |
161 | 244 | if (plugins) { |
162 | 7 | plugins.forEach(self.use, self); |
163 | } | |
164 | } | |
165 | ||
166 | // load command line plugins | |
167 | 122 | plugin(options.plugins); |
168 | ||
169 | // load lumbar.json plugins | |
170 | 122 | plugin(config.attributes.plugins); |
171 | }, | |
172 | ||
173 | loadMixin: function(context, complete) { | |
174 | 63 | runPlugins(context, 'loadMixin', complete, undefined, true); |
175 | }, | |
176 | loadConfig: function(context, complete) { | |
177 | 121 | runPlugins(context, 'loadConfig', complete, undefined, true); |
178 | }, | |
179 | outputConfigs: function(context, complete) { | |
180 | 229 | runPlugins(context, 'outputConfigs', complete, function() { |
181 | // Default to a one to one mapping for a given {platform, package, module, mode} combo | |
182 | 227 | return [ {} ]; |
183 | }); | |
184 | }, | |
185 | modeComplete: function(context, complete) { | |
186 | 177 | runPlugins(context, 'modeComplete', complete); |
187 | }, | |
188 | fileName: function(context, complete) { | |
189 | 246 | runPlugins(context, 'fileName', complete); |
190 | }, | |
191 | ||
192 | fileFilter: function(context) { | |
193 | 311 | return runPlugins(context, 'fileFilter'); |
194 | }, | |
195 | moduleResources: function(context, complete) { | |
196 | 423 | runPlugins(context, 'moduleResources', complete, function() { |
197 | 233 | var module = context.module; |
198 | 233 | return (module[context.mode] || []).slice(); |
199 | }); | |
200 | }, | |
201 | resourceList: function(context, complete) { | |
202 | 976 | runPlugins(context, 'resourceList', complete, function() { return [context.resource]; }); |
203 | }, | |
204 | ||
205 | file: function(context, complete) { | |
206 | 139 | runPlugins(context, 'file', complete); |
207 | }, | |
208 | module: function(context, complete) { | |
209 | 265 | runPlugins(context, 'module', complete); |
210 | }, | |
211 | resource: function(context, complete) { | |
212 | 775 | runPlugins(context, 'resource', complete, function() { return context.resource; }); |
213 | }, | |
214 | modes: function() { | |
215 | 45 | return modes; |
216 | } | |
217 | }; | |
218 | }; | |
219 |
Line | Hits | Source |
---|---|---|
1 | 1 | var CoffeeScript = require('coffee-script'), |
2 | path = require('path'), | |
3 | fu = require('../fileUtil'), | |
4 | _ = require('underscore'); | |
5 | ||
6 | 1 | module.exports = { |
7 | mode: 'scripts', | |
8 | priority: 50, | |
9 | resource: function(context, next, complete) { | |
10 | 262 | var resource = context.resource; |
11 | 262 | if (/\.coffee$/.test(resource.src)) { |
12 | ||
13 | 2 | next(function(err, resource) { |
14 | 2 | function generator(context, callback) { |
15 | // Load the source data | |
16 | 2 | context.loadResource(resource, function(err, file) { |
17 | 2 | if (err) { |
18 | 1 | return callback(err); |
19 | } | |
20 | ||
21 | // Update the content | |
22 | 1 | callback(err, { |
23 | data: CoffeeScript.compile(file.content.toString()), | |
24 | inputs: file.inputs | |
25 | }); | |
26 | }); | |
27 | } | |
28 | ||
29 | // Include any attributes that may have been defined on the base entry | |
30 | 2 | if (!_.isString(resource)) { |
31 | 2 | _.extend(generator, resource); |
32 | } | |
33 | 2 | complete(undefined, generator); |
34 | }); | |
35 | } else { | |
36 | 260 | next(complete); |
37 | } | |
38 | } | |
39 | }; | |
40 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Template Plugin : Includes handlebars templates associated with a given file | |
3 | * when said file is imported. | |
4 | * | |
5 | * Config: | |
6 | * root: | |
7 | * templates: | |
8 | * template: Defines the template that is used to output the template in the module. See consts below. | |
9 | * precompile: | |
10 | * Flag/hash that enable precompilation. Truthy will enable precompilation. A hash with | |
11 | * the key name "template" will override the rendering template. (See the template value above.) | |
12 | * cache: Name of the javascript object that templates will be assigned to. | |
13 | * Defaults to `$AppModule.templates` if an app module exists, otherwise `templates` | |
14 | * | |
15 | * Mixins: | |
16 | * The template plugin will mixin any special values directly, giving priority to the local version. | |
17 | * | |
18 | */ | |
19 | 1 | var _ = require('underscore'), |
20 | handlebars = require('handlebars'), | |
21 | templateUtil = require('../templateUtil'); | |
22 | ||
23 | 1 | handlebars.registerHelper('without-extension', function(str) { |
24 | 1 | return str.replace(/\.[a-zA-Z0-9]+$/, ''); |
25 | }); | |
26 | ||
27 | ||
28 | 1 | const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n"; |
29 | ||
30 | 1 | function ensureTemplateTemplates(context, complete) { |
31 | 37 | if (!context.configCache.templateTemplate) { |
32 | 11 | var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE; |
33 | 11 | context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) { |
34 | 11 | if (err) { |
35 | 1 | complete(err); |
36 | } else { | |
37 | 10 | context.configCache.templateTemplate = compiled; |
38 | 10 | complete(); |
39 | } | |
40 | }); | |
41 | } else { | |
42 | 26 | complete(); |
43 | } | |
44 | } | |
45 | ||
46 | 1 | function loadTemplate(name, resource, context, callback) { |
47 | 37 | ensureTemplateTemplates(context, function(err) { |
48 | 37 | if (err) { |
49 | 1 | return callback(err); |
50 | } | |
51 | 36 | context.fileUtil.readFileArtifact(name, 'template', function(err, cache) { |
52 | 36 | if (err) { |
53 | 0 | callback(new Error('Failed to load template "' + name + '"\n\t' + err)); |
54 | 0 | return; |
55 | } | |
56 | ||
57 | 36 | var artifact = cache.artifact || {}, |
58 | data = artifact.data || cache.data.toString(), | |
59 | attr = context.config.attributes, | |
60 | templates = attr.templates || attr.views || {}, | |
61 | appModule = context.config.scopedAppModuleName(context.module), | |
62 | templateCache = (context.config.attributes.templates && context.config.attributes.templates.cache) | |
63 | || context.config.attributes.templateCache | |
64 | || ((appModule ? appModule + '.' : '') + 'templates'), | |
65 | template = context.configCache.templateTemplate; | |
66 | ||
67 | // We have the template data, now convert it into the proper format | |
68 | 36 | name = templateUtil.escapeJsString(name); |
69 | 36 | if (!cache.artifact) { |
70 | 15 | if (templates.precompile) { |
71 | 1 | var options = context.configCache.precompileTemplates; |
72 | 1 | if (!options) { |
73 | 1 | context.configCache.precompileTemplates = options = _.clone(templates.precompile); |
74 | 1 | if (options.knownHelpers) { |
75 | 0 | options.knownHelpers = options.knownHelpers.reduce( |
76 | function(value, helper) { | |
77 | 0 | value[helper] = true; |
78 | 0 | return value; |
79 | }, {}); | |
80 | } | |
81 | } | |
82 | 1 | try { |
83 | 1 | data = handlebars.precompile(data, options); |
84 | } catch (err) { | |
85 | 0 | return callback(err); |
86 | } | |
87 | } else { | |
88 | 14 | data = "'" + templateUtil.escapeJsString(data) + "'"; |
89 | } | |
90 | 15 | context.fileUtil.setFileArtifact(name, 'template', {data: data, template: template}); |
91 | } | |
92 | ||
93 | ||
94 | 36 | var mixinRoot = (resource.library && resource.library.root) || ''; |
95 | 36 | if (name.indexOf(mixinRoot) === 0) { |
96 | 36 | name = name.substring(mixinRoot.length); |
97 | } | |
98 | 36 | if (templates.root && name.indexOf(templates.root) === 0) { |
99 | 3 | name = name.substring(templates.root.length); |
100 | } | |
101 | ||
102 | 36 | callback( |
103 | undefined, | |
104 | template({ | |
105 | name: name, | |
106 | handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile', | |
107 | templateCache: templateCache, | |
108 | data: data | |
109 | }) | |
110 | ); | |
111 | }); | |
112 | }); | |
113 | } | |
114 | ||
115 | 1 | module.exports = { |
116 | mode: 'scripts', | |
117 | priority: 50, | |
118 | ||
119 | loadMixin: function(context, next, complete) { | |
120 | 63 | var mixinTemplates = context.loadedLibrary.templates; |
121 | 63 | if (mixinTemplates) { |
122 | 8 | var templates = context.libraries.originalConfig.templates || {}, |
123 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
124 | assigned = false; | |
125 | ||
126 | 8 | ['template', 'precompile', 'cache', 'root'].forEach(function(key) { |
127 | 32 | if (_.has(mixinTemplates, key) && !_.has(templates, key)) { |
128 | 6 | configTemplates[key] = mixinTemplates[key]; |
129 | 6 | assigned = true; |
130 | } | |
131 | }); | |
132 | ||
133 | 8 | if (assigned) { |
134 | 3 | context.config.attributes.templates = configTemplates; |
135 | } | |
136 | } | |
137 | 63 | next(complete); |
138 | }, | |
139 | ||
140 | resource: function(context, next, complete) { | |
141 | 218 | var resource = context.resource; |
142 | ||
143 | 218 | if (/\.handlebars$/.test(resource.src) || resource.template) { |
144 | 25 | var loadedTemplates = context.fileCache.loadedTemplates; |
145 | 25 | if (!loadedTemplates) { |
146 | 24 | loadedTemplates = context.fileCache.loadedTemplates = {}; |
147 | } | |
148 | ||
149 | 25 | var generator = function(buildContext, callback) { |
150 | 25 | var output = [], |
151 | inputs = []; | |
152 | 25 | context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) { |
153 | 25 | if (err) { |
154 | 0 | callback(err); |
155 | 0 | return; |
156 | } | |
157 | ||
158 | 25 | function ignore(file) { |
159 | 113 | return file.dir || loadedTemplates[file.src || file]; |
160 | } | |
161 | 25 | function checkComplete() { |
162 | 61 | if (inputs.length === files.length) { |
163 | // Sorting is effectively sorting on the file name due to the name comment in the template | |
164 | 24 | callback(undefined, { |
165 | inputs: inputs, | |
166 | data: output.sort().join(''), | |
167 | generated: true, | |
168 | noSeparator: true, | |
169 | ignoreWarnings: true | |
170 | }); | |
171 | 24 | return true; |
172 | } | |
173 | } | |
174 | ||
175 | 45 | inputs = _.map(files.filter(ignore), function(input) { return input.src || input; }); |
176 | 25 | if (checkComplete()) { |
177 | 1 | return; |
178 | } | |
179 | ||
180 | 24 | files.forEach(function(file) { |
181 | 56 | if (ignore(file)) { |
182 | 19 | return; |
183 | } | |
184 | ||
185 | 37 | var src = file.src || file; |
186 | 37 | loadedTemplates[src] = true; |
187 | 37 | loadTemplate(src, resource, context, function(err, data) { |
188 | 37 | if (err) { |
189 | 1 | return callback(err); |
190 | } | |
191 | ||
192 | 36 | output.push(data.data || data); |
193 | 36 | inputs.push(src); |
194 | 36 | checkComplete(); |
195 | }); | |
196 | }); | |
197 | }); | |
198 | }; | |
199 | 25 | generator.sourceFile = resource.src; |
200 | 25 | complete(undefined, generator); |
201 | } else { | |
202 | 193 | next(complete); |
203 | } | |
204 | } | |
205 | }; | |
206 |
Line | Hits | Source |
---|---|---|
1 | 1 | var inlineStyles = require('./inline-styles'); |
2 | ||
3 | 1 | module.exports = { |
4 | mode: ['scripts', 'styles'], | |
5 | priority: 80, | |
6 | ||
7 | moduleResources: function(context, next, complete) { | |
8 | 341 | if (inlineStyles.isInline(context) && context.mode === 'styles') { |
9 | // Prevent stylesheet output if in inline mode | |
10 | 3 | complete(undefined, []); |
11 | 338 | } else if (inlineStyles.isInline(context)) { |
12 | 6 | next(function(err, scripts) { |
13 | 6 | complete(undefined, scripts.concat(context.module.styles || [])); |
14 | }); | |
15 | } else { | |
16 | 332 | next(complete); |
17 | } | |
18 | } | |
19 | }; | |
20 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Inline-Styles Plugin : Include stylesheet in javascript modules | |
3 | * | |
4 | * Config: | |
5 | * root: | |
6 | * styles: | |
7 | * inline: Truthy to inline styles on build. | |
8 | * inlineLoader: Javascript method used to load sheets on the client. | |
9 | * | |
10 | * Mixins: | |
11 | * All fields may be mixed in. In the case of conflicts the local config wins. | |
12 | */ | |
13 | 1 | var _ = require('underscore'); |
14 | ||
15 | 1 | function isInline(context) { |
16 | 1456 | return (context.config.attributes.styles || {}).inline; |
17 | } | |
18 | ||
19 | 1 | module.exports = { |
20 | isInline: isInline, | |
21 | mode: ['scripts', 'styles'], | |
22 | priority: 10, | |
23 | ||
24 | loadMixin: function(context, next, complete) { | |
25 | 63 | var mixinStyles = context.loadedLibrary.styles; |
26 | 63 | if (mixinStyles) { |
27 | 16 | var styles = context.libraries.originalConfig.styles || {}, |
28 | configStyles = _.clone(context.config.attributes.styles || styles), | |
29 | assigned = false; | |
30 | ||
31 | 16 | ['inline', 'inlineLoader'].forEach(function(key) { |
32 | 32 | if ((key in mixinStyles) && !(key in styles)) { |
33 | 6 | configStyles[key] = mixinStyles[key]; |
34 | ||
35 | 6 | assigned = true; |
36 | } | |
37 | }); | |
38 | ||
39 | 16 | if (assigned) { |
40 | 5 | context.config.attributes.styles = configStyles; |
41 | } | |
42 | } | |
43 | 63 | next(complete); |
44 | }, | |
45 | ||
46 | outputConfigs: function(context, next, complete) { | |
47 | 183 | if (isInline(context) && context.mode === 'styles') { |
48 | // Prevent stylesheet output if in inline mode | |
49 | 2 | complete(undefined, []); |
50 | } else { | |
51 | 181 | next(complete); |
52 | } | |
53 | }, | |
54 | ||
55 | module: function(context, next, complete) { | |
56 | 186 | next(function(err) { |
57 | 186 | if (err) { |
58 | 0 | return complete(err); |
59 | } | |
60 | ||
61 | 186 | if (isInline(context)) { |
62 | 3 | context.moduleResources = context.moduleResources.map(function(resource) { |
63 | 9 | if (resource.style || /\.css$/.test(resource.src)) { |
64 | 3 | var generator = function(context, callback) { |
65 | 3 | context.loadResource(resource, function(err, data) { |
66 | 3 | if (err) { |
67 | 0 | return callback(err); |
68 | } | |
69 | ||
70 | 3 | var config = context.config, |
71 | loaderName = config.attributes.styles.inlineLoader || (config.scopedAppModuleName(context.module) + '.loader.loadInlineCSS'); | |
72 | 3 | callback(err, { |
73 | data: loaderName + '("' | |
74 | + data.content | |
75 | .replace(/\\/g, '\\') | |
76 | .replace(/\n/g, '\\n') | |
77 | .replace(/"/g, '\\"') | |
78 | + '");\n', | |
79 | inputs: data.inputs, | |
80 | generated: true, | |
81 | noSeparator: true | |
82 | }); | |
83 | }); | |
84 | }; | |
85 | 3 | generator.style = true; |
86 | 3 | generator.sourceFile = resource.sourceFile || resource.src; |
87 | 3 | return generator; |
88 | } else { | |
89 | 6 | return resource; |
90 | } | |
91 | }); | |
92 | } | |
93 | ||
94 | 186 | complete(); |
95 | }); | |
96 | } | |
97 | }; | |
98 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | lumbar = require('../lumbar'); | |
3 | ||
4 | 1 | function filterDuplicates(context) { |
5 | 192 | if (context.config.attributes.filterDuplicates === false) { |
6 | 2 | return context.moduleResources; |
7 | } | |
8 | ||
9 | 190 | var paths = {}; |
10 | 190 | return _.filter(context.moduleResources, function(resource) { |
11 | 377 | if (resource.src) { |
12 | 185 | var id = (resource.global ? 'global_' : '') + resource.src; |
13 | 185 | if (paths[id] && !resource.duplicate) { |
14 | 2 | return false; |
15 | } | |
16 | 183 | paths[id] = true; |
17 | } | |
18 | 375 | return true; |
19 | }); | |
20 | } | |
21 | ||
22 | 1 | function combineResources(context, outputData, callback) { |
23 | 162 | var resources = context.resources || []; |
24 | 162 | if (!resources.length) { |
25 | 49 | return callback(); |
26 | } | |
27 | ||
28 | 113 | context.outputFile(function(callback) { |
29 | 113 | lumbar.combine( |
30 | context, | |
31 | resources, | |
32 | context.fileName, | |
33 | context.options.minimize && context.mode === 'scripts', | |
34 | context.mode === 'styles', | |
35 | function(err, data) { | |
36 | 113 | if (data) { |
37 | 112 | _.extend(data, outputData); |
38 | } | |
39 | 113 | callback(err, data); |
40 | }); | |
41 | }, | |
42 | callback); | |
43 | } | |
44 | ||
45 | 1 | module.exports = { |
46 | priority: 1, | |
47 | ||
48 | modeComplete: function(context, next, complete) { | |
49 | 125 | next(function(err) { |
50 | 125 | if (err) { |
51 | 0 | return complete(err); |
52 | } | |
53 | ||
54 | 125 | if (context.combined) { |
55 | // Build the resources array from each of the modules (Need to maintain proper ordering) | |
56 | 30 | var modules = context.config.moduleList(context.package); |
57 | 30 | context.resources = []; |
58 | 30 | modules.forEach(function(module) { |
59 | 60 | context.resources.push.apply(context.resources, context.combineResources[module]); |
60 | }); | |
61 | 30 | combineResources(context, {}, complete); |
62 | } else { | |
63 | 95 | complete(); |
64 | } | |
65 | }); | |
66 | }, | |
67 | module: function(context, next, complete) { | |
68 | 192 | next(function(err) { |
69 | 192 | if (err) { |
70 | 0 | return complete(err); |
71 | } | |
72 | ||
73 | 192 | if (!context.combined) { |
74 | 132 | context.resources = filterDuplicates(context); |
75 | 132 | context.moduleResources = undefined; |
76 | 132 | combineResources(context, { |
77 | module: context.module.name | |
78 | }, | |
79 | complete); | |
80 | } else { | |
81 | 60 | context.combineResources = context.combineResources || {}; |
82 | 60 | context.combineResources[context.module.name] = filterDuplicates(context); |
83 | 60 | context.moduleResources = undefined; |
84 | 60 | complete(); |
85 | } | |
86 | }); | |
87 | } | |
88 | }; | |
89 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'); |
2 | ||
3 | 1 | module.exports = { |
4 | priority: 1, | |
5 | ||
6 | loadConfig: function(context, next, complete) { | |
7 | 118 | var modules = context.config.attributes.modules, |
8 | errored; | |
9 | 118 | _.each(context.libraries.configs, function(library) { |
10 | // Import any modules that are not overriden in the core file | |
11 | 59 | _.each(library.modules, function(module, key) { |
12 | 19 | if (!_.has(modules, key)) { |
13 | 10 | module = modules[key] = _.clone(module); |
14 | ||
15 | 10 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
16 | 40 | var value = module[field]; |
17 | ||
18 | // Deep(er) clone, updating file references | |
19 | 40 | if (_.isArray(value)) { |
20 | 12 | module[field] = context.libraries.mapFiles(value, library); |
21 | 28 | } else if (value) { |
22 | 0 | module[field] = _.clone(value); |
23 | } | |
24 | }); | |
25 | } | |
26 | }); | |
27 | }); | |
28 | ||
29 | 118 | _.each(modules, function(module, name) { |
30 | 144 | var mixins; |
31 | 144 | try { |
32 | 144 | mixins = context.libraries.moduleMixins(module); |
33 | } catch (err) { | |
34 | 2 | errored = true; |
35 | 2 | return complete(new Error('Failed mixins for module "' + name + '": ' + err.message)); |
36 | } | |
37 | ||
38 | // Map existing files that have mixin references | |
39 | 142 | try { |
40 | 142 | ['scripts', 'styles', 'static'].forEach(function(field) { |
41 | 424 | var list = module[field]; |
42 | ||
43 | 424 | if (list) { |
44 | 114 | module[field] = context.libraries.mapFiles(list); |
45 | } | |
46 | }); | |
47 | ||
48 | 141 | _.each(mixins, function(mixin) { |
49 | 30 | var mixinConfig = mixin.mixinConfig, |
50 | library = mixin.library; | |
51 | ||
52 | // Direct copy for any fields that are not already defined on the object. | |
53 | 30 | _.defaults(module, library.attributes); |
54 | ||
55 | // Merge known array/object types | |
56 | 30 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
57 | 120 | mergeValues(module, field, library, mixinConfig, context); |
58 | }); | |
59 | }); | |
60 | } catch (err) { | |
61 | 1 | errored = true; |
62 | 1 | return complete(err); |
63 | } | |
64 | }); | |
65 | ||
66 | // Remove suppressed modules completely | |
67 | 118 | _.each(_.keys(modules), function(name) { |
68 | 144 | if (!modules[name]) { |
69 | 1 | delete modules[name]; |
70 | } | |
71 | }); | |
72 | ||
73 | 118 | if (!errored) { |
74 | 115 | next(complete); |
75 | } | |
76 | } | |
77 | }; | |
78 | ||
79 | 1 | function firstLocal(collection) { |
80 | 42 | for (var i = 0, len = collection.length; i < len; i++) { |
81 | 57 | if (!collection[i].global) { |
82 | 42 | return i; |
83 | } | |
84 | } | |
85 | 0 | return i; |
86 | } | |
87 | ||
88 | 1 | function mergeValues(module, field, library, mixinConfig, context) { |
89 | 120 | var value = module[field], |
90 | mixinValue = library.attributes[field]; | |
91 | ||
92 | 120 | if (!value) { |
93 | 81 | return; |
94 | } | |
95 | ||
96 | 39 | if (value === mixinValue) { |
97 | // Clone any direct copy entries from a mixin | |
98 | 12 | if (_.isArray(value)) { |
99 | 10 | module[field] = context.libraries.mapFiles(value, library, mixinConfig); |
100 | } else { | |
101 | 2 | module[field] = _.clone(value); |
102 | } | |
103 | 27 | } else if (!_.isArray(value)) { |
104 | 5 | _.defaults(value, mixinValue); |
105 | 22 | } else if (mixinValue) { |
106 | 21 | mixinValue = context.libraries.mapFiles(mixinValue, library, mixinConfig); |
107 | ||
108 | 21 | var mixinFirstLocal = firstLocal(mixinValue), |
109 | moduleFirstLocal = firstLocal(value); | |
110 | ||
111 | 21 | if (mixinFirstLocal) { |
112 | 4 | value.unshift.apply(value, mixinValue.slice(0, mixinFirstLocal)); |
113 | } | |
114 | 21 | if (mixinFirstLocal < mixinValue.length) { |
115 | 21 | var locals = mixinValue.slice(mixinFirstLocal); |
116 | 21 | locals.unshift(mixinFirstLocal + moduleFirstLocal, 0); |
117 | 21 | value.splice.apply(value, locals); |
118 | } | |
119 | } | |
120 | } | |
121 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | handlebars = require('handlebars'), | |
4 | fs = require('fs'), | |
5 | path = require('path'), | |
6 | dirname = path.dirname; | |
7 | ||
8 | 1 | var moduleMapTemplate; |
9 | ||
10 | 1 | function getModuleMapTemplate() { |
11 | 17 | if (!moduleMapTemplate) { |
12 | 1 | moduleMapTemplate = handlebars.compile(fs.readFileSync(__dirname + '/module-map.handlebars').toString()); |
13 | } | |
14 | 17 | return moduleMapTemplate; |
15 | } | |
16 | ||
17 | // Force template load before EMFILE may be an issue | |
18 | 1 | getModuleMapTemplate(); |
19 | ||
20 | 1 | function loadModuleMap(map, mapper, callback) { |
21 | 16 | var moduleMapTemplate = getModuleMapTemplate(); |
22 | ||
23 | // This bit of voodoo forces uniform ordering for the output under node. This is used primarily for | |
24 | // testing purposes. | |
25 | 16 | map = (function orderObject(map) { |
26 | 94 | var ret = _.isArray(map) ? [] : {}; |
27 | 94 | _.keys(map).sort().forEach(function(key) { |
28 | 167 | var value = map[key]; |
29 | 167 | ret[key] = _.isObject(value) ? orderObject(value) : value; |
30 | }); | |
31 | 94 | return ret; |
32 | })(map); | |
33 | ||
34 | 16 | callback( |
35 | undefined, | |
36 | moduleMapTemplate({ | |
37 | moduleMapper: mapper, | |
38 | map: JSON.stringify(map) | |
39 | }) | |
40 | ); | |
41 | } | |
42 | ||
43 | 1 | function buildMap(context, callback) { |
44 | 34 | if (context.combined) { |
45 | 15 | moduleConfig(context, undefined, function(err, config, prefix) { |
46 | 15 | callback(err, { base: config }, prefix); |
47 | }); | |
48 | } else { | |
49 | 19 | var attr = context.config.attributes || {}, |
50 | app = attr.application || {}, | |
51 | modules = context.config.moduleList(context.package); | |
52 | ||
53 | 19 | var map = {modules: {}, routes: {}}, |
54 | commonPrefix; | |
55 | ||
56 | 19 | async.forEach(modules, function(module, callback) { |
57 | 26 | moduleConfig(context, module, function(err, config, prefix) { |
58 | 26 | if (err) { |
59 | 0 | return callback(err); |
60 | } | |
61 | ||
62 | 26 | if (app.module === module) { |
63 | 4 | map.base = config; |
64 | } else { | |
65 | 22 | map.modules[module] = config; |
66 | ||
67 | 22 | var routes = context.config.routeList(module); |
68 | 22 | _.each(routes, function(value, route) { |
69 | 17 | map.routes[route] = module; |
70 | }); | |
71 | } | |
72 | 26 | commonPrefix = findPrefix(prefix, commonPrefix); |
73 | ||
74 | 26 | callback(); |
75 | }); | |
76 | }, | |
77 | function(err) { | |
78 | 19 | callback(err, map, commonPrefix); |
79 | }); | |
80 | } | |
81 | } | |
82 | ||
83 | 1 | function stripPrefix(map, prefix) { |
84 | 16 | if (!prefix) { |
85 | 15 | return; |
86 | } | |
87 | ||
88 | 1 | function stripModule(module) { |
89 | 0 | if (module.js) { |
90 | 0 | module.js = stripList(module.js); |
91 | } | |
92 | 0 | if (module.css) { |
93 | 0 | module.css = stripList(module.css); |
94 | } | |
95 | } | |
96 | 1 | function stripList(list) { |
97 | 0 | if (_.isArray(list)) { |
98 | 0 | return list.map(stripEntry); |
99 | } else { | |
100 | 0 | return stripEntry(list); |
101 | } | |
102 | } | |
103 | 1 | function stripEntry(entry) { |
104 | 0 | if (entry.href) { |
105 | 0 | entry.href = entry.href.substring(prefix.length); |
106 | 0 | return entry; |
107 | } else { | |
108 | 0 | return entry.substring(prefix.length); |
109 | } | |
110 | } | |
111 | 1 | if (map.base) { |
112 | 0 | stripModule(map.base); |
113 | } | |
114 | 1 | if (map.modules) { |
115 | 0 | _.each(map.modules, stripModule); |
116 | } | |
117 | } | |
118 | 1 | function moduleConfig(context, module, callback) { |
119 | 41 | var ret = {}, |
120 | commonPrefix, | |
121 | preload = module && context.config.module(module).preload, | |
122 | depends = module && context.config.module(module).depends; | |
123 | 41 | if (preload) { |
124 | 1 | ret.preload = preload; |
125 | } | |
126 | 41 | if (depends) { |
127 | 4 | ret.depends = depends; |
128 | } | |
129 | 41 | async.forEach([{key: 'js', mode: 'scripts'}, {key: 'css', mode: 'styles'}], function(obj, callback) { |
130 | 82 | fileList(context, obj.mode, module, function(err, list, prefix) { |
131 | 82 | ret[obj.key] = list; |
132 | 82 | commonPrefix = findPrefix(prefix, commonPrefix); |
133 | 82 | callback(err); |
134 | }); | |
135 | }, | |
136 | function(err) { | |
137 | 41 | callback(err, ret, commonPrefix); |
138 | }); | |
139 | } | |
140 | 1 | function fileList(context, mode, module, callback) { |
141 | // Check to see if we even have this type of resource | |
142 | 82 | var modules = !context.combined ? [ module ] : context.config.moduleList(context.package); |
143 | 82 | async.some(modules, function(module, callback) { |
144 | 112 | var resourceContext = context.clone(); |
145 | 112 | resourceContext.mode = mode; |
146 | 112 | resourceContext.module = context.config.module(module); |
147 | 112 | resourceContext.isModuleMap = true; |
148 | ||
149 | 112 | resourceContext.plugins.moduleResources(resourceContext, function(err, resources) { |
150 | 112 | callback((resources || []).length); |
151 | }); | |
152 | }, | |
153 | function(hasResource) { | |
154 | 82 | if (!hasResource) { |
155 | 9 | return callback(); |
156 | } | |
157 | ||
158 | // Output the config | |
159 | 73 | context.fileNamesForModule(mode, module, function(err, configs) { |
160 | 73 | if (err) { |
161 | 0 | return callback(err); |
162 | } | |
163 | ||
164 | 73 | var prefix; |
165 | 172 | configs = configs.filter(function(config) { return !config.server; }); |
166 | 99 | configs = configs.sort(function(a, b) { return a.pixelDensity - b.pixelDensity; }); |
167 | 73 | configs = configs.map(function(config, i) { |
168 | 99 | var path = config.fileName.path, |
169 | ret = path + '.' + config.fileName.extension; | |
170 | ||
171 | 99 | if (config.pixelDensity) { |
172 | 61 | ret = { href: ret }; |
173 | 61 | if (0 < i) { |
174 | 26 | ret.minRatio = configs[i - 1].pixelDensity + (config.pixelDensity - configs[i - 1].pixelDensity) / 2; |
175 | } | |
176 | 61 | if (i < configs.length - 1) { |
177 | 26 | ret.maxRatio = config.pixelDensity + (configs[i + 1].pixelDensity - config.pixelDensity) / 2; |
178 | } | |
179 | } | |
180 | ||
181 | // Update the prefix tracker | |
182 | 99 | prefix = findPrefix(path, prefix); |
183 | ||
184 | 99 | return ret; |
185 | }); | |
186 | ||
187 | 73 | var ret; |
188 | 73 | if (configs.length === 1) { |
189 | 48 | ret = configs[0]; |
190 | 25 | } else if (configs.length) { |
191 | 25 | ret = configs; |
192 | } | |
193 | 73 | callback(undefined, ret, prefix); |
194 | }); | |
195 | }); | |
196 | } | |
197 | ||
198 | 1 | function findPrefix(path, prefix) { |
199 | /*jshint eqnull:true*/ | |
200 | 207 | if (path == null) { |
201 | 9 | return prefix; |
202 | } | |
203 | 198 | if (prefix == null) { |
204 | // Ensure that we get 'x' for strings of type 'x/' | |
205 | 133 | prefix = dirname(path + 'a') + '/'; |
206 | } | |
207 | 198 | for (var i = 0, len = prefix.length; i < len; i++) { |
208 | 133 | if (path.charAt(i) !== prefix.charAt(i)) { |
209 | 133 | return prefix.substring(0, i); |
210 | } | |
211 | } | |
212 | 65 | return prefix; |
213 | } | |
214 | ||
215 | 1 | module.exports = { |
216 | mode: 'scripts', | |
217 | priority: 50, | |
218 | ||
219 | buildMap: buildMap, | |
220 | ||
221 | resource: function(context, next, complete) { | |
222 | 241 | var config = context.config; |
223 | ||
224 | 241 | if (context.resource['module-map']) { |
225 | 21 | var buildModuleMap = function(context, callback) { |
226 | 16 | module.exports.buildMap(context, function(err, map, prefix) { |
227 | 16 | if (err) { |
228 | 0 | callback(err); |
229 | } else { | |
230 | 16 | var moduleMap = config.attributes.moduleMap || 'module.exports.moduleMap'; |
231 | 16 | stripPrefix(map, prefix); |
232 | 16 | loadModuleMap(map, moduleMap, function(err, data) { |
233 | 16 | callback(err, data && {data: data, generated: true, noSeparator: true, ignoreWarnings: true}); |
234 | }); | |
235 | } | |
236 | }); | |
237 | }; | |
238 | 21 | buildModuleMap.sourceFile = undefined; |
239 | 21 | complete(undefined, buildModuleMap); |
240 | } else { | |
241 | 220 | next(complete); |
242 | } | |
243 | } | |
244 | }; | |
245 |
Line | Hits | Source |
---|---|---|
1 | 1 | var handlebars = require('handlebars'); |
2 | ||
3 | 1 | const DEFAULT_CONFIG_TEMPLATE = "{{{name}}} = {{{data}}};\n"; |
4 | 1 | var packageConfigTemplate = handlebars.compile(DEFAULT_CONFIG_TEMPLATE); |
5 | ||
6 | 1 | function loadPackageConfig(name, configFile, fileUtil, callback) { |
7 | 16 | if (!configFile) { |
8 | 1 | return callback(new Error('package_config.json specified without file being set')); |
9 | } | |
10 | ||
11 | 15 | fileUtil.readFile(configFile, function(err, data) { |
12 | 15 | if (err) { |
13 | 0 | callback(new Error('Failed to load package config "' + configFile + '"\n\t' + err)); |
14 | 0 | return; |
15 | } | |
16 | ||
17 | 15 | callback( |
18 | undefined, | |
19 | packageConfigTemplate({ | |
20 | name: name, | |
21 | data: data | |
22 | }) | |
23 | ); | |
24 | }); | |
25 | } | |
26 | ||
27 | 1 | module.exports = { |
28 | mode: 'scripts', | |
29 | priority: 50, | |
30 | ||
31 | resource: function(context, next, complete) { | |
32 | 279 | var resource = context.resource; |
33 | ||
34 | 279 | if (resource['package-config']) { |
35 | 17 | var packageConfigGen = function(context, callback) { |
36 | 16 | var config = context.config, |
37 | options = context.options, | |
38 | packageConfig = config.attributes.packageConfig || 'module.exports.config'; | |
39 | ||
40 | 16 | loadPackageConfig(packageConfig, options.packageConfigFile, context.fileUtil, function(err, data) { |
41 | 16 | callback(err, data && {data: data, inputs: [options.packageConfigFile], generated: true, noSeparator: true}); |
42 | }); | |
43 | }; | |
44 | 17 | packageConfigGen.sourceFile = undefined; |
45 | 17 | complete(undefined, packageConfigGen); |
46 | } else { | |
47 | 262 | next(complete); |
48 | } | |
49 | } | |
50 | }; | |
51 |
Line | Hits | Source |
---|---|---|
1 | 1 | var handlebars = require('handlebars'); |
2 | ||
3 | 1 | const TEMPLATE = '/* router : {{{name}}} */\nmodule.name = "{{{name}}}";\nmodule.routes = {{{routes}}};\n'; |
4 | 1 | var routerTemplate = handlebars.compile(TEMPLATE); |
5 | ||
6 | 1 | function loadRouter(context, name, routes, callback) { |
7 | 14 | callback( |
8 | undefined, | |
9 | routerTemplate({ | |
10 | name: name, | |
11 | routes: JSON.stringify(routes) | |
12 | }) | |
13 | ); | |
14 | } | |
15 | ||
16 | 1 | module.exports = { |
17 | mode: 'scripts', | |
18 | priority: 50, | |
19 | ||
20 | moduleResources: function(context, next, complete) { | |
21 | 187 | next(function(err, ret) { |
22 | 187 | if (err) { |
23 | 0 | return complete(err); |
24 | } | |
25 | ||
26 | // Generate the router if we have the info for it | |
27 | 187 | var module = context.module; |
28 | 187 | if (module.routes) { |
29 | 47 | ret.unshift({ routes: module.routes }); |
30 | } | |
31 | ||
32 | 187 | complete(undefined, ret); |
33 | }); | |
34 | }, | |
35 | resource: function(context, next, complete) { | |
36 | 262 | var resource = context.resource, |
37 | module = context.module.name; | |
38 | ||
39 | 262 | if (resource.routes) { |
40 | 21 | var routerGen = function(context, callback) { |
41 | 14 | loadRouter(context, module, resource.routes, function(err, data) { |
42 | 14 | callback(err, data && {data: data, generated: true, noSeparator: true}); |
43 | }); | |
44 | }; | |
45 | 21 | routerGen.moduleStart = true; |
46 | 21 | routerGen.sourceFile = undefined; |
47 | 21 | complete(undefined, routerGen); |
48 | } else { | |
49 | 241 | next(complete); |
50 | } | |
51 | } | |
52 | }; | |
53 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Scope Plugin : Wrap javascript units in module scopes. | |
3 | * | |
4 | * Config: | |
5 | * root: | |
6 | * scope: | |
7 | * scope: Size of the smallest module scope. May be: 'module', 'resource', 'none' | |
8 | * template: Template used override the default module logic. | |
9 | * This may be an inline handlebars template or a reference to a handlebars file. | |
10 | * Available fields: | |
11 | * scope : Name of the javascript module | |
12 | * isTopNamespace : Truthy if the current module is a top level namespace | |
13 | * appName : Name of the application object | |
14 | * yield : Location that the embedded javascript will be inserted | |
15 | * aliases : Key value mapping of objects that will be imported into the module locally. | |
16 | * This is useful for allowing minimization of commonly used objects such as the | |
17 | * application object or common libraries. | |
18 | * | |
19 | * root.scope may be set to the scope values as a shorthand. | |
20 | * | |
21 | * Mixins: | |
22 | * All fields may be mixed in. Template file references are converted to mixin space. The alias | |
23 | * field will be mixed in per-key with the local definition taking priority. | |
24 | */ | |
25 | 1 | var _ = require('underscore'); |
26 | ||
27 | 1 | function getScope(attr) { |
28 | 407 | return (attr.scope && attr.scope.scope) || attr.scope; |
29 | } | |
30 | 1 | function toObj(obj) { |
31 | 75 | return _.isString(obj) ? {scope: obj} : obj; |
32 | } | |
33 | ||
34 | 1 | function generator(string) { |
35 | 166 | var ret = function(context, callback) { callback(undefined, {data: string, generated: true, noSeparator: true}); }; |
36 | 96 | ret.stringValue = string; |
37 | 96 | ret.sourceFile = undefined; |
38 | 96 | ret.ignoreWarnings = true; |
39 | 96 | return ret; |
40 | } | |
41 | ||
42 | 1 | var scopeTemplateDelimiter = /\{?\{\{yield\}\}\}?/; |
43 | ||
44 | 1 | function ensureModuleTemplates(context, complete) { |
45 | 46 | if (!context.configCache.moduleTemplate) { |
46 | 29 | var template = context.config.attributes.scope && context.config.attributes.scope.template; |
47 | 29 | if (!template) { |
48 | 18 | template = __dirname + '/scope-module.handlebars'; |
49 | } | |
50 | ||
51 | 29 | context.fileUtil.loadTemplate(template, scopeTemplateDelimiter, function(err, templates) { |
52 | 29 | if (err) { |
53 | 0 | complete(err); |
54 | } else { | |
55 | 29 | context.configCache.moduleTemplate = { |
56 | start: templates[0], | |
57 | end: templates[1] | |
58 | }; | |
59 | 29 | complete(); |
60 | } | |
61 | }); | |
62 | } else { | |
63 | 17 | complete(); |
64 | } | |
65 | } | |
66 | ||
67 | 1 | function wrapResources(resources, context) { |
68 | 46 | var cache = context.moduleCache; |
69 | 46 | if (!cache.scopeName) { |
70 | 46 | var app = context.config.attributes.application, |
71 | appName = app && app.name; | |
72 | ||
73 | 46 | if (!appName || context.module.topLevelName || context.config.isAppModule(context.module)) { |
74 | 32 | cache.isTopNamespace = true; |
75 | 32 | cache.scopeName = context.module.topLevelName || appName || context.module.name; |
76 | } else { | |
77 | 14 | cache.scopeName = appName + "['" + context.module.name + "']"; |
78 | } | |
79 | 46 | cache.appName = appName; |
80 | } | |
81 | ||
82 | // Wrap the module content in a javascript module | |
83 | 46 | if (resources.length) { |
84 | 46 | function isModule(reference) { |
85 | 26 | var stripOperators = /['"\]]/g; |
86 | 26 | return reference === cache.scopeName |
87 | || (!cache.isTopNamespace | |
88 | && reference.replace(stripOperators, '').substr(-context.module.name.length) === context.module.name); | |
89 | } | |
90 | ||
91 | 46 | var scope = context.config.attributes.scope || {}, |
92 | ||
93 | // Call args calculation | |
94 | aliasesHash = context.module.aliases === false ? {} : _.extend({}, scope.aliases, context.module.aliases), | |
95 | aliases = _.pairs(aliasesHash), | |
96 | 16 | aliases = _.filter(aliases, function(alias) { return alias[1]; }), |
97 | 13 | externals = _.filter(aliases, function(alias) { return !isModule(alias[1]); }), |
98 | aliasVars = _.pluck(externals, '0'), | |
99 | callSpec = _.pluck(externals, '1'), | |
100 | ||
101 | // Internal scope calculation | |
102 | 13 | internals = _.filter(aliases, function(alias) { return isModule(alias[1]); }), |
103 | internalVars = _.pluck(internals, '0'), | |
104 | internalScope = ''; | |
105 | ||
106 | 46 | callSpec.unshift('this'); |
107 | 46 | if (cache.isTopNamespace) { |
108 | 32 | internalVars.unshift(cache.scopeName); |
109 | } else { | |
110 | 14 | internalScope += cache.scopeName + ' = exports;'; |
111 | } | |
112 | 84 | internalVars = _.map(internalVars, function(name) { return name + ' = exports'; }); |
113 | 46 | if (internalVars.length) { |
114 | 33 | internalScope += 'var ' + internalVars.join(', ') + ';'; |
115 | } | |
116 | ||
117 | 46 | var scopeDecl = ''; |
118 | 46 | if (context.moduleCache.isTopNamespace) { |
119 | // Insert the package declaration | |
120 | 32 | scopeDecl = 'var ' + context.moduleCache.scopeName + ';'; |
121 | } | |
122 | 46 | var templateContext = { |
123 | isTopNamespace: cache.isTopNamespace, | |
124 | name: cache.appName, | |
125 | scopeDecl: scopeDecl, | |
126 | scope: cache.scopeName, | |
127 | aliasVars: aliasVars.join(', '), | |
128 | internalScope: internalScope, | |
129 | callSpec: callSpec.join(', ') | |
130 | }; | |
131 | ||
132 | 46 | resources.unshift(generator(context.configCache.moduleTemplate.start(templateContext))); |
133 | 46 | resources.push(generator(context.configCache.moduleTemplate.end(templateContext))); |
134 | } | |
135 | 46 | return resources; |
136 | } | |
137 | ||
138 | 1 | module.exports = { |
139 | mode: 'scripts', | |
140 | priority: 50, | |
141 | ||
142 | loadMixin: function(context, next, complete) { | |
143 | 63 | var mixinScope = toObj(context.loadedLibrary.scope); |
144 | 63 | if (mixinScope) { |
145 | 6 | var scope = toObj(context.libraries.originalConfig.scope || {}), |
146 | configScope = toObj(_.clone(context.config.attributes.scope || scope)), | |
147 | assigned = false; | |
148 | ||
149 | 6 | if (('scope' in mixinScope) && !('scope' in scope)) { |
150 | 2 | configScope.scope = mixinScope.scope; |
151 | ||
152 | 2 | assigned = true; |
153 | } | |
154 | ||
155 | 6 | if (('template' in mixinScope) && !('template' in scope)) { |
156 | 3 | configScope.template = (context.loadedLibrary.root || '') + mixinScope.template; |
157 | ||
158 | 3 | assigned = true; |
159 | } | |
160 | ||
161 | 6 | if (context.libraries.mergeHash('aliases', scope, mixinScope, configScope)) { |
162 | 3 | assigned = true; |
163 | } | |
164 | ||
165 | 6 | if (assigned) { |
166 | 4 | context.config.attributes.scope = configScope; |
167 | } | |
168 | } | |
169 | 63 | next(complete); |
170 | }, | |
171 | loadConfig: function(context, next, complete) { | |
172 | 115 | var modules = context.config.attributes.modules; |
173 | ||
174 | 115 | try { |
175 | 115 | _.each(modules, function(module) { |
176 | 138 | var mixins = context.libraries.moduleMixins(module); |
177 | ||
178 | 138 | _.each(mixins, function(mixin) { |
179 | 30 | context.libraries.mergeHash('aliases', module, mixin.library.attributes, module); |
180 | }); | |
181 | }); | |
182 | } catch (err) { | |
183 | 0 | return complete(err); |
184 | } | |
185 | ||
186 | 115 | next(complete); |
187 | }, | |
188 | ||
189 | resourceList: function(context, next, complete) { | |
190 | 306 | next(function(err, resources) { |
191 | 306 | if (err) { |
192 | 0 | return complete(err); |
193 | } | |
194 | ||
195 | 306 | if (getScope(context.config.attributes) === 'resource' |
196 | && !context.resource.global | |
197 | && !context.resource.dir) { | |
198 | 2 | resources.unshift(generator('(function() {\n')); |
199 | 2 | resources.push(generator('}).call(this);\n')); |
200 | } | |
201 | 306 | complete(undefined, resources); |
202 | }); | |
203 | }, | |
204 | ||
205 | module: function(context, next, complete) { | |
206 | 101 | next(function(err) { |
207 | 101 | if (err) { |
208 | 0 | return complete(err); |
209 | } | |
210 | ||
211 | 101 | var resources = context.moduleResources, |
212 | scope = getScope(context.config.attributes); | |
213 | ||
214 | 101 | if (resources.length && scope !== 'none') { |
215 | 46 | ensureModuleTemplates(context, function(err) { |
216 | 46 | if (err) { |
217 | 0 | complete(err); |
218 | } else { | |
219 | // Split up globals and non-globals | |
220 | 46 | var globals = [], |
221 | children = [], | |
222 | moduleStart = []; | |
223 | 46 | for (var i = 0; i < resources.length; i++) { |
224 | 158 | var resource = resources[i]; |
225 | 158 | if (resource.moduleStart) { |
226 | 13 | moduleStart.push(resource); |
227 | 145 | } else if (!resource.global) { |
228 | 130 | children.push(resource); |
229 | } else { | |
230 | 15 | if (children.length) { |
231 | 0 | throw new Error('Scoped files may not appear before global files.'); |
232 | } | |
233 | 15 | globals.push(resource); |
234 | } | |
235 | } | |
236 | ||
237 | 46 | children = moduleStart.concat(children); |
238 | 46 | globals.push.apply(globals, wrapResources(children, context, complete)); |
239 | ||
240 | 46 | context.moduleResources = globals; |
241 | 46 | complete(); |
242 | } | |
243 | }); | |
244 | } else { | |
245 | 55 | complete(); |
246 | } | |
247 | }); | |
248 | } | |
249 | }; | |
250 | ||
251 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | manyToOne = require('./many-to-one-output'); | |
3 | ||
4 | 1 | module.exports = _.extend({ mode: 'scripts' }, manyToOne); |
5 |
Line | Hits | Source |
---|---|---|
1 | 1 | module.exports = { |
2 | mode: 'scripts', | |
3 | priority: 99, | |
4 | ||
5 | fileFilter: function(context, next) { | |
6 | 131 | return /\.(js|json)$/; |
7 | }, | |
8 | ||
9 | fileName: function(context, next, complete) { | |
10 | 107 | complete(undefined, {path: context.baseName, extension: 'js'}); |
11 | }, | |
12 | ||
13 | moduleResources: function(context, next, complete) { | |
14 | 187 | var module = context.module; |
15 | 187 | complete(undefined, (module.scripts || module.files || (module.slice && module) || []).slice()); |
16 | } | |
17 | }; | |
18 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'); |
2 | ||
3 | 1 | module.exports = { |
4 | mode: 'scripts', | |
5 | priority: 98, // Just below the core scripts plugin.... | |
6 | ||
7 | fileFilter: function(context, next) { | |
8 | 0 | return /\.(js|json)$/; |
9 | }, | |
10 | ||
11 | outputConfigs: function(context, next, complete) { | |
12 | 0 | next(function(err, files) { |
13 | 0 | if (err) { |
14 | 0 | return complete(err); |
15 | } | |
16 | ||
17 | // Permutation of other configs and ours | |
18 | 0 | var ret = []; |
19 | 0 | files.forEach(function(fileConfig) { |
20 | 0 | [true, false].forEach(function(server) { |
21 | 0 | var config = _.clone(fileConfig); |
22 | 0 | config.server = server; |
23 | 0 | ret.push(config); |
24 | }); | |
25 | }); | |
26 | 0 | complete(undefined, ret); |
27 | }); | |
28 | }, | |
29 | ||
30 | fileName: function(context, next, complete) { | |
31 | 0 | next(function(err, ret) { |
32 | 0 | if (ret && context.fileConfig.server) { |
33 | 0 | ret.path += '-server'; |
34 | } | |
35 | 0 | complete(err, ret); |
36 | }); | |
37 | }, | |
38 | ||
39 | moduleResources: function(context, next, complete) { | |
40 | 0 | var module = context.module; |
41 | ||
42 | 0 | var files = []; |
43 | 0 | (module.server || module.scripts).forEach(function(script) { |
44 | 0 | if (!_.has(script, 'server') || script.server === context.fileConfig.server) { |
45 | 0 | files.push(script); |
46 | } | |
47 | }); | |
48 | ||
49 | 0 | complete(undefined, files); |
50 | } | |
51 | }; | |
52 |
Line | Hits | Source |
---|---|---|
1 | 1 | var async = require('async'); |
2 | ||
3 | 1 | module.exports = { |
4 | mode: 'static', | |
5 | priority: 1, | |
6 | ||
7 | module: function(context, next, complete) { | |
8 | 74 | next(function(err) { |
9 | 74 | async.forEach(context.moduleResources, function(resource, callback) { |
10 | 28 | var fileContext = context.clone(); |
11 | 28 | fileContext.resource = resource; |
12 | ||
13 | // Filter out dir entries | |
14 | 28 | if (resource.dir) { |
15 | 2 | return callback(); |
16 | } | |
17 | ||
18 | 26 | fileContext.outputFile(function(callback) { |
19 | 26 | var fileInfo = fileContext.loadResource(resource, function(err, data) { |
20 | 26 | if (err || !data || !data.content) { |
21 | 0 | return callback(err); |
22 | } | |
23 | ||
24 | 26 | var ret = { |
25 | fileName: fileContext.fileName, | |
26 | inputs: fileInfo.inputs || [ fileInfo.name ], | |
27 | module: context.module.name, | |
28 | resource: resource | |
29 | }; | |
30 | ||
31 | 26 | context.fileUtil.writeFile(fileContext.fileName, data.content, function(err) { |
32 | 26 | if (err) { |
33 | 0 | err = new Error('Static output "' + fileContext.fileName + '" failed\n\t' + err); |
34 | } | |
35 | ||
36 | 26 | callback(err, ret); |
37 | }); | |
38 | }); | |
39 | }, | |
40 | callback); | |
41 | }, | |
42 | complete); | |
43 | }); | |
44 | } | |
45 | }; | |
46 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'); |
2 | ||
3 | /* | |
4 | * Replace variables with actual values | |
5 | */ | |
6 | 1 | function replaceVariables(str, context) { |
7 | 78 | return str.replace(/\#{platform}/, context.platform); |
8 | } | |
9 | ||
10 | /* | |
11 | * Make sure the directory name has a trailing slash | |
12 | */ | |
13 | 1 | function normalizeDirName(dirName) { |
14 | 2 | if (dirName.match(/\/$/)) { |
15 | 0 | return dirName; |
16 | } | |
17 | 2 | return dirName + '/'; |
18 | } | |
19 | ||
20 | 1 | module.exports = { |
21 | mode: 'static', | |
22 | priority: 99, | |
23 | ||
24 | fileName: function(context, next, complete) { | |
25 | 26 | var resource = context.resource, |
26 | src = resource.src || resource.sourceFile, | |
27 | dir = resource.dir; | |
28 | ||
29 | 26 | var root = ''; |
30 | ||
31 | 26 | if (resource.srcDir && resource.dest) { |
32 | // srcDir is some prefix of src - we want to append the remaining part or src to dest | |
33 | 2 | src = src.substring(resource.srcDir.length + 1); |
34 | 2 | root += normalizeDirName(resource.dest); |
35 | 24 | } else if (resource.dest) { |
36 | 19 | src = resource.dest; |
37 | } | |
38 | ||
39 | 26 | root = replaceVariables(root, context); |
40 | 26 | src = replaceVariables(src, context); |
41 | ||
42 | 26 | var components = /(.*?)(?:\.([^.]+))?$/.exec(src); |
43 | 26 | complete(undefined, {root: resource.root, path: root + components[1], extension: components[2]}); |
44 | }, | |
45 | ||
46 | resource: function(context, next, complete) { | |
47 | 28 | next(function(err, resource) { |
48 | 28 | if (_.isString(resource)) { |
49 | 0 | resource = replaceVariables(resource, context); |
50 | 28 | } else if (resource.src) { |
51 | 26 | resource.src = replaceVariables(resource.src, context); |
52 | } | |
53 | 28 | complete(undefined, resource); |
54 | }); | |
55 | } | |
56 | }; | |
57 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | manyToOne = require('./many-to-one-output'); | |
3 | ||
4 | 1 | module.exports = _.extend({ mode: 'styles' }, manyToOne); |
5 |
Line | Hits | Source |
---|---|---|
1 | 1 | module.exports = { |
2 | mode: 'styles', | |
3 | priority: 99, | |
4 | ||
5 | fileName: function(context, next, complete) { | |
6 | 113 | complete(undefined, {path: context.baseName, extension: 'css'}); |
7 | } | |
8 | }; | |
9 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'); | |
3 | ||
4 | 1 | module.exports = { |
5 | mode: ['scripts', 'styles'], | |
6 | priority: 25, | |
7 | ||
8 | loadMixin: function(context, next, complete) { | |
9 | 63 | var mixinStyles = context.loadedLibrary.styles; |
10 | 63 | if (mixinStyles) { |
11 | 16 | var styles = context.libraries.originalConfig.styles || {}, |
12 | configStyles = _.clone(context.config.attributes.styles || styles), | |
13 | assigned = false; | |
14 | ||
15 | 16 | ['configObject'].forEach(function(key) { |
16 | 16 | if ((key in mixinStyles) && !(key in styles)) { |
17 | 1 | configStyles[key] = mixinStyles[key]; |
18 | ||
19 | 1 | assigned = true; |
20 | } | |
21 | }); | |
22 | ||
23 | 16 | if (context.libraries.mergeFiles('config', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
24 | 4 | assigned = true; |
25 | } | |
26 | ||
27 | 16 | if (assigned) { |
28 | 4 | context.config.attributes.styles = configStyles; |
29 | } | |
30 | } | |
31 | 63 | next(complete); |
32 | }, | |
33 | ||
34 | resource: function(context, next, complete) { | |
35 | 405 | if (context.resource['stylus-config']) { |
36 | 3 | var configGenerator = function(context, callback) { |
37 | // TODO : Load and output the JSON config options | |
38 | // We can use normal JSON.parse here as stylus uses this -> we can call extend as part of the build | |
39 | 3 | var styles = context.config.attributes.styles || {}, |
40 | configFiles = styles.config || [], | |
41 | stylusConfig = styles.configObject || 'module.exports.stylusConfig'; | |
42 | ||
43 | 9 | configFiles = _.map(configFiles, function(config) { return config.src || config; }); |
44 | ||
45 | 3 | async.map(configFiles, |
46 | function(config, callback) { | |
47 | 6 | context.fileUtil.readFile(config, function(err, data) { |
48 | 6 | callback(err, data); |
49 | }); | |
50 | }, | |
51 | function(err, data) { | |
52 | 3 | if (data) { |
53 | 3 | try { |
54 | 3 | var config = _.reduce(data, function(config, json) { |
55 | 6 | return _.extend(config, JSON.parse(json)); |
56 | }, {}); | |
57 | 3 | data = {data: stylusConfig + ' = ' + JSON.stringify(config) + ';\n', inputs: configFiles, noSeparator: true}; |
58 | } catch (parseError) { | |
59 | // TODO : Better error handling here? | |
60 | 0 | err = parseError; |
61 | 0 | data = undefined; |
62 | } | |
63 | } | |
64 | 3 | callback(err, data); |
65 | }); | |
66 | }; | |
67 | 3 | configGenerator.sourceFile = undefined; |
68 | 3 | complete(undefined, configGenerator); |
69 | } else { | |
70 | 402 | next(complete); |
71 | } | |
72 | }, | |
73 | ||
74 | module: function(context, next, complete) { | |
75 | 192 | next(function() { |
76 | 192 | var styles = context.config.attributes.styles || {}, |
77 | config = styles.config || []; | |
78 | ||
79 | 192 | if (config.length) { |
80 | 59 | _.each(context.moduleResources, function(resource) { |
81 | 218 | if (resource.stylus) { |
82 | 29 | resource.plugins.push({ |
83 | plugin: __dirname + '/stylus-config-worker', | |
84 | data: config | |
85 | }); | |
86 | } | |
87 | }); | |
88 | } | |
89 | ||
90 | 192 | complete(); |
91 | }); | |
92 | } | |
93 | }; | |
94 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Stylus Plugin : Compile stylus files. | |
3 | * | |
4 | * Config: | |
5 | * root: | |
6 | * styles: | |
7 | * includes: Array of paths to add to stylus includes. | |
8 | * pixelDensity: Defines the pixel densities generated for each plaform. | |
9 | * urlSizeLimit: Maximum file size to inline. Passed to stylus-images plugin | |
10 | * copyFiles: Boolean specifying if non-inlined url references should be compied | |
11 | * To the build directly. Passed to stylus-images plugin. | |
12 | * styleRoot: Project path to resolve files from. | |
13 | * useNib: Truthy to include nib in the project build | |
14 | * | |
15 | * Mixins: | |
16 | * All fields may be mixed in. In the case of conflicts the local config wins for simple values and | |
17 | * for arrays the content will be merged in order. pixelDensity is mixed in at the platform definition | |
18 | * level. File references are converted to mixin space. | |
19 | * | |
20 | * styleRoot is used locally for file lookup when compiling the mixin content. | |
21 | */ | |
22 | 1 | var _ = require('underscore'), |
23 | ChildPool = require('../child-pool'), | |
24 | inlineStyles = require('./inline-styles'), | |
25 | path = require('path'), | |
26 | normalize = path.normalize, | |
27 | fu = require('../fileUtil'); | |
28 | ||
29 | // Forward cache resets to any workers | |
30 | 1 | fu.on('cache:reset', function(path) { |
31 | 233 | worker.sendAll({type: 'cache:reset', path: path}); |
32 | }); | |
33 | ||
34 | 1 | var worker = new ChildPool(__dirname + '/stylus-worker'); |
35 | ||
36 | 1 | function generateSource(context, options, styleConfig) { |
37 | 31 | var includes = (styleConfig.includes || []).concat(options.files), |
38 | module = options.module; | |
39 | ||
40 | 31 | var nibLocation = includes.indexOf('nib'), |
41 | useNib; | |
42 | 31 | if (styleConfig.useNib) { |
43 | 26 | useNib = true; |
44 | 26 | includes.unshift('nib'); |
45 | 5 | } else if (nibLocation >= 0) { |
46 | // Special case nib handling to maintain backwards compatibility | |
47 | // WARN: This may be deprecated in future releases | |
48 | 0 | useNib = true; |
49 | 0 | includes.splice(nibLocation, 1); |
50 | } | |
51 | ||
52 | 31 | var declare = context.config.platformList().map(function(platform) { |
53 | 66 | return '$' + platform + ' = ' + (platform === context.platform); |
54 | }).join('\n') + '\n'; | |
55 | ||
56 | 31 | var mixins = [], |
57 | mixinLUT = {}; | |
58 | ||
59 | 31 | var source = declare + includes.map(function(include) { |
60 | 103 | var source = include.library; |
61 | 103 | var statement = '@import ("' + (include.originalSrc || include.src || include) + '")\n'; |
62 | 103 | if (source) { |
63 | 8 | var name = '', |
64 | root = (source.parent || source).root || '', | |
65 | stylusRoot = ((source.parent || source).styles || {}).styleRoot, | |
66 | library = (source.parent || source).name || ''; | |
67 | 8 | if (source.parent) { |
68 | 7 | name = source.name || ''; |
69 | } | |
70 | 8 | var mixinName = name + '_' + library; |
71 | ||
72 | 8 | if (!mixinLUT[mixinName]) { |
73 | 5 | var mixinDecl = context.libraries.findDecl(module.mixins, {name: name, library: library}), |
74 | overrides = mixinDecl && mixinDecl.overrides; | |
75 | ||
76 | 5 | mixins.push({ |
77 | root: normalize(root), | |
78 | stylusRoot: stylusRoot && normalize(stylusRoot), | |
79 | overrides: overrides | |
80 | }); | |
81 | 5 | mixinLUT[mixinName] = mixins.length-1; |
82 | } | |
83 | 8 | mixinName = mixinLUT[mixinName]; |
84 | ||
85 | 8 | return 'push-mixin("' + mixinName + '")\n' |
86 | + statement | |
87 | + 'pop-mixin()\n'; | |
88 | } else { | |
89 | 95 | return statement; |
90 | } | |
91 | }).join(''); | |
92 | ||
93 | 31 | return { |
94 | useNib: useNib, | |
95 | source: source, | |
96 | mixins: mixins | |
97 | }; | |
98 | } | |
99 | ||
100 | 1 | function compile(options, callback) { |
101 | 31 | var context = options.context, |
102 | ||
103 | styleConfig = context.config.attributes.styles || {}; | |
104 | ||
105 | 31 | var loadPrefix = context.config.loadPrefix(), |
106 | externalPrefix; | |
107 | 31 | if (loadPrefix) { |
108 | 9 | externalPrefix = loadPrefix + (context.buildPath.indexOf('/') >= 0 ? path.dirname(context.buildPath) + '/' : ''); |
109 | } | |
110 | ||
111 | 31 | var imageOptions = { |
112 | outdir: path.dirname(context.fileName), | |
113 | resolutions: context.modeCache.pixelDensity, | |
114 | limit: styleConfig.urlSizeLimit, | |
115 | copyFiles: styleConfig.copyFiles, | |
116 | externalPrefix: externalPrefix | |
117 | }; | |
118 | ||
119 | 31 | var source = generateSource(context, options, styleConfig); |
120 | ||
121 | 31 | context.fileUtil.ensureDirs(context.fileName, function(err) { |
122 | 31 | if (err) { |
123 | 0 | return callback(err); |
124 | } | |
125 | ||
126 | 31 | worker.send({ |
127 | plugins: options.plugins, | |
128 | ||
129 | useNib: source.useNib, | |
130 | imageOptions: imageOptions, | |
131 | ||
132 | filename: options.filename, | |
133 | minimize: context.options.minimize, | |
134 | ||
135 | source: source.source, | |
136 | mixins: source.mixins, | |
137 | ||
138 | lookupPath: context.fileUtil.lookupPath(), | |
139 | styleRoot: styleConfig.styleRoot && context.fileUtil.resolvePath(styleConfig.styleRoot) | |
140 | }, | |
141 | callback); | |
142 | }); | |
143 | } | |
144 | ||
145 | 1 | module.exports = { |
146 | // scripts mode is used also to support inline styles | |
147 | mode: ['styles', 'scripts'], | |
148 | priority: 50, | |
149 | ||
150 | loadMixin: function(context, next, complete) { | |
151 | 63 | var mixinStyles = context.loadedLibrary.styles; |
152 | 63 | if (mixinStyles) { |
153 | 16 | var styles = context.libraries.originalConfig.styles || {}, |
154 | configStyles = _.clone(context.config.attributes.styles || styles), | |
155 | assigned = false; | |
156 | ||
157 | 16 | ['urlSizeLimit', 'copyFiles', 'useNib'].forEach(function(key) { |
158 | 48 | if ((key in mixinStyles) && !(key in styles)) { |
159 | 6 | configStyles[key] = mixinStyles[key]; |
160 | ||
161 | 6 | assigned = true; |
162 | } | |
163 | }); | |
164 | ||
165 | 16 | if (context.libraries.mergeFiles('includes', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
166 | 4 | assigned = true; |
167 | } | |
168 | ||
169 | 16 | if (context.libraries.mergeHash('pixelDensity', styles, mixinStyles, configStyles)) { |
170 | 3 | assigned = true; |
171 | } | |
172 | ||
173 | 16 | if (assigned) { |
174 | 6 | context.config.attributes.styles = configStyles; |
175 | } | |
176 | } | |
177 | 63 | next(complete); |
178 | }, | |
179 | ||
180 | outputConfigs: function(context, next, complete) { | |
181 | 184 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
182 | 89 | return next(complete); |
183 | } | |
184 | ||
185 | 95 | next(function(err, files) { |
186 | 95 | if (err) { |
187 | 0 | return complete(err); |
188 | } | |
189 | ||
190 | 95 | var ret = [], |
191 | styleConfig = context.config.attributes.styles || {}, | |
192 | pixelDensity = styleConfig.pixelDensity || {}; | |
193 | 95 | if (context.platform) { |
194 | 74 | pixelDensity = pixelDensity[context.platform] || pixelDensity; |
195 | } | |
196 | 95 | if (!_.isArray(pixelDensity)) { |
197 | 58 | pixelDensity = [ 1 ]; |
198 | } | |
199 | 95 | context.modeCache.pixelDensity = pixelDensity; |
200 | ||
201 | // Permutation of other configs and ours | |
202 | 95 | var primary = true; |
203 | 95 | files.forEach(function(fileConfig) { |
204 | 95 | pixelDensity.forEach(function(density) { |
205 | 133 | var config = _.clone(fileConfig); |
206 | 133 | config.pixelDensity = density; |
207 | 133 | config.isPrimary = primary; |
208 | 133 | primary = false; |
209 | 133 | ret.push(config); |
210 | }); | |
211 | }); | |
212 | 95 | complete(undefined, ret); |
213 | }); | |
214 | }, | |
215 | ||
216 | fileName: function(context, next, complete) { | |
217 | 224 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
218 | 101 | return next(complete); |
219 | } | |
220 | ||
221 | 123 | next(function(err, ret) { |
222 | 123 | if (ret && context.fileConfig.pixelDensity !== 1) { |
223 | 40 | ret.path += '@' + context.fileConfig.pixelDensity + 'x'; |
224 | } | |
225 | 123 | complete(err, ret); |
226 | }); | |
227 | }, | |
228 | ||
229 | module: function(moduleContext, next, complete) { | |
230 | 194 | next(function(err) { |
231 | /*jshint eqnull: true */ | |
232 | 194 | if (err) { |
233 | 0 | return complete(err); |
234 | } | |
235 | ||
236 | 194 | function mergeResources(start) { |
237 | 49 | var generator = function(context, callback) { |
238 | 49 | function response(data, density) { |
239 | 49 | if (data) { |
240 | 48 | return { |
241 | data: data.data[density || 1], | |
242 | inputs: data.inputs, | |
243 | noSeparator: true | |
244 | }; | |
245 | } | |
246 | } | |
247 | ||
248 | 49 | var filename = generator.filename; |
249 | ||
250 | // We only want to call stylus once which will generate the css for all of the | |
251 | // resolutions we support on this platform. This ugly bit of code make sure that | |
252 | // we properly handle all of that loading states that can come into play under these | |
253 | // circumstances while still adhering to the output models prescribed by lumbar. | |
254 | 49 | var queue = context.modeCache['stylus_' + filename]; |
255 | 49 | if (_.isArray(queue)) { |
256 | // We are currently executing | |
257 | 18 | queue.push({density: context.fileConfig.pixelDensity, callback: callback}); |
258 | 31 | } else if (_.isObject(queue)) { |
259 | // We already have data | |
260 | 0 | callback(undefined, response(queue, context.fileConfig.pixelDensity)); |
261 | } else { | |
262 | // We need to kick of a stylus build | |
263 | 31 | queue = context.modeCache['stylus_' + filename] = [ |
264 | {density: context.fileConfig.pixelDensity, callback: callback} | |
265 | ]; | |
266 | 31 | var options = { |
267 | filename: filename, | |
268 | files: generator.stylusFiles, | |
269 | ||
270 | context: context, | |
271 | module: moduleContext.module, // To play nicely with combined mode | |
272 | plugins: generator.plugins | |
273 | }; | |
274 | 31 | compile(options, function(err, data) { |
275 | 31 | if (err) { |
276 | 1 | data = undefined; |
277 | } | |
278 | 31 | _.each(queue, function(callback) { |
279 | 49 | callback.callback(err, response(data, callback.density)); |
280 | }); | |
281 | 31 | context.modeCache['stylus_' + filename] = data; |
282 | }); | |
283 | } | |
284 | }; | |
285 | 49 | generator.stylusFiles = resources.splice(start, rangeEnd - start + 1); |
286 | 133 | generator.filename = 'stylus_' + _.map(generator.stylusFiles, function(file) { return file.originalSrc || file.src; }).join(';'); |
287 | 49 | generator.style = true; |
288 | 49 | generator.stylus = true; |
289 | 49 | generator.plugins = []; |
290 | ||
291 | 49 | resources.splice(start, 0, generator); |
292 | 49 | rangeEnd = undefined; |
293 | } | |
294 | ||
295 | // Merge all consequtive stylus files together | |
296 | 194 | var resources = moduleContext.moduleResources, |
297 | len = resources.length, | |
298 | rangeEnd; | |
299 | 194 | while (len--) { |
300 | 409 | var resource = resources[len]; |
301 | ||
302 | 409 | if (/\.styl$/.test(resource.src)) { |
303 | 84 | if (!rangeEnd) { |
304 | 49 | rangeEnd = len; |
305 | } | |
306 | 325 | } else if (rangeEnd) { |
307 | 3 | mergeResources(len + 1); |
308 | } | |
309 | } | |
310 | 194 | if (rangeEnd != null) { |
311 | 46 | mergeResources(0); |
312 | } | |
313 | 194 | complete(); |
314 | }); | |
315 | } | |
316 | }; | |
317 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Template Plugin : Includes templates associated with a given file when said file is imported. | |
3 | * | |
4 | * Config: | |
5 | * root: | |
6 | * templates: | |
7 | * Key value hash mapping file names to arrays of templates to include | |
8 | * | |
9 | * Special Values: | |
10 | * auto-include: Key value pair mapping a regular expression key to a series of values | |
11 | * to insert. Matching groups in the regular expression may be replaced using $i notation. | |
12 | * | |
13 | * Example: 'js/views/(.*)\\.js': ['templates/$1.handlebars'] | |
14 | * | |
15 | * Mixins: | |
16 | * The template plugin will mixin auto-include mappings per item, giving priority to the local version. | |
17 | * File mappings will be mixed in but are executed within the scope of the mixin only. I.e. foo.js | |
18 | * in the local file will not match file mappings for foo.js in a mixin. | |
19 | * | |
20 | */ | |
21 | 1 | var _ = require('underscore'), |
22 | build = require('../build'), | |
23 | path = require('path'); | |
24 | ||
25 | 1 | module.exports = { |
26 | mode: 'scripts', | |
27 | priority: 50, | |
28 | ||
29 | loadMixin: function(context, next, complete) { | |
30 | 63 | var mixinTemplates = context.loadedLibrary.templates; |
31 | 63 | if (mixinTemplates) { |
32 | 8 | var templates = context.libraries.originalConfig.templates || {}, |
33 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
34 | assigned = false; | |
35 | ||
36 | 8 | if (context.libraries.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) { |
37 | 3 | assigned = true; |
38 | } | |
39 | ||
40 | 8 | if (assigned) { |
41 | 3 | context.config.attributes.templates = configTemplates; |
42 | } | |
43 | } | |
44 | 63 | next(complete); |
45 | }, | |
46 | ||
47 | resourceList: function(context, next, complete) { | |
48 | 306 | var library = context.resource.library, |
49 | attr = (library && library.parent) || context.config.attributes; | |
50 | ||
51 | 306 | next(function(err, ret) { |
52 | 306 | if (err || !ret) { |
53 | 0 | return complete(err); |
54 | } | |
55 | ||
56 | 306 | function pushTemplates(templates) { |
57 | 271 | _.each(templates, function(template) { |
58 | 40 | var src = template.src; |
59 | 40 | if (!src || (template.library && !template.library.attributes)) { |
60 | 30 | var templateMixin = template.library ? context.libraries.getConfig(template.library) : library; |
61 | 30 | src = context.libraries.resolvePath(template.src || template, templateMixin); |
62 | } | |
63 | ||
64 | 40 | ret.push({ |
65 | src: src, | |
66 | name: template.name || template.src || template, | |
67 | library: template.library || library, | |
68 | template: true | |
69 | }); | |
70 | }); | |
71 | } | |
72 | ||
73 | 306 | var views = attr.templates || attr.views || {}, |
74 | resource = context.resource.originalSrc || context.resource.src || context.resource, | |
75 | mixinRoot = (context.resource.library && context.resource.library.root) || ''; | |
76 | 306 | if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) { |
77 | 232 | resource = resource.substring(mixinRoot.length); |
78 | } | |
79 | ||
80 | 306 | var deferComplete; |
81 | 306 | if (build.filterResource(context.resource, context)) { |
82 | 262 | pushTemplates(views[resource]); |
83 | ||
84 | 262 | if (views['auto-include']) { |
85 | 10 | var config = context.configCache['template-auto-include']; |
86 | 10 | if (!config) { |
87 | 5 | config = module.exports.generateMappings(views['auto-include']); |
88 | 5 | context.configCache['template-auto-include'] = config; |
89 | } | |
90 | ||
91 | 10 | var autoIncludes = module.exports.autoIncludes(resource, config, context); |
92 | 10 | if (autoIncludes.length) { |
93 | 9 | deferComplete = true; |
94 | ||
95 | 9 | context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) { |
96 | 9 | if (err) { |
97 | 0 | return complete(err); |
98 | } | |
99 | ||
100 | 9 | var watchDirs = []; |
101 | 9 | autoIncludes = _.filter(autoIncludes, function(file) { |
102 | 13 | if (file.enoent) { |
103 | 3 | watchDirs.push({watch: path.dirname(file.src)}); |
104 | } else { | |
105 | 10 | return true; |
106 | } | |
107 | }); | |
108 | ||
109 | 9 | pushTemplates(autoIncludes); |
110 | 9 | ret.push.apply(ret, watchDirs); |
111 | ||
112 | 9 | complete(undefined, ret); |
113 | }); | |
114 | } | |
115 | } | |
116 | } | |
117 | 306 | if (!deferComplete) { |
118 | 297 | complete(undefined, ret); |
119 | } | |
120 | }); | |
121 | }, | |
122 | ||
123 | resource: function(context, next, complete) { | |
124 | 220 | var resource = context.resource; |
125 | ||
126 | 220 | if (resource.watch) { |
127 | 2 | function generator(buildContext, callback) { |
128 | // Ensure that the directory actually exists | |
129 | 2 | var path = context.fileUtil.resolvePath(resource.watch); |
130 | 2 | context.fileUtil.stat(path, function(err, stat) { |
131 | // Ignore any errors here | |
132 | 2 | var inputs = []; |
133 | 2 | if (stat && stat.isDirectory()) { |
134 | 2 | inputs.push(path); |
135 | } | |
136 | 2 | callback(undefined, {inputs: inputs, data: '', noSeparator: true}); |
137 | }); | |
138 | } | |
139 | 2 | complete(undefined, generator); |
140 | } else { | |
141 | 218 | next(complete); |
142 | } | |
143 | }, | |
144 | ||
145 | autoIncludes: function(resource, config, context) { | |
146 | 10 | var autoIncludes = []; |
147 | 10 | _.each(config, function(mapping) { |
148 | 10 | var remap = module.exports.remapFile(mapping, resource, context); |
149 | 10 | if (remap) { |
150 | 9 | autoIncludes.push.apply(autoIncludes, remap); |
151 | } | |
152 | }); | |
153 | 10 | return autoIncludes; |
154 | }, | |
155 | generateMappings: function(autoInclude) { | |
156 | 5 | return _.map(autoInclude, function(templates, source) { |
157 | 5 | if (!_.isArray(templates)) { |
158 | 2 | templates = [templates]; |
159 | } | |
160 | 5 | return {regex: new RegExp(source), templates: templates}; |
161 | }); | |
162 | }, | |
163 | remapFile: function(mapping, resource, context) { | |
164 | /*jshint boss:true */ | |
165 | 13 | var match; |
166 | 13 | if (match = mapping.regex.exec(resource)) { |
167 | 11 | return _.map(mapping.templates, function(template) { |
168 | // Work in reverse so $10 takes priority over $1 | |
169 | 21 | var i = match.length; |
170 | 21 | while (i--) { |
171 | 46 | template = template.replace('$' + i, match[i]); |
172 | } | |
173 | 21 | return {name: template, src: context.libraries.resolvePath(template, template.library || context.resource.library)}; |
174 | }); | |
175 | } | |
176 | } | |
177 | }; | |
178 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | cheerio = require('cheerio'), | |
4 | path = require('path'), | |
5 | basename = path.basename, | |
6 | dirname = path.dirname, | |
7 | pathRelative = require('../path-relative'); // Shim for 0.4 support | |
8 | ||
9 | 1 | module.exports = { |
10 | mode: 'static', | |
11 | priority: 50, | |
12 | ||
13 | updateHtmlReferences: function(context, content, callback) { | |
14 | 15 | function updateResources(mode, query, create) { |
15 | 30 | return function(callback) { |
16 | 29 | async.forEach($(query), function(el, callback) { |
17 | 10 | el = $(el); |
18 | 10 | var module = (el.attr('src') || el.attr('href')).replace(/^module:/, ''); |
19 | 10 | context.fileNamesForModule(mode, module, function(err, fileNames) { |
20 | 10 | if (err) { |
21 | 2 | return callback(err); |
22 | } | |
23 | ||
24 | // Generate replacement elements for each of the entries | |
25 | 8 | var content = fileNames.map(function(fileName) { |
26 | 8 | if (fileName.server) { |
27 | 0 | return ''; |
28 | } | |
29 | 8 | return create(loadDirName + basename(fileName.fileName.path) + '.' + fileName.fileName.extension); |
30 | }); | |
31 | ||
32 | // Output and kill the original | |
33 | 8 | el.replaceWith(content.join('')); |
34 | ||
35 | 8 | callback(); |
36 | }); | |
37 | }, | |
38 | callback); | |
39 | }; | |
40 | } | |
41 | 15 | var $ = cheerio.load(content), |
42 | loadDirName = ''; | |
43 | 15 | async.series([ |
44 | function(callback) { | |
45 | // Output the load prefix script we we have a module: script reference | |
46 | 15 | var firstScript = $('script[src^="module:"]'); |
47 | 15 | if (firstScript) { |
48 | 15 | context.plugins.get('module-map').buildMap(context, function(err, map, loadPrefix) { |
49 | 15 | if (err) { |
50 | 0 | return callback(err); |
51 | } | |
52 | ||
53 | 15 | var noFileComponent = !loadPrefix; |
54 | 15 | loadPrefix = context.platformPath + loadPrefix; |
55 | 15 | var dirname = path.dirname(loadPrefix + 'a'); // Force a component for the trailing '/' case |
56 | ||
57 | // Only remap load prefix if not defined by the user | |
58 | 15 | if (!(loadDirName = context.config.loadPrefix())) { |
59 | 14 | var resourcePath = path.dirname(context.fileName.substring(context.outdir.length + 1)); |
60 | 14 | loadPrefix = pathRelative.relative(resourcePath, loadPrefix); |
61 | 14 | loadDirName = pathRelative.relative(resourcePath, dirname); |
62 | ||
63 | 14 | if (loadDirName) { |
64 | 7 | loadDirName += '/'; |
65 | } | |
66 | 14 | if (loadPrefix && noFileComponent) { |
67 | 7 | loadPrefix += '/'; |
68 | } | |
69 | } else { | |
70 | // A load prefix was given, just combine this with the module map prefix | |
71 | 1 | loadPrefix = loadDirName + loadPrefix; |
72 | 1 | if (dirname !== '.') { |
73 | 1 | loadDirName += dirname + '/'; |
74 | } | |
75 | } | |
76 | ||
77 | 15 | var script = '<script type="text/javascript">var lumbarLoadPrefix = \'' + loadPrefix + '\';</script>'; |
78 | 15 | firstScript.before(script); |
79 | 15 | callback(); |
80 | }); | |
81 | } else { | |
82 | 0 | callback(); |
83 | } | |
84 | }, | |
85 | updateResources('scripts', 'script[src^="module:"]', function(href) { | |
86 | 7 | return '<script type="text/javascript" src="' + href + '"></script>'; |
87 | }), | |
88 | updateResources('styles', 'link[href^="module:"]', function(href) { | |
89 | 1 | return '<link rel="stylesheet" type="text/css" href="' + href + '"/>'; |
90 | }) | |
91 | ], | |
92 | function(err) { | |
93 | 15 | callback(err, $.html()); |
94 | }); | |
95 | }, | |
96 | ||
97 | resource: function(context, next, complete) { | |
98 | 28 | var resource = context.resource; |
99 | 28 | if (resource['update-externals'] || (/\.html?$/.test(resource.src) && resource['update-externals'] !== false)) { |
100 | 6 | next(function(err, resource) { |
101 | 6 | function generator(context, callback) { |
102 | // Load the source data | |
103 | 6 | context.loadResource(resource, function(err, file) { |
104 | 6 | if (err) { |
105 | 0 | return complete(err); |
106 | } | |
107 | ||
108 | // Update the content | |
109 | 6 | module.exports.updateHtmlReferences(context, file.content, function(err, data) { |
110 | 6 | callback(err, { |
111 | data: data, | |
112 | inputs: file.inputs | |
113 | }); | |
114 | }); | |
115 | }); | |
116 | } | |
117 | ||
118 | // Include any attributes that may have been defined on the base entry | |
119 | 6 | if (!_.isString(resource)) { |
120 | 6 | _.extend(generator, resource); |
121 | } | |
122 | 6 | complete(undefined, generator); |
123 | }); | |
124 | } else { | |
125 | 22 | next(complete); |
126 | } | |
127 | } | |
128 | }; | |
129 |
Line | Hits | Source |
---|---|---|
1 | 1 | const ESCAPER_LUT = { |
2 | '\b': '\\b', | |
3 | '\f': '\\f', | |
4 | '\n': '\\n', | |
5 | '\r': '\\r', | |
6 | '\t': '\\t', | |
7 | '\v': '\\v', | |
8 | '\'': '\\\'', | |
9 | '\"': '\\\"', | |
10 | '\\': '\\\\' | |
11 | }; | |
12 | 1 | const ESCAPER = /[\b\f\n\r\t\v\'\"\\]/g; |
13 | ||
14 | 1 | exports.escapeJsString = function(string) { |
15 | // TODO : Handle unicode escapes | |
16 | 62 | return string.replace(ESCAPER, function(c) { return ESCAPER_LUT[c] || c; }); |
17 | }; | |
18 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | fu = require('../fileUtil'), | |
4 | path = require('path'), | |
5 | dirname = path.dirname, | |
6 | basename = path.basename, | |
7 | sourceMap, | |
8 | SourceMapConsumer, | |
9 | SourceMapGenerator; | |
10 | ||
11 | 1 | try { |
12 | 1 | sourceMap = require('source-map'); |
13 | 1 | SourceMapConsumer = sourceMap.SourceMapConsumer; |
14 | 1 | SourceMapGenerator = sourceMap.SourceMapGenerator; |
15 | } catch (err) { | |
16 | /* NOP */ | |
17 | } | |
18 | ||
19 | 1 | const WARNING_CONTEXT = 3, |
20 | GENERATED = '<generated>'; | |
21 | ||
22 | 1 | module.exports = exports = function(output) { |
23 | 106 | this.output = output; |
24 | 106 | if (SourceMapGenerator) { |
25 | 106 | this.generator = new SourceMapGenerator({file: output}); |
26 | } | |
27 | ||
28 | 106 | this.contentCache = {}; |
29 | 106 | this.line = 1; |
30 | 106 | this.column = 1; |
31 | 106 | this._content = ''; |
32 | }; | |
33 | ||
34 | 1 | exports.prototype.add = function(name, content, context) { |
35 | 483 | this._sourceMap = ''; |
36 | 483 | this._consumer = undefined; |
37 | ||
38 | 483 | var lines = content.split('\n'); |
39 | 483 | if (name) { |
40 | 178 | this.contentCache[name] = { |
41 | lines: lines, | |
42 | context: context | |
43 | }; | |
44 | } | |
45 | ||
46 | 483 | if (this.generator) { |
47 | 483 | _.each(lines, function(line, index) { |
48 | 2750 | this.generator.addMapping({ |
49 | source: name || GENERATED, | |
50 | generated: { | |
51 | line: this.line + index, | |
52 | column: index ? 1 : this.column | |
53 | }, | |
54 | original: { | |
55 | line: index + 1, | |
56 | column: 1 | |
57 | } | |
58 | }); | |
59 | }, this); | |
60 | } | |
61 | ||
62 | 483 | this.line += lines.length - 1; |
63 | 483 | if (lines.length >= 2) { |
64 | 461 | this.column = 1; |
65 | } | |
66 | 483 | this.column += lines[lines.length - 1].length; |
67 | ||
68 | 483 | this._content += content; |
69 | }; | |
70 | 1 | exports.prototype.content = function() { |
71 | 271 | return this._content; |
72 | }; | |
73 | 1 | exports.prototype.sourceMap = function() { |
74 | 0 | this._sourceMap = this._sourceMap || this.generator.toString(); |
75 | 0 | return this._sourceMap; |
76 | }; | |
77 | ||
78 | 1 | exports.prototype.sourceMapToken = function() { |
79 | 0 | return '//@ sourceMappingURL=' + basename(this.output) + '.map\n'; |
80 | }; | |
81 | ||
82 | 1 | exports.prototype.writeSourceMap = function(options) { |
83 | 0 | var tasks = [], |
84 | outputDir = dirname(this.output) + '/', | |
85 | self = this; | |
86 | ||
87 | 0 | tasks.push(function(callback) { |
88 | 0 | fu.writeFile((options.mapDestination || self.output) + '.map', self.sourceMap(), callback); |
89 | }); | |
90 | 0 | if (options.outputSource) { |
91 | 0 | _.each(this.contentCache, function(content, name) { |
92 | 0 | tasks.push(function(callback) { |
93 | 0 | var file = outputDir + name; |
94 | 0 | fu.ensureDirs(dirname(file), function(err) { |
95 | 0 | if (err) { |
96 | 0 | return callback(err); |
97 | } | |
98 | 0 | fu.writeFile(file, content.lines.join('\n'), callback); |
99 | }); | |
100 | }); | |
101 | }); | |
102 | } | |
103 | ||
104 | 0 | async.parallel(tasks, function(err) { |
105 | 0 | if (err) { |
106 | 0 | throw err; |
107 | } | |
108 | ||
109 | 0 | self.add(undefined, self.sourceMapToken()); |
110 | 0 | options.callback(); |
111 | }); | |
112 | }; | |
113 | ||
114 | 1 | exports.prototype.context = function(line, column) { |
115 | 0 | if (!SourceMapConsumer) { |
116 | 0 | return { |
117 | file: this.output, | |
118 | line: line, | |
119 | column: column | |
120 | }; | |
121 | } | |
122 | ||
123 | 0 | this._consumer = this._consumer || new SourceMapConsumer(this.sourceMap()); |
124 | 0 | var original = this._consumer.originalPositionFor({line: line, column: column}), |
125 | lines; | |
126 | ||
127 | 0 | var content = this.contentCache[original.source]; |
128 | 0 | if (content) { |
129 | 0 | var lines = content.lines, |
130 | line = original.line - 1, | |
131 | start = Math.max(line - WARNING_CONTEXT + 1, 0), | |
132 | end = Math.min(line + WARNING_CONTEXT, lines.length), | |
133 | gutterWidth = (end + '').length; | |
134 | 0 | line = line + 1; |
135 | ||
136 | 0 | lines = lines.slice(start, end).map(function(value, index) { |
137 | 0 | var lineNum = start + index + 1, |
138 | lineText = lineNum + '', | |
139 | buffer = ''; | |
140 | 0 | for (var i = lineText.length; i < gutterWidth; i++) { |
141 | 0 | buffer += ' '; |
142 | } | |
143 | 0 | buffer += lineText; |
144 | 0 | buffer += (lineNum === line) ? ': ' : ' '; |
145 | 0 | buffer += value; |
146 | 0 | return buffer; |
147 | }); | |
148 | } else { | |
149 | 0 | return; |
150 | } | |
151 | ||
152 | 0 | return { |
153 | file: original.source, | |
154 | fileContext: content.context, | |
155 | line: original.line, | |
156 | column: original.column, | |
157 | context: lines | |
158 | }; | |
159 | }; | |
160 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | EventEmitter = require('events').EventEmitter, | |
3 | fu = require('./fileUtil'), | |
4 | path = require('path'), | |
5 | watcher = require('./watcher'); | |
6 | ||
7 | 1 | function WatchManager() { |
8 | 27 | EventEmitter.call(this); |
9 | ||
10 | 27 | this.reset(); |
11 | ||
12 | 27 | this._exec = this.setupExec(); |
13 | } | |
14 | ||
15 | 1 | WatchManager.prototype = { |
16 | configFile: function(path, mixins, callback) { | |
17 | 16 | if (_.isFunction(mixins)) { |
18 | 0 | callback = mixins; |
19 | 0 | mixins = undefined; |
20 | } | |
21 | ||
22 | 16 | var self = this; |
23 | 16 | watcher.watchFile(path, mixins || [], function() { |
24 | 4 | self.emit('watch-change', {fileName: path, config: true}); |
25 | ||
26 | 4 | self.pushChange({callback: callback, fileName: path, config: true}); |
27 | }); | |
28 | }, | |
29 | moduleOutput: function(status, callback) { | |
30 | 72 | var self = this; |
31 | ||
32 | 72 | function theWatcher(type, filename, sourceChange) { |
33 | 36 | self.emit('watch-change', {fileName: sourceChange, output: status.fileName}); |
34 | 36 | self.pushChange({ |
35 | callback: callback, | |
36 | type: type, | |
37 | fileName: status.fileName, | |
38 | sourceChange: sourceChange | |
39 | }); | |
40 | } | |
41 | ||
42 | 336 | var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); }), |
43 | removed = _.difference(this.watching[status.fileName], input); | |
44 | ||
45 | 72 | if (removed.length) { |
46 | 0 | watcher.unwatch(status.fileName, removed); |
47 | } | |
48 | ||
49 | 72 | watcher.watchFile({ virtual: status.fileName }, input, theWatcher); |
50 | 72 | this.watching[status.fileName] = input; |
51 | }, | |
52 | ||
53 | ||
54 | setupExec: function() { | |
55 | 11 | return _.debounce(_.bind(this.flushQueue, this), 500); |
56 | }, | |
57 | flushQueue: function() { | |
58 | 21 | if (this.queue.length) { |
59 | 21 | _.each(this.queue, function(change) { |
60 | 38 | change.callback(); |
61 | }); | |
62 | 21 | this.queue = []; |
63 | } | |
64 | }, | |
65 | ||
66 | reset: function() { | |
67 | // Cleanup what we can, breaking things along the way | |
68 | // WARN: This prevents concurrent execution within the same process. | |
69 | 48 | watcher.unwatchAll(); |
70 | ||
71 | 48 | this.watching = {}; |
72 | 48 | this.queue = []; |
73 | }, | |
74 | pushChange: function(change) { | |
75 | 57 | fu.resetCache(change.sourceChange); |
76 | 57 | if (change.type === 'remove' && change.sourceChange) { |
77 | 3 | fu.resetCache(path.dirname(change.sourceChange)); |
78 | } | |
79 | ||
80 | 57 | if (_.find(this.queue, function(existing) { |
81 | 44 | return existing.config || (change.fileName && (existing.fileName === change.fileName)); |
82 | })) { | |
83 | // If we have a pending config change or changes to the same file that has not started then | |
84 | // we can ignore subsequent changes | |
85 | 7 | return; |
86 | } | |
87 | ||
88 | 50 | if (change.config) { |
89 | 10 | this.reset(); |
90 | } | |
91 | ||
92 | 50 | this.queue.push(change); |
93 | 50 | this._exec(); |
94 | } | |
95 | }; | |
96 | ||
97 | 1 | WatchManager.prototype.__proto__ = EventEmitter.prototype; |
98 | ||
99 | 1 | exports = module.exports = WatchManager; |
100 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Adds dependency watching to the core fs.watchFile implementation. | |
3 | */ | |
4 | 1 | var _ = require('underscore'), |
5 | fs = require('fs'); | |
6 | ||
7 | 1 | var watchedFiles = {}; |
8 | ||
9 | 1 | function notifyWatch(filename, type, sourceChange, trigger) { |
10 | 75 | var watchInfo = watchedFiles[filename]; |
11 | 75 | if (watchInfo) { |
12 | 72 | var inQueue = _.find(watchInfo.queue, function(entry) { |
13 | 0 | return entry.type === type |
14 | && entry.filename === filename | |
15 | && entry.sourceChange === sourceChange; | |
16 | }); | |
17 | ||
18 | 72 | if (!inQueue) { |
19 | 72 | var entry = {type: type, filename: filename, sourceChange: sourceChange}; |
20 | 72 | watchInfo.queue.push(entry); |
21 | ||
22 | 72 | function exec() { |
23 | 72 | watchInfo.queue = _.without(watchInfo.queue, entry); |
24 | ||
25 | 72 | if (watchInfo.callback) { |
26 | 49 | watchInfo.callback(type, filename, sourceChange); |
27 | } | |
28 | 72 | watchInfo.parents.forEach(function(parent) { |
29 | 40 | notifyWatch(parent, type, sourceChange, trigger); |
30 | }); | |
31 | } | |
32 | ||
33 | 72 | if (trigger) { |
34 | 66 | exec(); |
35 | } else { | |
36 | // Debounce so we don't output multiple instances of the same event on platforms | |
37 | // such as linux that may send multiple events on write, etc. | |
38 | 6 | _.defer(exec, 200); |
39 | } | |
40 | } | |
41 | } | |
42 | } | |
43 | ||
44 | 1 | function watchFile(filename, callback, parent) { |
45 | 176 | var watchInfo = { |
46 | callback: callback, | |
47 | parents: [], | |
48 | queue: [] | |
49 | }; | |
50 | 176 | if (parent) { |
51 | 101 | watchInfo.parents.push(parent); |
52 | } | |
53 | 176 | watchedFiles[filename.virtual || filename] = watchInfo; |
54 | ||
55 | 176 | if (!filename.virtual) { |
56 | 6 | var hasRetried; |
57 | ||
58 | 6 | (function watch(ignoreError) { |
59 | 7 | try { |
60 | 7 | var oldStat = fs.statSync(filename), |
61 | lastType, | |
62 | rewatch; | |
63 | ||
64 | 7 | var changeHandler = _.debounce(function() { |
65 | 3 | if (rewatch) { |
66 | // Attempt to reattach on rename | |
67 | 1 | watchInfo.watch.close(); |
68 | 1 | watch(true); |
69 | } | |
70 | 3 | notifyWatch(filename, lastType, filename); |
71 | }, 1000); | |
72 | ||
73 | 7 | watchInfo.watch = fs.watch(filename, function(type) { |
74 | 6 | try { |
75 | 6 | var newStat = fs.statSync(filename); |
76 | 4 | if (newStat.isDirectory()) { |
77 | 1 | notifyWatch(filename, 'create', filename); |
78 | 3 | } else if (newStat.size !== oldStat.size || newStat.mtime.getTime() > oldStat.mtime.getTime()) { |
79 | 3 | oldStat = newStat; |
80 | 3 | if (type === 'rename') { |
81 | 1 | rewatch = true; |
82 | } | |
83 | 3 | lastType = type; |
84 | 3 | changeHandler(); |
85 | } | |
86 | } catch (err) { | |
87 | 2 | if (err.code === 'ENOENT') { |
88 | // The file was removed by the time we got to it. This could be a case of it actually being removed | |
89 | // or a race condtion with rewriting APIs. | |
90 | 2 | watchInfo.watch.close(); |
91 | ||
92 | // Pause a bit to see if this was a replace that we raced with... | |
93 | 2 | setTimeout(function() { |
94 | 2 | try { |
95 | 2 | fs.statSync(filename); // No exception: file still exists, notify and restart the watch |
96 | 0 | notifyWatch(filename, 'change', filename); |
97 | 0 | watch(true); |
98 | } catch (err) { | |
99 | // The file is really gone... or we just got hit with a race condtion twice. Give up. | |
100 | 2 | notifyWatch(filename, 'remove', filename); |
101 | } | |
102 | }, 500); | |
103 | } else { | |
104 | 0 | throw err; |
105 | } | |
106 | } | |
107 | }); | |
108 | } catch (err) { | |
109 | 0 | if (!hasRetried && err.code === 'EMFILE') { |
110 | 0 | hasRetried = true; |
111 | 0 | setTimeout(function() { |
112 | 0 | watch(ignoreError); |
113 | }, 250); | |
114 | 0 | } else if (!ignoreError) { |
115 | 0 | throw err; |
116 | } | |
117 | } | |
118 | })(); | |
119 | } | |
120 | } | |
121 | ||
122 | 1 | exports.watchFile = function(filename, dependencies, callback) { |
123 | 95 | var watch = watchedFiles[filename.virtual || filename]; |
124 | 95 | if (!watch) { |
125 | // Create a watch on this and all others | |
126 | 75 | watchFile(filename, callback); |
127 | } else { | |
128 | 20 | watch.callback = callback; |
129 | } | |
130 | ||
131 | 95 | filename = filename.virtual || filename; |
132 | ||
133 | 95 | dependencies.forEach(function(depend) { |
134 | 270 | var watch = watchedFiles[depend.virtual || depend]; |
135 | 270 | if (!watch) { |
136 | 101 | watchFile(depend, undefined, filename); |
137 | } else { | |
138 | 169 | if (!_.contains(watch.parents, filename)) { |
139 | 90 | watch.parents.push(filename); |
140 | } | |
141 | } | |
142 | }); | |
143 | }; | |
144 | ||
145 | 1 | exports.trigger = function(type, filename) { |
146 | 29 | notifyWatch(filename, type, filename, true); |
147 | }; | |
148 | ||
149 | 1 | exports.unwatch = function(filename, dependencies) { |
150 | 5 | var watch = watchedFiles[filename.virtual || filename]; |
151 | 5 | if (!watch) { |
152 | 1 | return; |
153 | } | |
154 | ||
155 | // Remove the callback | |
156 | 4 | if (!dependencies) { |
157 | 2 | watch.callback = undefined; |
158 | } | |
159 | ||
160 | // For each dependency remove the parent link | |
161 | 4 | filename = filename.virtual || filename; |
162 | ||
163 | 4 | _.each(dependencies || watchedFiles, function(depend) { |
164 | 6 | var watch = watchedFiles[depend.virtual || depend]; |
165 | 6 | if (!watch) { |
166 | 4 | return; |
167 | } | |
168 | ||
169 | 2 | watch.parents = _.without(watch.parents, filename); |
170 | }); | |
171 | ||
172 | // Kill this watch if it can't trigger or fire | |
173 | 4 | var canTrigger = watch.watch || _.some(watchedFiles, function(watch) { |
174 | 9 | return _.contains(watch.parents, filename); |
175 | }); | |
176 | 4 | if (!watch.callback || !canTrigger) { |
177 | 3 | unwatch(filename); |
178 | } | |
179 | ||
180 | // Kill any other watches that might not be valid anymore | |
181 | 4 | _.each(_.clone(watchedFiles), function(watch, name) { |
182 | 6 | if (!watch.callback && !watch.parents.length) { |
183 | 2 | exports.unwatch(name); |
184 | } | |
185 | }); | |
186 | }; | |
187 | 1 | exports.unwatchAll = function() { |
188 | 56 | _.each(watchedFiles, function(watch, name) { |
189 | 173 | unwatch(name); |
190 | }); | |
191 | }; | |
192 | ||
193 | 1 | function unwatch(name) { |
194 | 176 | watchedFiles[name].callback = undefined; |
195 | 176 | if (watchedFiles[name].watch) { |
196 | 6 | watchedFiles[name].watch.close(); |
197 | } | |
198 | 176 | delete watchedFiles[name]; |
199 | } | |
200 |