[4mRunning "mochacov:cov" (mochacov) task[24m
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | fu = require('./fileUtil'), | |
4 | resources = require('./util/resources'); | |
5 | ||
6 | /** | |
7 | * Creates a list of all of the resources for the current module. | |
8 | * | |
9 | * Context state: module | |
10 | * | |
11 | * Plugin Calls: | |
12 | * moduleResources | |
13 | * fileFilter | |
14 | * resourceList | |
15 | */ | |
16 | 1 | exports.loadResources = function(context, callback) { |
17 | 331 | var plugins = context.plugins; |
18 | ||
19 | 331 | function filterResource(resource) { |
20 | 569 | resource = resources.cast(resource); |
21 | ||
22 | 569 | if (exports.filterResource(resource, context)) { |
23 | 488 | return resource; |
24 | } | |
25 | } | |
26 | ||
27 | 331 | plugins.moduleResources(context, function(err, files) { |
28 | 331 | if (err) { |
29 | 1 | return callback(err); |
30 | } | |
31 | ||
32 | 330 | var fileFilter = plugins.fileFilter(context) || /.*/; |
33 | 330 | fu.fileList(files, fileFilter, function(err, files) { |
34 | 330 | if (err) { |
35 | 1 | return callback(err); |
36 | } | |
37 | ||
38 | 329 | async.map(files, function(resource, callback) { |
39 | 514 | var resourceContext = context.clone(); |
40 | 514 | resourceContext.resource = resource; |
41 | 514 | plugins.resourceList(resourceContext, callback); |
42 | }, | |
43 | function(err, resources) { | |
44 | 329 | resources = _.flatten(resources); |
45 | 329 | resources = _.map(resources, filterResource); |
46 | 898 | resources = _.filter(resources, function(resource) { return resource; }); |
47 | 329 | callback(err, resources); |
48 | }); | |
49 | }); | |
50 | }); | |
51 | }; | |
52 | ||
53 | /** | |
54 | * Filters a given resource for platform constraints, if specified. | |
55 | */ | |
56 | 1 | exports.filterResource = function(resource, context) { |
57 | 918 | function check(value, singular, plural) { |
58 | 2506 | if (typeof singular !== 'undefined') { |
59 | 172 | return singular.not ? singular.not !== value : singular === value; |
60 | 2334 | } else if (plural) { |
61 | 73 | var ret = (plural.not || plural).reduce(function(found, filePlatform) { |
62 | 105 | return found || filePlatform === value; |
63 | }, false); | |
64 | 73 | return plural.not ? !ret : ret; |
65 | } | |
66 | 2261 | return true; |
67 | } | |
68 | ||
69 | 918 | function checkResource(resource) { |
70 | 922 | return check(context.platform, resource.platform, resource.platforms) |
71 | && check(context.package, resource.package, resource.packages) | |
72 | && check(!!context.combined, resource.combined); | |
73 | } | |
74 | 918 | return checkResource(resource) |
75 | && (!resource.originalResource || checkResource(resource.originalResource)); | |
76 | }; | |
77 | ||
78 | ||
79 | /** | |
80 | * Runs a set of resources through the resource plugin. | |
81 | * | |
82 | * Context state: module | |
83 | * | |
84 | * Plugin Calls: | |
85 | * resource | |
86 | */ | |
87 | 1 | exports.processResources = function(resources, context, callback) { |
88 | 329 | var plugins = context.plugins; |
89 | ||
90 | 329 | async.map(resources, function(resource, callback) { |
91 | 461 | var resourceContext = context.clone(); |
92 | 461 | resourceContext.resource = resource; |
93 | 461 | plugins.resource(resourceContext, function(err, newResource) { |
94 | 461 | if (newResource && newResource !== resource) { |
95 | 103 | newResource.originalResource = resource; |
96 | } | |
97 | ||
98 | 461 | callback(err, newResource); |
99 | }); | |
100 | }, | |
101 | function(err, resources) { | |
102 | 329 | if (err) { |
103 | 1 | return callback(err); |
104 | } | |
105 | ||
106 | 788 | callback(err, resources.filter(function(resource) { return resource; })); |
107 | }); | |
108 | }; | |
109 |
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 | 183 | var packageList, moduleList; |
49 | ||
50 | 183 | function loadPackageList() { |
51 | 183 | if (!config.packages) { |
52 | 137 | config.packages = { web: { name: '' } }; |
53 | } | |
54 | ||
55 | 183 | packageList = _.keys(config.packages); |
56 | } | |
57 | 183 | function loadModuleList() { |
58 | 183 | if (!config.modules) { |
59 | 1 | throw new Error('No modules object defined: ' + JSON.stringify(config, undefined, 2)); |
60 | } | |
61 | 182 | moduleList = _.keys(config.modules); |
62 | } | |
63 | ||
64 | 183 | loadPackageList(); |
65 | 183 | loadModuleList(); |
66 | ||
67 | 182 | return { |
68 | /** @typedef {Object} The raw lumbar file as a JSON object. */ | |
69 | attributes: config, | |
70 | loadPrefix: function() { | |
71 | 53 | 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 | 60 | 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 | 1918 | if (config && config.packages && config.packages[pkg]) { |
95 | 1615 | return config.packages[pkg].combine; |
96 | } | |
97 | 303 | return false; |
98 | }, | |
99 | platformList: function(pkg) { | |
100 | 109 | if (!pkg) { |
101 | 62 | return config.platforms || ['']; |
102 | } else { | |
103 | 47 | if (config.packages[pkg]) { |
104 | 47 | return config.packages[pkg].platforms || this.platformList(); |
105 | } | |
106 | 0 | return this.platformList(); |
107 | } | |
108 | }, | |
109 | ||
110 | moduleList: function(pkg) { | |
111 | 258 | return (config.packages[pkg] || {}).modules || _.keys(config.modules); |
112 | }, | |
113 | ||
114 | module: function(name) { | |
115 | 523 | var ret = config.modules[name]; |
116 | 523 | if (ret) { |
117 | 520 | ret.name = name; |
118 | } | |
119 | 523 | return ret; |
120 | }, | |
121 | isAppModule: function(module) { | |
122 | 76 | var app = config.application; |
123 | 76 | return (app && app.module) === (module.name || module); |
124 | }, | |
125 | scopedAppModuleName: function(module) { | |
126 | 46 | var app = config.application; |
127 | 46 | if (this.isAppModule(module)) { |
128 | 4 | return 'module.exports'; |
129 | } else { | |
130 | 42 | var app = config.application; |
131 | 42 | return app && app.name; |
132 | } | |
133 | }, | |
134 | ||
135 | routeList: function(module) { | |
136 | 28 | 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 && 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 | resources = require('./util/resources'); | |
6 | ||
7 | 1 | function Context(options, config, plugins, libraries, event) { |
8 | 2271 | this._package = options.package; |
9 | 2271 | this._platform = options.platform; |
10 | 2271 | this._plugins = plugins; |
11 | 2271 | this.mode = options.mode; |
12 | 2271 | this.module = options.module; |
13 | 2271 | this.fileConfig = options.fileConfig; |
14 | 2271 | this.resource = options.resource; |
15 | 2271 | this.config = config; |
16 | 2271 | this.libraries = libraries || options.libraries; |
17 | 2271 | this.event = event || options.event; |
18 | } | |
19 | 1 | Context.prototype = { |
20 | fileUtil: fu, | |
21 | ||
22 | clone: function(options) { | |
23 | 2092 | var ret = new Context(this, this.config); |
24 | 2092 | ret.parent = this; |
25 | 2092 | var prototype = Object.keys(Context.prototype); |
26 | 2092 | for (var name in this) { |
27 | 64420 | if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) { |
28 | 37224 | ret[name] = this[name]; |
29 | } | |
30 | } | |
31 | 2092 | if (options) { |
32 | 293 | _.extend(ret, options); |
33 | 293 | ret._package = options.package || this._package; |
34 | 293 | ret._platform = options.platform || this._platform; |
35 | } | |
36 | 2092 | return ret; |
37 | }, | |
38 | ||
39 | fileNamesForModule: function(mode, moduleName, callback) { | |
40 | 89 | var context = this.clone(); |
41 | 89 | context.mode = mode; |
42 | 89 | context.module = moduleName && this.config.module(moduleName); |
43 | 89 | if (moduleName && !context.module) { |
44 | 2 | return callback(new Error('Unknown module "' + moduleName + '"')); |
45 | } | |
46 | ||
47 | 87 | this.plugins.outputConfigs(context, function(err, configs) { |
48 | 87 | if (err) { |
49 | 0 | return callback(err); |
50 | } | |
51 | ||
52 | 87 | async.map(configs, function(config, callback) { |
53 | 113 | var fileContext = context.clone(); |
54 | 113 | fileContext.fileConfig = config; |
55 | 113 | fileContext._plugins.fileName(fileContext, function(err, fileName) { |
56 | 113 | config.fileName = fileName; |
57 | 113 | callback(err, config); |
58 | }); | |
59 | }, | |
60 | callback); | |
61 | }); | |
62 | }, | |
63 | ||
64 | loadResource: function(resource, callback) { | |
65 | 421 | if (!callback) { |
66 | // if only single param, assume as callback and resource from context | |
67 | 0 | resource = this.resource; |
68 | 0 | callback = resource; |
69 | } | |
70 | ||
71 | 421 | var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src}; |
72 | ||
73 | 421 | function loaded(err, data) { |
74 | /*jshint eqnull: true */ | |
75 | 421 | if (err) { |
76 | 4 | if (!err.resourceLoadError) { |
77 | 4 | var json = ''; |
78 | 4 | try { |
79 | // Output JSON for the resource... but protect ourselves from a failure masking a failure | |
80 | 4 | resource = _.clone(resource.originalResource || resource); |
81 | 4 | delete resource.library; |
82 | 4 | delete resource.enoent; |
83 | 4 | json = '\n\t' + JSON.stringify(resource); |
84 | } catch (err) { /* NOP */ } | |
85 | ||
86 | 4 | var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err)); |
87 | 4 | errorWrapper.stack = errorWrapper.message + ' ' + (err.stack || err); |
88 | 4 | errorWrapper.source = err; |
89 | 4 | errorWrapper.code = err.code; |
90 | 4 | errorWrapper.resourceLoadError = true; |
91 | 4 | err = errorWrapper; |
92 | } | |
93 | 4 | callback(err); |
94 | 4 | return; |
95 | } | |
96 | 417 | fileInfo.inputs = data.inputs; |
97 | 417 | fileInfo.generated = data.generated; |
98 | 417 | fileInfo.noSeparator = data.noSeparator; |
99 | 417 | fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings; |
100 | 417 | fileInfo.content = data.data != null ? data.data : data; |
101 | ||
102 | // Ensure that we dump off the stack | |
103 | 417 | _.defer(function() { |
104 | 417 | callback(err, fileInfo); |
105 | }); | |
106 | } | |
107 | ||
108 | 421 | if (typeof resource === 'function') { |
109 | 202 | resource(this, loaded); |
110 | 219 | } else if (resource.src) { |
111 | // Assume a file page, attempt to load | |
112 | 206 | fu.readFile(resource.src, loaded); |
113 | } else { | |
114 | 13 | loaded(undefined, {data: '', noSeparator: true, inputs: resource.dir ? [resource.dir] : []}); |
115 | } | |
116 | ||
117 | 421 | return fileInfo; |
118 | }, | |
119 | ||
120 | outputFile: function(writer, callback) { | |
121 | 144 | var context = this; |
122 | 144 | context.plugins.file(context, function(err) { |
123 | 144 | if (err) { |
124 | 0 | return callback(err); |
125 | } | |
126 | ||
127 | 144 | context.plugins.fileName(context, function(err, fileName) { |
128 | 144 | if (err) { |
129 | 0 | return callback(err); |
130 | } | |
131 | ||
132 | 144 | context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension; |
133 | 144 | context.fileName = context.outdir + '/' + context.buildPath; |
134 | 144 | writer(function(err, data) { |
135 | 144 | data = _.defaults({ |
136 | fileConfig: context.fileConfig, | |
137 | platform: context.platform, | |
138 | package: context.package, | |
139 | mode: context.mode | |
140 | }, data); | |
141 | ||
142 | 144 | if (err) { |
143 | 3 | fs.unlink(context.fileName, function() { /* NOP To Prevent warning */}); |
144 | 3 | data.error = err; |
145 | } | |
146 | 144 | context.event.emit('output', data); |
147 | ||
148 | 144 | context.fileCache = undefined; |
149 | 144 | callback(err, data); |
150 | }); | |
151 | }); | |
152 | }); | |
153 | }, | |
154 | ||
155 | get description() { | |
156 | 547 | var ret = 'package:' + this.package + '_platform:' + this.platform; |
157 | 547 | if (this.mode) { |
158 | 350 | ret += '_mode:' + this.mode; |
159 | } | |
160 | 547 | if (this.fileName) { |
161 | 115 | ret += '_config:' + this.fileName; |
162 | } | |
163 | 547 | if (this.module) { |
164 | 329 | ret += '_module:' + (this.module.name || this.module); |
165 | } | |
166 | 547 | if (this.resource) { |
167 | // TODO : Anything better for this? | |
168 | 19 | ret += '_resource:' + resources.source(this.resource); |
169 | } | |
170 | 547 | return ret; |
171 | }, | |
172 | ||
173 | 1988 | get plugins() { return this._plugins; }, |
174 | ||
175 | 5961 | get package() { return this._package; }, |
176 | 4329 | get platform() { return this._platform; }, |
177 | get platformPath() { | |
178 | 156 | return this.platform ? this.platform + '/' : ''; |
179 | }, | |
180 | ||
181 | get combined() { | |
182 | 1918 | return this.config.combineModules(this.package); |
183 | }, | |
184 | get baseName() { | |
185 | 231 | if (!this.combined) { |
186 | 163 | return this.module.name; |
187 | } else { | |
188 | 68 | return (this.config.attributes.packages[this.package] || {}).name || this.package; |
189 | } | |
190 | }, | |
191 | ||
192 | get resources() { | |
193 | 289 | if (this.parent) { |
194 | 0 | return this.parent.resources; |
195 | } else { | |
196 | 289 | return this._resources; |
197 | } | |
198 | }, | |
199 | set resources(value) { | |
200 | 362 | if (this.parent) { |
201 | 326 | delete this.parent; |
202 | } | |
203 | 362 | this._resources = value; |
204 | } | |
205 | }; | |
206 | ||
207 | 1 | module.exports = Context; |
208 |
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 | resources = require('./util/resources'); | |
7 | ||
8 | 1 | const EMFILE_RETRY = 250; |
9 | ||
10 | 1 | var fileCache = {}; |
11 | ||
12 | 1 | exports = module.exports = new EventEmitter(); |
13 | ||
14 | 1 | function cacheRead(path, exec, callback) { |
15 | 352 | path = exports.resolvePath(path); |
16 | ||
17 | 352 | var cache = fileCache[path]; |
18 | 352 | if (cache) { |
19 | 192 | if (cache.data) { |
20 | 99 | callback(undefined, cache); |
21 | } else { | |
22 | 93 | cache.pending.push(callback); |
23 | } | |
24 | 192 | return; |
25 | } | |
26 | ||
27 | 160 | cache = fileCache[path] = { |
28 | pending: [callback], | |
29 | artifacts: {} | |
30 | }; | |
31 | ||
32 | 160 | exec(path, function _callback(err, data) { |
33 | 160 | if (err && err.code === 'EMFILE') { |
34 | 0 | setTimeout(exec.bind(this, path, _callback), EMFILE_RETRY); |
35 | } else { | |
36 | 160 | if (err) { |
37 | 2 | delete fileCache[path]; |
38 | } | |
39 | ||
40 | 160 | cache.data = data; |
41 | 160 | cache.pending.forEach(function(callback) { |
42 | 253 | callback(err, cache); |
43 | }); | |
44 | 160 | exports.emit('cache:set', path); |
45 | } | |
46 | }); | |
47 | } | |
48 | ||
49 | 1 | exports.resetCache = function(filePath) { |
50 | 259 | filePath = filePath && path.normalize(filePath); |
51 | 259 | exports.emit('cache:reset', filePath); |
52 | ||
53 | 259 | if (filePath) { |
54 | 177 | filePath = exports.resolvePath(filePath); |
55 | 177 | delete fileCache[filePath]; |
56 | } else { | |
57 | 82 | fileCache = {}; |
58 | } | |
59 | }; | |
60 | ||
61 | 1 | var lookupPath; |
62 | 1 | exports.resolvePath = function(pathName) { |
63 | // Poormans path.resolve. We aren't able to use the bundled path.resolve due to | |
64 | // it throwing sync EMFILE errors without a type to key on. | |
65 | 1459 | if (lookupPath |
66 | && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1) | |
67 | && pathName.indexOf(lookupPath) !== 0) { | |
68 | 974 | return lookupPath + pathName; |
69 | } else { | |
70 | 485 | return pathName; |
71 | } | |
72 | }; | |
73 | 1 | exports.makeRelative = function(pathName) { |
74 | 597 | if (pathName.indexOf(lookupPath) === 0) { |
75 | 565 | return pathName.substring(lookupPath.length); |
76 | } else { | |
77 | 32 | return pathName; |
78 | } | |
79 | }; | |
80 | ||
81 | 1 | exports.lookupPath = function(pathName) { |
82 | 156 | if (pathName !== undefined) { |
83 | 94 | lookupPath = pathName; |
84 | 94 | if (lookupPath && !/\/$/.test(lookupPath)) { |
85 | 38 | lookupPath += '/'; |
86 | } | |
87 | } | |
88 | 156 | return lookupPath; |
89 | }; | |
90 | ||
91 | 1 | exports.stat = function(file, callback) { |
92 | 826 | fs.stat(file, function(err, stat) { |
93 | 826 | if (err && err.code === 'EMFILE') { |
94 | 0 | setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY); |
95 | } else { | |
96 | 826 | callback(err, stat); |
97 | } | |
98 | }); | |
99 | }; | |
100 | ||
101 | 1 | exports.readFileSync = function(file) { |
102 | 37 | return fs.readFileSync(exports.resolvePath(file)); |
103 | }; | |
104 | 1 | exports.readFile = function(file, callback) { |
105 | 230 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
106 | 230 | callback(err, cache && cache.data); |
107 | }); | |
108 | }; | |
109 | 1 | exports.readFileArtifact = function(file, name, callback) { |
110 | 61 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
111 | 61 | var artifacts = cache.artifacts; |
112 | 61 | callback(err, {data: cache.data, artifact: artifacts[name]}); |
113 | }); | |
114 | }; | |
115 | 1 | exports.setFileArtifact = function(path, name, artifact) { |
116 | 27 | path = exports.resolvePath(path); |
117 | ||
118 | 27 | var cache = fileCache[path]; |
119 | 27 | if (cache) { |
120 | 27 | cache.artifacts[name] = artifact; |
121 | } | |
122 | }; | |
123 | ||
124 | 1 | exports.readdir = function(dir, callback) { |
125 | 61 | cacheRead(dir, fs.readdir.bind(fs), function(err, cache) { |
126 | 61 | callback(err, cache && cache.data); |
127 | }); | |
128 | }; | |
129 | ||
130 | 1 | exports.ensureDirs = function(pathname, callback) { |
131 | 245 | var dirname = path.dirname(pathname); |
132 | 245 | exports.stat(dirname, function(err) { |
133 | 245 | if (err && err.code === 'ENOENT') { |
134 | // If we don't exist, check to see if our parent exists before trying to create ourselves | |
135 | 42 | exports.ensureDirs(dirname, function() { |
136 | 42 | fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) { |
137 | 42 | if (err && err.code === 'EMFILE') { |
138 | 0 | setTimeout(fs.mkdir.bind(fs, dirname, parseInt('0755', 8), _callback), EMFILE_RETRY); |
139 | } else { | |
140 | // Off to the races... and we lost. | |
141 | 42 | callback(err && err.code === 'EEXIST' ? undefined : err); |
142 | } | |
143 | }); | |
144 | }); | |
145 | } else { | |
146 | 203 | callback(); |
147 | } | |
148 | }); | |
149 | }; | |
150 | ||
151 | 1 | exports.writeFile = function(file, data, callback) { |
152 | 136 | exports.resetCache(file); |
153 | ||
154 | 136 | exports.ensureDirs(file, function(err) { |
155 | 136 | if (err) { |
156 | 0 | return callback(err); |
157 | } | |
158 | ||
159 | 136 | fs.writeFile(file, data, 'utf8', function _callback(err) { |
160 | 136 | if (err && err.code === 'EMFILE') { |
161 | 0 | setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY); |
162 | } else { | |
163 | 136 | callback(err); |
164 | } | |
165 | }); | |
166 | }); | |
167 | }; | |
168 | ||
169 | /** | |
170 | * Takes a given input and returns the files that are represented. | |
171 | * | |
172 | * pathname may be: | |
173 | * a resource object | |
174 | * a path on the file system | |
175 | * an array of resources | |
176 | */ | |
177 | 1 | exports.fileList = function(pathname, extension, callback, dirList, resource, srcDir) { |
178 | 886 | if (_.isFunction(extension)) { |
179 | 5 | callback = extension; |
180 | 5 | extension = /.*/; |
181 | } | |
182 | ||
183 | 886 | if (_.isArray(pathname)) { |
184 | 307 | var files = pathname; |
185 | 307 | pathname = ''; |
186 | 307 | if (!files.length) { |
187 | 123 | return callback(undefined, []); |
188 | } | |
189 | 184 | return handleFiles(false, undefined, _.uniq(files)); |
190 | 579 | } else if (!dirList) { |
191 | 418 | if (pathname.src) { |
192 | 0 | resource = resource || pathname; |
193 | 0 | pathname = pathname.src; |
194 | } | |
195 | ||
196 | 418 | pathname = exports.resolvePath(pathname); |
197 | } | |
198 | 579 | if (resource && resource.src) { |
199 | 193 | resource = _.clone(resource); |
200 | 193 | delete resource.src; |
201 | } | |
202 | ||
203 | 579 | function handleFiles(dirname, err, files, srcDir) { |
204 | 242 | if (err) { |
205 | 0 | return callback(err); |
206 | } | |
207 | ||
208 | 242 | var ret = [], |
209 | count = 0, | |
210 | expected = files.length, | |
211 | prefix = pathname ? pathname.replace(/\/$/, '') + '/' : ''; | |
212 | ||
213 | 242 | function complete(files, index) { |
214 | 614 | count++; |
215 | ||
216 | 614 | ret[index] = files; |
217 | ||
218 | 614 | if (count === expected) { |
219 | 241 | ret = _.flatten(ret); |
220 | ||
221 | 241 | if (srcDir) { |
222 | 57 | ret = ret.map(function(file) { |
223 | 124 | file = resources.cast(file); |
224 | 124 | file.srcDir = srcDir; |
225 | 124 | return file; |
226 | }); | |
227 | } | |
228 | ||
229 | 241 | if (dirname) { |
230 | 57 | ret.push(_.defaults({dir: dirname}, resource)); |
231 | 57 | ret = ret.sort(function(a, b) { |
232 | 241 | return resources.source(a).localeCompare(resources.source(b)); |
233 | }); | |
234 | } | |
235 | ||
236 | 241 | callback(undefined, ret); |
237 | } | |
238 | } | |
239 | ||
240 | 242 | if (!files.length) { |
241 | 1 | callback(undefined, []); |
242 | } | |
243 | ||
244 | 242 | files.forEach(function(file, index) { |
245 | 614 | var fileResource = resource; |
246 | 614 | if (file.src) { |
247 | 193 | fileResource = resource || file; |
248 | 193 | file = file.src; |
249 | 421 | } else if (_.isObject(file)) { |
250 | 64 | complete(file, index); |
251 | 64 | return; |
252 | } | |
253 | ||
254 | 550 | exports.fileList(prefix + file, extension, function(err, files) { |
255 | 550 | if (err) { |
256 | 0 | callback(err); |
257 | 0 | return; |
258 | } | |
259 | ||
260 | 550 | complete(files, index); |
261 | }, dirname, fileResource, srcDir); | |
262 | }); | |
263 | } | |
264 | ||
265 | 579 | exports.stat(pathname, function(err, stat) { |
266 | 579 | if (err) { |
267 | 69 | if (err.code === 'ENOENT') { |
268 | 69 | callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]); |
269 | } else { | |
270 | 0 | callback(err); |
271 | } | |
272 | 69 | return; |
273 | } | |
274 | ||
275 | 510 | if (stat.isDirectory()) { |
276 | 58 | exports.readdir(pathname, function(err, files) { |
277 | 58 | var _pathname = exports.makeRelative(pathname); |
278 | 58 | handleFiles(_pathname, undefined, files, srcDir || _pathname); |
279 | }); | |
280 | } else { | |
281 | 452 | pathname = exports.makeRelative(pathname); |
282 | ||
283 | 452 | var basename = path.basename(pathname), |
284 | namePasses = basename[0] !== '.' && basename !== 'vendor' && (!dirList || extension.test(pathname)), | |
285 | ret = []; | |
286 | 452 | if (namePasses) { |
287 | 394 | if (resource) { |
288 | 170 | ret = [ _.defaults({src: pathname, srcDir: srcDir}, resource) ]; |
289 | 224 | } else if (srcDir) { |
290 | 71 | ret = [ { src: pathname, srcDir: srcDir } ]; |
291 | } else { | |
292 | 153 | ret = [ pathname ]; |
293 | } | |
294 | } | |
295 | 452 | callback(undefined, ret); |
296 | } | |
297 | }); | |
298 | }; | |
299 | ||
300 | //accepts a template string or a filename ending in .handlebars | |
301 | 1 | exports.loadTemplate = function(template, splitOnDelimiter, callback) { |
302 | 44 | function compile(templateStr, callback) { |
303 | 33 | try { |
304 | 33 | if (splitOnDelimiter) { |
305 | 20 | callback(null, templateStr.split(splitOnDelimiter).map(function(bit) { |
306 | 40 | return handlebars.compile(bit); |
307 | })); | |
308 | } else { | |
309 | 13 | callback(null, handlebars.compile(templateStr)); |
310 | } | |
311 | } catch (e) { | |
312 | 1 | callback(e); |
313 | } | |
314 | } | |
315 | 44 | if (template.match(/\.handlebars$/)) { |
316 | 19 | exports.readFileArtifact(template, 'template', function(err, data) { |
317 | 19 | if (err) { |
318 | 1 | return callback(err); |
319 | } | |
320 | ||
321 | 18 | if (data.artifact) { |
322 | 10 | callback(undefined, data.artifact); |
323 | } else { | |
324 | 8 | compile(data.data.toString(), function(err, data) { |
325 | 8 | if (!err) { |
326 | 8 | exports.setFileArtifact(template, 'template', data); |
327 | } | |
328 | 8 | callback(err, data); |
329 | }); | |
330 | } | |
331 | }); | |
332 | } else { | |
333 | 25 | compile(template, callback); |
334 | } | |
335 | }; | |
336 |
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', {logId: '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 | resources = require('./util/resources'); | |
9 | ||
10 | 1 | function Libraries(options) { |
11 | 152 | this.options = options; |
12 | 152 | this.mixins = []; |
13 | 152 | this.configs = []; |
14 | } | |
15 | ||
16 | 1 | Libraries.prototype.initialize = function(context, callback) { |
17 | 149 | this.mixins = []; |
18 | 149 | this.originalConfig = _.clone(context.config.attributes); |
19 | ||
20 | 149 | function normalize(libraries) { |
21 | 298 | if (_.isString(libraries)) { |
22 | 2 | return [libraries]; |
23 | } else { | |
24 | 296 | return _.map(libraries, function (name) { |
25 | 83 | if (_.isString(name)) { |
26 | 11 | return path.normalize(name); |
27 | } else { | |
28 | 72 | return name; |
29 | } | |
30 | }); | |
31 | } | |
32 | } | |
33 | ||
34 | 149 | var commandLineLibraries = normalize(this.options.libraries || []), |
35 | configLibraries = normalize(context.config.attributes.libraries || context.config.attributes.mixins || []), | |
36 | bowerLibraries = this.bowerLibraries(context) || [], | |
37 | ||
38 | allLibraries = _.union(commandLineLibraries, configLibraries, bowerLibraries); | |
39 | ||
40 | 149 | delete context.config.attributes.mixins; |
41 | ||
42 | 149 | async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback); |
43 | }; | |
44 | ||
45 | 1 | Libraries.prototype.bowerLibraries = function(context) { |
46 | 150 | try { |
47 | 150 | fs.statSync(fu.resolvePath('bower.json')); |
48 | ||
49 | 2 | var bowerDir = bower.config.directory, |
50 | possibleModules = fs.readdirSync(bowerDir); | |
51 | ||
52 | 1 | return possibleModules |
53 | .map(function(name) { | |
54 | 3 | return path.normalize(path.join(bowerDir, name)); |
55 | }) | |
56 | .filter(function(name) { | |
57 | 3 | try { |
58 | 3 | fs.statSync(path.join(name, 'lumbar.json')); |
59 | 2 | return true; |
60 | } catch (err) { | |
61 | /* NOP */ | |
62 | } | |
63 | }); | |
64 | } catch (err) { | |
65 | 149 | context.event.emit('debug', err); |
66 | } | |
67 | }; | |
68 | ||
69 | 1 | Libraries.prototype.load = function(context, libraryConfig, callback) { |
70 | // Allow mixins to be passed directly | |
71 | 88 | var root = libraryConfig.root, |
72 | configPath, | |
73 | self = this; | |
74 | ||
75 | // Or as a file reference | |
76 | 88 | if (!_.isObject(libraryConfig)) { |
77 | 8 | root = root || libraryConfig; |
78 | ||
79 | // If we have a dir then pull lumbar.json from that | |
80 | 8 | try { |
81 | 8 | var stat = fs.statSync(fu.resolvePath(libraryConfig)); |
82 | 8 | if (stat.isDirectory()) { |
83 | 3 | libraryConfig = libraryConfig + '/lumbar.json'; |
84 | 5 | } else if (root === libraryConfig) { |
85 | // If we are a file the root should be the file's directory unless explicitly passed | |
86 | 5 | root = path.dirname(root); |
87 | } | |
88 | } catch (err) { | |
89 | 0 | return callback(err); |
90 | } | |
91 | ||
92 | 8 | configPath = fu.resolvePath(libraryConfig); |
93 | 8 | libraryConfig = config.readConfig(configPath); |
94 | } | |
95 | ||
96 | // To make things easy force root to be a dir | |
97 | 88 | if (root && !/\/$/.test(root)) { |
98 | 29 | root = root + '/'; |
99 | } | |
100 | ||
101 | 88 | if (!libraryConfig.name) { |
102 | 4 | return callback(new Error('Mixin with root "' + root + '" is missing a name.')); |
103 | } | |
104 | ||
105 | 84 | var mixins = libraryConfig.mixins, |
106 | toRegister = {}; | |
107 | 84 | delete libraryConfig.mixins; |
108 | ||
109 | 84 | function mapMixin(mixin, name) { |
110 | // Only register once, giving priority to an explicitly defined mixin | |
111 | 64 | if (!toRegister[name]) { |
112 | 63 | toRegister[name] = { |
113 | serialize: function() { | |
114 | 0 | return {name: this.name, library: this.parent.name}; |
115 | }, | |
116 | name: name, | |
117 | attributes: mixin, | |
118 | parent: libraryConfig, | |
119 | root: root | |
120 | }; | |
121 | } | |
122 | } | |
123 | ||
124 | // Read each of the mixins that are defined in the config | |
125 | 84 | _.each(mixins, mapMixin, this); |
126 | ||
127 | // Make mixin modules accessible as normal mixins as well | |
128 | 84 | _.each(libraryConfig.modules, mapMixin, this); |
129 | ||
130 | // After we've pulled everything in register | |
131 | 84 | _.each(toRegister, function(mixin, name) { |
132 | 63 | this.mixins[name] = this.mixins[name] || []; |
133 | 63 | var list = this.mixins[name]; |
134 | 63 | list.push(mixin); |
135 | }, this); | |
136 | ||
137 | // Run all of the plugins that are concerned with this. | |
138 | 84 | libraryConfig.root = root; |
139 | 84 | libraryConfig.path = configPath; |
140 | 84 | context.loadedLibrary = libraryConfig; |
141 | 84 | context.plugins.loadMixin(context, function(err) { |
142 | 84 | delete libraryConfig.root; |
143 | ||
144 | // And then splat everything else into our config | |
145 | 84 | _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path')); |
146 | ||
147 | 84 | libraryConfig.serialize = function() { |
148 | 0 | return { library: this.name }; |
149 | }; | |
150 | ||
151 | 84 | libraryConfig.root = root; |
152 | 84 | self.configs.push(libraryConfig); |
153 | ||
154 | 84 | callback(err); |
155 | }); | |
156 | }; | |
157 | ||
158 | 1 | Libraries.prototype.findDecl = function(mixins, mixinName) { |
159 | 22 | if (!mixinName.name) { |
160 | 2 | mixinName = {name: mixinName}; |
161 | } | |
162 | ||
163 | 22 | return _.find(mixins, function(mixinDecl) { |
164 | 26 | return (mixinDecl.name || mixinDecl) === mixinName.name |
165 | && (!mixinDecl.library || mixinDecl.library === mixinName.library); | |
166 | }); | |
167 | }; | |
168 | ||
169 | 1 | Libraries.prototype.moduleMixins = function(module) { |
170 | // Perform any nested mixin lookup | |
171 | 330 | var mixins = _.clone(module.mixins || []), |
172 | processed = {}; | |
173 | 330 | for (var i = 0; i < mixins.length; i++) { |
174 | 120 | var firstInclude = mixins[i], |
175 | mixinConfig = firstInclude.name && firstInclude, | |
176 | mixin = this.getMixin(firstInclude), | |
177 | added = [i, 0]; | |
178 | ||
179 | // Save a config object off for propagation to included mixins | |
180 | 118 | if (mixinConfig) { |
181 | 52 | mixinConfig = _.omit(mixinConfig, 'overrides', 'name', 'library'); |
182 | } | |
183 | ||
184 | 118 | if (!mixin) { |
185 | 0 | throw new Error('Unable to find mixin "' + ((firstInclude && firstInclude.name) || firstInclude) + '"'); |
186 | } | |
187 | ||
188 | // Check if we need to include any modules that this defined | |
189 | 118 | var processedName = mixin.name + '_' + (mixin.parent && mixin.parent.name); |
190 | 118 | if (!processed[processedName]) { |
191 | 96 | processed[processedName] = true; |
192 | ||
193 | 96 | _.each(mixin.attributes.mixins, function(mixinInclude) { |
194 | // Apply any attributes that were applied to the mixin config here | |
195 | 22 | if (mixinConfig) { |
196 | 8 | mixinInclude = mixinInclude.name ? _.clone(mixinInclude) : {name: mixinInclude}; |
197 | 8 | _.extend(mixinInclude, mixinConfig); |
198 | } | |
199 | ||
200 | // Save the library that caused the include so we can lookup the root and reverse | |
201 | // any overrides in the future. | |
202 | 22 | if (firstInclude.overrides) { |
203 | 4 | mixinInclude.overrideLibrary = _.extend({root: mixin.parent.root}, firstInclude); |
204 | } else { | |
205 | 18 | mixinInclude.overrideLibrary = mixin.parent; |
206 | } | |
207 | ||
208 | 22 | if (!this.findDecl(mixins, mixinInclude)) { |
209 | 22 | added.push(mixinInclude); |
210 | } | |
211 | }, this); | |
212 | } | |
213 | ||
214 | // If we've found any new mixins insert them at the current spot and iterate | |
215 | // over those items | |
216 | 118 | if (added.length > 2) { |
217 | 22 | mixins.splice.apply(mixins, added); |
218 | 22 | i--; |
219 | } | |
220 | } | |
221 | ||
222 | // Extend the module with each of the mixins content, giving priority to the module | |
223 | 328 | return _.map(mixins.reverse(), function(mixin) { |
224 | 96 | var mixinConfig = mixin.name && mixin, |
225 | name = mixin; | |
226 | 96 | if (mixinConfig) { |
227 | 44 | mixinConfig = _.clone(mixinConfig); |
228 | 44 | delete mixinConfig.library; |
229 | 44 | delete mixinConfig.container; |
230 | } | |
231 | 96 | mixin = _.extend( |
232 | {}, | |
233 | this.getMixin(name), | |
234 | mixinConfig); | |
235 | 96 | if (!mixin.attributes) { |
236 | 0 | throw new Error('Mixin "' + (name.name || name) + '" is not defined.'); |
237 | } | |
238 | ||
239 | // Save a distinct instance of the config for resource extension | |
240 | 96 | if (mixinConfig) { |
241 | 44 | mixinConfig = _.clone(mixinConfig); |
242 | 44 | delete mixinConfig.overrides; |
243 | 44 | delete mixinConfig.name; |
244 | } | |
245 | ||
246 | 96 | return { |
247 | library: mixin, | |
248 | mixinConfig: mixinConfig | |
249 | }; | |
250 | }, this); | |
251 | }; | |
252 | ||
253 | 1 | Libraries.prototype.mapFiles = function(value, library, config) { |
254 | 182 | var files = _.map(value, function(resource) { |
255 | 280 | return this.mapFile(resource, library, config); |
256 | }, this); | |
257 | 460 | files = _.filter(files, function(file) { return file; }); |
258 | ||
259 | 181 | return files; |
260 | }; | |
261 | 1 | Libraries.prototype.mapFile = function(resource, library, config) { |
262 | // If explicitly declared the resource library takes precedence | |
263 | 331 | if (_.isString(resource.library || resource.mixin)) { |
264 | 3 | library = this.getConfig(resource.library || resource.mixin); |
265 | 3 | if (!library) { |
266 | 1 | throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found'); |
267 | } | |
268 | 2 | delete resource.mixin; |
269 | } | |
270 | ||
271 | 330 | return resources.map(resource, library, config); |
272 | }; | |
273 | ||
274 | 1 | Libraries.prototype.mapPathToLibrary = function(src, library) { |
275 | 39 | return resources.pathToLibrary(src, library); |
276 | }; | |
277 | ||
278 | 1 | Libraries.prototype.getMixin = function(name) { |
279 | 216 | var mixins = (this.mixins && this.mixins[name.name || name]) || [], |
280 | library = name.library || name.container; | |
281 | 216 | if (mixins.length > 1 && !library) { |
282 | 1 | throw new Error( |
283 | 'Duplicate mixins found for "' + (name.name || name) + '"' | |
284 | + _.map(mixins, function(mixin) { | |
285 | 2 | return ' parent: "' + mixin.parent.name + '"'; |
286 | }).join('')); | |
287 | } | |
288 | ||
289 | 215 | if (library) { |
290 | 9 | if (name.name === undefined) { |
291 | 0 | var found = _.find(this.configs, function(config) { |
292 | 0 | return config.name === library; |
293 | }); | |
294 | 0 | if (!found) { |
295 | 0 | throw new Error('Unable to find library "' + library + '"'); |
296 | } | |
297 | 0 | return found; |
298 | } | |
299 | ||
300 | 9 | var found = _.find(mixins, function(mixin) { |
301 | 17 | return mixin.parent.name === library; |
302 | }); | |
303 | 9 | if (found) { |
304 | 8 | return found; |
305 | } else { | |
306 | 1 | throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"'); |
307 | } | |
308 | 206 | } else if (mixins.length === 1) { |
309 | 206 | return mixins[0]; |
310 | } | |
311 | }; | |
312 | 1 | Libraries.prototype.getConfig = function(name) { |
313 | 13 | return _.find(this.configs, function(config) { return config.name === name; }); |
314 | }; | |
315 | ||
316 | 1 | Libraries.prototype.mergeHash = function(hashName, input, mixin, output) { |
317 | 90 | if (mixin[hashName]) { |
318 | // Close the value to make sure that we are not overriding anything | |
319 | 11 | if (!output[hashName] || output[hashName] === input[hashName]) { |
320 | 9 | output[hashName] = _.clone(input[hashName] || {}); |
321 | } | |
322 | 11 | _.each(mixin[hashName], function(value, key) { |
323 | 17 | if (!input[hashName] || !(key in input[hashName])) { |
324 | 13 | output[hashName][key] = value; |
325 | } | |
326 | }); | |
327 | 11 | return true; |
328 | } | |
329 | }; | |
330 | 1 | Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) { |
331 | 44 | if (mixinData[fieldName]) { |
332 | 10 | mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]]; |
333 | ||
334 | 10 | var configData = input[fieldName] || []; |
335 | 10 | if (!output[fieldName] || configData === output[fieldName]) { |
336 | 8 | output[fieldName] = _.clone(configData); |
337 | } | |
338 | 10 | if (!_.isArray(configData)) { |
339 | 2 | configData = [configData]; |
340 | } | |
341 | 10 | if (!_.isArray(output[fieldName])) { |
342 | 1 | output[fieldName] = [output[fieldName]]; |
343 | } | |
344 | ||
345 | // Insert point is at the start of the upstream list, which we are | |
346 | // assuming occurs at length postions from the end. | |
347 | 10 | _.each(mixinData, function(value) { |
348 | //Make the include relative to the mixin | |
349 | 13 | value = (library.root || '') + value; |
350 | ||
351 | 13 | output[fieldName].splice( |
352 | output[fieldName].length - configData.length, | |
353 | 0, | |
354 | {src: value, library: library}); | |
355 | }); | |
356 | ||
357 | 10 | return true; |
358 | } | |
359 | }; | |
360 | ||
361 | 1 | module.exports = Libraries; |
362 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | ChildPool = require('child-pool'), | |
4 | Context = require('./context'), | |
5 | EventEmitter = require('events').EventEmitter, | |
6 | fs = require('fs'), | |
7 | stateMachine = require('./state-machine'), | |
8 | WatchManager = require('./watch-manager'); | |
9 | ||
10 | 1 | exports.build = require('./build'); |
11 | 1 | exports.fileUtil = require('./fileUtil'); |
12 | 1 | exports.plugin = require('./plugin').plugin; |
13 | 1 | exports.combine = require('./jsCombine').combine; |
14 | 1 | exports.config = require('./config'); |
15 | ||
16 | /** | |
17 | * | |
18 | * @name init | |
19 | * @function This function initializes a Lumbar instance | |
20 | * @param {string} lumbarFile The lumbarFile is the main | |
21 | * file. Its responsible to define all the platforms, | |
22 | * packages, modules, and templates for Lumbar to use. | |
23 | * @param {Object} options supports the following options: | |
24 | * packageConfigFile (string): name of the package config file. | |
25 | * outdir (string): path to directory of where to output the files. | |
26 | * minimize (boolean): Should we minimize the files? | |
27 | * @return {Object.<Function>} | |
28 | */ | |
29 | 1 | exports.init = function(lumbarFile, options) { |
30 | // Clone so we can mutate in the use API | |
31 | 30 | options = _.clone(options || {}); |
32 | 30 | options.plugins = _.clone(options.plugins || []); |
33 | ||
34 | 30 | function logError(err) { |
35 | 57 | if (err) { |
36 | 3 | event.emit('error', err); |
37 | } | |
38 | } | |
39 | ||
40 | 30 | var event = new EventEmitter(), |
41 | watch, | |
42 | watchContext; | |
43 | ||
44 | 30 | function watchOutputHandler(status) { |
45 | 102 | if (!watch) { |
46 | // We've been cleaned up but residuals may still exist, do nothing on this exec | |
47 | 14 | return; |
48 | } | |
49 | ||
50 | 88 | if (status.fileConfig.isPrimary) { |
51 | 36 | delete status.fileConfig; |
52 | 52 | } else if (status.fileConfig.isPrimary === false) { |
53 | // This config is directly linked to another meaning we don't want to watch on it as | |
54 | // it will be rebuilt. | |
55 | 11 | return; |
56 | } | |
57 | ||
58 | 77 | var originalContext = watchContext; |
59 | 77 | watch.moduleOutput(status, function() { |
60 | 35 | if (watchContext !== originalContext) { |
61 | // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch) | |
62 | 0 | return; |
63 | } | |
64 | ||
65 | 35 | stateMachine.loadPlatform(watchContext.clone(status), function(err, contexts) { |
66 | 35 | if (err) { |
67 | 0 | return logError(err); |
68 | } | |
69 | ||
70 | 35 | stateMachine.buildContexts(contexts, logError); |
71 | }); | |
72 | }); | |
73 | } | |
74 | ||
75 | 30 | return _.extend(event, { |
76 | use: function(plugin) { | |
77 | // Only has impact before exec | |
78 | 0 | options.plugins.push(plugin); |
79 | }, | |
80 | ||
81 | moduleMap: function(packageName, callback) { | |
82 | 5 | if (!callback) { |
83 | 1 | callback = packageName; |
84 | 1 | packageName = undefined; |
85 | } | |
86 | ||
87 | 5 | stateMachine.loadConfig(lumbarFile, event, options, function(err, rootContext) { |
88 | 5 | if (err) { |
89 | 1 | return callback(err); |
90 | } | |
91 | ||
92 | 4 | rootContext.mode = 'scripts'; |
93 | 4 | stateMachine.loadPackages(rootContext, packageName, function(err, contexts) { |
94 | 4 | if (err) { |
95 | 1 | return callback(err); |
96 | } | |
97 | ||
98 | 3 | async.forEach(_.keys(contexts), function(packageName, callback) { |
99 | 3 | var package = contexts[packageName]; |
100 | 3 | async.forEach(_.keys(package), function(platformName, callback) { |
101 | 4 | var platform = package[platformName], |
102 | context = platform[0]; | |
103 | ||
104 | 4 | rootContext.plugins.get('module-map').buildMap(context, function(err, map) { |
105 | 4 | if (!err) { |
106 | 4 | package[platformName] = map; |
107 | } | |
108 | 4 | callback(err); |
109 | }); | |
110 | }, | |
111 | callback); | |
112 | }, | |
113 | function(err) { | |
114 | 3 | callback(err, contexts); |
115 | }); | |
116 | }); | |
117 | }); | |
118 | }, | |
119 | ||
120 | /** | |
121 | * | |
122 | * @name build | |
123 | * @function This function builds out the package(s). | |
124 | * @param {string} packageName the name of the package listed under | |
125 | * 'packages' from the lumbarFile passed in during the call to init(). | |
126 | * @param {Function} callback the node process Function | |
127 | */ | |
128 | build: function(packageName, modules, callback) { | |
129 | 11 | stateMachine.loadAndInitDir(lumbarFile, event, options, function(err, rootContext) { |
130 | 11 | if (err) { |
131 | 0 | if (!callback) { |
132 | 0 | throw err; |
133 | } | |
134 | 0 | return callback(err); |
135 | } | |
136 | ||
137 | 11 | stateMachine.buildPackages(rootContext, packageName, modules, callback); |
138 | }); | |
139 | }, | |
140 | watch: function(packageName, modules, callback) { | |
141 | 18 | if (!fs.watch) { |
142 | 0 | throw new Error('Watch requires fs.watch, introduced in Node v0.6.0'); |
143 | } | |
144 | ||
145 | 18 | ChildPool.isBackground(true); |
146 | ||
147 | 18 | watch = new WatchManager(); |
148 | 18 | watch.on('watch-change', function(info) { |
149 | 43 | event.emit('watch-change', info); |
150 | }); | |
151 | ||
152 | 18 | var self = this; |
153 | 18 | stateMachine.loadAndInitDir(lumbarFile, event, options, function(err, rootContext) { |
154 | 18 | if (err) { |
155 | 0 | logError(err); |
156 | } | |
157 | ||
158 | 18 | if (!callback) { |
159 | 18 | callback = modules; |
160 | 18 | modules = undefined; |
161 | } | |
162 | ||
163 | 18 | watchContext = rootContext; |
164 | ||
165 | // Watch for changes in the config file | |
166 | 23 | var mixinPaths = _.filter(_.pluck(rootContext.libraries.configs, 'path'), function(path) { return path; }); |
167 | 18 | watch.configFile(lumbarFile, mixinPaths, function() { |
168 | 4 | watchContext = undefined; |
169 | 4 | self.watch(packageName, callback); |
170 | }); | |
171 | ||
172 | // If we have errored do not exec everything as it could be in an indeterminate state | |
173 | 18 | if (err) { |
174 | 0 | return; |
175 | } | |
176 | ||
177 | // Watch the individual components | |
178 | 18 | event.removeListener('output', watchOutputHandler); |
179 | 18 | event.on('output', watchOutputHandler); |
180 | ||
181 | // Actual build everything | |
182 | 18 | var packages = packageName ? [packageName] : rootContext.config.packageList(); |
183 | 18 | packages.forEach(function(name) { |
184 | 22 | stateMachine.buildPackages(rootContext, name, modules, logError); |
185 | }); | |
186 | }); | |
187 | }, | |
188 | unwatch: function() { | |
189 | 14 | event.removeListener('output', watchOutputHandler); |
190 | 14 | if (watch) { |
191 | 14 | watch.removeAllListeners(); |
192 | 14 | watch.reset(); |
193 | 14 | watch = undefined; |
194 | 14 | watchContext = undefined; |
195 | } | |
196 | } | |
197 | }); | |
198 | }; | |
199 |
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 | 'server-scripts', | |
11 | 'inline-styles-resources', 'styles', 'scripts', 'static' | |
12 | ]; | |
13 | 1 | var fileUtils = require("/Users/kpdecker/dev/walmart/lumbar/lib/./fileUtil"); |
14 | ||
15 | 1 | var globalPlugins = {}; |
16 | ||
17 | 1 | exports.plugin = function(name, plugin) { |
18 | 20 | globalPlugins[name] = plugin; |
19 | 20 | plugin.id = name; |
20 | }; | |
21 | ||
22 | 1 | exports.plugin('module-map', require('./plugins/module-map')); |
23 | 1 | exports.plugin('package-config', require('./plugins/package-config')); |
24 | 1 | exports.plugin('router', require('./plugins/router')); |
25 | 1 | exports.plugin('scope', require('./plugins/scope')); |
26 | 1 | exports.plugin('stylus', require('./plugins/stylus')); |
27 | 1 | exports.plugin('stylus-config', require('./plugins/stylus-config')); |
28 | 1 | exports.plugin('coffee-script', require('./plugins/coffee-script')); |
29 | 1 | exports.plugin('handlebars', require('./plugins/handlebars')); |
30 | 1 | exports.plugin('inline-styles', require('./plugins/inline-styles')); |
31 | 1 | exports.plugin('inline-styles-resources', require('./plugins/inline-styles-resources')); |
32 | 1 | exports.plugin('mixin', require('./plugins/mixin')); |
33 | 1 | exports.plugin('update-externals', require('./plugins/update-externals')); |
34 | 1 | exports.plugin('template', require('./plugins/template')); |
35 | 1 | exports.plugin('styles', require('./plugins/styles.js')); |
36 | 1 | exports.plugin('server-scripts', require('./plugins/server-scripts.js')); |
37 | 1 | exports.plugin('scripts', require('./plugins/scripts.js')); |
38 | 1 | exports.plugin('static', require('./plugins/static.js')); |
39 | 1 | exports.plugin('styles-output', require('./plugins/styles-output.js')); |
40 | 1 | exports.plugin('scripts-output', require('./plugins/scripts-output.js')); |
41 | 1 | exports.plugin('static-output', require('./plugins/static-output.js')); |
42 | ||
43 | 1 | exports.create = function(options) { |
44 | 142 | var plugins; |
45 | 142 | var modes; // all registered modes |
46 | 142 | var pluginModes; // map of modes and plugins scoped to the mode |
47 | 142 | var modeAll; // plugins that are scoped to all modes |
48 | ||
49 | 142 | function runPlugins(context, methodName, complete, failOver, noMode) { |
50 | 3088 | var len = 0, |
51 | pluginMode = pluginModes[context.mode] || []; | |
52 | ||
53 | 3088 | return (function next(complete) { |
54 | /*jshint boss:true */ | |
55 | 9151 | var plugin; |
56 | 9151 | while (plugin = plugins[len++]) { |
57 | // if plugin shouldn't work with current mode, go to next | |
58 | 59897 | if (!noMode |
59 | && (!context.mode || pluginMode.indexOf(plugin) < 0) | |
60 | && modeAll.indexOf(plugin) < 0) { | |
61 | 26387 | continue; |
62 | } | |
63 | ||
64 | 33510 | var method = plugin[methodName]; |
65 | 33510 | if (method) { |
66 | 6765 | if (complete) { |
67 | 6625 | process.nextTick(function() { |
68 | 6625 | method.call(plugin, context, next, complete); |
69 | }); | |
70 | 6625 | return; |
71 | } else { | |
72 | 140 | return method.call(plugin, context, next, complete); |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | // We're done, send data back | |
78 | 2386 | if (complete) { |
79 | // async | |
80 | // Clear out our stack under async mode to try to keep the stack somewhat sane. | |
81 | 2197 | process.nextTick(function() { |
82 | 2197 | complete(undefined, failOver && failOver()); |
83 | }); | |
84 | } else { | |
85 | // sync | |
86 | 189 | return failOver && failOver(); |
87 | } | |
88 | })(complete); | |
89 | } | |
90 | ||
91 | 142 | function registerPlugin(plugin) { |
92 | 2793 | var _plugin = globalPlugins[plugin] || plugin; |
93 | ||
94 | 2793 | var mode = _plugin.mode; |
95 | 2793 | if (mode) { |
96 | 2654 | if (_.isString(mode)) { |
97 | 2091 | mode = [mode]; |
98 | } | |
99 | 2654 | _.each(mode, function(_mode) { |
100 | 3217 | if (mode === 'all') { |
101 | // allow plugins to contribute new modes and participate in all modes | |
102 | 0 | modeAll.push(_plugin); |
103 | } else { | |
104 | 3217 | if (modes.indexOf(_mode) < 0) { |
105 | 424 | modes.push(_mode); |
106 | 424 | pluginModes[_mode] = []; |
107 | } | |
108 | 3217 | pluginModes[_mode].push(_plugin); |
109 | } | |
110 | }); | |
111 | } else { | |
112 | 139 | modeAll.push(_plugin); |
113 | } | |
114 | 2793 | plugins.push(_plugin); |
115 | 2793 | plugins.sort(function(a, b) { |
116 | 39521 | return (a.priority || 50) - (b.priority || 50); |
117 | }); | |
118 | } | |
119 | ||
120 | 142 | return { |
121 | get: function(name) { | |
122 | // Find the plugin with this id, if one exists | |
123 | 77 | var plugin = plugins.reduce(function(plugin, left) { |
124 | 1463 | return plugin.id === name ? plugin : left; |
125 | }); | |
126 | ||
127 | // If the plugin was not found do not return the last item in the reduce | |
128 | 77 | if (plugin.id === name) { |
129 | 75 | return plugin; |
130 | } | |
131 | }, | |
132 | use: function(plugin) { | |
133 | 13 | if (plugin.path || (_.isString(plugin) && !globalPlugins[plugin])) { |
134 | 1 | var pluginPath = plugin.path || plugin; |
135 | 1 | var options = plugin.options; |
136 | 1 | try { |
137 | 1 | plugin = require(pluginPath); |
138 | } catch (e) { | |
139 | 1 | plugin = require(path.resolve(process.cwd(), fileUtils.lookupPath()) + '/node_modules/' + pluginPath); |
140 | } | |
141 | 1 | if ('function' === typeof plugin) { |
142 | 1 | plugin = plugin(options); |
143 | } | |
144 | } | |
145 | 13 | registerPlugin(plugin); |
146 | }, | |
147 | ||
148 | initialize: function(config) { | |
149 | // reset | |
150 | 142 | plugins = []; |
151 | 142 | modes = []; // all registered modes |
152 | 142 | pluginModes = {}; // map of modes and plugins scoped to the mode |
153 | 142 | modeAll = []; // plugins that are scoped to all modes |
154 | ||
155 | // load the core plugins | |
156 | 142 | if (!options.ignoreCorePlugins) { |
157 | 139 | corePlugins.forEach(registerPlugin); |
158 | } | |
159 | ||
160 | 142 | var self = this; |
161 | 142 | function plugin(plugins) { |
162 | 284 | if (plugins) { |
163 | 38 | plugins.forEach(self.use, self); |
164 | } | |
165 | } | |
166 | ||
167 | // load command line plugins | |
168 | 142 | plugin(options.plugins); |
169 | ||
170 | // load lumbar.json plugins | |
171 | 142 | plugin(config.attributes.plugins); |
172 | }, | |
173 | ||
174 | loadMixin: function(context, complete) { | |
175 | 84 | runPlugins(context, 'loadMixin', complete, undefined, true); |
176 | }, | |
177 | loadConfig: function(context, complete) { | |
178 | 141 | runPlugins(context, 'loadConfig', complete, undefined, true); |
179 | }, | |
180 | outputConfigs: function(context, complete) { | |
181 | 248 | runPlugins(context, 'outputConfigs', complete, function() { |
182 | // Default to a one to one mapping for a given {platform, package, module, mode} combo | |
183 | 246 | return [ {} ]; |
184 | }); | |
185 | }, | |
186 | modeComplete: function(context, complete) { | |
187 | 184 | runPlugins(context, 'modeComplete', complete); |
188 | }, | |
189 | fileName: function(context, complete) { | |
190 | 257 | runPlugins(context, 'fileName', complete); |
191 | }, | |
192 | ||
193 | fileFilter: function(context) { | |
194 | 329 | return runPlugins(context, 'fileFilter'); |
195 | }, | |
196 | moduleResources: function(context, complete) { | |
197 | 453 | runPlugins(context, 'moduleResources', complete, function() { |
198 | 248 | var module = context.module; |
199 | 248 | return (module[context.mode] || []).slice(); |
200 | }); | |
201 | }, | |
202 | resourceList: function(context, complete) { | |
203 | 1028 | runPlugins(context, 'resourceList', complete, function() { return [context.resource]; }); |
204 | }, | |
205 | ||
206 | file: function(context, complete) { | |
207 | 144 | runPlugins(context, 'file', complete); |
208 | }, | |
209 | module: function(context, complete) { | |
210 | 274 | runPlugins(context, 'module', complete); |
211 | }, | |
212 | resource: function(context, complete) { | |
213 | 825 | runPlugins(context, 'resource', complete, function() { return context.resource; }); |
214 | }, | |
215 | modes: function() { | |
216 | 47 | return modes; |
217 | } | |
218 | }; | |
219 | }; | |
220 |
Line | Hits | Source |
---|---|---|
1 | 1 | var CoffeeScript, |
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 | 273 | var resource = context.resource; |
11 | 273 | if (/\.coffee$/.test(resource.src)) { |
12 | 2 | CoffeeScript = CoffeeScript || require('coffee-script'); |
13 | ||
14 | 2 | next(function(err, resource) { |
15 | 2 | function generator(context, callback) { |
16 | // Load the source data | |
17 | 2 | context.loadResource(resource, function(err, file) { |
18 | 2 | if (err) { |
19 | 1 | return callback(err); |
20 | } | |
21 | ||
22 | // Update the content | |
23 | 1 | callback(err, { |
24 | data: CoffeeScript.compile(file.content.toString()), | |
25 | inputs: file.inputs | |
26 | }); | |
27 | }); | |
28 | } | |
29 | ||
30 | // Include any attributes that may have been defined on the base entry | |
31 | 2 | if (!_.isString(resource)) { |
32 | 2 | _.extend(generator, resource); |
33 | } | |
34 | 2 | complete(undefined, generator); |
35 | }); | |
36 | } else { | |
37 | 271 | next(complete); |
38 | } | |
39 | } | |
40 | }; | |
41 |
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 | resources = require('../util/resources'), | |
22 | templateUtil = require('../templateUtil'); | |
23 | ||
24 | 1 | handlebars.registerHelper('without-extension', function(str) { |
25 | 1 | return str.replace(/\.[a-zA-Z0-9]+$/, ''); |
26 | }); | |
27 | ||
28 | ||
29 | 1 | const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n"; |
30 | ||
31 | 1 | function ensureTemplateTemplates(context, complete) { |
32 | 40 | if (!context.configCache.templateTemplate) { |
33 | 14 | var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE; |
34 | 14 | context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) { |
35 | 14 | if (err) { |
36 | 1 | complete(err); |
37 | } else { | |
38 | 13 | context.configCache.templateTemplate = compiled; |
39 | 13 | complete(); |
40 | } | |
41 | }); | |
42 | } else { | |
43 | 26 | complete(); |
44 | } | |
45 | } | |
46 | ||
47 | 1 | function loadTemplate(src, resource, context, callback) { |
48 | 40 | ensureTemplateTemplates(context, function(err) { |
49 | 40 | if (err) { |
50 | 1 | return callback(err); |
51 | } | |
52 | 39 | var artifactType = 'template' + context.fileConfig.server; |
53 | 39 | context.fileUtil.readFileArtifact(src, artifactType, function(err, cache) { |
54 | 39 | if (err) { |
55 | 0 | callback(new Error('Failed to load template "' + src + '"\n\t' + err)); |
56 | 0 | return; |
57 | } | |
58 | ||
59 | 39 | var artifact = cache.artifact || {}, |
60 | data = artifact.data || cache.data.toString(), | |
61 | attr = context.config.attributes, | |
62 | templates = attr.templates || {}, | |
63 | appModule = context.config.scopedAppModuleName(context.module), | |
64 | templateCache = (attr.templates && attr.templates.cache) | |
65 | || attr.templateCache | |
66 | || ((appModule ? appModule + '.' : '') + 'templates'), | |
67 | template = context.configCache.templateTemplate; | |
68 | ||
69 | // Figure out what this file is called. This could vary due to prefixing and overriding | |
70 | 39 | var name = context.libraries.mapPathToLibrary(src, resource.library); |
71 | 39 | if (templates.root && name.indexOf(templates.root) === 0) { |
72 | 4 | name = name.substring(templates.root.length); |
73 | } | |
74 | 39 | name = templateUtil.escapeJsString(name); |
75 | ||
76 | // We have the template data, now convert it into the proper format | |
77 | 39 | if (!cache.artifact) { |
78 | 18 | if (templates.precompile) { |
79 | 2 | var options = context.fileCache.precompileTemplates; |
80 | 2 | if (!options) { |
81 | 2 | context.fileCache.precompileTemplates = options = _.clone(templates.precompile); |
82 | 2 | if (templates.knownHelpers || options.knownHelpers) { |
83 | 0 | options.knownHelpers = (options.knownHelpers || templates.knownHelpers).reduce( |
84 | function(value, helper) { | |
85 | 0 | value[helper] = true; |
86 | 0 | return value; |
87 | }, {}); | |
88 | } | |
89 | 2 | if (context.fileConfig.server && templates.server) { |
90 | 1 | _.extend(options, templates.server); |
91 | } | |
92 | } | |
93 | 2 | try { |
94 | 2 | data = handlebars.precompile(data, options); |
95 | } catch (err) { | |
96 | 0 | return callback(err); |
97 | } | |
98 | } else { | |
99 | 16 | data = "'" + templateUtil.escapeJsString(data) + "'"; |
100 | } | |
101 | 18 | context.fileUtil.setFileArtifact(src, artifactType, {data: data, template: template}); |
102 | } | |
103 | ||
104 | 39 | callback( |
105 | undefined, | |
106 | template({ | |
107 | name: name, | |
108 | handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile', | |
109 | templateCache: templateCache, | |
110 | data: data | |
111 | }) | |
112 | ); | |
113 | }); | |
114 | }); | |
115 | } | |
116 | ||
117 | 1 | module.exports = { |
118 | mode: 'scripts', | |
119 | priority: 50, | |
120 | ||
121 | loadMixin: function(context, next, complete) { | |
122 | 84 | var mixinTemplates = context.loadedLibrary.templates; |
123 | 84 | if (mixinTemplates) { |
124 | 14 | var templates = context.libraries.originalConfig.templates || {}, |
125 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
126 | assigned = false; | |
127 | ||
128 | 14 | ['template', 'precompile', 'cache', 'root'].forEach(function(key) { |
129 | 56 | if (_.has(mixinTemplates, key) && !_.has(templates, key)) { |
130 | 10 | configTemplates[key] = mixinTemplates[key]; |
131 | 10 | assigned = true; |
132 | } | |
133 | }); | |
134 | ||
135 | 14 | if (_.has(mixinTemplates, 'knownHelpers')) { |
136 | 1 | configTemplates.knownHelpers = (configTemplates.knownHelpers || []).concat(mixinTemplates.knownHelpers); |
137 | 1 | assigned = true; |
138 | } | |
139 | ||
140 | 14 | if (assigned) { |
141 | 7 | context.config.attributes.templates = configTemplates; |
142 | } | |
143 | } | |
144 | 84 | next(complete); |
145 | }, | |
146 | ||
147 | resource: function(context, next, complete) { | |
148 | 229 | var resource = context.resource; |
149 | ||
150 | 229 | if (/\.handlebars$/.test(resource.src) || resource.template) { |
151 | 31 | var loadedTemplates = context.fileCache.loadedTemplates; |
152 | 31 | if (!loadedTemplates) { |
153 | 27 | loadedTemplates = context.fileCache.loadedTemplates = {}; |
154 | } | |
155 | ||
156 | 31 | var generator = function(buildContext, callback) { |
157 | 28 | var output = [], |
158 | inputs = []; | |
159 | 28 | context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) { |
160 | 28 | if (err) { |
161 | 0 | callback(err); |
162 | 0 | return; |
163 | } | |
164 | ||
165 | 28 | function ignore(file) { |
166 | 121 | return file.dir || loadedTemplates[resources.source(file)]; |
167 | } | |
168 | 28 | function checkComplete() { |
169 | 67 | if (inputs.length === files.length) { |
170 | // Sorting is effectively sorting on the file name due to the name comment in the template | |
171 | 27 | callback(undefined, { |
172 | inputs: inputs, | |
173 | data: output.sort().join(''), | |
174 | name: resource.src, | |
175 | generated: true, | |
176 | noSeparator: true, | |
177 | ignoreWarnings: true | |
178 | }); | |
179 | 27 | return true; |
180 | } | |
181 | } | |
182 | ||
183 | 49 | inputs = _.map(files.filter(ignore), function(input) { return input.src || input; }); |
184 | 28 | if (checkComplete()) { |
185 | 1 | return; |
186 | } | |
187 | ||
188 | 27 | files.forEach(function(file) { |
189 | 60 | if (ignore(file)) { |
190 | 20 | return; |
191 | } | |
192 | ||
193 | 40 | var src = file.src || file; |
194 | 40 | loadedTemplates[src] = true; |
195 | 40 | loadTemplate(src, resource, context, function(err, data) { |
196 | 40 | if (err) { |
197 | 1 | return callback(err); |
198 | } | |
199 | ||
200 | 39 | output.push(data.data || data); |
201 | 39 | inputs.push(src); |
202 | 39 | checkComplete(); |
203 | }); | |
204 | }); | |
205 | }); | |
206 | }; | |
207 | 31 | generator.sourceFile = resource.src; |
208 | 31 | complete(undefined, generator); |
209 | } else { | |
210 | 198 | next(complete); |
211 | } | |
212 | } | |
213 | }; | |
214 |
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 | 369 | if (inlineStyles.isInline(context) && context.mode === 'styles') { |
9 | // Prevent stylesheet output if in inline mode | |
10 | 3 | complete(undefined, []); |
11 | 366 | } else if (inlineStyles.isInline(context)) { |
12 | 6 | next(function(err, scripts) { |
13 | 6 | complete(undefined, scripts.concat(context.module.styles || [])); |
14 | }); | |
15 | } else { | |
16 | 360 | 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 | 1564 | 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 | 84 | var mixinStyles = context.loadedLibrary.styles; |
26 | 84 | if (mixinStyles) { |
27 | 22 | var styles = context.libraries.originalConfig.styles || {}, |
28 | configStyles = _.clone(context.config.attributes.styles || styles), | |
29 | assigned = false; | |
30 | ||
31 | 22 | ['inline', 'inlineLoader'].forEach(function(key) { |
32 | 44 | if ((key in mixinStyles) && !(key in styles)) { |
33 | 6 | configStyles[key] = mixinStyles[key]; |
34 | ||
35 | 6 | assigned = true; |
36 | } | |
37 | }); | |
38 | ||
39 | 22 | if (assigned) { |
40 | 5 | context.config.attributes.styles = configStyles; |
41 | } | |
42 | } | |
43 | 84 | next(complete); |
44 | }, | |
45 | ||
46 | outputConfigs: function(context, next, complete) { | |
47 | 200 | if (isInline(context) && context.mode === 'styles') { |
48 | // Prevent stylesheet output if in inline mode | |
49 | 2 | complete(undefined, []); |
50 | } else { | |
51 | 198 | 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 | 138 | var modules = context.config.attributes.modules, |
8 | errored; | |
9 | 138 | _.each(context.libraries.configs, function(library) { |
10 | // Import any modules that are not overriden in the core file | |
11 | 79 | _.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 | 138 | _.each(modules, function(module, name) { |
30 | 168 | module.name = module.name || name; |
31 | 168 | var mixins; |
32 | 168 | try { |
33 | 168 | mixins = context.libraries.moduleMixins(module); |
34 | } catch (err) { | |
35 | 2 | errored = true; |
36 | 2 | return complete(new Error('Failed mixins for module "' + name + '": ' + err.message)); |
37 | } | |
38 | ||
39 | // Map existing files that have mixin references | |
40 | 166 | try { |
41 | 166 | ['scripts', 'styles', 'static'].forEach(function(field) { |
42 | 496 | var list = module[field]; |
43 | ||
44 | 496 | if (list) { |
45 | 126 | module[field] = context.libraries.mapFiles(list); |
46 | } | |
47 | }); | |
48 | ||
49 | 165 | _.each(mixins, function(mixin) { |
50 | 48 | var mixinConfig = mixin.mixinConfig, |
51 | library = mixin.library; | |
52 | ||
53 | // Direct copy for any fields that are not already defined on the object. | |
54 | 48 | _.defaults(module, library.attributes); |
55 | ||
56 | // Merge known array/object types | |
57 | 48 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
58 | 192 | mergeValues(module, field, library, mixinConfig, context); |
59 | }); | |
60 | }); | |
61 | } catch (err) { | |
62 | 1 | errored = true; |
63 | 1 | return complete(err); |
64 | } | |
65 | }); | |
66 | ||
67 | // Remove suppressed modules completely | |
68 | 138 | _.each(_.keys(modules), function(name) { |
69 | 168 | if (!modules[name]) { |
70 | 1 | delete modules[name]; |
71 | } | |
72 | }); | |
73 | ||
74 | 138 | if (!errored) { |
75 | 135 | next(complete); |
76 | } | |
77 | } | |
78 | }; | |
79 | ||
80 | 1 | function firstLocal(collection) { |
81 | 58 | for (var i = 0, len = collection.length; i < len; i++) { |
82 | 73 | if (!collection[i].global) { |
83 | 58 | return i; |
84 | } | |
85 | } | |
86 | 0 | return i; |
87 | } | |
88 | ||
89 | 1 | function mergeValues(module, field, library, mixinConfig, context) { |
90 | 192 | var value = module[field], |
91 | mixinValue = library.attributes[field]; | |
92 | ||
93 | 192 | if (!value) { |
94 | 137 | return; |
95 | } | |
96 | ||
97 | 55 | if (value === mixinValue) { |
98 | // Clone any direct copy entries from a mixin | |
99 | 16 | if (_.isArray(value)) { |
100 | 14 | module[field] = context.libraries.mapFiles(value, library, mixinConfig); |
101 | } else { | |
102 | 2 | module[field] = _.clone(value); |
103 | } | |
104 | 39 | } else if (!_.isArray(value)) { |
105 | 5 | _.defaults(value, mixinValue); |
106 | 34 | } else if (mixinValue) { |
107 | 29 | mixinValue = context.libraries.mapFiles(mixinValue, library, mixinConfig); |
108 | ||
109 | 29 | var mixinFirstLocal = firstLocal(mixinValue), |
110 | moduleFirstLocal = firstLocal(value); | |
111 | ||
112 | 29 | if (mixinFirstLocal) { |
113 | 4 | value.unshift.apply(value, mixinValue.slice(0, mixinFirstLocal)); |
114 | } | |
115 | 29 | if (mixinFirstLocal < mixinValue.length) { |
116 | 29 | var locals = mixinValue.slice(mixinFirstLocal); |
117 | 29 | locals.unshift(mixinFirstLocal + moduleFirstLocal, 0); |
118 | 29 | value.splice.apply(value, locals); |
119 | } | |
120 | } | |
121 | } | |
122 |
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 | 38 | if (context.combined) { |
45 | 15 | moduleConfig(context, undefined, function(err, config, prefix) { |
46 | 15 | callback(err, { base: config }, prefix); |
47 | }); | |
48 | } else { | |
49 | 23 | var attr = context.config.attributes || {}, |
50 | app = attr.application || {}, | |
51 | modules = context.config.moduleList(context.package); | |
52 | ||
53 | 23 | var map = {modules: {}, routes: {}}, |
54 | commonPrefix; | |
55 | ||
56 | 23 | async.forEach(modules, function(module, callback) { |
57 | 32 | moduleConfig(context, module, function(err, config, prefix) { |
58 | 32 | if (err) { |
59 | 0 | return callback(err); |
60 | } | |
61 | ||
62 | 32 | if (app.module === module) { |
63 | 4 | map.base = config; |
64 | } else { | |
65 | 28 | map.modules[module] = config; |
66 | ||
67 | 28 | var routes = context.config.routeList(module); |
68 | 28 | _.each(routes, function(value, route) { |
69 | 29 | map.routes[route] = module; |
70 | }); | |
71 | } | |
72 | 32 | commonPrefix = findPrefix(prefix, commonPrefix); |
73 | ||
74 | 32 | callback(); |
75 | }); | |
76 | }, | |
77 | function(err) { | |
78 | 23 | 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 | 47 | var ret = {}, |
120 | commonPrefix, | |
121 | preload = module && context.config.module(module).preload, | |
122 | depends = module && context.config.module(module).depends; | |
123 | 47 | if (preload) { |
124 | 1 | ret.preload = preload; |
125 | } | |
126 | 47 | if (depends) { |
127 | 4 | ret.depends = depends; |
128 | } | |
129 | 47 | async.forEach([{key: 'js', mode: 'scripts'}, {key: 'css', mode: 'styles'}], function(obj, callback) { |
130 | 94 | fileList(context, obj.mode, module, function(err, list, prefix) { |
131 | 94 | ret[obj.key] = list; |
132 | 94 | commonPrefix = findPrefix(prefix, commonPrefix); |
133 | 94 | callback(err); |
134 | }); | |
135 | }, | |
136 | function(err) { | |
137 | 47 | 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 | 94 | var modules = !context.combined ? [ module ] : context.config.moduleList(context.package); |
143 | 94 | async.some(modules, function(module, callback) { |
144 | 124 | var resourceContext = context.clone(); |
145 | 124 | resourceContext.mode = mode; |
146 | 124 | resourceContext.module = context.config.module(module); |
147 | 124 | resourceContext.isModuleMap = true; |
148 | ||
149 | 124 | resourceContext.plugins.moduleResources(resourceContext, function(err, resources) { |
150 | 124 | callback((resources || []).length); |
151 | }); | |
152 | }, | |
153 | function(hasResource) { | |
154 | 94 | if (!hasResource) { |
155 | 15 | return callback(); |
156 | } | |
157 | ||
158 | // Output the config | |
159 | 79 | context.fileNamesForModule(mode, module, function(err, configs) { |
160 | 79 | if (err) { |
161 | 0 | return callback(err); |
162 | } | |
163 | ||
164 | 79 | var prefix; |
165 | 184 | configs = configs.filter(function(config) { return !config.server; }); |
166 | 105 | configs = configs.sort(function(a, b) { return a.pixelDensity - b.pixelDensity; }); |
167 | 79 | configs = configs.map(function(config, i) { |
168 | 105 | var path = config.fileName.path, |
169 | ret = path + '.' + config.fileName.extension; | |
170 | ||
171 | 105 | 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 | 105 | prefix = findPrefix(path, prefix); |
183 | ||
184 | 105 | return ret; |
185 | }); | |
186 | ||
187 | 79 | var ret; |
188 | 79 | if (configs.length === 1) { |
189 | 54 | ret = configs[0]; |
190 | 25 | } else if (configs.length) { |
191 | 25 | ret = configs; |
192 | } | |
193 | 79 | callback(undefined, ret, prefix); |
194 | }); | |
195 | }); | |
196 | } | |
197 | ||
198 | 1 | function findPrefix(path, prefix) { |
199 | /*jshint eqnull:true*/ | |
200 | 231 | if (path == null) { |
201 | 15 | return prefix; |
202 | } | |
203 | 216 | if (prefix == null) { |
204 | // Ensure that we get 'x' for strings of type 'x/' | |
205 | 149 | prefix = dirname(path + 'a') + '/'; |
206 | } | |
207 | 216 | for (var i = 0, len = prefix.length; i < len; i++) { |
208 | 149 | if (path.charAt(i) !== prefix.charAt(i)) { |
209 | 149 | return prefix.substring(0, i); |
210 | } | |
211 | } | |
212 | 67 | 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 | 252 | var config = context.config; |
223 | ||
224 | 252 | 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 | 231 | 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 | 290 | var resource = context.resource; |
33 | ||
34 | 290 | 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 | 273 | 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 | 202 | next(function(err, ret) { |
22 | 202 | if (err) { |
23 | 0 | return complete(err); |
24 | } | |
25 | ||
26 | // Generate the router if we have the info for it | |
27 | 202 | var module = context.module; |
28 | 202 | if (module.routes) { |
29 | 53 | ret.unshift({ routes: module.routes }); |
30 | } | |
31 | ||
32 | 202 | complete(undefined, ret); |
33 | }); | |
34 | }, | |
35 | resource: function(context, next, complete) { | |
36 | 273 | var resource = context.resource, |
37 | module = context.module.name; | |
38 | ||
39 | 273 | 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 | 252 | 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 | 419 | return (attr.scope && attr.scope.scope) || attr.scope; |
29 | } | |
30 | 1 | function toObj(obj) { |
31 | 96 | 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 | 47 | if (!context.configCache.moduleTemplate) { |
46 | 30 | var template = context.config.attributes.scope && context.config.attributes.scope.template; |
47 | 30 | if (!template) { |
48 | 18 | template = __dirname + '/scope-module.handlebars'; |
49 | } | |
50 | ||
51 | 30 | context.fileUtil.loadTemplate(template, scopeTemplateDelimiter, function(err, templates) { |
52 | 31 | if (err) { |
53 | 1 | complete(err); |
54 | } else { | |
55 | 30 | context.configCache.moduleTemplate = { |
56 | start: templates[0], | |
57 | end: templates[1] | |
58 | }; | |
59 | 30 | 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 | 84 | var mixinScope = toObj(context.loadedLibrary.scope); |
144 | 84 | 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 | 84 | next(complete); |
170 | }, | |
171 | loadConfig: function(context, next, complete) { | |
172 | 135 | var modules = context.config.attributes.modules; |
173 | ||
174 | 135 | try { |
175 | 135 | _.each(modules, function(module) { |
176 | 162 | var mixins = context.libraries.moduleMixins(module); |
177 | ||
178 | 162 | _.each(mixins, function(mixin) { |
179 | 48 | context.libraries.mergeHash('aliases', module, mixin.library.attributes, module); |
180 | }); | |
181 | }); | |
182 | } catch (err) { | |
183 | 0 | return complete(err); |
184 | } | |
185 | ||
186 | 135 | next(complete); |
187 | }, | |
188 | ||
189 | resourceList: function(context, next, complete) { | |
190 | 315 | next(function(err, resources) { |
191 | 315 | if (err) { |
192 | 0 | return complete(err); |
193 | } | |
194 | ||
195 | 315 | 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 | 315 | complete(undefined, resources); |
202 | }); | |
203 | }, | |
204 | ||
205 | module: function(context, next, complete) { | |
206 | 104 | next(function(err) { |
207 | 104 | if (err) { |
208 | 0 | return complete(err); |
209 | } | |
210 | ||
211 | 104 | var resources = context.moduleResources, |
212 | scope = getScope(context.config.attributes); | |
213 | ||
214 | 104 | if (resources.length && scope !== 'none') { |
215 | 47 | ensureModuleTemplates(context, function(err) { |
216 | 48 | if (err) { |
217 | 1 | complete(err); |
218 | } else { | |
219 | // Split up globals and non-globals | |
220 | 47 | var globals = [], |
221 | children = [], | |
222 | moduleStart = []; | |
223 | 47 | for (var i = 0; i < resources.length; i++) { |
224 | 160 | var resource = resources[i]; |
225 | 160 | if (resource.moduleStart) { |
226 | 13 | moduleStart.push(resource); |
227 | 147 | } else if (!resource.global) { |
228 | 131 | children.push(resource); |
229 | } else { | |
230 | 16 | if (children.length) { |
231 | 1 | throw new Error('Scoped files may not appear before global files.\n' + _.map(children, function(resource) { |
232 | 1 | return resource.stringValue || resource.sourceFile || resource.src || resource; |
233 | }).join(', ') + ', ' + (resource.src || resource)); | |
234 | } | |
235 | 15 | globals.push(resource); |
236 | } | |
237 | } | |
238 | ||
239 | 46 | children = moduleStart.concat(children); |
240 | 46 | globals.push.apply(globals, wrapResources(children, context, complete)); |
241 | ||
242 | 46 | context.moduleResources = globals; |
243 | 46 | complete(); |
244 | } | |
245 | }); | |
246 | } else { | |
247 | 57 | complete(); |
248 | } | |
249 | }); | |
250 | } | |
251 | }; | |
252 | ||
253 |
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 | 0 | return /\.(js|json)$/; |
7 | }, | |
8 | ||
9 | fileName: function(context, next, complete) { | |
10 | 113 | complete(undefined, {path: context.baseName, extension: 'js'}); |
11 | }, | |
12 | ||
13 | moduleResources: function(context, next, complete) { | |
14 | 202 | var module = context.module; |
15 | 202 | 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 | 141 | return /\.(js|json)$/; |
9 | }, | |
10 | ||
11 | outputConfigs: function(context, next, complete) { | |
12 | 108 | next(function(err, files) { |
13 | 108 | if (err) { |
14 | 1 | return complete(err); |
15 | } | |
16 | ||
17 | // Permutation of other configs and ours | |
18 | 107 | var ret = []; |
19 | 107 | files.forEach(function(fileConfig) { |
20 | 109 | [true, false].forEach(function(server) { |
21 | // If they did not opt into server mode then we want to only emit non-server | |
22 | // mode | |
23 | 218 | if (context.config.attributes.server || !server) { |
24 | 111 | var config = _.clone(fileConfig); |
25 | 111 | config.server = server; |
26 | 111 | ret.push(config); |
27 | } | |
28 | }); | |
29 | }); | |
30 | 107 | complete(undefined, ret); |
31 | }); | |
32 | }, | |
33 | ||
34 | fileName: function(context, next, complete) { | |
35 | 115 | next(function(err, ret) { |
36 | 115 | if (ret && context.fileConfig.server) { |
37 | 1 | ret.path += '-server'; |
38 | } | |
39 | 115 | complete(err, ret); |
40 | }); | |
41 | }, | |
42 | ||
43 | moduleResources: function(context, next, complete) { | |
44 | 208 | var module = context.module; |
45 | ||
46 | 208 | if (module.server && context.fileConfig.server) { |
47 | 1 | return complete(undefined, module.server); |
48 | } | |
49 | ||
50 | 207 | next(function(err, scripts) { |
51 | 207 | if (err) { |
52 | 1 | return complete(err); |
53 | } | |
54 | ||
55 | 206 | var files = []; |
56 | 206 | _.each(scripts, function(script) { |
57 | 483 | if (!_.has(script, 'server') || script.server === context.fileConfig.server) { |
58 | 481 | files.push(script); |
59 | } | |
60 | }); | |
61 | 206 | complete(undefined, files); |
62 | }); | |
63 | } | |
64 | }; | |
65 |
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 | 84 | var mixinStyles = context.loadedLibrary.styles; |
10 | 84 | if (mixinStyles) { |
11 | 22 | var styles = context.libraries.originalConfig.styles || {}, |
12 | configStyles = _.clone(context.config.attributes.styles || styles), | |
13 | assigned = false; | |
14 | ||
15 | 22 | ['configObject'].forEach(function(key) { |
16 | 22 | if ((key in mixinStyles) && !(key in styles)) { |
17 | 1 | configStyles[key] = mixinStyles[key]; |
18 | ||
19 | 1 | assigned = true; |
20 | } | |
21 | }); | |
22 | ||
23 | 22 | if (context.libraries.mergeFiles('config', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
24 | 4 | assigned = true; |
25 | } | |
26 | ||
27 | 22 | if (assigned) { |
28 | 4 | context.config.attributes.styles = configStyles; |
29 | } | |
30 | } | |
31 | 84 | next(complete); |
32 | }, | |
33 | ||
34 | resource: function(context, next, complete) { | |
35 | 433 | 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 | 430 | 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 | resources = require('../util/resources'); | |
29 | ||
30 | // Forward cache resets to any workers | |
31 | 1 | fu.on('cache:reset', function(path) { |
32 | 259 | worker.sendAll({type: 'cache:reset', path: path}); |
33 | }); | |
34 | ||
35 | 1 | var worker = new ChildPool(__dirname + '/stylus-worker', {logId: 'stylus-worker'}); |
36 | ||
37 | 1 | function generateSource(context, options, styleConfig) { |
38 | 38 | var includes = (styleConfig.includes || []).concat(options.files), |
39 | module = options.module; | |
40 | ||
41 | 38 | var nibLocation = includes.indexOf('nib'), |
42 | useNib; | |
43 | 38 | if (styleConfig.useNib) { |
44 | 31 | useNib = true; |
45 | 31 | includes.unshift('nib'); |
46 | 7 | } else if (nibLocation >= 0) { |
47 | // Special case nib handling to maintain backwards compatibility | |
48 | // WARN: This may be deprecated in future releases | |
49 | 0 | useNib = true; |
50 | 0 | includes.splice(nibLocation, 1); |
51 | } | |
52 | ||
53 | 38 | var declare = context.config.platformList().map(function(platform) { |
54 | 73 | return '$' + platform + ' = ' + (platform === context.platform); |
55 | }).join('\n') + '\n'; | |
56 | ||
57 | 38 | var mixins = [], |
58 | mixinLUT = {}; | |
59 | ||
60 | 38 | var source = declare + includes.map(function(include) { |
61 | 127 | var source = include.library; |
62 | 127 | var statement = '@import ("' + (include.originalSrc || include.src || include) + '")\n'; |
63 | 127 | if (source) { |
64 | 18 | var name = '', |
65 | root = (source.parent || source).root || '', | |
66 | stylusRoot = ((source.parent || source).styles || {}).styleRoot, | |
67 | library = (source.parent || source).name || ''; | |
68 | 18 | if (source.parent) { |
69 | 15 | name = source.name || ''; |
70 | } | |
71 | 18 | var mixinName = name + '_' + library; |
72 | ||
73 | 18 | if (!mixinLUT[mixinName]) { |
74 | 9 | var overrides = resources.calcOverrides(source, function(library, src, ret) { |
75 | 9 | if (library && library.root) { |
76 | 3 | ret.root = normalize(library.root); |
77 | } | |
78 | ||
79 | 9 | if (library) { |
80 | 3 | var styles = library.styles || {}; |
81 | 3 | ret.stylusRoot = styles.styleRoot; |
82 | 3 | if (ret.styleRoot) { |
83 | 0 | ret.styleRoot = normalize(ret.styleRoot); |
84 | } | |
85 | } | |
86 | }); | |
87 | 9 | mixins.push({ |
88 | root: normalize(root), | |
89 | stylusRoot: stylusRoot && normalize(stylusRoot), | |
90 | overrides: overrides | |
91 | }); | |
92 | 9 | mixinLUT[mixinName] = mixins.length-1; |
93 | } | |
94 | 18 | mixinName = mixinLUT[mixinName]; |
95 | ||
96 | 18 | return 'push-mixin("' + mixinName + '")\n' |
97 | + statement | |
98 | + 'pop-mixin()\n'; | |
99 | } else { | |
100 | 109 | return statement; |
101 | } | |
102 | }).join(''); | |
103 | ||
104 | 38 | return { |
105 | useNib: useNib, | |
106 | source: source, | |
107 | mixins: mixins | |
108 | }; | |
109 | } | |
110 | ||
111 | 1 | function compile(options, callback) { |
112 | 38 | var context = options.context, |
113 | ||
114 | styleConfig = context.config.attributes.styles || {}; | |
115 | ||
116 | 38 | var loadPrefix = context.config.loadPrefix(), |
117 | externalPrefix; | |
118 | 38 | if (loadPrefix) { |
119 | 9 | externalPrefix = loadPrefix + (context.buildPath.indexOf('/') >= 0 ? path.dirname(context.buildPath) + '/' : ''); |
120 | } | |
121 | ||
122 | 38 | var imageOptions = { |
123 | outdir: path.dirname(context.fileName), | |
124 | resolutions: context.modeCache.pixelDensity, | |
125 | limit: styleConfig.urlSizeLimit, | |
126 | copyFiles: styleConfig.copyFiles, | |
127 | externalPrefix: externalPrefix | |
128 | }; | |
129 | ||
130 | 38 | var source = generateSource(context, options, styleConfig); |
131 | ||
132 | 38 | context.fileUtil.ensureDirs(context.fileName, function(err) { |
133 | 38 | if (err) { |
134 | 0 | return callback(err); |
135 | } | |
136 | ||
137 | 38 | worker.send({ |
138 | plugins: options.plugins, | |
139 | ||
140 | useNib: source.useNib, | |
141 | imageOptions: imageOptions, | |
142 | ||
143 | filename: options.filename, | |
144 | minimize: context.options.minimize, | |
145 | ||
146 | source: source.source, | |
147 | mixins: source.mixins, | |
148 | ||
149 | lookupPath: context.fileUtil.lookupPath(), | |
150 | styleRoot: styleConfig.styleRoot && context.fileUtil.resolvePath(styleConfig.styleRoot) | |
151 | }, | |
152 | callback); | |
153 | }); | |
154 | } | |
155 | ||
156 | 1 | module.exports = { |
157 | // scripts mode is used also to support inline styles | |
158 | mode: ['styles', 'scripts'], | |
159 | priority: 50, | |
160 | ||
161 | loadMixin: function(context, next, complete) { | |
162 | 84 | var mixinStyles = context.loadedLibrary.styles; |
163 | 84 | if (mixinStyles) { |
164 | 22 | var styles = context.libraries.originalConfig.styles || {}, |
165 | configStyles = _.clone(context.config.attributes.styles || styles), | |
166 | assigned = false; | |
167 | ||
168 | 22 | ['urlSizeLimit', 'copyFiles', 'useNib'].forEach(function(key) { |
169 | 66 | if ((key in mixinStyles) && !(key in styles)) { |
170 | 8 | configStyles[key] = mixinStyles[key]; |
171 | ||
172 | 8 | assigned = true; |
173 | } | |
174 | }); | |
175 | ||
176 | 22 | if (context.libraries.mergeFiles('includes', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
177 | 6 | assigned = true; |
178 | } | |
179 | ||
180 | 22 | if (context.libraries.mergeHash('pixelDensity', styles, mixinStyles, configStyles)) { |
181 | 3 | assigned = true; |
182 | } | |
183 | ||
184 | 22 | if (assigned) { |
185 | 10 | context.config.attributes.styles = configStyles; |
186 | } | |
187 | } | |
188 | 84 | next(complete); |
189 | }, | |
190 | ||
191 | outputConfigs: function(context, next, complete) { | |
192 | 201 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
193 | 101 | return next(complete); |
194 | } | |
195 | ||
196 | 100 | next(function(err, files) { |
197 | 100 | if (err) { |
198 | 0 | return complete(err); |
199 | } | |
200 | ||
201 | 100 | var ret = [], |
202 | styleConfig = context.config.attributes.styles || {}, | |
203 | pixelDensity = styleConfig.pixelDensity || {}; | |
204 | 100 | if (context.platform) { |
205 | 74 | pixelDensity = pixelDensity[context.platform] || pixelDensity; |
206 | } | |
207 | 100 | if (!_.isArray(pixelDensity)) { |
208 | 63 | pixelDensity = [ 1 ]; |
209 | } | |
210 | 100 | context.modeCache.pixelDensity = pixelDensity; |
211 | ||
212 | // Permutation of other configs and ours | |
213 | 100 | var primary = true; |
214 | 100 | files.forEach(function(fileConfig) { |
215 | 100 | pixelDensity.forEach(function(density) { |
216 | 138 | var config = _.clone(fileConfig); |
217 | 138 | config.pixelDensity = density; |
218 | 138 | config.isPrimary = primary; |
219 | 138 | primary = false; |
220 | 138 | ret.push(config); |
221 | }); | |
222 | }); | |
223 | 100 | complete(undefined, ret); |
224 | }); | |
225 | }, | |
226 | ||
227 | fileName: function(context, next, complete) { | |
228 | 235 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
229 | 107 | return next(complete); |
230 | } | |
231 | ||
232 | 128 | next(function(err, ret) { |
233 | 128 | if (ret && context.fileConfig.pixelDensity !== 1) { |
234 | 40 | ret.path += '@' + context.fileConfig.pixelDensity + 'x'; |
235 | } | |
236 | 128 | complete(err, ret); |
237 | }); | |
238 | }, | |
239 | ||
240 | module: function(moduleContext, next, complete) { | |
241 | 203 | next(function(err) { |
242 | /*jshint eqnull: true */ | |
243 | 203 | if (err) { |
244 | 0 | return complete(err); |
245 | } | |
246 | ||
247 | 203 | function mergeResources(start) { |
248 | 56 | var generator = function(context, callback) { |
249 | 56 | function response(data, density) { |
250 | 56 | if (data) { |
251 | 53 | return { |
252 | data: data.data[density || 1], | |
253 | inputs: data.inputs, | |
254 | noSeparator: true | |
255 | }; | |
256 | } | |
257 | } | |
258 | ||
259 | 56 | var filename = generator.filename; |
260 | ||
261 | // We only want to call stylus once which will generate the css for all of the | |
262 | // resolutions we support on this platform. This ugly bit of code make sure that | |
263 | // we properly handle all of that loading states that can come into play under these | |
264 | // circumstances while still adhering to the output models prescribed by lumbar. | |
265 | 56 | var queue = context.modeCache['stylus_' + filename]; |
266 | 56 | if (_.isArray(queue)) { |
267 | // We are currently executing | |
268 | 18 | queue.push({density: context.fileConfig.pixelDensity, callback: callback}); |
269 | 38 | } else if (_.isObject(queue)) { |
270 | // We already have data | |
271 | 0 | callback(undefined, response(queue, context.fileConfig.pixelDensity)); |
272 | } else { | |
273 | // We need to kick of a stylus build | |
274 | 38 | queue = context.modeCache['stylus_' + filename] = [ |
275 | {density: context.fileConfig.pixelDensity, callback: callback} | |
276 | ]; | |
277 | 38 | var options = { |
278 | filename: filename, | |
279 | files: generator.inputs, | |
280 | ||
281 | context: context, | |
282 | module: moduleContext.module, // To play nicely with combined mode | |
283 | plugins: generator.plugins | |
284 | }; | |
285 | 38 | compile(options, function(err, data) { |
286 | 38 | if (err) { |
287 | 3 | data = undefined; |
288 | } | |
289 | 38 | _.each(queue, function(callback) { |
290 | 56 | callback.callback(err, response(data, callback.density)); |
291 | }); | |
292 | 38 | context.modeCache['stylus_' + filename] = data; |
293 | }); | |
294 | } | |
295 | }; | |
296 | 56 | generator.inputs = resources.splice(start, rangeEnd - start + 1); |
297 | 157 | generator.filename = 'stylus_' + _.map(generator.inputs, function(file) { return file.originalSrc || file.src; }).join(';'); |
298 | 56 | generator.style = true; |
299 | 56 | generator.stylus = true; |
300 | 56 | generator.plugins = []; |
301 | ||
302 | 56 | resources.splice(start, 0, generator); |
303 | 56 | rangeEnd = undefined; |
304 | } | |
305 | ||
306 | // Merge all consequtive stylus files together | |
307 | 203 | var resources = moduleContext.moduleResources, |
308 | len = resources.length, | |
309 | rangeEnd; | |
310 | 203 | while (len--) { |
311 | 426 | var resource = resources[len]; |
312 | ||
313 | 426 | if (/\.styl$/.test(resource.src)) { |
314 | 101 | if (!rangeEnd) { |
315 | 56 | rangeEnd = len; |
316 | } | |
317 | 325 | } else if (rangeEnd) { |
318 | 3 | mergeResources(len + 1); |
319 | } | |
320 | } | |
321 | 203 | if (rangeEnd != null) { |
322 | 53 | mergeResources(0); |
323 | } | |
324 | 203 | complete(); |
325 | }); | |
326 | } | |
327 | }; | |
328 |
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 | resources = require('../util/resources'); | |
25 | ||
26 | 1 | module.exports = { |
27 | mode: 'scripts', | |
28 | priority: 50, | |
29 | ||
30 | loadMixin: function(context, next, complete) { | |
31 | 84 | var mixinTemplates = context.loadedLibrary.templates; |
32 | 84 | if (mixinTemplates) { |
33 | 14 | var templates = context.libraries.originalConfig.templates || {}, |
34 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
35 | assigned = false; | |
36 | ||
37 | 14 | if (context.libraries.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) { |
38 | 4 | assigned = true; |
39 | } | |
40 | ||
41 | 14 | if (assigned) { |
42 | 4 | context.config.attributes.templates = configTemplates; |
43 | } | |
44 | } | |
45 | 84 | next(complete); |
46 | }, | |
47 | ||
48 | resourceList: function(context, next, complete) { | |
49 | 315 | var library = context.resource.library, |
50 | attr = (library && library.parent || library) || context.config.attributes; | |
51 | ||
52 | 315 | next(function(err, ret) { |
53 | 315 | if (err || !ret) { |
54 | 0 | return complete(err); |
55 | } | |
56 | ||
57 | 315 | function pushTemplates(templates) { |
58 | 282 | _.each(templates, function(template) { |
59 | 48 | var src = template.src; |
60 | 48 | if (!src || (template.library && !template.library.attributes)) { |
61 | 34 | var templateLibrary = template.library ? context.libraries.getConfig(template.library) : library; |
62 | 34 | src = mapSrc(template.src || template, templateLibrary, context); |
63 | } | |
64 | ||
65 | 48 | ret.unshift({ |
66 | src: src, | |
67 | name: template.name || template.src || template, | |
68 | library: templateLibrary || template.library || library, | |
69 | template: true | |
70 | }); | |
71 | }); | |
72 | } | |
73 | ||
74 | 315 | var views = attr.templates || {}, |
75 | globalConfig = context.config.attributes.templates || {}, | |
76 | resource = context.resource.originalSrc || context.resource.src || context.resource, | |
77 | mixinRoot = (context.resource.library && context.resource.library.root) || ''; | |
78 | 315 | if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) { |
79 | 241 | resource = resource.substring(mixinRoot.length); |
80 | } | |
81 | ||
82 | 315 | var deferComplete; |
83 | 315 | if (build.filterResource(context.resource, context)) { |
84 | 271 | pushTemplates(views[resource]); |
85 | ||
86 | 271 | if (globalConfig['auto-include']) { |
87 | 12 | var config = context.configCache['template-auto-include']; |
88 | 12 | if (!config) { |
89 | 6 | config = module.exports.generateMappings(globalConfig['auto-include']); |
90 | 6 | context.configCache['template-auto-include'] = config; |
91 | } | |
92 | ||
93 | 12 | var autoIncludes = module.exports.autoIncludes(resource, config, context); |
94 | 12 | if (autoIncludes.length) { |
95 | 11 | deferComplete = true; |
96 | ||
97 | 11 | context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) { |
98 | 11 | if (err) { |
99 | 0 | return complete(err); |
100 | } | |
101 | ||
102 | 11 | var watchDirs = []; |
103 | 11 | autoIncludes = _.filter(autoIncludes, function(file) { |
104 | 17 | if (file.enoent) { |
105 | 3 | watchDirs.push({watch: path.dirname(file.src)}); |
106 | } else { | |
107 | 14 | return true; |
108 | } | |
109 | }); | |
110 | ||
111 | 11 | if (autoIncludes.length) { |
112 | 9 | context.event.emit('log', 'Autoincludes for "' + resource + '" ' + JSON.stringify(_.pluck(autoIncludes, 'src'), undefined, 2)); |
113 | } | |
114 | ||
115 | 11 | pushTemplates(autoIncludes); |
116 | 11 | ret.unshift.apply(ret, watchDirs); |
117 | ||
118 | 11 | complete(undefined, ret); |
119 | }); | |
120 | } | |
121 | } | |
122 | } | |
123 | 315 | if (!deferComplete) { |
124 | 304 | complete(undefined, ret); |
125 | } | |
126 | }); | |
127 | }, | |
128 | ||
129 | resource: function(context, next, complete) { | |
130 | 231 | var resource = context.resource; |
131 | ||
132 | 231 | if (resource.watch) { |
133 | 2 | function generator(buildContext, callback) { |
134 | // Ensure that the directory actually exists | |
135 | 2 | var path = context.fileUtil.resolvePath(resource.watch); |
136 | 2 | context.fileUtil.stat(path, function(err, stat) { |
137 | // Ignore any errors here | |
138 | 2 | var inputs = []; |
139 | 2 | if (stat && stat.isDirectory()) { |
140 | 2 | inputs.push(path); |
141 | } | |
142 | 2 | callback(undefined, {inputs: inputs, data: '', noSeparator: true}); |
143 | }); | |
144 | } | |
145 | 2 | complete(undefined, generator); |
146 | } else { | |
147 | 229 | next(complete); |
148 | } | |
149 | }, | |
150 | ||
151 | autoIncludes: function(resource, config, context) { | |
152 | 12 | var autoIncludes = []; |
153 | 12 | _.each(config, function(mapping) { |
154 | 12 | var remap = module.exports.remapFile(mapping, resource, context); |
155 | 12 | if (remap) { |
156 | 11 | autoIncludes.push.apply(autoIncludes, remap); |
157 | } | |
158 | }); | |
159 | 12 | return autoIncludes; |
160 | }, | |
161 | generateMappings: function(autoInclude) { | |
162 | 6 | return _.map(autoInclude, function(templates, source) { |
163 | 6 | if (!_.isArray(templates)) { |
164 | 2 | templates = [templates]; |
165 | } | |
166 | 6 | return {regex: new RegExp(source), templates: templates}; |
167 | }); | |
168 | }, | |
169 | remapFile: function(mapping, resource, context) { | |
170 | /*jshint boss:true */ | |
171 | 15 | var match; |
172 | 15 | if (match = mapping.regex.exec(resource)) { |
173 | 13 | return _.map(mapping.templates, function(template) { |
174 | // Work in reverse so $10 takes priority over $1 | |
175 | 25 | var i = match.length; |
176 | 25 | while (i--) { |
177 | 50 | template = template.replace('$' + i, match[i]); |
178 | } | |
179 | 25 | var resource = context.libraries.mapFile(template, template.library || context.resource.library); |
180 | 25 | resource = resources.cast(resource); |
181 | 25 | resource.name = template; |
182 | 25 | return resource; |
183 | }); | |
184 | } | |
185 | } | |
186 | }; | |
187 | ||
188 | 1 | function mapSrc(template, library, context) { |
189 | 34 | var resource = context.libraries.mapFile(template, library); |
190 | 34 | return _.isString(resource.src) ? resource.src : resource; |
191 | } | |
192 |
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 | ||
8 | 1 | module.exports = { |
9 | mode: 'static', | |
10 | priority: 50, | |
11 | ||
12 | updateHtmlReferences: function(context, content, callback) { | |
13 | 15 | function updateResources(mode, query, create) { |
14 | 30 | return function(callback) { |
15 | 29 | async.forEach($(query), function(el, callback) { |
16 | 10 | el = $(el); |
17 | 10 | var module = (el.attr('src') || el.attr('href')).replace(/^module:/, ''); |
18 | 10 | context.fileNamesForModule(mode, module, function(err, fileNames) { |
19 | 10 | if (err) { |
20 | 2 | return callback(err); |
21 | } | |
22 | ||
23 | // Generate replacement elements for each of the entries | |
24 | 8 | var content = fileNames.map(function(fileName) { |
25 | 8 | if (fileName.server) { |
26 | 0 | return ''; |
27 | } | |
28 | 8 | return create(loadDirName + basename(fileName.fileName.path) + '.' + fileName.fileName.extension); |
29 | }); | |
30 | ||
31 | // Output and kill the original | |
32 | 8 | el.replaceWith(content.join('')); |
33 | ||
34 | 8 | callback(); |
35 | }); | |
36 | }, | |
37 | callback); | |
38 | }; | |
39 | } | |
40 | 15 | var $ = cheerio.load(content), |
41 | loadDirName = ''; | |
42 | 15 | async.series([ |
43 | function(callback) { | |
44 | // Output the load prefix script we we have a module: script reference | |
45 | 15 | var firstScript = $('script[src^="module:"]'); |
46 | 15 | if (firstScript) { |
47 | 15 | context.plugins.get('module-map').buildMap(context, function(err, map, loadPrefix) { |
48 | 15 | if (err) { |
49 | 0 | return callback(err); |
50 | } | |
51 | ||
52 | 15 | var noFileComponent = !loadPrefix; |
53 | 15 | loadPrefix = context.platformPath + loadPrefix; |
54 | 15 | var dirname = path.dirname(loadPrefix + 'a'); // Force a component for the trailing '/' case |
55 | ||
56 | // Only remap load prefix if not defined by the user | |
57 | 15 | if (!(loadDirName = context.config.loadPrefix())) { |
58 | 14 | var resourcePath = path.dirname(context.fileName.substring(context.outdir.length + 1)); |
59 | 14 | loadPrefix = path.relative(resourcePath, loadPrefix); |
60 | 14 | loadDirName = path.relative(resourcePath, dirname); |
61 | ||
62 | 14 | if (loadDirName) { |
63 | 7 | loadDirName += '/'; |
64 | } | |
65 | 14 | if (loadPrefix && noFileComponent) { |
66 | 7 | loadPrefix += '/'; |
67 | } | |
68 | } else { | |
69 | // A load prefix was given, just combine this with the module map prefix | |
70 | 1 | loadPrefix = loadDirName + loadPrefix; |
71 | 1 | if (dirname !== '.') { |
72 | 1 | loadDirName += dirname + '/'; |
73 | } | |
74 | } | |
75 | ||
76 | 15 | var script = '<script type="text/javascript">var lumbarLoadPrefix = \'' + loadPrefix + '\';</script>'; |
77 | 15 | firstScript.before(script); |
78 | 15 | callback(); |
79 | }); | |
80 | } else { | |
81 | 0 | callback(); |
82 | } | |
83 | }, | |
84 | updateResources('scripts', 'script[src^="module:"]', function(href) { | |
85 | 7 | return '<script type="text/javascript" src="' + href + '"></script>'; |
86 | }), | |
87 | updateResources('styles', 'link[href^="module:"]', function(href) { | |
88 | 1 | return '<link rel="stylesheet" type="text/css" href="' + href + '"/>'; |
89 | }) | |
90 | ], | |
91 | function(err) { | |
92 | 15 | callback(err, $.html()); |
93 | }); | |
94 | }, | |
95 | ||
96 | resource: function(context, next, complete) { | |
97 | 28 | var resource = context.resource; |
98 | 28 | if (resource['update-externals'] || (/\.html?$/.test(resource.src) && resource['update-externals'] !== false)) { |
99 | 6 | next(function(err, resource) { |
100 | 6 | function generator(context, callback) { |
101 | // Load the source data | |
102 | 6 | context.loadResource(resource, function(err, file) { |
103 | 6 | if (err) { |
104 | 0 | return callback(err); |
105 | } | |
106 | ||
107 | // Update the content | |
108 | 6 | module.exports.updateHtmlReferences(context, file.content, function(err, data) { |
109 | 6 | callback(err, { |
110 | data: data, | |
111 | inputs: file.inputs | |
112 | }); | |
113 | }); | |
114 | }); | |
115 | } | |
116 | ||
117 | // Include any attributes that may have been defined on the base entry | |
118 | 6 | if (!_.isString(resource)) { |
119 | 6 | _.extend(generator, resource); |
120 | } | |
121 | 6 | complete(undefined, generator); |
122 | }); | |
123 | } else { | |
124 | 22 | next(complete); |
125 | } | |
126 | } | |
127 | }; | |
128 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | build = require('./build'), | |
4 | combine = require('./jsCombine'), | |
5 | configLoader = require('./config'), | |
6 | Context = require('./context'), | |
7 | fs = require('fs'), | |
8 | fu = require('./fileUtil'), | |
9 | Libraries = require('./libraries'), | |
10 | plugin = require('./plugin'), | |
11 | WatchManager = require('./watch-manager'); | |
12 | ||
13 | 1 | exports.loadAndInitDir = function(path, event, options, callback) { |
14 | 31 | exports.loadConfig(path, event, options, function(err, context) { |
15 | 31 | if (err) { |
16 | 0 | return callback(err); |
17 | } | |
18 | ||
19 | 31 | exports.ensureDirs(context, function(err) { |
20 | 31 | return callback(err, context); |
21 | }); | |
22 | }); | |
23 | }; | |
24 | ||
25 | 1 | exports.loadConfig = function(path, event, options, callback) { |
26 | 41 | try { |
27 | 41 | fu.resetCache(); |
28 | ||
29 | 41 | var config = _.isString(path) ? configLoader.load(path) : configLoader.create(path); |
30 | ||
31 | 41 | var plugins = plugin.create(options); |
32 | 41 | plugins.initialize(config); |
33 | ||
34 | 40 | config.outdir = options.outdir = options.outdir || config.attributes.output; |
35 | ||
36 | 40 | var libraries = new Libraries(options); |
37 | 40 | var context = new Context(options, config, plugins, libraries, event); |
38 | 40 | context.options = options; |
39 | 40 | context.configCache = {}; |
40 | ||
41 | 40 | libraries.initialize(context, function(err) { |
42 | 40 | if (err) { |
43 | 1 | return callback(err); |
44 | } | |
45 | ||
46 | 39 | plugins.loadConfig(context, function(err) { |
47 | 39 | if (err) { |
48 | 1 | return callback(err); |
49 | } | |
50 | ||
51 | 38 | event.emit('config', context.config); |
52 | 38 | if (options.verbose) { |
53 | 1 | event.emit('log', 'Finalized config ' + JSON.stringify(context.config.serialize(), undefined, 2)); |
54 | } | |
55 | ||
56 | 38 | callback(undefined, context); |
57 | }); | |
58 | }); | |
59 | } catch (err) { | |
60 | 1 | callback(err); |
61 | } | |
62 | }; | |
63 | ||
64 | 1 | exports.ensureDirs = function(context, callback) { |
65 | 31 | var config = context.config; |
66 | ||
67 | // Ensure that we have the proper build output | |
68 | 31 | if (!config.outdir) { |
69 | 1 | return callback(new Error('Output must be defined on the command line or config file.')); |
70 | } | |
71 | 30 | context.outdir = config.outdir; |
72 | ||
73 | 30 | fu.ensureDirs(config.outdir + '/.', function() { |
74 | 30 | var stat = fs.statSync(config.outdir); |
75 | 30 | if (!stat.isDirectory()) { |
76 | 1 | callback(new Error('Output must be a directory')); |
77 | } else { | |
78 | 29 | callback(); |
79 | } | |
80 | }); | |
81 | }; | |
82 | ||
83 | 1 | exports.buildPackages = function(rootContext, packageName, modules, callback) { |
84 | 33 | if (!callback) { |
85 | 11 | callback = modules; |
86 | 11 | modules = undefined; |
87 | } | |
88 | ||
89 | 33 | exports.loadPackages(rootContext, packageName, modules, function(err, contexts) { |
90 | 33 | if (err) { |
91 | 0 | return callback(err); |
92 | } | |
93 | ||
94 | 33 | exports.buildContexts(contexts, callback); |
95 | }); | |
96 | }; | |
97 | ||
98 | 1 | exports.loadPackages = function(rootContext, packageName, modules, callback) { |
99 | 42 | if (!callback) { |
100 | 6 | callback = modules; |
101 | 6 | modules = undefined; |
102 | } | |
103 | ||
104 | // Allow a string or a list as modules input | |
105 | 42 | if (!_.isArray(modules)) { |
106 | 40 | modules = [modules]; |
107 | 2 | } else if (!modules.length) { |
108 | // Special case empty array input to build all | |
109 | 1 | modules = [undefined]; |
110 | } | |
111 | ||
112 | 42 | var options = {}; |
113 | 42 | if (typeof packageName === 'object') { |
114 | 1 | options = packageName; |
115 | 1 | packageName = options.package; |
116 | } | |
117 | ||
118 | 42 | var packages = rootContext.config.packageList(); |
119 | 42 | if (packageName && !_.contains(packages, packageName)) { |
120 | 1 | return callback(undefined, {}); |
121 | } | |
122 | ||
123 | 41 | var packageNames = packageName ? [packageName] : packages, |
124 | contexts = []; | |
125 | ||
126 | 41 | packageNames.forEach(function(pkg) { |
127 | 46 | modules.forEach(function(module) { |
128 | 47 | options.package = pkg; |
129 | 47 | options.module = module || undefined; // '' -> undefined |
130 | ||
131 | 47 | rootContext.event.emit('debug', 'Load package: ' + pkg); |
132 | ||
133 | 47 | var platforms = rootContext.config.platformList(pkg); |
134 | 47 | platforms.forEach(function(platform) { |
135 | 64 | options.platform = platform; |
136 | ||
137 | 64 | var newContext = rootContext.clone(options); |
138 | 64 | contexts.push(newContext); |
139 | }); | |
140 | }); | |
141 | }); | |
142 | ||
143 | 41 | var ret = {}; |
144 | 41 | async.forEach( |
145 | contexts, | |
146 | function(context, callback) { | |
147 | 64 | exports.loadPlatform(context, function(err, contexts) { |
148 | 51 | if (!err) { |
149 | 51 | var pkg = ret[context.package] = ret[context.package] || {}; |
150 | 51 | pkg[context.platform] = _.flatten(contexts); |
151 | } | |
152 | 51 | return callback(err); |
153 | }); | |
154 | }, | |
155 | function(err) { | |
156 | 35 | callback(err, ret); |
157 | }); | |
158 | }; | |
159 | ||
160 | 1 | exports.loadPlatform = function(context, callback) { |
161 | 88 | context.event.emit('debug', 'Load platform: ' + context.description); |
162 | 88 | var modes = context.mode ? [context.mode] : context.plugins.modes(); |
163 | ||
164 | 88 | async.map(modes, function(mode, callback) { |
165 | 184 | exports.loadMode(mode, context, callback); |
166 | }, | |
167 | function(err, contexts) { | |
168 | 86 | callback(err, contexts && _.flatten(contexts)); |
169 | }); | |
170 | }; | |
171 | ||
172 | 1 | exports.loadMode = function(mode, context, callback) { |
173 | 184 | context.event.emit('debug', 'Load mode: ' + context.description); |
174 | ||
175 | 184 | context = context.clone(); |
176 | 184 | context.mode = mode; |
177 | 184 | context.modeCache = {}; |
178 | ||
179 | 184 | if (context.fileConfig) { |
180 | 21 | callback(undefined, [processFileConfig(context.fileConfig)]); |
181 | } else { | |
182 | 163 | context.plugins.outputConfigs(context, function(err, configs) { |
183 | 163 | if (err) { |
184 | 1 | return callback(err); |
185 | } | |
186 | ||
187 | 162 | callback(undefined, _.map(configs, processFileConfig)); |
188 | }); | |
189 | } | |
190 | ||
191 | 184 | function processFileConfig(fileConfig) { |
192 | 194 | var fileContext = context.clone(true); |
193 | 194 | fileContext.fileConfig = fileConfig; |
194 | ||
195 | 194 | return fileContext; |
196 | } | |
197 | }; | |
198 | ||
199 | 1 | exports.buildContexts = function(configContexts, callback) { |
200 | 73 | if (configContexts instanceof Context) { |
201 | 0 | configContexts = [configContexts]; |
202 | 73 | } else if (!_.isArray(configContexts)) { |
203 | 33 | configContexts = _.map(configContexts, function(package) { |
204 | 36 | return _.values(package); |
205 | }); | |
206 | 33 | configContexts = _.flatten(configContexts); |
207 | } | |
208 | ||
209 | 73 | async.forEach( |
210 | configContexts, | |
211 | function(fileContext, callback) { | |
212 | 193 | var modules = fileContext.module ? [fileContext.module] : fileContext.config.moduleList(fileContext.package); |
213 | ||
214 | 193 | fileContext.resources = []; |
215 | 193 | fileContext.combineResources = {}; |
216 | 193 | fileContext.fileCache = fileContext.combined ? {} : undefined; |
217 | ||
218 | 193 | async.forEach(modules, function(module, callback) { |
219 | 281 | var moduleContext = fileContext.clone(); |
220 | 281 | moduleContext.module = module; |
221 | ||
222 | 281 | exports.buildModule(moduleContext, callback); |
223 | }, | |
224 | function(err) { | |
225 | 189 | if (err) { |
226 | 4 | return callback(err); |
227 | } | |
228 | ||
229 | 185 | fileContext.plugins.modeComplete(fileContext, callback); |
230 | }); | |
231 | }, | |
232 | callback); | |
233 | }; | |
234 | ||
235 | 1 | exports.buildModule = function(context, callback) { |
236 | 275 | context.event.emit('debug', 'Build module: ' + context.description); |
237 | ||
238 | 275 | var module = context.config.module(context.module); |
239 | 275 | if (!module) { |
240 | 1 | return callback(new Error('Unable to find module "' + context.module + '"')); |
241 | } | |
242 | ||
243 | 274 | context.module = module; |
244 | 274 | context.moduleCache = {}; |
245 | 274 | context.fileCache = context.combined ? context.fileCache : {}; |
246 | ||
247 | 274 | var resource = context.resource; |
248 | 274 | if (resource) { |
249 | 7 | resource = resource.originalResource || resource; |
250 | 7 | exports.processResources(context, [resource], callback); |
251 | } else { | |
252 | // Load all resources associated with this module | |
253 | 267 | build.loadResources(context, function(err, resources) { |
254 | 267 | if (err) { |
255 | 1 | return callback(err); |
256 | } | |
257 | 266 | exports.processResources(context, resources, callback); |
258 | }); | |
259 | } | |
260 | }; | |
261 | ||
262 | 1 | exports.processResources = function(context, resources, callback) { |
263 | 273 | build.processResources(resources, context, function(err, resources) { |
264 | 273 | if (err) { |
265 | 1 | return callback(err); |
266 | } | |
267 | ||
268 | 272 | context.moduleResources = resources; |
269 | 272 | context.plugins.module(context, callback); |
270 | }); | |
271 | }; | |
272 |
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 | 69 | 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 | 2822 | 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 | 1 | var _ = require('underscore'), |
2 | bower = require('bower'), | |
3 | path = require('path'), | |
4 | normalize = path.normalize; | |
5 | ||
6 | /** | |
7 | * Standalone helpers for resource lifetime management and mapping. | |
8 | */ | |
9 | 1 | var resources = module.exports = { |
10 | cast: function(resource) { | |
11 | 823 | if (_.isString(resource)) { |
12 | 268 | return {src: resource}; |
13 | } else { | |
14 | 555 | return resource; |
15 | } | |
16 | }, | |
17 | ||
18 | source: function(resource) { | |
19 | 687 | return resource.src || resource.dir || resource; |
20 | }, | |
21 | ||
22 | map: function(resource, library, config) { | |
23 | 330 | var bowerPath; |
24 | 330 | if (_.isString(resource.bower)) { |
25 | 2 | bowerPath = path.join(bower.config.directory, resource.bower); |
26 | } | |
27 | ||
28 | // If no mixin was defined on either side then return the identity | |
29 | 330 | if (!library && !bowerPath) { |
30 | 225 | return resource; |
31 | } | |
32 | ||
33 | 105 | resource = resources.cast(_.clone(resource)); |
34 | ||
35 | 105 | var src = resources.source(resource); |
36 | ||
37 | // Include any config information such as env or platform that may have been | |
38 | // specified on the library settings | |
39 | 105 | _.extend(resource, _.omit(config, 'overrideLibrary')); |
40 | ||
41 | 105 | if (_.isString(src)) { |
42 | 103 | var override = findOverride(library, src), |
43 | librarySrc = bowerPath || library.root || ''; | |
44 | 103 | librarySrc = librarySrc ? path.join(librarySrc, src) : src; |
45 | ||
46 | 103 | if (override) { |
47 | 18 | resource.originalSrc = librarySrc; |
48 | 18 | librarySrc = _.isString(override.override) ? override.override : src; |
49 | ||
50 | 18 | if (override.root) { |
51 | 6 | librarySrc = path.join(override.root, librarySrc); |
52 | } | |
53 | 85 | } else if (override === false) { |
54 | 3 | return; |
55 | } | |
56 | ||
57 | 100 | if (resource.src) { |
58 | 99 | resource.src = librarySrc; |
59 | 1 | } else if (resource.dir) { |
60 | 1 | resource.dir = librarySrc; |
61 | } | |
62 | } | |
63 | ||
64 | 102 | resource.library = library; |
65 | 102 | return resource; |
66 | }, | |
67 | ||
68 | calcOverrides: function(library, extend) { | |
69 | 9 | var ret = {}; |
70 | 9 | while (library) { |
71 | 11 | _.each(library.overrides, function(override, src) { |
72 | /*jshint eqnull:true */ | |
73 | 9 | if (override != null) { |
74 | 9 | ret[src] = { |
75 | override: override | |
76 | }; | |
77 | 9 | extend && extend(library.overrideLibrary, src, ret[src]); |
78 | } | |
79 | }); | |
80 | ||
81 | 11 | library = library.overrideLibrary; |
82 | } | |
83 | 9 | return ret; |
84 | }, | |
85 | ||
86 | relativePath: function(src, library) { | |
87 | 39 | if (src.indexOf('./') === 0) { |
88 | 0 | src = src.substring(2); |
89 | } | |
90 | 39 | src = normalize(src); |
91 | ||
92 | // Attempt to strip either the root of the base or overriding library as we don't know | |
93 | // which we might be | |
94 | 39 | while (library) { |
95 | 4 | var mixinRoot = library.root || ''; |
96 | 4 | if (src.indexOf(mixinRoot) === 0) { |
97 | 3 | return src.substring(mixinRoot.length); |
98 | } | |
99 | ||
100 | 1 | library = library.overrideLibrary; |
101 | } | |
102 | ||
103 | 36 | return src; |
104 | }, | |
105 | ||
106 | pathToLibrary: function(src, library) { | |
107 | 39 | src = resources.relativePath(src, library); |
108 | ||
109 | 39 | var overrides = library && library.overrides; |
110 | 39 | if (overrides) { |
111 | 1 | overrides = _.invert(overrides); |
112 | ||
113 | // Warn not supporting directories at this point in time. Matches must be 1 to 1 | |
114 | 1 | return overrides[src] || src; |
115 | } | |
116 | ||
117 | 38 | return src; |
118 | } | |
119 | }; | |
120 | ||
121 | 1 | function findOverride(library, src) { |
122 | /*jshint eqnull:true */ | |
123 | 103 | var ret; |
124 | 103 | while (library) { |
125 | 123 | var override = library.overrides && library.overrides[src]; |
126 | 123 | if (override != null) { |
127 | 23 | ret = { |
128 | override: override, | |
129 | root: (library.overrideLibrary || {}).root || '' | |
130 | }; | |
131 | } | |
132 | ||
133 | 123 | library = library.overrideLibrary; |
134 | } | |
135 | ||
136 | 103 | if (ret) { |
137 | 21 | return ret.override === false ? false : ret; |
138 | } | |
139 | } | |
140 |
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 | 8 | try { |
75 | 8 | var newStat = fs.statSync(filename); |
76 | 6 | if (newStat.isDirectory()) { |
77 | 1 | notifyWatch(filename, 'create', filename); |
78 | 5 | } else if (newStat.size !== oldStat.size || newStat.mtime.getTime() > oldStat.mtime.getTime()) { |
79 | 5 | oldStat = newStat; |
80 | 5 | if (type === 'rename') { |
81 | 1 | rewatch = true; |
82 | } | |
83 | 5 | lastType = type; |
84 | 5 | 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 | 102 | var watch = watchedFiles[filename.virtual || filename]; |
124 | 102 | if (!watch) { |
125 | // Create a watch on this and all others | |
126 | 79 | watchFile(filename, callback); |
127 | } else { | |
128 | 23 | watch.callback = callback; |
129 | } | |
130 | ||
131 | 102 | filename = filename.virtual || filename; |
132 | ||
133 | 102 | dependencies.forEach(function(depend) { |
134 | 279 | var watch = watchedFiles[depend.virtual || depend]; |
135 | 279 | if (!watch) { |
136 | 106 | watchFile(depend, undefined, filename); |
137 | } else { | |
138 | 173 | 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 | 77 | var self = this; |
31 | ||
32 | 77 | 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 | 349 | var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); }), |
43 | removed = _.difference(this.watching[status.fileName], input); | |
44 | ||
45 | 77 | if (removed.length) { |
46 | 2 | watcher.unwatch(status.fileName, removed); |
47 | } | |
48 | ||
49 | 77 | watcher.watchFile({ virtual: status.fileName }, input, theWatcher); |
50 | 77 | 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 |