Comparing version 0.9.0 to 0.10.0
# master | ||
# 0.10.0 | ||
* Move process.exit listener out of builder into server | ||
* Change `Builder::build()` method to return a `{ directory, graph }` hash | ||
instead of only the directory, where `graph` contains the output directories | ||
and timings for each tree | ||
* Avoid keeping file streams open in server, to fix EBUSY issues on Windows | ||
# 0.9.0 | ||
* `Brocfile.js` now exports a tree, not a function | ||
* `Brocfile.js` now exports a tree, not a function ([sample diff](https://gist.github.com/joliss/15630762fa0f43976418)) | ||
@@ -7,0 +15,0 @@ # 0.8.0 |
@@ -9,5 +9,5 @@ var path = require('path') | ||
this.tree = tree | ||
this.buildTime = null; | ||
this.treesRead = [] // last build | ||
this.allTreesRead = [] // across all builds | ||
process.addListener('exit', this.cleanup.bind(this)) | ||
} | ||
@@ -19,11 +19,12 @@ | ||
var newTreesRead = [] | ||
var dirsCache = [] | ||
var nodeCache = [] | ||
var startTime = Date.now() | ||
return Promise.resolve() | ||
.then(function () { | ||
return getReadTreeFn(null)(self.tree) // call self.tree.read() | ||
return readAndReturnNodeFor(self.tree) // call self.tree.read() | ||
}) | ||
.then(function (dir) { | ||
.then(function (node) { | ||
self.treesRead = newTreesRead | ||
return dir | ||
return { directory: node.directory, graph: node } | ||
}, function (err) { | ||
@@ -51,28 +52,83 @@ // self.treesRead is used by the watcher. Do not stop watching | ||
}) | ||
.finally(function() { | ||
self.buildTime = Date.now() - startTime | ||
}) | ||
function getReadTreeFn (tree) { | ||
function readTree (subtree) { | ||
// To do: Complain about parallel execution | ||
// To do: Timing | ||
var index = newTreesRead.indexOf(subtree) | ||
if (index === -1) { | ||
newTreesRead.push(subtree) | ||
dirsCache.push(null) | ||
index = dirsCache.length - 1 | ||
var subtreeDir = typeof subtree === 'string' ? | ||
subtree : | ||
subtree.read(getReadTreeFn(subtree)) | ||
return Promise.resolve(subtreeDir) | ||
.then(function (dir) { | ||
if (dir == null) throw new Error(subtree + ': .read must return a directory') | ||
dirsCache[index] = dir | ||
return dir | ||
}) | ||
} else { | ||
// Do not re-run .read; just return the cached directory path | ||
if (dirsCache[index] == null) throw new Error('Tree cycle detected') | ||
return Promise.resolve(dirsCache[index]) | ||
// Read the `tree` and return its node, which in particular contains the | ||
// tree's output directory (node.directory) | ||
function readAndReturnNodeFor (tree) { | ||
// To do: Complain about parallel execution | ||
// To do: Timing | ||
var index = newTreesRead.indexOf(tree) | ||
if (index !== -1) { | ||
// Return node from cache to deduplicate `.read` | ||
if (nodeCache[index].directory == null) { | ||
// node.directory gets set at the very end, so we have found an as-yet | ||
// incomplete node. This can happen if there is a cycle. | ||
throw new Error('Tree cycle detected') | ||
} | ||
return Promise.resolve(nodeCache[index]) | ||
} | ||
return readTree | ||
var node = { | ||
tree: tree, | ||
subtrees: [], | ||
selfTime: 0, | ||
totalTime: 0 | ||
} | ||
newTreesRead.push(tree) | ||
nodeCache.push(node) | ||
var treeDirPromise | ||
if (typeof tree === 'string') { | ||
treeDirPromise = Promise.resolve(tree) | ||
} else { | ||
// To do: Throw nice error message on null/undefined/non-tree object | ||
var now = process.hrtime() | ||
var totalStartTime = now | ||
var selfStartTime = now | ||
var readTreeRunning = false | ||
treeDirPromise = Promise.resolve() | ||
.then(function () { | ||
return tree.read(function readTree (subtree) { | ||
if (readTreeRunning) { | ||
throw new Error('Parallel readTree call detected; read trees in sequence, e.g. using https://github.com/joliss/promise-map-series') | ||
} | ||
readTreeRunning = true | ||
// Pause self timer | ||
var now = process.hrtime() | ||
node.selfTime += (now[0] - selfStartTime[0]) * 1e9 + (now[1] - selfStartTime[1]) | ||
selfStartTime = null | ||
return Promise.resolve() | ||
.then(function () { | ||
return readAndReturnNodeFor(subtree) // recurse | ||
}) | ||
.then(function (childNode) { | ||
node.subtrees.push(childNode) | ||
return childNode.directory | ||
}) | ||
.finally(function () { | ||
readTreeRunning = false | ||
// Resume self timer | ||
selfStartTime = process.hrtime() | ||
}) | ||
}) | ||
}) | ||
.then(function (dir) { | ||
if (readTreeRunning) { | ||
throw new Error('.read returned before readTree finished') | ||
} | ||
var now = process.hrtime() | ||
node.selfTime += (now[0] - selfStartTime[0]) * 1e9 + (now[1] - selfStartTime[1]) | ||
node.totalTime += (now[0] - totalStartTime[0]) * 1e9 + (now[1] - totalStartTime[1]) | ||
return dir | ||
}) | ||
} | ||
return treeDirPromise | ||
.then(function (treeDir) { | ||
if (treeDir == null) throw new Error(tree + ': .read must return a directory') | ||
node.directory = treeDir | ||
return node | ||
}) | ||
} | ||
@@ -79,0 +135,0 @@ } |
@@ -33,3 +33,3 @@ var fs = require('fs') | ||
builder.build() | ||
.then(function (dir) { | ||
.then(function (hash) { | ||
try { | ||
@@ -42,2 +42,3 @@ fs.mkdirSync(outputDir) | ||
} | ||
var dir = hash.directory | ||
return RSVP.denodeify(ncp)(dir, outputDir, { | ||
@@ -48,2 +49,5 @@ clobber: false, | ||
}) | ||
.finally(function () { | ||
builder.cleanup() | ||
}) | ||
.then(function () { | ||
@@ -54,2 +58,5 @@ process.exit(0) | ||
// Should show file and line/col if present | ||
if (err.file) { | ||
console.error('File: ' + err.file) | ||
} | ||
console.error(err.stack) | ||
@@ -56,0 +63,0 @@ console.error('\nBuild failed') |
@@ -6,3 +6,3 @@ var path = require('path') | ||
var url = require('url') | ||
var send = require('send') | ||
var mime = require('mime') | ||
@@ -13,13 +13,62 @@ var errorTemplate = handlebars.compile(fs.readFileSync(path.resolve(__dirname, '../templates/error.html')).toString()) | ||
return function broccoliMiddleware(request, response, next) { | ||
watcher.then(function(directory) { | ||
send(request, url.parse(request.url).pathname) | ||
.root(directory) | ||
.on('error', function(err) { | ||
if (404 === err.status) { | ||
next() | ||
} else { | ||
next(err) | ||
} | ||
}) | ||
.pipe(response) | ||
watcher.then(function(hash) { | ||
var directory = path.normalize(hash.directory) | ||
var pathname = url.parse(request.url).pathname | ||
var filename = path.normalize(path.join(directory, decodeURIComponent(pathname))) | ||
var stat, lastModified, type, charset, buffer | ||
// this middleware is for development use only | ||
// contains null byte or escapes directory | ||
if (filename.indexOf('\0') !== -1 || filename.indexOf(directory) !== 0) { | ||
response.writeHead(400) | ||
response.end() | ||
return | ||
} | ||
// handle document index | ||
if (filename[filename.length - 1] === '/') { | ||
filename = path.join(filename, 'index.html') | ||
} | ||
try { | ||
stat = fs.statSync(filename) | ||
} catch (e) { | ||
// 404 | ||
next() | ||
return | ||
} | ||
// if directory redirect with trailing slash | ||
if (stat.isDirectory()) { | ||
response.setHeader('Location', pathname + '/') | ||
response.writeHead(301) | ||
response.end() | ||
return | ||
} | ||
lastModified = stat.mtime.toUTCString() | ||
response.setHeader('Last-Modified', lastModified) | ||
// nginx style treat last-modified as a tag since browsers echo it back | ||
if (request.headers['if-modified-since'] === lastModified) { | ||
response.writeHead(304) | ||
response.end() | ||
return | ||
} | ||
type = mime.lookup(filename) | ||
charset = mime.charsets.lookup(type) | ||
if (charset) { | ||
type += '; charset=' + charset | ||
} | ||
// we don't want stale build files | ||
response.setHeader('Cache-Control', 'private, max-age=0, must-revalidate') | ||
response.setHeader('Content-Length', stat.size) | ||
response.setHeader('Content-Type', type) | ||
// read file sync so we don't hold open the file creating a race with | ||
// the builder (Windows does not allow us to delete while the file is open). | ||
buffer = fs.readFileSync(filename) | ||
response.writeHead(200) | ||
response.end(buffer) | ||
}, function(buildError) { | ||
@@ -26,0 +75,0 @@ var context = { |
@@ -13,3 +13,3 @@ var Watcher = require('./watcher') | ||
var watcher = new Watcher(builder) | ||
var watcher = options.watcher || new Watcher(builder) | ||
@@ -20,2 +20,6 @@ var app = connect().use(middleware(watcher)) | ||
process.addListener('exit', function () { | ||
builder.cleanup() | ||
}) | ||
// We register these so the 'exit' handler removing temp dirs is called | ||
@@ -44,3 +48,3 @@ process.on('SIGINT', function () { | ||
watcher.on('change', function(dir) { | ||
console.log('Built') | ||
console.log('Built - ' + builder.buildTime) | ||
liveReload() | ||
@@ -52,2 +56,5 @@ }) | ||
// Should also show file and line/col if present; see cli.js | ||
if (err.file) { | ||
console.log('File: ' + err.file) | ||
} | ||
console.log(err.stack) | ||
@@ -54,0 +61,0 @@ console.log('') |
@@ -7,4 +7,5 @@ var EventEmitter = require('events').EventEmitter | ||
module.exports = Watcher | ||
function Watcher(builder) { | ||
function Watcher(builder, options) { | ||
this.builder = builder | ||
this.options = options || {} | ||
this.check() | ||
@@ -18,2 +19,3 @@ } | ||
try { | ||
var interval = this.options.interval || 100 | ||
var newStatsHash = this.builder.treesRead.map(function (tree) { | ||
@@ -25,3 +27,4 @@ return typeof tree === 'string' ? helpers.hashTree(tree) : '' | ||
this.current = this.builder.build() | ||
this.current.then(function(directory) { | ||
this.current.then(function(hash) { | ||
var directory = hash.directory | ||
this.emit('change', directory) | ||
@@ -34,3 +37,3 @@ return directory | ||
} else { | ||
setTimeout(this.check.bind(this), 100) | ||
setTimeout(this.check.bind(this), interval) | ||
} | ||
@@ -37,0 +40,0 @@ } catch (err) { |
{ | ||
"name": "broccoli", | ||
"description": "Fast client-side asset builder", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"author": "Jo Liss <joliss42@gmail.com>", | ||
@@ -10,21 +10,22 @@ "main": "lib/index.js", | ||
"type": "git", | ||
"url": "https://github.com/joliss/broccoli" | ||
"url": "https://github.com/broccolijs/broccoli" | ||
}, | ||
"dependencies": { | ||
"handlebars": "^1.3.0", | ||
"broccoli-kitchen-sink-helpers": "^0.2.0", | ||
"commander": "^2.0.0", | ||
"connect": "~2.14.1", | ||
"findup-sync": "^0.1.2", | ||
"tiny-lr": "0.0.5", | ||
"rsvp": "^3.0.3", | ||
"handlebars": "^1.3.0", | ||
"mime": "^1.2.11", | ||
"ncp": "^0.5.0", | ||
"connect": "~2.14.1", | ||
"send": "~0.2.0", | ||
"broccoli-kitchen-sink-helpers": "^0.2.0" | ||
"rsvp": "^3.0.6", | ||
"tiny-lr": "0.0.5" | ||
}, | ||
"devDependencies": { | ||
"jshint": "~2.3.0" | ||
"jshint": "~2.3.0", | ||
"tap": "^0.4.8" | ||
}, | ||
"scripts": { | ||
"test": "jshint lib/*.js" | ||
"test": "jshint lib/*.js test/*.js && tap --stderr --timeout 2 ./test/*_test.js" | ||
} | ||
} |
@@ -19,8 +19,6 @@ # Broccoli | ||
Windows is not yet supported. | ||
## Installation | ||
```bash | ||
npm install --save broccoli | ||
npm install --save-dev broccoli | ||
npm install --global broccoli-cli | ||
@@ -37,40 +35,59 @@ ``` | ||
A `Brocfile.js` file in the project root contains the build specification. It | ||
has the following format: | ||
should export a tree which may simply be the directory path (as a string). To | ||
build more advanced output trees you may want to use some of the plugins listed | ||
below. | ||
The following would export the `app/` subdirectory as a tree: | ||
```js | ||
module.exports = function (broccoli) { | ||
}; | ||
module.exports = 'app' | ||
``` | ||
The function must return a tree object, which is typically created using a | ||
Broccoli plugin. | ||
Alternatively, the following would export the `app/` subdirectory as `appkit/`: | ||
```js | ||
var pickFiles = require('broccoli-static-compiler') | ||
module.exports = pickFiles('app', { | ||
srcDir: '/', | ||
destDir: 'appkit' | ||
}) | ||
``` | ||
## Plugins | ||
* [broccoli-autoprefixer](https://github.com/sindresorhus/broccoli-autoprefixer) | ||
* [broccoli-bake-handlebars](https://github.com/thomasboyt/broccoli-bake-handlebars) | ||
* [broccoli-bower](https://github.com/joliss/broccoli-bower) | ||
* [broccoli-closure-compiler](https://github.com/sindresorhus/broccoli-closure-compiler) | ||
* [broccoli-coffee](https://github.com/joliss/broccoli-coffee) | ||
* [broccoli-template](https://github.com/joliss/broccoli-template) | ||
* [broccoli-static-compiler](https://github.com/joliss/broccoli-static-compiler) | ||
* [broccoli-uglify-js](https://github.com/joliss/broccoli-uglify-js) | ||
* [broccoli-csso](https://github.com/sindresorhus/broccoli-csso) | ||
* [broccoli-defeatureify](https://github.com/sindresorhus/broccoli-defeatureify) | ||
* [broccoli-dust](https://github.com/sindresorhus/broccoli-dust) | ||
* [broccoli-ember-script](https://github.com/aradabaugh/broccoli-ember-script) | ||
* [broccoli-es6-concatenator](https://github.com/joliss/broccoli-es6-concatenator) | ||
* [broccoli-es6-module-filter](https://github.com/rpflorence/broccoli-es6-module-filter) | ||
* [broccoli-sass](https://github.com/joliss/broccoli-sass) | ||
* [broccoli-swig](https://github.com/shanielh/broccoli-swig) | ||
* [broccoli-replace](https://github.com/outaTiME/broccoli-replace) | ||
* [broccoli-autoprefixer](https://github.com/sindresorhus/broccoli-autoprefixer) | ||
* [broccoli-svgo](https://github.com/sindresorhus/broccoli-svgo) | ||
* [broccoli-csso](https://github.com/sindresorhus/broccoli-csso) | ||
* [broccoli-es6-transpiler](https://github.com/sindresorhus/broccoli-es6-transpiler) | ||
* [broccoli-file-creator](https://github.com/rjackson/broccoli-file-creator) | ||
* [broccoli-file-mover](https://github.com/rjackson/broccoli-file-mover) | ||
* [broccoli-file-remover](https://github.com/rjackson/broccoli-file-remover) | ||
* [broccoli-fixturify](https://github.com/rjackson/broccoli-fixturify) | ||
* [broccoli-htmlmin](https://github.com/sindresorhus/broccoli-htmlmin) | ||
* [broccoli-imagemin](https://github.com/Xulai/broccoli-imagemin) | ||
* [broccoli-jade](https://github.com/sindresorhus/broccoli-jade) | ||
* [broccoli-uncss](https://github.com/sindresorhus/broccoli-uncss) | ||
* [broccoli-jstransform](https://github.com/aexmachina/broccoli-jstransform) | ||
* [broccoli-nunjucks](https://github.com/sindresorhus/broccoli-nunjucks) | ||
* [broccoli-regenerator](https://github.com/sindresorhus/broccoli-regenerator) | ||
* [broccoli-replace](https://github.com/outaTiME/broccoli-replace) | ||
* [broccoli-sass](https://github.com/joliss/broccoli-sass) | ||
* [broccoli-static-compiler](https://github.com/joliss/broccoli-static-compiler) | ||
* [broccoli-strip-debug](https://github.com/sindresorhus/broccoli-strip-debug) | ||
* [broccoli-strip-json-comments](https://github.com/sindresorhus/broccoli-strip-json-comments) | ||
* [broccoli-svgo](https://github.com/sindresorhus/broccoli-svgo) | ||
* [broccoli-sweetjs](https://github.com/sindresorhus/broccoli-sweetjs) | ||
* [broccoli-swig](https://github.com/shanielh/broccoli-swig) | ||
* [broccoli-template](https://github.com/joliss/broccoli-template) | ||
* [broccoli-traceur](https://github.com/sindresorhus/broccoli-traceur) | ||
* [broccoli-sweetjs](https://github.com/sindresorhus/broccoli-sweetjs) | ||
* [broccoli-closure-compiler](https://github.com/sindresorhus/broccoli-closure-compiler) | ||
* [broccoli-regenerator](https://github.com/sindresorhus/broccoli-regenerator) | ||
* [broccoli-defeatureify](https://github.com/sindresorhus/broccoli-defeatureify) | ||
* [broccoli-nunjucks](https://github.com/sindresorhus/broccoli-nunjucks) | ||
* [broccoli-dust](https://github.com/sindresorhus/broccoli-dust) | ||
* [broccoli-strip-json-comments](https://github.com/sindresorhus/broccoli-strip-json-comments) | ||
* [broccoli-es6-transpiler](https://github.com/sindresorhus/broccoli-es6-transpiler) | ||
* [broccoli-ember-script](https://github.com/aradabaugh/broccoli-ember-script) | ||
* [broccoli-uglify-js](https://github.com/joliss/broccoli-uglify-js) | ||
* [broccoli-uncss](https://github.com/sindresorhus/broccoli-uncss) | ||
@@ -80,4 +97,5 @@ More plugins may be found under the [broccoli-plugin | ||
### Plugging Broccoli Into Other Tools | ||
### Running Broccoli, Directly or Through Other Tools | ||
* [broccoli-timepiece](https://github.com/rjackson/broccoli-timepiece) | ||
* [grunt-broccoli](https://github.com/quandl/grunt-broccoli) | ||
@@ -90,5 +108,5 @@ * [grunt-broccoli-build](https://github.com/ericf/grunt-broccoli-build) | ||
* [broccoli-caching-writer](https://github.com/rjackson/broccoli-caching-writer) | ||
* [broccoli-filter](https://github.com/joliss/broccoli-filter) | ||
* [broccoli-transform](https://github.com/joliss/broccoli-transform) | ||
* [broccoli-env](https://github.com/joliss/broccoli-env) | ||
* [broccoli-writer](https://github.com/joliss/broccoli-writer) | ||
* [node-quick-temp](https://github.com/joliss/node-quick-temp) | ||
@@ -95,0 +113,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45166
18
550
166
2
+ Addedmime@^1.2.11
+ Addedmime@1.6.0(transitive)
- Removedsend@~0.2.0
- Removedsend@0.2.0(transitive)
Updatedrsvp@^3.0.6