[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 | 334 | var plugins = context.plugins; |
18 | ||
19 | 334 | function filterResource(resource) { |
20 | 565 | resource = resources.cast(resource); |
21 | ||
22 | 565 | if (exports.filterResource(resource, context)) { |
23 | 484 | return resource; |
24 | } | |
25 | } | |
26 | ||
27 | 334 | plugins.moduleResources(context, function(err, files) { |
28 | 334 | if (err) { |
29 | 1 | return callback(err); |
30 | } | |
31 | ||
32 | 333 | var fileFilter = plugins.fileFilter(context) || /.*/; |
33 | 333 | fu.fileList(files, fileFilter, function(err, files) { |
34 | 333 | if (err) { |
35 | 1 | return callback(err); |
36 | } | |
37 | ||
38 | 332 | async.map(files, function(resource, callback) { |
39 | 510 | var resourceContext = context.clone(); |
40 | 510 | resourceContext.resource = resource; |
41 | 510 | plugins.resourceList(resourceContext, callback); |
42 | }, | |
43 | function(err, resources) { | |
44 | 332 | resources = _.flatten(resources); |
45 | 332 | resources = _.map(resources, filterResource); |
46 | 897 | resources = _.filter(resources, function(resource) { return resource; }); |
47 | 332 | 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 | 962 | function check(value, singular, plural) { |
58 | 2638 | if (typeof singular !== 'undefined') { |
59 | 172 | return singular.not ? singular.not !== value : singular === value; |
60 | 2466 | } 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 | 2393 | return true; |
67 | } | |
68 | ||
69 | 962 | function checkResource(resource) { |
70 | 966 | return check(context.platform, resource.platform, resource.platforms) |
71 | && check(context.package, resource.package, resource.packages) | |
72 | && check(!!context.combined, resource.combined); | |
73 | } | |
74 | 962 | 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 | 332 | var plugins = context.plugins; |
89 | ||
90 | 332 | async.map(resources, function(resource, callback) { |
91 | 457 | var resourceContext = context.clone(); |
92 | 457 | resourceContext.resource = resource; |
93 | 457 | plugins.resource(resourceContext, function(err, newResource) { |
94 | 457 | if (newResource && newResource !== resource) { |
95 | 105 | newResource.originalResource = resource; |
96 | } | |
97 | ||
98 | 457 | callback(err, newResource); |
99 | }); | |
100 | }, | |
101 | function(err, resources) { | |
102 | 332 | if (err) { |
103 | 1 | return callback(err); |
104 | } | |
105 | ||
106 | 787 | 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 | 38 | try { |
11 | 38 | 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 | 38 | 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 | 30 | fu.lookupPath(''); |
40 | ||
41 | 30 | var config = exports.readConfig(lumbarFile); |
42 | 30 | fu.lookupPath(path.dirname(lumbarFile)); |
43 | ||
44 | 30 | return exports.create(config); |
45 | }; | |
46 | ||
47 | 1 | exports.create = function(config) { |
48 | 185 | var packageList, moduleList; |
49 | ||
50 | 185 | function loadPackageList() { |
51 | 185 | if (!config.packages) { |
52 | 137 | config.packages = { web: { name: '' } }; |
53 | } | |
54 | ||
55 | 185 | packageList = _.keys(config.packages); |
56 | } | |
57 | 185 | function loadModuleList() { |
58 | 185 | if (!config.modules) { |
59 | 1 | throw new Error('No modules object defined: ' + JSON.stringify(config, undefined, 2)); |
60 | } | |
61 | 184 | moduleList = _.keys(config.modules); |
62 | } | |
63 | ||
64 | 185 | loadPackageList(); |
65 | 185 | loadModuleList(); |
66 | ||
67 | 184 | return { |
68 | /** @typedef {Object} The raw lumbar file as a JSON object. */ | |
69 | attributes: config, | |
70 | loadPrefix: function() { | |
71 | 52 | 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 | 31 | 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 | 1923 | if (config && config.packages && config.packages[pkg]) { |
95 | 1621 | return config.packages[pkg].combine; |
96 | } | |
97 | 302 | return false; |
98 | }, | |
99 | platformList: function(pkg) { | |
100 | 106 | if (!pkg) { |
101 | 61 | return config.platforms || ['']; |
102 | } else { | |
103 | 45 | if (config.packages[pkg]) { |
104 | 45 | return config.packages[pkg].platforms || this.platformList(); |
105 | } | |
106 | 0 | return this.platformList(); |
107 | } | |
108 | }, | |
109 | ||
110 | moduleList: function(pkg) { | |
111 | 252 | return (config.packages[pkg] || {}).modules || _.keys(config.modules); |
112 | }, | |
113 | ||
114 | module: function(name) { | |
115 | 512 | var ret = config.modules[name]; |
116 | 512 | if (ret) { |
117 | 509 | ret.name = name; |
118 | } | |
119 | 512 | return ret; |
120 | }, | |
121 | isTopLevel: function(module) { | |
122 | 52 | var app = config.application || {}; |
123 | 52 | if (!app.name) { |
124 | 22 | return true; |
125 | } | |
126 | ||
127 | 30 | module = module.name ? module : this.module(module); |
128 | 30 | return module.topLevelName || this.isAppModule(module); |
129 | }, | |
130 | isAppModule: function(module) { | |
131 | 80 | var app = config.application; |
132 | 80 | return (app && app.module) === (module.name || module); |
133 | }, | |
134 | scopedAppModuleName: function(module) { | |
135 | 46 | var app = config.application; |
136 | 46 | if (this.isAppModule(module)) { |
137 | 4 | return 'module.exports'; |
138 | } else { | |
139 | 42 | var app = config.application; |
140 | 42 | return app && app.name; |
141 | } | |
142 | }, | |
143 | ||
144 | routeList: function(module) { | |
145 | 24 | return config.modules[module].routes; |
146 | }, | |
147 | ||
148 | serialize: function() { | |
149 | 2 | function objectClone(object) { |
150 | 19 | var clone = object; |
151 | ||
152 | 19 | if (object && object.serialize) { |
153 | // Allow specialized objects to handle themselves | |
154 | 0 | clone = object.serialize(); |
155 | 19 | } else if (_.isArray(object)) { |
156 | 1 | clone = _.map(object, objectClone); |
157 | 18 | } else if (_.isObject(object)) { |
158 | 12 | clone = {}; |
159 | 12 | _.each(object, function(value, name) { |
160 | 15 | clone[name] = objectClone(value); |
161 | }); | |
162 | } | |
163 | ||
164 | // Collapse simple resources | |
165 | 19 | if (clone.src && _.keys(clone).length === 1) { |
166 | 0 | clone = clone.src; |
167 | } | |
168 | ||
169 | 19 | return clone; |
170 | } | |
171 | ||
172 | 2 | return objectClone(this.attributes); |
173 | } | |
174 | }; | |
175 | }; | |
176 | ||
177 |
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 | 2261 | this._package = options.package; |
9 | 2261 | this._platform = options.platform; |
10 | 2261 | this._plugins = plugins; |
11 | 2261 | this.mode = options.mode; |
12 | 2261 | this.module = options.module; |
13 | 2261 | this.fileConfig = options.fileConfig; |
14 | 2261 | this.resource = options.resource; |
15 | 2261 | this.config = config; |
16 | 2261 | this.libraries = libraries || options.libraries; |
17 | 2261 | this.event = event || options.event; |
18 | } | |
19 | 1 | Context.prototype = { |
20 | fileUtil: fu, | |
21 | ||
22 | clone: function(options) { | |
23 | 2080 | var ret = new Context(this, this.config); |
24 | 2080 | ret.parent = this; |
25 | 2080 | var prototype = Object.keys(Context.prototype); |
26 | 2080 | for (var name in this) { |
27 | 65882 | if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) { |
28 | 38842 | ret[name] = this[name]; |
29 | } | |
30 | } | |
31 | 2080 | if (options) { |
32 | 297 | _.extend(ret, options); |
33 | 297 | ret._package = options.package || this._package; |
34 | 297 | ret._platform = options.platform || this._platform; |
35 | } | |
36 | 2080 | return ret; |
37 | }, | |
38 | ||
39 | fileNamesForModule: function(mode, moduleName, callback) { | |
40 | 85 | var context = this.clone(); |
41 | 85 | context.mode = mode; |
42 | 85 | context.module = moduleName && this.config.module(moduleName); |
43 | 85 | if (moduleName && !context.module) { |
44 | 2 | return callback(new Error('Unknown module "' + moduleName + '"')); |
45 | } | |
46 | ||
47 | 83 | this.plugins.outputConfigs(context, function(err, configs) { |
48 | 83 | if (err) { |
49 | 0 | return callback(err); |
50 | } | |
51 | ||
52 | 83 | async.map(configs, function(config, callback) { |
53 | 109 | var fileContext = context.clone(); |
54 | 109 | fileContext.fileConfig = config; |
55 | 109 | fileContext._plugins.fileName(fileContext, function(err, fileName) { |
56 | 109 | config.fileName = fileName; |
57 | 109 | callback(err, config); |
58 | }); | |
59 | }, | |
60 | callback); | |
61 | }); | |
62 | }, | |
63 | ||
64 | loadResource: function(resource, callback) { | |
65 | 426 | 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 | 426 | var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src}; |
72 | ||
73 | 426 | function loaded(err, data) { |
74 | /*jshint eqnull: true */ | |
75 | 426 | 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 | 422 | fileInfo.inputs = data.inputs; |
97 | 422 | fileInfo.generated = data.generated; |
98 | 422 | fileInfo.noSeparator = data.noSeparator; |
99 | 422 | fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings; |
100 | 422 | fileInfo.content = data.data != null ? data.data : data; |
101 | ||
102 | // Ensure that we dump off the stack | |
103 | 422 | _.defer(function() { |
104 | 422 | callback(err, fileInfo); |
105 | }); | |
106 | } | |
107 | ||
108 | 426 | if (typeof resource === 'function') { |
109 | 207 | 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 | 426 | return fileInfo; |
118 | }, | |
119 | ||
120 | outputFile: function(writer, callback) { | |
121 | 146 | var context = this; |
122 | 146 | context.plugins.file(context, function(err) { |
123 | 146 | if (err) { |
124 | 0 | return callback(err); |
125 | } | |
126 | ||
127 | 146 | context.plugins.fileName(context, function(err, fileName) { |
128 | 146 | if (err) { |
129 | 0 | return callback(err); |
130 | } | |
131 | ||
132 | 146 | context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension; |
133 | 146 | context.fileName = context.outdir + '/' + context.buildPath; |
134 | 146 | writer(function(err, data) { |
135 | 146 | data = _.defaults({ |
136 | fileConfig: context.fileConfig, | |
137 | platform: context.platform, | |
138 | package: context.package, | |
139 | mode: context.mode | |
140 | }, data); | |
141 | ||
142 | 146 | if (err) { |
143 | 3 | fs.unlink(context.fileName, function() { /* NOP To Prevent warning */}); |
144 | 3 | data.error = err; |
145 | } | |
146 | 146 | context.event.emit('output', data); |
147 | ||
148 | 146 | context.fileCache = undefined; |
149 | 146 | callback(err, data); |
150 | }); | |
151 | }); | |
152 | }); | |
153 | }, | |
154 | ||
155 | get description() { | |
156 | 560 | var ret = 'package:' + this.package + '_platform:' + this.platform; |
157 | 560 | if (this.mode) { |
158 | 350 | ret += '_mode:' + this.mode; |
159 | } | |
160 | 560 | if (this.fileName) { |
161 | 118 | ret += '_config:' + this.fileName; |
162 | } | |
163 | 560 | if (this.module) { |
164 | 340 | ret += '_module:' + (this.module.name || this.module); |
165 | } | |
166 | 560 | if (this.resource) { |
167 | // TODO : Anything better for this? | |
168 | 19 | ret += '_resource:' + resources.source(this.resource); |
169 | } | |
170 | 560 | return ret; |
171 | }, | |
172 | ||
173 | 1994 | get plugins() { return this._plugins; }, |
174 | ||
175 | 5859 | get package() { return this._package; }, |
176 | 4281 | get platform() { return this._platform; }, |
177 | get platformPath() { | |
178 | 158 | return this.platform ? this.platform + '/' : ''; |
179 | }, | |
180 | ||
181 | get combined() { | |
182 | 1923 | return this.config.combineModules(this.package); |
183 | }, | |
184 | get baseName() { | |
185 | 229 | if (!this.combined) { |
186 | 161 | 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 | 292 | if (this.parent) { |
194 | 0 | return this.parent.resources; |
195 | } else { | |
196 | 292 | return this._resources; |
197 | } | |
198 | }, | |
199 | set resources(value) { | |
200 | 372 | if (this.parent) { |
201 | 336 | delete this.parent; |
202 | } | |
203 | 372 | 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 | 419 | path = exports.resolvePath(path); |
16 | ||
17 | 419 | var cache = fileCache[path]; |
18 | 419 | if (cache) { |
19 | 195 | if (cache.data) { |
20 | 102 | callback(undefined, cache); |
21 | } else { | |
22 | 93 | cache.pending.push(callback); |
23 | } | |
24 | 195 | return; |
25 | } | |
26 | ||
27 | 224 | cache = fileCache[path] = { |
28 | pending: [callback], | |
29 | artifacts: {} | |
30 | }; | |
31 | ||
32 | 224 | exec(path, function _callback(err, data) { |
33 | 224 | if (err && err.code === 'EMFILE') { |
34 | 0 | setTimeout(exec.bind(this, path, _callback), EMFILE_RETRY); |
35 | } else { | |
36 | 224 | if (err) { |
37 | 3 | delete fileCache[path]; |
38 | } | |
39 | ||
40 | 224 | cache.data = data; |
41 | 224 | cache.pending.forEach(function(callback) { |
42 | 317 | callback(err, cache); |
43 | }); | |
44 | 224 | exports.emit('cache:set', path); |
45 | } | |
46 | }); | |
47 | } | |
48 | ||
49 | 1 | exports.resetCache = function(filePath) { |
50 | 305 | filePath = filePath && path.normalize(filePath); |
51 | 305 | exports.emit('cache:reset', filePath); |
52 | ||
53 | 305 | if (filePath) { |
54 | 179 | filePath = exports.resolvePath(filePath); |
55 | 179 | delete fileCache[filePath]; |
56 | } else { | |
57 | 126 | 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 | 1515 | if (lookupPath |
66 | && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1) | |
67 | && pathName.indexOf(lookupPath) !== 0) { | |
68 | 1036 | return lookupPath + pathName; |
69 | } else { | |
70 | 479 | return pathName; |
71 | } | |
72 | }; | |
73 | 1 | exports.makeRelative = function(pathName) { |
74 | 591 | if (pathName.indexOf(lookupPath) === 0) { |
75 | 559 | return pathName.substring(lookupPath.length); |
76 | } else { | |
77 | 32 | return pathName; |
78 | } | |
79 | }; | |
80 | ||
81 | 1 | exports.lookupPath = function(pathName) { |
82 | 157 | if (pathName !== undefined) { |
83 | 95 | lookupPath = pathName; |
84 | 95 | if (lookupPath && !/\/$/.test(lookupPath)) { |
85 | 39 | lookupPath += '/'; |
86 | } | |
87 | } | |
88 | 157 | return lookupPath; |
89 | }; | |
90 | ||
91 | 1 | exports.stat = function(file, callback) { |
92 | 819 | fs.stat(file, function(err, stat) { |
93 | 819 | if (err && err.code === 'EMFILE') { |
94 | 0 | setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY); |
95 | } else { | |
96 | 819 | callback(err, stat); |
97 | } | |
98 | }); | |
99 | }; | |
100 | ||
101 | 1 | exports.readFileSync = function(file) { |
102 | 38 | 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 | 128 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) { |
111 | 128 | var artifacts = cache.artifacts; |
112 | 128 | callback(err, {data: cache.data, artifact: artifacts[name]}); |
113 | }); | |
114 | }; | |
115 | 1 | exports.setFileArtifact = function(path, name, artifact) { |
116 | 28 | path = exports.resolvePath(path); |
117 | ||
118 | 28 | var cache = fileCache[path]; |
119 | 28 | if (cache) { |
120 | 28 | 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 | 244 | var dirname = path.dirname(pathname); |
132 | 244 | exports.stat(dirname, function(err) { |
133 | 244 | if (err && err.code === 'ENOENT') { |
134 | // If we don't exist, check to see if our parent exists before trying to create ourselves | |
135 | 39 | exports.ensureDirs(dirname, function() { |
136 | 39 | fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) { |
137 | 39 | 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 | 39 | callback(err && err.code === 'EEXIST' ? undefined : err); |
142 | } | |
143 | }); | |
144 | }); | |
145 | } else { | |
146 | 205 | callback(); |
147 | } | |
148 | }); | |
149 | }; | |
150 | ||
151 | 1 | exports.writeFile = function(file, data, callback) { |
152 | 138 | exports.resetCache(file); |
153 | ||
154 | 138 | exports.ensureDirs(file, function(err) { |
155 | 138 | if (err) { |
156 | 0 | return callback(err); |
157 | } | |
158 | ||
159 | 138 | fs.writeFile(file, data, 'utf8', function _callback(err) { |
160 | 138 | if (err && err.code === 'EMFILE') { |
161 | 0 | setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY); |
162 | } else { | |
163 | 138 | 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 | 883 | if (_.isFunction(extension)) { |
179 | 5 | callback = extension; |
180 | 5 | extension = /.*/; |
181 | } | |
182 | ||
183 | 883 | if (_.isArray(pathname)) { |
184 | 310 | var files = pathname; |
185 | 310 | pathname = ''; |
186 | 310 | if (!files.length) { |
187 | 125 | return callback(undefined, []); |
188 | } | |
189 | 185 | return handleFiles(false, undefined, _.uniq(files)); |
190 | 573 | } else if (!dirList) { |
191 | 412 | if (pathname.src) { |
192 | 0 | resource = resource || pathname; |
193 | 0 | pathname = pathname.src; |
194 | } | |
195 | ||
196 | 412 | pathname = exports.resolvePath(pathname); |
197 | } | |
198 | 573 | if (resource && resource.src) { |
199 | 189 | resource = _.clone(resource); |
200 | 189 | delete resource.src; |
201 | } | |
202 | ||
203 | 573 | function handleFiles(dirname, err, files, srcDir) { |
204 | 243 | if (err) { |
205 | 0 | return callback(err); |
206 | } | |
207 | ||
208 | 243 | var ret = [], |
209 | count = 0, | |
210 | expected = files.length, | |
211 | prefix = pathname ? pathname.replace(/\/$/, '') + '/' : ''; | |
212 | ||
213 | 243 | function complete(files, index) { |
214 | 610 | count++; |
215 | ||
216 | 610 | ret[index] = files; |
217 | ||
218 | 610 | if (count === expected) { |
219 | 242 | ret = _.flatten(ret); |
220 | ||
221 | 242 | 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 | 242 | 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 | 242 | callback(undefined, ret); |
237 | } | |
238 | } | |
239 | ||
240 | 243 | if (!files.length) { |
241 | 1 | callback(undefined, []); |
242 | } | |
243 | ||
244 | 243 | files.forEach(function(file, index) { |
245 | 610 | var fileResource = resource; |
246 | 610 | if (file.src) { |
247 | 189 | fileResource = resource || file; |
248 | 189 | file = file.src; |
249 | 421 | } else if (_.isObject(file)) { |
250 | 66 | complete(file, index); |
251 | 66 | return; |
252 | } | |
253 | ||
254 | 544 | exports.fileList(prefix + file, extension, function(err, files) { |
255 | 544 | if (err) { |
256 | 0 | callback(err); |
257 | 0 | return; |
258 | } | |
259 | ||
260 | 544 | complete(files, index); |
261 | }, dirname, fileResource, srcDir); | |
262 | }); | |
263 | } | |
264 | ||
265 | 573 | exports.stat(pathname, function(err, stat) { |
266 | 573 | if (err) { |
267 | 63 | if (err.code === 'ENOENT') { |
268 | 63 | callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]); |
269 | } else { | |
270 | 0 | callback(err); |
271 | } | |
272 | 63 | 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 | 45 | function compile(templateStr, callback) { |
303 | 34 | try { |
304 | 34 | if (splitOnDelimiter) { |
305 | 21 | callback(null, templateStr.split(splitOnDelimiter).map(function(bit) { |
306 | 42 | 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 | 45 | if (template.match(/\.handlebars$/)) { |
316 | 20 | exports.readFileArtifact(template, 'template', function(err, data) { |
317 | 20 | if (err) { |
318 | 1 | return callback(err); |
319 | } | |
320 | ||
321 | 19 | if (data.artifact) { |
322 | 10 | callback(undefined, data.artifact); |
323 | } else { | |
324 | 9 | compile(data.data.toString(), function(err, data) { |
325 | 9 | if (!err) { |
326 | 9 | exports.setFileArtifact(template, 'template', data); |
327 | } | |
328 | 9 | 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'); |
8 | ||
9 | 1 | exports.combine = function(context, files, output, minimize, noSeparator, callback) { |
10 | ||
11 | 114 | function outputIfCompleted() { |
12 | 378 | if (completed >= files.length) { |
13 | 111 | var lastEl, |
14 | map = new FileMap(output), | |
15 | warnings = [], | |
16 | ||
17 | tasks = []; | |
18 | ||
19 | 111 | _.each(content, function(el) { |
20 | 378 | var content = el.content.toString(); |
21 | ||
22 | 378 | if (!noSeparator && (!lastEl || !lastEl.noSeparator) && map.content()) { |
23 | 114 | map.add(undefined, '\n;;\n'); |
24 | } | |
25 | ||
26 | 378 | map.add(el.name, content, el, el.generated); |
27 | ||
28 | 378 | lastEl = el; |
29 | }, ''); | |
30 | ||
31 | 111 | var inputs = []; |
32 | 111 | content.forEach(function(el) { |
33 | 378 | if (el.inputs) { |
34 | 113 | inputs.push.apply(inputs, el.inputs); |
35 | 265 | } else if (el.name) { |
36 | 178 | inputs.push(el.name); |
37 | } | |
38 | }); | |
39 | 111 | inputs = _.unique(inputs); |
40 | ||
41 | // "Serialize" the data in the map | |
42 | 111 | tasks.push(function(callback) { |
43 | 111 | callback(undefined, map.content()); |
44 | }); | |
45 | ||
46 | // Minimize the content if flagged | |
47 | 111 | 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 | 111 | var sourceMap = context.options.sourceMap; |
95 | 111 | 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 | 111 | tasks.push(function(data, callback) { |
114 | 111 | fu.writeFile(output, data, callback); |
115 | }); | |
116 | ||
117 | // Excute everything and return to the caller | |
118 | 111 | async.waterfall(tasks, function(err) { |
119 | 111 | if (err) { |
120 | 0 | callback(new Error('Combined output "' + output + '" failed\n\t' + err)); |
121 | 0 | return; |
122 | } | |
123 | ||
124 | 111 | callback(undefined, { |
125 | fileName: output, | |
126 | inputs: inputs, | |
127 | warnings: warnings | |
128 | }); | |
129 | }); | |
130 | } | |
131 | } | |
132 | 114 | var completed = 0, |
133 | content = []; | |
134 | ||
135 | 114 | files.forEach(function(resource) { |
136 | 381 | var fileInfo = context.loadResource(resource, function(err) { |
137 | 381 | if (err && callback) { |
138 | 3 | callback(err); |
139 | 3 | callback = undefined; |
140 | 3 | return; |
141 | } | |
142 | ||
143 | 378 | if (callback) { |
144 | 378 | completed++; |
145 | 378 | outputIfCompleted(); |
146 | } | |
147 | }); | |
148 | 381 | 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 | 148 | this.options = options; |
12 | 148 | this.mixins = []; |
13 | 148 | this.configs = []; |
14 | } | |
15 | ||
16 | 1 | Libraries.prototype.initialize = function(context, callback) { |
17 | 145 | this.mixins = []; |
18 | 145 | this.originalConfig = _.clone(context.config.attributes); |
19 | ||
20 | 145 | function normalize(libraries) { |
21 | 290 | if (_.isString(libraries)) { |
22 | 2 | return [libraries]; |
23 | } else { | |
24 | 288 | return _.map(libraries, function (name) { |
25 | 82 | if (_.isString(name)) { |
26 | 11 | return path.normalize(name); |
27 | } else { | |
28 | 71 | return name; |
29 | } | |
30 | }); | |
31 | } | |
32 | } | |
33 | ||
34 | 145 | 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 | 145 | delete context.config.attributes.mixins; |
41 | ||
42 | 145 | async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback); |
43 | }; | |
44 | ||
45 | 1 | Libraries.prototype.bowerLibraries = function(context) { |
46 | 146 | try { |
47 | 146 | 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 | 145 | 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 | 87 | var root = libraryConfig.root, |
72 | configPath, | |
73 | self = this; | |
74 | ||
75 | // Or as a file reference | |
76 | 87 | 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 | 87 | if (root && !/\/$/.test(root)) { |
98 | 29 | root = root + '/'; |
99 | } | |
100 | ||
101 | 87 | if (!libraryConfig.name) { |
102 | 4 | return callback(new Error('Mixin with root "' + root + '" is missing a name.')); |
103 | } | |
104 | ||
105 | 83 | var mixins = libraryConfig.mixins, |
106 | toRegister = {}; | |
107 | 83 | delete libraryConfig.mixins; |
108 | ||
109 | 83 | function mapMixin(mixin, name) { |
110 | // Only register once, giving priority to an explicitly defined mixin | |
111 | 62 | if (!toRegister[name]) { |
112 | 61 | 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 | 83 | _.each(mixins, mapMixin, this); |
126 | ||
127 | // Make mixin modules accessible as normal mixins as well | |
128 | 83 | _.each(libraryConfig.modules, mapMixin, this); |
129 | ||
130 | // After we've pulled everything in register | |
131 | 83 | _.each(toRegister, function(mixin, name) { |
132 | 61 | this.mixins[name] = this.mixins[name] || []; |
133 | 61 | var list = this.mixins[name]; |
134 | 61 | list.push(mixin); |
135 | }, this); | |
136 | ||
137 | // Run all of the plugins that are concerned with this. | |
138 | 83 | libraryConfig.root = root; |
139 | 83 | libraryConfig.path = configPath; |
140 | 83 | context.loadedLibrary = libraryConfig; |
141 | 83 | context.plugins.loadMixin(context, function(err) { |
142 | 83 | delete libraryConfig.root; |
143 | ||
144 | // And then splat everything else into our config | |
145 | 83 | _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path')); |
146 | ||
147 | 83 | libraryConfig.serialize = function() { |
148 | 0 | return { library: this.name }; |
149 | }; | |
150 | ||
151 | 83 | libraryConfig.root = root; |
152 | 83 | self.configs.push(libraryConfig); |
153 | ||
154 | 83 | callback(err); |
155 | }); | |
156 | }; | |
157 | ||
158 | 1 | Libraries.prototype.findDecl = function(mixins, mixinName) { |
159 | 25 | if (!mixinName.name) { |
160 | 4 | mixinName = {name: mixinName}; |
161 | } | |
162 | ||
163 | 25 | return _.find(mixins, function(mixinDecl) { |
164 | 29 | 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 | 314 | var mixins = _.clone(module.mixins || []), |
172 | processed = {}; | |
173 | 314 | for (var i = 0; i < mixins.length; i++) { |
174 | 112 | 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 | 110 | if (mixinConfig) { |
181 | 40 | mixinConfig = _.omit(mixinConfig, 'overrides', 'name', 'library'); |
182 | } | |
183 | ||
184 | 110 | 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 | 110 | var processedName = mixin.name + '_' + (mixin.parent && mixin.parent.name); |
190 | 110 | if (!processed[processedName]) { |
191 | 92 | processed[processedName] = true; |
192 | ||
193 | 92 | _.each(mixin.attributes.mixins, function(mixinInclude) { |
194 | // Apply any attributes that were applied to the mixin config here | |
195 | 18 | if (mixinConfig) { |
196 | 4 | mixinInclude = mixinInclude.name ? _.clone(mixinInclude) : {name: mixinInclude}; |
197 | 4 | _.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 | 18 | mixinInclude.overrideLibrary = mixin.parent; |
203 | ||
204 | 18 | if (!this.findDecl(mixins, mixinInclude)) { |
205 | 18 | added.push(mixinInclude); |
206 | } | |
207 | }, this); | |
208 | } | |
209 | ||
210 | // If we've found any new mixins insert them at the current spot and iterate | |
211 | // over those items | |
212 | 110 | if (added.length > 2) { |
213 | 18 | mixins.splice.apply(mixins, added); |
214 | 18 | i--; |
215 | } | |
216 | } | |
217 | ||
218 | // Extend the module with each of the mixins content, giving priority to the module | |
219 | 312 | return _.map(mixins.reverse(), function(mixin) { |
220 | 92 | var mixinConfig = mixin.name && mixin, |
221 | name = mixin; | |
222 | 92 | if (mixinConfig) { |
223 | 36 | mixinConfig = _.clone(mixinConfig); |
224 | 36 | delete mixinConfig.library; |
225 | 36 | delete mixinConfig.container; |
226 | } | |
227 | 92 | mixin = _.extend( |
228 | {}, | |
229 | this.getMixin(name), | |
230 | mixinConfig); | |
231 | 92 | if (!mixin.attributes) { |
232 | 0 | throw new Error('Mixin "' + (name.name || name) + '" is not defined.'); |
233 | } | |
234 | ||
235 | // Save a distinct instance of the config for resource extension | |
236 | 92 | if (mixinConfig) { |
237 | 36 | mixinConfig = _.clone(mixinConfig); |
238 | 36 | delete mixinConfig.overrides; |
239 | 36 | delete mixinConfig.name; |
240 | } | |
241 | ||
242 | 92 | return { |
243 | library: mixin, | |
244 | mixinConfig: mixinConfig | |
245 | }; | |
246 | }, this); | |
247 | }; | |
248 | ||
249 | 1 | Libraries.prototype.mapFiles = function(value, library, config) { |
250 | 181 | var files = _.map(value, function(resource) { |
251 | 275 | return this.mapFile(resource, library, config); |
252 | }, this); | |
253 | 454 | files = _.filter(files, function(file) { return file; }); |
254 | ||
255 | 180 | return files; |
256 | }; | |
257 | 1 | Libraries.prototype.mapFile = function(resource, library, config) { |
258 | // If explicitly declared the resource library takes precedence | |
259 | 326 | if (_.isString(resource.library || resource.mixin)) { |
260 | 3 | library = this.getConfig(resource.library || resource.mixin); |
261 | 3 | if (!library) { |
262 | 1 | throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found'); |
263 | } | |
264 | 2 | delete resource.mixin; |
265 | } | |
266 | ||
267 | 325 | return resources.map(resource, library, config); |
268 | }; | |
269 | ||
270 | 1 | Libraries.prototype.mapPathToLibrary = function(src, library) { |
271 | 39 | return resources.pathToLibrary(src, library); |
272 | }; | |
273 | ||
274 | 1 | Libraries.prototype.getMixin = function(name) { |
275 | 204 | var mixins = (this.mixins && this.mixins[name.name || name]) || [], |
276 | library = name.library || name.container; | |
277 | 204 | if (mixins.length > 1 && !library) { |
278 | 1 | throw new Error( |
279 | 'Duplicate mixins found for "' + (name.name || name) + '"' | |
280 | + _.map(mixins, function(mixin) { | |
281 | 2 | return ' parent: "' + mixin.parent.name + '"'; |
282 | }).join('')); | |
283 | } | |
284 | ||
285 | 203 | if (library) { |
286 | 9 | if (name.name === undefined) { |
287 | 0 | var found = _.find(this.configs, function(config) { |
288 | 0 | return config.name === library; |
289 | }); | |
290 | 0 | if (!found) { |
291 | 0 | throw new Error('Unable to find library "' + library + '"'); |
292 | } | |
293 | 0 | return found; |
294 | } | |
295 | ||
296 | 9 | var found = _.find(mixins, function(mixin) { |
297 | 17 | return mixin.parent.name === library; |
298 | }); | |
299 | 9 | if (found) { |
300 | 8 | return found; |
301 | } else { | |
302 | 1 | throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"'); |
303 | } | |
304 | 194 | } else if (mixins.length === 1) { |
305 | 194 | return mixins[0]; |
306 | } | |
307 | }; | |
308 | 1 | Libraries.prototype.getConfig = function(name) { |
309 | 13 | return _.find(this.configs, function(config) { return config.name === name; }); |
310 | }; | |
311 | ||
312 | 1 | Libraries.prototype.mergeHash = function(hashName, input, mixin, output) { |
313 | 86 | if (mixin[hashName]) { |
314 | // Close the value to make sure that we are not overriding anything | |
315 | 11 | if (!output[hashName] || output[hashName] === input[hashName]) { |
316 | 9 | output[hashName] = _.clone(input[hashName] || {}); |
317 | } | |
318 | 11 | _.each(mixin[hashName], function(value, key) { |
319 | 17 | if (!input[hashName] || !(key in input[hashName])) { |
320 | 13 | output[hashName][key] = value; |
321 | } | |
322 | }); | |
323 | 11 | return true; |
324 | } | |
325 | }; | |
326 | 1 | Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) { |
327 | 40 | if (mixinData[fieldName]) { |
328 | 9 | mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]]; |
329 | ||
330 | 9 | var configData = input[fieldName] || []; |
331 | 9 | if (!output[fieldName] || configData === output[fieldName]) { |
332 | 7 | output[fieldName] = _.clone(configData); |
333 | } | |
334 | 9 | if (!_.isArray(configData)) { |
335 | 2 | configData = [configData]; |
336 | } | |
337 | 9 | if (!_.isArray(output[fieldName])) { |
338 | 1 | output[fieldName] = [output[fieldName]]; |
339 | } | |
340 | ||
341 | // Insert point is at the start of the upstream list, which we are | |
342 | // assuming occurs at length postions from the end. | |
343 | 9 | _.each(mixinData, function(value) { |
344 | //Make the include relative to the mixin | |
345 | 12 | value = (library.root || '') + value; |
346 | ||
347 | 12 | output[fieldName].splice( |
348 | output[fieldName].length - configData.length, | |
349 | 0, | |
350 | {src: value, library: library}); | |
351 | }); | |
352 | ||
353 | 9 | return true; |
354 | } | |
355 | }; | |
356 | ||
357 | 1 | module.exports = Libraries; |
358 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | ChildPool = require('child-pool'), | |
3 | Context = require('./context'), | |
4 | EventEmitter = require('events').EventEmitter, | |
5 | fs = require('fs'), | |
6 | stateMachine = require('./state-machine'), | |
7 | WatchManager = require('./watch-manager'); | |
8 | ||
9 | 1 | exports.build = require('./build'); |
10 | 1 | exports.fileUtil = require('./fileUtil'); |
11 | 1 | exports.plugin = require('./plugin').plugin; |
12 | 1 | exports.combine = require('./jsCombine').combine; |
13 | 1 | exports.config = require('./config'); |
14 | ||
15 | /** | |
16 | * | |
17 | * @name init | |
18 | * @function This function initializes a Lumbar instance | |
19 | * @param {string} lumbarFile The lumbarFile is the main | |
20 | * file. Its responsible to define all the platforms, | |
21 | * packages, modules, and templates for Lumbar to use. | |
22 | * @param {Object} options supports the following options: | |
23 | * packageConfigFile (string): name of the package config file. | |
24 | * outdir (string): path to directory of where to output the files. | |
25 | * minimize (boolean): Should we minimize the files? | |
26 | * @return {Object.<Function>} | |
27 | */ | |
28 | 1 | exports.init = function(lumbarFile, options) { |
29 | // Clone so we can mutate in the use API | |
30 | 26 | options = _.clone(options || {}); |
31 | 26 | options.plugins = _.clone(options.plugins || []); |
32 | ||
33 | 26 | function logError(err) { |
34 | 59 | if (err) { |
35 | 3 | event.emit('error', err); |
36 | } | |
37 | } | |
38 | ||
39 | 26 | var event = new EventEmitter(), |
40 | watch, | |
41 | watchContext; | |
42 | ||
43 | 26 | function watchOutputHandler(status) { |
44 | 104 | if (!watch) { |
45 | // We've been cleaned up but residuals may still exist, do nothing on this exec | |
46 | 15 | return; |
47 | } | |
48 | ||
49 | 89 | if (status.fileConfig.isPrimary) { |
50 | 35 | delete status.fileConfig; |
51 | 54 | } else if (status.fileConfig.isPrimary === false) { |
52 | // This config is directly linked to another meaning we don't want to watch on it as | |
53 | // it will be rebuilt. | |
54 | 12 | return; |
55 | } | |
56 | ||
57 | 77 | var originalContext = watchContext; |
58 | 77 | watch.moduleOutput(status, function() { |
59 | 36 | if (watchContext !== originalContext) { |
60 | // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch) | |
61 | 0 | return; |
62 | } | |
63 | ||
64 | 36 | stateMachine.buildPlatform(watchContext.clone(status), logError); |
65 | }); | |
66 | } | |
67 | ||
68 | 26 | return _.extend(event, { |
69 | use: function(plugin) { | |
70 | // Only has impact before exec | |
71 | 0 | options.plugins.push(plugin); |
72 | }, | |
73 | /** | |
74 | * | |
75 | * @name build | |
76 | * @function This function builds out the package(s). | |
77 | * @param {string} packageName the name of the package listed under | |
78 | * 'packages' from the lumbarFile passed in during the call to init(). | |
79 | * @param {Function} callback the node process Function | |
80 | */ | |
81 | build: function(packageName, modules, callback) { | |
82 | 11 | stateMachine.loadConfig(lumbarFile, event, options, function(err, context) { |
83 | 11 | if (err) { |
84 | 0 | if (!callback) { |
85 | 0 | throw err; |
86 | } | |
87 | 0 | return callback(err); |
88 | } | |
89 | ||
90 | 11 | stateMachine.buildPackages(context, packageName, modules, callback); |
91 | }); | |
92 | }, | |
93 | watch: function(packageName, modules, callback) { | |
94 | 19 | if (!fs.watch) { |
95 | 0 | throw new Error('Watch requires fs.watch, introduced in Node v0.6.0'); |
96 | } | |
97 | ||
98 | 19 | ChildPool.isBackground(true); |
99 | ||
100 | 19 | watch = new WatchManager(event); |
101 | 19 | watch.on('watch-change', function(info) { |
102 | 44 | event.emit('watch-change', info); |
103 | }); | |
104 | ||
105 | 19 | var self = this; |
106 | 19 | stateMachine.loadConfig(lumbarFile, event, options, function(err, context) { |
107 | 19 | if (err) { |
108 | 0 | logError(err); |
109 | } | |
110 | ||
111 | 19 | if (!callback) { |
112 | 19 | callback = modules; |
113 | 19 | modules = undefined; |
114 | } | |
115 | ||
116 | 19 | watchContext = context; |
117 | ||
118 | // Watch for changes in the config file | |
119 | 24 | var mixinPaths = _.filter(_.pluck(context.libraries.configs, 'path'), function(path) { return path; }); |
120 | 19 | watch.configFile(lumbarFile, mixinPaths, function() { |
121 | 4 | watchContext = undefined; |
122 | 4 | self.watch(packageName, callback); |
123 | }); | |
124 | ||
125 | // If we have errored do not exec everything as it could be in an indeterminate state | |
126 | 19 | if (err) { |
127 | 0 | return; |
128 | } | |
129 | ||
130 | // Watch the individual components | |
131 | 19 | event.removeListener('output', watchOutputHandler); |
132 | 19 | event.on('output', watchOutputHandler); |
133 | ||
134 | // Actual build everything | |
135 | 19 | var packages = packageName ? [packageName] : context.config.packageList(); |
136 | 19 | packages.forEach(function(name) { |
137 | 23 | stateMachine.buildPackages(context, name, modules, logError); |
138 | }); | |
139 | }); | |
140 | }, | |
141 | unwatch: function() { | |
142 | 15 | event.removeListener('output', watchOutputHandler); |
143 | 15 | if (watch) { |
144 | 15 | watch.removeAllListeners(); |
145 | 15 | watch.reset(); |
146 | 15 | watch = undefined; |
147 | 15 | watchContext = undefined; |
148 | } | |
149 | } | |
150 | }); | |
151 | }; | |
152 |
Line | Hits | Source |
---|---|---|
1 | // Copyright Joyent, Inc. and other Node contributors. | |
2 | // | |
3 | // Permission is hereby granted, free of charge, to any person obtaining a | |
4 | // copy of this software and associated documentation files (the | |
5 | // "Software"), to deal in the Software without restriction, including | |
6 | // without limitation the rights to use, copy, modify, merge, publish, | |
7 | // distribute, sublicense, and/or sell copies of the Software, and to permit | |
8 | // persons to whom the Software is furnished to do so, subject to the | |
9 | // following conditions: | |
10 | // | |
11 | // The above copyright notice and this permission notice shall be included | |
12 | // in all copies or substantial portions of the Software. | |
13 | // | |
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | ||
22 | 1 | var path = require('path'); |
23 | ||
24 | // path.relative(from, to) | |
25 | // posix version | |
26 | 1 | exports.relative = function(from, to) { |
27 | 28 | from = path.resolve(from).substr(1); |
28 | 28 | to = path.resolve(to).substr(1); |
29 | ||
30 | 28 | function trim(arr) { |
31 | 56 | var start = 0; |
32 | 56 | for (; start < arr.length; start++) { |
33 | 56 | if (arr[start] !== '') { |
34 | 56 | break; |
35 | } | |
36 | } | |
37 | ||
38 | 56 | var end = arr.length - 1; |
39 | 56 | for (; end >= 0; end--) { |
40 | 56 | if (arr[end] !== '') { |
41 | 56 | break; |
42 | } | |
43 | } | |
44 | ||
45 | 56 | if (start > end) { |
46 | 0 | return []; |
47 | } | |
48 | 56 | return arr.slice(start, end - start + 1); |
49 | } | |
50 | ||
51 | 28 | var fromParts = trim(from.split('/')); |
52 | 28 | var toParts = trim(to.split('/')); |
53 | ||
54 | 28 | var length = Math.min(fromParts.length, toParts.length); |
55 | 28 | var samePartsLength = length; |
56 | 28 | for (var i = 0; i < length; i++) { |
57 | 156 | if (fromParts[i] !== toParts[i]) { |
58 | 0 | samePartsLength = i; |
59 | 0 | break; |
60 | } | |
61 | } | |
62 | ||
63 | 28 | var outputParts = []; |
64 | 28 | for (var i = samePartsLength; i < fromParts.length; i++) { |
65 | 2 | outputParts.push('..'); |
66 | } | |
67 | ||
68 | 28 | outputParts = outputParts.concat(toParts.slice(samePartsLength)); |
69 | ||
70 | 28 | return outputParts.join('/'); |
71 | }; | |
72 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | path = require('path'); | |
3 | 1 | const corePlugins = [ |
4 | 'mixin', 'amd', | |
5 | 'styles-output', 'scripts-output', 'static-output', | |
6 | 'scope', 'router', 'template', 'inline-styles', | |
7 | 'coffee-script', 'stylus', 'handlebars', | |
8 | 'module-map', 'package-config', 'stylus-config', | |
9 | 'update-externals', | |
10 | 'inline-styles-resources', 'styles', 'scripts', 'static' | |
11 | ]; | |
12 | 1 | var fileUtils = require("/Users/kpdecker/dev/walmart/lumbar/lib/./fileUtil"); |
13 | ||
14 | 1 | var globalPlugins = {}; |
15 | ||
16 | 1 | exports.plugin = function(name, plugin) { |
17 | 21 | globalPlugins[name] = plugin; |
18 | 21 | plugin.id = name; |
19 | }; | |
20 | ||
21 | 1 | exports.plugin('amd', require('./plugins/amd')); |
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 | 138 | var plugins; |
45 | 138 | var modes; // all registered modes |
46 | 138 | var pluginModes; // map of modes and plugins scoped to the mode |
47 | 138 | var modeAll; // plugins that are scoped to all modes |
48 | ||
49 | 138 | function runPlugins(context, methodName, complete, failOver, noMode) { |
50 | 3076 | var len = 0, |
51 | pluginMode = pluginModes[context.mode] || []; | |
52 | ||
53 | 3076 | return (function next(complete) { |
54 | /*jshint boss:true */ | |
55 | 9143 | var plugin; |
56 | 9143 | while (plugin = plugins[len++]) { |
57 | // if plugin shouldn't work with current mode, go to next | |
58 | 59937 | if (!noMode |
59 | && (!context.mode || pluginMode.indexOf(plugin) < 0) | |
60 | && modeAll.indexOf(plugin) < 0) { | |
61 | 24977 | continue; |
62 | } | |
63 | ||
64 | 34960 | var method = plugin[methodName]; |
65 | 34960 | if (method) { |
66 | 6769 | if (complete) { |
67 | 6627 | process.nextTick(function() { |
68 | 6627 | method.call(plugin, context, next, complete); |
69 | }); | |
70 | 6627 | return; |
71 | } else { | |
72 | 142 | return method.call(plugin, context, next, complete); |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | // We're done, send data back | |
78 | 2374 | if (complete) { |
79 | // async | |
80 | // Clear out our stack under async mode to try to keep the stack somewhat sane. | |
81 | 2184 | process.nextTick(function() { |
82 | 2184 | complete(undefined, failOver && failOver()); |
83 | }); | |
84 | } else { | |
85 | // sync | |
86 | 190 | return failOver && failOver(); |
87 | } | |
88 | })(complete); | |
89 | } | |
90 | ||
91 | 138 | function registerPlugin(plugin) { |
92 | 2713 | var _plugin = globalPlugins[plugin] || plugin; |
93 | ||
94 | 2713 | var mode = _plugin.mode; |
95 | 2713 | if (mode) { |
96 | 2443 | if (_.isString(mode)) { |
97 | 1896 | mode = [mode]; |
98 | } | |
99 | 2443 | _.each(mode, function(_mode) { |
100 | 2990 | if (mode === 'all') { |
101 | // allow plugins to contribute new modes and participate in all modes | |
102 | 0 | modeAll.push(_plugin); |
103 | } else { | |
104 | 2990 | if (modes.indexOf(_mode) < 0) { |
105 | 412 | modes.push(_mode); |
106 | 412 | pluginModes[_mode] = []; |
107 | } | |
108 | 2990 | pluginModes[_mode].push(_plugin); |
109 | } | |
110 | }); | |
111 | } else { | |
112 | 270 | modeAll.push(_plugin); |
113 | } | |
114 | 2713 | plugins.push(_plugin); |
115 | 2713 | plugins.sort(function(a, b) { |
116 | 40953 | return (a.priority || 50) - (b.priority || 50); |
117 | }); | |
118 | } | |
119 | ||
120 | 138 | return { |
121 | get: function(name) { | |
122 | // Find the plugin with this id, if one exists | |
123 | 72 | var plugin = plugins.reduce(function(plugin, left) { |
124 | 1368 | 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 | 72 | if (plugin.id === name) { |
129 | 70 | 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 | 138 | plugins = []; |
151 | 138 | modes = []; // all registered modes |
152 | 138 | pluginModes = {}; // map of modes and plugins scoped to the mode |
153 | 138 | modeAll = []; // plugins that are scoped to all modes |
154 | ||
155 | // load the core plugins | |
156 | 138 | if (!options.ignoreCorePlugins) { |
157 | 135 | corePlugins.forEach(registerPlugin); |
158 | } | |
159 | ||
160 | 138 | var self = this; |
161 | 138 | function plugin(plugins) { |
162 | 276 | if (plugins) { |
163 | 35 | plugins.forEach(self.use, self); |
164 | } | |
165 | } | |
166 | ||
167 | // load command line plugins | |
168 | 138 | plugin(options.plugins); |
169 | ||
170 | // load lumbar.json plugins | |
171 | 138 | plugin(config.attributes.plugins); |
172 | }, | |
173 | ||
174 | loadMixin: function(context, complete) { | |
175 | 83 | runPlugins(context, 'loadMixin', complete, undefined, true); |
176 | }, | |
177 | loadConfig: function(context, complete) { | |
178 | 137 | runPlugins(context, 'loadConfig', complete, undefined, true); |
179 | }, | |
180 | outputConfigs: function(context, complete) { | |
181 | 243 | runPlugins(context, 'outputConfigs', complete, function() { |
182 | // Default to a one to one mapping for a given {platform, package, module, mode} combo | |
183 | 241 | return [ {} ]; |
184 | }); | |
185 | }, | |
186 | modeComplete: function(context, complete) { | |
187 | 188 | runPlugins(context, 'modeComplete', complete); |
188 | }, | |
189 | fileName: function(context, complete) { | |
190 | 255 | runPlugins(context, 'fileName', complete); |
191 | }, | |
192 | ||
193 | fileFilter: function(context) { | |
194 | 332 | return runPlugins(context, 'fileFilter'); |
195 | }, | |
196 | moduleResources: function(context, complete) { | |
197 | 448 | runPlugins(context, 'moduleResources', complete, function() { |
198 | 245 | var module = context.module; |
199 | 245 | return (module[context.mode] || []).slice(); |
200 | }); | |
201 | }, | |
202 | resourceList: function(context, complete) { | |
203 | 1020 | runPlugins(context, 'resourceList', complete, function() { return [context.resource]; }); |
204 | }, | |
205 | ||
206 | file: function(context, complete) { | |
207 | 146 | runPlugins(context, 'file', complete); |
208 | }, | |
209 | module: function(context, complete) { | |
210 | 278 | runPlugins(context, 'module', complete); |
211 | }, | |
212 | resource: function(context, complete) { | |
213 | 815 | runPlugins(context, 'resource', complete, function() { return context.resource; }); |
214 | }, | |
215 | modes: function() { | |
216 | 48 | return modes; |
217 | } | |
218 | }; | |
219 | }; | |
220 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | build = require('../build'), | |
4 | defineParser = require('../util/define-parser'), | |
5 | fu = require('../fileUtil'), | |
6 | path = require('path'), | |
7 | join = path.join, | |
8 | resources = require('../util/resources'), | |
9 | templateUtil = require('../templateUtil'); | |
10 | ||
11 | 1 | module.exports = { |
12 | loaders: {}, | |
13 | generator: generator, | |
14 | registerOutput: function(path, context) { | |
15 | 20 | var appId = context.platformCache.amdAppModules[path], |
16 | fileId = context.fileCache.amdFileModules[path], | |
17 | ret = 'lwmd[' + fileId + '] = '; | |
18 | ||
19 | 20 | if (!_.isNumber(fileId)) { |
20 | 0 | throw new Error('Missing module definition for ' + path); |
21 | } | |
22 | ||
23 | 20 | if (appId) { |
24 | 2 | ret = 'wmd[' + appId + '] = ' + ret; |
25 | } | |
26 | ||
27 | 20 | return generator(ret); |
28 | }, | |
29 | ||
30 | priority: 2, // After mixins | |
31 | ||
32 | resourceList: function(context, next, complete) { | |
33 | 555 | if (context.config.attributes.amd |
34 | && build.filterResource(context.resource, context)) { | |
35 | 46 | next(function(err, resources) { |
36 | 46 | if (err) { |
37 | 1 | return complete(err); |
38 | } | |
39 | ||
40 | 45 | if (context.config.isAppModule(context.module) && !context.platformCache.amdAppModules) { |
41 | 1 | context.platformCache.amdAppModules = {}; |
42 | } | |
43 | 45 | if (!context.platformCache.amdModuleCache) { |
44 | 40 | context.platformCache.amdModuleCache = {}; |
45 | } | |
46 | 45 | if (!context.fileCache.amdFileModules) { |
47 | 1 | context.fileCache.amdFileModules = {}; |
48 | } | |
49 | ||
50 | 45 | async.map( |
51 | resources, | |
52 | function(resource, callback) { | |
53 | 45 | parseFile(resource, undefined, context, true, callback); |
54 | }, | |
55 | function(err, data) { | |
56 | 45 | if (data) { |
57 | 45 | data = _.compact(_.flatten(data)); |
58 | } | |
59 | 45 | complete(err, data); |
60 | }); | |
61 | }); | |
62 | } else { | |
63 | 509 | next(complete); |
64 | } | |
65 | } | |
66 | }; | |
67 | ||
68 | 1 | module.exports.loaders.view = { |
69 | resource: function(name) { | |
70 | 54 | return 'js/views/' + name + '.js'; |
71 | }, | |
72 | amdName: function(path) { | |
73 | 88 | if (/js\/views\/(.*)\.js$/.exec(path)) { |
74 | 1 | return 'view!' + RegExp.$1; |
75 | } | |
76 | }, | |
77 | ||
78 | canOutput: function(data, resource, context) { | |
79 | 65 | return data.view; |
80 | }, | |
81 | output: function(defined, isAppModule, dependencies, context) { | |
82 | 1 | var viewName = templateUtil.escapeJsString(defined.name.replace(/^.*!/, '')), |
83 | fileId = context.fileCache.amdFileModules[defined.resourceName]; | |
84 | ||
85 | 1 | return [ |
86 | module.exports.registerOutput(defined.resourceName, context), | |
87 | generator('Thorax.Views["' + viewName + '"] = ('), | |
88 | generator(defined.source), | |
89 | generator(')(' + dependencies + ');\n'), | |
90 | ||
91 | generator('lwmd[' + fileId + '].prototype.name = "' + viewName + '";\n') | |
92 | ]; | |
93 | } | |
94 | }; | |
95 | ||
96 | 1 | module.exports.loaders.router = { |
97 | resource: function(name) { | |
98 | 0 | return 'js/routers/' + name + '.js'; |
99 | }, | |
100 | amdName: function(path) { | |
101 | 87 | if (/js\/routers\/(.*)\.js$/.exec(path)) { |
102 | 0 | return 'router!' + RegExp.$1; |
103 | } | |
104 | }, | |
105 | ||
106 | diffDefine: function(oldConfig, newConfig, context) { | |
107 | // Look only at the routes | |
108 | 6 | oldConfig = _.keys((oldConfig && oldConfig.routes) || {}); |
109 | 6 | newConfig = _.keys((newConfig && newConfig.routes) || {}); |
110 | ||
111 | 6 | if (!_.isEqual(oldConfig, newConfig)) { |
112 | 5 | context.event.emit('module-map'); |
113 | } | |
114 | }, | |
115 | ||
116 | canOutput: function(data, resource, context) { | |
117 | 26 | return data.router; |
118 | }, | |
119 | output: function(defined, isAppModule, dependencies, context) { | |
120 | 5 | var viewName = templateUtil.escapeJsString(defined.name.replace(/^.*!/, '')), |
121 | fileId = context.fileCache.amdFileModules[defined.resourceName]; | |
122 | ||
123 | 5 | return [ |
124 | generator('module.routes = '), | |
125 | generator(JSON.stringify(defined.routes)), | |
126 | generator(';\n'), | |
127 | module.exports.registerOutput(defined.resourceName, context), | |
128 | generator('Phoenix.Router.create(module, ('), | |
129 | generator(defined.source), | |
130 | generator(')(' + dependencies + '));\n') | |
131 | ]; | |
132 | } | |
133 | }; | |
134 | ||
135 | // Left out of the loaders object so we do not have to worry about enumaration order | |
136 | 1 | module.exports.defaultLoader = { |
137 | resource: function(name, resource, context) { | |
138 | 44 | if (name.indexOf('.') === -1) { |
139 | 44 | return 'js/' + name + '.js'; |
140 | } | |
141 | 0 | return resource; |
142 | }, | |
143 | amdName: function(path) { | |
144 | 85 | if (/js\/(.*)\.js$/.exec(path)) { |
145 | 40 | return RegExp.$1; |
146 | } else { | |
147 | 45 | return path; |
148 | } | |
149 | }, | |
150 | ||
151 | output: function(defined, isAppModule, dependencies, context) { | |
152 | 13 | if (!defined.amd) { |
153 | 1 | return generator(defined.source); |
154 | } else { | |
155 | 12 | return [ |
156 | module.exports.registerOutput(defined.resourceName, context), | |
157 | generator('(' + defined.source), | |
158 | generator(')(' + dependencies + ');\n') | |
159 | ]; | |
160 | } | |
161 | }, | |
162 | lookup: function(name, path, isAppModule, context) { | |
163 | // WARN : We are assuming that the dependency has been looked up at some point | |
164 | 50 | var appId = context.platformCache.amdAppModules[path], |
165 | fileId = context.fileCache.amdFileModules[path], | |
166 | lookup = 'lwmd[', | |
167 | id = fileId; | |
168 | ||
169 | 50 | if (appId) { |
170 | 6 | lookup = 'wmd['; |
171 | 6 | id = appId; |
172 | } | |
173 | 50 | if (id) { |
174 | 42 | return lookup + id + ']'; |
175 | } | |
176 | } | |
177 | }; | |
178 | ||
179 | 1 | function parseFile(resource, library, context, isRoot, complete) { |
180 | /*jshint eqnull: true */ | |
181 | 101 | var cache; |
182 | ||
183 | 101 | var resourceName = resources.source(resource); |
184 | 101 | if (!resourceName || !_.isString(resourceName) || resource.amd === false) { |
185 | 1 | return complete(undefined, resource); |
186 | } | |
187 | ||
188 | 100 | var loaderInfo = loaderFromName(resourceName, context), |
189 | loader = loaderInfo.loader, | |
190 | amdName = loaderInfo.amdName; | |
191 | ||
192 | 100 | library = loaderInfo.library || library || resource.library; |
193 | ||
194 | 100 | if (!isRoot && loader && loader.resource) { |
195 | 56 | resource = loader.resource(loaderInfo.name, resource, context); |
196 | ||
197 | // Map the returned resource name to the library | |
198 | 56 | resourceName = join((library && library.root) || '', resources.source(resource)); |
199 | } | |
200 | ||
201 | // Remap the amdName for the current library | |
202 | 100 | if (library && amdName.indexOf(':') < 0) { |
203 | 16 | amdName = amdName.replace(/^(.*!)?(.*)$/, function(match, loader, name) { return (loader || '') + library.name + ':' + name; }); |
204 | } | |
205 | ||
206 | 100 | var isAppModule = context.config.isAppModule(context.module), |
207 | isTopModule = !isAppModule && context.config.isTopLevel(context.module), | |
208 | inApp = context.platformCache.amdAppModules && (context.platformCache.amdAppModules[resourceName] != null), | |
209 | inFile = context.fileCache.amdFileModules[resourceName] != null, | |
210 | appId = isAppModule ? _.keys(context.platformCache.amdAppModules).length + 1 : 0, | |
211 | fileId = _.keys(context.fileCache.amdFileModules).length + 1; | |
212 | ||
213 | 100 | if (!isRoot && ((!isTopModule && inApp) || inFile)) { |
214 | 30 | return complete(); |
215 | } | |
216 | ||
217 | 70 | if (!resource.global && /\bjs\/.*\.js$/.test(resourceName)) { |
218 | // Handle any overrides as necessary | |
219 | 66 | resource = resources.map(resource, library); |
220 | 66 | var fileName = resources.source(resource); |
221 | ||
222 | // Flag state now so we don't read multiple times due to IO wait in the readFile call | |
223 | 66 | if (isAppModule) { |
224 | 5 | context.platformCache.amdAppModules[resourceName] = true; |
225 | } | |
226 | 66 | context.fileCache.amdFileModules[resourceName] = true; |
227 | ||
228 | 66 | cache = fu.readFileArtifact(fileName, 'amd', function(err, data) { |
229 | 66 | if (err) { |
230 | 1 | return complete(err); |
231 | } | |
232 | 65 | var cache = data.artifact; |
233 | ||
234 | // If we have something in the cache, shortcircuit the parsing | |
235 | 65 | if (cache === undefined) { |
236 | 65 | try { |
237 | 65 | data = defineParser(data.data.toString(), {file: fileName}); |
238 | } catch (err) { | |
239 | 2 | context.event.emit('log', 'Failed to parse AMD file: ' + fileName + ' ' + err); |
240 | 2 | data = false; |
241 | } | |
242 | ||
243 | 65 | if (data) { |
244 | 63 | cache = { |
245 | defined: _.map(data, function(data) { | |
246 | 65 | var loader = _.find(module.exports.loaders, function(loader) { |
247 | 137 | return loader.canOutput && loader.canOutput(data, resource, context); |
248 | }) || module.exports.defaultLoader; | |
249 | ||
250 | 65 | var ret = _.omit(data, 'define'); |
251 | 65 | if (data.define) { |
252 | 61 | ret.amd = data.define; |
253 | 61 | ret.name = ret.name || amdName; |
254 | 61 | ret.resourceName = resourceName; |
255 | } | |
256 | 65 | ret.loader = loader; |
257 | 65 | return ret; |
258 | }), | |
259 | dependencies: _.compact(_.flatten(_.pluck(data, 'deps'))) | |
260 | }; | |
261 | ||
262 | // If we have no AMD content then leave this as is | |
263 | 128 | var defines = _.filter(cache.defined, function(define) { return define.amd; }); |
264 | 63 | if (!defines.length) { |
265 | 3 | cache = false; |
266 | 60 | } else if (defines.length > 1) { |
267 | 1 | return complete(new Error('Multiple modules defined in "' + fileName + '"')); |
268 | } else { | |
269 | 59 | context.event.emit('debug', 'Dependencies ' + cache.dependencies + ' found for ' + amdName); |
270 | } | |
271 | ||
272 | 62 | defines = defines[0]; |
273 | ||
274 | 62 | var oldConfig = context.platformCache.amdModuleCache[fileName]; |
275 | 62 | var loader = defines && defines.loader; |
276 | 62 | if (loader && loader.diffDefine) { |
277 | 5 | loader.diffDefine(oldConfig, defines, context); |
278 | } | |
279 | 62 | if (oldConfig && oldConfig.loader && oldConfig.loader.diffDefine && oldConfig.loader !== loader) { |
280 | 1 | oldConfig.loader.diffDefine(oldConfig, defines, context); |
281 | } | |
282 | 62 | context.platformCache.amdModuleCache[fileName] = defines; |
283 | } else { | |
284 | 2 | cache = false; |
285 | 2 | context.platformCache.amdModuleCache[fileName] = undefined; |
286 | } | |
287 | ||
288 | 64 | fu.setFileArtifact(fileName, 'amd', cache); |
289 | } | |
290 | 64 | if (isAppModule) { |
291 | 5 | context.platformCache.amdAppModules[resourceName] = cache && appId; |
292 | } | |
293 | 64 | context.fileCache.amdFileModules[resourceName] = cache && fileId; |
294 | ||
295 | 64 | async.map( |
296 | cache.dependencies || [], | |
297 | function(resource, callback) { | |
298 | 56 | parseFile(resource, library, context, false, callback); |
299 | }, | |
300 | function(err, data) { | |
301 | 64 | if (data) { |
302 | 64 | if (cache) { |
303 | // If we have AMD content then handle it | |
304 | 59 | _.each(cache.defined, function(defined) { |
305 | 60 | var dependencies = lookupDepenencies(defined.deps, isAppModule, context), |
306 | output = defined.loader.output || module.exports.defaultLoader.output; | |
307 | 60 | data.push(output(defined, isAppModule, dependencies, context)); |
308 | }); | |
309 | } else { | |
310 | // Otherwise pass through | |
311 | 5 | data.push(resource); |
312 | } | |
313 | ||
314 | // Reduce everything to a tidy list | |
315 | 64 | data = _.compact(_.flatten(data)); |
316 | } | |
317 | ||
318 | 64 | complete(err, data); |
319 | }); | |
320 | }); | |
321 | } else { | |
322 | 4 | if (library) { |
323 | 1 | resource = resources.map(resource, library); |
324 | } | |
325 | ||
326 | 4 | complete(undefined, resource); |
327 | } | |
328 | } | |
329 | ||
330 | 1 | function loaderFromName(name, context) { |
331 | 156 | var amdName = name, |
332 | loader = module.exports.defaultLoader, | |
333 | library; | |
334 | 156 | if (/^(.*)!(.*)$/.test(name)) { |
335 | 68 | amdName = name; |
336 | ||
337 | 68 | var loaderName = RegExp.$1; |
338 | 68 | loader = module.exports.loaders[loaderName] || loader, |
339 | name = RegExp.$2; | |
340 | } else { | |
341 | 88 | var remapedName = amdName; |
342 | 88 | _.find(module.exports.loaders, function(loader) { |
343 | 353 | return remapedName = (loader.amdName && loader.amdName(name)); |
344 | }); | |
345 | 88 | amdName = remapedName || module.exports.defaultLoader.amdName(amdName); |
346 | } | |
347 | ||
348 | 156 | if (/^(.*):(.*)$/.test(name)) { |
349 | 10 | library = context.libraries.getConfig(RegExp.$1); |
350 | 10 | name = RegExp.$2; |
351 | } | |
352 | ||
353 | 156 | return { |
354 | loader: loader, | |
355 | amdName: amdName, | |
356 | name: name, | |
357 | library: library | |
358 | }; | |
359 | } | |
360 | ||
361 | 1 | function lookupDepenencies(dependencies, isAppModule, context) { |
362 | 60 | return _.map(dependencies, function(dep) { |
363 | 56 | var loaderInfo = loaderFromName(dep, context); |
364 | ||
365 | 56 | dep = resources.source(loaderInfo.loader.resource(loaderInfo.name, undefined, context)); |
366 | 56 | if (loaderInfo.loader.lookup) { |
367 | 28 | return loaderInfo.loader.lookup(loaderInfo.name, dep, isAppModule, context) || 'undefined'; |
368 | } | |
369 | ||
370 | // Lookup javascript resources using normal means if the loader does not define any helpers | |
371 | 28 | if (/\.js$/.test(dep)) { |
372 | 28 | return module.exports.defaultLoader.lookup(loaderInfo.name, dep, isAppModule, context) || 'undefined'; |
373 | } | |
374 | ||
375 | 0 | return 'undefined'; |
376 | }).join(', '); | |
377 | } | |
378 | ||
379 | 1 | function generator(string) { |
380 | // TODO : Handle proper source mapping here | |
381 | 87 | var ret = function(context, callback) { callback(undefined, {data: string, generated: true, noSeparator: true}); }; |
382 | 87 | ret.stringValue = string; |
383 | 87 | ret.sourceFile = undefined; |
384 | 87 | ret.ignoreWarnings = true; |
385 | 87 | return ret; |
386 | } | |
387 |
Line | Hits | Source |
---|---|---|
1 | 1 | var CoffeeScript = require('coffee-script'), |
2 | path = require('path'), | |
3 | fu = require('../fileUtil'), | |
4 | _ = require('underscore'); | |
5 | ||
6 | 1 | module.exports = { |
7 | mode: 'scripts', | |
8 | priority: 50, | |
9 | resource: function(context, next, complete) { | |
10 | 269 | var resource = context.resource; |
11 | 269 | if (/\.coffee$/.test(resource.src)) { |
12 | ||
13 | 2 | next(function(err, resource) { |
14 | 2 | function generator(context, callback) { |
15 | // Load the source data | |
16 | 2 | context.loadResource(resource, function(err, file) { |
17 | 2 | if (err) { |
18 | 1 | return callback(err); |
19 | } | |
20 | ||
21 | // Update the content | |
22 | 1 | callback(err, { |
23 | data: CoffeeScript.compile(file.content.toString()), | |
24 | inputs: file.inputs | |
25 | }); | |
26 | }); | |
27 | } | |
28 | ||
29 | // Include any attributes that may have been defined on the base entry | |
30 | 2 | if (!_.isString(resource)) { |
31 | 2 | _.extend(generator, resource); |
32 | } | |
33 | 2 | complete(undefined, generator); |
34 | }); | |
35 | } else { | |
36 | 267 | next(complete); |
37 | } | |
38 | } | |
39 | }; | |
40 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Template Plugin : Includes handlebars templates associated with a given file | |
3 | * when said file is imported. | |
4 | * | |
5 | * Config: | |
6 | * root: | |
7 | * templates: | |
8 | * template: Defines the template that is used to output the template in the module. See consts below. | |
9 | * precompile: | |
10 | * Flag/hash that enable precompilation. Truthy will enable precompilation. A hash with | |
11 | * the key name "template" will override the rendering template. (See the template value above.) | |
12 | * cache: Name of the javascript object that templates will be assigned to. | |
13 | * Defaults to `$AppModule.templates` if an app module exists, otherwise `templates` | |
14 | * | |
15 | * Mixins: | |
16 | * The template plugin will mixin any special values directly, giving priority to the local version. | |
17 | * | |
18 | */ | |
19 | 1 | var _ = require('underscore'), |
20 | amd = require('./amd'), | |
21 | handlebars = require('handlebars'), | |
22 | path = require('path'), | |
23 | resources = require('../util/resources'), | |
24 | templateUtil = require('../templateUtil'); | |
25 | ||
26 | 1 | handlebars.registerHelper('without-extension', function(str) { |
27 | 1 | return str.replace(/\.[a-zA-Z0-9]+$/, ''); |
28 | }); | |
29 | ||
30 | ||
31 | 1 | const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n"; |
32 | ||
33 | 1 | function ensureTemplateTemplates(context, complete) { |
34 | 40 | if (!context.configCache.templateTemplate) { |
35 | 14 | var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE; |
36 | 14 | context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) { |
37 | 14 | if (err) { |
38 | 1 | complete(err); |
39 | } else { | |
40 | 13 | context.configCache.templateTemplate = compiled; |
41 | 13 | complete(); |
42 | } | |
43 | }); | |
44 | } else { | |
45 | 26 | complete(); |
46 | } | |
47 | } | |
48 | ||
49 | 1 | function loadTemplate(src, resource, context, callback) { |
50 | 40 | ensureTemplateTemplates(context, function(err) { |
51 | 40 | if (err) { |
52 | 1 | return callback(err); |
53 | } | |
54 | 39 | var artifactType = 'template' + context.fileConfig.server; |
55 | 39 | context.fileUtil.readFileArtifact(src, artifactType, function(err, cache) { |
56 | 39 | if (err) { |
57 | 0 | callback(new Error('Failed to load template "' + src + '"\n\t' + err)); |
58 | 0 | return; |
59 | } | |
60 | ||
61 | 39 | var artifact = cache.artifact || {}, |
62 | data = artifact.data || cache.data.toString(), | |
63 | attr = context.config.attributes, | |
64 | templates = attr.templates || {}, | |
65 | appModule = context.config.scopedAppModuleName(context.module), | |
66 | templateCache = (attr.templates && attr.templates.cache) | |
67 | || attr.templateCache | |
68 | || ((appModule ? appModule + '.' : '') + 'templates'), | |
69 | template = context.configCache.templateTemplate; | |
70 | ||
71 | // Figure out what this file is called. This could vary due to prefixing and overriding | |
72 | 39 | var name = context.libraries.mapPathToLibrary(src, resource.library); |
73 | 39 | if (templates.root && name.indexOf(templates.root) === 0) { |
74 | 4 | name = name.substring(templates.root.length); |
75 | } | |
76 | 39 | name = templateUtil.escapeJsString(name); |
77 | ||
78 | // We have the template data, now convert it into the proper format | |
79 | 39 | if (!cache.artifact) { |
80 | 18 | if (templates.precompile) { |
81 | 2 | var options = context.fileCache.precompileTemplates; |
82 | 2 | if (!options) { |
83 | 2 | context.fileCache.precompileTemplates = options = _.clone(templates.precompile); |
84 | 2 | if (templates.knownHelpers || options.knownHelpers) { |
85 | 0 | var helpersList = _.union( |
86 | options.knownHelpers || templates.knownHelpers, | |
87 | context.fileCache.knownHelpers); | |
88 | ||
89 | 0 | options.knownHelpers = helpersList.reduce( |
90 | function(value, helper) { | |
91 | 0 | value[helper] = true; |
92 | 0 | return value; |
93 | }, {}); | |
94 | } | |
95 | 2 | if (context.fileConfig.server && templates.server) { |
96 | 1 | _.extend(options, templates.server); |
97 | } | |
98 | } | |
99 | 2 | try { |
100 | 2 | data = handlebars.precompile(data, options); |
101 | } catch (err) { | |
102 | 0 | return callback(new Error('Template "' + name + '": ' + err.message)); |
103 | } | |
104 | } else { | |
105 | 16 | data = "'" + templateUtil.escapeJsString(data) + "'"; |
106 | } | |
107 | 18 | context.fileUtil.setFileArtifact(src, artifactType, {data: data, template: template}); |
108 | } | |
109 | ||
110 | 39 | callback( |
111 | undefined, | |
112 | template({ | |
113 | name: name, | |
114 | handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile', | |
115 | templateCache: templateCache, | |
116 | data: data | |
117 | }) | |
118 | ); | |
119 | }); | |
120 | }); | |
121 | } | |
122 | ||
123 | 1 | module.exports = { |
124 | mode: 'scripts', | |
125 | priority: 50, | |
126 | ||
127 | loadMixin: function(context, next, complete) { | |
128 | 83 | var mixinTemplates = context.loadedLibrary.templates; |
129 | 83 | if (mixinTemplates) { |
130 | 14 | var templates = context.libraries.originalConfig.templates || {}, |
131 | configTemplates = _.clone(context.config.attributes.templates || templates), | |
132 | assigned = false; | |
133 | ||
134 | 14 | ['template', 'precompile', 'cache', 'root'].forEach(function(key) { |
135 | 56 | if (_.has(mixinTemplates, key) && !_.has(templates, key)) { |
136 | 10 | configTemplates[key] = mixinTemplates[key]; |
137 | 10 | assigned = true; |
138 | } | |
139 | }); | |
140 | ||
141 | 14 | if (_.has(mixinTemplates, 'knownHelpers')) { |
142 | 1 | configTemplates.knownHelpers = (configTemplates.knownHelpers || []).concat(mixinTemplates.knownHelpers); |
143 | 1 | assigned = true; |
144 | } | |
145 | ||
146 | 14 | if (assigned) { |
147 | 7 | context.config.attributes.templates = configTemplates; |
148 | } | |
149 | } | |
150 | 83 | next(complete); |
151 | }, | |
152 | ||
153 | resource: function(context, next, complete) { | |
154 | 269 | var resource = context.resource; |
155 | ||
156 | 269 | if (/\.handlebars$/.test(resource.src) || resource.template) { |
157 | 31 | var loadedTemplates = context.fileCache.loadedTemplates; |
158 | 31 | if (!loadedTemplates) { |
159 | 27 | loadedTemplates = context.fileCache.loadedTemplates = {}; |
160 | } | |
161 | ||
162 | 31 | var generator = function(buildContext, callback) { |
163 | 28 | var output = [], |
164 | inputs = []; | |
165 | 28 | context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) { |
166 | 28 | if (err) { |
167 | 0 | callback(err); |
168 | 0 | return; |
169 | } | |
170 | ||
171 | 28 | function ignore(file) { |
172 | 121 | return file.dir || loadedTemplates[resources.source(file)]; |
173 | } | |
174 | 28 | function checkComplete() { |
175 | 67 | if (inputs.length === files.length) { |
176 | // Sorting is effectively sorting on the file name due to the name comment in the template | |
177 | 27 | callback(undefined, { |
178 | inputs: inputs, | |
179 | data: output.sort().join(''), | |
180 | name: resource.src, | |
181 | generated: true, | |
182 | noSeparator: true, | |
183 | ignoreWarnings: true | |
184 | }); | |
185 | 27 | return true; |
186 | } | |
187 | } | |
188 | ||
189 | 49 | inputs = _.map(files.filter(ignore), function(input) { return input.src || input; }); |
190 | 28 | if (checkComplete()) { |
191 | 1 | return; |
192 | } | |
193 | ||
194 | 27 | files.forEach(function(file) { |
195 | 60 | if (ignore(file)) { |
196 | 20 | return; |
197 | } | |
198 | ||
199 | 40 | var src = file.src || file; |
200 | 40 | loadedTemplates[src] = true; |
201 | 40 | loadTemplate(src, resource, context, function(err, data) { |
202 | 40 | if (err) { |
203 | 1 | return callback(err); |
204 | } | |
205 | ||
206 | 39 | output.push(data.data || data); |
207 | 39 | inputs.push(src); |
208 | 39 | checkComplete(); |
209 | }); | |
210 | }); | |
211 | }); | |
212 | }; | |
213 | 31 | generator.sourceFile = resource.src; |
214 | 31 | complete(undefined, generator); |
215 | } else { | |
216 | 238 | next(complete); |
217 | } | |
218 | } | |
219 | }; | |
220 | ||
221 | 1 | amd.loaders.hbs = { |
222 | resource: function(name, resource, context) { | |
223 | 4 | var attr = context.config.attributes, |
224 | templates = attr.templates || {}; | |
225 | ||
226 | 4 | return {src: path.join(templates.root || '', name + '.handlebars')}; |
227 | }, | |
228 | lookup: function(name) { | |
229 | 2 | return 'Handlebars.templates["' + templateUtil.escapeJsString(name) + '"]'; |
230 | } | |
231 | }; | |
232 | ||
233 | 1 | amd.loaders.helper = { |
234 | resource: function(name, resource, context) { | |
235 | 2 | return 'js/helpers/' + name + '.js'; |
236 | }, | |
237 | amdName: function(name) { | |
238 | 87 | if (/js\/helpers\/(.*)\.js$/.exec(name)) { |
239 | 2 | return 'helpers!' + RegExp.$1; |
240 | } | |
241 | }, | |
242 | ||
243 | canOutput: function(data, resource, context) { | |
244 | 21 | return data.helper; |
245 | }, | |
246 | output: function(defined, isAppModule, dependencies, context) { | |
247 | 2 | var helperName = templateUtil.escapeJsString(defined.name.replace(/^.*!/, '')), |
248 | fileId = context.fileCache.amdFileModules[defined.resourceName], | |
249 | knownHelpers; | |
250 | ||
251 | // Update known helpers list | |
252 | 2 | if (isAppModule) { |
253 | // Silently dropping on the floor if the knownHelpers feature is not enabled | |
254 | 1 | var attr = context.config.attributes, |
255 | templates = attr.templates || {}; | |
256 | 1 | knownHelpers = templates.knownHelpers || []; |
257 | } else { | |
258 | 1 | knownHelpers = context.fileCache.knownHelpers = context.fileCache.knownHelpers || []; |
259 | } | |
260 | 2 | knownHelpers.push(helperName); |
261 | ||
262 | 2 | return [ |
263 | amd.registerOutput(defined.resourceName, context), | |
264 | amd.generator('('), | |
265 | amd.generator(defined.source), | |
266 | amd.generator(')(' + dependencies + '));\n'), | |
267 | amd.generator('Handlebars.registerHelper("' + helperName + '", lwmd[' + fileId + ']);\n') | |
268 | ]; | |
269 | } | |
270 | }; | |
271 |
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 | 363 | if (inlineStyles.isInline(context) && context.mode === 'styles') { |
9 | // Prevent stylesheet output if in inline mode | |
10 | 3 | complete(undefined, []); |
11 | 360 | } else if (inlineStyles.isInline(context)) { |
12 | 6 | next(function(err, scripts) { |
13 | 6 | complete(undefined, scripts.concat(context.module.styles || [])); |
14 | }); | |
15 | } else { | |
16 | 354 | 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 | 1541 | 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 | 83 | var mixinStyles = context.loadedLibrary.styles; |
26 | 83 | if (mixinStyles) { |
27 | 20 | var styles = context.libraries.originalConfig.styles || {}, |
28 | configStyles = _.clone(context.config.attributes.styles || styles), | |
29 | assigned = false; | |
30 | ||
31 | 20 | ['inline', 'inlineLoader'].forEach(function(key) { |
32 | 40 | if ((key in mixinStyles) && !(key in styles)) { |
33 | 6 | configStyles[key] = mixinStyles[key]; |
34 | ||
35 | 6 | assigned = true; |
36 | } | |
37 | }); | |
38 | ||
39 | 20 | if (assigned) { |
40 | 5 | context.config.attributes.styles = configStyles; |
41 | } | |
42 | } | |
43 | 83 | next(complete); |
44 | }, | |
45 | ||
46 | outputConfigs: function(context, next, complete) { | |
47 | 194 | if (isInline(context) && context.mode === 'styles') { |
48 | // Prevent stylesheet output if in inline mode | |
49 | 2 | complete(undefined, []); |
50 | } else { | |
51 | 192 | next(complete); |
52 | } | |
53 | }, | |
54 | ||
55 | module: function(context, next, complete) { | |
56 | 196 | next(function(err) { |
57 | 196 | if (err) { |
58 | 0 | return complete(err); |
59 | } | |
60 | ||
61 | 196 | 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 | 196 | complete(); |
95 | }); | |
96 | } | |
97 | }; | |
98 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | lumbar = require('../lumbar'); | |
3 | ||
4 | 1 | function filterDuplicates(context) { |
5 | 202 | if (context.config.attributes.filterDuplicates === false) { |
6 | 2 | return context.moduleResources; |
7 | } | |
8 | ||
9 | 200 | var paths = {}; |
10 | 200 | return _.filter(context.moduleResources, function(resource) { |
11 | 388 | 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 | 386 | return true; |
19 | }); | |
20 | } | |
21 | ||
22 | 1 | function combineResources(context, outputData, callback) { |
23 | 172 | var resources = context.resources || []; |
24 | 172 | if (!resources.length) { |
25 | 52 | return callback(); |
26 | } | |
27 | ||
28 | 120 | context.outputFile(function(callback) { |
29 | 120 | 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 | 120 | data = data || {}; |
37 | 120 | _.extend(data, outputData); |
38 | ||
39 | 120 | if (!data.fileName) { |
40 | 9 | data.fileName = context.fileName; |
41 | } | |
42 | 120 | 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 | 120 | callback(err, data); |
53 | }); | |
54 | }, | |
55 | callback); | |
56 | } | |
57 | ||
58 | 1 | module.exports = { |
59 | priority: 1, | |
60 | ||
61 | modeComplete: function(context, next, complete) { | |
62 | 133 | next(function(err) { |
63 | 133 | if (err) { |
64 | 0 | return complete(err); |
65 | } | |
66 | ||
67 | 133 | 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 | 103 | complete(); |
77 | } | |
78 | }); | |
79 | }, | |
80 | module: function(context, next, complete) { | |
81 | 202 | next(function(err) { |
82 | 202 | if (err) { |
83 | 0 | return complete(err); |
84 | } | |
85 | ||
86 | 202 | if (!context.combined) { |
87 | 142 | context.resources = filterDuplicates(context); |
88 | 142 | context.moduleResources = undefined; |
89 | 142 | 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 | 134 | var modules = context.config.attributes.modules, |
8 | errored; | |
9 | 134 | _.each(context.libraries.configs, function(library) { |
10 | // Import any modules that are not overriden in the core file | |
11 | 78 | _.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 | 134 | _.each(modules, function(module, name) { |
30 | 160 | module.name = module.name || name; |
31 | 160 | var mixins; |
32 | 160 | try { |
33 | 160 | 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 | 158 | try { |
41 | 158 | ['scripts', 'styles', 'static'].forEach(function(field) { |
42 | 472 | var list = module[field]; |
43 | ||
44 | 472 | if (list) { |
45 | 126 | module[field] = context.libraries.mapFiles(list); |
46 | } | |
47 | }); | |
48 | ||
49 | 157 | _.each(mixins, function(mixin) { |
50 | 46 | var mixinConfig = mixin.mixinConfig, |
51 | library = mixin.library; | |
52 | ||
53 | // Direct copy for any fields that are not already defined on the object. | |
54 | 46 | _.defaults(module, library.attributes); |
55 | ||
56 | // Merge known array/object types | |
57 | 46 | ['scripts', 'styles', 'static', 'routes'].forEach(function(field) { |
58 | 184 | 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 | 134 | _.each(_.keys(modules), function(name) { |
69 | 160 | if (!modules[name]) { |
70 | 1 | delete modules[name]; |
71 | } | |
72 | }); | |
73 | ||
74 | 134 | if (!errored) { |
75 | 131 | next(complete); |
76 | } | |
77 | } | |
78 | }; | |
79 | ||
80 | 1 | function firstLocal(collection) { |
81 | 56 | for (var i = 0, len = collection.length; i < len; i++) { |
82 | 71 | if (!collection[i].global) { |
83 | 56 | return i; |
84 | } | |
85 | } | |
86 | 0 | return i; |
87 | } | |
88 | ||
89 | 1 | function mergeValues(module, field, library, mixinConfig, context) { |
90 | 184 | var value = module[field], |
91 | mixinValue = library.attributes[field]; | |
92 | ||
93 | 184 | if (!value) { |
94 | 131 | return; |
95 | } | |
96 | ||
97 | 53 | 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 | 37 | } else if (!_.isArray(value)) { |
105 | 5 | _.defaults(value, mixinValue); |
106 | 32 | } else if (mixinValue) { |
107 | 28 | mixinValue = context.libraries.mapFiles(mixinValue, library, mixinConfig); |
108 | ||
109 | 28 | var mixinFirstLocal = firstLocal(mixinValue), |
110 | moduleFirstLocal = firstLocal(value); | |
111 | ||
112 | 28 | if (mixinFirstLocal) { |
113 | 4 | value.unshift.apply(value, mixinValue.slice(0, mixinFirstLocal)); |
114 | } | |
115 | 28 | if (mixinFirstLocal < mixinValue.length) { |
116 | 28 | var locals = mixinValue.slice(mixinFirstLocal); |
117 | 28 | locals.unshift(mixinFirstLocal + moduleFirstLocal, 0); |
118 | 28 | 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 | 19 | if (!moduleMapTemplate) { |
12 | 1 | moduleMapTemplate = handlebars.compile(fs.readFileSync(__dirname + '/module-map.handlebars').toString()); |
13 | } | |
14 | 19 | 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 | 18 | 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 | 18 | map = (function orderObject(map) { |
26 | 102 | var ret = _.isArray(map) ? [] : {}; |
27 | 102 | _.keys(map).sort().forEach(function(key) { |
28 | 177 | var value = map[key]; |
29 | 177 | ret[key] = _.isObject(value) ? orderObject(value) : value; |
30 | }); | |
31 | 102 | return ret; |
32 | })(map); | |
33 | ||
34 | 18 | callback( |
35 | undefined, | |
36 | moduleMapTemplate({ | |
37 | moduleMapper: mapper, | |
38 | map: JSON.stringify(map) | |
39 | }) | |
40 | ); | |
41 | } | |
42 | ||
43 | 1 | function buildMap(context, callback) { |
44 | 36 | if (context.combined) { |
45 | 15 | moduleConfig(context, undefined, function(err, config, prefix) { |
46 | 15 | callback(err, { base: config }, prefix); |
47 | }); | |
48 | } else { | |
49 | 21 | var attr = context.config.attributes || {}, |
50 | app = attr.application || {}, | |
51 | modules = context.config.moduleList(context.package); | |
52 | ||
53 | 21 | var map = {modules: {}, routes: {}}, |
54 | commonPrefix; | |
55 | ||
56 | 21 | async.forEach(modules, function(module, callback) { |
57 | 28 | moduleConfig(context, module, function(err, config, prefix) { |
58 | 28 | if (err) { |
59 | 0 | return callback(err); |
60 | } | |
61 | ||
62 | 28 | if (app.module === module) { |
63 | 4 | map.base = config; |
64 | } else { | |
65 | 24 | map.modules[module] = config; |
66 | ||
67 | 24 | var routes = context.config.routeList(module); |
68 | 24 | _.each(routes, function(value, route) { |
69 | 17 | map.routes[route] = module; |
70 | }); | |
71 | } | |
72 | 28 | commonPrefix = findPrefix(prefix, commonPrefix); |
73 | ||
74 | 28 | callback(); |
75 | }); | |
76 | }, | |
77 | function(err) { | |
78 | 21 | callback(err, map, commonPrefix); |
79 | }); | |
80 | } | |
81 | } | |
82 | ||
83 | 1 | function stripPrefix(map, prefix) { |
84 | 18 | if (!prefix) { |
85 | 17 | 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 | 43 | var ret = {}, |
120 | commonPrefix, | |
121 | preload = module && context.config.module(module).preload, | |
122 | depends = module && context.config.module(module).depends; | |
123 | 43 | if (preload) { |
124 | 1 | ret.preload = preload; |
125 | } | |
126 | 43 | if (depends) { |
127 | 4 | ret.depends = depends; |
128 | } | |
129 | 43 | async.forEach([{key: 'js', mode: 'scripts'}, {key: 'css', mode: 'styles'}], function(obj, callback) { |
130 | 86 | fileList(context, obj.mode, module, function(err, list, prefix) { |
131 | 86 | ret[obj.key] = list; |
132 | 86 | commonPrefix = findPrefix(prefix, commonPrefix); |
133 | 86 | callback(err); |
134 | }); | |
135 | }, | |
136 | function(err) { | |
137 | 43 | 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 | 86 | var modules = !context.combined ? [ module ] : context.config.moduleList(context.package); |
143 | 86 | async.some(modules, function(module, callback) { |
144 | 116 | var resourceContext = context.clone(); |
145 | 116 | resourceContext.mode = mode; |
146 | 116 | resourceContext.module = context.config.module(module); |
147 | 116 | resourceContext.isModuleMap = true; |
148 | ||
149 | 116 | resourceContext.plugins.moduleResources(resourceContext, function(err, resources) { |
150 | 116 | callback((resources || []).length); |
151 | }); | |
152 | }, | |
153 | function(hasResource) { | |
154 | 86 | if (!hasResource) { |
155 | 11 | return callback(); |
156 | } | |
157 | ||
158 | // Output the config | |
159 | 75 | context.fileNamesForModule(mode, module, function(err, configs) { |
160 | 75 | if (err) { |
161 | 0 | return callback(err); |
162 | } | |
163 | ||
164 | 75 | var prefix; |
165 | 176 | configs = configs.filter(function(config) { return !config.server; }); |
166 | 101 | configs = configs.sort(function(a, b) { return a.pixelDensity - b.pixelDensity; }); |
167 | 75 | configs = configs.map(function(config, i) { |
168 | 101 | var path = config.fileName.path, |
169 | ret = path + '.' + config.fileName.extension; | |
170 | ||
171 | 101 | 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 | 101 | prefix = findPrefix(path, prefix); |
183 | ||
184 | 101 | return ret; |
185 | }); | |
186 | ||
187 | 75 | var ret; |
188 | 75 | if (configs.length === 1) { |
189 | 50 | ret = configs[0]; |
190 | 25 | } else if (configs.length) { |
191 | 25 | ret = configs; |
192 | } | |
193 | 75 | callback(undefined, ret, prefix); |
194 | }); | |
195 | }); | |
196 | } | |
197 | ||
198 | 1 | function findPrefix(path, prefix) { |
199 | /*jshint eqnull:true*/ | |
200 | 215 | if (path == null) { |
201 | 11 | return prefix; |
202 | } | |
203 | 204 | if (prefix == null) { |
204 | // Ensure that we get 'x' for strings of type 'x/' | |
205 | 139 | prefix = dirname(path + 'a') + '/'; |
206 | } | |
207 | 204 | for (var i = 0, len = prefix.length; i < len; i++) { |
208 | 139 | if (path.charAt(i) !== prefix.charAt(i)) { |
209 | 139 | return prefix.substring(0, i); |
210 | } | |
211 | } | |
212 | 65 | return prefix; |
213 | } | |
214 | ||
215 | 1 | module.exports = { |
216 | mode: 'scripts', | |
217 | priority: 50, | |
218 | ||
219 | buildMap: buildMap, | |
220 | ||
221 | resource: function(context, next, complete) { | |
222 | 292 | var config = context.config; |
223 | ||
224 | 292 | if (context.resource['module-map']) { |
225 | 23 | var buildModuleMap = function(context, callback) { |
226 | 18 | module.exports.buildMap(context, function(err, map, prefix) { |
227 | 18 | if (err) { |
228 | 0 | callback(err); |
229 | } else { | |
230 | 18 | var moduleMap = config.attributes.moduleMap || 'module.exports.moduleMap'; |
231 | 18 | stripPrefix(map, prefix); |
232 | 18 | loadModuleMap(map, moduleMap, function(err, data) { |
233 | 18 | callback(err, data && {data: data, inputs: [{event: 'module-map'}], generated: true, noSeparator: true, ignoreWarnings: true}); |
234 | }); | |
235 | } | |
236 | }); | |
237 | }; | |
238 | 23 | buildModuleMap.sourceFile = undefined; |
239 | 23 | complete(undefined, buildModuleMap); |
240 | } else { | |
241 | 269 | 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 | 217 | var resource = context.resource; |
33 | ||
34 | 217 | 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 | 200 | 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 | 200 | next(function(err, ret) { |
22 | 200 | if (err) { |
23 | 0 | return complete(err); |
24 | } | |
25 | ||
26 | // Generate the router if we have the info for it | |
27 | 200 | var module = context.module; |
28 | 200 | if (module.routes) { |
29 | 47 | ret.unshift({ routes: module.routes }); |
30 | } | |
31 | ||
32 | 200 | complete(undefined, ret); |
33 | }); | |
34 | }, | |
35 | resource: function(context, next, complete) { | |
36 | 238 | var resource = context.resource, |
37 | module = context.module.name; | |
38 | ||
39 | 238 | 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 | 217 | 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 | 423 | return (attr.scope && attr.scope.scope) || attr.scope; |
29 | } | |
30 | 1 | function toObj(obj) { |
31 | 95 | return _.isString(obj) ? {scope: obj} : obj; |
32 | } | |
33 | ||
34 | 1 | function generator(string) { |
35 | 174 | var ret = function(context, callback) { callback(undefined, {data: string, generated: true, noSeparator: true}); }; |
36 | 100 | ret.stringValue = string; |
37 | 100 | ret.sourceFile = undefined; |
38 | 100 | ret.ignoreWarnings = true; |
39 | 100 | return ret; |
40 | } | |
41 | ||
42 | 1 | var scopeTemplateDelimiter = /\{?\{\{yield\}\}\}?/; |
43 | ||
44 | 1 | function ensureModuleTemplates(context, complete) { |
45 | 49 | if (!context.configCache.moduleTemplate) { |
46 | 31 | var template = context.config.attributes.scope && context.config.attributes.scope.template; |
47 | 31 | if (!template) { |
48 | 19 | template = __dirname + '/scope-module.handlebars'; |
49 | } | |
50 | ||
51 | 31 | context.fileUtil.loadTemplate(template, scopeTemplateDelimiter, function(err, templates) { |
52 | 32 | if (err) { |
53 | 1 | complete(err); |
54 | } else { | |
55 | 31 | context.configCache.moduleTemplate = { |
56 | start: templates[0], | |
57 | end: templates[1] | |
58 | }; | |
59 | 31 | complete(); |
60 | } | |
61 | }); | |
62 | } else { | |
63 | 18 | complete(); |
64 | } | |
65 | } | |
66 | ||
67 | 1 | function wrapResources(resources, context) { |
68 | 48 | var cache = context.moduleCache; |
69 | 48 | if (!cache.scopeName) { |
70 | 48 | var app = context.config.attributes.application, |
71 | appName = app && app.name; | |
72 | ||
73 | 48 | if (context.config.isTopLevel(context.module)) { |
74 | 34 | cache.isTopNamespace = true; |
75 | 34 | cache.scopeName = context.module.topLevelName || appName || context.module.name; |
76 | } else { | |
77 | 14 | cache.scopeName = appName + "['" + context.module.name + "']"; |
78 | } | |
79 | 48 | cache.appName = appName; |
80 | } | |
81 | ||
82 | // Wrap the module content in a javascript module | |
83 | 48 | if (resources.length) { |
84 | 48 | 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 | 48 | 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 | 48 | callSpec.unshift('this'); |
107 | 48 | if (cache.isTopNamespace) { |
108 | 34 | internalVars.unshift(cache.scopeName); |
109 | } else { | |
110 | 14 | internalScope += cache.scopeName + ' = exports;'; |
111 | } | |
112 | 88 | internalVars = _.map(internalVars, function(name) { return name + ' = exports'; }); |
113 | 48 | if (internalVars.length) { |
114 | 35 | internalScope += 'var ' + internalVars.join(', ') + ';'; |
115 | } | |
116 | ||
117 | 48 | var scopeDecl = ''; |
118 | 48 | if (context.moduleCache.isTopNamespace) { |
119 | // Insert the package declaration | |
120 | 34 | scopeDecl = 'var ' + context.moduleCache.scopeName + ';'; |
121 | } | |
122 | 48 | 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 | 48 | resources.unshift(generator(context.configCache.moduleTemplate.start(templateContext))); |
133 | 48 | resources.push(generator(context.configCache.moduleTemplate.end(templateContext))); |
134 | } | |
135 | 48 | return resources; |
136 | } | |
137 | ||
138 | 1 | module.exports = { |
139 | mode: 'scripts', | |
140 | priority: 50, | |
141 | ||
142 | loadMixin: function(context, next, complete) { | |
143 | 83 | var mixinScope = toObj(context.loadedLibrary.scope); |
144 | 83 | 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 | 83 | next(complete); |
170 | }, | |
171 | loadConfig: function(context, next, complete) { | |
172 | 131 | var modules = context.config.attributes.modules; |
173 | ||
174 | 131 | try { |
175 | 131 | _.each(modules, function(module) { |
176 | 154 | var mixins = context.libraries.moduleMixins(module); |
177 | ||
178 | 154 | _.each(mixins, function(mixin) { |
179 | 46 | context.libraries.mergeHash('aliases', module, mixin.library.attributes, module); |
180 | }); | |
181 | }); | |
182 | } catch (err) { | |
183 | 0 | return complete(err); |
184 | } | |
185 | ||
186 | 131 | next(complete); |
187 | }, | |
188 | ||
189 | resourceList: function(context, next, complete) { | |
190 | 317 | next(function(err, resources) { |
191 | 317 | if (err) { |
192 | 0 | return complete(err); |
193 | } | |
194 | ||
195 | 317 | 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 | 317 | complete(undefined, resources); |
202 | }); | |
203 | }, | |
204 | ||
205 | module: function(context, next, complete) { | |
206 | 106 | next(function(err) { |
207 | 106 | if (err) { |
208 | 0 | return complete(err); |
209 | } | |
210 | ||
211 | 106 | var resources = context.moduleResources, |
212 | scope = getScope(context.config.attributes); | |
213 | ||
214 | 106 | if (resources.length && scope !== 'none') { |
215 | 49 | ensureModuleTemplates(context, function(err) { |
216 | 50 | if (err) { |
217 | 1 | complete(err); |
218 | } else { | |
219 | // Split up globals and non-globals | |
220 | 49 | var globals = [], |
221 | children = [], | |
222 | moduleStart = []; | |
223 | 49 | for (var i = 0; i < resources.length; i++) { |
224 | 162 | var resource = resources[i]; |
225 | 162 | if (resource.moduleStart) { |
226 | 13 | moduleStart.push(resource); |
227 | 149 | } else if (!resource.global) { |
228 | 133 | 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 | 48 | children = moduleStart.concat(children); |
240 | 48 | globals.push.apply(globals, wrapResources(children, context, complete)); |
241 | ||
242 | 48 | context.moduleResources = globals; |
243 | 48 | 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 | 142 | return /\.(js|json)$/; |
7 | }, | |
8 | ||
9 | fileName: function(context, next, complete) { | |
10 | 111 | complete(undefined, {path: context.baseName, extension: 'js'}); |
11 | }, | |
12 | ||
13 | moduleResources: function(context, next, complete) { | |
14 | 200 | var module = context.module; |
15 | 200 | complete(undefined, (module.scripts || module.files || (module.slice && module) || []).slice()); |
16 | } | |
17 | }; | |
18 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'); |
2 | ||
3 | 1 | module.exports = { |
4 | mode: 'scripts', | |
5 | priority: 98, // Just below the core scripts plugin.... | |
6 | ||
7 | fileFilter: function(context, next) { | |
8 | 0 | return /\.(js|json)$/; |
9 | }, | |
10 | ||
11 | outputConfigs: function(context, next, complete) { | |
12 | 0 | next(function(err, files) { |
13 | 0 | if (err) { |
14 | 0 | return complete(err); |
15 | } | |
16 | ||
17 | // Permutation of other configs and ours | |
18 | 0 | var ret = []; |
19 | 0 | files.forEach(function(fileConfig) { |
20 | 0 | [true, false].forEach(function(server) { |
21 | 0 | var config = _.clone(fileConfig); |
22 | 0 | config.server = server; |
23 | 0 | ret.push(config); |
24 | }); | |
25 | }); | |
26 | 0 | complete(undefined, ret); |
27 | }); | |
28 | }, | |
29 | ||
30 | fileName: function(context, next, complete) { | |
31 | 0 | next(function(err, ret) { |
32 | 0 | if (ret && context.fileConfig.server) { |
33 | 0 | ret.path += '-server'; |
34 | } | |
35 | 0 | complete(err, ret); |
36 | }); | |
37 | }, | |
38 | ||
39 | moduleResources: function(context, next, complete) { | |
40 | 0 | var module = context.module; |
41 | ||
42 | 0 | var files = []; |
43 | 0 | (module.server || module.scripts).forEach(function(script) { |
44 | 0 | if (!_.has(script, 'server') || script.server === context.fileConfig.server) { |
45 | 0 | files.push(script); |
46 | } | |
47 | }); | |
48 | ||
49 | 0 | complete(undefined, files); |
50 | } | |
51 | }; | |
52 |
Line | Hits | Source |
---|---|---|
1 | 1 | var async = require('async'); |
2 | ||
3 | 1 | module.exports = { |
4 | mode: 'static', | |
5 | priority: 1, | |
6 | ||
7 | module: function(context, next, complete) { | |
8 | 77 | next(function(err) { |
9 | 77 | 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 | 83 | var mixinStyles = context.loadedLibrary.styles; |
10 | 83 | if (mixinStyles) { |
11 | 20 | var styles = context.libraries.originalConfig.styles || {}, |
12 | configStyles = _.clone(context.config.attributes.styles || styles), | |
13 | assigned = false; | |
14 | ||
15 | 20 | ['configObject'].forEach(function(key) { |
16 | 20 | if ((key in mixinStyles) && !(key in styles)) { |
17 | 1 | configStyles[key] = mixinStyles[key]; |
18 | ||
19 | 1 | assigned = true; |
20 | } | |
21 | }); | |
22 | ||
23 | 20 | if (context.libraries.mergeFiles('config', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
24 | 4 | assigned = true; |
25 | } | |
26 | ||
27 | 20 | if (assigned) { |
28 | 4 | context.config.attributes.styles = configStyles; |
29 | } | |
30 | } | |
31 | 83 | next(complete); |
32 | }, | |
33 | ||
34 | resource: function(context, next, complete) { | |
35 | 429 | 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 | 426 | next(complete); |
71 | } | |
72 | }, | |
73 | ||
74 | module: function(context, next, complete) { | |
75 | 202 | next(function() { |
76 | 202 | var styles = context.config.attributes.styles || {}, |
77 | config = styles.config || []; | |
78 | ||
79 | 202 | 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 | 202 | complete(); |
91 | }); | |
92 | } | |
93 | }; | |
94 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Stylus Plugin : Compile stylus files. | |
3 | * | |
4 | * Config: | |
5 | * root: | |
6 | * styles: | |
7 | * includes: Array of paths to add to stylus includes. | |
8 | * pixelDensity: Defines the pixel densities generated for each plaform. | |
9 | * urlSizeLimit: Maximum file size to inline. Passed to stylus-images plugin | |
10 | * copyFiles: Boolean specifying if non-inlined url references should be compied | |
11 | * To the build directly. Passed to stylus-images plugin. | |
12 | * styleRoot: Project path to resolve files from. | |
13 | * useNib: Truthy to include nib in the project build | |
14 | * | |
15 | * Mixins: | |
16 | * All fields may be mixed in. In the case of conflicts the local config wins for simple values and | |
17 | * for arrays the content will be merged in order. pixelDensity is mixed in at the platform definition | |
18 | * level. File references are converted to mixin space. | |
19 | * | |
20 | * styleRoot is used locally for file lookup when compiling the mixin content. | |
21 | */ | |
22 | 1 | var _ = require('underscore'), |
23 | ChildPool = require('child-pool'), | |
24 | inlineStyles = require('./inline-styles'), | |
25 | path = require('path'), | |
26 | normalize = path.normalize, | |
27 | fu = require('../fileUtil'); | |
28 | ||
29 | // Forward cache resets to any workers | |
30 | 1 | fu.on('cache:reset', function(path) { |
31 | 305 | worker.sendAll({type: 'cache:reset', path: path}); |
32 | }); | |
33 | ||
34 | 1 | var worker = new ChildPool(__dirname + '/stylus-worker'); |
35 | ||
36 | 1 | function generateSource(context, options, styleConfig) { |
37 | 37 | var includes = (styleConfig.includes || []).concat(options.files), |
38 | module = options.module; | |
39 | ||
40 | 37 | var nibLocation = includes.indexOf('nib'), |
41 | useNib; | |
42 | 37 | if (styleConfig.useNib) { |
43 | 31 | useNib = true; |
44 | 31 | includes.unshift('nib'); |
45 | 6 | } else if (nibLocation >= 0) { |
46 | // Special case nib handling to maintain backwards compatibility | |
47 | // WARN: This may be deprecated in future releases | |
48 | 0 | useNib = true; |
49 | 0 | includes.splice(nibLocation, 1); |
50 | } | |
51 | ||
52 | 37 | var declare = context.config.platformList().map(function(platform) { |
53 | 72 | return '$' + platform + ' = ' + (platform === context.platform); |
54 | }).join('\n') + '\n'; | |
55 | ||
56 | 37 | var mixins = [], |
57 | mixinLUT = {}; | |
58 | ||
59 | 37 | var source = declare + includes.map(function(include) { |
60 | 120 | var source = include.library; |
61 | 120 | var statement = '@import ("' + (include.originalSrc || include.src || include) + '")\n'; |
62 | 120 | if (source) { |
63 | 13 | var name = '', |
64 | root = (source.parent || source).root || '', | |
65 | stylusRoot = ((source.parent || source).styles || {}).styleRoot, | |
66 | library = (source.parent || source).name || ''; | |
67 | 13 | if (source.parent) { |
68 | 11 | name = source.name || ''; |
69 | } | |
70 | 13 | var mixinName = name + '_' + library; |
71 | ||
72 | 13 | if (!mixinLUT[mixinName]) { |
73 | 7 | var mixinDecl = context.libraries.findDecl(module.mixins, {name: name, library: library}), |
74 | overrides = mixinDecl && mixinDecl.overrides; | |
75 | 7 | var overrideLibrary; |
76 | 7 | if (source.overrideLibrary) { |
77 | 1 | overrideLibrary = { |
78 | root: normalize(source.overrideLibrary.root), | |
79 | stylusRoot: (source.overrideLibrary.styles || {}).styleRoot | |
80 | }; | |
81 | } | |
82 | ||
83 | 7 | mixins.push({ |
84 | root: normalize(root), | |
85 | stylusRoot: stylusRoot && normalize(stylusRoot), | |
86 | overrides: source.overrides, | |
87 | overrideLibrary: overrideLibrary | |
88 | }); | |
89 | 7 | mixinLUT[mixinName] = mixins.length-1; |
90 | } | |
91 | 13 | mixinName = mixinLUT[mixinName]; |
92 | ||
93 | 13 | return 'push-mixin("' + mixinName + '")\n' |
94 | + statement | |
95 | + 'pop-mixin()\n'; | |
96 | } else { | |
97 | 107 | return statement; |
98 | } | |
99 | }).join(''); | |
100 | ||
101 | 37 | return { |
102 | useNib: useNib, | |
103 | source: source, | |
104 | mixins: mixins | |
105 | }; | |
106 | } | |
107 | ||
108 | 1 | function compile(options, callback) { |
109 | 37 | var context = options.context, |
110 | ||
111 | styleConfig = context.config.attributes.styles || {}; | |
112 | ||
113 | 37 | var loadPrefix = context.config.loadPrefix(), |
114 | externalPrefix; | |
115 | 37 | if (loadPrefix) { |
116 | 9 | externalPrefix = loadPrefix + (context.buildPath.indexOf('/') >= 0 ? path.dirname(context.buildPath) + '/' : ''); |
117 | } | |
118 | ||
119 | 37 | var imageOptions = { |
120 | outdir: path.dirname(context.fileName), | |
121 | resolutions: context.modeCache.pixelDensity, | |
122 | limit: styleConfig.urlSizeLimit, | |
123 | copyFiles: styleConfig.copyFiles, | |
124 | externalPrefix: externalPrefix | |
125 | }; | |
126 | ||
127 | 37 | var source = generateSource(context, options, styleConfig); |
128 | ||
129 | 37 | context.fileUtil.ensureDirs(context.fileName, function(err) { |
130 | 37 | if (err) { |
131 | 0 | return callback(err); |
132 | } | |
133 | ||
134 | 37 | worker.send({ |
135 | plugins: options.plugins, | |
136 | ||
137 | useNib: source.useNib, | |
138 | imageOptions: imageOptions, | |
139 | ||
140 | filename: options.filename, | |
141 | minimize: context.options.minimize, | |
142 | ||
143 | source: source.source, | |
144 | mixins: source.mixins, | |
145 | ||
146 | lookupPath: context.fileUtil.lookupPath(), | |
147 | styleRoot: styleConfig.styleRoot && context.fileUtil.resolvePath(styleConfig.styleRoot) | |
148 | }, | |
149 | callback); | |
150 | }); | |
151 | } | |
152 | ||
153 | 1 | module.exports = { |
154 | // scripts mode is used also to support inline styles | |
155 | mode: ['styles', 'scripts'], | |
156 | priority: 50, | |
157 | ||
158 | loadMixin: function(context, next, complete) { | |
159 | 83 | var mixinStyles = context.loadedLibrary.styles; |
160 | 83 | if (mixinStyles) { |
161 | 20 | var styles = context.libraries.originalConfig.styles || {}, |
162 | configStyles = _.clone(context.config.attributes.styles || styles), | |
163 | assigned = false; | |
164 | ||
165 | 20 | ['urlSizeLimit', 'copyFiles', 'useNib'].forEach(function(key) { |
166 | 60 | if ((key in mixinStyles) && !(key in styles)) { |
167 | 8 | configStyles[key] = mixinStyles[key]; |
168 | ||
169 | 8 | assigned = true; |
170 | } | |
171 | }); | |
172 | ||
173 | 20 | if (context.libraries.mergeFiles('includes', styles, mixinStyles, configStyles, context.loadedLibrary)) { |
174 | 5 | assigned = true; |
175 | } | |
176 | ||
177 | 20 | if (context.libraries.mergeHash('pixelDensity', styles, mixinStyles, configStyles)) { |
178 | 3 | assigned = true; |
179 | } | |
180 | ||
181 | 20 | if (assigned) { |
182 | 9 | context.config.attributes.styles = configStyles; |
183 | } | |
184 | } | |
185 | 83 | next(complete); |
186 | }, | |
187 | ||
188 | outputConfigs: function(context, next, complete) { | |
189 | 195 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
190 | 94 | return next(complete); |
191 | } | |
192 | ||
193 | 101 | next(function(err, files) { |
194 | 101 | if (err) { |
195 | 0 | return complete(err); |
196 | } | |
197 | ||
198 | 101 | var ret = [], |
199 | styleConfig = context.config.attributes.styles || {}, | |
200 | pixelDensity = styleConfig.pixelDensity || {}; | |
201 | 101 | if (context.platform) { |
202 | 74 | pixelDensity = pixelDensity[context.platform] || pixelDensity; |
203 | } | |
204 | 101 | if (!_.isArray(pixelDensity)) { |
205 | 64 | pixelDensity = [ 1 ]; |
206 | } | |
207 | 101 | context.modeCache.pixelDensity = pixelDensity; |
208 | ||
209 | // Permutation of other configs and ours | |
210 | 101 | var primary = true; |
211 | 101 | files.forEach(function(fileConfig) { |
212 | 101 | pixelDensity.forEach(function(density) { |
213 | 139 | var config = _.clone(fileConfig); |
214 | 139 | config.pixelDensity = density; |
215 | 139 | config.isPrimary = primary; |
216 | 139 | primary = false; |
217 | 139 | ret.push(config); |
218 | }); | |
219 | }); | |
220 | 101 | complete(undefined, ret); |
221 | }); | |
222 | }, | |
223 | ||
224 | fileName: function(context, next, complete) { | |
225 | 233 | if (!inlineStyles.isInline(context) && context.mode !== 'styles') { |
226 | 105 | return next(complete); |
227 | } | |
228 | ||
229 | 128 | next(function(err, ret) { |
230 | 128 | if (ret && context.fileConfig.pixelDensity !== 1) { |
231 | 40 | ret.path += '@' + context.fileConfig.pixelDensity + 'x'; |
232 | } | |
233 | 128 | complete(err, ret); |
234 | }); | |
235 | }, | |
236 | ||
237 | module: function(moduleContext, next, complete) { | |
238 | 205 | next(function(err) { |
239 | /*jshint eqnull: true */ | |
240 | 205 | if (err) { |
241 | 0 | return complete(err); |
242 | } | |
243 | ||
244 | 205 | function mergeResources(start) { |
245 | 55 | var generator = function(context, callback) { |
246 | 55 | function response(data, density) { |
247 | 55 | if (data) { |
248 | 52 | return { |
249 | data: data.data[density || 1], | |
250 | inputs: data.inputs, | |
251 | noSeparator: true | |
252 | }; | |
253 | } | |
254 | } | |
255 | ||
256 | 55 | var filename = generator.filename; |
257 | ||
258 | // We only want to call stylus once which will generate the css for all of the | |
259 | // resolutions we support on this platform. This ugly bit of code make sure that | |
260 | // we properly handle all of that loading states that can come into play under these | |
261 | // circumstances while still adhering to the output models prescribed by lumbar. | |
262 | 55 | var queue = context.modeCache['stylus_' + filename]; |
263 | 55 | if (_.isArray(queue)) { |
264 | // We are currently executing | |
265 | 18 | queue.push({density: context.fileConfig.pixelDensity, callback: callback}); |
266 | 37 | } else if (_.isObject(queue)) { |
267 | // We already have data | |
268 | 0 | callback(undefined, response(queue, context.fileConfig.pixelDensity)); |
269 | } else { | |
270 | // We need to kick of a stylus build | |
271 | 37 | queue = context.modeCache['stylus_' + filename] = [ |
272 | {density: context.fileConfig.pixelDensity, callback: callback} | |
273 | ]; | |
274 | 37 | var options = { |
275 | filename: filename, | |
276 | files: generator.inputs, | |
277 | ||
278 | context: context, | |
279 | module: moduleContext.module, // To play nicely with combined mode | |
280 | plugins: generator.plugins | |
281 | }; | |
282 | 37 | compile(options, function(err, data) { |
283 | 37 | if (err) { |
284 | 3 | data = undefined; |
285 | } | |
286 | 37 | _.each(queue, function(callback) { |
287 | 55 | callback.callback(err, response(data, callback.density)); |
288 | }); | |
289 | 37 | context.modeCache['stylus_' + filename] = data; |
290 | }); | |
291 | } | |
292 | }; | |
293 | 55 | generator.inputs = resources.splice(start, rangeEnd - start + 1); |
294 | 150 | generator.filename = 'stylus_' + _.map(generator.inputs, function(file) { return file.originalSrc || file.src; }).join(';'); |
295 | 55 | generator.style = true; |
296 | 55 | generator.stylus = true; |
297 | 55 | generator.plugins = []; |
298 | ||
299 | 55 | resources.splice(start, 0, generator); |
300 | 55 | rangeEnd = undefined; |
301 | } | |
302 | ||
303 | // Merge all consequtive stylus files together | |
304 | 205 | var resources = moduleContext.moduleResources, |
305 | len = resources.length, | |
306 | rangeEnd; | |
307 | 205 | while (len--) { |
308 | 430 | var resource = resources[len]; |
309 | ||
310 | 430 | if (/\.styl$/.test(resource.src)) { |
311 | 95 | if (!rangeEnd) { |
312 | 55 | rangeEnd = len; |
313 | } | |
314 | 335 | } else if (rangeEnd) { |
315 | 3 | mergeResources(len + 1); |
316 | } | |
317 | } | |
318 | 205 | if (rangeEnd != null) { |
319 | 52 | mergeResources(0); |
320 | } | |
321 | 205 | complete(); |
322 | }); | |
323 | } | |
324 | }; | |
325 |
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 | 83 | var mixinTemplates = context.loadedLibrary.templates; |
32 | 83 | 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 | 83 | next(complete); |
46 | }, | |
47 | ||
48 | resourceList: function(context, next, complete) { | |
49 | 317 | var library = context.resource.library, |
50 | attr = (library && library.parent || library) || context.config.attributes; | |
51 | ||
52 | 317 | next(function(err, ret) { |
53 | 317 | if (err || !ret) { |
54 | 0 | return complete(err); |
55 | } | |
56 | ||
57 | 317 | function pushTemplates(templates) { |
58 | 284 | _.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 | 317 | 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 | 317 | if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) { |
79 | 241 | resource = resource.substring(mixinRoot.length); |
80 | } | |
81 | ||
82 | 317 | var deferComplete; |
83 | 317 | if (build.filterResource(context.resource, context)) { |
84 | 273 | pushTemplates(views[resource]); |
85 | ||
86 | 273 | 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 | 317 | if (!deferComplete) { |
124 | 306 | complete(undefined, ret); |
125 | } | |
126 | }); | |
127 | }, | |
128 | ||
129 | resource: function(context, next, complete) { | |
130 | 200 | var resource = context.resource; |
131 | ||
132 | 200 | 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 | 198 | 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 | pathRelative = require('../path-relative'); // Shim for 0.4 support | |
8 | ||
9 | 1 | module.exports = { |
10 | mode: 'static', | |
11 | priority: 50, | |
12 | ||
13 | updateHtmlReferences: function(context, content, callback) { | |
14 | 15 | function updateResources(mode, query, create) { |
15 | 30 | return function(callback) { |
16 | 29 | async.forEach($(query), function(el, callback) { |
17 | 10 | el = $(el); |
18 | 10 | var module = (el.attr('src') || el.attr('href')).replace(/^module:/, ''); |
19 | 10 | context.fileNamesForModule(mode, module, function(err, fileNames) { |
20 | 10 | if (err) { |
21 | 2 | return callback(err); |
22 | } | |
23 | ||
24 | // Generate replacement elements for each of the entries | |
25 | 8 | var content = fileNames.map(function(fileName) { |
26 | 8 | if (fileName.server) { |
27 | 0 | return ''; |
28 | } | |
29 | 8 | return create(loadDirName + basename(fileName.fileName.path) + '.' + fileName.fileName.extension); |
30 | }); | |
31 | ||
32 | // Output and kill the original | |
33 | 8 | el.replaceWith(content.join('')); |
34 | ||
35 | 8 | callback(); |
36 | }); | |
37 | }, | |
38 | callback); | |
39 | }; | |
40 | } | |
41 | 15 | var $ = cheerio.load(content), |
42 | loadDirName = ''; | |
43 | 15 | async.series([ |
44 | function(callback) { | |
45 | // Output the load prefix script we we have a module: script reference | |
46 | 15 | var firstScript = $('script[src^="module:"]'); |
47 | 15 | if (firstScript) { |
48 | 15 | context.plugins.get('module-map').buildMap(context, function(err, map, loadPrefix) { |
49 | 15 | if (err) { |
50 | 0 | return callback(err); |
51 | } | |
52 | ||
53 | 15 | var noFileComponent = !loadPrefix; |
54 | 15 | loadPrefix = context.platformPath + loadPrefix; |
55 | 15 | var dirname = path.dirname(loadPrefix + 'a'); // Force a component for the trailing '/' case |
56 | ||
57 | // Only remap load prefix if not defined by the user | |
58 | 15 | if (!(loadDirName = context.config.loadPrefix())) { |
59 | 14 | var resourcePath = path.dirname(context.fileName.substring(context.outdir.length + 1)); |
60 | 14 | loadPrefix = pathRelative.relative(resourcePath, loadPrefix); |
61 | 14 | loadDirName = pathRelative.relative(resourcePath, dirname); |
62 | ||
63 | 14 | if (loadDirName) { |
64 | 7 | loadDirName += '/'; |
65 | } | |
66 | 14 | if (loadPrefix && noFileComponent) { |
67 | 7 | loadPrefix += '/'; |
68 | } | |
69 | } else { | |
70 | // A load prefix was given, just combine this with the module map prefix | |
71 | 1 | loadPrefix = loadDirName + loadPrefix; |
72 | 1 | if (dirname !== '.') { |
73 | 1 | loadDirName += dirname + '/'; |
74 | } | |
75 | } | |
76 | ||
77 | 15 | var script = '<script type="text/javascript">var lumbarLoadPrefix = \'' + loadPrefix + '\';</script>'; |
78 | 15 | firstScript.before(script); |
79 | 15 | callback(); |
80 | }); | |
81 | } else { | |
82 | 0 | callback(); |
83 | } | |
84 | }, | |
85 | updateResources('scripts', 'script[src^="module:"]', function(href) { | |
86 | 7 | return '<script type="text/javascript" src="' + href + '"></script>'; |
87 | }), | |
88 | updateResources('styles', 'link[href^="module:"]', function(href) { | |
89 | 1 | return '<link rel="stylesheet" type="text/css" href="' + href + '"/>'; |
90 | }) | |
91 | ], | |
92 | function(err) { | |
93 | 15 | callback(err, $.html()); |
94 | }); | |
95 | }, | |
96 | ||
97 | resource: function(context, next, complete) { | |
98 | 28 | var resource = context.resource; |
99 | 28 | if (resource['update-externals'] || (/\.html?$/.test(resource.src) && resource['update-externals'] !== false)) { |
100 | 6 | next(function(err, resource) { |
101 | 6 | function generator(context, callback) { |
102 | // Load the source data | |
103 | 6 | context.loadResource(resource, function(err, file) { |
104 | 6 | if (err) { |
105 | 0 | return callback(err); |
106 | } | |
107 | ||
108 | // Update the content | |
109 | 6 | module.exports.updateHtmlReferences(context, file.content, function(err, data) { |
110 | 6 | callback(err, { |
111 | data: data, | |
112 | inputs: file.inputs | |
113 | }); | |
114 | }); | |
115 | }); | |
116 | } | |
117 | ||
118 | // Include any attributes that may have been defined on the base entry | |
119 | 6 | if (!_.isString(resource)) { |
120 | 6 | _.extend(generator, resource); |
121 | } | |
122 | 6 | complete(undefined, generator); |
123 | }); | |
124 | } else { | |
125 | 22 | next(complete); |
126 | } | |
127 | } | |
128 | }; | |
129 |
Line | Hits | Source |
---|---|---|
1 | 1 | var _ = require('underscore'), |
2 | async = require('async'), | |
3 | build = require('./build'), | |
4 | configLoader = require('./config'), | |
5 | Context = require('./context'), | |
6 | fs = require('fs'), | |
7 | fu = require('./fileUtil'), | |
8 | Libraries = require('./libraries'), | |
9 | plugin = require('./plugin'); | |
10 | ||
11 | 1 | exports.loadConfig = function(path, event, options, callback) { |
12 | 38 | try { |
13 | 38 | fu.resetCache(); |
14 | ||
15 | 38 | var config = _.isString(path) ? configLoader.load(path) : configLoader.create(path); |
16 | ||
17 | 38 | var plugins = plugin.create(options); |
18 | 38 | plugins.initialize(config); |
19 | ||
20 | 37 | config.outdir = options.outdir = options.outdir || config.attributes.output; |
21 | ||
22 | 37 | var libraries = new Libraries(options); |
23 | 37 | var context = new Context(options, config, plugins, libraries, event); |
24 | 37 | context.options = options; |
25 | 37 | context.configCache = {}; |
26 | ||
27 | 37 | libraries.initialize(context, function(err) { |
28 | 37 | if (err) { |
29 | 1 | return callback(err); |
30 | } | |
31 | ||
32 | 36 | plugins.loadConfig(context, function(err) { |
33 | 36 | if (err) { |
34 | 1 | return callback(err); |
35 | } | |
36 | ||
37 | 35 | event.emit('config', context.config); |
38 | 35 | if (options.verbose) { |
39 | 1 | event.emit('log', 'Finalized config ' + JSON.stringify(context.config.serialize(), undefined, 2)); |
40 | } | |
41 | ||
42 | // Ensure that we have the proper build output | |
43 | 35 | if (!config.outdir) { |
44 | 1 | return callback(new Error('Output must be defined on the command line or config file.')); |
45 | } | |
46 | 34 | context.outdir = config.outdir; |
47 | ||
48 | 34 | fu.ensureDirs(config.outdir + '/.', function() { |
49 | 34 | var stat = fs.statSync(config.outdir); |
50 | 34 | if (!stat.isDirectory()) { |
51 | 1 | callback(new Error('Output must be a directory')); |
52 | } else { | |
53 | 33 | callback(undefined, context); |
54 | } | |
55 | }); | |
56 | }); | |
57 | }); | |
58 | } catch (err) { | |
59 | 1 | callback(err); |
60 | } | |
61 | }; | |
62 | ||
63 | 1 | exports.buildPackages = function(context, packageName, modules, callback) { |
64 | 40 | if (!callback) { |
65 | 14 | callback = modules; |
66 | 14 | modules = undefined; |
67 | } | |
68 | ||
69 | // Allow a string or a list as modules input | |
70 | 40 | if (!_.isArray(modules)) { |
71 | 38 | modules = [modules]; |
72 | 2 | } else if (!modules.length) { |
73 | // Special case empty array input to build all | |
74 | 1 | modules = [undefined]; |
75 | } | |
76 | ||
77 | 40 | var options = {}; |
78 | 40 | if (typeof packageName === 'object') { |
79 | 1 | options = packageName; |
80 | 1 | packageName = options.package; |
81 | } | |
82 | ||
83 | 40 | var packageNames = packageName ? [packageName] : context.config.packageList(), |
84 | contexts = []; | |
85 | ||
86 | 40 | packageNames.forEach(function(pkg) { |
87 | 44 | modules.forEach(function(module) { |
88 | 45 | options.package = pkg; |
89 | 45 | options.module = module || undefined; // '' -> undefined |
90 | ||
91 | 45 | context.event.emit('debug', 'Build package: ' + pkg); |
92 | ||
93 | 45 | var platforms = context.config.platformList(pkg); |
94 | 45 | platforms.forEach(function(platform) { |
95 | 61 | options.platform = platform; |
96 | ||
97 | 61 | var newContext = context.clone(options); |
98 | 61 | contexts.push(newContext); |
99 | }); | |
100 | }); | |
101 | }); | |
102 | ||
103 | 40 | async.forEach(contexts, exports.buildPlatform, callback); |
104 | }; | |
105 | 1 | exports.buildPlatform = function(context, callback) { |
106 | 90 | context.event.emit('debug', 'Build platform: ' + context.description); |
107 | 90 | var modes = context.mode ? [context.mode] : context.plugins.modes(); |
108 | ||
109 | 90 | context.platformCache = context.platformCache || {}; |
110 | ||
111 | // If in AMD mode then run scripts modes sync before others. This ensures that | |
112 | // any scripts the define style or script dependencies will be processed prior | |
113 | // to the dependency module's output. | |
114 | 90 | if (context.config.attributes.amd && _.contains(modes, 'scripts')) { |
115 | 2 | modes = _.without(modes, 'scripts'); |
116 | 2 | exports.buildMode('scripts', context, function(err) { |
117 | 2 | if (err) { |
118 | 1 | return callback(err); |
119 | } | |
120 | ||
121 | 1 | execModes(); |
122 | }); | |
123 | } else { | |
124 | 88 | if (context.config.attributes.amd && !context.platformCache.amdAppModules) { |
125 | 1 | return callback(new Error('Attempting to build mode "' + context.mode + '" without AMD config')); |
126 | } | |
127 | ||
128 | 87 | execModes(); |
129 | } | |
130 | ||
131 | 89 | function execModes() { |
132 | 88 | async.forEach(modes, function(mode, callback) { |
133 | 187 | exports.buildMode(mode, context, callback); |
134 | }, | |
135 | callback); | |
136 | } | |
137 | }; | |
138 | 1 | exports.buildMode = function(mode, context, callback) { |
139 | 188 | context.event.emit('debug', 'Build mode: ' + mode + ' ' + context.description); |
140 | ||
141 | 188 | var modules = context.module ? [context.module] : context.config.moduleList(context.package); |
142 | ||
143 | 188 | if (context.config.attributes.amd) { |
144 | 1 | var topLevelNames = modules.filter(function(module) { |
145 | 2 | return context.config.isTopLevel(module); |
146 | }); | |
147 | 1 | modules = _.difference(modules, topLevelNames); |
148 | } | |
149 | ||
150 | 188 | context = context.clone(); |
151 | 188 | context.mode = mode; |
152 | 188 | context.modeCache = {}; |
153 | ||
154 | 188 | if (context.fileConfig) { |
155 | 24 | processFileConfig(context.fileConfig, callback); |
156 | } else { | |
157 | 164 | context.plugins.outputConfigs(context, function(err, configs) { |
158 | 164 | if (err) { |
159 | 1 | return callback(err); |
160 | } | |
161 | 163 | async.forEach(configs, processFileConfig, callback); |
162 | }); | |
163 | } | |
164 | ||
165 | 188 | function processFileConfig(fileConfig, callback) { |
166 | 200 | var fileContext = context.clone(true); |
167 | 200 | fileContext.fileConfig = fileConfig; |
168 | 200 | fileContext.resources = []; |
169 | 200 | fileContext.combineResources = {}; |
170 | 200 | fileContext.fileCache = fileContext.combined ? {} : undefined; |
171 | ||
172 | 200 | async.eachSeries([ |
173 | topLevelNames, | |
174 | modules | |
175 | ], | |
176 | function(modules, callback) { | |
177 | 400 | if (!modules) { |
178 | 198 | return callback(); |
179 | } | |
180 | ||
181 | 202 | async.forEach(modules, function(module, callback) { |
182 | 291 | var moduleContext = fileContext.clone(); |
183 | 291 | moduleContext.module = module; |
184 | ||
185 | 291 | exports.buildModule(moduleContext, callback); |
186 | }, | |
187 | callback); | |
188 | }, | |
189 | function(err) { | |
190 | 196 | if (err) { |
191 | 5 | return callback(err); |
192 | } | |
193 | ||
194 | 191 | context.plugins.modeComplete(fileContext, callback); |
195 | }); | |
196 | } | |
197 | }; | |
198 | 1 | exports.buildModule = function(context, callback) { |
199 | 282 | context.event.emit('debug', 'Build module: ' + context.description); |
200 | ||
201 | 282 | var module = context.config.module(context.module); |
202 | 282 | if (!module) { |
203 | 1 | return callback(new Error('Unable to find module "' + context.module + '"')); |
204 | } | |
205 | ||
206 | 281 | if (context.config.attributes.amd |
207 | && !context.platformCache.amdAppModules | |
208 | && !context.config.isTopLevel(module)) { | |
209 | 1 | return callback(new Error('Attempting to build module "' + context.module + '" without AMD config')); |
210 | } | |
211 | ||
212 | 280 | context.module = module; |
213 | 280 | context.fileCache = context.combined ? context.fileCache : {}; |
214 | 280 | context.moduleCache = {}; |
215 | ||
216 | 280 | var resource = context.resource; |
217 | 280 | if (resource) { |
218 | 7 | resource = resource.originalResource || resource; |
219 | 7 | exports.processResources(context, [resource], callback); |
220 | } else { | |
221 | // Load all resources associated with this module | |
222 | 273 | build.loadResources(context, function(err, resources) { |
223 | 271 | if (err) { |
224 | 1 | return callback(err); |
225 | } | |
226 | 270 | exports.processResources(context, resources, callback); |
227 | }); | |
228 | } | |
229 | }; | |
230 | ||
231 | 1 | exports.processResources = function(context, resources, callback) { |
232 | 277 | build.processResources(resources, context, function(err, resources) { |
233 | 277 | if (err) { |
234 | 1 | return callback(err); |
235 | } | |
236 | ||
237 | 276 | context.moduleResources = resources; |
238 | 276 | context.plugins.module(context, callback); |
239 | }); | |
240 | }; | |
241 |
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 | 79 | return string.replace(ESCAPER, function(c) { return ESCAPER_LUT[c] || c; }); |
17 | }; | |
18 |
Line | Hits | Source |
---|---|---|
1 | 1 | var esprima = require('esprima'), |
2 | traverse = require('estraverse').traverse; | |
3 | ||
4 | 1 | var sourceMap = require('source-map'), |
5 | SourceMapConsumer = sourceMap.SourceMapConsumer, | |
6 | SourceMapGenerator = sourceMap.SourceMapGenerator; | |
7 | ||
8 | 1 | function expect(node, type, hint) { |
9 | 229 | node = node || {}; |
10 | ||
11 | 229 | if (node.type !== type) { |
12 | 2 | throw new Error((hint || 'Expected ') + '"' + type + '" but found "' + node.type + '"' + errorLocation(node)); |
13 | } | |
14 | } | |
15 | 1 | function errorLocation(node) { |
16 | 6 | return ' location: line: ' + node.loc.start.line + ' column: ' + node.loc.start.column; |
17 | } | |
18 | ||
19 | 1 | function parseDefine(node, allowConfig) { |
20 | 83 | var args = node.arguments, |
21 | name, | |
22 | deps, | |
23 | functionArg; | |
24 | ||
25 | 83 | if (args[0].type === 'FunctionExpression') { |
26 | 8 | functionArg = args[0]; |
27 | 75 | } else if (args[0].type === 'ArrayExpression') { |
28 | 56 | deps = args[0]; |
29 | 56 | functionArg = args[1]; |
30 | 19 | } else if (args[0].type === 'Literal') { |
31 | 18 | name = args[0].value; |
32 | 18 | if (args.length > 2) { |
33 | 11 | deps = args[1]; |
34 | 11 | functionArg = args[2]; |
35 | } else { | |
36 | 7 | functionArg = args[1]; |
37 | } | |
38 | } else { | |
39 | 1 | expect(args[0], 'Literal', 'Expected name to be '); |
40 | } | |
41 | ||
42 | 82 | if (deps) { |
43 | 67 | expect(deps, 'ArrayExpression', 'Expected dependencies to be '); |
44 | 67 | deps.elements.forEach(function(arg) { |
45 | 65 | expect(arg, 'Literal', 'Expected dependency to be '); |
46 | }); | |
47 | ||
48 | 130 | deps = deps.elements.map(function(arg) { return arg.value; }); |
49 | } | |
50 | ||
51 | 81 | expect(functionArg, 'FunctionExpression'); |
52 | 81 | return { |
53 | define: true, | |
54 | name: name, | |
55 | deps: deps, | |
56 | range: functionArg.range, | |
57 | loc: functionArg.loc | |
58 | }; | |
59 | } | |
60 | 1 | var workers = { |
61 | define: function(node) { | |
62 | 27 | return parseDefine(node); |
63 | }, | |
64 | defineView: function(node) { | |
65 | 44 | var ret = parseDefine(node); |
66 | 44 | ret.view = true; |
67 | 44 | return ret; |
68 | }, | |
69 | defineHelper: function(node) { | |
70 | 5 | var ret = parseDefine(node); |
71 | 5 | ret.helper = true; |
72 | 5 | return ret; |
73 | }, | |
74 | defineRouter: function(node) { | |
75 | // Pull the first node off for the routes. Note that this creates an odd structure for | |
76 | // explicitly named routers but we're ok with that as that case should be uncommon and the | |
77 | // code is much simplier via this route. | |
78 | 7 | var routesNode = node.arguments[0], |
79 | routes = {}; | |
80 | 7 | expect(routesNode, 'ObjectExpression', 'Expected first argument to be'); |
81 | ||
82 | 7 | routesNode.properties.forEach(function(prop) { |
83 | 8 | if (prop.key.type !== 'Identifier' && prop.key.type !== 'Literal') { |
84 | 0 | throw new Error('Expected route to be "Identifier" or "Literal" but found "' + prop.key.type + '"' + errorLocation(prop)); |
85 | } | |
86 | 8 | expect(prop.value, 'Literal', 'Expected route handler to be'); |
87 | ||
88 | 8 | routes[prop.key.name || prop.key.value] = prop.value.value; |
89 | }); | |
90 | ||
91 | 7 | node.arguments.shift(); |
92 | ||
93 | 7 | var ret = parseDefine(node); |
94 | 7 | ret.router = true; |
95 | 7 | ret.routes = routes; |
96 | 7 | return ret; |
97 | } | |
98 | }; | |
99 | ||
100 | 1 | module.exports = function(src, options) { |
101 | 82 | options = options || {}; |
102 | ||
103 | 82 | var ast = esprima.parse(src, {range: true, loc: true}), |
104 | defineInfo, | |
105 | defineNode, | |
106 | rangeStart = 0, | |
107 | locStart = {line: 1, column: 0}, | |
108 | locEnd, | |
109 | ret = []; | |
110 | ||
111 | 80 | traverse(ast, { |
112 | enter: function(node, parent) { | |
113 | 674 | if (node.type === 'CallExpression') { |
114 | 83 | var worker = workers[node.callee.name]; |
115 | 83 | if (worker) { |
116 | 83 | defineInfo = worker(node); |
117 | ||
118 | 81 | if (defineNode) { |
119 | 4 | throw new Error('Unsupported nested define "' + defineInfo.name + '" found' + errorLocation(node)); |
120 | } | |
121 | 77 | defineNode = node; |
122 | ||
123 | ||
124 | 77 | defineInfo.source = src.slice.apply(src, defineInfo.range); |
125 | 77 | delete defineInfo.range; |
126 | ||
127 | 77 | if (rangeStart !== node.range[0]) { |
128 | 3 | ret.push({ |
129 | source: src.slice(rangeStart, node.range[0]), | |
130 | loc: { | |
131 | start: locStart, | |
132 | end: {line: node.loc.start.line, column: Math.max(node.loc.start.column-1, 0)} | |
133 | } | |
134 | }); | |
135 | } | |
136 | } | |
137 | } | |
138 | }, | |
139 | leave: function(node, parent) { | |
140 | 640 | if (node === defineNode) { |
141 | 73 | ret.push(defineInfo); |
142 | ||
143 | 73 | rangeStart = defineNode.range[1] + 1; |
144 | 73 | locStart = {line: node.loc.end.line, column: node.loc.end.column + 1}; |
145 | ||
146 | 73 | defineNode = defineInfo = undefined; |
147 | } | |
148 | 640 | locEnd = node.loc.end; |
149 | } | |
150 | }); | |
151 | ||
152 | // Handle trailing source | |
153 | 74 | if (rangeStart < src.length) { |
154 | 6 | ret.push({ |
155 | source: src.slice(rangeStart), | |
156 | loc: { | |
157 | start: locStart, | |
158 | end: locEnd | |
159 | } | |
160 | }); | |
161 | } | |
162 | ||
163 | 74 | return ret; |
164 | }; | |
165 |
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 | 123 | this.output = output; |
24 | 123 | if (SourceMapGenerator) { |
25 | 123 | this.generator = new SourceMapGenerator({file: output}); |
26 | } | |
27 | ||
28 | 123 | this.contentCache = {}; |
29 | 123 | this.line = 1; |
30 | 123 | this.column = 1; |
31 | 123 | this._content = ''; |
32 | }; | |
33 | ||
34 | 1 | exports.prototype.add = function(name, content, context, generated) { |
35 | 517 | this._sourceMap = ''; |
36 | 517 | this._consumer = undefined; |
37 | ||
38 | 517 | var lines = content.split('\n'); |
39 | 517 | if (name && !generated) { |
40 | 191 | this.contentCache[name] = { |
41 | lines: lines, | |
42 | context: context | |
43 | }; | |
44 | } | |
45 | ||
46 | 517 | if (this.generator) { |
47 | 517 | _.each(lines, function(line, index) { |
48 | 2878 | 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 | 517 | this.line += lines.length - 1; |
63 | 517 | if (lines.length >= 2) { |
64 | 486 | this.column = 1; |
65 | } | |
66 | 517 | this.column += lines[lines.length - 1].length; |
67 | ||
68 | 517 | this._content += content; |
69 | }; | |
70 | 1 | exports.prototype.content = function() { |
71 | 279 | 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 | 825 | if (_.isString(resource)) { |
12 | 270 | return {src: resource}; |
13 | } else { | |
14 | 555 | return resource; |
15 | } | |
16 | }, | |
17 | ||
18 | source: function(resource) { | |
19 | 972 | return resource.src || resource.dir || resource; |
20 | }, | |
21 | ||
22 | map: function(resource, library, config) { | |
23 | 392 | var bowerPath; |
24 | 392 | 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 | 392 | if (!library && !bowerPath) { |
30 | 281 | return resource; |
31 | } | |
32 | ||
33 | 111 | resource = resources.cast(_.clone(resource)); |
34 | ||
35 | 111 | 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 | 111 | _.extend(resource, _.omit(config, 'overrideLibrary')); |
40 | ||
41 | 111 | if (_.isString(src)) { |
42 | 109 | var mixinOverride = library && library.overrides && library.overrides[src], |
43 | libraryOverride = library && library.parent && library.parent.overrides && library.parent.overrides[src], | |
44 | override = (mixinOverride || mixinOverride === false) ? mixinOverride : libraryOverride, | |
45 | ||
46 | librarySrc = bowerPath || library.root || ''; | |
47 | 109 | librarySrc = librarySrc ? path.join(librarySrc, src) : src; |
48 | ||
49 | 109 | if (override) { |
50 | 17 | resource.originalSrc = librarySrc; |
51 | 17 | librarySrc = _.isString(override) ? override : src; |
52 | ||
53 | 17 | if (library.overrideLibrary) { |
54 | 6 | librarySrc = path.join(library.overrideLibrary.root || '', librarySrc); |
55 | } | |
56 | 92 | } else if (override === false) { |
57 | 3 | return; |
58 | } | |
59 | ||
60 | 106 | if (resource.src) { |
61 | 105 | resource.src = librarySrc; |
62 | 1 | } else if (resource.dir) { |
63 | 1 | resource.dir = librarySrc; |
64 | } | |
65 | } | |
66 | ||
67 | 108 | resource.library = library; |
68 | 108 | return resource; |
69 | }, | |
70 | ||
71 | relativePath: function(src, library) { | |
72 | 39 | if (src.indexOf('./') === 0) { |
73 | 0 | src = src.substring(2); |
74 | } | |
75 | 39 | src = normalize(src); |
76 | ||
77 | 39 | if (!library) { |
78 | 36 | return src; |
79 | } | |
80 | ||
81 | // Attempt to strip either the root of the base or overriding library as we don't know | |
82 | // which we might be | |
83 | 3 | var mixinRoot = library.root || ''; |
84 | 3 | if (src.indexOf(mixinRoot) === 0) { |
85 | 2 | src = src.substring(mixinRoot.length); |
86 | 1 | } else if (library.overrideLibrary) { |
87 | 1 | mixinRoot = library.overrideLibrary.root || ''; |
88 | ||
89 | 1 | if (src.indexOf(mixinRoot) === 0) { |
90 | 1 | src = src.substring(mixinRoot.length); |
91 | } | |
92 | } | |
93 | 3 | return src; |
94 | }, | |
95 | ||
96 | pathToLibrary: function(src, library) { | |
97 | 39 | src = resources.relativePath(src, library); |
98 | ||
99 | 39 | var overrides = library && library.overrides; |
100 | 39 | if (overrides) { |
101 | 1 | overrides = _.invert(overrides); |
102 | ||
103 | // Warn not supporting directories at this point in time. Matches must be 1 to 1 | |
104 | 1 | return overrides[src] || src; |
105 | } | |
106 | ||
107 | 38 | return src; |
108 | } | |
109 | }; | |
110 |
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 | 187 | var watchInfo = { |
46 | callback: callback, | |
47 | parents: [], | |
48 | queue: [] | |
49 | }; | |
50 | 187 | if (parent) { |
51 | 106 | watchInfo.parents.push(parent); |
52 | } | |
53 | 187 | watchedFiles[filename.virtual || filename] = watchInfo; |
54 | ||
55 | 187 | 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 | 103 | var watch = watchedFiles[filename.virtual || filename]; |
124 | 103 | if (!watch) { |
125 | // Create a watch on this and all others | |
126 | 81 | watchFile(filename, callback); |
127 | } else { | |
128 | 22 | watch.callback = callback; |
129 | } | |
130 | ||
131 | 103 | filename = filename.virtual || filename; |
132 | ||
133 | 103 | dependencies.forEach(function(depend) { |
134 | 275 | var watch = watchedFiles[depend.virtual || depend]; |
135 | 275 | if (!watch) { |
136 | 106 | watchFile(depend, undefined, filename); |
137 | } else { | |
138 | 169 | if (!_.contains(watch.parents, filename)) { |
139 | 90 | watch.parents.push(filename); |
140 | } | |
141 | } | |
142 | }); | |
143 | }; | |
144 | ||
145 | 1 | exports.trigger = function(type, filename) { |
146 | 32 | notifyWatch(filename, type, filename, true); |
147 | }; | |
148 | ||
149 | 1 | exports.unwatch = function(filename, dependencies) { |
150 | 9 | var watch = watchedFiles[filename.virtual || filename]; |
151 | 9 | if (!watch) { |
152 | 1 | return; |
153 | } | |
154 | ||
155 | // Remove the callback | |
156 | 8 | if (!dependencies) { |
157 | 4 | watch.callback = undefined; |
158 | } | |
159 | ||
160 | // For each dependency remove the parent link | |
161 | 8 | filename = filename.virtual || filename; |
162 | ||
163 | 8 | _.each(dependencies || watchedFiles, function(depend) { |
164 | 18 | var watch = watchedFiles[depend.virtual || depend]; |
165 | 18 | if (!watch) { |
166 | 14 | return; |
167 | } | |
168 | ||
169 | 4 | watch.parents = _.without(watch.parents, filename); |
170 | }); | |
171 | ||
172 | // Kill this watch if it can't trigger or fire | |
173 | 8 | var canTrigger = watch.watch || _.some(watchedFiles, function(watch) { |
174 | 29 | return _.contains(watch.parents, filename); |
175 | }); | |
176 | 8 | if (!watch.callback || !canTrigger) { |
177 | 5 | unwatch(filename); |
178 | } | |
179 | ||
180 | // Kill any other watches that might not be valid anymore | |
181 | 8 | _.each(_.clone(watchedFiles), function(watch, name) { |
182 | 24 | if (!watch.callback && !watch.parents.length) { |
183 | 4 | exports.unwatch(name); |
184 | } | |
185 | }); | |
186 | }; | |
187 | 1 | exports.unwatchAll = function() { |
188 | 62 | _.each(watchedFiles, function(watch, name) { |
189 | 182 | unwatch(name); |
190 | }); | |
191 | }; | |
192 | ||
193 | 1 | function unwatch(name) { |
194 | 187 | watchedFiles[name].callback = undefined; |
195 | 187 | if (watchedFiles[name].watch) { |
196 | 6 | watchedFiles[name].watch.close(); |
197 | } | |
198 | 187 | 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(eventBus) { |
8 | 30 | EventEmitter.call(this); |
9 | ||
10 | 30 | this.eventBus = eventBus; |
11 | 30 | this.reset(); |
12 | ||
13 | 30 | this._exec = this.setupExec(); |
14 | } | |
15 | ||
16 | 1 | WatchManager.prototype = { |
17 | configFile: function(path, mixins, callback) { | |
18 | 19 | if (_.isFunction(mixins)) { |
19 | 0 | callback = mixins; |
20 | 0 | mixins = undefined; |
21 | } | |
22 | ||
23 | 19 | var self = this; |
24 | 19 | watcher.watchFile(path, mixins || [], function() { |
25 | 4 | self.emit('watch-change', {fileName: path, config: true}); |
26 | ||
27 | 4 | self.pushChange({callback: callback, fileName: path, config: true}); |
28 | }); | |
29 | }, | |
30 | moduleOutput: function(status, callback) { | |
31 | 77 | var self = this; |
32 | ||
33 | 77 | function theWatcher(type, filename, sourceChange) { |
34 | 40 | self.emit('watch-change', {fileName: sourceChange, output: status.fileName}); |
35 | 40 | self.pushChange({ |
36 | callback: callback, | |
37 | type: type, | |
38 | fileName: status.fileName, | |
39 | sourceChange: sourceChange | |
40 | }); | |
41 | } | |
42 | ||
43 | 77 | var watchInfo = this.watching[status.fileName] || {}, |
44 | ||
45 | 280 | eventInputs = status.inputs.filter(function(input) { return input.event; }), |
46 | 268 | fileInputs = _.difference(status.inputs, eventInputs).map(function(input) { return fu.resolvePath(input.dir || input); }), |
47 | ||
48 | removedFiles = _.difference(watchInfo.files, fileInputs); | |
49 | ||
50 | 77 | if (removedFiles.length) { |
51 | 2 | watcher.unwatch(status.fileName, removedFiles); |
52 | } | |
53 | 77 | watcher.watchFile({ virtual: status.fileName }, fileInputs, theWatcher); |
54 | ||
55 | // Remove all events as we will reregister below | |
56 | 77 | _.each(watchInfo.events, function(event) { |
57 | 5 | this.eventBus.removeListener(event, watchInfo.watcher); |
58 | }, this); | |
59 | ||
60 | 77 | eventInputs = _.pluck(eventInputs, 'event'); |
61 | 77 | _.each(eventInputs, function(event) { |
62 | 12 | this.eventBus.on(event, theWatcher); |
63 | }, this); | |
64 | ||
65 | 77 | this.watching[status.fileName] = { |
66 | watcher: theWatcher, | |
67 | files: fileInputs, | |
68 | events: eventInputs | |
69 | }; | |
70 | }, | |
71 | ||
72 | ||
73 | setupExec: function() { | |
74 | 11 | return _.debounce(_.bind(this.flushQueue, this), 500); |
75 | }, | |
76 | flushQueue: function() { | |
77 | 25 | if (this.queue.length) { |
78 | 25 | _.each(this.queue, function(change) { |
79 | 42 | change.callback(); |
80 | }); | |
81 | 25 | this.queue = []; |
82 | } | |
83 | }, | |
84 | ||
85 | reset: function() { | |
86 | // Cleanup what we can, breaking things along the way | |
87 | // WARN: This prevents concurrent execution within the same process. | |
88 | 54 | watcher.unwatchAll(); |
89 | ||
90 | 54 | this.watching = {}; |
91 | 54 | this.queue = []; |
92 | }, | |
93 | pushChange: function(change) { | |
94 | 61 | fu.resetCache(change.sourceChange); |
95 | 61 | if (change.type === 'remove' && change.sourceChange) { |
96 | 3 | fu.resetCache(path.dirname(change.sourceChange)); |
97 | } | |
98 | ||
99 | 61 | if (_.find(this.queue, function(existing) { |
100 | 44 | return existing.config || (change.fileName && (existing.fileName === change.fileName)); |
101 | })) { | |
102 | // If we have a pending config change or changes to the same file that has not started then | |
103 | // we can ignore subsequent changes | |
104 | 7 | return; |
105 | } | |
106 | ||
107 | 54 | if (change.config) { |
108 | 10 | this.reset(); |
109 | } | |
110 | ||
111 | 54 | this.queue.push(change); |
112 | 54 | this._exec(); |
113 | } | |
114 | }; | |
115 | ||
116 | 1 | WatchManager.prototype.__proto__ = EventEmitter.prototype; |
117 | ||
118 | 1 | exports = module.exports = WatchManager; |
119 |