├── example ├── files │ ├── file1.txt │ └── file&2.t-t ├── index.html ├── index.js ├── package.json └── example.js ├── test ├── fixtures │ ├── files │ │ ├── .hiddenfile │ │ ├── file1.check │ │ ├── file1.txt │ │ └── file-3-other&file.txt │ ├── source │ │ ├── data.json │ │ ├── include-folder-default.js │ │ ├── include-folder-default.custom-js │ │ ├── include-folder-regex.js │ │ ├── bundle.js │ │ ├── include-folder-filenames.js │ │ ├── bundle-with-json.js │ │ └── unrequire-include-folder.js │ └── expected │ │ ├── unrequire-include-folder.js │ │ ├── include-folder-default.js │ │ ├── include-folder-default.custom-js │ │ ├── include-folder-filenames.js │ │ ├── include-folder-regex.js │ │ ├── bundle.js │ │ └── bundle-with-json.js ├── .eslintrc └── folderify_test.js ├── .travis.yml ├── .gitignore ├── .npmignore ├── folderify.sublime-project ├── LICENSE-MIT ├── package.json ├── README.md └── lib └── folderify.js /example/files/file1.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /example/files/file&2.t-t: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /test/fixtures/files/.hiddenfile: -------------------------------------------------------------------------------- 1 | ciao -------------------------------------------------------------------------------- /test/fixtures/files/file1.check: -------------------------------------------------------------------------------- 1 | this is file1 content -------------------------------------------------------------------------------- /test/fixtures/files/file1.txt: -------------------------------------------------------------------------------- 1 | this is file1_1 content -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/files/file-3-other&file.txt: -------------------------------------------------------------------------------- 1 | this is file3OtherContent content -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /**/config.json 3 | *.sublime-workspace 4 | *.log 5 | -------------------------------------------------------------------------------- /test/fixtures/source/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar", 3 | "beep": 3, 4 | "boop": true 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.sublime-* 2 | private 3 | example 4 | test 5 | .travis.yml 6 | .gitignore 7 | *.log 8 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | folderify example 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/source/include-folder-default.js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | var files = iF("./test/fixtures/files"); 3 | -------------------------------------------------------------------------------- /test/fixtures/source/include-folder-default.custom-js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | var files = iF("./test/fixtures/files"); 3 | -------------------------------------------------------------------------------- /test/fixtures/source/include-folder-regex.js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | var files = iF("./test/fixtures/files",/(.*)/); 3 | -------------------------------------------------------------------------------- /test/fixtures/source/bundle.js: -------------------------------------------------------------------------------- 1 | var iF = require('include-folder'); 2 | var files = iF(__dirname + '/../files',/(.*)/); 3 | console.dir(files); 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "extends": "eslint-config-semistandard" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/source/include-folder-filenames.js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | var files = iF("./test/fixtures/files",null,{preserveFilenames:true}); 3 | -------------------------------------------------------------------------------- /folderify.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | }, 7 | { 8 | "path": "/home/parro-it/Desktop/repos/include-folder" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/source/bundle-with-json.js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | var json = require("./data.json"); 3 | var files = iF(__dirname + "/../files",/(.*)/); 4 | console.dir(files); 5 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var includeFolder = require('include-folder'); 4 | 5 | var folder = includeFolder(__dirname + '/files'); 6 | 7 | console.log(folder.file1, folder.file2); 8 | -------------------------------------------------------------------------------- /test/fixtures/expected/unrequire-include-folder.js: -------------------------------------------------------------------------------- 1 | var iF = undefined; 2 | console.log("anything"); 3 | var another = undefined; 4 | console.log("anything"); 5 | var ciao=iF; 6 | var b; 7 | console.log(b = another); 8 | console.log(b = undefined); 9 | -------------------------------------------------------------------------------- /test/fixtures/source/unrequire-include-folder.js: -------------------------------------------------------------------------------- 1 | var iF = require("include-folder"); 2 | console.log("anything"); 3 | var another = require("include-folder"); 4 | console.log("anything"); 5 | var ciao=iF; 6 | var b; 7 | console.log(b = another); 8 | console.log(b = require("include-folder")); 9 | -------------------------------------------------------------------------------- /test/fixtures/expected/include-folder-default.js: -------------------------------------------------------------------------------- 1 | var iF = undefined; 2 | var files = (function(){var self={},fs = require("fs"); 3 | self["file3OtherFile"] = "this is file3OtherContent content"; 4 | self["file1"] = "this is file1 content"; 5 | self["file1_1"] = "this is file1_1 content"; 6 | return self})(); 7 | -------------------------------------------------------------------------------- /test/fixtures/expected/include-folder-default.custom-js: -------------------------------------------------------------------------------- 1 | var iF = undefined; 2 | var files = (function(){var self={},fs = require("fs"); 3 | self["file3OtherFile"] = "this is file3OtherContent content"; 4 | self["file1"] = "this is file1 content"; 5 | self["file1_1"] = "this is file1_1 content"; 6 | return self})(); 7 | -------------------------------------------------------------------------------- /test/fixtures/expected/include-folder-filenames.js: -------------------------------------------------------------------------------- 1 | var iF = undefined; 2 | var files = (function(){var self={},fs = require("fs"); 3 | self["file-3-other&file.txt"] = "this is file3OtherContent content"; 4 | self["file1.check"] = "this is file1 content"; 5 | self["file1.txt"] = "this is file1_1 content"; 6 | return self})(); 7 | -------------------------------------------------------------------------------- /test/fixtures/expected/include-folder-regex.js: -------------------------------------------------------------------------------- 1 | var iF = undefined; 2 | var files = (function(){var self={},fs = require("fs"); 3 | self["hiddenfile"] = "ciao"; 4 | self["file3OtherFile"] = "this is file3OtherContent content"; 5 | self["file1"] = "this is file1 content"; 6 | self["file1_1"] = "this is file1_1 content"; 7 | return self})(); 8 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "folderify-example", 3 | "version": "1.0.0", 4 | "description": "an example project for folderify", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify index.js -t folderify > example.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/parro-it/folderify" 13 | }, 14 | "author": "andrea@parro.it", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/parro-it/folderify/issues" 18 | }, 19 | "homepage": "https://github.com/parro-it/folderify", 20 | "dependencies": { 21 | "include-folder": "^0.9.0" 22 | }, 23 | "devDependencies": { 24 | "browserify": "^11.0.0", 25 | "folderify": "*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | (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= 0.10.0" 19 | }, 20 | "scripts": { 21 | "test": "eslint lib test/folderify_test.js && node test/folderify_test.js | faucet" 22 | }, 23 | "devDependencies": { 24 | "browserify": "^11.0.0", 25 | "eslint": "^1.10.3", 26 | "eslint-plugin-standard": "^1.3.1", 27 | "eslint-config-semistandard": "^5.0.0", 28 | "eslint-config-standard": "^4.4.0", 29 | "expect.js": "^0.3.1", 30 | "faucet": "0.0.1", 31 | "tape": "^4.4.0" 32 | }, 33 | "keywords": [ 34 | "folder", 35 | "content", 36 | "file", 37 | "require", 38 | "readFileSync", 39 | "browserify", 40 | "browserify-plugin", 41 | "browserify-transform" 42 | ], 43 | "dependencies": { 44 | "brfs": "~1.0.2", 45 | "concat-stream": "^1.5.0", 46 | "falafel": "^1.2.0", 47 | "include-folder": "^1.0.0", 48 | "through": "^2.3.8" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # folderify 4 | [![Build Status](https://img.shields.io/travis/parro-it/folderify.svg)](http://travis-ci.org/parro-it/folderify) 5 | [![Npm module](https://img.shields.io/npm/dt/folderify.svg)](https://npmjs.org/package/folderify) 6 | [![Code Climate](https://img.shields.io/codeclimate/github/parro-it/folderify.svg)](https://codeclimate.com/github/parro-it/folderify) 7 | [![Coverage](https://img.shields.io/codeclimate/coverage/github/parro-it/folderify.svg)](https://codeclimate.com/github/parro-it/folderify) 8 | [![Dependencies](https://img.shields.io/versioneye/d/parro-it/folderify.svg)](https://codeclimate.com/github/parro-it/folderify) 9 | 10 | 11 | 12 | 13 | 14 | browserify call to [includeFolder](https://github.com/parro-it/include-folder) 15 | 16 | 17 | This module is a plugin for [browserify](http://browserify.org) to parse the AST 18 | for `includeFolder()` calls so that you can inline folder contents into your 19 | bundles. 20 | 21 | Even though this module is intended for use with browserify, nothing about it is 22 | particularly specific to browserify so it should be generally useful in other 23 | projects. 24 | 25 | ## Getting Started 26 | Install the module with: `npm install folderify --save` 27 | 28 | then, for a main.js: 29 | 30 | ``` js 31 | var includeFolder = require('include-folder'), 32 | folder = includeFolder("./aFolder"); 33 | ``` 34 | 35 | and a [aFolder like this](https://github.com/parro-it/include-folder/tree/master/test/files): 36 | 37 | 38 | when you run the browserify command: 39 | 40 | ``` 41 | $ browserify -t folderify main.js > bundle.js 42 | ``` 43 | 44 | now in the bundle output file you get, 45 | 46 | ``` js 47 | var includeFolder = undefined, 48 | folder = { 49 | file3OtherFile: 'this is file3OtherContent content', 50 | file1: 'this is file1 content', 51 | file1_1: 'this is file1_1 content' 52 | }; 53 | ``` 54 | 55 | 56 | ## or with the api 57 | 58 | ``` js 59 | var browserify = require('browserify'); 60 | var fs = require('fs'); 61 | 62 | var b = browserify('example/main.js'); 63 | b.transform('folderify'); 64 | 65 | b.bundle().pipe(fs.createWriteStream('bundle.js')); 66 | ``` 67 | 68 | 69 | 70 | 71 | ##How it works 72 | 73 | Folderify inline a whole directory content in browserify results. 74 | 75 | 1. It uses falafel to intercepts calls to [include-folder](https://github.com/parro-it/include-folder) 76 | 2. use include-folder to generate source code of a function with a fs.readFileSync call for each file in directory 77 | 3. feed brfs stream with generated source code 78 | 4. replace include-folder call with brfs output 79 | 80 | 81 | ##Use cases 82 | 83 | I use it to inline my HTML templates folder when I browserify 84 | sites, but I guess it could be useful in many situations... 85 | 86 | ##Custom file extensions 87 | 88 | By default, supported file extensions are: 89 | - `.es` 90 | - `.es6` 91 | - `.js` 92 | - `.jsx` 93 | 94 | The list is exposed as a property `validExtensions` on the folderify function and can be easily extended: 95 | ```js 96 | var browserify = require('browserify'); 97 | var folderify = require('folderify'); 98 | folderify.validExtensions.push('.custom-js'); 99 | 100 | var b = browserify('example/main.js'); 101 | b.transform(folderify); 102 | ``` 103 | 104 | 105 | ## Contributing 106 | 107 | In lieu of a formal styleguide, take care to maintain the existing coding style. 108 | Add unit tests for any new or changed functionality. 109 | 110 | 111 | ## License 112 | 113 | Copyright (c) 2013 Andrea Parodi 114 | 115 | Licensed under the MIT license. 116 | 117 | -------------------------------------------------------------------------------- /test/folderify_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var concat = require('concat-stream'); 5 | var test = require('tape'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var folderify = require('../lib/folderify'); 9 | var browserify = require('browserify'); 10 | 11 | function rf (file) { 12 | return fs.readFileSync(path.join(__dirname, file), 'utf8'); 13 | } 14 | 15 | function checkTransform (sourcefile, expectedfile, t) { 16 | var source = rf(sourcefile); 17 | var expected = rf(expectedfile); 18 | var result = concat(function (data) { 19 | expect(data).to.be.equal(expected); 20 | t.end(); 21 | }); 22 | var stream = folderify(sourcefile); 23 | stream.pipe(result); 24 | stream.write(source); 25 | stream.end(); 26 | } 27 | 28 | test('folderify exports a function', function (t) { 29 | expect(folderify).to.be.an('function'); 30 | t.end(); 31 | }); 32 | 33 | test('return a through stream', function (t) { 34 | expect(folderify().constructor.name).to.be.equal('Stream'); 35 | t.end(); 36 | }); 37 | 38 | test('un-requires include-folder', function (t) { 39 | checkTransform( 40 | 'fixtures/source/unrequire-include-folder.js', 41 | 'fixtures/expected/unrequire-include-folder.js', 42 | t 43 | ); 44 | }); 45 | 46 | test('replaces includeFolder call with `files` array', function (t) { 47 | checkTransform( 48 | 'fixtures/source/include-folder-default.js', 49 | 'fixtures/expected/include-folder-default.js', 50 | t 51 | ); 52 | }); 53 | 54 | test('respects includeFolder pattern-match arguments', function (t) { 55 | checkTransform( 56 | 'fixtures/source/include-folder-regex.js', 57 | 'fixtures/expected/include-folder-regex.js', 58 | t 59 | ); 60 | }); 61 | 62 | test('preserves filenames when option is set', function (t) { 63 | checkTransform( 64 | 'fixtures/source/include-folder-filenames.js', 65 | 'fixtures/expected/include-folder-filenames.js', 66 | t 67 | ); 68 | }); 69 | 70 | test('as a browserify transform', function (s) { 71 | var expectedBundle = rf('fixtures/expected/bundle.js'); 72 | var expectedBundleWithJson = rf('fixtures/expected/bundle-with-json.js'); 73 | 74 | s.test('doesn\'t require brfs', function (t) { 75 | var b = browserify(path.join(__dirname, 'fixtures/source/bundle.js')); 76 | b.transform(folderify); 77 | 78 | b 79 | .bundle() 80 | .on('error', t.end.bind(t)) 81 | .pipe(concat(function (data) { 82 | expect(data.toString('utf8')).to.be.equal(expectedBundle); 83 | t.end(); 84 | })); 85 | }); 86 | 87 | s.test('supports brfs running after folderify', function (t) { 88 | var b = browserify(path.join(__dirname, 'fixtures/source/bundle.js')); 89 | b.transform(folderify); 90 | b.transform('brfs'); 91 | 92 | b 93 | .bundle() 94 | .on('error', t.end.bind(t)) 95 | .pipe(concat(function (data) { 96 | expect(data.toString('utf8')).to.be.equal(expectedBundle); 97 | t.end(); 98 | })); 99 | }); 100 | 101 | s.test('support brfs running before folderify', function (t) { 102 | var b = browserify(__dirname + '/fixtures/source/bundle.js'); 103 | b.transform('brfs'); 104 | b.transform(folderify); 105 | 106 | b 107 | .bundle() 108 | .on('error', t.end.bind(t)) 109 | .pipe(concat(function (data) { 110 | expect(data.toString('utf8')).to.be.equal(expectedBundle); 111 | t.end(); 112 | })); 113 | }); 114 | 115 | s.test('support bundles with non JavaScript files', function (t) { 116 | var b = browserify(__dirname + '/fixtures/source/bundle-with-json.js'); 117 | b.transform('brfs'); 118 | b.transform(folderify); 119 | 120 | b 121 | .bundle() 122 | .on('error', t.end.bind(t)) 123 | .pipe(concat(function (data) { 124 | expect(data.toString('utf8')).to.be.equal(expectedBundleWithJson); 125 | t.end(); 126 | })); 127 | }); 128 | }); 129 | 130 | test('Custom extensions', function (s) { 131 | s.test('expose validExtensions', function (t) { 132 | expect(folderify.validExtensions).to.be.an('array'); 133 | t.end(); 134 | }); 135 | 136 | s.test('can modify validExtensions for allowing custom extensions', function (t) { 137 | var origin = folderify.validExtensions; 138 | folderify.validExtensions = ['.custom-js']; 139 | checkTransform( 140 | 'fixtures/source/include-folder-default.custom-js', 141 | 'fixtures/expected/include-folder-default.custom-js', 142 | { 143 | end: function (err) { 144 | folderify.validExtensions = origin; 145 | t.end(err); 146 | } 147 | } 148 | ); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /lib/folderify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var brfs = require('brfs'); 4 | var concat = require('concat-stream'); 5 | var includeFolder = require('include-folder'); 6 | var falafel = require('falafel'); 7 | var through = require('through'); 8 | var path = require('path'); 9 | var fs = require('fs'); 10 | 11 | function folderify (file) { 12 | var data; 13 | var pending; 14 | var ifNames; 15 | var tr; 16 | var itsDirName; 17 | var itsFileName; 18 | 19 | function isIF (node) { 20 | var c = node.callee; 21 | 22 | return c && 23 | node.type === 'CallExpression' && 24 | node.arguments && 25 | node.arguments.length && 26 | node.arguments[0].value === 'include-folder' && 27 | 28 | c.type === 'Identifier' && 29 | c.name === 'require'; 30 | } 31 | 32 | function isParsableFileName (filename) { 33 | return folderify.validExtensions.indexOf(path.extname(filename)) >= 0; 34 | } 35 | 36 | function write (buf) { 37 | data += buf; 38 | } 39 | 40 | function finish (output) { 41 | tr.queue(String(output)); 42 | tr.queue(null); 43 | } 44 | 45 | function isVarDecl (node) { 46 | return isIF(node) && 47 | node.parent.type === 'VariableDeclarator' && 48 | node.parent.id.type === 'Identifier'; 49 | } 50 | 51 | function isVarAssign (node) { 52 | return isIF(node) && 53 | node.parent.type === 'AssignmentExpression' && 54 | node.parent.left.type === 'Identifier' 55 | 56 | ; 57 | } 58 | 59 | function unrequireIF (node) { 60 | function unrequire (n) { 61 | n.update('undefined'); 62 | } 63 | if (isVarDecl(node)) { 64 | ifNames[node.parent.id.name] = true; 65 | 66 | unrequire(node.parent.init); 67 | } else if (isVarAssign(node)) { 68 | ifNames[node.parent.left.name] = true; 69 | 70 | unrequire(node.parent.right); 71 | } 72 | } 73 | 74 | function buildOriginalSource (folder, filter, options, stream) { 75 | if (!filter) { 76 | filter = /^[^.].*$/; 77 | } 78 | 79 | if (typeof options !== 'object') { 80 | options = {}; 81 | } 82 | 83 | // Emit file events for each file in this folder. 84 | // Used by a file-watcher like watchify. 85 | fs.readdirSync(folder) 86 | .filter(filter.test.bind(filter)) 87 | .map(function (file) { 88 | stream.emit('file', path.join(folder, file)); 89 | }); 90 | 91 | var fnBody = includeFolder.buildSource(folder, filter, options); 92 | 93 | return '(function(){' + 94 | fnBody + 95 | '})()'; 96 | } 97 | 98 | function parse (stream) { 99 | if (!isParsableFileName(itsFileName)) { 100 | finish(data); 101 | return; 102 | } 103 | 104 | var output = falafel(data, function (node) { 105 | unrequireIF(node); 106 | 107 | if (node.type === 'CallExpression' && node.callee && ifNames[node.callee.name] === true) { 108 | var folderSourceCode = node.arguments[0].source(); 109 | 110 | folderSourceCode = folderSourceCode 111 | .replace(/__dirname/g, '"' + itsDirName + '"') 112 | .replace(/__filename/g, '"' + itsFileName + '"') 113 | .replace(/\\/g, '/'); 114 | 115 | var folder = eval(folderSourceCode); // eslint-disable-line no-eval 116 | 117 | var filesFilter; 118 | 119 | if (node.arguments.length > 1) { 120 | filesFilter = eval(node.arguments[1].source()); // eslint-disable-line no-eval 121 | } 122 | 123 | var options; 124 | 125 | if (node.arguments.length > 2) { 126 | options = eval('(' + node.arguments[2].source() + ')'); // eslint-disable-line no-eval 127 | } 128 | 129 | var originalSource; 130 | 131 | originalSource = buildOriginalSource(folder, filesFilter, options, stream); 132 | 133 | var brfsStream = brfs(folder + 'bogus.txt'); 134 | 135 | var brfsResult = concat({encoding: 'string'}, function (result) { 136 | node.update(result); 137 | pending--; 138 | if (pending === 0) { 139 | finish(output); 140 | } 141 | }); 142 | 143 | brfsStream.on('error', function (err) { 144 | this.emit('error', err); 145 | }); 146 | 147 | brfsStream.pipe(brfsResult); 148 | 149 | brfsStream.write(originalSource); 150 | brfsStream.end(); 151 | 152 | pending++; 153 | } 154 | }); 155 | 156 | if (pending === 0) { 157 | finish(output); 158 | } 159 | } 160 | 161 | function end () { 162 | try { 163 | parse(this); 164 | } catch (err) { 165 | this.emit('error', err); 166 | } 167 | } 168 | 169 | itsDirName = path.dirname(file); 170 | itsFileName = file; 171 | 172 | data = ''; 173 | pending = 0; 174 | ifNames = {}; 175 | tr = through(write, end); 176 | 177 | return tr; 178 | } 179 | 180 | folderify.validExtensions = [ 181 | '.es', 182 | '.es6', 183 | '.js', 184 | '.jsx' 185 | ]; 186 | 187 | module.exports = folderify; 188 | --------------------------------------------------------------------------------