diff --git a/README.md b/README.md index f48b2db..be78ec5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ There are three properties that are not passed directly: * [transform](#transforms) * [plugin](#plugins) -* [prebundle](#additional-bundle-configuration) +* [configure](#additional-bundle-configuration) #### Transforms @@ -91,12 +91,14 @@ The [browserify plugin](https://github.com/substack/node-browserify#bpluginplugi #### Additional Bundle Configuration -You may perform additional configuration in a function that you pass as the `prebundle` option and that receives the bundle as an argument. This is useful when you need to set up things like [externals](https://github.com/substack/node-browserify#external-requires): +You may perform additional configuration in a function passed as the `configure` option and that receives the browserify instance as an argument. A custom `prebundle` event is emitted on the bundle right before a bundling operation takes place. This is useful when setting up things like [externals](https://github.com/substack/node-browserify#external-requires): ```javascript browserify: { - prebundle: function(bundle) { - bundle.external('foobar'); + configure: function(bundle) { + bundle.on('prebundle', function() { + bundle.external('foobar'); + }); } } ``` @@ -144,9 +146,10 @@ module.exports = function(karma) { browserify: { debug: true, transform: [ 'brfs' ], - prebundle: function(bundle) { - // additional configuration, e.g. externals - bundle.external('foobar'); + configure: function(bundle) { + bundle.on('prebundle', function() { + bundle.external('foobar'); + }); } } }); diff --git a/lib/bro.js b/lib/bro.js index dfb5777..28fea9f 100644 --- a/lib/bro.js +++ b/lib/bro.js @@ -110,6 +110,8 @@ function Bro(bundleFile) { // add bundle file to the list of files defined in the // configuration. be smart by doing so. addBundleFile(bundleFile, config); + + return b; } framework.$inject = [ 'emitter', 'config', 'logger' ]; @@ -132,11 +134,17 @@ function Bro(bundleFile) { } }); - var browserifyOptions = _.extend({}, watchify.args, _.omit(bopts, [ - 'transform', 'plugin', 'prebundle' + var browserifyOptions = _.extend({ basedir: config.basePath }, watchify.args, _.omit(bopts, [ + 'transform', 'plugin', 'configure', 'bundleDelay' ])); + if ('prebundle' in browserifyOptions) { + log.warn('The prebundle hook got removed in favor of configure'); + } + + var w = watchify(browserify(browserifyOptions)); + w.setMaxListeners(Infinity); _.forEach(bopts.plugin, function(p) { // ensure we can pass plugin options as @@ -156,9 +164,9 @@ function Bro(bundleFile) { w.transform.apply(w, t); }); - // test if we have a prebundle function - if (bopts.prebundle && typeof bopts.prebundle === 'function') { - bopts.prebundle(w); + // test if we have a configure function + if (bopts.configure && typeof bopts.configure === 'function') { + bopts.configure(w); } // register rebuild bundle on change @@ -167,16 +175,14 @@ function Bro(bundleFile) { w.on('update', function(updated) { - var rebundleRequired = _.any(updated, function(f) { - return files.indexOf(f) === -1; - }); + // we perform an update, karma will trigger one, too + // because the bundling is deferred only one change will + // be triggered. Anything else is the result of a + // raise condition or a problem of watchify firing file + // changes to late - if (rebundleRequired) { - log.debug('files changed'); - deferredBundle(); - } else { - log.debug('skipping update'); - } + log.debug('files changed'); + deferredBundle(); }); } @@ -184,9 +190,6 @@ function Bro(bundleFile) { log.info(msg); }); - // files contained in bundle - var files = [ ]; - // update bundle file w.on('bundled', function(err, content) { @@ -207,76 +210,57 @@ function Bro(bundleFile) { rebuild(); } - - var MISSING_MESSAGE = /^Cannot find module '([^']+)'/; - var rebuild = _.debounce(function rebuild() { - // check if we already bundled once and - // restore transforms / plug-ins accordingly - // after resetting the bundle - if (w._bundled) { + log.debug('resetting bundle'); + var recorded = w._recorded; w.reset(); - log.debug('resetting bundle'); - - recorded.forEach(function (tr) { - if (tr.transform) { - w.pipeline.write(tr); + recorded.forEach(function(e) { + // we remove missing files on the fly + // to cope with bundle internals missing + if (e.file && !fs.existsSync(e.file)) { + log.debug('removing missing file', path.relative(config.basePath, e.file)); + } else { + w.pipeline.write(e); } }); } - log.debug('bundling'); + w.emit('prebundle', w); - files.forEach(function(f) { - w.require(f, { expose: path.relative(config.basePath, f) }); - }); + log.debug('bundling'); w.bundle(function(err, content) { if (err) { log.error('bundle error'); log.error(String(err)); - - // try to recover from removed test case - // rebuild, if successful - var match = MISSING_MESSAGE.exec(err.message); - if (match) { - var idx = files.indexOf(match[1]); - - if (idx !== -1) { - log.debug('removing %s from bundle', match[1]); - files.splice(idx, 1); - - log.debug('attempting rebuild'); - return rebuild(); - } - } } w.emit('bundled', err, content); }); - }, 500); + }, bopts.bundleDelay || 700); w.bundleFile = function(file, done) { var absolutePath = file.path, - relativePath = path.relative(config.basePath, absolutePath); + relativePath = path.relative(config.basePath, absolutePath), + cache = w._options.cache; - if (files.indexOf(absolutePath) === -1) { + // add file + log.debug('updating %s in bundle', relativePath); - // add file - log.debug('adding %s to bundle', relativePath); - - files.push(absolutePath); - } + // add the file during next prebundle step + w.once('prebundle', function() { + w.require('./' + relativePath, { expose: absolutePath }); + }); deferredBundle(function(err) { - done(err, 'require("' + escape(relativePath) + '");'); + done(err, 'require("' + absolutePath + '");'); }); }; @@ -284,9 +268,7 @@ function Bro(bundleFile) { /** * Wait for the bundle creation to have stabilized (no more additions) and invoke a callback. * - * @param {Function} cb - * @param {Number} delay - * @param {Number} timeout + * @param {Function} [callback] invoked with (err, content) */ w.deferredBundle = deferredBundle; diff --git a/package.json b/package.json index 305da16..1172550 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "bugs": "https://github.com/Nikku/karma-browserify/issues", "dependencies": { - "browserify": "~7.0.2", + "browserify": "Nikku/node-browserify.git#local-fix", "convert-source-map": "~0.3.3", "js-string-escape": "^1.0.0", "lodash": "~2.4.1", @@ -33,6 +33,7 @@ }, "devDependencies": { "brfs": "^1.2.0", + "browser-unpack": "~1.0.0", "chai": "^1.9.1", "chai-spies": "^0.5.1", "grunt": "~0.4.4", @@ -41,9 +42,11 @@ "grunt-mocha-test": "^0.11.0", "grunt-release": "~0.7.0", "karma": "^0.12.19", + "karma-jasmine": "^0.1.5", + "karma-phantomjs-launcher": "^0.1.4", "load-grunt-tasks": "~0.4.0", - "tsify": "^0.6.1", - "browser-unpack": "~1.0.0" + "touch": "0.0.3", + "tsify": "^0.6.1" }, "peerDependencies": { "karma": ">=0.10" diff --git a/test/fixtures/prebundle.js b/test/fixtures/configure.js similarity index 100% rename from test/fixtures/prebundle.js rename to test/fixtures/configure.js diff --git a/test/integration/README.md b/test/integration/README.md index 4387c97..6954094 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -23,4 +23,11 @@ karma start ``` karma start --auto-watch --no-single-run --browsers=Chrome +``` + + +## Recreate prebundled common module + +``` +npm install browserify && node_modules/.bin/browserify -r ./lib/common.js -o prebundled/common.js ``` \ No newline at end of file diff --git a/test/integration/auto-watch.conf.js b/test/integration/auto-watch.conf.js new file mode 100644 index 0000000..8487443 --- /dev/null +++ b/test/integration/auto-watch.conf.js @@ -0,0 +1,12 @@ +'use strict'; + +var base = require('./single-run.conf'); + +module.exports = function(karma) { + base(karma); + + karma.set({ + singleRun: false, + autoWatch: true + }); +}; \ No newline at end of file diff --git a/test/integration/karma.conf.js b/test/integration/karma.conf.js index 6c5369c..a8a81bf 100644 --- a/test/integration/karma.conf.js +++ b/test/integration/karma.conf.js @@ -3,12 +3,17 @@ module.exports = function(karma) { karma.set({ + plugins: ['karma-*', require('../..')], + frameworks: [ 'jasmine', 'browserify' ], files: [ // external (non-browserified) library that exposes a global 'vendor/external.js', + // external (browserified) bundle + 'prebundled/common.js', + // source file, accidently included // (there is usually no reason to do this) 'lib/a.js', @@ -25,7 +30,7 @@ module.exports = function(karma) { preprocessors: { 'lib/a.js': [ 'browserify' ], 'test/*Spec.js': [ 'browserify' ], - 'test/helper.js': [ 'browserify' ], + 'test/helper.js': [ 'browserify' ] }, browsers: [ 'PhantomJS' ], @@ -38,7 +43,15 @@ module.exports = function(karma) { // browserify configuration browserify: { debug: true, - transform: [ 'brfs' ] + transform: [ 'brfs' ], + + // configure browserify + configure: function(b) { + + b.on('prebundle', function() { + b.external('lib/common.js'); + }); + } } }); }; diff --git a/test/integration/lib/a.js b/test/integration/lib/a.js index b66d157..cbb90ab 100644 --- a/test/integration/lib/a.js +++ b/test/integration/lib/a.js @@ -1 +1 @@ -module.exports = require('./c'); \ No newline at end of file +module.exports = require('./common').a; \ No newline at end of file diff --git a/test/integration/lib/c.js b/test/integration/lib/c.js deleted file mode 100644 index 6d414a1..0000000 --- a/test/integration/lib/c.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'A'; \ No newline at end of file diff --git a/test/integration/lib/common.js b/test/integration/lib/common.js new file mode 100644 index 0000000..288e4de --- /dev/null +++ b/test/integration/lib/common.js @@ -0,0 +1,6 @@ +/* + * This file gets prebundled into a dist/common.js bundle. + * + * It is included before the browserified test files. + */ +module.exports.a = 'A'; \ No newline at end of file diff --git a/test/integration/package.json b/test/integration/package.json deleted file mode 100644 index 982f776..0000000 --- a/test/integration/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "karma-browserify-integration", - "version": "0.0.0", - "description": "An integration test project for karma-browserify", - "main": "lib/a.js", - "scripts": { - "test": "karma start" - }, - "keywords": [ - "karma-browserify" - ], - "author": "Nico Rehwaldt ", - "license": "MIT", - "devDependencies": { - "brfs": "^1.2.0", - "karma": "^0.12.0", - "karma-browserify": "../../", - "karma-chrome-launcher": "^0.1.4", - "karma-jasmine": "^0.1.5", - "karma-phantomjs-launcher": "^0.1.4" - } -} diff --git a/test/integration/prebundled/common.js b/test/integration/prebundled/common.js new file mode 100644 index 0000000..c277894 --- /dev/null +++ b/test/integration/prebundled/common.js @@ -0,0 +1,8 @@ +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o'"); done(); }); }); - // Rebundle + + // rebundle plugin.preprocess(bundleFile, [ testFile ], function() {}); }); } } }); - // Initial bundle + + // initial bundle plugin.preprocess(bundleFile, [ testFile ], function() {}); }); @@ -645,7 +661,7 @@ describe('bro', function() { browserify: { plugin: [ ['tsify', { removeComments: true } ] ], // Hook into bundler/pipeline events for success/error - prebundle: function(bundle) { + configure: function(bundle) { // After first bundle bundle.once('bundled', function (err) { // Fail if there was an error