Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

`externs` for Closure from individual packages

the `options` object that initially contains options from the
commandline that are then passed to the minifier is first passed through
each of the source packages (bundled libs, including dependencies) and
extended if there is anything to extend. Currently just the `externs`
property.
  • Loading branch information...
commit e87a91d12be58f94cd3d368370379d8831b61293 1 parent f09d75f
@rvagg rvagg authored
View
5 lib/package-descriptor.js
@@ -38,7 +38,7 @@ var overrides = {
}
, create = function (json) {
- var newJson = {}
+ var newJson = Object.create(json) // original is available via Object.getPrototypeOf
, key
if (typeof json.overlay == 'object'
@@ -59,12 +59,11 @@ var overrides = {
}
for (key in json) {
- if (!(key in newJson)) {
+ if (!newJson.hasOwnProperty(key)) {
newJson[key] = json[key]
}
}
- newJson._original = json
return newJson
}
View
15 lib/source-build.js
@@ -32,6 +32,7 @@
var async = require('async')
, fs = require('fs')
+ , util = require('./util')
, minify = require('./minify')
, template = require('./template')
, argsParse = require('./args-parse')
@@ -57,8 +58,8 @@ var async = require('async')
//options.type == plain||minified
var finish = function (err, source) {
if (err) return callback(err) // wrapped in template.js
- if (options.type === 'minified') minify.minify(this.options, source, callback)
- else callback(null, source)
+ if (options.type != 'minified') return callback(null, source)
+ minify.minify(this.completeOptions(), source, callback)
}.bind(this)
, tmplData = function (sources) {
@@ -67,7 +68,7 @@ var async = require('async')
, context: argsParse.toContextString(this.options)
, sandbox: !!this.options.sandbox
, packages: this.packages.map(function (p) {
- return p.getIdentifier()
+ return p.identifier
}).join(' ')
}
}.bind(this)
@@ -84,6 +85,14 @@ var async = require('async')
// async.map, oh my! do an asString() on each SourcePackage async but reassemble them in order
async.map(this.packages, packageToString, readComplete)
}
+
+ , completeOptions: function () { // options + any additional options child packages may wish to add
+ var options = util.extend(this.options, {})
+ this.packages.forEach(function (pkg) {
+ pkg.extendOptions(options)
+ })
+ return options
+ }
}
// a utility static method to partially read an ender build file and parse the head comment
View
60 lib/source-package.js
@@ -31,13 +31,11 @@
* compatible output (the less screwing with strings here the better).
*/
-var fs = require('fs')
- , path = require('path')
+var path = require('path')
, async = require('async')
, template = require('./template')
, packageUtil = require('./package-util')
, sourcePackageUtil = require('./source-package-util')
- , FilesystemError = require('./errors').FilesystemError
, templateFiles = {
'standard' : '../resources/source-package.mustache'
@@ -64,17 +62,14 @@ var fs = require('fs')
// generate an object that can be fed to the templates
, makeTemplateData = function (sources) {
- return {
- packageName : this.packageJSON.name
- , options : {
- noop : this.options.noop
- , sandbox :
- // if this package is in the `--sandbox <packages>` list, or, if we are
- // the root package and a --sandbox option is passed, then set this to true
- Array.isArray(this.options.sandbox) && (
+ // if this package is in the `--sandbox <packages>` list, or, if we are
+ // the root package and a --sandbox option is passed, then set this to true
+ var isSandbox = Array.isArray(this.options.sandbox) && (
this.options.sandbox.indexOf(this.packageJSON.name) != -1
|| this.isRoot)
- }
+ return {
+ packageName : this.packageJSON.name
+ , options : { noop: this.options.noop, sandbox: isSandbox }
// these objects have lazy methods so we don't do unnecessary indent()ing
, mainSource: sources.main && {
raw : function () { return sources.main }
@@ -101,8 +96,21 @@ var fs = require('fs')
return this
}
- , getIdentifier: function () {
- return this.packageJSON._original.name + '@' + this.packageJSON.version
+ // not the overridden name, the name from the package.json
+ , get realName () {
+ return this.packageJSON && Object.getPrototypeOf(this.packageJSON).name
+ ? Object.getPrototypeOf(this.packageJSON).name
+ : this.packageName
+ }
+
+ // get the `name` from the original json data, available as proto (see package-descriptor.js)
+ , get identifier () {
+ return this.realName + '@' + this.packageJSON.version
+ }
+
+ // the root of this package on the filesystem
+ , get packageRoot () {
+ return packageUtil.getPackageRoot(this.parents, this.realName)
}
// this method supports multiple calls but a single execution, hence the async.memoize in init()
@@ -110,8 +118,8 @@ var fs = require('fs')
// note that "main" and "ender" are processed in the same way so they can both be just
// a string pointing to a source file or an array of source files that are concatenated
// or be left unspecified
- var root = packageUtil.getPackageRoot(this.parents, this.packageName)
- , asValidType = function (value) {
+ var root = this.packageRoot
+ , asValidType = function (value) {
return typeof value == 'string' || Array.isArray(value) ? value : []
}
, mainSources = asValidType(this.packageJSON.main)
@@ -129,18 +137,32 @@ var fs = require('fs')
generateSource(
this.isRoot
- , makeTemplateData.bind(this)(sources)
+ , makeTemplateData.call(this, sources)
, callback
)
}.bind(this)
, sourceLoaders = {
- main: sourcePackageUtil.loadFiles.bind(this, root, mainSources)
- , ender: sourcePackageUtil.loadFiles.bind(this, root, enderBridgeSources)
+ main : sourcePackageUtil.loadFiles.bind(this, root, mainSources)
+ , ender : sourcePackageUtil.loadFiles.bind(this, root, enderBridgeSources)
}
async.parallel(sourceLoaders, handleSourceData)
}
+
+ , extendOptions: function (options) {
+ var externs = this.packageJSON && this.packageJSON.externs
+ , root
+
+ if (externs) {
+ if (!Array.isArray(externs)) externs = [ externs ]
+ root = this.packageRoot
+ if (!options.externs) options.externs = []
+ options.externs = options.externs.concat(externs.map(function (e) {
+ return path.join(root, e)
+ }))
+ }
+ }
}
module.exports.create = function (packageName, parents, isRoot, packageJSON, options) {
View
24 test/unit/package-util-test.js
@@ -102,8 +102,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'node_modules/amodule/', expected)
packageUtil.readPackageJSON([], 'amodule', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -115,8 +114,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'node_modules/amodule.js/', expected)
packageUtil.readPackageJSON([], 'amodule.js', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -128,8 +126,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'node_modules/amodule/', expected)
packageUtil.readPackageJSON([], 'amodule@0.1.200', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -141,8 +138,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'node_modules/aparent/node_modules/amodule/', expected)
packageUtil.readPackageJSON([ 'aparent' ], 'amodule', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -160,8 +156,7 @@ testCase('Package util', {
[ 'aparent1', 'aparent2', 'aparent3' ]
, 'amodule', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
}
@@ -174,8 +169,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, '.', expected)
packageUtil.readPackageJSON([ 'this shouldn\'t matter' ], './', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -187,8 +181,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'some/path/without/dots', expected)
packageUtil.readPackageJSON([ 'foobar' ], 'some/path/without/dots', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
@@ -200,8 +193,7 @@ testCase('Package util', {
setupReadPackageJSON.call(this, 'some/path/without/dots', expected)
packageUtil.readPackageJSON([ 'what???' ], './some/path/../path/without/dots', function (err, actual) {
refute(err)
- assert.equals(actual._original, expected)
- ; delete actual._original
+ assert.equals(Object.getPrototypeOf(actual), expected)
assert.equals(actual, expected)
done()
})
View
226 test/unit/source-build-test.js
@@ -50,8 +50,9 @@ testCase('Source build', {
this.createPackageMock = function (content, identifier) {
var pkg = SourcePackage.create()
, pkgMock = this.mock(pkg)
+
pkgMock.expects('asString').once().callsArgWith(0, null, content)
- pkgMock.expects('getIdentifier').once().returns(identifier)
+ pkg.__defineGetter__('identifier', function () { return identifier }) // sinon can't mock getters
return pkg
}
this.createArgsParseMock = function (optionsArg, contextArg) {
@@ -60,98 +61,141 @@ testCase('Source build', {
}
}
- , 'asString plain': function (done) {
- var pkg1Content = 'package 1\ncontents'
- , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
- , pkg2Content = 'package 2\n\ncontents'
- , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
- , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
- , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
- , optionsArg = { options: 1 }
- , srcBuild = SourceBuild.create(optionsArg)
- , contextArg = 'some context here & don\'t escape <this>'
- , plainSource =
- createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
- + pkg1Content + '\n\n'
- + pkg2Content + '\n\n'
- + pkg3Content
- , mockMinify = this.mock(minify)
-
- this.createArgsParseMock(optionsArg, contextArg)
- srcBuild.addPackage(pkg1)
- srcBuild.addPackage(pkg2)
- srcBuild.addPackage(pkg3)
-
- mockMinify.expects('minify').never()
-
- srcBuild.asString({ type: 'plain' }, function (err, actual) {
- refute(err)
- assert.equals(actual, plainSource)
- done()
- })
- }
+ , 'asString': {
+ 'plain': function (done) {
+ var pkg1Content = 'package 1\ncontents'
+ , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
+ , pkg2Content = 'package 2\n\ncontents'
+ , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
+ , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
+ , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
+ , optionsArg = { options: 1 }
+ , srcBuild = SourceBuild.create(optionsArg)
+ , contextArg = 'some context here & don\'t escape <this>'
+ , plainSource =
+ createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
+ + pkg1Content + '\n\n'
+ + pkg2Content + '\n\n'
+ + pkg3Content
+ , mockMinify = this.mock(minify)
+
+ this.createArgsParseMock(optionsArg, contextArg)
+ srcBuild.addPackage(pkg1)
+ srcBuild.addPackage(pkg2)
+ srcBuild.addPackage(pkg3)
+
+ mockMinify.expects('minify').never()
+
+ srcBuild.asString({ type: 'plain' }, function (err, actual) {
+ refute(err)
+ assert.equals(actual, plainSource)
+ done()
+ })
+ }
- , 'asString minify': function (done) {
- var pkg1Content = 'package 1\ncontents'
- , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
- , pkg2Content = 'package 2\n\ncontents'
- , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
- , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
- , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
- , optionsArg = { options: 1 }
- , srcBuild = SourceBuild.create(optionsArg)
- , contextArg = 'some minified context here & don\'t escape <this>'
- , plainSource =
- createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
- + pkg1Content + '\n\n'
- + pkg2Content + '\n\n'
- + pkg3Content
- , minifiedSource = 'this is minified, these are not the droids you are looking for'
- , mockMinify = this.mock(minify)
-
- this.createArgsParseMock(optionsArg, contextArg)
- srcBuild.addPackage(pkg1)
- srcBuild.addPackage(pkg2)
- srcBuild.addPackage(pkg3)
-
- mockMinify.expects('minify').once().withArgs(optionsArg, plainSource).callsArgWith(2, null, minifiedSource)
-
- srcBuild.asString({ type: 'minified' }, function (err, actual) {
- refute(err)
- assert.equals(actual, minifiedSource)
- done()
- })
- }
+ , 'minify': function (done) {
+ var pkg1Content = 'package 1\ncontents'
+ , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
+ , pkg2Content = 'package 2\n\ncontents'
+ , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
+ , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
+ , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
+ , optionsArg = { options: 1 }
+ , srcBuild = SourceBuild.create(optionsArg)
+ , contextArg = 'some minified context here & don\'t escape <this>'
+ , plainSource =
+ createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
+ + pkg1Content + '\n\n'
+ + pkg2Content + '\n\n'
+ + pkg3Content
+ , minifiedSource = 'this is minified, these are not the droids you are looking for'
+ , mockMinify = this.mock(minify)
+
+ this.createArgsParseMock(optionsArg, contextArg)
+ srcBuild.addPackage(pkg1)
+ srcBuild.addPackage(pkg2)
+ srcBuild.addPackage(pkg3)
+
+ mockMinify.expects('minify').once().withArgs(optionsArg, plainSource).callsArgWith(2, null, minifiedSource)
+
+ srcBuild.asString({ type: 'minified' }, function (err, actual) {
+ refute(err)
+ assert.equals(actual, minifiedSource)
+ done()
+ })
+ }
+
+ , 'sandboxed': function (done) {
+ var pkg1Content = 'package 1\ncontents'
+ , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
+ , pkg2Content = 'package 2\n\ncontents'
+ , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
+ , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
+ , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
+ , optionsArg = { sandbox: [ 'foo', 'bar' ] }
+ , srcBuild = SourceBuild.create(optionsArg)
+ , contextArg = 'some context here & don\'t escape <this>'
+ , plainSource =
+ createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
+ + '!function () {\n\n'
+ + pkg1Content + '\n\n' + pkg2Content + '\n\n' + pkg3Content
+ + '\n\n}.call({});'
+ , mockMinify = this.mock(minify)
+
+ this.createArgsParseMock(optionsArg, contextArg)
+ srcBuild.addPackage(pkg1)
+ srcBuild.addPackage(pkg2)
+ srcBuild.addPackage(pkg3)
+
+ mockMinify.expects('minify').never()
+
+ srcBuild.asString({ type: 'plain' }, function (err, actual) {
+ refute(err)
+ assert.equals(actual, plainSource)
+ done()
+ })
+ }
- , 'asString sandboxed': function (done) {
- var pkg1Content = 'package 1\ncontents'
- , pkg1 = this.createPackageMock(pkg1Content, "pkg1@0.1.1")
- , pkg2Content = 'package 2\n\ncontents'
- , pkg2 = this.createPackageMock(pkg2Content, "pkg2@1.1.1")
- , pkg3Content = 'package 3\n\ncontents\nright\nhere\n'
- , pkg3 = this.createPackageMock(pkg3Content, "pkg3@1.2.3")
- , optionsArg = { sandbox: [ 'foo', 'bar' ] }
- , srcBuild = SourceBuild.create(optionsArg)
- , contextArg = 'some context here & don\'t escape <this>'
- , plainSource =
- createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
- + '!function () {\n\n'
- + pkg1Content + '\n\n' + pkg2Content + '\n\n' + pkg3Content
- + '\n\n}.call({});'
- , mockMinify = this.mock(minify)
-
- this.createArgsParseMock(optionsArg, contextArg)
- srcBuild.addPackage(pkg1)
- srcBuild.addPackage(pkg2)
- srcBuild.addPackage(pkg3)
-
- mockMinify.expects('minify').never()
-
- srcBuild.asString({ type: 'plain' }, function (err, actual) {
- refute(err)
- assert.equals(actual, plainSource)
- done()
- })
+ // the minifier function should be passed an options object that has been extended by
+ // each of the packages, this allows for packages to add on options such as 'externs'
+ // which are passed to the minifier
+ , 'minify extends options for each package (externs)': function (done) {
+ var pkg1 = this.createPackageMock('p1', "pkg1@0.1.1")
+ , pkg2 = this.createPackageMock('p2', "pkg2@1.1.1")
+ , pkg3 = this.createPackageMock('p3', "pkg3@1.2.3")
+ , optionsArg = { options: 1, externs: [ 'extern0' ] }
+ , expectedOptionsArg
+ , srcBuild = SourceBuild.create(optionsArg)
+ , contextArg = 'some minified context here & don\'t escape <this>'
+ , plainSource =
+ createExpectedHeader(contextArg, "pkg1@0.1.1 pkg2@1.1.1 pkg3@1.2.3")
+ + 'p1\n\np2\n\np3'
+ , minifiedSource = 'this is minified, these are not the droids you are looking for'
+ , mockMinify = this.mock(minify)
+
+ this.createArgsParseMock(optionsArg, contextArg)
+ srcBuild.addPackage(pkg1)
+ srcBuild.addPackage(pkg2)
+ srcBuild.addPackage(pkg3)
+
+ // sort of mock out the extendOptions() function
+ pkg2.extendOptions = function (options) {
+ options.externs.push('extern1')
+ options.externs.push('extern2')
+ }
+ pkg3.extendOptions = function (options) {
+ options.externs.push('extern3')
+ }
+ expectedOptionsArg = { options: 1, externs: [ 'extern0', 'extern1', 'extern2', 'extern3' ] }
+
+ mockMinify.expects('minify').once().withArgs(expectedOptionsArg, plainSource).callsArgWith(2, null, minifiedSource)
+
+ srcBuild.asString({ type: 'minified' }, function (err, actual) {
+ refute(err)
+ assert.equals(actual, minifiedSource)
+ done()
+ })
+ }
}
, 'parseContext': {
View
65 test/unit/source-package-test.js
@@ -31,8 +31,8 @@ var testCase = require('buster').testCase
, FilesystemError = require('../../lib/errors').FilesystemError
, templateFiles = {
- 'standard' : __dirname + '/../../resources/source-package.mustache'
- , 'root' : __dirname + '/../../resources/root-package.mustache'
+ 'standard' : path.join(__dirname, '/../../resources/source-package.mustache')
+ , 'root' : path.join(__dirname, '/../../resources/root-package.mustache')
}
, templateFileContents
@@ -573,8 +573,11 @@ testCase('Source package', {
}
, 'test identifier': function () {
- var srcPackage = SourcePackage.create('foobar', null, false, { name: 'foobar', version: '1.2.3', _original: { name: 'barfoo' } })
- assert.equals(srcPackage.getIdentifier(), 'barfoo@1.2.3')
+ var json = { name: 'foobar', version: '1.2.3' }
+ , srcPackage = SourcePackage.create('foobar', null, false, json)
+
+ json.__proto__ = { name: 'barfoo' } // original json, see package-descriptor.js
+ assert.equals(srcPackage.identifier, 'barfoo@1.2.3')
}
, 'test fs error': function (done) {
@@ -594,4 +597,58 @@ testCase('Source package', {
done()
})
}
+
+ , 'extendOptions': {
+ 'test nothing to extend': function () {
+ var pkg = SourcePackage.create('whatevs', [], false, { name: 'whatevs', main: './main.js' }, {})
+ , opts = { foo: 'bar' }
+
+ pkg.extendOptions(opts)
+ assert.equals(opts, { foo: 'bar' }) // shoudn't be touched
+ }
+
+ , 'test externs': function () {
+ var pkg = SourcePackage.create('whatevs', [], false, { name: 'whatevs', main: './main.js', externs: 'lib/foo.js' }, {})
+ , opts = { foo: 'bar' }
+
+ pkg.extendOptions(opts)
+ assert.equals(opts, { foo: 'bar', externs: [ path.resolve('node_modules/whatevs/lib/foo.js') ] }) // shoudn't be touched
+ }
+
+ , 'test externs with overridden pkg name': function () {
+ // just to make sure we're pointing to the right dir, not using the overridden name
+
+ var json = { name: 'whatevs', main: './main.js', externs: 'lib/foo.js' }
+ , pkg = SourcePackage.create('whatevs', [], false, json, {})
+ , opts = { foo: 'bar' }
+ json.__proto__ = { name: 'whoa' }
+
+ pkg.extendOptions(opts)
+ assert.equals(opts, { foo: 'bar', externs: [ path.resolve('node_modules/whoa/lib/foo.js') ] })
+ }
+
+ , 'test externs array and nested pkg': function () {
+ var pkg = SourcePackage.create('whatevs', [ 'boom', 'bang' ], false, { name: 'whatevs', main: './main.js', externs: [ 'lib/foo.js', 'BOOM.js' ] }, {})
+ , opts = { foo: 'bar' }
+
+ pkg.extendOptions(opts)
+ assert.equals(opts, { foo: 'bar', externs: [
+ path.resolve('node_modules/boom/node_modules/bang/node_modules/whatevs/lib/foo.js')
+ , path.resolve('node_modules/boom/node_modules/bang/node_modules/whatevs/BOOM.js')
+ ] })
+ }
+
+ , 'test externs array over existing externs': function () {
+ var pkg = SourcePackage.create('whatevs', [ ], false, { name: 'whatevs', main: './main.js', externs: [ 'lib/foo.js', 'BOOM.js' ] }, {})
+ , opts = { foo: 'bar', externs: [ 'existing1.js', 'existing2.js' ] }
+
+ pkg.extendOptions(opts)
+ assert.equals(opts, { foo: 'bar', externs: [
+ 'existing1.js'
+ , 'existing2.js'
+ , path.resolve('node_modules/whatevs/lib/foo.js')
+ , path.resolve('node_modules/whatevs/BOOM.js')
+ ] })
+ }
+ }
})

0 comments on commit e87a91d

Please sign in to comment.
Something went wrong with that request. Please try again.