grunt-neuter
Advanced tools
Comparing version 0.5.0 to 0.6.0
@@ -61,22 +61,40 @@ /* | ||
// Run to test the default simple require options. | ||
custom_separator_options: { | ||
// Run to test the basePath option. | ||
simple_basepath_options: { | ||
// NOTE: this uses the filepath transform fixture because it performs the same operation | ||
files: { | ||
'tmp/custom_separator_options' : ['test/fixtures/simple_require_statements.js'] | ||
'tmp/simple_basepath_options' : ['test/fixtures/simple_require_filepath_transforms.js'] | ||
}, | ||
options: { | ||
separator: '!!!!' | ||
basePath: 'test/fixtures/' | ||
} | ||
}, | ||
// Run to test the inclusion of source urls. | ||
custom_source_url_inclusion_option: { | ||
// Run to test relative require statements. | ||
relative_require_statements: { | ||
files: { | ||
'tmp/custom_source_url_inclusion_option' : ['test/fixtures/simple_require_statements.js'] | ||
'tmp/relative_require_statements' : ['test/fixtures/relative_require_statements.js'] | ||
} | ||
}, | ||
// Run to test relative require statements in conjunction with the basePath option | ||
relative_requires_with_basepath: { | ||
files: { | ||
'tmp/relative_requires_with_basepath' : ['test/fixtures/relative_requires_with_basepath.js'], | ||
}, | ||
options: { | ||
includeSourceURL: true | ||
basePath: 'test/fixtures/' | ||
} | ||
}, | ||
// Run to test the default simple require options. | ||
custom_separator_options: { | ||
files: { | ||
'tmp/custom_separator_options' : ['test/fixtures/simple_require_statements.js'] | ||
}, | ||
options: { | ||
separator: '!!!!' | ||
} | ||
}, | ||
// Run to test that duplicate require statemtns only write a source file to the | ||
@@ -137,2 +155,43 @@ // destination once. | ||
} | ||
}, | ||
optional_semicolons: { | ||
files: { | ||
'tmp/optional_semicolons': ['test/fixtures/optional_semicolons.js'] | ||
} | ||
}, | ||
optional_dotjs: { | ||
files: { | ||
'tmp/optional_dotjs': ['test/fixtures/optional_dotjs.js'] | ||
} | ||
}, | ||
source_maps: { | ||
files: { | ||
'tmp/source_maps': ['test/fixtures/glob_require.js'] | ||
}, | ||
options: { | ||
includeSourceMap: true | ||
} | ||
}, | ||
process_as_template: { | ||
files: { | ||
'tmp/process_as_template': ['test/fixtures/process_as_template.js'] | ||
}, | ||
options: { | ||
process: { | ||
data: { | ||
foo: 5, | ||
bar: 'baz' | ||
} | ||
} | ||
} | ||
}, | ||
process_with_function: { | ||
files: { | ||
'tmp/process_with_function': ['test/fixtures/simple_require.js'] | ||
}, | ||
options: { | ||
process: function(src, filepath) { | ||
return '// Source for: ' + filepath + '\n' + src; | ||
} | ||
} | ||
} | ||
@@ -139,0 +198,0 @@ }, |
{ | ||
"name": "grunt-neuter", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Builds source files in the order you require.", | ||
@@ -19,3 +19,4 @@ "main": "Gruntfile.js", | ||
"dependencies": { | ||
"glob": "~3.2.1" | ||
"glob": "~3.2.3", | ||
"source-map": "~0.1.22" | ||
}, | ||
@@ -22,0 +23,0 @@ "devDependencies": { |
126
README.md
@@ -1,7 +0,6 @@ | ||
# grunt-neuter | ||
# grunt-neuter [![Build Status](https://travis-ci.org/trek/grunt-neuter.png)](https://travis-ci.org/trek/grunt-neuter) | ||
> Concatenate files in the order you `require`. | ||
_Note that this plugin has not yet been released, and only works with the latest bleeding-edge, in-development version of grunt. See the [When will I be able to use in-development feature 'X'?](https://github.com/gruntjs/grunt/wiki/Frequently-Asked-Questions) FAQ entry for more information._ | ||
## Getting Started | ||
@@ -53,2 +52,98 @@ If you haven't used [grunt][] before, be sure to check out the [Getting Started][] guide, as it explains how to create a [gruntfile][Getting Started] as well as install and use grunt plugins. Once you're familiar with that process, install this plugin with this command: | ||
## Example | ||
Given the following files: | ||
`a.js` | ||
```javascript | ||
require('b'); | ||
var myVariable = 'hello'; | ||
``` | ||
`b.js` | ||
```javascript | ||
var variableFromB = 'b'; | ||
window.availableEverywhere = true; | ||
``` | ||
Resulting output would be | ||
```javascript | ||
(function(){ | ||
var variableFromB = 'b'; | ||
window.availableEverywhere = true; | ||
})(); | ||
(function(){ | ||
var myVariable = 'hello'; | ||
})(); | ||
``` | ||
## Relative Paths | ||
Relative paths using a dot to indicate the file's current directory are valid as well: | ||
`a.js` | ||
```javascript | ||
require('dir/b'); | ||
var variableFromA = 'a'; | ||
``` | ||
`dir/b.js` | ||
```javascript | ||
require('./c'); | ||
var variableFromB = 'b'; | ||
``` | ||
`dir/c.js` | ||
```javascript | ||
var variableFromC = 'c'; | ||
``` | ||
Outputs | ||
```javascript | ||
(function(){ | ||
var variableFromC = 'c'; | ||
})(); | ||
(function(){ | ||
var variableFromB = 'b'; | ||
})(); | ||
(function(){ | ||
var variableFromA = 'a'; | ||
})(); | ||
``` | ||
Note that directory traversal using `../` is **not** supported. | ||
## Example Gruntfile Use | ||
```javascript | ||
grunt.initConfig({ | ||
neuter: { | ||
application: { | ||
src: 'tmp/application.js', | ||
dest: 'app/index.js' | ||
} | ||
} | ||
}); | ||
``` | ||
or | ||
```javascript | ||
grunt.initConfig({ | ||
neuter: { | ||
'tmp/application.js' :'app/index.js' | ||
} | ||
}); | ||
``` | ||
### Options | ||
@@ -65,2 +160,9 @@ | ||
### basePath | ||
Type: `String` | ||
Default: `""` | ||
Specifying a base path allows you to omit said portion of the filepath from your require statements. For example: when using `basePath: "lib/js/"` in your task options, `require("lib/js/file.js");` can instead be written as `require("file.js");`. Note that the trailing slash *must* be included. | ||
### filepathTransform | ||
@@ -71,6 +173,6 @@ Type: `Function` | ||
Specifying a filepath transform allows you to omit said portion of the filepath from your require statements. For example: when using `filepathTransform: function(filepath){ return 'lib/js/' + filepath; }` in your task options, require("lib/js/file.js") can instead be written as require("file.js"). | ||
Specifying a filepath transform allows you to control the path to the file that actually gets concatenated. For example, when using `filepathTransform: function(filepath){ return 'lib/js/' + filepath; }` in your task options, `require("lib/js/file.js");` can instead be written as `require("file.js");` (This achieves the same result as specifying `basePath: "lib/js/"`). When used in conjunction with the `basePath` option, the base path will be prepended to the `filepath` argument and a second argument will be provided that is the directory of the file **without** the `basePath`. | ||
### includeSourceURL | ||
Type: Boolean` | ||
Type: `Boolean` | ||
@@ -97,1 +199,15 @@ Default: `false` | ||
around in a way that isn't meaningful to neutering. | ||
### process | ||
Type: `Boolean` `Object` `Function` Default: `false` | ||
Process source files before concatenating, either as [templates](https://github.com/gruntjs/grunt/wiki/grunt.template) or with a custom function (similar to [grunt-contrib-concat](https://github.com/gruntjs/grunt-contrib-concat)). When using grunt for templating, the delimiters default to neuter's own special type (`{% %}`), which helps avoid errors when requiring libraries like [Underscore](http://underscorejs.org/) or [Lo-Dash](http://lodash.com/). | ||
* `false` - No processing will occur. | ||
* `true` - Process source files using [grunt.template.process][] without any data. | ||
* `options` object - Process source files using [grunt.template.process][], using the specified options. | ||
* `function(src, filepath)` - Process source files using the given function, called once for each file. The returned value will be used as source code. | ||
_(Default processing options are explained in the [grunt.template.process][] documentation)_ | ||
[grunt.template.process]: https://github.com/gruntjs/grunt/wiki/grunt.template#grunttemplateprocess |
@@ -11,3 +11,8 @@ /* | ||
var glob = require("glob"); | ||
var path = require("path"); | ||
var SourceNode = require('source-map').SourceNode; | ||
var SourceMapGenerator = require('source-map').SourceMapGenerator; | ||
var SourceMapConsumer = require('source-map').SourceMapConsumer; | ||
module.exports = function(grunt) { | ||
@@ -26,16 +31,29 @@ grunt.registerMultiTask('neuter', 'Concatenate files in the order you require', function() { | ||
// no need to include a .js as this will be appended for you. | ||
var requireSplitter = /^\s*(require\(\s*[\'||\"].*[\'||\"]\s*\));+\n*/m; | ||
var requireMatcher = /^require\(\s*[\'||\"](.*)[\'||\"]\s*\)/m; | ||
var requireSplitter = /^\s*(require\(\s*[\'||\"].*[\'||\"]\s*\));*\n*/m; | ||
var requireMatcher = /^require\(\s*[\'||\"](.*?)(?:\.js)?[\'||\"]\s*\)/m; | ||
// add mustache style delimiters | ||
grunt.template.addDelimiters('neuter', '{%', '%}'); | ||
var options = this.options({ | ||
basePath: '', | ||
filepathTransform: function(filepath){ return filepath; }, | ||
template: "(function() {\n\n{%= src %}\n\n})();", | ||
separator: "\n\n", | ||
includeSourceURL: false, | ||
skipFiles: [] | ||
includeSourceMap: false, | ||
skipFiles: [], | ||
process: false | ||
}); | ||
// process, but with no data | ||
if (options.process === true) { | ||
options.process = {}; | ||
} | ||
// default to using 'neuter' style templates for processing | ||
// (this avoids issues with requiring underscore or lodash) | ||
if (grunt.util.kindOf(options.process) === 'object') { | ||
options.process.delimiters = options.process.delimiters || 'neuter'; | ||
} | ||
// a poor man's Set | ||
@@ -54,3 +72,10 @@ var skipFiles = {}; | ||
files.forEach(function(filepath) { | ||
// save the file's directory without the 'basePath' prefix | ||
var dirname = (path.dirname(filepath) + '/').replace(options.basePath, ''); | ||
// create an absolute path to the file and prepend the 'basePath' | ||
var normalizePath = function(path) { | ||
return options.basePath + path.replace(/^\.\//, dirname) + '.js'; | ||
}; | ||
// once a file has been required its source will | ||
@@ -63,2 +88,9 @@ // never be written to the resulting destination file again. | ||
// process file as a template if specified | ||
if (typeof options.process === 'function') { | ||
src = options.process(src, filepath); | ||
} else if (options.process) { | ||
src = grunt.template.process(src, options.process); | ||
} | ||
// if a file should not be nuetered | ||
@@ -70,3 +102,2 @@ // it is part of the skipFiles option | ||
} else { | ||
// split the source into code sections | ||
@@ -87,3 +118,3 @@ // these will be either require(...) statements | ||
if (match) { | ||
finder(options.filepathTransform(match[1]) + '.js'); | ||
finder(options.filepathTransform(normalizePath(match[1]), dirname)); | ||
} else { | ||
@@ -101,20 +132,74 @@ out.push({filepath: filepath, src: section}); | ||
// which defaults to a functional closure. | ||
// source map support adapted from Koji NAKAMURA's grunt-concat-sourcemap | ||
// https://github.com/kozy4324/grunt-concat-sourcemap | ||
this.files.forEach(function(file) { | ||
grunt.file.expand({nonull: true}, file.src).map(finder, this); | ||
var outStr = out.map(function(section){ | ||
var templateData = { | ||
data: section, | ||
delimiters:'neuter' | ||
}; | ||
if (options.includeSourceURL) { | ||
return "eval(" + JSON.stringify(grunt.template.process(options.template, templateData) + "//@ sourceURL=" + section.filepath) +")"; | ||
} else { | ||
return grunt.template.process(options.template, templateData); | ||
} | ||
}).join(options.separator); | ||
var sourceNode = new SourceNode(null, null, null); | ||
grunt.file.write(file.dest, outStr); | ||
out = out.map(function(section) { | ||
return { | ||
src: grunt.template.process(options.template, {data: section, delimiters: 'neuter'}), | ||
filepath: section.filepath | ||
}; | ||
}); | ||
// test if template block has newlines to offset against | ||
var m, n; | ||
if (m = options.template.match(/([\S\s]*)(?={%= src %})/)) { | ||
var beforeOffset = m[0].split("\n").length - 1; | ||
} | ||
if (n = options.template.match(/{%= src %}([\S\s]*)/)) { | ||
var afterOffset = n[1].split("\n").length - 1; | ||
} | ||
for (var i = 0; i < out.length; i++) { | ||
var src = out[i].src; | ||
// split on newline and re-add | ||
var chunks = src.split('\n'); | ||
for (var j=0; j < chunks.length - 1; j++) { | ||
chunks[j] = chunks[j] + '\n'; | ||
} | ||
// Lines that map to their original file are added as SourceNodes | ||
// (with line data). Others are added as dataless chunks. | ||
for (var k=0; k < chunks.length; k++) { | ||
var line = chunks[k]; | ||
if (k > beforeOffset && k < chunks.length - afterOffset) { | ||
sourceNode.add(new SourceNode(k + 1 - beforeOffset, 0, out[i].filepath, line)); | ||
} | ||
else { | ||
sourceNode.add(line); | ||
} | ||
}; | ||
// If this isn't the last file, add the separator as a dataless | ||
// chunk. | ||
if (i != out.length - 1) { | ||
sourceNode.add(options.separator); | ||
} | ||
} | ||
if (options.includeSourceMap) { | ||
var mapFilePath = file.dest.split('/').pop() + '.map'; | ||
sourceNode.add('//@ sourceMappingURL=' + mapFilePath); | ||
} | ||
var codeMap = sourceNode.toStringWithSourceMap({ | ||
file: file.dest, | ||
sourceRoot: options.sourceRoot | ||
}); | ||
grunt.file.write(file.dest, codeMap.code); | ||
if (options.includeSourceMap) { | ||
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(codeMap.map.toJSON())); | ||
var newSourceMap = generator.toJSON(); | ||
newSourceMap.file = path.basename(newSourceMap.file); | ||
grunt.file.write(file.dest + ".map", JSON.stringify(newSourceMap, null, ' ')); | ||
} | ||
}); | ||
}); | ||
}; |
@@ -22,26 +22,34 @@ 'use strict'; | ||
}, | ||
custom_separator_options: function(test){ | ||
simple_basepath_options: function(test) { | ||
var actual = grunt.file.read('tmp/custom_separator_options'); | ||
var expected = grunt.file.read('test/expected/custom_separator_options'); | ||
test.equal(actual, expected, 'statment separator can be customized'); | ||
var actual = grunt.file.read('tmp/simple_basepath_options'); | ||
var expected = grunt.file.read('test/expected/simple_require_filepath_transforms'); | ||
test.equal(actual, expected, 'files are correctly combined'); | ||
test.done(); | ||
}, | ||
custom_source_url_inclusion_option: function(test){ | ||
relative_require_statements: function(test) { | ||
var actual = grunt.file.read('tmp/custom_source_url_inclusion_option'); | ||
var expected = grunt.file.read('test/expected/custom_source_url_inclusion_option'); | ||
test.equal(actual, expected, '@ sourceURL can be included for debugging'); | ||
var actual = grunt.file.read('tmp/relative_require_statements'); | ||
var expected = grunt.file.read('test/expected/relative_require_statements'); | ||
test.equal(actual, expected, 'files are correctly combined'); | ||
test.done(); | ||
}, | ||
requires_are_only_included_once: function(test){ | ||
relative_requires_with_basepath: function(test) { | ||
var actual = grunt.file.read('tmp/custom_source_url_inclusion_option'); | ||
var expected = grunt.file.read('test/expected/custom_source_url_inclusion_option'); | ||
test.equal(actual, expected, '@ sourceURL can be included for debugging'); | ||
var actual = grunt.file.read('tmp/relative_requires_with_basepath'); | ||
var expected = grunt.file.read('test/expected/relative_requires_with_basepath'); | ||
test.equal(actual, expected, 'files are correctly combined'); | ||
test.done(); | ||
}, | ||
custom_separator_options: function(test){ | ||
var actual = grunt.file.read('tmp/custom_separator_options'); | ||
var expected = grunt.file.read('test/expected/custom_separator_options'); | ||
test.equal(actual, expected, 'statment separator can be customized'); | ||
test.done(); | ||
}, | ||
duplicate_require_statements: function(test){ | ||
@@ -88,5 +96,5 @@ | ||
}, | ||
do_not_replace_requires_in_statements: function(test){ | ||
var actual = grunt.file.read('tmp/do_not_replace_requires_in_statements'); | ||
@@ -99,3 +107,3 @@ var expected = grunt.file.read('test/expected/do_not_replace_requires_in_statements'); | ||
comment_out_require: function(test){ | ||
var actual = grunt.file.read('tmp/comment_out_require'); | ||
@@ -122,4 +130,45 @@ var expected = grunt.file.read('test/expected/comment_out_require'); | ||
test.done(); | ||
}, | ||
optional_semicolons: function(test){ | ||
var actual = grunt.file.read('tmp/optional_semicolons'); | ||
var expected = grunt.file.read('test/expected/optional_semicolons'); | ||
test.equal(actual, expected, 'semicolons are optional as long as there\'s a newline'); | ||
test.done(); | ||
}, | ||
optional_dotjs: function(test){ | ||
var actual = grunt.file.read('tmp/optional_dotjs'); | ||
var expected = grunt.file.read('test/expected/optional_dotjs'); | ||
test.equal(actual, expected, 'the .js extension is optional'); | ||
test.done(); | ||
}, | ||
source_maps: function(test) { | ||
var actualJs = grunt.file.read('tmp/source_maps'); | ||
var actualMap = grunt.file.read('tmp/source_maps.map'); | ||
var expectedJs = grunt.file.read('test/expected/source_maps'); | ||
var expectedMap = grunt.file.read('test/expected/source_maps.map'); | ||
test.equal(actualJs, expectedJs, 'the js includes sourceMap output'); | ||
test.equal(actualMap, expectedMap, 'the map is generated'); | ||
test.done(); | ||
}, | ||
process_as_template: function(test) { | ||
var actual = grunt.file.read('tmp/process_as_template'); | ||
var expected = grunt.file.read('test/expected/process_as_template'); | ||
test.equal(actual, expected, 'files are processed as templates'); | ||
test.done(); | ||
}, | ||
process_with_function: function(test) { | ||
var actual = grunt.file.read('tmp/process_with_function'); | ||
var expected = grunt.file.read('test/expected/process_with_function'); | ||
test.equal(actual, expected, 'files are processed with a function'); | ||
test.done(); | ||
} | ||
}; |
32125
59
549
210
2
+ Addedsource-map@~0.1.22
+ Addedamdefine@1.0.1(transitive)
+ Addedsource-map@0.1.43(transitive)
Updatedglob@~3.2.3