├── .gitignore
├── .travis.yml
├── test
├── dedupe
│ ├── x.js
│ ├── a.js
│ ├── b.js
│ ├── d.js
│ ├── e.js
│ └── c.js
├── deps
│ ├── w.js
│ ├── z.js
│ ├── y.js
│ └── x.js
├── files
│ ├── a.js
│ ├── z.js
│ ├── w.js
│ ├── x.js
│ ├── t.js
│ └── y.js
├── rel
│ ├── w.js
│ ├── z.js
│ ├── y.js
│ └── x.js
├── common-deps.js
├── exposed-deps.js
├── plugin-dedupe.js
├── rel.js
├── lift.js
├── deps.js
├── plugin.js
├── outputs.js
└── files.js
├── example
├── files
│ ├── w.js
│ ├── z.js
│ ├── y.js
│ └── x.js
├── minify.sh
├── api.js
└── stream.js
├── bin
├── usage.txt
└── cmd.js
├── LICENSE
├── package.json
├── index.js
└── readme.markdown
/.gitignore:
--------------------------------------------------------------------------------
1 | example/bundle
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
--------------------------------------------------------------------------------
/test/dedupe/x.js:
--------------------------------------------------------------------------------
1 | module.exports = function(n) { return n * 5; };
2 |
--------------------------------------------------------------------------------
/test/deps/w.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 50 }
2 |
--------------------------------------------------------------------------------
/test/deps/z.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 111 }
2 |
--------------------------------------------------------------------------------
/test/files/a.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n + 10 }
2 |
--------------------------------------------------------------------------------
/test/files/z.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 111 }
2 |
--------------------------------------------------------------------------------
/test/rel/w.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 50 }
2 |
--------------------------------------------------------------------------------
/test/rel/z.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 111 }
2 |
--------------------------------------------------------------------------------
/example/files/w.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 50 }
2 |
--------------------------------------------------------------------------------
/example/files/z.js:
--------------------------------------------------------------------------------
1 | module.exports = function (n) { return n * 111 }
2 |
--------------------------------------------------------------------------------
/test/dedupe/a.js:
--------------------------------------------------------------------------------
1 | var e = require('./e');
2 | console.log(e(2) * 111);
3 |
--------------------------------------------------------------------------------
/test/dedupe/b.js:
--------------------------------------------------------------------------------
1 | var e = require('./e');
2 | console.log(e(3) * 11);
3 |
--------------------------------------------------------------------------------
/test/rel/y.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | console.log(z(2) + 111);
3 |
--------------------------------------------------------------------------------
/example/files/y.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | console.log(z(2) + 111);
3 |
--------------------------------------------------------------------------------
/test/deps/y.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | console.log(z(2) + 111);
3 |
--------------------------------------------------------------------------------
/test/dedupe/d.js:
--------------------------------------------------------------------------------
1 | var x = require('./x');
2 | module.exports = function(n) { return x(n) * 7; };
3 |
--------------------------------------------------------------------------------
/test/dedupe/e.js:
--------------------------------------------------------------------------------
1 | var x = require('./x');
2 | module.exports = function(n) { return x(n) * 7; };
3 |
--------------------------------------------------------------------------------
/test/files/w.js:
--------------------------------------------------------------------------------
1 | var a = require('./a.js');
2 | module.exports = function (n) { return n * a(40) }
3 |
--------------------------------------------------------------------------------
/test/dedupe/c.js:
--------------------------------------------------------------------------------
1 | var d = require('./d');
2 | var x = require('./x');
3 | console.log(d(2) * x(3) * 11);
4 |
--------------------------------------------------------------------------------
/test/deps/x.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | var w = require('./w.js');
3 | console.log(z(5) * w(2));
4 |
--------------------------------------------------------------------------------
/test/files/x.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | var w = require('./w.js');
3 | console.log(z(5) * w(2));
4 |
--------------------------------------------------------------------------------
/test/rel/x.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | var w = require('./w.js');
3 | console.log(z(5) * w(2));
4 |
--------------------------------------------------------------------------------
/example/files/x.js:
--------------------------------------------------------------------------------
1 | var z = require('./z.js');
2 | var w = require('./w.js');
3 | console.log(z(5) * w(2));
4 |
--------------------------------------------------------------------------------
/test/files/t.js:
--------------------------------------------------------------------------------
1 | var w = require('./w.js');
2 | var a = require('./a.js');
3 |
4 | console.log(w(5) + a(40));
5 |
--------------------------------------------------------------------------------
/test/files/y.js:
--------------------------------------------------------------------------------
1 | var a = require('./a.js');
2 | var z = require('./z.js');
3 | console.log(z(2) + a(101));
4 |
--------------------------------------------------------------------------------
/example/minify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | browserify files/*.js \
4 | -p [ ../ -o 'uglifyjs -cm | gzip > bundle/`basename $FILE`.gz' ] \
5 | | uglifyjs -cm | gzip > bundle/common.js.gz
6 |
--------------------------------------------------------------------------------
/example/api.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify');
2 | var fs = require('fs');
3 |
4 | var files = [ './files/x.js', './files/y.js' ];
5 | var b = browserify(files);
6 | b.plugin('../', { o: [ 'bundle/x.js', 'bundle/y.js' ] });
7 | b.bundle().pipe(fs.createWriteStream('bundle/common.js'));
8 |
--------------------------------------------------------------------------------
/example/stream.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify');
2 | var concat = require('concat-stream');
3 |
4 | var files = [ './files/x.js', './files/y.js' ];
5 | var b = browserify(files);
6 |
7 | b.plugin('../', { outputs: [ write('x'), write('y') ] });
8 | b.bundle().pipe(write('common'));
9 |
10 | function write (name) {
11 | return concat(function (body) {
12 | console.log('// ----- ' + name + ' -----');
13 | console.log(body.toString('utf8'));
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/bin/usage.txt:
--------------------------------------------------------------------------------
1 | usage: factor-bundle [ x.js -o bundle/x.js ... ] > bundle/common.js
2 |
3 | Read `module-deps` json output from stdin, factoring each entry file out into
4 | the corresponding output file (-o).
5 |
6 | -o FILE, -o CMD
7 |
8 | Write output to FILE or CMD. CMD is distinguished from FILE by the presence
9 | of one or more `>` or `|` characters. For example, use:
10 |
11 | factor-bundle browser/a.js browser/b.js \
12 | -o bundle/a.js -o bundle/b.js
13 |
14 | to write to a FILE. And do something like:
15 |
16 | factor-bundle browser/*.js \
17 | -o 'uglifyjs -cm | gzip > bundle/`basename $FILE`.gz'
18 |
19 | to use a CMD. In the CMD, $FILE is set to the corresponding input file path.
20 |
21 | If there is a trailing unpaired `-o`, that file will be used for the common
22 | bundle output. Otherwise, the final bundle is written to stdout.
23 |
24 |
--------------------------------------------------------------------------------
/bin/cmd.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var factor = require('../');
6 | var minimist = require('minimist');
7 | var pack = require('browser-pack');
8 | var Transform = require('stream').Transform;
9 |
10 | var argv = minimist(process.argv.slice(2));
11 | if (argv.h || argv.help || argv._.length === 0) {
12 | return fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
13 | }
14 |
15 | var basedir = argv.basedir || process.cwd();
16 |
17 | var fr = factor(argv._, { raw: true, root: basedir });
18 | var files = argv._.reduce(function (acc, x, ix) {
19 | acc[path.resolve(basedir, argv._[ix])] = argv.o[ix];
20 | return acc;
21 | }, {});
22 |
23 | var output = argv.o.length > files.length && argv.o[files.length] !== '-'
24 | ? fs.createWriteStream(argv.o[files.length]) : process.stdout
25 | ;
26 |
27 | fr.on('stream', function (bundle) {
28 | var ws = fs.createWriteStream(files[bundle.file]);
29 | bundle.pipe(pack({ raw: true })).pipe(ws);
30 | });
31 | process.stdin.pipe(fr).pipe(pack({ raw: true })).pipe(output);
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This software is released under the MIT license:
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/test/common-deps.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var through = require('through');
3 | var factor = require('../');
4 |
5 | var ROWS = {
6 | A: { id: '/a.js', deps: { '/c.js': '/c.js' } },
7 | B: { id: '/b.js', deps: { '/c.js': '/c.js' } },
8 | C: { id: '/c.js', deps: {} }
9 | };
10 |
11 | var expected = {};
12 | expected.common = [ ROWS.A, ROWS.C ].sort(cmp);
13 | expected['/b.js'] = [ ROWS.B ].sort(cmp);
14 |
15 | test('lift singly-shared dependencies', function (t) {
16 | t.plan(2);
17 |
18 | var files = [ '/b.js' ];
19 | var fr = factor(files, { objectMode: true, raw: true });
20 | fr.on('stream', function (bundle) {
21 | bundle.pipe(rowsOf(function (rows) {
22 | console.log('----- ' + bundle.file + ' ROWS -----');
23 | console.log(rows);
24 | t.deepEqual(rows.sort(cmp), expected[bundle.file]);
25 | }));
26 | });
27 |
28 | fr.pipe(rowsOf(function (rows) {
29 | console.log('----- COMMON ROWS -----');
30 | console.log(rows);
31 | t.deepEqual(rows.sort(cmp), expected.common);
32 | }));
33 |
34 | fr.write(ROWS.A);
35 | fr.write(ROWS.B);
36 | fr.write(ROWS.C);
37 | fr.end();
38 | });
39 |
40 | function rowsOf (cb) {
41 | var rows = [];
42 | return through(write, end);
43 |
44 | function write (row) { rows.push(row) }
45 | function end () { cb(rows) }
46 | }
47 |
48 | function cmp (a, b) {
49 | return a.id < b.id ? -1 : 1;
50 | }
51 |
--------------------------------------------------------------------------------
/test/exposed-deps.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var through = require('through');
3 | var factor = require('../');
4 |
5 | var ROWS = {
6 | A: { id: '/a.js', deps: { '/c.js': '/c.js' } },
7 | B: { id: '/b.js', deps: { '/c.js': '/c.js' } },
8 | C: { id: '/c.js', deps: { 'd': undefined } }
9 | };
10 |
11 | var expected = {};
12 | expected.common = [ ROWS.A, ROWS.C ].sort(cmp);
13 | expected['/b.js'] = [ ROWS.B ].sort(cmp);
14 |
15 | test('Copes with missing dependencies', function (t) {
16 | t.plan(2);
17 |
18 | var files = [ '/b.js' ];
19 | var fr = factor(files, { objectMode: true, raw: true });
20 | fr.on('stream', function (bundle) {
21 | bundle.pipe(rowsOf(function (rows) {
22 | console.log('----- ' + bundle.file + ' ROWS -----');
23 | console.log(rows);
24 | t.deepEqual(rows.sort(cmp), expected[bundle.file]);
25 | }));
26 | });
27 |
28 | fr.pipe(rowsOf(function (rows) {
29 | console.log('----- COMMON ROWS -----');
30 | console.log(rows);
31 | t.deepEqual(rows.sort(cmp), expected.common);
32 | }));
33 |
34 | fr.write(ROWS.A);
35 | fr.write(ROWS.B);
36 | fr.write(ROWS.C);
37 | fr.end();
38 | });
39 |
40 | function rowsOf (cb) {
41 | var rows = [];
42 | return through(write, end);
43 |
44 | function write (row) { rows.push(row) }
45 | function end () { cb(rows) }
46 | }
47 |
48 | function cmp (a, b) {
49 | return a.id < b.id ? -1 : 1;
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "factor-bundle",
3 | "version": "2.5.0",
4 | "description": "factor browser-pack bundles into common shared bundles",
5 | "main": "index.js",
6 | "bin": "bin/cmd.js",
7 | "dependencies": {
8 | "JSONStream": "^1.3.2",
9 | "browser-pack": "^6.0.4",
10 | "defined": "^1.0.0",
11 | "deps-topo-sort": "~0.2.1",
12 | "inherits": "^2.0.1",
13 | "isarray": "^2.0.4",
14 | "labeled-stream-splicer": "^2.0.1",
15 | "minimist": "^1.2.0",
16 | "nub": "^1.0.0",
17 | "outpipe": "^1.1.0",
18 | "reversepoint": "~0.2.0",
19 | "stream-combiner": "~0.2.1",
20 | "through2": "^2.0.3",
21 | "xtend": "^4.0.0"
22 | },
23 | "devDependencies": {
24 | "browser-unpack": "^1.1.1",
25 | "browserify": "^11.0.1",
26 | "concat-stream": "^1.4.6",
27 | "mkdirp": "~0.5.0",
28 | "module-deps": "^3.9.0",
29 | "osenv": "^0.1.0",
30 | "tape": "^4.0.1",
31 | "through": "^2.3.4"
32 | },
33 | "scripts": {
34 | "test": "tape test/*.js"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "git://github.com/substack/factor-bundle.git"
39 | },
40 | "homepage": "https://github.com/substack/factor-bundle",
41 | "keywords": [
42 | "browser-pack",
43 | "browserify",
44 | "browserify-plugin",
45 | "browserify-tool",
46 | "bundle"
47 | ],
48 | "author": {
49 | "email": "mail@substack.net",
50 | "name": "James Halliday",
51 | "url": "http://substack.net"
52 | },
53 | "license": "MIT"
54 | }
55 |
--------------------------------------------------------------------------------
/test/plugin-dedupe.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var fs = require('fs');
3 | var vm = require('vm');
4 | var tmp = require('osenv').tmpdir;
5 | var mkdirp = require('mkdirp');
6 | var spawn = require('child_process').spawn;
7 | var browserify = require('browserify');
8 | var factor = require('../');
9 | var concat = require('concat-stream');
10 |
11 | var files = [
12 | __dirname + '/dedupe/a.js',
13 | __dirname + '/dedupe/b.js',
14 | __dirname + '/dedupe/c.js'
15 | ];
16 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
17 | mkdirp.sync(tmpdir);
18 |
19 | test('browserify plugin handles deduped modules', function (t) {
20 | t.plan(4);
21 | var args = files.concat('-p', '[',
22 | __dirname + '/..',
23 | '-o', tmpdir + '/a.js', '-o', tmpdir + '/b.js', '-o', tmpdir + '/c.js',
24 | ']', '-o', tmpdir + '/common.js'
25 | );
26 | var ps = spawn('browserify', args);
27 | ps.on('exit', function (code) {
28 | t.equal(code, 0);
29 |
30 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
31 | var a = fs.readFileSync(tmpdir + '/a.js', 'utf8');
32 | var b = fs.readFileSync(tmpdir + '/b.js', 'utf8');
33 | var c = fs.readFileSync(tmpdir + '/c.js', 'utf8');
34 |
35 | vm.runInNewContext(common + a, { console: { log: function (msg) {
36 | t.equal(msg, 7770);
37 | } } });
38 |
39 | vm.runInNewContext(common + b, { console: { log: function (msg) {
40 | t.equal(msg, 1155);
41 | } } });
42 |
43 | vm.runInNewContext(common + c, { console: { log: function (msg) {
44 | t.equal(msg, 11550);
45 | } } });
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/rel.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var tmp = require('osenv').tmpdir;
3 | var fs = require('fs');
4 | var path = require('path');
5 | var vm = require('vm');
6 | var mkdirp = require('mkdirp');
7 | var browserify = require('browserify');
8 | var factor = require('../');
9 | var concat = require('concat-stream');
10 |
11 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
12 | mkdirp.sync(tmpdir);
13 |
14 | var cwd = process.cwd();
15 | process.chdir(__dirname);
16 |
17 | var files = [ './rel/x.js', './rel/y.js' ];
18 | var outputs = [
19 | path.join(tmpdir, 'x.js'),
20 | path.join(tmpdir, 'y.js')
21 | ];
22 |
23 | test('relative entry paths', function (t) {
24 | t.plan(2);
25 | t.on('end', function () { process.chdir(cwd) });
26 |
27 | var sources = {};
28 | var pending = 3;
29 | var b = browserify(files);
30 |
31 | b.plugin(factor, {
32 | o: [
33 | concat(function(data) {
34 | sources.x = data.toString('utf8');
35 | done();
36 | }),
37 | concat(function(data) {
38 | sources.y = data.toString('utf8');
39 | done();
40 | })
41 | ]
42 | });
43 |
44 | b.bundle().pipe(concat(function (data) {
45 | sources.common = data.toString('utf8');
46 | done();
47 | }));
48 |
49 | function done () {
50 | if (--pending !== 0) return;
51 | var x = sources.common + sources.x;
52 | var y = sources.common + sources.y;
53 |
54 | vm.runInNewContext(x, { console: { log: function (msg) {
55 | t.equal(msg, 55500);
56 | } } });
57 |
58 | vm.runInNewContext(y, { console: { log: function (msg) {
59 | t.equal(msg, 333);
60 | } } });
61 | }
62 | });
63 |
--------------------------------------------------------------------------------
/test/lift.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var through = require('through');
3 | var factor = require('../');
4 |
5 | var ROWS = {
6 | A: { id: '/a.js', deps: { '/c.js': '/c.js', '/z.js': '/z.js' } },
7 | B: { id: '/b.js', deps: { '/c.js': '/c.js', '/x.js': '/x.js' } },
8 | C: { id: '/c.js', deps: { '/d.js': '/d.js' } },
9 | D: { id: '/d.js', deps: { '/e.js': '/e.js' } },
10 | E: { id: '/e.js', deps: {} },
11 | X: { id: '/x.js', deps: { '/y.js': '/y.js' } },
12 | Y: { id: '/y.js', deps: { '/z.js': '/z.js' } },
13 | Z: { id: '/z.js', deps: { '/c.js': '/c.js' } }
14 | };
15 |
16 | var expected = {};
17 | expected.common = [ ROWS.C, ROWS.D, ROWS.E, ROWS.Z ].sort(cmp);
18 | expected['/a.js'] = [ ROWS.A ].sort(cmp);
19 | expected['/b.js'] = [ ROWS.B, ROWS.X, ROWS.Y ].sort(cmp);
20 |
21 | test('lift singly-shared dependencies', function (t) {
22 | t.plan(3);
23 |
24 | var files = [ '/a.js', '/b.js' ];
25 | var fr = factor(files, { objectMode: true, raw: true });
26 | fr.on('stream', function (bundle) {
27 | bundle.pipe(rowsOf(function (rows) {
28 | console.log('----- ' + bundle.file + ' ROWS -----');
29 | console.log(rows);
30 | t.deepEqual(rows.sort(cmp), expected[bundle.file]);
31 | }));
32 | });
33 |
34 | fr.pipe(rowsOf(function (rows) {
35 | console.log('----- COMMON ROWS -----');
36 | console.log(rows);
37 | t.deepEqual(rows.sort(cmp), expected.common);
38 | }));
39 |
40 | fr.write(ROWS.A);
41 | fr.write(ROWS.B);
42 | fr.write(ROWS.C);
43 | fr.write(ROWS.D);
44 | fr.write(ROWS.E);
45 | fr.write(ROWS.X);
46 | fr.write(ROWS.Y);
47 | fr.write(ROWS.Z);
48 | fr.end();
49 | });
50 |
51 | function rowsOf (cb) {
52 | var rows = [];
53 | return through(write, end);
54 |
55 | function write (row) { rows.push(row) }
56 | function end () { cb(rows) }
57 | }
58 |
59 | function cmp (a, b) {
60 | return a.id < b.id ? -1 : 1;
61 | }
62 |
--------------------------------------------------------------------------------
/test/deps.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var factor = require('../');
3 | var mdeps = require('module-deps');
4 | var through = require('through');
5 | var path = require('path');
6 | var fs = require('fs');
7 | var pack = require('browser-pack');
8 | var concat = require('concat-stream');
9 | var vm = require('vm');
10 |
11 | var files = [ 'x.js', 'y.js' ].map(function (file) {
12 | return path.join(__dirname, 'deps', file);
13 | });
14 | var expected = {
15 | common: [ read('z.js') ],
16 | 'x.js': [
17 | read('x.js', {
18 | entry: true,
19 | deps: { './z.js': norm('z.js'), './w.js': norm('w.js') }
20 | }),
21 | read('w.js')
22 | ],
23 | 'y.js': [
24 | read('y.js', {
25 | entry: true,
26 | deps: { './z.js': norm('z.js') }
27 | })
28 | ]
29 | };
30 |
31 | test('trust but verify', function (t) {
32 | t.plan(5);
33 | var packs = {
34 | common: pack({ raw: true }),
35 | 'x.js': pack({ raw: true }),
36 | 'y.js': pack({ raw: true })
37 | };
38 |
39 | var pending = 3;
40 |
41 | var sources = {};
42 | packs.common.pipe(concat(function (src) {
43 | sources.common = src;
44 | done();
45 | }));
46 | packs['x.js'].pipe(concat(function (src) {
47 | sources['x.js'] = src;
48 | done();
49 | }));
50 | packs['y.js'].pipe(concat(function (src) {
51 | sources['y.js'] = src;
52 | done();
53 | }));
54 |
55 | function done () {
56 | if (--pending !== 0) return;
57 | var srcx = 'require=' + sources.common
58 | + ';require=' + sources['x.js']
59 | ;
60 | function logx (msg) { t.equal(msg, 55500) }
61 | vm.runInNewContext(srcx, { console: { log: logx } });
62 |
63 | var srcy = 'require=' + sources.common
64 | + ';require=' + sources['y.js']
65 | ;
66 | function logy (msg) { t.equal(msg, 333) }
67 | vm.runInNewContext(srcy, { console: { log: logy } });
68 | }
69 |
70 | var rows = [];
71 | var fr = factor(files, { objectMode: true, raw: true });
72 | fr.on('stream', function (bundle) {
73 | var name = path.basename(bundle.file);
74 | bundle.pipe(rowsOf(function (rows) {
75 | t.deepEqual(rows, expected[name]);
76 | }));
77 | bundle.pipe(packs[name]);
78 | });
79 | var md = mdeps();
80 | md.pipe(fr);
81 | fr.pipe(rowsOf(function (rows) {
82 | t.deepEqual(rows, expected.common);
83 | }));
84 | fr.pipe(packs.common);
85 | files.forEach(function (file) { md.write({ file: file }) });
86 | md.end();
87 | });
88 |
89 | function rowsOf (cb) {
90 | var rows = [];
91 | return through(write, end);
92 |
93 | function write (row) { rows.push(row) }
94 | function end () { cb(rows) }
95 | }
96 |
97 | function read (name, ref) {
98 | if (!ref) ref = {};
99 | var file = norm(name);
100 | ref.id = file;
101 | ref.file = file;
102 | ref.source = fs.readFileSync(file, 'utf8');
103 | if (!ref.deps) ref.deps = {};
104 | return ref;
105 | }
106 |
107 | function norm (file) {
108 | return path.normalize(__dirname + '/deps/' + file);
109 | }
110 |
--------------------------------------------------------------------------------
/test/plugin.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var tmp = require('osenv').tmpdir;
3 | var fs = require('fs');
4 | var vm = require('vm');
5 | var mkdirp = require('mkdirp');
6 | var spawn = require('child_process').spawn;
7 | var browserify = require('browserify');
8 | var factor = require('../');
9 | var concat = require('concat-stream');
10 |
11 | var files = [
12 | __dirname + '/deps/x.js',
13 | __dirname + '/deps/y.js'
14 | ];
15 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
16 | mkdirp.sync(tmpdir);
17 |
18 | test('browserify plugin', function (t) {
19 | t.plan(3);
20 | var args = files.concat('-p', '[',
21 | __dirname + '/..',
22 | '-o', tmpdir + '/x.js', '-o', tmpdir + '/y.js',
23 | ']', '-o', tmpdir + '/common.js'
24 | );
25 | var ps = spawn('browserify', args);
26 | ps.on('exit', function (code) {
27 | t.equal(code, 0);
28 |
29 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
30 | var x = fs.readFileSync(tmpdir + '/x.js', 'utf8');
31 | var y = fs.readFileSync(tmpdir + '/y.js', 'utf8');
32 |
33 | vm.runInNewContext(common + x, { console: { log: function (msg) {
34 | t.equal(msg, 55500);
35 | } } });
36 |
37 | vm.runInNewContext(common + y, { console: { log: function (msg) {
38 | t.equal(msg, 333);
39 | } } });
40 | });
41 | });
42 |
43 | test('browserify plugin streams', function(t) {
44 | t.plan(2);
45 |
46 | var b = browserify(files);
47 | var sources = {};
48 | b.plugin(factor, {
49 | o: [
50 | concat(function(data) { sources.x = data }),
51 | concat(function(data) { sources.y = data })
52 | ]
53 | });
54 |
55 | b.bundle().pipe(concat(function(data) {
56 | var common = data.toString('utf8');
57 | var x = sources.x.toString('utf8');
58 | var y = sources.y.toString('utf8');
59 |
60 | vm.runInNewContext(common + x, { console: { log: function (msg) {
61 | t.equal(msg, 55500);
62 | } } });
63 |
64 | vm.runInNewContext(common + y, { console: { log: function (msg) {
65 | t.equal(msg, 333);
66 | } } });
67 | }));
68 | });
69 |
70 | test('browserify plugin multiple bundle calls', function(t) {
71 | t.plan(4);
72 |
73 | var b = browserify(files);
74 | var sources = {};
75 | b.on('factor.pipeline', function(id, pipeline) {
76 | pipeline.pipe(concat(function(data) {
77 | if (/x\.js$/.test(id)) sources.x = data;
78 | else sources.y = data;
79 | }));
80 | });
81 | b.plugin(factor);
82 |
83 | b.bundle().pipe(concat(function(data) {
84 | checkBundle(data);
85 |
86 | b.bundle().pipe(concat(checkBundle));
87 | }));
88 |
89 | function checkBundle(data) {
90 | var common = data.toString('utf8');
91 | var x = sources.x.toString('utf8');
92 | var y = sources.y.toString('utf8');
93 |
94 | vm.runInNewContext(common + x, { console: { log: function (msg) {
95 | t.equal(msg, 55500);
96 | } } });
97 |
98 | vm.runInNewContext(common + y, { console: { log: function (msg) {
99 | t.equal(msg, 333);
100 | } } });
101 | }
102 | });
103 |
--------------------------------------------------------------------------------
/test/outputs.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var tmp = require('osenv').tmpdir;
3 | var fs = require('fs');
4 | var path = require('path');
5 | var vm = require('vm');
6 | var mkdirp = require('mkdirp');
7 | var browserify = require('browserify');
8 | var factor = require('../');
9 | var concat = require('concat-stream');
10 | var through = require('through2');
11 |
12 | var files = [
13 | __dirname + '/deps/x.js',
14 | __dirname + '/deps/y.js'
15 | ];
16 |
17 | test('file outputs', function (t) {
18 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
19 | mkdirp.sync(tmpdir);
20 |
21 | t.plan(2);
22 | var b = browserify(files);
23 | b.plugin(factor, {
24 | outputs: [
25 | path.join(tmpdir, 'x.js'),
26 | path.join(tmpdir, 'y.js')
27 | ]
28 | });
29 | var w = fs.createWriteStream(path.join(tmpdir, 'common.js'));
30 | b.bundle().pipe(w);
31 |
32 | w.on('finish', function () {
33 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
34 | var x = fs.readFileSync(tmpdir + '/x.js', 'utf8');
35 | var y = fs.readFileSync(tmpdir + '/y.js', 'utf8');
36 |
37 | vm.runInNewContext(common + x, { console: { log: function (msg) {
38 | t.equal(msg, 55500);
39 | } } });
40 |
41 | vm.runInNewContext(common + y, { console: { log: function (msg) {
42 | t.equal(msg, 333);
43 | } } });
44 | });
45 | });
46 |
47 | test('stream outputs', function (t) {
48 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
49 | mkdirp.sync(tmpdir);
50 | t.plan(2);
51 | var sources = {}, pending = 3;
52 | function write (key) {
53 | return concat(function (body) {
54 | sources[key] = body.toString('utf8');
55 | if (-- pending === 0) done();
56 | });
57 | }
58 |
59 | var b = browserify(files);
60 | b.plugin(factor, { outputs: [ write('x'), write('y') ] });
61 | b.bundle().pipe(write('common'));
62 |
63 | function done () {
64 | var common = sources.common, x = sources.x, y = sources.y;
65 |
66 | vm.runInNewContext(common + x, { console: { log: function (msg) {
67 | t.equal(msg, 55500);
68 | } } });
69 |
70 | vm.runInNewContext(common + y, { console: { log: function (msg) {
71 | t.equal(msg, 333);
72 | } } });
73 | }
74 | });
75 |
76 | test('function outputs', function (t) {
77 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
78 | mkdirp.sync(tmpdir);
79 |
80 | t.plan(2);
81 | var b = browserify(files);
82 | b.plugin(factor, {
83 | output: function() { return ' cat > ' + tmpdir + '/`basename $FILE`'; }
84 | });
85 | var w = fs.createWriteStream(path.join(tmpdir, 'common.js'));
86 | b.bundle().pipe(w);
87 |
88 | w.on('finish', function () {
89 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
90 | var x = fs.readFileSync(tmpdir + '/x.js', 'utf8');
91 | var y = fs.readFileSync(tmpdir + '/y.js', 'utf8');
92 |
93 | vm.runInNewContext(common + x, { console: { log: function (msg) {
94 | t.equal(msg, 55500);
95 | } } });
96 |
97 | vm.runInNewContext(common + y, { console: { log: function (msg) {
98 | t.equal(msg, 333);
99 | } } });
100 | });
101 | });
102 |
103 | test('bundle twice', function (t) {
104 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
105 | mkdirp.sync(tmpdir);
106 |
107 | t.plan(4);
108 | var b = browserify(files);
109 | var outputs = [
110 | path.join(tmpdir, 'x.js'),
111 | path.join(tmpdir, 'y.js')
112 | ];
113 | b.on('reset', function(){
114 | b.pipeline.get('deps').push(through.obj(function(data, enc, next){
115 | if (data.file.indexOf('x.js') !== -1) {
116 | data.source = data.source.replace('z(5)', 'z(6)');
117 | }
118 | this.push(data);
119 | next();
120 | }));
121 | });
122 | b.plugin(factor, {
123 | outputs: outputs
124 | });
125 | validate(55500, 333, function(){
126 | validate(66600, 333);
127 | });
128 | function validate (xVal, yVal, cb) {
129 | var w = fs.createWriteStream(path.join(tmpdir, 'common.js'));
130 | b.bundle().pipe(w);
131 | w.on('finish', function(){
132 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
133 | var x = fs.readFileSync(tmpdir + '/x.js', 'utf8');
134 | var y = fs.readFileSync(tmpdir + '/y.js', 'utf8');
135 |
136 | vm.runInNewContext(common + x, { console: { log: function (msg) {
137 | t.equal(msg, xVal);
138 | } } });
139 |
140 | vm.runInNewContext(common + y, { console: { log: function (msg) {
141 | t.equal(msg, yVal);
142 | } } });
143 | if (cb) cb();
144 | });
145 | }
146 | });
147 |
148 | test('outpipe outputs', function (t) {
149 | var tmpdir = tmp() + '/factor-bundle-' + Math.random();
150 | mkdirp.sync(tmpdir);
151 |
152 | t.plan(2);
153 | var b = browserify(files);
154 | b.plugin(factor, {
155 | output: ' cat > ' + tmpdir + '/`basename $FILE`'
156 | });
157 | var w = fs.createWriteStream(path.join(tmpdir, 'common.js'));
158 | b.bundle().pipe(w);
159 |
160 | w.on('finish', function () {
161 | var common = fs.readFileSync(tmpdir + '/common.js', 'utf8');
162 | var x = fs.readFileSync(tmpdir + '/x.js', 'utf8');
163 | var y = fs.readFileSync(tmpdir + '/y.js', 'utf8');
164 |
165 | vm.runInNewContext(common + x, { console: { log: function (msg) {
166 | t.equal(msg, 55500);
167 | } } });
168 |
169 | vm.runInNewContext(common + y, { console: { log: function (msg) {
170 | t.equal(msg, 333);
171 | } } });
172 | });
173 | });
174 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var Transform = require('stream').Transform;
2 | var through = require('through2');
3 | var Readable = require('stream').Readable;
4 | var inherits = require('inherits');
5 | var path = require('path');
6 | var JSONStream = require('JSONStream');
7 | var combine = require('stream-combiner');
8 | var nub = require('nub');
9 | var depsTopoSort = require('deps-topo-sort');
10 | var reverse = require('reversepoint');
11 | var fs = require('fs');
12 | var pack = require('browser-pack');
13 | var xtend = require('xtend');
14 | var defined = require('defined');
15 | var splicer = require('labeled-stream-splicer');
16 | var outpipe = require('outpipe');
17 | var isarray = require('isarray');
18 |
19 | module.exports = function f (b, opts) {
20 | if (!opts) opts = {};
21 | if (typeof b === 'string' || Array.isArray(b)) {
22 | return createStream(b, opts)
23 | }
24 |
25 | var files = [].concat(opts.entries).concat(opts.e)
26 | .concat(opts._).filter(Boolean);
27 |
28 | var needRecords = !files.length;
29 |
30 | var outopt = defined(opts.outputs, opts.output, opts.o);
31 |
32 | opts.objectMode = true;
33 | opts.raw = true;
34 | opts.rmap = {};
35 |
36 | var cwd = defined(opts.basedir, b._options.basedir, process.cwd()),
37 | packOpts = xtend(b._options, {
38 | raw: true,
39 | hasExports: true
40 | });
41 |
42 | b.on('reset', addHooks);
43 | addHooks();
44 |
45 | function addHooks () {
46 | b.pipeline.get('record').push(through.obj(function(row, enc, next) {
47 | if (row.file && needRecords) {
48 | files.push(row.file);
49 | }
50 | next(null, row);
51 | }, function(next) {
52 | var outopt = defined(opts.outputs, opts.output, opts.o);
53 |
54 | if (typeof outopt === 'function') {
55 | outopt = outopt();
56 | }
57 |
58 | var outputs;
59 | if (isarray(outopt)) {
60 | outputs = outopt.map(function (o) {
61 | if (isStream(o)) return o;
62 | return fs.createWriteStream(o);
63 | });
64 | } else {
65 | outputs = [];
66 | }
67 |
68 | function moreOutputs (file) {
69 | if (isarray(outopt)) return [];
70 | if (!outopt) return [];
71 | var xopts = { env: xtend(process.env, { FILE: file }) };
72 | return [ outpipe(outopt, xopts) ];
73 | }
74 |
75 | var pipelines = files.reduce(function (acc, x, ix) {
76 | var pipeline = splicer.obj([
77 | 'pack', [ pack(packOpts) ],
78 | 'wrap', []
79 | ]);
80 |
81 | if (ix >= outputs.length) {
82 | outputs.push.apply(outputs, moreOutputs(x));
83 | }
84 | if (outputs[ix]) pipeline.pipe(outputs[ix]);
85 |
86 | acc[path.resolve(cwd, x)] = pipeline;
87 | return acc;
88 | }, {});
89 |
90 | // Force browser-pack to wrap the common bundle
91 | b._bpack.hasExports = true;
92 |
93 | Object.keys(pipelines).forEach(function (id) {
94 | b.emit('factor.pipeline', id, pipelines[id]);
95 | });
96 |
97 | var s = createStream(files, opts);
98 | s.on('stream', function (bundle) {
99 | bundle.pipe(pipelines[bundle.file]);
100 | });
101 |
102 | b.pipeline.get('pack').unshift(s);
103 |
104 | if (needRecords) files = [];
105 |
106 | next();
107 | }));
108 |
109 | b.pipeline.get('label').push(through.obj(function(row, enc, next) {
110 | opts.rmap[row.id] = path.resolve(cwd, row.file);
111 | next(null, row);
112 | }));
113 | }
114 |
115 | return b;
116 |
117 | };
118 |
119 | function createStream (files, opts) {
120 | if (!opts) opts = {};
121 |
122 | var fr = new Factor(files, opts);
123 | var parse, dup;
124 |
125 | if (opts.objectMode) {
126 | dup = combine(depsTopoSort(), reverse(), fr);
127 | }
128 | else {
129 | parse = JSONStream.parse([true]);
130 | dup = opts.raw
131 | ? combine(parse, depsTopoSort(), reverse(), fr)
132 | : combine(
133 | parse, depsTopoSort(), reverse(), fr, JSONStream.stringify()
134 | )
135 | ;
136 | parse.on('error', function (err) { dup.emit('error', err) });
137 | }
138 |
139 | fr.on('error', function (err) { dup.emit('error', err) });
140 | fr.on('stream', function (s) {
141 | if (opts.raw) dup.emit('stream', s)
142 | else dup.emit('stream', s.pipe(JSONStream.stringify()))
143 | });
144 | return dup;
145 | }
146 |
147 | inherits(Factor, Transform);
148 |
149 | function Factor (files, opts) {
150 | var self = this;
151 | if (!(this instanceof Factor)) return new Factor(files, opts);
152 | Transform.call(this, { objectMode: true });
153 |
154 | if (!opts) opts = {};
155 | this.basedir = defined(opts.basedir, process.cwd());
156 |
157 | this._streams = {};
158 | this._groups = {};
159 | this._buffered = {};
160 |
161 | this._ensureCommon = {};
162 | this._files = files.reduce(function (acc, file) {
163 | acc[path.resolve(self.basedir, file)] = true;
164 | return acc;
165 | }, {});
166 | this._rmap = opts.rmap || {};
167 |
168 | this._thresholdVal = typeof opts.threshold === "number"
169 | ? opts.threshold : 1
170 | ;
171 | this._defaultThreshold = function(row, group) {
172 | return group.length > this._thresholdVal || group.length === 0;
173 | };
174 | this._threshold = typeof opts.threshold === "function"
175 | ? opts.threshold
176 | : this._defaultThreshold
177 | ;
178 | }
179 |
180 | Factor.prototype._transform = function (row, enc, next) {
181 | var self = this;
182 | var groups = nub(self._groups[row.id] || []);
183 | var id = this._resolveMap(row.id);
184 |
185 | if (self._files[id]) {
186 | var s = self._streams[id];
187 | if (!s) s = self._makeStream(row);
188 | groups.push(id);
189 | }
190 | groups.forEach(addGroups);
191 |
192 | if (self._ensureCommon[row.id] || self._threshold(row, groups)) {
193 | Object.keys(row.deps).forEach(function(k) {
194 | self._ensureCommon[row.deps[k]] = true;
195 | });
196 | self.push(row);
197 | }
198 | else {
199 | groups.forEach(function (id) {
200 | self._streams[id].push(row);
201 | });
202 | }
203 |
204 | next();
205 |
206 | function addGroups (gid) {
207 | Object.keys(row.deps || {}).forEach(function (key) {
208 | var file = row.deps[key];
209 | var g = self._groups[file];
210 | if (!g) g = self._groups[file] = [];
211 | g.push(gid);
212 | });
213 | }
214 | };
215 |
216 | Factor.prototype._flush = function () {
217 | var self = this;
218 |
219 | Object.keys(self._streams).forEach(function (key) {
220 | self._streams[key].push(null);
221 | });
222 | self.push(null);
223 | };
224 |
225 | Factor.prototype._makeStream = function (row) {
226 | var s = new Readable({ objectMode: true });
227 | var id = this._resolveMap(row.id);
228 | s.file = id;
229 | s._read = function () {};
230 | this._streams[id] = s;
231 | this.emit('stream', s);
232 | return s;
233 | };
234 |
235 | Factor.prototype._resolveMap = function(id) {
236 | return this._rmap[id] || id;
237 | }
238 |
239 | function isStream (s) { return s && typeof s.pipe === 'function' }
240 |
--------------------------------------------------------------------------------
/readme.markdown:
--------------------------------------------------------------------------------
1 | # factor-bundle
2 |
3 | factor [browser-pack](https://npmjs.org/package/browser-pack) bundles into a
4 | common bundle and entry-specific bundles
5 |
6 | [](http://travis-ci.org/substack/factor-bundle)
7 |
8 | # example
9 |
10 | x.js:
11 |
12 | ``` js
13 | var z = require('./z.js');
14 | var w = require('./w.js');
15 | console.log(z(5) * w(2));
16 | ```
17 |
18 | y.js:
19 |
20 | ``` js
21 | var z = require('./z.js');
22 | console.log(z(2) + 111);
23 | ```
24 |
25 | z.js:
26 |
27 | ``` js
28 | module.exports = function (n) { return n * 111 }
29 | ```
30 |
31 | w.js:
32 |
33 | ``` js
34 | module.exports = function (n) { return n * 50 }
35 | ```
36 |
37 | Now run factor-bundle as a plugin (new in browserify 3.28.0):
38 |
39 | ``` sh
40 | browserify x.js y.js -p [ factor-bundle -o bundle/x.js -o bundle/y.js ] \
41 | -o bundle/common.js
42 | ```
43 |
44 | or you can pipe [module-deps](https://npmjs.org/package/module-deps) json
45 | directly into the `factor-bundle` command:
46 |
47 | ``` sh
48 | $ module-deps x.js y.js | factor-bundle \
49 | x.js -o bundle/x.js \
50 | y.js -o bundle/y.js \
51 | > bundle/common.js
52 | ```
53 |
54 | or factor out an existing bundle already compiled by browserify:
55 |
56 | ``` sh
57 | $ browserify x.js y.js > bundle.js
58 | $ browser-unpack < bundle.js | factor-bundle \
59 | x.js -o bundle/x.js \
60 | y.js -o bundle/y.js \
61 | > bundle/common.js
62 | ```
63 |
64 | Whichever one of these 3 options, you take, you can now have 2 pages, each with
65 | a different combination of script tags but with all the common modules factored
66 | out into a `common.js` to avoid transferring the same code multiple times:
67 |
68 | ``` html
69 |
70 |
71 | ```
72 |
73 | ``` html
74 |
75 |
76 | ```
77 |
78 | to verify this works from node you can do:
79 |
80 | ```
81 | $ cat bundle/common.js bundle/x.js | node
82 | 55500
83 | $ cat bundle/common.js bundle/y.js | node
84 | 333
85 | ```
86 |
87 | ## command-line outpipe example
88 |
89 | We can pipe each output file through some other processes. Here we'll do
90 | minification with uglify compression with gzip:
91 |
92 | ``` sh
93 | browserify files/*.js \
94 | -p [ factor-bundle -o 'uglifyjs -cm | gzip > bundle/`basename $FILE`.gz' ] \
95 | | uglifyjs -cm | gzip > bundle/common.js.gz
96 | ```
97 |
98 | ## api plugin example
99 |
100 | If you prefer you can use the factor-bundle plugin api directly in code:
101 |
102 | ``` js
103 | var browserify = require('browserify');
104 | var fs = require('fs');
105 |
106 | var files = [ './files/x.js', './files/y.js' ];
107 | var b = browserify(files);
108 | b.plugin('factor-bundle', { outputs: [ 'bundle/x.js', 'bundle/y.js' ] });
109 | b.bundle().pipe(fs.createWriteStream('bundle/common.js'));
110 | ```
111 |
112 | or instead of writing to files you can output to writable streams:
113 |
114 | ``` js
115 | var browserify = require('browserify');
116 | var concat = require('concat-stream');
117 |
118 | var files = [ './files/x.js', './files/y.js' ];
119 | var b = browserify(files);
120 |
121 | b.plugin('factor-bundle', { outputs: [ write('x'), write('y') ] });
122 | b.bundle().pipe(write('common'));
123 |
124 | function write (name) {
125 | return concat(function (body) {
126 | console.log('// ----- ' + name + ' -----');
127 | console.log(body.toString('utf8'));
128 | });
129 | }
130 | ```
131 |
132 | # usage
133 |
134 | You can use factor-bundle as a browserify plugin:
135 |
136 | ```
137 | browserify -p [ factor-bundle OPTIONS ]
138 |
139 | where OPTIONS are:
140 |
141 | -o Output FILE or CMD that maps to a corresponding entry file at the same
142 | index. CMDs are executed with $FILE set to the corresponding input file.
143 | Optionally specify a function that returns a valid value for this argument.
144 |
145 | -e Entry file to use, overriding the entry files listed in the original
146 | bundle.
147 |
148 | ```
149 |
150 | or you can use the command:
151 |
152 | ```
153 | usage: factor-bundle [ x.js -o bundle/x.js ... ] > bundle/common.js
154 |
155 | Read `module-deps` json output from stdin, factoring each entry file out into
156 | the corresponding output file (-o).
157 |
158 | -o FILE, -o CMD
159 |
160 | Write output to FILE or CMD. CMD is distinguished from FILE by the presence
161 | of one or more `>` or `|` characters. For example, use:
162 |
163 | factor-bundle browser/a.js browser/b.js \
164 | -o bundle/a.js -o bundle/b.js
165 |
166 | to write to a FILE. And do something like:
167 |
168 | factor-bundle browser/*.js \
169 | -o 'uglifyjs -cm | gzip > bundle/`basename $FILE`.gz'
170 |
171 | to use a CMD. In the CMD, $FILE is set to the corresponding input file path.
172 |
173 | If there is a trailing unpaired `-o`, that file will be used for the common
174 | bundle output. Otherwise, the final bundle is written to stdout.
175 |
176 | ```
177 |
178 | # methods
179 |
180 | ``` js
181 | var factor = require('factor-bundle')
182 | ```
183 |
184 | ## var fr = factor(files, opts={})
185 |
186 | Return a transform stream `tr` that factors the array of entry path strings
187 | `files` out into bundle files. The input format that `fr` expects is described
188 | in the [module-deps package](https://npmjs.org/package/module-deps).
189 |
190 | The output format for `fr` and each of the `fr` sub-streams given by each
191 | `'stream'` event is also in the
192 | [module-deps](https://npmjs.org/package/module-deps) format.
193 |
194 | `opts.o` or `opts.outputs` should be an array that pairs up with the `files` array to specify
195 | where each bundle output for each entry file should be written. The elements in
196 | `opts.o` can be string filenames, writable streams, or functions that return a
197 | string filename or writable stream.
198 |
199 | `opts.entries` or `opts.e` should be the array of entry files to create
200 | a page-specific bundle for each file. If you don't pass in an `opts.entries`,
201 | this information is gathered from browserify itself.
202 |
203 | The files held in common among `> opts.threshold` (default: 1) bundles will be
204 | output on the `fr` stream itself. The entry-specific bundles are diverted into
205 | each `'stream'` event's output. `opts.threshold` can be a number or a function
206 | `opts.threshold(row, groups)` where `row` is a
207 | [module-deps](https://github.com/substack/module-deps) object and `groups` is
208 | an array of bundles which depend on the row. If the threshold function returns
209 | `true`, that row and all its dependencies will go to the `common` bundle. If
210 | false, the row (but not its dependencies) will go to each bundle in `groups`.
211 | For example:
212 |
213 | ``` js
214 | factor(files, {threshold: function(row, groups) {
215 | if (/.*a\.js$/.test(row.id)) return false;
216 | if (/.*[z]\.js$/.test(row.id)) return true;
217 | return this._defaultThreshold(row, groups);
218 | }});
219 | ```
220 |
221 | # events
222 |
223 | ## fr.on('stream', function (stream) {})
224 |
225 | Each entry file emits a `'stream'` event containing all of the entry-specific
226 | rows that are only used by that entry file (when `opts.threshold === 1`, the
227 | default).
228 |
229 | The entry file name is available as `stream.file`.
230 |
231 | ## b.on('factor.pipeline', function (file, pipeline) {})
232 |
233 | Emits the full path to the entry file (`file`) and a [labeled-stream-splicer](https://npmjs.org/package/labeled-stream-splicer) (`pipeline`) for each entry file with these labels:
234 |
235 | * `'pack'` - [browser-pack](https://npmjs.org/package/browser-pack)
236 | * `'wrap'` - apply final wrapping
237 |
238 | You can call `pipeline.get` with a label name to get a handle on a stream pipeline that you can `push()`, `unshift()`, or `splice()` to insert your own transform streams.
239 |
240 | Event handlers must be attached *before* calling `b.plugin`.
241 |
242 | # install
243 |
244 | With [npm](https://npmjs.org) do:
245 |
246 | ```
247 | npm install factor-bundle
248 | ```
249 |
250 | # license
251 |
252 | MIT
253 |
--------------------------------------------------------------------------------
/test/files.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 | var factor = require('../');
3 | var mdeps = require('module-deps');
4 | var through = require('through');
5 | var path = require('path');
6 | var fs = require('fs');
7 | var pack = require('browser-pack');
8 | var concat = require('concat-stream');
9 | var vm = require('vm');
10 |
11 | test('more complicated dependencies', function (t) {
12 | t.plan(5);
13 | var files = [ 'x.js', 'y.js' ].map(function (file) {
14 | return path.join(__dirname, 'files', file);
15 | });
16 |
17 | var expected = {
18 | common: [ read('z.js'), read('a.js') ],
19 | 'x.js': [
20 | read('x.js', {
21 | entry: true,
22 | deps: { './z.js': norm('z.js'), './w.js': norm('w.js') }
23 | }),
24 | read('w.js', {
25 | deps: { './a.js': norm('a.js') }
26 | })
27 | ],
28 | 'y.js': [
29 | read('y.js', {
30 | entry: true,
31 | deps: { './z.js': norm('z.js'), './a.js': norm('a.js') }
32 | })
33 | ]
34 | };
35 |
36 | var packs = {
37 | common: pack({ raw: true }),
38 | 'x.js': pack({ raw: true }),
39 | 'y.js': pack({ raw: true })
40 | };
41 |
42 | var pending = 3;
43 |
44 | var sources = {};
45 | packs.common.pipe(concat(function (src) {
46 | sources.common = src;
47 | done();
48 | }));
49 | packs['x.js'].pipe(concat(function (src) {
50 | sources['x.js'] = src;
51 | done();
52 | }));
53 | packs['y.js'].pipe(concat(function (src) {
54 | sources['y.js'] = src;
55 | done();
56 | }));
57 |
58 | function done () {
59 | if (--pending !== 0) return;
60 | var srcx = 'require=' + sources.common
61 | + ';require=' + sources['x.js']
62 | ;
63 | function logx (msg) { t.equal(msg, 55500) }
64 | vm.runInNewContext(srcx, { console: { log: logx } });
65 |
66 | var srcy = 'require=' + sources.common
67 | + ';require=' + sources['y.js']
68 | ;
69 | function logy (msg) { t.equal(msg, 333) }
70 | vm.runInNewContext(srcy, { console: { log: logy } });
71 | }
72 |
73 | var rows = [];
74 | var fr = factor(files, { objectMode: true, raw: true });
75 | fr.on('stream', function (bundle) {
76 | var name = path.basename(bundle.file);
77 | bundle.pipe(rowsOf(function (rows) {
78 | t.deepEqual(rows, expected[name]);
79 | }));
80 | bundle.pipe(packs[name]);
81 | });
82 | var md = mdeps();
83 | md.pipe(fr)
84 | fr.pipe(rowsOf(function (rows) {
85 | t.deepEqual(rows, expected.common);
86 | }));
87 | fr.pipe(packs.common);
88 | files.forEach(function (file) { md.write({ file: file }) });
89 | md.end();
90 | });
91 |
92 | test('same module included twice', function (t) {
93 | //t.plan(5);
94 | t.plan(3);
95 |
96 | var files = [ 't.js' ].map(function (file) {
97 | return path.join(__dirname, 'files', file);
98 | });
99 |
100 | var expected = {
101 | common: [],
102 | 't.js': sortRows([
103 | read('t.js', {
104 | entry: true,
105 | deps: { './a.js': norm('a.js'), './w.js': norm('w.js') }
106 | }),
107 | read('w.js', {
108 | deps: { './a.js': norm('a.js') }
109 | }),
110 | read('a.js')
111 | ])
112 | };
113 |
114 | var packs = {
115 | common: pack({ raw: true }),
116 | 't.js': pack({ raw: true })
117 | };
118 |
119 | var pending = 2;
120 |
121 | var sources = {};
122 | packs.common.pipe(concat(function (src) {
123 | sources.common = src;
124 | done();
125 | }));
126 | packs['t.js'].pipe(concat(function (src) {
127 | sources['t.js'] = src;
128 | done();
129 | }));
130 |
131 | function done () {
132 | if (--pending !== 0) return;
133 | var srct = 'require=' + sources.common
134 | + ';require=' + sources['t.js']
135 | ;
136 | function logx (msg) { t.equal(msg, 300) }
137 | vm.runInNewContext(srct, { console: { log: logx } });
138 | }
139 |
140 | var rows = [];
141 | var fr = factor(files, { objectMode: true, raw: true });
142 | fr.on('stream', function (bundle) {
143 | var name = path.basename(bundle.file);
144 | bundle.pipe(rowsOf(function (rows) {
145 | t.deepEqual(rows, expected[name]);
146 | }));
147 | bundle.pipe(packs[name]);
148 | });
149 | var md = mdeps();
150 | md.pipe(fr)
151 | fr.pipe(rowsOf(function (rows) {
152 | t.deepEqual(rows, expected.common);
153 | }));
154 | fr.pipe(packs.common);
155 | files.forEach(function (file) { md.write({ file: file }) });
156 | md.end();
157 | });
158 |
159 | test('threshold function reorganizes bundles', function (t) {
160 | t.plan(5);
161 | var files = [ 'y.js', 't.js' ].map(function (file) {
162 | return path.join(__dirname, 'files', file);
163 | });
164 |
165 | var expected = {
166 | common: [
167 | read('z.js')
168 | ],
169 | 't.js': sortRows([
170 | read('t.js', {
171 | entry: true,
172 | deps: { './a.js': norm('a.js'), './w.js': norm('w.js') }
173 | }),
174 | read('w.js', {
175 | deps: { './a.js': norm('a.js') }
176 | }),
177 | read('a.js')
178 | ]),
179 | 'y.js': sortRows([
180 | read('y.js', {
181 | entry: true,
182 | deps: { './z.js': norm('z.js'), './a.js': norm('a.js') }
183 | }),
184 | read('a.js')
185 | ])
186 | };
187 |
188 | var packs = {
189 | common: pack({ raw: true }),
190 | 't.js': pack({ raw: true }),
191 | 'y.js': pack({ raw: true })
192 | };
193 |
194 | var pending = 3;
195 |
196 | var sources = {};
197 | packs.common.pipe(concat(function (src) {
198 | sources.common = src;
199 | done();
200 | }));
201 | packs['t.js'].pipe(concat(function (src) {
202 | sources['t.js'] = src;
203 | done();
204 | }));
205 | packs['y.js'].pipe(concat(function (src) {
206 | sources['y.js'] = src;
207 | done();
208 | }));
209 |
210 | function done () {
211 | if (--pending !== 0) return;
212 | var srct = 'require=' + sources.common
213 | + ';require=' + sources['t.js']
214 | ;
215 | function logt (msg) { t.equal(msg, 300) }
216 | vm.runInNewContext(srct, { console: { log: logt } });
217 |
218 | var srcy = 'require=' + sources.common
219 | + ';require=' + sources['y.js']
220 | ;
221 | function logy (msg) { t.equal(msg, 333) }
222 | vm.runInNewContext(srcy, { console: { log: logy } });
223 | }
224 |
225 | var rows = [];
226 | var fr = factor(files, { objectMode: true, raw: true, threshold: function(row, groups) {
227 | if (/.*a\.js$/.test(row.id)) {
228 | return false;
229 | };
230 | if (/.*[z]\.js$/.test(row.id)) {
231 | return true;
232 | };
233 | return this._defaultThreshold(row, groups);
234 | }});
235 | fr.on('stream', function (bundle) {
236 | var name = path.basename(bundle.file);
237 | bundle.pipe(rowsOf(function (rows) {
238 | t.deepEqual(rows, expected[name]);
239 | }));
240 | bundle.pipe(packs[name]);
241 | });
242 | var md = mdeps();
243 | md.pipe(fr)
244 | fr.pipe(rowsOf(function (rows) {
245 | t.deepEqual(rows, expected.common);
246 | }));
247 | fr.pipe(packs.common);
248 | files.forEach(function (file) { md.write({ file: file }) });
249 | md.end();
250 | });
251 |
252 | test('if dependent is in common, so is dependee', function (t) {
253 | t.plan(3);
254 | var files = [ 't.js' ].map(function (file) {
255 | return path.join(__dirname, 'files', file);
256 | });
257 |
258 | var expected = {
259 | common: [
260 | read('w.js', {
261 | deps: { './a.js': norm('a.js') }
262 | }),
263 | read('a.js')
264 | ],
265 | 't.js': [
266 | read('t.js', {
267 | entry: true,
268 | deps: { './a.js': norm('a.js'), './w.js': norm('w.js') }
269 | })
270 | ]
271 | };
272 |
273 | var packs = {
274 | common: pack({ raw: true }),
275 | 't.js': pack({ raw: true }),
276 | };
277 |
278 | var pending = 2;
279 |
280 | var sources = {};
281 | packs.common.pipe(concat(function (src) {
282 | sources.common = src;
283 | done();
284 | }));
285 | packs['t.js'].pipe(concat(function (src) {
286 | sources['t.js'] = src;
287 | done();
288 | }));
289 |
290 | function done () {
291 | if (--pending !== 0) return;
292 | var srct = 'require=' + sources.common
293 | + ';require=' + sources['t.js']
294 | ;
295 | function logt (msg) { t.equal(msg, 300) }
296 | vm.runInNewContext(srct, { console: { log: logt } });
297 | }
298 |
299 | var rows = [];
300 | var fr = factor(files, { objectMode: true, raw: true, threshold: function(row, groups) {
301 | if (/.*[w]\.js$/.test(row.id)) {
302 | return true;
303 | };
304 | return this._defaultThreshold(row, groups);
305 | }});
306 | fr.on('stream', function (bundle) {
307 | var name = path.basename(bundle.file);
308 | bundle.pipe(rowsOf(function (rows) {
309 | t.deepEqual(rows, expected[name]);
310 | }));
311 | bundle.pipe(packs[name]);
312 | });
313 | var md = mdeps();
314 | md.pipe(fr)
315 | fr.pipe(rowsOf(function (rows) {
316 | t.deepEqual(rows, expected.common);
317 | }));
318 | fr.pipe(packs.common);
319 | files.forEach(function (file) { md.write({ file: file }) });
320 | md.end();
321 | });
322 |
323 | function rowsOf (cb) {
324 | var rows = [];
325 | return through(write, end);
326 |
327 | function write (row) { rows.push(row) }
328 | function end () { cb(sortRows(rows)) }
329 | }
330 |
331 | function sortRows (rows) {
332 | return rows.sort(function (a, b) {
333 | return a.id < b.id ? 1 : -1;
334 | });
335 | }
336 |
337 | function read (name, ref) {
338 | if (!ref) ref = {};
339 | var file = norm(name);
340 | ref.id = file;
341 | ref.file = file;
342 | ref.source = fs.readFileSync(file, 'utf8');
343 | if (!ref.deps) ref.deps = {};
344 | return ref;
345 | }
346 |
347 | function norm (file) {
348 | return path.normalize(__dirname + '/files/' + file);
349 | }
350 |
--------------------------------------------------------------------------------