[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 | 325 | var plugins = context.plugins; |
17 | ||
18 | 325 | function filterResource(resource) { |
19 | 546 | if (_.isString(resource)) { |
20 | 162 | resource = { src: resource }; |
21 | } | |
22 | ||
23 | 546 | if (exports.filterResource(resource, context)) { |
24 | 465 | return resource; |
25 | } | |
26 | } | |
27 | ||
28 | 325 | plugins.moduleResources(context, function(err, files) { |
29 | 325 | if (err) { |
30 | 1 | return callback(err); |
31 | } | |
32 | ||
33 | 324 | var fileFilter = plugins.fileFilter(context) || /.*/; |
34 | 324 | fu.fileList(files, fileFilter, function(err, files) { |
35 | 324 | if (err) { |
36 | 1 | return callback(err); |
37 | } | |
38 | ||
39 | 323 | async.map(files, function(resource, callback) { |
40 | 496 | var resourceContext = context.clone(); |
41 | 496 | resourceContext.resource = resource; |
42 | 496 | plugins.resourceList(resourceContext, callback); |
43 | }, | |
44 | function(err, resources) { | |
45 | 323 | resources = _.flatten(resources); |
46 | 323 | resources = _.map(resources, filterResource); |
47 | 869 | resources = _.filter(resources, function(resource) { return resource; }); |
48 | 323 | callback(err, resources); |
49 | }); | |
50 | }); | |
51 | }); | |
52 | }; | |
53 | ||
54 | /** | |
55 | * Filters a given resource for platform constraints, if specified. | |
56 | */ | |
57 | 1 | exports.filterResource = function(resource, context) { |
58 | 889 | function check(value, singular, plural) { |
59 | 2419 | if (typeof singular !== 'undefined') { |
60 | 172 | return singular.not ? singular.not !== value : singular === value; |
61 | 2247 | } else if (plural) { |
62 | 73 | var ret = (plural.not || plural).reduce(function(found, filePlatform) { |
63 | 105 | return found || filePlatform === value; |
64 | }, false); | |
65 | 73 | return plural.not ? !ret : ret; |
66 | } | |
67 | 2174 | return true; |
68 | } | |
69 | ||
70 | 889 | function checkResource(resource) { |
71 | 893 | return check(context.platform, resource.platform, resource.platforms) |
72 | && check(context.package, resource.package, resource.packages) | |
73 | && check(!!context.combined, resource.combined); | |
74 | } | |
75 | 889 | return checkResource(resource) |
76 | && (!resource.originalResource || checkResource(resource.originalResource)); | |
77 | }; | |
78 | ||
79 | ||
80 | /** | |
81 | * Runs a set of resources through the resource plugin. | |
82 | * | |
83 | * Context state: module | |
84 | * | |
85 | * Plugin Calls: | |
86 | * resource | |
87 | */ | |
88 | 1 | exports.processResources = function(resources, context, callback) { |
89 | 324 | var plugins = context.plugins; |
90 | ||
91 | 324 | async.map(resources, function(resource, callback) { |
92 | 440 | var resourceContext = context.clone(); |
93 | 440 | resourceContext.resource = resource; |
94 | 440 | plugins.resource(resourceContext, function(err, newResource) { |
95 | 440 | if (newResource && newResource !== resource) { |
96 | 98 | newResource.originalResource = resource; |
97 | } | |
98 | ||
99 | 440 | callback(err, newResource); |
100 | }); | |
101 | }, | |
102 | function(err, resources) { | |
103 | 324 | if (err) { |
104 | 1 | return callback(err); |
105 | } | |
106 | ||
107 | 762 | callback(err, resources.filter(function(resource) { return resource; })); |
108 | }); | |
109 | }; | |
110 |
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 | 37 | try { |
11 | 37 | 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 | 37 | 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 | 29 | fu.lookupPath(''); |
40 | ||
41 | 29 | var config = exports.readConfig(lumbarFile); |
42 | 29 | fu.lookupPath(path.dirname(lumbarFile)); |
43 | ||
44 | 29 | return exports.create(config); |
45 | }; | |
46 | ||
47 | 1 | exports.create = function(config) { |
48 | 168 | var packageList, moduleList; |
49 | ||
50 | 168 | function loadPackageList() { |
51 | 168 | if (!config.packages) { |
52 | 128 | config.packages = { web: { name: '' } }; |
53 | } | |
54 | ||
55 | 168 | packageList = _.keys(config.packages); |
56 | } | |
57 | 168 | function loadModuleList() { |
58 | 168 | if (!config.modules) { |
59 | 1 | throw new Error('No modules object defined: ' + JSON.stringify(config, undefined, 2)); |
60 | } | |
61 | 167 | moduleList = _.keys(config.modules); |
62 | } | |
63 | ||
64 | 168 | loadPackageList(); |
65 | 168 | loadModuleList(); |
66 | ||
67 | 167 | return { |
68 | /** @typedef {Object} The raw lumbar file as a JSON object. */ | |
69 | attributes: config, | |
70 | loadPrefix: function() { | |
71 | 51 | 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 | 30 | 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 | 1868 | if (config && config.packages && config.packages[pkg]) { |
95 | 1593 | return config.packages[pkg].combine; |
96 | } | |
97 | 275 | return false; |
98 | }, | |
99 | platformList: function(pkg) { | |
100 | 103 | if (!pkg) { |
101 | 59 | return config.platforms || ['']; |
102 | } else { | |
103 | 44 | if (config.packages[pkg]) { |
104 | 44 | return config.packages[pkg].platforms || this.platformList(); |
105 | } | |
106 | 0 | return this.platformList(); |
107 | } | |
108 | }, | |
109 | ||
110 | moduleList: function(pkg) { | |
111 | 246 | return (config.packages[pkg] || {}).modules || _.keys(config.modules); |
112 | }, | |
113 | ||
114 | module: function(name) { | |
115 | 493 | var ret = config.modules[name]; |
116 | 493 | if (ret) { |
117 | 490 | ret.name = name; |
118 | } | |
119 | 493 | return ret; |
120 | }, | |
121 | isAppModule: function(module) { | |
122 | 74 | var app = config.application; |
123 | 74 | return (app && app.module) === (module.name || module); |
124 | }, | |
125 | scopedAppModuleName: function(module) { | |
126 | 44 | var app = config.application; |
127 | 44 | if (this.isAppModule(module)) { |
128 | 4 | return 'module.exports'; |
129 | } else { | |
130 | 40 | var app = config.application; |
131 | 40 | return app && app.name; |
132 | } | |
133 | }, | |
134 | ||
135 | routeList: function(module) { | |
136 | 22 | return config.modules[module].routes; |
137 | }, | |
138 | ||
139 | serialize: function() { | |
140 | 2 | function objectClone(object) { |
141 | 19 | var clone = object; |
142 | ||
143 | 19 | if (object && object.serialize) { |
144 | // Allow specialized objects to handle themselves | |
145 | 0 | clone = object.serialize(); |
146 | 19 | } else if (_.isArray(object)) { |
147 | 1 | clone = _.map(object, objectClone); |
148 | 18 | } else if (_.isObject(object)) { |
149 | 12 | clone = {}; |
150 | 12 | _.each(object, function(value, name) { |
151 | 15 | clone[name] = objectClone(value); |
152 | }); | |
153 | } | |
154 | ||
155 | // Collapse simple resources | |
156 | 19 | if (clone.src && _.keys(clone).length === 1) { |
157 | 0 | clone = clone.src; |
158 | } | |
159 | ||
160 | 19 | return clone; |
161 | } | |
162 | ||
163 | 2 | 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 | 2184 | this._package = options.package; |
8 | 2184 | this._platform = options.platform; |
9 | 2184 | this._plugins = plugins; |
10 | 2184 | this.mode = options.mode; |
11 | 2184 | this.module = options.module; |
12 | 2184 | this.fileConfig = options.fileConfig; |
13 | 2184 | this.resource = options.resource; |
14 | 2184 | this.config = config; |
15 | 2184 | this.libraries = libraries || options.libraries; |
16 | 2184 | this.event = event || options.event; |
17 | } | |
18 | 1 | Context.prototype = { |
19 | fileUtil: fu, | |
20 | ||
21 | clone: function(options) { | |
22 | 2020 | var ret = new Context(this, this.config); |
23 | 2020 | ret.parent = this; |
24 | 2020 | var prototype = Object.keys(Context.prototype); |
25 | 2020 | for (var name in this) { |
26 | 62469 | if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) { |
27 | 36209 | ret[name] = this[name]; |
28 | } | |
29 | } | |
30 | 2020 | if (options) { |
31 | 289 | _.extend(ret, options); |
32 | 289 | ret._package = options.package || this._package; |
33 | 289 | ret._platform = options.platform || this._platform; |
34 | } | |
35 | 2020 | 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 | 417 | 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 | 417 | var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src}; |
71 | ||
72 | 417 | function loaded(err, data) { |
73 | /*jshint eqnull: true */ | |
74 | 417 | if (err) { |
75 | 4 | var json = ''; |
76 | 4 | try { |
77 | // Output JSON for the resource... but protect ourselves from a failure masking a failure | |
78 | 4 | resource = _.clone(resource); |
79 | 4 | delete resource.library; |
80 | 4 | delete resource.enoent; |
81 | 4 | json = '\n\t' + JSON.stringify(resource); |
82 | } catch (err) { /* NOP */ } | |
83 | ||
84 | 4 | var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err)); |
85 | 4 | errorWrapper.source = err; |
86 | 4 | errorWrapper.code = err.code; |
87 | 4 | callback(errorWrapper); |
88 | 4 | return; |
89 | } | |
90 | 413 | fileInfo.inputs = data.inputs; |
91 | 413 | fileInfo.generated = data.generated; |
92 | 413 | fileInfo.noSeparator = data.noSeparator; |
93 | 413 | fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings; |
94 | 413 | fileInfo.content = data.data != null ? data.data : data; |
95 | ||
96 | // Ensure that we dump off the stack | |
97 | 413 | _.defer(function() { |
98 | 413 | callback(err, fileInfo); |
99 | }); | |
100 | } | |
101 | ||
102 | 417 | if (typeof resource === 'function') { |
103 | 198 | 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 | 417 | return fileInfo; |
112 | }, | |
113 | ||
114 | outputFile: function(writer, callback) { | |
115 | 144 | var context = this; |
116 | 144 | context.plugins.file(context, function(err) { |
117 | 144 | if (err) { |
118 | 0 | return callback(err); |
119 | } | |
120 | ||
121 | 144 | context.plugins.fileName(context, function(err, fileName) { |
122 | 144 | if (err) { |
123 | 0 | return callback(err); |
124 | } | |
125 | ||
126 | 144 | context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension; |
127 | 144 | context.fileName = context.outdir + '/' + context.buildPath; |
128 | 144 | writer(function(err, data) { |
129 | 144 | data = _.defaults({ |
130 | fileConfig: context.fileConfig, | |
131 | platform: context.platform, | |
132 | package: context.package, | |
133 | mode: context.mode | |
134 | }, data); | |
135 | ||
136 | 144 | if (err) { |
137 | 3 | fs.unlink(context.fileName, function() { /* NOP To Prevent warning */}); |
138 | 3 | data.error = err; |
139 | } | |
140 | 144 | context.event.emit('output', data); |
141 | ||
142 | 144 | context.fileCache = undefined; |
143 | 144 | callback(err, data); |
144 | }); | |
145 | }); | |
146 | }); | |
147 | }, | |
148 | ||
149 | get description() { | |
150 | 542 | var ret = 'package:' + this.package + '_platform:' + this.platform; |
151 | 542 | if (this.mode) { |
152 | 342 | ret += '_mode:' + this.mode; |
153 | } | |
154 | 542 | if (this.fileName) { |
155 | 115 | ret += '_config:' + this.fileName; |
156 | } | |
157 | 542 | if (this.module) { |
158 | 331 | ret += '_module:' + (this.module.name || this.module); |
159 | } | |
160 | 542 | if (this.resource) { |
161 | // TODO : Anything better for this? | |
162 | 19 | ret += '_resource:' + (this.resource.src || this.resource); |
163 | } | |
164 | 542 | return ret; |
165 | }, | |
166 | ||
167 | 1933 | get plugins() { return this._plugins; }, |
168 | ||
169 | 5691 | get package() { return this._package; }, |
170 | 4170 | get platform() { return this._platform; }, |
171 | get platformPath() { | |
172 | 156 | return this.platform ? this.platform + '/' : ''; |
173 | }, | |
174 | ||
175 | get combined() { | |
176 | 1868 | return this.config.combineModules(this.package); |
177 | }, | |
178 | get baseName() { | |
179 | 225 | if (!this.combined) { |
180 | 157 | return this.module.name; |
181 | } else { | |
182 | 68 | return (this.config.attributes.packages[this.package] || {}).name || this.package; |
183 | } | |
184 | }, | |
185 | ||
186 | get resources() { | |
187 | 289 | if (this.parent) { |
188 | 0 | return this.parent.resources; |
189 | } else { | |
190 | 289 | return this._resources; |
191 | } | |
192 | }, | |
193 | set resources(value) { | |
194 | 363 | if (this.parent) { |
195 | 327 | delete this.parent; |
196 | } | |
197 | 363 | this._resources = value; |
198 | } | |
199 | }; | |
200 | ||
201 | 1 | module.exports = Context; |
202 |
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 | 350 | path = exports.resolvePath(path); |
15 | ||
16 | 350 | var cache = fileCache[path]; |
17 | 350 | if (cache) { |
18 | 192 | if (cache.data) { |
19 | 144 | callback(undefined, cache); |
20 | } else { | |
21 | 48 | cache.pending.push(callback); |
22 | } | |
23 | 192 | return; |
24 | } | |
25 | ||
26 | 158 | cache = fileCache[path] = { |
27 | pending: [callback], | |
28 | artifacts: {} | |
29 | }; | |
30 | ||
31 | 158 | exec(path, function _callback(err, data) { |
32 | 158 | if (err && err.code === 'EMFILE') { |
33 | 0 | setTimeout(exec.bind(this, path, _callback), EMFILE_RETRY); |
34 | } else { | |
35 | 158 | if (err) { |
36 | 2 | delete fileCache[path]; |
37 | } | |
38 | ||
39 | 158 | cache.data = data; |
40 | 158 | cache.pending.forEach(function(callback) { |
41 | 206 | callback(err, cache); |
42 | }); | |
43 | 158 | exports.emit('cache:set', path); |
44 | } | |
45 | }); | |
46 | } | |
47 | ||
48 | 1 | exports.resetCache = function(filePath) { |
49 | 253 | filePath = filePath && path.normalize(filePath); |
50 | 253 | exports.emit('cache:reset', filePath); |
51 | ||
52 | 253 | if (filePath) { |
53 | 177 | filePath = exports.resolvePath(filePath); |
54 | 177 | delete fileCache[filePath]; |
55 | } else { | |
56 | 76 | 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 | 1420 | if (lookupPath |
65 | && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1) | |
66 | && pathName.indexOf(lookupPath) !== 0) { | |
67 | 958 | return lookupPath + pathName; |
68 | } else { | |
69 | 462 | return pathName; |
70 | } | |
71 | }; | |
72 | 1 | exports.makeRelative = function(pathName) { |
73 | 581 | if (pathName.indexOf(lookupPath) === 0) { |
74 | 549 | return pathName.substring(lookupPath.length); |
75 | } else { | |
76 | 32 | return pathName; |
77 | } | |
78 | }; | |
79 | ||
80 | 1 | exports.lookupPath = function(pathName) { |
81 | 152 | if (pathName !== undefined) { |
82 | 92 | lookupPath = pathName; |
83 | 92 | if (lookupPath && !/\/$/.test(lookupPath)) { |
84 | 38 | lookupPath += '/'; |
85 | } | |
86 | } | |
87 | 152 | return lookupPath; |
88 | }; | |
89 | ||
90 | 1 | exports.stat = function(file, callback) { |
91 | 808 | fs.stat(file, function(err, stat) { |
92 | 808 | if (err && err.code === 'EMFILE') { |
93 | 0 | setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY); |
94 | } else { | |
95 | 808 | callback(err, stat); |
96 | } | |
97 | }); | |
98 | }; | |
99 | ||
100 | 1 | exports.readFileSync = function(file) { |
101 | 37 | 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 | 59 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
110 | 59 | var artifacts = cache.artifacts; |
111 | 59 | callback(err, {data: cache.data, artifact: artifacts[name]}); |
112 | }); | |
113 | }; | |
114 | 1 | exports.setFileArtifact = function(path, name, artifact) { |
115 | 25 | path = exports.resolvePath(path); |
116 | ||
117 | 25 | var cache = fileCache[path]; |
118 | 25 | if (cache) { |
119 | 25 | cache.artifacts[name] = artifact; |
120 | } | |
121 | }; | |
122 | ||
123 | 1 | exports.readdir = function(dir, callback) { |
124 | 61 | cacheRead(dir, fs.readdir.bind(fs), function(err, cache) { |
125 | 61 | callback(err, cache && cache.data); |
126 | }); | |
127 | }; | |
128 | ||
129 | 1 | exports.ensureDirs = function(pathname, callback) { |
130 | 243 | var dirname = path.dirname(pathname); |
131 | 243 | exports.stat(dirname, function(err) { |
132 | 243 | if (err && err.code === 'ENOENT') { |
133 | // If we don't exist, check to see if our parent exists before trying to create ourselves | |
134 | 42 | exports.ensureDirs(dirname, function() { |
135 | 42 | fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) { |
136 | 42 | 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 | 42 | callback(err && err.code === 'EEXIST' ? undefined : err); |
141 | } | |
142 | }); | |
143 | }); | |
144 | } else { | |
145 | 201 | callback(); |
146 | } | |
147 | }); | |
148 | }; | |
149 | ||
150 | 1 | exports.writeFile = function(file, data, callback) { |
151 | 136 | exports.resetCache(file); |
152 | ||
153 | 136 | exports.ensureDirs(file, function(err) { |
154 | 136 | if (err) { |
155 | 0 | return callback(err); |
156 | } | |
157 | ||
158 | 136 | fs.writeFile(file, data, 'utf8', function _callback(err) { |
159 | 136 | if (err && err.code === 'EMFILE') { |
160 | 0 | setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY); |
161 | } else { | |
162 | 136 | 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 | 866 | if (_.isFunction(extension)) { |
178 | 5 | callback = extension; |
179 | 5 | extension = /.*/; |
180 | } | |
181 | ||
182 | 866 | if (_.isArray(pathname)) { |
183 | 303 | var files = pathname; |
184 | 303 | pathname = ''; |
185 | 303 | if (!files.length) { |
186 | 123 | return callback(undefined, []); |
187 | } | |
188 | 180 | return handleFiles(false, undefined, _.uniq(files)); |
189 | 563 | } else if (!dirList) { |
190 | 402 | if (pathname.src) { |
191 | 0 | resource = resource || pathname; |
192 | 0 | pathname = pathname.src; |
193 | } | |
194 | ||
195 | 402 | pathname = exports.resolvePath(pathname); |
196 | } | |
197 | 563 | if (resource && resource.src) { |
198 | 183 | resource = _.clone(resource); |
199 | 183 | delete resource.src; |
200 | } | |
201 | ||
202 | 563 | function handleFiles(dirname, err, files, srcDir) { |
203 | 238 | if (err) { |
204 | 0 | return callback(err); |
205 | } | |
206 | ||
207 | 238 | var ret = [], |
208 | count = 0, | |
209 | expected = files.length, | |
210 | prefix = pathname ? pathname.replace(/\/$/, '') + '/' : ''; | |
211 | ||
212 | 238 | function complete(files, index) { |
213 | 599 | count++; |
214 | ||
215 | 599 | ret[index] = files; |
216 | ||
217 | 599 | if (count === expected) { |
218 | 237 | ret = _.flatten(ret); |
219 | ||
220 | 237 | if (srcDir) { |
221 | 57 | ret = ret.map(function(file) { |
222 | 124 | if (!file.src && !file.dir) { |
223 | 0 | file = { src: file }; |
224 | } | |
225 | 124 | file.srcDir = srcDir; |
226 | 124 | return file; |
227 | }); | |
228 | } | |
229 | ||
230 | 237 | if (dirname) { |
231 | 57 | ret.push(_.defaults({dir: dirname}, resource)); |
232 | 57 | ret = ret.sort(function(a, b) { |
233 | 241 | return (a.dir || a.src || a).localeCompare(b.dir || b.src || b); |
234 | }); | |
235 | } | |
236 | ||
237 | 237 | callback(undefined, ret); |
238 | } | |
239 | } | |
240 | ||
241 | 238 | if (!files.length) { |
242 | 1 | callback(undefined, []); |
243 | } | |
244 | ||
245 | 238 | files.forEach(function(file, index) { |
246 | 599 | var fileResource = resource; |
247 | 599 | if (file.src) { |
248 | 183 | fileResource = resource || file; |
249 | 183 | file = file.src; |
250 | 416 | } else if (_.isObject(file)) { |
251 | 64 | complete(file, index); |
252 | 64 | return; |
253 | } | |
254 | ||
255 | 535 | exports.fileList(prefix + file, extension, function(err, files) { |
256 | 535 | if (err) { |
257 | 0 | callback(err); |
258 | 0 | return; |
259 | } | |
260 | ||
261 | 535 | complete(files, index); |
262 | }, dirname, fileResource, srcDir); | |
263 | }); | |
264 | } | |
265 | ||
266 | 563 | exports.stat(pathname, function(err, stat) { |
267 | 563 | if (err) { |
268 | 53 | if (err.code === 'ENOENT') { |
269 | 53 | callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]); |
270 | } else { | |
271 | 0 | callback(err); |
272 | } | |
273 | 53 | return; |
274 | } | |
275 | ||
276 | 510 | if (stat.isDirectory()) { |
277 | 58 | exports.readdir(pathname, function(err, files) { |
278 | 58 | var _pathname = exports.makeRelative(pathname); |
279 | 58 | handleFiles(_pathname, undefined, files, srcDir || _pathname); |
280 | }); | |
281 | } else { | |
282 | 452 | pathname = exports.makeRelative(pathname); |
283 | ||
284 | 452 | var basename = path.basename(pathname), |
285 | namePasses = basename[0] !== '.' && basename !== 'vendor' && (!dirList || extension.test(pathname)), | |
286 | ret = []; | |
287 | 452 | if (namePasses) { |
288 | 394 | if (resource) { |
289 | 170 | ret = [ _.defaults({src: pathname, srcDir: srcDir}, resource) ]; |
290 | 224 | } else if (srcDir) { |
291 | 71 | ret = [ { src: pathname, srcDir: srcDir } ]; |
292 | } else { | |
293 | 153 | ret = [ pathname ]; |
294 | } | |
295 | } | |
296 | 452 | 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 | 41 | function compile(templateStr, callback) { |
304 | 30 | try { |
305 | 30 | if (splitOnDelimiter) { |
306 | 19 | callback(null, templateStr.split(splitOnDelimiter).map(function(bit) { |
307 | 38 | return handlebars.compile(bit); |
308 | })); | |
309 | } else { | |
310 | 11 | callback(null, handlebars.compile(templateStr)); |
311 | } | |
312 | } catch (e) { | |
313 | 0 | callback(e); |
314 | } | |
315 | } | |
316 | 41 | 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 | 22 | 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 | 112 | function outputIfCompleted() { |
12 | 372 | if (completed >= files.length) { |
13 | 109 | var lastEl, |
14 | map = new FileMap(output), | |
15 | warnings = [], | |
16 | ||
17 | tasks = []; | |
18 | ||
19 | 109 | _.each(content, function(el) { |
20 | 372 | var content = el.content.toString(); |
21 | ||
22 | 372 | if (!noSeparator && (!lastEl || !lastEl.noSeparator) && map.content()) { |
23 | 114 | map.add(undefined, '\n;;\n'); |
24 | } | |
25 | ||
26 | 372 | map.add(el.name, content, el, el.generated); |
27 | ||
28 | 372 | lastEl = el; |
29 | }, ''); | |
30 | ||
31 | 109 | var inputs = []; |
32 | 109 | content.forEach(function(el) { |
33 | 372 | if (el.inputs) { |
34 | 96 | inputs.push.apply(inputs, el.inputs); |
35 | 276 | } else if (el.name) { |
36 | 178 | inputs.push(el.name); |
37 | } | |
38 | }); | |
39 | 109 | inputs = _.unique(inputs); |
40 | ||
41 | // "Serialize" the data in the map | |
42 | 109 | tasks.push(function(callback) { |
43 | 109 | callback(undefined, map.content()); |
44 | }); | |
45 | ||
46 | // Minimize the content if flagged | |
47 | 109 | 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 | 109 | var sourceMap = context.options.sourceMap; |
95 | 109 | 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 | 109 | tasks.push(function(data, callback) { |
114 | 109 | fu.writeFile(output, data, callback); |
115 | }); | |
116 | ||
117 | // Excute everything and return to the caller | |
118 | 109 | async.waterfall(tasks, function(err) { |
119 | 109 | if (err) { |
120 | 0 | callback(new Error('Combined output "' + output + '" failed\n\t' + err)); |
121 | 0 | return; |
122 | } | |
123 | ||
124 | 109 | callback(undefined, { |
125 | fileName: output, | |
126 | inputs: inputs, | |
127 | warnings: warnings | |
128 | }); | |
129 | }); | |
130 | } | |
131 | } | |
132 | 112 | var completed = 0, |
133 | content = []; | |
134 | ||
135 | 112 | files.forEach(function(resource) { |
136 | 375 | var fileInfo = context.loadResource(resource, function(err) { |
137 | 375 | if (err && callback) { |
138 | 3 | callback(err); |
139 | 3 | callback = undefined; |
140 | 3 | return; |
141 | } | |
142 | ||
143 | 372 | if (callback) { |
144 | 372 | completed++; |
145 | 372 | outputIfCompleted(); |
146 | } | |
147 | }); | |
148 | 375 | content.push(fileInfo); |
149 | }); | |
150 | }; | |
151 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | bower = require('bower'), | |
4 | config = require('./config'), | |
5 | fs = require('fs'), | |
6 | fu = require('./fileUtil'), | |
7 | path = require('path'); | |
8 | ||
9 | 1 | function Libraries(options) { |
10 | 139 | this.options = options; |
11 | 139 | this.mixins = []; |
12 | 139 | this.configs = []; |
13 | } | |
14 | ||
15 | 1 | Libraries.prototype.initialize = function(context, callback) { |
16 | 136 | this.mixins = []; |
17 | 136 | this.originalConfig = _.clone(context.config.attributes); |
18 | ||
19 | 136 | function normalize(libraries) { |
20 | 272 | if (_.isString(libraries)) { |
21 | 2 | return [libraries]; |
22 | } else { | |
23 | 270 | return _.map(libraries, function (name) { |
24 | 69 | if (_.isString(name)) { |
25 | 11 | return path.normalize(name); |
26 | } else { | |
27 | 58 | return name; |
28 | } | |
29 | }); | |
30 | } | |
31 | } | |
32 | ||
33 | 136 | var commandLineLibraries = normalize(this.options.libraries || []), |
34 | configLibraries = normalize(context.config.attributes.libraries || context.config.attributes.mixins || []), | |
35 | bowerLibraries = this.bowerLibraries(context) || [], | |
36 | ||
37 | allLibraries = _.union(commandLineLibraries, configLibraries, bowerLibraries); | |
38 | ||
39 | 136 | delete context.config.attributes.mixins; |
40 | ||
41 | 136 | async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback); |
42 | }; | |
43 | ||
44 | 1 | Libraries.prototype.bowerLibraries = function(context) { |
45 | 137 | try { |
46 | 137 | fs.statSync(fu.resolvePath('bower.json')); |
47 | ||
48 | 2 | var bowerDir = bower.config.directory, |
49 | possibleModules = fs.readdirSync(bowerDir); | |
50 | ||
51 | 1 | return possibleModules |
52 | .map(function(name) { | |
53 | 3 | return path.normalize(path.join(bowerDir, name)); |
54 | }) | |
55 | .filter(function(name) { | |
56 | 3 | try { |
57 | 3 | fs.statSync(path.join(name, 'lumbar.json')); |
58 | 2 | return true; |
59 | } catch (err) { | |
60 | /* NOP */ | |
61 | } | |
62 | }); | |
63 | } catch (err) { | |
64 | 136 | context.event.emit('debug', err); |
65 | } | |
66 | }; | |
67 | ||
68 | 1 | Libraries.prototype.load = function(context, libraryConfig, callback) { |
69 | // Allow mixins to be passed directly | |
70 | 74 | var root = libraryConfig.root, |
71 | configPath, | |
72 | self = this; | |
73 | ||
74 | // Or as a file reference | |
75 | 74 | if (!_.isObject(libraryConfig)) { |
76 | 8 | root = root || libraryConfig; |
77 | ||
78 | // If we have a dir then pull lumbar.json from that | |
79 | 8 | try { |
80 | 8 | var stat = fs.statSync(fu.resolvePath(libraryConfig)); |
81 | 8 | if (stat.isDirectory()) { |
82 | 3 | libraryConfig = libraryConfig + '/lumbar.json'; |
83 | 5 | } else if (root === libraryConfig) { |
84 | // If we are a file the root should be the file's directory unless explicitly passed | |
85 | 5 | root = path.dirname(root); |
86 | } | |
87 | } catch (err) { | |
88 | 0 | return callback(err); |
89 | } | |
90 | ||
91 | 8 | configPath = fu.resolvePath(libraryConfig); |
92 | 8 | libraryConfig = config.readConfig(configPath); |
93 | } | |
94 | ||
95 | // To make things easy force root to be a dir | |
96 | 74 | if (root && !/\/$/.test(root)) { |
97 | 29 | root = root + '/'; |
98 | } | |
99 | ||
100 | 74 | if (!libraryConfig.name) { |
101 | 4 | return callback(new Error('Mixin with root "' + root + '" is missing a name.')); |
102 | } | |
103 | ||
104 | 70 | var mixins = libraryConfig.mixins, |
105 | toRegister = {}; | |
106 | 70 | delete libraryConfig.mixins; |
107 | ||
108 | 70 | function mapMixin(mixin, name) { |
109 | // Only register once, giving priority to an explicitly defined mixin | |
110 | 47 | if (!toRegister[name]) { |
111 | 46 | toRegister[name] = { |
112 | serialize: function() { | |
113 | 0 | return {name: this.name, library: this.parent.name}; |
114 | }, | |
115 | attributes: mixin, | |
116 | parent: libraryConfig, | |
117 | root: root | |
118 | }; | |
119 | } | |
120 | } | |
121 | ||
122 | // Read each of the mixins that are defined in the config | |
123 | 70 | _.each(mixins, mapMixin, this); |
124 | ||
125 | // Make mixin modules accessible as normal mixins as well | |
126 | 70 | _.each(libraryConfig.modules, mapMixin, this); |
127 | ||
128 | // After we've pulled everything in register | |
129 | 70 | _.each(toRegister, function(mixin, name) { |
130 | 46 | this.mixins[name] = this.mixins[name] || []; |
131 | 46 | var list = this.mixins[name]; |
132 | 46 | list.push(mixin); |
133 | }, this); | |
134 | ||
135 | // Run all of the plugins that are concerned with this. | |
136 | 70 | libraryConfig.root = root; |
137 | 70 | libraryConfig.path = configPath; |
138 | 70 | context.loadedLibrary = libraryConfig; |
139 | 70 | context.plugins.loadMixin(context, function(err) { |
140 | 70 | delete libraryConfig.root; |
141 | ||
142 | // And then splat everything else into our config | |
143 | 70 | _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path')); |
144 | ||
145 | 70 | libraryConfig.serialize = function() { |
146 | 0 | return { library: this.name }; |
147 | }; | |
148 | ||
149 | 70 | libraryConfig.root = root; |
150 | 70 | self.configs.push(libraryConfig); |
151 | ||
152 | 70 | callback(err); |
153 | }); | |
154 | }; | |
155 | ||
156 | 1 | Libraries.prototype.findDecl = function(mixins, mixinName) { |
157 | 9 | if (!mixinName.name) { |
158 | 6 | mixinName = {name: mixinName}; |
159 | } | |
160 | ||
161 | 9 | return _.find(mixins, function(mixinDecl) { |
162 | 11 | return (mixinDecl.name || mixinDecl) === mixinName.name |
163 | && (!mixinDecl.library || mixinDecl.library === mixinName.library); | |
164 | }); | |
165 | }; | |
166 | ||
167 | 1 | Libraries.prototype.moduleMixins = function(module) { |
168 | // Perform any nested mixin lookup | |
169 | 296 | var mixins = _.clone(module.mixins || []); |
170 | 296 | for (var i = 0, len = mixins.length; i < len; i++) { |
171 | 64 | var firstInclude = mixins[i], |
172 | mixin = this.getMixin(firstInclude), | |
173 | added = [i, 0]; | |
174 | ||
175 | 62 | if (!mixin) { |
176 | 0 | throw new Error('Unable to find mixin "' + ((firstInclude && firstInclude.name) || firstInclude) + '"'); |
177 | } | |
178 | ||
179 | // Check if we need to include any modules that this defined | |
180 | 62 | _.each(mixin.attributes.mixins, function(mixinInclude) { |
181 | 4 | if (!this.findDecl(mixins, mixinInclude)) { |
182 | 4 | added.push(mixinInclude); |
183 | } | |
184 | }, this); | |
185 | ||
186 | // If we've found any new mixins insert them at the current spot and iterate | |
187 | // over those items | |
188 | 62 | if (added.length > 2) { |
189 | 4 | mixins.splice.apply(mixins, added); |
190 | 4 | i--; |
191 | } | |
192 | } | |
193 | ||
194 | // Extend the module with each of the mixins content, giving priority to the module | |
195 | 294 | return _.map(mixins.reverse(), function(mixin) { |
196 | 62 | var mixinConfig = mixin.name && mixin, |
197 | name = mixin; | |
198 | 62 | if (mixinConfig) { |
199 | 20 | mixinConfig = _.clone(mixinConfig); |
200 | 20 | delete mixinConfig.library; |
201 | 20 | delete mixinConfig.container; |
202 | } | |
203 | 62 | mixin = _.extend( |
204 | {}, | |
205 | this.getMixin(name), | |
206 | mixinConfig); | |
207 | 62 | if (!mixin.attributes) { |
208 | 0 | throw new Error('Mixin "' + (name.name || name) + '" is not defined.'); |
209 | } | |
210 | ||
211 | // Save a distinct instance of the config for resource extension | |
212 | 62 | if (mixinConfig) { |
213 | 20 | mixinConfig = _.clone(mixinConfig); |
214 | 20 | delete mixinConfig.overrides; |
215 | 20 | delete mixinConfig.name; |
216 | } | |
217 | ||
218 | 62 | return { |
219 | library: mixin, | |
220 | mixinConfig: mixinConfig | |
221 | }; | |
222 | }, this); | |
223 | }; | |
224 | ||
225 | 1 | Libraries.prototype.mapFiles = function(value, library, config) { |
226 | 165 | var files = _.map(value, function(resource) { |
227 | 248 | return this.mapFile(resource, library, config); |
228 | }, this); | |
229 | 411 | files = _.filter(files, function(file) { return file; }); |
230 | ||
231 | 164 | return files; |
232 | }; | |
233 | 1 | Libraries.prototype.mapFile = function(resource, library, config) { |
234 | // If explicitly declared the resource library takes precedence | |
235 | 248 | if (_.isString(resource.library || resource.mixin)) { |
236 | 3 | library = this.getConfig(resource.library || resource.mixin); |
237 | 3 | if (!library) { |
238 | 1 | throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found'); |
239 | } | |
240 | 2 | delete resource.mixin; |
241 | } | |
242 | ||
243 | 247 | var bowerPath; |
244 | 247 | if (_.isString(resource.bower)) { |
245 | 2 | bowerPath = path.join(bower.config.directory, resource.bower); |
246 | } | |
247 | ||
248 | // If no mixin was defined on either side then return the identity | |
249 | 247 | if (!library && !bowerPath) { |
250 | 177 | return resource; |
251 | } | |
252 | ||
253 | 70 | if (_.isString(resource)) { |
254 | 57 | resource = {src: resource}; |
255 | } else { | |
256 | 13 | resource = _.clone(resource); |
257 | } | |
258 | ||
259 | 70 | var src = resource.src || resource.dir; |
260 | ||
261 | // Include any config information such as env or platform that may have been | |
262 | // specified on the library settings | |
263 | 70 | _.extend(resource, config); |
264 | ||
265 | 70 | if (src) { |
266 | 68 | var librarySrc = bowerPath || library.root || ''; |
267 | 68 | librarySrc = librarySrc ? path.join(librarySrc, src) : src; |
268 | ||
269 | 68 | var override = library && library.overrides && library.overrides[src]; |
270 | 68 | if (override) { |
271 | 6 | resource.originalSrc = librarySrc; |
272 | 6 | librarySrc = _.isString(override) ? override : src; |
273 | 62 | } else if (override === false) { |
274 | 1 | return; |
275 | } | |
276 | ||
277 | 67 | if (resource.src) { |
278 | 66 | resource.src = librarySrc; |
279 | 1 | } else if (resource.dir) { |
280 | 1 | resource.dir = librarySrc; |
281 | } | |
282 | } | |
283 | ||
284 | 69 | resource.library = library; |
285 | 69 | return resource; |
286 | }; | |
287 | ||
288 | 1 | Libraries.prototype.getMixin = function(name) { |
289 | 126 | var mixins = (this.mixins && this.mixins[name.name || name]) || [], |
290 | library = name.library || name.container; | |
291 | 126 | if (mixins.length > 1 && !library) { |
292 | 1 | throw new Error( |
293 | 'Duplicate mixins found for "' + (name.name || name) + '"' | |
294 | + _.map(mixins, function(mixin) { | |
295 | 2 | return ' parent: "' + mixin.parent.name + '"'; |
296 | }).join('')); | |
297 | } | |
298 | ||
299 | 125 | if (library) { |
300 | 9 | if (name.name === undefined) { |
301 | 0 | var found = _.find(this.configs, function(config) { |
302 | 0 | return config.name === library; |
303 | }); | |
304 | 0 | if (!found) { |
305 | 0 | throw new Error('Unable to find library "' + library + '"'); |
306 | } | |
307 | 0 | return found; |
308 | } | |
309 | ||
310 | 9 | var found = _.find(mixins, function(mixin) { |
311 | 17 | return mixin.parent.name === library; |
312 | }); | |
313 | 9 | if (found) { |
314 | 8 | return found; |
315 | } else { | |
316 | 1 | throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"'); |
317 | } | |
318 | 116 | } else if (mixins.length === 1) { |
319 | 116 | return mixins[0]; |
320 | } | |
321 | }; | |
322 | 1 | Libraries.prototype.getConfig = function(name) { |
323 | 13 | return _.find(this.configs, function(config) { return config.name === name; }); |
324 | }; | |
325 | ||
326 | 1 | Libraries.prototype.resolvePath = function(name, mixin) { |
327 | 46 | if (!mixin) { |
328 | 37 | return name; |
329 | } | |
330 | ||
331 | 9 | var override = mixin.overrides && mixin.overrides[name]; |
332 | 9 | if (override) { |
333 | 2 | return _.isString(override) ? override : name; |
334 | } | |
335 | ||
336 | 7 | return mixin.root + name; |
337 | }; | |
338 | ||
339 | 1 | Libraries.prototype.mergeHash = function(hashName, input, mixin, output) { |
340 | 64 | if (mixin[hashName]) { |
341 | // Close the value to make sure that we are not overriding anything | |
342 | 10 | if (!output[hashName] || output[hashName] === input[hashName]) { |
343 | 8 | output[hashName] = _.clone(input[hashName] || {}); |
344 | } | |
345 | 10 | _.each(mixin[hashName], function(value, key) { |
346 | 16 | if (!input[hashName] || !(key in input[hashName])) { |
347 | 12 | output[hashName][key] = value; |
348 | } | |
349 | }); | |
350 | 10 | return true; |
351 | } | |
352 | }; | |
353 | 1 | Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) { |
354 | 36 | if (mixinData[fieldName]) { |
355 | 8 | mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]]; |
356 | ||
357 | 8 | var configData = input[fieldName] || []; |
358 | 8 | if (!output[fieldName] || configData === output[fieldName]) { |
359 | 6 | output[fieldName] = _.clone(configData); |
360 | } | |
361 | 8 | if (!_.isArray(configData)) { |
362 | 2 | configData = [configData]; |
363 | } | |
364 | 8 | if (!_.isArray(output[fieldName])) { |
365 | 1 | output[fieldName] = [output[fieldName]]; |
366 | } | |
367 | ||
368 | // Insert point is at the start of the upstream list, which we are | |
369 | // assuming occurs at length postions from the end. | |
370 | 8 | _.each(mixinData, function(value) { |
371 | //Make the include relative to the mixin | |
372 | 11 | value = (library.root || '') + value; |
373 | ||
374 | 11 | output[fieldName].splice( |
375 | output[fieldName].length - configData.length, | |
376 | 0, | |
377 | {src: value, library: library}); | |
378 | }); | |
379 | ||
380 | 8 | return true; |
381 | } | |
382 | }; | |
383 | ||
384 | 1 | module.exports = Libraries; |
385 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | ChildPool = require('child-pool'), | |
3 | Context = require('./context'), | |
4 | EventEmitter = require('events').EventEmitter, | |
5 | fs = require('fs'), | |
6 | stateMachine = require('./state-machine'), | |
7 | WatchManager = require('./watch-manager'); | |
8 | ||
9 | 1 | exports.build = require('./build'); |
10 | 1 | exports.fileUtil = require('./fileUtil'); |
11 | 1 | exports.plugin = require('./plugin').plugin; |
12 | 1 | exports.combine = require('./jsCombine').combine; |
13 | 1 | exports.config = require('./config'); |
14 | ||
15 | /** | |
16 | * | |
17 | * @name init | |
18 | * @function This function initializes a Lumbar instance | |
19 | * @param {string} lumbarFile The lumbarFile is the main | |
20 | * file. Its responsible to define all the platforms, | |
21 | * packages, modules, and templates for Lumbar to use. | |
22 | * @param {Object} options supports the following options: | |
23 | * packageConfigFile (string): name of the package config file. | |
24 | * outdir (string): path to directory of where to output the files. | |
25 | * minimize (boolean): Should we minimize the files? | |
26 | * @return {Object.<Function>} | |
27 | */ | |
28 | 1 | exports.init = function(lumbarFile, options) { |
29 | // Clone so we can mutate in the use API | |
30 | 25 | options = _.clone(options || {}); |
31 | 25 | options.plugins = _.clone(options.plugins || []); |
32 | ||
33 | 25 | function logError(err) { |
34 | 57 | if (err) { |
35 | 3 | event.emit('error', err); |
36 | } | |
37 | } | |
38 | ||
39 | 25 | var event = new EventEmitter(), |
40 | watch, | |
41 | watchContext; | |
42 | ||
43 | 25 | function watchOutputHandler(status) { |
44 | 102 | if (!watch) { |
45 | // We've been cleaned up but residuals may still exist, do nothing on this exec | |
46 | 14 | return; |
47 | } | |
48 | ||
49 | 88 | if (status.fileConfig.isPrimary) { |
50 | 35 | delete status.fileConfig; |
51 | 53 | } else if (status.fileConfig.isPrimary === false) { |
52 | // This config is directly linked to another meaning we don't want to watch on it as | |
53 | // it will be rebuilt. | |
54 | 12 | return; |
55 | } | |
56 | ||
57 | 76 | var originalContext = watchContext; |
58 | 76 | watch.moduleOutput(status, function() { |
59 | 35 | if (watchContext !== originalContext) { |
60 | // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch) | |
61 | 0 | return; |
62 | } | |
63 | ||
64 | 35 | stateMachine.buildPlatform(watchContext.clone(status), logError); |
65 | }); | |
66 | } | |
67 | ||
68 | 25 | return _.extend(event, { |
69 | use: function(plugin) { | |
70 | // Only has impact before exec | |
71 | 0 | options.plugins.push(plugin); |
72 | }, | |
73 | /** | |
74 | * | |
75 | * @name build | |
76 | * @function This function builds out the package(s). | |
77 | * @param {string} packageName the name of the package listed under | |
78 | * 'packages' from the lumbarFile passed in during the call to init(). | |
79 | * @param {Function} callback the node process Function | |
80 | */ | |
81 | build: function(packageName, modules, callback) { | |
82 | 11 | stateMachine.loadConfig(lumbarFile, event, options, function(err, context) { |
83 | 11 | if (err) { |
84 | 0 | if (!callback) { |
85 | 0 | throw err; |
86 | } | |
87 | 0 | return callback(err); |
88 | } | |
89 | ||
90 | 11 | stateMachine.buildPackages(context, packageName, modules, callback); |
91 | }); | |
92 | }, | |
93 | watch: function(packageName, modules, callback) { | |
94 | 18 | if (!fs.watch) { |
95 | 0 | throw new Error('Watch requires fs.watch, introduced in Node v0.6.0'); |
96 | } | |
97 | ||
98 | 18 | ChildPool.isBackground(true); |
99 | ||
100 | 18 | watch = new WatchManager(); |
101 | 18 | watch.on('watch-change', function(info) { |
102 | 43 | event.emit('watch-change', info); |
103 | }); | |
104 | ||
105 | 18 | var self = this; |
106 | 18 | stateMachine.loadConfig(lumbarFile, event, options, function(err, context) { |
107 | 18 | if (err) { |
108 | 0 | logError(err); |
109 | } | |
110 | ||
111 | 18 | if (!callback) { |
112 | 18 | callback = modules; |
113 | 18 | modules = undefined; |
114 | } | |
115 | ||
116 | 18 | watchContext = context; |
117 | ||
118 | // Watch for changes in the config file | |
119 | 23 | var mixinPaths = _.filter(_.pluck(context.libraries.configs, 'path'), function(path) { return path; }); |
120 | 18 | watch.configFile(lumbarFile, mixinPaths, function() { |
121 | 4 | watchContext = undefined; |
122 | 4 | self.watch(packageName, callback); |
123 | }); | |
124 | ||
125 | // If we have errored do not exec everything as it could be in an indeterminate state | |
126 | 18 | if (err) { |
127 | 0 | return; |
128 | } | |
129 | ||
130 | // Watch the individual components | |
131 | 18 | event.removeListener('output', watchOutputHandler); |
132 | 18 | event.on('output', watchOutputHandler); |
133 | ||
134 | // Actual build everything | |
135 | 18 | var packages = packageName ? [packageName] : context.config.packageList(); |
136 | 18 | packages.forEach(function(name) { |
137 | 22 | stateMachine.buildPackages(context, name, modules, logError); |
138 | }); | |
139 | }); | |
140 | }, | |
141 | unwatch: function() { | |
142 | 14 | event.removeListener('output', watchOutputHandler); |
143 | 14 | if (watch) { |
144 | 14 | watch.removeAllListeners(); |
145 | 14 | watch.reset(); |
146 | 14 | watch = undefined; |
147 | 14 | watchContext = undefined; |
148 | } | |
149 | } | |
150 | }); | |
151 | }; | |
152 |
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 | 129 | var plugins; |
44 | 129 | var modes; // all registered modes |
45 | 129 | var pluginModes; // map of modes and plugins scoped to the mode |
46 | 129 | var modeAll; // plugins that are scoped to all modes |
47 | ||
48 | 129 | function runPlugins(context, methodName, complete, failOver, noMode) { |
49 | 2982 | var len = 0, |
50 | pluginMode = pluginModes[context.mode] || []; | |
51 | ||
52 | 2982 | return (function next(complete) { |
53 | /*jshint boss:true */ | |
54 | 8379 | var plugin; |
55 | 8379 | while (plugin = plugins[len++]) { |
56 | // if plugin shouldn't work with current mode, go to next | |
57 | 55235 | if (!noMode |
58 | && (!context.mode || pluginMode.indexOf(plugin) < 0) | |
59 | && modeAll.indexOf(plugin) < 0) { | |
60 | 24458 | continue; |
61 | } | |
62 | ||
63 | 30777 | var method = plugin[methodName]; |
64 | 30777 | if (method) { |
65 | 6074 | if (complete) { |
66 | 5938 | process.nextTick(function() { |
67 | 5938 | method.call(plugin, context, next, complete); |
68 | }); | |
69 | 5938 | return; |
70 | } else { | |
71 | 136 | return method.call(plugin, context, next, complete); |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | // We're done, send data back | |
77 | 2305 | if (complete) { |
78 | // async | |
79 | // Clear out our stack under async mode to try to keep the stack somewhat sane. | |
80 | 2118 | process.nextTick(function() { |
81 | 2118 | complete(undefined, failOver && failOver()); |
82 | }); | |
83 | } else { | |
84 | // sync | |
85 | 187 | return failOver && failOver(); |
86 | } | |
87 | })(complete); | |
88 | } | |
89 | ||
90 | 129 | function registerPlugin(plugin) { |
91 | 2407 | var _plugin = globalPlugins[plugin] || plugin; |
92 | ||
93 | 2407 | var mode = _plugin.mode; |
94 | 2407 | if (mode) { |
95 | 2281 | if (_.isString(mode)) { |
96 | 1770 | mode = [mode]; |
97 | } | |
98 | 2281 | _.each(mode, function(_mode) { |
99 | 2792 | if (mode === 'all') { |
100 | // allow plugins to contribute new modes and participate in all modes | |
101 | 0 | modeAll.push(_plugin); |
102 | } else { | |
103 | 2792 | if (modes.indexOf(_mode) < 0) { |
104 | 385 | modes.push(_mode); |
105 | 385 | pluginModes[_mode] = []; |
106 | } | |
107 | 2792 | pluginModes[_mode].push(_plugin); |
108 | } | |
109 | }); | |
110 | } else { | |
111 | 126 | modeAll.push(_plugin); |
112 | } | |
113 | 2407 | plugins.push(_plugin); |
114 | 2407 | plugins.sort(function(a, b) { |
115 | 31923 | return (a.priority || 50) - (b.priority || 50); |
116 | }); | |
117 | } | |
118 | ||
119 | 129 | return { |
120 | get: function(name) { | |
121 | // Find the plugin with this id, if one exists | |
122 | 68 | var plugin = plugins.reduce(function(plugin, left) { |
123 | 1224 | 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 | 68 | if (plugin.id === name) { |
128 | 66 | return plugin; |
129 | } | |
130 | }, | |
131 | use: function(plugin) { | |
132 | 13 | 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 | 13 | registerPlugin(plugin); |
145 | }, | |
146 | ||
147 | initialize: function(config) { | |
148 | // reset | |
149 | 129 | plugins = []; |
150 | 129 | modes = []; // all registered modes |
151 | 129 | pluginModes = {}; // map of modes and plugins scoped to the mode |
152 | 129 | modeAll = []; // plugins that are scoped to all modes |
153 | ||
154 | // load the core plugins | |
155 | 129 | if (!options.ignoreCorePlugins) { |
156 | 126 | corePlugins.forEach(registerPlugin); |
157 | } | |
158 | ||
159 | 129 | var self = this; |
160 | 129 | function plugin(plugins) { |
161 | 258 | if (plugins) { |
162 | 34 | plugins.forEach(self.use, self); |
163 | } | |
164 | } | |
165 | ||
166 | // load command line plugins | |
167 | 129 | plugin(options.plugins); |
168 | ||
169 | // load lumbar.json plugins | |
170 | 129 | plugin(config.attributes.plugins); |
171 | }, | |
172 | ||
173 | loadMixin: function(context, complete) { | |
174 | 70 | runPlugins(context, 'loadMixin', complete, undefined, true); |
175 | }, | |
176 | loadConfig: function(context, complete) { | |
177 | 128 | runPlugins(context, 'loadConfig', complete, undefined, true); |
178 | }, | |
179 | outputConfigs: function(context, complete) { | |
180 | 238 | runPlugins(context, 'outputConfigs', complete, function() { |
181 | // Default to a one to one mapping for a given {platform, package, module, mode} combo | |
182 | 236 | return [ {} ]; |
183 | }); | |
184 | }, | |
185 | modeComplete: function(context, complete) { | |
186 | 184 | runPlugins(context, 'modeComplete', complete); |
187 | }, | |
188 | fileName: function(context, complete) { | |
189 | 251 | runPlugins(context, 'fileName', complete); |
190 | }, | |
191 | ||
192 | fileFilter: function(context) { | |
193 | 323 | return runPlugins(context, 'fileFilter'); |
194 | }, | |
195 | moduleResources: function(context, complete) { | |
196 | 435 | runPlugins(context, 'moduleResources', complete, function() { |
197 | 240 | var module = context.module; |
198 | 240 | return (module[context.mode] || []).slice(); |
199 | }); | |
200 | }, | |
201 | resourceList: function(context, complete) { | |
202 | 992 | runPlugins(context, 'resourceList', complete, function() { return [context.resource]; }); |
203 | }, | |
204 | ||
205 | file: function(context, complete) { | |
206 | 144 | runPlugins(context, 'file', complete); |
207 | }, | |
208 | module: function(context, complete) { | |
209 | 274 | runPlugins(context, 'module', complete); |
210 | }, | |
211 | resource: function(context, complete) { | |
212 | 788 | runPlugins(context, 'resource', complete, function() { return context.resource; }); |
213 | }, | |
214 | modes: function() { | |
215 | 47 | 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 | 264 | var resource = context.resource; |
11 | 264 | 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 | 262 | 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 | 38 | if (!context.configCache.templateTemplate) { |
32 | 12 | var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE; |
33 | 12 | context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) { |
34 | 12 | if (err) { |
35 | 1 | complete(err); |
36 | } else { | |
37 | 11 | context.configCache.templateTemplate = compiled; |
38 | 11 | complete(); |
39 | } | |
40 | }); | |
41 | } else { | |
42 | 26 | complete(); |
43 | } | |
44 | } | |
45 | ||
46 | 1 | function loadTemplate(name, resource, context, callback) { |
47 | 38 | ensureTemplateTemplates(context, function(err) { |
48 | 38 | if (err) { |
49 | 1 | return callback(err); |
50 | } | |
51 | 37 | var artifactType = 'template' + context.fileConfig.server; |
52 | 37 | context.fileUtil.readFileArtifact(name, artifactType, function(err, cache) { |
53 | 37 | if (err) { |
54 | 0 | callback(new Error('Failed to load template "' + name + '"\n\t' + err)); |
55 | 0 | return; |
56 | } | |
57 | ||
58 | 37 | var artifact = cache.artifact || {}, |
59 | data = artifact.data || cache.data.toString(), | |
60 | attr = context.config.attributes, | |
61 | templates = attr.templates || attr.views || {}, | |
62 | appModule = context.config.scopedAppModuleName(context.module), | |
63 | templateCache = (context.config.attributes.templates && context.config.attributes.templates.cache) | |
64 | || context.config.attributes.templateCache | |
65 | || ((appModule ? appModule + '.' : '') + 'templates'), | |
66 | template = context.configCache.templateTemplate; | |
67 | ||
68 | // We have the template data, now convert it into the proper format | |
69 | 37 | name = templateUtil.escapeJsString(name); |
70 | 37 | if (!cache.artifact) { |
71 | 16 | if (templates.precompile) { |
72 | 2 | var options = context.fileCache.precompileTemplates; |
73 | 2 | if (!options) { |
74 | 2 | context.fileCache.precompileTemplates = options = _.clone(templates.precompile); |
75 | 2 | if (templates.knownHelpers || options.knownHelpers) { |
76 | 0 | options.knownHelpers = (options.knownHelpers || templates.knownHelpers).reduce( |
77 | function(value, helper) { | |
78 | 0 | value[helper] = true; |
79 | 0 | return value; |
80 | }, {}); | |
81 | } | |
82 | 2 | if (context.fileConfig.server && templates.server) { |
83 | 1 | _.extend(options, templates.server); |
84 | } | |
85 | } | |
86 | 2 | try { |
87 | 2 | data = handlebars.precompile(data, options); |
88 | } catch (err) { | |
89 | 0 | return callback(err); |
90 | } | |
91 | } else { | |
92 | 14 | data = "'" + templateUtil.escapeJsString(data) + "'"; |
93 | } | |
94 | 16 | context.fileUtil.setFileArtifact(name, artifactType, {data: data, template: template}); |
95 | } | |
96 | ||
97 | ||
98 | 37 | var mixinRoot = (resource.library && resource.library.root) || ''; |
99 | 37 | if (name.indexOf(mixinRoot) === 0) { |
100 | 37 | name = name.substring(mixinRoot.length); |
101 | } | |
102 | 37 | if (templates.root && name.indexOf(templates.root) === 0) { |
103 | 3 | name = name.substring(templates.root.length); |
104 | } | |
105 | ||
106 | 37 | callback( |
107 | undefined, | |
108 | template({ | |
109 | name: name, | |
110 | handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile', | |
111 | templateCache: templateCache, | |
112 | data: data | |
113 | }) | |
114 | ); | |
115 | }); | |
116 | }); | |
117 | } | |
118 | ||
119 | 1 | module.exports = { |
120 | mode: 'scripts', | |
121 | priority: 50, | |
122 | ||
123 | loadMixin: function(context, next, complete) { | |
124 | 70 | var mixinTemplates = context.loadedLibrary.templates; |
125 | 70 | if (mixinTemplates) { |
126 | 9 | var templates = context.libraries.originalConfig.templates || {}, |
127 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
128 | assigned = false; | |
129 | ||
130 | 9 | ['template', 'precompile', 'cache', 'root'].forEach(function(key) { |
131 | 36 | if (_.has(mixinTemplates, key) && !_.has(templates, key)) { |
132 | 6 | configTemplates[key] = mixinTemplates[key]; |
133 | 6 | assigned = true; |
134 | } | |
135 | }); | |
136 | ||
137 | 9 | if (_.has(mixinTemplates, 'knownHelpers')) { |
138 | 1 | configTemplates.knownHelpers = (configTemplates.knownHelpers || []).concat(mixinTemplates.knownHelpers); |
139 | 1 | assigned = true; |
140 | } | |
141 | ||
142 | 9 | if (assigned) { |
143 | 3 | context.config.attributes.templates = configTemplates; |
144 | } | |
145 | } | |
146 | 70 | next(complete); |
147 | }, | |
148 | ||
149 | resource: function(context, next, complete) { | |
150 | 220 | var resource = context.resource; |
151 | ||
152 | 220 | if (/\.handlebars$/.test(resource.src) || resource.template) { |
153 | 26 | var loadedTemplates = context.fileCache.loadedTemplates; |
154 | 26 | if (!loadedTemplates) { |
155 | 25 | loadedTemplates = context.fileCache.loadedTemplates = {}; |
156 | } | |
157 | ||
158 | 26 | var generator = function(buildContext, callback) { |
159 | 26 | var output = [], |
160 | inputs = []; | |
161 | 26 | context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) { |
162 | 26 | if (err) { |
163 | 0 | callback(err); |
164 | 0 | return; |
165 | } | |
166 | ||
167 | 26 | function ignore(file) { |
168 | 117 | return file.dir || loadedTemplates[file.src || file]; |
169 | } | |
170 | 26 | function checkComplete() { |
171 | 63 | if (inputs.length === files.length) { |
172 | // Sorting is effectively sorting on the file name due to the name comment in the template | |
173 | 25 | callback(undefined, { |
174 | inputs: inputs, | |
175 | data: output.sort().join(''), | |
176 | name: resource.src, | |
177 | generated: true, | |
178 | noSeparator: true, | |
179 | ignoreWarnings: true | |
180 | }); | |
181 | 25 | return true; |
182 | } | |
183 | } | |
184 | ||
185 | 47 | inputs = _.map(files.filter(ignore), function(input) { return input.src || input; }); |
186 | 26 | if (checkComplete()) { |
187 | 1 | return; |
188 | } | |
189 | ||
190 | 25 | files.forEach(function(file) { |
191 | 58 | if (ignore(file)) { |
192 | 20 | return; |
193 | } | |
194 | ||
195 | 38 | var src = file.src || file; |
196 | 38 | loadedTemplates[src] = true; |
197 | 38 | loadTemplate(src, resource, context, function(err, data) { |
198 | 38 | if (err) { |
199 | 1 | return callback(err); |
200 | } | |
201 | ||
202 | 37 | output.push(data.data || data); |
203 | 37 | inputs.push(src); |
204 | 37 | checkComplete(); |
205 | }); | |
206 | }); | |
207 | }); | |
208 | }; | |
209 | 26 | generator.sourceFile = resource.src; |
210 | 26 | complete(undefined, generator); |
211 | } else { | |
212 | 194 | next(complete); |
213 | } | |
214 | } | |
215 | }; | |
216 |
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 | 351 | if (inlineStyles.isInline(context) && context.mode === 'styles') { |
9 | // Prevent stylesheet output if in inline mode | |
10 | 3 | complete(undefined, []); |
11 | 348 | } else if (inlineStyles.isInline(context)) { |
12 | 6 | next(function(err, scripts) { |
13 | 6 | complete(undefined, scripts.concat(context.module.styles || [])); |
14 | }); | |
15 | } else { | |
16 | 342 | 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 | 1502 | 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 | 70 | var mixinStyles = context.loadedLibrary.styles; |
26 | 70 | if (mixinStyles) { |
27 | 18 | var styles = context.libraries.originalConfig.styles || {}, |
28 | configStyles = _.clone(context.config.attributes.styles || styles), | |
29 | assigned = false; | |
30 | ||
31 | 18 | ['inline', 'inlineLoader'].forEach(function(key) { |
32 | 36 | if ((key in mixinStyles) && !(key in styles)) { |
33 | 6 | configStyles[key] = mixinStyles[key]; |
34 | ||
35 | 6 | assigned = true; |
36 | } | |
37 | }); | |
38 | ||
39 | 18 | if (assigned) { |
40 | 5 | context.config.attributes.styles = configStyles; |
41 | } | |
42 | } | |
43 | 70 | next(complete); |
44 | }, | |
45 | ||
46 | outputConfigs: function(context, next, complete) { | |
47 | 190 | if (isInline(context) && context.mode === 'styles') { |
48 | // Prevent stylesheet output if in inline mode | |
49 | 2 | complete(undefined, []); |
50 | } else { | |
51 | 188 | next(complete); |
52 | } | |
53 | }, | |
54 | ||
55 | module: function(context, next, complete) { | |
56 | 193 | next(function(err) { |
57 | 193 | if (err) { |
58 | 0 | return complete(err); |
59 | } | |
60 | ||
61 | 193 | 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 | 193 | complete(); |
95 | }); | |
96 | } | |
97 | }; | |
98 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | lumbar = require('../lumbar'); | |
3 | ||
4 | 1 | function filterDuplicates(context) { |
5 | 199 | if (context.config.attributes.filterDuplicates === false) { |
6 | 2 | return context.moduleResources; |
7 | } | |
8 | ||
9 | 197 | var paths = {}; |
10 | 197 | return _.filter(context.moduleResources, function(resource) { |
11 | 382 | 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 | 380 | return true; |
19 | }); | |
20 | } | |
21 | ||
22 | 1 | function combineResources(context, outputData, callback) { |
23 | 169 | var resources = context.resources || []; |
24 | 169 | if (!resources.length) { |
25 | 51 | return callback(); |
26 | } | |
27 | ||
28 | 118 | context.outputFile(function(callback) { |
29 | 118 | 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 | 118 | data = data || {}; |
37 | 118 | _.extend(data, outputData); |
38 | ||
39 | 118 | if (!data.fileName) { |
40 | 9 | data.fileName = context.fileName; |
41 | } | |
42 | 118 | if (!data.inputs) { |
43 | 9 | data.inputs = _.chain(resources) |
44 | 12 | .map(function(resource) { return resource.inputs || resource; }) |
45 | .flatten() | |
46 | 12 | .map(function(resource) { return resource.src || resource; }) |
47 | 12 | .filter(function(resource) { return _.isString(resource); }) |
48 | .map(context.fileUtil.makeRelative, context.fileUtil) | |
49 | .value(); | |
50 | } | |
51 | ||
52 | 118 | callback(err, data); |
53 | }); | |
54 | }, | |
55 | callback); | |
56 | } | |
57 | ||
58 | 1 | module.exports = { |
59 | priority: 1, | |
60 | ||
61 | modeComplete: function(context, next, complete) { | |
62 | 130 | next(function(err) { |
63 | 130 | if (err) { |
64 | 0 | return complete(err); |
65 | } | |
66 | ||
67 | 130 | if (context.combined) { |
68 | // Build the resources array from each of the modules (Need to maintain proper ordering) | |
69 | 30 | var modules = context.config.moduleList(context.package); |
70 | 30 | context.resources = []; |
71 | 30 | modules.forEach(function(module) { |
72 | 60 | context.resources.push.apply(context.resources, context.combineResources[module]); |
73 | }); | |
74 | 30 | combineResources(context, {}, complete); |
75 | } else { | |
76 | 100 | complete(); |
77 | } | |
78 | }); | |
79 | }, | |
80 | module: function(context, next, complete) { | |
81 | 199 | next(function(err) { |
82 | 199 | if (err) { |
83 | 0 | return complete(err); |
84 | } | |
85 | ||
86 | 199 | if (!context.combined) { |
87 | 139 | context.resources = filterDuplicates(context); |
88 | 139 | context.moduleResources = undefined; |
89 | 139 | combineResources(context, { |
90 | module: context.module.name | |
91 | }, | |
92 | complete); | |
93 | } else { | |
94 | 60 | context.combineResources = context.combineResources || {}; |
95 | 60 | context.combineResources[context.module.name] = filterDuplicates(context); |
96 | 60 | context.moduleResources = undefined; |
97 | 60 | complete(); |
98 | } | |
99 | }); | |
100 | } | |
101 | }; | |
102 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'); |
2 | ||
3 | 1 | module.exports = { |
4 | priority: 1, | |
5 | ||
6 | loadConfig: function(context, next, complete) { | |
7 | 125 | var modules = context.config.attributes.modules, |
8 | errored; | |
9 | 125 | _.each(context.libraries.configs, function(library) { |
10 | // Import any modules that are not overriden in the core file | |
11 | 65 | _.each(library.modules, function(module, key) { |
12 | 21 | if (!_.has(modules, key)) { |
13 | 11 | module = modules[key] = _.clone(module); |
14 | ||
15 | 11 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
16 | 44 | var value = module[field]; |
17 | ||
18 | // Deep(er) clone, updating file references | |
19 | 44 | if (_.isArray(value)) { |
20 | 13 | module[field] = context.libraries.mapFiles(value, library); |
21 | 31 | } else if (value) { |
22 | 0 | module[field] = _.clone(value); |
23 | } | |
24 | }); | |
25 | } | |
26 | }); | |
27 | }); | |
28 | ||
29 | 125 | _.each(modules, function(module, name) { |
30 | 151 | var mixins; |
31 | 151 | try { |
32 | 151 | 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 | 149 | try { |
40 | 149 | ['scripts', 'styles', 'static'].forEach(function(field) { |
41 | 445 | var list = module[field]; |
42 | ||
43 | 445 | if (list) { |
44 | 120 | module[field] = context.libraries.mapFiles(list); |
45 | } | |
46 | }); | |
47 | ||
48 | 148 | _.each(mixins, function(mixin) { |
49 | 31 | var mixinConfig = mixin.mixinConfig, |
50 | library = mixin.library; | |
51 | ||
52 | // Direct copy for any fields that are not already defined on the object. | |
53 | 31 | _.defaults(module, library.attributes); |
54 | ||
55 | // Merge known array/object types | |
56 | 31 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
57 | 124 | 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 | 125 | _.each(_.keys(modules), function(name) { |
68 | 151 | if (!modules[name]) { |
69 | 1 | delete modules[name]; |
70 | } | |
71 | }); | |
72 | ||
73 | 125 | if (!errored) { |
74 | 122 | 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 | 124 | var value = module[field], |
90 | mixinValue = library.attributes[field]; | |
91 | ||
92 | 124 | if (!value) { |
93 | 84 | return; |
94 | } | |
95 | ||
96 | 40 | if (value === mixinValue) { |
97 | // Clone any direct copy entries from a mixin | |
98 | 13 | if (_.isArray(value)) { |
99 | 11 | 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 | 243 | var config = context.config; |
223 | ||
224 | 243 | 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 | 222 | 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 | 281 | var resource = context.resource; |
33 | ||
34 | 281 | 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 | 264 | 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 | 192 | next(function(err, ret) { |
22 | 192 | if (err) { |
23 | 0 | return complete(err); |
24 | } | |
25 | ||
26 | // Generate the router if we have the info for it | |
27 | 192 | var module = context.module; |
28 | 192 | if (module.routes) { |
29 | 47 | ret.unshift({ routes: module.routes }); |
30 | } | |
31 | ||
32 | 192 | complete(undefined, ret); |
33 | }); | |
34 | }, | |
35 | resource: function(context, next, complete) { | |
36 | 264 | var resource = context.resource, |
37 | module = context.module.name; | |
38 | ||
39 | 264 | 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 | 243 | 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 | 412 | return (attr.scope && attr.scope.scope) || attr.scope; |
29 | } | |
30 | 1 | function toObj(obj) { |
31 | 82 | 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 | 70 | var mixinScope = toObj(context.loadedLibrary.scope); |
144 | 70 | 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 | 70 | next(complete); |
170 | }, | |
171 | loadConfig: function(context, next, complete) { | |
172 | 122 | var modules = context.config.attributes.modules; |
173 | ||
174 | 122 | try { |
175 | 122 | _.each(modules, function(module) { |
176 | 145 | var mixins = context.libraries.moduleMixins(module); |
177 | ||
178 | 145 | _.each(mixins, function(mixin) { |
179 | 31 | context.libraries.mergeHash('aliases', module, mixin.library.attributes, module); |
180 | }); | |
181 | }); | |
182 | } catch (err) { | |
183 | 0 | return complete(err); |
184 | } | |
185 | ||
186 | 122 | next(complete); |
187 | }, | |
188 | ||
189 | resourceList: function(context, next, complete) { | |
190 | 309 | next(function(err, resources) { |
191 | 309 | if (err) { |
192 | 0 | return complete(err); |
193 | } | |
194 | ||
195 | 309 | 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 | 309 | complete(undefined, resources); |
202 | }); | |
203 | }, | |
204 | ||
205 | module: function(context, next, complete) { | |
206 | 103 | next(function(err) { |
207 | 103 | if (err) { |
208 | 0 | return complete(err); |
209 | } | |
210 | ||
211 | 103 | var resources = context.moduleResources, |
212 | scope = getScope(context.config.attributes); | |
213 | ||
214 | 103 | 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 | 57 | 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 | 136 | 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 | 192 | var module = context.module; |
15 | 192 | 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 | 76 | next(function(err) { |
9 | 76 | 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 | 118 | 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 | 70 | var mixinStyles = context.loadedLibrary.styles; |
10 | 70 | if (mixinStyles) { |
11 | 18 | var styles = context.libraries.originalConfig.styles || {}, |
12 | configStyles = _.clone(context.config.attributes.styles || styles), | |
13 | assigned = false; | |
14 | ||
15 | 18 | ['configObject'].forEach(function(key) { |
16 | 18 | if ((key in mixinStyles) && !(key in styles)) { |
17 | 1 | configStyles[key] = mixinStyles[key]; |
18 | ||
19 | 1 | assigned = true; |
20 | } | |
21 | }); | |
22 | ||
23 | 18 | if (context.libraries.mergeFiles('config', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
24 | 4 | assigned = true; |
25 | } | |
26 | ||
27 | 18 | if (assigned) { |
28 | 4 | context.config.attributes.styles = configStyles; |
29 | } | |
30 | } | |
31 | 70 | next(complete); |
32 | }, | |
33 | ||
34 | resource: function(context, next, complete) { | |
35 | 412 | 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 | 409 | next(complete); |
71 | } | |
72 | }, | |
73 | ||
74 | module: function(context, next, complete) { | |
75 | 199 | next(function() { |
76 | 199 | var styles = context.config.attributes.styles || {}, |
77 | config = styles.config || []; | |
78 | ||
79 | 199 | 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 | 199 | 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 | 253 | 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 | 36 | var includes = (styleConfig.includes || []).concat(options.files), |
38 | module = options.module; | |
39 | ||
40 | 36 | var nibLocation = includes.indexOf('nib'), |
41 | useNib; | |
42 | 36 | if (styleConfig.useNib) { |
43 | 31 | useNib = true; |
44 | 31 | 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 | 36 | var declare = context.config.platformList().map(function(platform) { |
53 | 71 | return '$' + platform + ' = ' + (platform === context.platform); |
54 | }).join('\n') + '\n'; | |
55 | ||
56 | 36 | var mixins = [], |
57 | mixinLUT = {}; | |
58 | ||
59 | 36 | var source = declare + includes.map(function(include) { |
60 | 113 | var source = include.library; |
61 | 113 | var statement = '@import ("' + (include.originalSrc || include.src || include) + '")\n'; |
62 | 113 | 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 | 105 | return statement; |
90 | } | |
91 | }).join(''); | |
92 | ||
93 | 36 | return { |
94 | useNib: useNib, | |
95 | source: source, | |
96 | mixins: mixins | |
97 | }; | |
98 | } | |
99 | ||
100 | 1 | function compile(options, callback) { |
101 | 36 | var context = options.context, |
102 | ||
103 | styleConfig = context.config.attributes.styles || {}; | |
104 | ||
105 | 36 | var loadPrefix = context.config.loadPrefix(), |
106 | externalPrefix; | |
107 | 36 | if (loadPrefix) { |
108 | 9 | externalPrefix = loadPrefix + (context.buildPath.indexOf('/') >= 0 ? path.dirname(context.buildPath) + '/' : ''); |
109 | } | |
110 | ||
111 | 36 | 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 | 36 | var source = generateSource(context, options, styleConfig); |
120 | ||
121 | 36 | context.fileUtil.ensureDirs(context.fileName, function(err) { |
122 | 36 | if (err) { |
123 | 0 | return callback(err); |
124 | } | |
125 | ||
126 | 36 | 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 | 70 | var mixinStyles = context.loadedLibrary.styles; |
152 | 70 | if (mixinStyles) { |
153 | 18 | var styles = context.libraries.originalConfig.styles || {}, |
154 | configStyles = _.clone(context.config.attributes.styles || styles), | |
155 | assigned = false; | |
156 | ||
157 | 18 | ['urlSizeLimit', 'copyFiles', 'useNib'].forEach(function(key) { |
158 | 54 | if ((key in mixinStyles) && !(key in styles)) { |
159 | 8 | configStyles[key] = mixinStyles[key]; |
160 | ||
161 | 8 | assigned = true; |
162 | } | |
163 | }); | |
164 | ||
165 | 18 | if (context.libraries.mergeFiles('includes', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
166 | 4 | assigned = true; |
167 | } | |
168 | ||
169 | 18 | if (context.libraries.mergeHash('pixelDensity', styles, mixinStyles, configStyles)) { |
170 | 3 | assigned = true; |
171 | } | |
172 | ||
173 | 18 | if (assigned) { |
174 | 8 | context.config.attributes.styles = configStyles; |
175 | } | |
176 | } | |
177 | 70 | next(complete); |
178 | }, | |
179 | ||
180 | outputConfigs: function(context, next, complete) { | |
181 | 191 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
182 | 91 | return next(complete); |
183 | } | |
184 | ||
185 | 100 | next(function(err, files) { |
186 | 100 | if (err) { |
187 | 0 | return complete(err); |
188 | } | |
189 | ||
190 | 100 | var ret = [], |
191 | styleConfig = context.config.attributes.styles || {}, | |
192 | pixelDensity = styleConfig.pixelDensity || {}; | |
193 | 100 | if (context.platform) { |
194 | 74 | pixelDensity = pixelDensity[context.platform] || pixelDensity; |
195 | } | |
196 | 100 | if (!_.isArray(pixelDensity)) { |
197 | 63 | pixelDensity = [ 1 ]; |
198 | } | |
199 | 100 | context.modeCache.pixelDensity = pixelDensity; |
200 | ||
201 | // Permutation of other configs and ours | |
202 | 100 | var primary = true; |
203 | 100 | files.forEach(function(fileConfig) { |
204 | 100 | pixelDensity.forEach(function(density) { |
205 | 138 | var config = _.clone(fileConfig); |
206 | 138 | config.pixelDensity = density; |
207 | 138 | config.isPrimary = primary; |
208 | 138 | primary = false; |
209 | 138 | ret.push(config); |
210 | }); | |
211 | }); | |
212 | 100 | complete(undefined, ret); |
213 | }); | |
214 | }, | |
215 | ||
216 | fileName: function(context, next, complete) { | |
217 | 229 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
218 | 101 | return next(complete); |
219 | } | |
220 | ||
221 | 128 | next(function(err, ret) { |
222 | 128 | if (ret && context.fileConfig.pixelDensity !== 1) { |
223 | 40 | ret.path += '@' + context.fileConfig.pixelDensity + 'x'; |
224 | } | |
225 | 128 | complete(err, ret); |
226 | }); | |
227 | }, | |
228 | ||
229 | module: function(moduleContext, next, complete) { | |
230 | 201 | next(function(err) { |
231 | /*jshint eqnull: true */ | |
232 | 201 | if (err) { |
233 | 0 | return complete(err); |
234 | } | |
235 | ||
236 | 201 | function mergeResources(start) { |
237 | 54 | var generator = function(context, callback) { |
238 | 54 | function response(data, density) { |
239 | 54 | if (data) { |
240 | 51 | return { |
241 | data: data.data[density || 1], | |
242 | inputs: data.inputs, | |
243 | noSeparator: true | |
244 | }; | |
245 | } | |
246 | } | |
247 | ||
248 | 54 | 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 | 54 | var queue = context.modeCache['stylus_' + filename]; |
255 | 54 | if (_.isArray(queue)) { |
256 | // We are currently executing | |
257 | 18 | queue.push({density: context.fileConfig.pixelDensity, callback: callback}); |
258 | 36 | } 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 | 36 | queue = context.modeCache['stylus_' + filename] = [ |
264 | {density: context.fileConfig.pixelDensity, callback: callback} | |
265 | ]; | |
266 | 36 | var options = { |
267 | filename: filename, | |
268 | files: generator.inputs, | |
269 | ||
270 | context: context, | |
271 | module: moduleContext.module, // To play nicely with combined mode | |
272 | plugins: generator.plugins | |
273 | }; | |
274 | 36 | compile(options, function(err, data) { |
275 | 36 | if (err) { |
276 | 3 | data = undefined; |
277 | } | |
278 | 36 | _.each(queue, function(callback) { |
279 | 54 | callback.callback(err, response(data, callback.density)); |
280 | }); | |
281 | 36 | context.modeCache['stylus_' + filename] = data; |
282 | }); | |
283 | } | |
284 | }; | |
285 | 54 | generator.inputs = resources.splice(start, rangeEnd - start + 1); |
286 | 143 | generator.filename = 'stylus_' + _.map(generator.inputs, function(file) { return file.originalSrc || file.src; }).join(';'); |
287 | 54 | generator.style = true; |
288 | 54 | generator.stylus = true; |
289 | 54 | generator.plugins = []; |
290 | ||
291 | 54 | resources.splice(start, 0, generator); |
292 | 54 | rangeEnd = undefined; |
293 | } | |
294 | ||
295 | // Merge all consequtive stylus files together | |
296 | 201 | var resources = moduleContext.moduleResources, |
297 | len = resources.length, | |
298 | rangeEnd; | |
299 | 201 | while (len--) { |
300 | 414 | var resource = resources[len]; |
301 | ||
302 | 414 | if (/\.styl$/.test(resource.src)) { |
303 | 89 | if (!rangeEnd) { |
304 | 54 | rangeEnd = len; |
305 | } | |
306 | 325 | } else if (rangeEnd) { |
307 | 3 | mergeResources(len + 1); |
308 | } | |
309 | } | |
310 | 201 | if (rangeEnd != null) { |
311 | 51 | mergeResources(0); |
312 | } | |
313 | 201 | 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 | 70 | var mixinTemplates = context.loadedLibrary.templates; |
31 | 70 | if (mixinTemplates) { |
32 | 9 | var templates = context.libraries.originalConfig.templates || {}, |
33 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
34 | assigned = false; | |
35 | ||
36 | 9 | if (context.libraries.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) { |
37 | 3 | assigned = true; |
38 | } | |
39 | ||
40 | 9 | if (assigned) { |
41 | 3 | context.config.attributes.templates = configTemplates; |
42 | } | |
43 | } | |
44 | 70 | next(complete); |
45 | }, | |
46 | ||
47 | resourceList: function(context, next, complete) { | |
48 | 309 | var library = context.resource.library, |
49 | attr = (library && library.parent || library) || context.config.attributes; | |
50 | ||
51 | 309 | next(function(err, ret) { |
52 | 309 | if (err || !ret) { |
53 | 0 | return complete(err); |
54 | } | |
55 | ||
56 | 309 | function pushTemplates(templates) { |
57 | 274 | _.each(templates, function(template) { |
58 | 43 | var src = template.src; |
59 | 43 | if (!src || (template.library && !template.library.attributes)) { |
60 | 33 | var templateLibrary = template.library ? context.libraries.getConfig(template.library) : library; |
61 | 33 | src = context.libraries.resolvePath(template.src || template, templateLibrary); |
62 | } | |
63 | ||
64 | 43 | ret.unshift({ |
65 | src: src, | |
66 | name: template.name || template.src || template, | |
67 | library: templateLibrary || template.library || library, | |
68 | template: true | |
69 | }); | |
70 | }); | |
71 | } | |
72 | ||
73 | 309 | var views = attr.templates || attr.views || {}, |
74 | globalConfig = (context.config.attributes.templates || {}), | |
75 | resource = context.resource.originalSrc || context.resource.src || context.resource, | |
76 | mixinRoot = (context.resource.library && context.resource.library.root) || ''; | |
77 | 309 | if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) { |
78 | 235 | resource = resource.substring(mixinRoot.length); |
79 | } | |
80 | ||
81 | 309 | var deferComplete; |
82 | 309 | if (build.filterResource(context.resource, context)) { |
83 | 265 | pushTemplates(views[resource]); |
84 | ||
85 | 265 | if (globalConfig['auto-include']) { |
86 | 10 | var config = context.configCache['template-auto-include']; |
87 | 10 | if (!config) { |
88 | 5 | config = module.exports.generateMappings(globalConfig['auto-include']); |
89 | 5 | context.configCache['template-auto-include'] = config; |
90 | } | |
91 | ||
92 | 10 | var autoIncludes = module.exports.autoIncludes(resource, config, context); |
93 | 10 | if (autoIncludes.length) { |
94 | 9 | deferComplete = true; |
95 | ||
96 | 9 | context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) { |
97 | 9 | if (err) { |
98 | 0 | return complete(err); |
99 | } | |
100 | ||
101 | 9 | var watchDirs = []; |
102 | 9 | autoIncludes = _.filter(autoIncludes, function(file) { |
103 | 13 | if (file.enoent) { |
104 | 3 | watchDirs.push({watch: path.dirname(file.src)}); |
105 | } else { | |
106 | 10 | return true; |
107 | } | |
108 | }); | |
109 | ||
110 | 9 | if (autoIncludes.length) { |
111 | 7 | context.event.emit('log', 'Autoincludes for "' + resource + '" ' + JSON.stringify(_.pluck(autoIncludes, 'src'), undefined, 2)); |
112 | } | |
113 | ||
114 | 9 | pushTemplates(autoIncludes); |
115 | 9 | ret.unshift.apply(ret, watchDirs); |
116 | ||
117 | 9 | complete(undefined, ret); |
118 | }); | |
119 | } | |
120 | } | |
121 | } | |
122 | 309 | if (!deferComplete) { |
123 | 300 | complete(undefined, ret); |
124 | } | |
125 | }); | |
126 | }, | |
127 | ||
128 | resource: function(context, next, complete) { | |
129 | 222 | var resource = context.resource; |
130 | ||
131 | 222 | if (resource.watch) { |
132 | 2 | function generator(buildContext, callback) { |
133 | // Ensure that the directory actually exists | |
134 | 2 | var path = context.fileUtil.resolvePath(resource.watch); |
135 | 2 | context.fileUtil.stat(path, function(err, stat) { |
136 | // Ignore any errors here | |
137 | 2 | var inputs = []; |
138 | 2 | if (stat && stat.isDirectory()) { |
139 | 2 | inputs.push(path); |
140 | } | |
141 | 2 | callback(undefined, {inputs: inputs, data: '', noSeparator: true}); |
142 | }); | |
143 | } | |
144 | 2 | complete(undefined, generator); |
145 | } else { | |
146 | 220 | next(complete); |
147 | } | |
148 | }, | |
149 | ||
150 | autoIncludes: function(resource, config, context) { | |
151 | 10 | var autoIncludes = []; |
152 | 10 | _.each(config, function(mapping) { |
153 | 10 | var remap = module.exports.remapFile(mapping, resource, context); |
154 | 10 | if (remap) { |
155 | 9 | autoIncludes.push.apply(autoIncludes, remap); |
156 | } | |
157 | }); | |
158 | 10 | return autoIncludes; |
159 | }, | |
160 | generateMappings: function(autoInclude) { | |
161 | 5 | return _.map(autoInclude, function(templates, source) { |
162 | 5 | if (!_.isArray(templates)) { |
163 | 2 | templates = [templates]; |
164 | } | |
165 | 5 | return {regex: new RegExp(source), templates: templates}; |
166 | }); | |
167 | }, | |
168 | remapFile: function(mapping, resource, context) { | |
169 | /*jshint boss:true */ | |
170 | 13 | var match; |
171 | 13 | if (match = mapping.regex.exec(resource)) { |
172 | 11 | return _.map(mapping.templates, function(template) { |
173 | // Work in reverse so $10 takes priority over $1 | |
174 | 21 | var i = match.length; |
175 | 21 | while (i--) { |
176 | 46 | template = template.replace('$' + i, match[i]); |
177 | } | |
178 | 21 | return {name: template, src: context.libraries.resolvePath(template, template.library || context.resource.library)}; |
179 | }); | |
180 | } | |
181 | } | |
182 | }; | |
183 |
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 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | build = require('./build'), | |
4 | ChildPool = require('child-pool'), | |
5 | combine = require('./jsCombine'), | |
6 | configLoader = require('./config'), | |
7 | Context = require('./context'), | |
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.loadConfig = function(path, event, options, callback) { |
15 | 37 | try { |
16 | 37 | fu.resetCache(); |
17 | ||
18 | 37 | var config = _.isString(path) ? configLoader.load(path) : configLoader.create(path); |
19 | ||
20 | 37 | var plugins = plugin.create(options); |
21 | 37 | plugins.initialize(config); |
22 | ||
23 | 36 | config.outdir = options.outdir = options.outdir || config.attributes.output; |
24 | ||
25 | 36 | var libraries = new Libraries(options); |
26 | 36 | var context = new Context(options, config, plugins, libraries, event); |
27 | 36 | context.options = options; |
28 | 36 | context.configCache = {}; |
29 | ||
30 | 36 | libraries.initialize(context, function(err) { |
31 | 36 | if (err) { |
32 | 1 | return callback(err); |
33 | } | |
34 | ||
35 | 35 | plugins.loadConfig(context, function(err) { |
36 | 35 | if (err) { |
37 | 1 | return callback(err); |
38 | } | |
39 | ||
40 | 34 | event.emit('config', context.config); |
41 | 34 | if (options.verbose) { |
42 | 1 | event.emit('log', 'Finalized config ' + JSON.stringify(context.config.serialize(), undefined, 2)); |
43 | } | |
44 | ||
45 | // Ensure that we have the proper build output | |
46 | 34 | if (!config.outdir) { |
47 | 1 | return callback(new Error('Output must be defined on the command line or config file.')); |
48 | } | |
49 | 33 | context.outdir = config.outdir; |
50 | ||
51 | 33 | fu.ensureDirs(config.outdir + '/.', function() { |
52 | 33 | var stat = fs.statSync(config.outdir); |
53 | 33 | if (!stat.isDirectory()) { |
54 | 1 | callback(new Error('Output must be a directory')); |
55 | } else { | |
56 | 32 | callback(undefined, context); |
57 | } | |
58 | }); | |
59 | }); | |
60 | }); | |
61 | } catch (err) { | |
62 | 1 | callback(err); |
63 | } | |
64 | }; | |
65 | ||
66 | 1 | exports.buildPackages = function(context, packageName, modules, callback) { |
67 | 39 | if (!callback) { |
68 | 14 | callback = modules; |
69 | 14 | modules = undefined; |
70 | } | |
71 | ||
72 | // Allow a string or a list as modules input | |
73 | 39 | if (!_.isArray(modules)) { |
74 | 37 | modules = [modules]; |
75 | 2 | } else if (!modules.length) { |
76 | // Special case empty array input to build all | |
77 | 1 | modules = [undefined]; |
78 | } | |
79 | ||
80 | 39 | var options = {}; |
81 | 39 | if (typeof packageName === 'object') { |
82 | 1 | options = packageName; |
83 | 1 | packageName = options.package; |
84 | } | |
85 | ||
86 | 39 | var packageNames = packageName ? [packageName] : context.config.packageList(), |
87 | contexts = []; | |
88 | ||
89 | 39 | packageNames.forEach(function(pkg) { |
90 | 43 | modules.forEach(function(module) { |
91 | 44 | options.package = pkg; |
92 | 44 | options.module = module || undefined; // '' -> undefined |
93 | ||
94 | 44 | context.event.emit('debug', 'Build package: ' + pkg); |
95 | ||
96 | 44 | var platforms = context.config.platformList(pkg); |
97 | 44 | platforms.forEach(function(platform) { |
98 | 60 | options.platform = platform; |
99 | ||
100 | 60 | var newContext = context.clone(options); |
101 | 60 | contexts.push(newContext); |
102 | }); | |
103 | }); | |
104 | }); | |
105 | ||
106 | 39 | async.forEach(contexts, exports.buildPlatform, callback); |
107 | }; | |
108 | 1 | exports.buildPlatform = function(context, callback) { |
109 | 84 | context.event.emit('debug', 'Build platform: ' + context.description); |
110 | 84 | var modes = context.mode ? [context.mode] : context.plugins.modes(); |
111 | ||
112 | 84 | async.forEach(modes, function(mode, callback) { |
113 | 180 | exports.buildMode(mode, context, callback); |
114 | }, | |
115 | callback); | |
116 | }; | |
117 | 1 | exports.buildMode = function(mode, context, callback) { |
118 | 183 | context.event.emit('debug', 'Build mode: ' + context.description); |
119 | ||
120 | 183 | var modules = context.module ? [context.module] : context.config.moduleList(context.package); |
121 | ||
122 | 183 | context = context.clone(); |
123 | 183 | context.mode = mode; |
124 | 183 | context.modeCache = {}; |
125 | ||
126 | 183 | if (context.fileConfig) { |
127 | 23 | processFileConfig(context.fileConfig, callback); |
128 | } else { | |
129 | 160 | context.plugins.outputConfigs(context, function(err, configs) { |
130 | 160 | if (err) { |
131 | 1 | return callback(err); |
132 | } | |
133 | 159 | async.forEach(configs, processFileConfig, callback); |
134 | }); | |
135 | } | |
136 | ||
137 | 183 | function processFileConfig(fileConfig, callback) { |
138 | 194 | var fileContext = context.clone(true); |
139 | 194 | fileContext.fileConfig = fileConfig; |
140 | 194 | fileContext.resources = []; |
141 | 194 | fileContext.combineResources = {}; |
142 | 194 | fileContext.fileCache = fileContext.combined ? {} : undefined; |
143 | ||
144 | 194 | async.forEach(modules, function(module, callback) { |
145 | 283 | var moduleContext = fileContext.clone(); |
146 | 283 | moduleContext.module = module; |
147 | ||
148 | 283 | exports.buildModule(moduleContext, callback); |
149 | }, | |
150 | function(err) { | |
151 | 190 | if (err) { |
152 | 5 | return callback(err); |
153 | } | |
154 | ||
155 | 185 | context.plugins.modeComplete(fileContext, callback); |
156 | }); | |
157 | } | |
158 | }; | |
159 | 1 | exports.buildModule = function(context, callback) { |
160 | 275 | context.event.emit('debug', 'Build module: ' + context.description); |
161 | ||
162 | 275 | var module = context.config.module(context.module); |
163 | 275 | if (!module) { |
164 | 1 | return callback(new Error('Unable to find module "' + context.module + '"')); |
165 | } | |
166 | ||
167 | 274 | context.module = module; |
168 | 274 | context.fileCache = context.combined ? context.fileCache : {}; |
169 | 274 | context.moduleCache = {}; |
170 | ||
171 | 274 | var resource = context.resource; |
172 | 274 | if (resource) { |
173 | 7 | resource = resource.originalResource || resource; |
174 | 7 | exports.processResources(context, [resource], callback); |
175 | } else { | |
176 | // Load all resources associated with this module | |
177 | 267 | build.loadResources(context, function(err, resources) { |
178 | 267 | if (err) { |
179 | 1 | return callback(err); |
180 | } | |
181 | 266 | exports.processResources(context, resources, callback); |
182 | }); | |
183 | } | |
184 | }; | |
185 | ||
186 | 1 | exports.processResources = function(context, resources, callback) { |
187 | 273 | build.processResources(resources, context, function(err, resources) { |
188 | 273 | if (err) { |
189 | 1 | return callback(err); |
190 | } | |
191 | ||
192 | 272 | context.moduleResources = resources; |
193 | 272 | context.plugins.module(context, callback); |
194 | }); | |
195 | }; | |
196 |
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 | 63 | 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 | 121 | this.output = output; |
24 | 121 | if (SourceMapGenerator) { |
25 | 121 | this.generator = new SourceMapGenerator({file: output}); |
26 | } | |
27 | ||
28 | 121 | this.contentCache = {}; |
29 | 121 | this.line = 1; |
30 | 121 | this.column = 1; |
31 | 121 | this._content = ''; |
32 | }; | |
33 | ||
34 | 1 | exports.prototype.add = function(name, content, context, generated) { |
35 | 511 | this._sourceMap = ''; |
36 | 511 | this._consumer = undefined; |
37 | ||
38 | 511 | var lines = content.split('\n'); |
39 | 511 | if (name && !generated) { |
40 | 191 | this.contentCache[name] = { |
41 | lines: lines, | |
42 | context: context | |
43 | }; | |
44 | } | |
45 | ||
46 | 511 | if (this.generator) { |
47 | 511 | _.each(lines, function(line, index) { |
48 | 2842 | this.generator.addMapping({ |
49 | source: generated && name ? (GENERATED + ':' + name + '>') : (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 | 511 | this.line += lines.length - 1; |
63 | 511 | if (lines.length >= 2) { |
64 | 480 | this.column = 1; |
65 | } | |
66 | 511 | this.column += lines[lines.length - 1].length; |
67 | ||
68 | 511 | this._content += content; |
69 | }; | |
70 | 1 | exports.prototype.content = function() { |
71 | 275 | return this._content; |
72 | }; | |
73 | 1 | exports.prototype.sourceMap = function() { |
74 | 4 | this._sourceMap = this._sourceMap || this.generator.toString(); |
75 | 4 | return this._sourceMap; |
76 | }; | |
77 | ||
78 | 1 | exports.prototype.sourceMapToken = function() { |
79 | 3 | return '//@ sourceMappingURL=' + basename(this.output) + '.map\n'; |
80 | }; | |
81 | ||
82 | 1 | exports.prototype.writeSourceMap = function(options) { |
83 | 3 | var tasks = [], |
84 | outputDir = dirname(this.output) + '/', | |
85 | self = this; | |
86 | ||
87 | 3 | tasks.push(function(callback) { |
88 | 3 | fu.writeFile((options.mapDestination || self.output) + '.map', self.sourceMap(), callback); |
89 | }); | |
90 | 3 | if (options.outputSource) { |
91 | 1 | _.each(this.contentCache, function(content, name) { |
92 | 2 | tasks.push(function(callback) { |
93 | 2 | var file = outputDir + name; |
94 | 2 | fu.ensureDirs(dirname(file), function(err) { |
95 | 2 | if (err) { |
96 | 0 | return callback(err); |
97 | } | |
98 | 2 | fu.writeFile(file, content.lines.join('\n'), callback); |
99 | }); | |
100 | }); | |
101 | }); | |
102 | } | |
103 | ||
104 | 3 | async.parallel(tasks, function(err) { |
105 | 3 | if (err) { |
106 | 0 | throw err; |
107 | } | |
108 | ||
109 | 3 | self.add(undefined, self.sourceMapToken()); |
110 | 3 | options.callback(); |
111 | }); | |
112 | }; | |
113 | ||
114 | 1 | exports.prototype.context = function(line, column) { |
115 | 5 | if (!SourceMapConsumer) { |
116 | 0 | return { |
117 | file: this.output, | |
118 | line: line, | |
119 | column: column | |
120 | }; | |
121 | } | |
122 | ||
123 | 5 | this._consumer = this._consumer || new SourceMapConsumer(this.sourceMap()); |
124 | 5 | var original = this._consumer.originalPositionFor({line: line, column: column}), |
125 | lines; | |
126 | ||
127 | 5 | var content = this.contentCache[original.source]; |
128 | 5 | if (content) { |
129 | 4 | 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 | 4 | line = line + 1; |
135 | ||
136 | 4 | lines = lines.slice(start, end).map(function(value, index) { |
137 | 15 | var lineNum = start + index + 1, |
138 | lineText = lineNum + '', | |
139 | buffer = ''; | |
140 | 15 | for (var i = lineText.length; i < gutterWidth; i++) { |
141 | 7 | buffer += ' '; |
142 | } | |
143 | 15 | buffer += lineText; |
144 | 15 | buffer += (lineNum === line) ? ': ' : ' '; |
145 | 15 | buffer += value; |
146 | 15 | return buffer; |
147 | }); | |
148 | } else { | |
149 | 1 | return; |
150 | } | |
151 | ||
152 | 4 | 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 | /** | |
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 | 81 | var watchInfo = watchedFiles[filename]; |
11 | 81 | if (watchInfo) { |
12 | 78 | var inQueue = _.find(watchInfo.queue, function(entry) { |
13 | 0 | return entry.type === type |
14 | && entry.filename === filename | |
15 | && entry.sourceChange === sourceChange; | |
16 | }); | |
17 | ||
18 | 78 | if (!inQueue) { |
19 | 78 | var entry = {type: type, filename: filename, sourceChange: sourceChange}; |
20 | 78 | watchInfo.queue.push(entry); |
21 | ||
22 | 78 | function exec() { |
23 | 78 | watchInfo.queue = _.without(watchInfo.queue, entry); |
24 | ||
25 | 78 | if (watchInfo.callback) { |
26 | 52 | watchInfo.callback(type, filename, sourceChange); |
27 | } | |
28 | 78 | watchInfo.parents.forEach(function(parent) { |
29 | 43 | notifyWatch(parent, type, sourceChange, trigger); |
30 | }); | |
31 | } | |
32 | ||
33 | 78 | if (trigger) { |
34 | 72 | 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 | 185 | var watchInfo = { |
46 | callback: callback, | |
47 | parents: [], | |
48 | queue: [] | |
49 | }; | |
50 | 185 | if (parent) { |
51 | 106 | watchInfo.parents.push(parent); |
52 | } | |
53 | 185 | watchedFiles[filename.virtual || filename] = watchInfo; |
54 | ||
55 | 185 | 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 | 101 | var watch = watchedFiles[filename.virtual || filename]; |
124 | 101 | if (!watch) { |
125 | // Create a watch on this and all others | |
126 | 79 | watchFile(filename, callback); |
127 | } else { | |
128 | 22 | watch.callback = callback; |
129 | } | |
130 | ||
131 | 101 | filename = filename.virtual || filename; |
132 | ||
133 | 101 | dependencies.forEach(function(depend) { |
134 | 275 | var watch = watchedFiles[depend.virtual || depend]; |
135 | 275 | if (!watch) { |
136 | 106 | 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 | 32 | notifyWatch(filename, type, filename, true); |
147 | }; | |
148 | ||
149 | 1 | exports.unwatch = function(filename, dependencies) { |
150 | 9 | var watch = watchedFiles[filename.virtual || filename]; |
151 | 9 | if (!watch) { |
152 | 1 | return; |
153 | } | |
154 | ||
155 | // Remove the callback | |
156 | 8 | if (!dependencies) { |
157 | 4 | watch.callback = undefined; |
158 | } | |
159 | ||
160 | // For each dependency remove the parent link | |
161 | 8 | filename = filename.virtual || filename; |
162 | ||
163 | 8 | _.each(dependencies || watchedFiles, function(depend) { |
164 | 18 | var watch = watchedFiles[depend.virtual || depend]; |
165 | 18 | if (!watch) { |
166 | 14 | return; |
167 | } | |
168 | ||
169 | 4 | watch.parents = _.without(watch.parents, filename); |
170 | }); | |
171 | ||
172 | // Kill this watch if it can't trigger or fire | |
173 | 8 | var canTrigger = watch.watch || _.some(watchedFiles, function(watch) { |
174 | 29 | return _.contains(watch.parents, filename); |
175 | }); | |
176 | 8 | if (!watch.callback || !canTrigger) { |
177 | 5 | unwatch(filename); |
178 | } | |
179 | ||
180 | // Kill any other watches that might not be valid anymore | |
181 | 8 | _.each(_.clone(watchedFiles), function(watch, name) { |
182 | 24 | if (!watch.callback && !watch.parents.length) { |
183 | 4 | exports.unwatch(name); |
184 | } | |
185 | }); | |
186 | }; | |
187 | 1 | exports.unwatchAll = function() { |
188 | 60 | _.each(watchedFiles, function(watch, name) { |
189 | 180 | unwatch(name); |
190 | }); | |
191 | }; | |
192 | ||
193 | 1 | function unwatch(name) { |
194 | 185 | watchedFiles[name].callback = undefined; |
195 | 185 | if (watchedFiles[name].watch) { |
196 | 6 | watchedFiles[name].watch.close(); |
197 | } | |
198 | 185 | delete watchedFiles[name]; |
199 | } | |
200 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | EventEmitter = require('events').EventEmitter, | |
3 | fu = require('./fileUtil'), | |
4 | path = require('path'), | |
5 | watcher = require('./util/watcher'); | |
6 | ||
7 | 1 | function WatchManager() { |
8 | 29 | EventEmitter.call(this); |
9 | ||
10 | 29 | this.reset(); |
11 | ||
12 | 29 | this._exec = this.setupExec(); |
13 | } | |
14 | ||
15 | 1 | WatchManager.prototype = { |
16 | configFile: function(path, mixins, callback) { | |
17 | 18 | if (_.isFunction(mixins)) { |
18 | 0 | callback = mixins; |
19 | 0 | mixins = undefined; |
20 | } | |
21 | ||
22 | 18 | var self = this; |
23 | 18 | 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 | 76 | var self = this; |
31 | ||
32 | 76 | function theWatcher(type, filename, sourceChange) { |
33 | 39 | self.emit('watch-change', {fileName: sourceChange, output: status.fileName}); |
34 | 39 | self.pushChange({ |
35 | callback: callback, | |
36 | type: type, | |
37 | fileName: status.fileName, | |
38 | sourceChange: sourceChange | |
39 | }); | |
40 | } | |
41 | ||
42 | 344 | var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); }), |
43 | removed = _.difference(this.watching[status.fileName], input); | |
44 | ||
45 | 76 | if (removed.length) { |
46 | 2 | watcher.unwatch(status.fileName, removed); |
47 | } | |
48 | ||
49 | 76 | watcher.watchFile({ virtual: status.fileName }, input, theWatcher); |
50 | 76 | 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 | 24 | if (this.queue.length) { |
59 | 24 | _.each(this.queue, function(change) { |
60 | 41 | change.callback(); |
61 | }); | |
62 | 24 | 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 | 52 | watcher.unwatchAll(); |
70 | ||
71 | 52 | this.watching = {}; |
72 | 52 | this.queue = []; |
73 | }, | |
74 | pushChange: function(change) { | |
75 | 60 | fu.resetCache(change.sourceChange); |
76 | 60 | if (change.type === 'remove' && change.sourceChange) { |
77 | 3 | fu.resetCache(path.dirname(change.sourceChange)); |
78 | } | |
79 | ||
80 | 60 | if (_.find(this.queue, function(existing) { |
81 | 45 | 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 | 53 | if (change.config) { |
89 | 10 | this.reset(); |
90 | } | |
91 | ||
92 | 53 | this.queue.push(change); |
93 | 53 | this._exec(); |
94 | } | |
95 | }; | |
96 | ||
97 | 1 | WatchManager.prototype.__proto__ = EventEmitter.prototype; |
98 | ||
99 | 1 | exports = module.exports = WatchManager; |
100 |