Comparing version
# 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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
45166
28.41%18
5.88%550
76.28%166
12.16%2
100%4
33.33%+ Added
+ Added
- Removed
- Removed
Updated