├── .DS_Store ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── cmd.js └── help.txt ├── index.js ├── lib ├── asset.js ├── package.js └── parcel.js ├── package.json └── test ├── .DS_Store ├── cli ├── index.html ├── main.js ├── node_modules │ └── my-module │ │ ├── index.js │ │ ├── myModule.css │ │ ├── package.json │ │ └── template.tmpl ├── package.json ├── run-cli-tests.sh └── scripts │ ├── bundle.sh │ ├── cli-tests.sh │ └── watch.sh ├── node_modules ├── .DS_Store ├── base-css │ ├── base.css │ ├── index.js │ └── package.json ├── footer │ ├── header.css │ ├── index.js │ └── package.json ├── header │ ├── header.css │ ├── index.js │ └── package.json ├── my-module │ ├── index.js │ ├── myModule.css │ ├── myModule.tmpl │ └── package.json └── my-other-module │ ├── index.js │ ├── myOtherModuleBlue.css │ ├── myOtherModuleRed.css │ └── package.json ├── package.json ├── page1 ├── empty.css ├── main.js └── package.json ├── page2 ├── index.js ├── package.json └── styles.css ├── page3 ├── index.js └── package.json ├── page4 ├── main.js ├── main.scss ├── main.tmpl └── package.json ├── page5 └── main.js ├── page6 ├── main.js ├── main.scss └── package.json ├── page7 ├── main.css ├── main.js └── package.json ├── test.js ├── watch-test-output ├── bundle.css └── bundle.js └── watchTest.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotundasoftware/parcelify/8ba813a6a1e93bddf398c8d5042e9e7583254dbe/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ### v1.2.0 4 | * added support for multiple javascript entry points via the `bundlesByEntryPoint` option 5 | 6 | ### v1.1.0 7 | * added support for single string as appTransformDirs 8 | * added single letter aliases for logLevel and appTransformDirs options 9 | 10 | ### v1.0.0 11 | * changed to browserify plugin architecture 12 | * now can be used with any compatible version of browserify 13 | * the `transforms` package.json no longer supports js transforms 14 | * the following API options were removed: packageTransform, browserifyInstance, browserifyBundleOptions 15 | 16 | ### v0.12.0 17 | * add log level option 18 | 19 | ### v0.11.0 20 | * BREAKING CHANGE: Corrected spelling of "appTranform" and "appTranformDir" to "appTransforms" and "appTransformDirs" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Rotunda Software, LLC. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parcelify 2 | 3 | Add css to your npm modules consumed with [browserify](http://browserify.org/). 4 | 5 | * Just add a `style` key to your `package.json` to specify the package's css file(s). 6 | * Efficiently transform scss / less to css, etc. using transform streams. 7 | * Rebuild css bundles automatically on changes in watch mode. 8 | * Leverage a robust API to create larger build tools like [cartero](https://github.com/rotundasoftware/cartero). 9 | 10 | Many thanks to [James Halliday](https://twitter.com/substack) for his help and guidance in bringing this project into reality. 11 | 12 | [![build status](https://secure.travis-ci.org/rotundasoftware/parcelify.png)](https://api.travis-ci.org/rotundasoftware/parcelify.svg?branch=master) 13 | 14 | ## How dat work? 15 | 16 | ``` 17 | ├── node_modules 18 | │   └── my-module 19 | │   ├── index.js 20 | │   ├── myModule.css 21 | │   └── package.json 22 | └── main.js 23 | ``` 24 | 25 | In my-module's `package.json`, the module's style assets just need to be enumerated (in [glob](https://github.com/isaacs/node-glob#glob-primer) notation): 26 | 27 | ``` 28 | { 29 | "name" : "my-module", 30 | "version": "1.5.0", 31 | "style" : "*.css" // glob notation. can optionally be an array 32 | } 33 | ``` 34 | 35 | In `main.js`, everything looks the same: 36 | 37 | ```javascript 38 | myModule = require( 'my-module' ); 39 | 40 | console.log( 'hello world' ); 41 | ``` 42 | 43 | Now just run parcelify as a [browserify plugin](https://github.com/substack/node-browserify#plugins) using browserify's `-p` flag: 44 | 45 | ``` 46 | $ browserify main.js -o bundle.js -p [ parcelify -o bundle.css ] 47 | ``` 48 | 49 | Parcelify will concatenate all the css files in the modules on which `main.js` depends -- in this case just `myModule.css` -- in the order of the js dependency graph, and write the output to `bundle.css`. 50 | 51 | Use the `-w` flag to keep the bundle up to date when changes are made in dev mode: 52 | 53 | ``` 54 | $ watchify main.js -o bundle.js -p [ parcelify -wo bundle.css ] 55 | ``` 56 | 57 | ## Installation 58 | 59 | In your project directory, 60 | 61 | ``` 62 | $ npm install parcelify 63 | ``` 64 | 65 | ## Plugin options 66 | 67 | ``` 68 | --cssBundle, -o Path of the destination css bundle. 69 | 70 | --watch, -w Watch mode - automatically rebuild css bundle as appropriate for changes. 71 | 72 | --transform, -t Name or path of an application transform. (See discussion of application transforms.) 73 | 74 | --transformDir, -d Path of an application transform directory. (See discussion of application transforms.) 75 | 76 | --loglevel -l Set the verbosity of npmlog, eg. "silent", "error", "warn", "info", "verbose" 77 | ``` 78 | 79 | ## Transforms 80 | 81 | ### Local (package specific) transforms 82 | 83 | The safest and most portable way to apply transforms like sass -> css is using the `transforms` key in a package's package.json. The key should be an array of names or file paths of [transform modules](https://github.com/substack/module-deps#transforms). For example, 84 | 85 | ``` 86 | { 87 | "name": "my-module", 88 | "description": "Example module.", 89 | "version": "1.5.0", 90 | "style" : "*.scss", 91 | "transforms" : [ "sass-css-stream" ], 92 | "dependencies" : { 93 | "sass-css-stream": "^0.0.1" 94 | } 95 | } 96 | ``` 97 | 98 | All transform modules are called on all assets. It is up to the transform module to determine whether or not it should apply itself to a file (usually based on the file extension). 99 | 100 | ### Application level transforms 101 | 102 | You can apply transforms to all packages within an entire branch of the directory tree using the `appTransforms` and `appTransformDirs` options or their corresponding command line arguments. (Packages inside a `node_modules` folder located inside one of the supplied directories are not effected.) For example, to transform all sass files inside the current working directory to css, 103 | 104 | ``` 105 | $ browserify main.js -o bundle.js -p [ parcelify -o bundle.css -t sass-css-stream -d . ] 106 | ``` 107 | 108 | ### Catalog of transforms 109 | 110 | The following transforms can be used with parcelify. Please let us know if you develop a transform and we'll include it in this list. 111 | 112 | * [sass-css-stream](https://github.com/rotundasoftware/sass-css-stream) - convert sass to css. 113 | * [less-css-stream](https://github.com/jsdf/less-css-stream) - convert less to css. 114 | * [sass-bourbon-transform](https://github.com/rotundasoftware/sass-bourbon-transform) - convert sass to css with [bourbon](http://bourbon.io/). 115 | * [css-img-datauri-stream](https://github.com/jbkirby/css-img-datauri-stream) - inline images in your css with data urls. 116 | * [parcelify-import-resolver](https://github.com/johanneslumpe/parcelify-import-resolver) - resolve paths using the node resolve algorithm. 117 | 118 | ## API 119 | 120 | First instantiate a parcelify instance. 121 | 122 | #### p = parcelify( b, [options] ) 123 | 124 | `b` is a browserify instance. Then you must call `b.bundle()` to start browserify, which will automatically trigger parcelify. Options are: 125 | 126 | * `bundles` - A hash that maps asset types to bundle paths. You will generally just want an entry for a `style` bundle, but arbitrary asset types are supported. Default: 127 | 128 | ```javascript 129 | bundles : { 130 | style : 'bundle.css' // bundle `style` assets and output here 131 | } 132 | ``` 133 | * `bundlesByEntryPoint` (default: undefined) - If multiple entry points have been supplied to the browserify instance, this option is used to determine the output bundles for each entry point, instead of `bundles`. For example: 134 | ``` 135 | { 136 | '/Users/me/myWebApp/views/page1/page1.js' : { style : 'static/page1.css' }, 137 | '/Users/me/myWebApp/views/page2/page2.js' : { style : 'static/page2.css' } 138 | } 139 | ``` 140 | * `appTransforms` (default: undefined) - An array of [transform modules](https://github.com/substack/module-deps#transforms) names / paths or functions to be applied to all packages in directories in the `appTransformDirs` array. 141 | * `appTransformDirs` (default: undefined) - `appTransforms` are applied to any packages that are within one of the directories in this array. (The recursive search is stopped on `node_module` directories.) 142 | * `logLevel` - set the [npmlog](https://www.npmjs.org/package/npmlog) logging level. 143 | * `watch` (default: false) - automatically rebuild bundles as appropriate for changes. 144 | 145 | A parcelify object is returned, which is an event emitter. 146 | 147 | ### p.on( 'done', function(){} ); 148 | Called when all bundles have been output. 149 | 150 | ### p.on( 'error', function( err ){} ); 151 | Called when an error occurs. 152 | 153 | ### p.on( 'packageCreated', function( package ){} ); 154 | Called when a new package is created. `package` is a package object as defined in `lib/package.js`. 155 | 156 | ### p.on( 'assetUpdated', function( eventType, asset ){} ); 157 | Called when a style asset is updated in watch mode. `eventType` is `'added'`, `'changed'`, or `'deleted'`, and `asset` is an asset object as defined in `lib/asset.js`. 158 | 159 | ## Client side templates and other assets 160 | 161 | Parcelify actually supports concatenation / enumeration of arbitrary asset types. Just add a bundle for an asset type in the `bundles` option and use the same key to enumerate assets of that type in package.json. 162 | 163 | A tempting use case for this feature is client side templates - just include a `template` key in package.json and a corresponding entry in the `bundles` option, and you have a bundle of client side templates. However, if you plan to share your packages we recommend against this practice as it makes your packages difficult to consume. Instead we recommend using a browserify transform like [nunjucksify](https://github.com/rotundasoftware/nunjucksify) or [node-hbsfy](https://github.com/epeli/node-hbsfy) to precompile templates and `require` them explicitly from your JavaScript files. 164 | 165 | For the case of assets like images, that do not need to be concatenated, you can specify a `null` path for the bundle. Parcelify will collect all assets of that type but not concatenate them. You can then process the individual assets further using the event callbacks. See [cartero](https://github.com/rotundasoftware/cartero) for an example of this more advanced use case. 166 | 167 | ### Command line usage (depreciated) 168 | 169 | You can also run parcelify directly from the command line, although this functionality is depreciated. Note browserify needs to be installed (or watchify, in the case that the -w flag is used). 170 | 171 | ``` 172 | $ parcelify main.js -o bundle.css 173 | ``` 174 | 175 | In addition to the options available when running parcelify as a browserify plugin, the follow options are also supported from the command line. 176 | 177 | ``` 178 | --jsBundle, -j Path to save the JavaScript bundle (i.e. browserify's output). 179 | 180 | --maps, -m Enable JavaScript source maps in js bundles (for dev mode). 181 | 182 | --help, -h Show this message 183 | ``` 184 | 185 | ## Contributors 186 | 187 | * [James Halliday](https://twitter.com/substack) (Initial design, sage advice, supporting modules) 188 | * [David Beck](https://twitter.com/davegbeck) 189 | * [Oleg Seletsky](https://github.com/go-oleg) 190 | 191 | ## License 192 | 193 | MIT 194 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var parcelify = require( '../' ); 4 | var browserify = require( 'browserify' ); 5 | var watchify = require( 'watchify' ); 6 | var minimist = require( 'minimist' ); 7 | var path = require( 'path' ); 8 | var fs = require( 'fs' ); 9 | var os = require( 'os' ); 10 | var tmpdir = ( os.tmpdir || os.tmpDir )(); 11 | 12 | var argv = minimist( process.argv.slice(2), 13 | { 14 | alias : { 15 | jsBundle : 'j', 16 | cssBundle : 'c', 17 | transform : 't', 18 | transformDirs : 'd', 19 | watch : 'w', 20 | maps : 'm', 21 | help : 'h' 22 | }, 23 | boolean : [ 'watch', 'help', 'maps' ] 24 | } 25 | ); 26 | 27 | if( argv.help ) { 28 | return fs.createReadStream( __dirname + '/help.txt' ).pipe( process.stdout ).on( 'close', function() { 29 | process.exit( 0 ); 30 | } ); 31 | } 32 | 33 | // resolve to absolute paths 34 | var jsBundle = resolvePath( argv.jsBundle ) || path.resolve( tmpdir, 'parcelify-js-bundle-' + Math.random() ); 35 | var cssBundle = resolvePath( argv.cssBundle ); 36 | var tmplBundle = resolvePath( argv.tmplBundle ); 37 | var mainPath = resolvePath( argv._[0] ); 38 | var appTransforms = argv.transform; 39 | var appTransformDirs = argv.transformDirs; 40 | var defaultTransforms = argv.transform; 41 | var logLevel = argv.loglevel; 42 | var watch = argv.watch; 43 | var maps = argv.maps; 44 | 45 | if( typeof appTransformDirs === 'string' ) appTransformDirs = [ appTransformDirs ]; 46 | 47 | if( ! mainPath ) { 48 | console.log( 'No entry point specified' ); 49 | process.exit( 1 ); 50 | } 51 | 52 | var browserifyInstance = browserify( mainPath ); 53 | if( watch ) watchify( browserifyInstance ); 54 | 55 | var p = parcelify( browserifyInstance, { 56 | bundles : { 57 | script : jsBundle, 58 | style : cssBundle, 59 | template : tmplBundle 60 | }, 61 | appTransforms : appTransforms, 62 | appTransformDirs : appTransformDirs, 63 | browserifyBundleOptions : { 64 | debug : maps 65 | }, 66 | watch : watch, 67 | logLevel : logLevel 68 | } ); 69 | 70 | browserifyInstance.bundle().pipe( fs.createWriteStream( jsBundle ) ); 71 | 72 | p.on( 'error', function( err ) { 73 | console.log( err.stack ); 74 | process.exit( 1 ); 75 | } ); 76 | 77 | p.on( 'done', function() { 78 | if( ! watch ) 79 | process.exit( 0 ); 80 | } ); 81 | 82 | function resolvePath( inputPath ) { 83 | return inputPath ? path.resolve( inputPath ) : inputPath; 84 | } 85 | -------------------------------------------------------------------------------- /bin/help.txt: -------------------------------------------------------------------------------- 1 | 2 | EXAMPLE: 3 | $ parcelify main.js -j bundle.js -c bundle.css 4 | 5 | OPTIONS: 6 | --cssBundle, -o Path of a destination css bundle. 7 | 8 | --jsBundle, -j Path of the JavaScript bundle (i.e. browserify's output). 9 | 10 | --watch, -w Watch mode - automatically rebuild bundles as appropriate for changes. 11 | 12 | --maps, -m Enable JavaScript source maps in js bundles (for dev mode). 13 | 14 | --transform, -t Name or path of an application transform. 15 | 16 | --transformDir Path of an application transform directory. 17 | 18 | --loglevel Set the verbosity of npmlog, eg. "silent", "error", "warn", "info", "verbose" 19 | 20 | --help, -h Show this message 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var parcelMap = require( 'parcel-map' ); 3 | var shasum = require( 'shasum' ); 4 | var through2 = require( 'through2' ); 5 | var path = require( 'path' ); 6 | var _ = require( 'underscore' ); 7 | var async = require( 'async' ); 8 | var glob = require( 'glob' ); 9 | var Package = require( './lib/package' ); 10 | var Parcel = require( './lib/parcel' ); 11 | var inherits = require( 'inherits' ); 12 | var log = require( 'npmlog' ); 13 | 14 | var EventEmitter = require('events').EventEmitter; 15 | var Package = require('./lib/package.js'); 16 | 17 | module.exports = Parcelify; 18 | 19 | inherits( Parcelify, EventEmitter ); 20 | 21 | function Parcelify( browserifyInstance, options ) { 22 | var _this = this; 23 | 24 | if( ! ( this instanceof Parcelify ) ) return new Parcelify( browserifyInstance, options ); 25 | 26 | options = _.defaults( {}, options, { 27 | bundles : {}, // ignored when bundlesByEntryPoint is provided 28 | bundlesByEntryPoint : undefined, // required when there are multiple entry points 29 | 30 | appTransforms : undefined, 31 | appTransformDirs : undefined, 32 | 33 | watch : undefined, 34 | logLevel : undefined, 35 | 36 | // used internally or in order to share packages between multiple parcelify instances 37 | existingPackages : undefined 38 | } ); 39 | 40 | // option aliases 41 | if( _.isUndefined( options.bundles.style ) ) options.bundles.style = options.o || 'bundle.css'; 42 | if( _.isUndefined( options.appTransforms ) ) options.appTransforms = options.t || []; 43 | if( _.isUndefined( options.appTransformDirs ) ) options.appTransformDirs = options.d || []; 44 | if( _.isUndefined( options.watch ) ) options.watch = options.w || false; 45 | if( _.isUndefined( options.logLevel ) ) options.logLevel = options.l; 46 | 47 | if( _.isString( options.appTransforms ) ) options.appTransforms = [ options.appTransforms ]; 48 | 49 | this.watching = false; 50 | 51 | if( options.logLevel ) log.level = options.logLevel; 52 | 53 | // before we jump the gun, return from this function so we can listen to events from the calling function 54 | process.nextTick( function() { 55 | var existingPackages = options.existingPackages || {}; 56 | var mappedAssets = {}; 57 | 58 | _this.on( 'error', function( err ) { 59 | log.error( '', err ); // otherwise errors kill our watch task. Especially bad for transform errors 60 | } ); 61 | 62 | if( options.watch ) { 63 | browserifyInstance.on( 'update', _.debounce( function( changedMains ) { 64 | _this.watching = true; 65 | 66 | var processParcelOptions = _.clone( options ); 67 | processParcelOptions.existingPackages = existingPackages; 68 | processParcelOptions.mappedAssets = mappedAssets; 69 | 70 | _this.processParcels( browserifyInstance, processParcelOptions, function( err ) { 71 | if( err ) _this.emit( 'error', err ); 72 | } ); 73 | }, 1000, true ) ); 74 | } 75 | 76 | var processParcelOptions = _.clone( options ); 77 | processParcelOptions.existingPackages = existingPackages; 78 | processParcelOptions.mappedAssets = mappedAssets; 79 | 80 | _this.processParcels( browserifyInstance, processParcelOptions, function( err ) { 81 | if( err ) _this.emit( 'error', err ); 82 | } ); 83 | } ); 84 | 85 | return _this; 86 | } 87 | 88 | Parcelify.prototype.processParcels = function( browserifyInstance, options, callback ) { 89 | var _this = this; 90 | 91 | var existingPackages = options.existingPackages || {}; 92 | var assetTypes; 93 | 94 | if( options.bundlesByEntryPoint ) { 95 | assetTypes = _.reduce( options.bundlesByEntryPoint, function( assetTypesMemo, bundlesForThisEntryPoint ) { 96 | return _.union( assetTypesMemo, _.keys( bundlesForThisEntryPoint ) ); 97 | }, [] ) 98 | } else assetTypes = _.keys( options.bundles ); 99 | 100 | var packages = _.reduce( existingPackages, function( memo, thisPackage, thisPackageId ) { 101 | memo[ thisPackage.path ] = thisPackage.package; 102 | return memo; 103 | }, {} ); 104 | 105 | var dependencies = _.reduce( existingPackages, function( memo, thisPackage, thisPackageId ) { 106 | memo[ thisPackage.path ] = _.map( thisPackage.dependencies, function( thisDependency ) { return thisDependency.path; } ); 107 | return memo; 108 | }, {} ); 109 | 110 | var parcelMapEmitter = parcelMap( browserifyInstance, { 111 | keys : assetTypes, 112 | files : options.mappedAssets, 113 | packages : packages, 114 | dependencies : dependencies 115 | } ); 116 | 117 | parcelMapEmitter.on( 'error', function( err ) { 118 | return callback( err ); 119 | } ); 120 | 121 | parcelMapEmitter.on( 'done', function( parcelMapResult ) { 122 | _this.instantiateParcelAndPackagesFromMap( parcelMapResult, existingPackages, assetTypes, options.appTransforms, options.appTransformDirs, function( err, packagesThatWereCreated ) { 123 | if( err ) return callback( err ); 124 | 125 | var parcelsThatWereCreated = []; 126 | 127 | process.nextTick( function() { 128 | async.series( [ function( nextSeries ) { 129 | // fire package events for any new packages 130 | _.each( packagesThatWereCreated, function( thisPackage ) { 131 | var isParcel = thisPackage.isParcel; 132 | 133 | log.verbose( 'Created new ' + ( isParcel ? 'parcel' : 'package' ) + ' ' + thisPackage.path + ' with id ' + thisPackage.id ); 134 | 135 | existingPackages[ thisPackage.id ] = thisPackage; 136 | if( isParcel ) { 137 | _this._setupParcelEventRelays( thisPackage ); 138 | parcelsThatWereCreated.push( thisPackage ); 139 | } 140 | 141 | thisPackage.on( 'error', function( err ) { 142 | _this.emit( 'error', err ); 143 | } ); 144 | 145 | _this.emit( 'packageCreated', thisPackage ); 146 | } ); 147 | 148 | nextSeries(); 149 | }, function( nextSeries ) { 150 | if( parcelsThatWereCreated.length > 1 && ! options.bundlesByEntryPoint ) { 151 | return nextSeries( new Error( 'Multiple entry points detected, but bundlesByEntryPoint option was not supplied.' ) ); 152 | } 153 | 154 | if( parcelsThatWereCreated.length === 1 && ! options.bundlesByEntryPoint ) { 155 | options.bundlesByEntryPoint = {}; 156 | options.bundlesByEntryPoint[ _.first( parcelsThatWereCreated ).mainPath ] = options.bundles; 157 | } 158 | 159 | // we are done copying packages and collecting our asset streams. Now write our bundles to disk. 160 | async.each( _.values( packagesThatWereCreated ), function( thisParcel, nextEach ) { 161 | if( ! thisParcel.isParcel ) return nextEach(); 162 | 163 | var thisParcelBundles = options.bundlesByEntryPoint[ thisParcel.mainPath ]; 164 | 165 | async.each( Object.keys( thisParcelBundles ), function( thisAssetType, nextEach ) { 166 | var thisBundlePath = thisParcelBundles[ thisAssetType ]; 167 | if( ! thisBundlePath ) return nextEach(); 168 | 169 | thisParcel.writeBundle( thisAssetType, thisBundlePath, function( err, bundleWasWritten ) { 170 | // don't stop writing other bundles if there was an error on this one. errors happen 171 | // frequently with transforms.. like invalid scss, etc. don't stop the show, just 172 | // keep going with our other bundles. 173 | 174 | if( err ) _this.emit( 'error', err ); 175 | else if( bundleWasWritten ) _this.emit( 'bundleWritten', thisBundlePath, thisAssetType, thisParcel, _this.watching ); 176 | 177 | nextEach(); 178 | } ); 179 | }, nextEach ); 180 | }, nextSeries ); 181 | }, function( nextSeries ) { 182 | if( options.watch ) { 183 | // we only create glob watchers for the packages that parcel added to the manifest. Again, we want to avoid doubling up 184 | // work in situations where we have multiple parcelify instances running that share common bundles 185 | _.each( packagesThatWereCreated, function( thisPackage ) { 186 | thisPackage.createWatchers( assetTypes, browserifyInstance._options.packageFilter, options.appTransforms, options.appTransformDirs ); 187 | if( thisPackage.isParcel ) { 188 | thisPackage.attachWatchListeners( options.bundlesByEntryPoint[ thisPackage.mainPath ] ); 189 | } 190 | } ); 191 | } 192 | 193 | if( ! _this.watching ) _this.emit( 'done' ); 194 | 195 | nextSeries(); 196 | } ], callback ); 197 | } ); 198 | 199 | return callback( null ); 200 | } ); 201 | } ); 202 | }; 203 | 204 | Parcelify.prototype.instantiateParcelAndPackagesFromMap = function( parcelMapResult, existingPacakages, assetTypes, appTransforms, appTransformDirs, callback ) { 205 | var _this = this; 206 | var mappedParcel = null; 207 | var packagesThatWereCreated = {}; 208 | 209 | async.series( [ function( nextSeries ) { 210 | async.each( Object.keys( parcelMapResult.packages ), function( thisPackageId, nextPackageId ) { 211 | var packageJson = parcelMapResult.packages[ thisPackageId ]; 212 | var packageOptions = {}; 213 | 214 | async.waterfall( [ function( nextWaterfall ) { 215 | Package.getOptionsFromPackageJson( thisPackageId, packageJson.__path, packageJson, assetTypes, appTransforms, appTransformDirs, nextWaterfall ); 216 | }, function( packageOptions, nextWaterfall ) { 217 | var thisPackage; 218 | 219 | var thisPackageIsAParcel = packageJson.__isParcel; 220 | 221 | if( ! existingPacakages[ thisPackageId ] ) { 222 | if( thisPackageIsAParcel ) { 223 | thisPackage = packagesThatWereCreated[ thisPackageId ] = new Parcel( _.extend( packageOptions, { mainPath : packageJson.__mainPath } ) ); 224 | } 225 | else thisPackage = packagesThatWereCreated[ thisPackageId ] = new Package( packageOptions ); 226 | 227 | thisPackage.createAllAssets( assetTypes ); 228 | } 229 | else if( thisPackageIsAParcel && ! existingPacakages[ thisPackageId ] instanceof Parcel ) { 230 | // k tricky here.. if this package is a parcel, but it exists in the manifest as a plain 231 | // old package, then we gotta recreate this package as a parcel. also we have to update 232 | // any parcels that are dependENTS of this package/parcel in order to use the new 233 | // assets that we are about to create. man, scary, hope nothing gets broke in the process. 234 | // we could also pre-preemptively list out which packages are parcels by adding an option 235 | // to parcelify itself, but that seems a little weird. In the context of cartero that 236 | // depends on the path of each package relative to the parcelDirs cartero option. 237 | var oldPackage = existingPacakages[ thisPackageId ]; 238 | var oldDependentParcels = oldPackage.dependentParcels; 239 | 240 | oldPackage.destroy(); 241 | 242 | thisPackage = packagesThatWereCreated[ thisPackageId ] = new Parcel( packageOptions ); 243 | thisPackage.createAllAssets( assetTypes ); 244 | 245 | oldDependentParcels.forEach( function( thisDependentParcel ) { 246 | thisPackage.addDependentParcel( thisDependentParcel ); 247 | thisDependentParcel.calcSortedDependencies(); 248 | thisDependentParcel.calcParcelAssets( assetTypes ); 249 | } ); 250 | 251 | log.warn( '', 'Recreated package at ' + thisPackage.path + ' as Parcel.' ); 252 | } else 253 | thisPackage = existingPacakages[ thisPackageId ]; 254 | 255 | nextWaterfall(); 256 | } ], nextPackageId ); 257 | }, nextSeries ); 258 | }, function( nextSeries ) { 259 | var allPackages = _.extend( {}, existingPacakages, packagesThatWereCreated ); 260 | 261 | // now that we have all our packages instantiated, hook up dependencies 262 | _.each( parcelMapResult.dependencies, function( dependencyIds, thisPackageId ) { 263 | var thisPackage = allPackages[ thisPackageId ]; 264 | 265 | if( ! thisPackage ) return nextSeries( new Error( 'Unknown package id in dependency ' + thisPackageId ) ); 266 | 267 | var thisPackageDependencies = _.map( dependencyIds, function( thisDependencyId ) { return allPackages[ thisDependencyId ]; } ); 268 | thisPackage.setDependencies( thisPackageDependencies ); 269 | } ); 270 | 271 | // finally, we can calculate the topo sort of any parcels that were created 272 | _.each( packagesThatWereCreated, function( thisParcel ) { 273 | if( thisParcel.isParcel ) { 274 | thisParcel.calcSortedDependencies(); 275 | thisParcel.calcParcelAssets( assetTypes ); 276 | 277 | _.each( thisParcel.sortedDependencies, function( thisDependentPackage ) { 278 | thisDependentPackage.addDependentParcel( thisParcel ); 279 | } ); 280 | } 281 | } ); 282 | 283 | nextSeries(); 284 | } ], function( err ) { 285 | return callback( err, packagesThatWereCreated ); 286 | } ); 287 | }; 288 | 289 | Parcelify.prototype._setupParcelEventRelays = function( parcel ) { 290 | var _this = this; 291 | var eventsToRelay = [ 'assetUpdated', 'packageJsonUpdated' ]; 292 | 293 | eventsToRelay.forEach( function( thisEvent ) { 294 | parcel.on( thisEvent, function() { 295 | var args = Array.prototype.slice.call( arguments ); 296 | _this.emit.apply( _this, [].concat( thisEvent, args ) ); 297 | } ); 298 | } ); 299 | 300 | parcel.on( 'bundleUpdated', function( bundlePath, assetType ) { 301 | _this.emit( 'bundleWritten', bundlePath, assetType, parcel, true ); 302 | } ); 303 | }; -------------------------------------------------------------------------------- /lib/asset.js: -------------------------------------------------------------------------------- 1 | var fs = require( 'fs' ); 2 | var async = require( 'async' ); 3 | var mkdirp = require( 'mkdirp' ); 4 | var combine = require( 'stream-combiner' ); 5 | var path = require( 'path' ); 6 | var _ = require( 'underscore' ); 7 | 8 | module.exports = Asset; 9 | 10 | function Asset( srcPath, type, transforms, appData ) { 11 | this.srcPath = srcPath; 12 | this.type = type; 13 | this.transforms = transforms; 14 | this.appData = appData; 15 | } 16 | 17 | Asset.prototype.addTransform = function( transform, prepend ) { 18 | if( _.isUndefined( prepend ) ) prepend = false; 19 | 20 | if( prepend ) this.transforms.unshift( transform ); 21 | else this.transforms.push( transform ); 22 | }; 23 | 24 | Asset.prototype.createReadStream = function() { 25 | var stream = fs.createReadStream( this.srcPath ); 26 | return this._applyTransforms( stream, this.transforms ); 27 | }; 28 | 29 | Asset.prototype.writeToDisk = function( dstPath, callback ) { 30 | var _this = this; 31 | 32 | this.dstPath = dstPath; // save this for later 33 | 34 | async.series( [ function( nextSeries ) { 35 | mkdirp( path.dirname( dstPath ), nextSeries ); 36 | }, function( nextSeries ) { 37 | var stream = _this.createReadStream(); 38 | stream.on( 'error', function( err ) { 39 | nextSeries( new Error( 'While reading or transforming "' + _this.srcPath + '":\n' + err.message ) ); 40 | } ); 41 | stream.on( 'end', nextSeries ); 42 | stream.pipe( fs.createWriteStream( dstPath ) ); 43 | } ], callback ); 44 | }; 45 | 46 | Asset.prototype._applyTransforms = function( stream, transforms ) { 47 | var _this = this; 48 | 49 | if( ! transforms || transforms.length === 0 ) return stream; 50 | 51 | var combinedStream = combine.apply( null, transforms.map( function( thisTransform ) { 52 | return thisTransform( _this.srcPath ); 53 | } ) ); 54 | 55 | return stream.pipe( combinedStream ); 56 | }; 57 | -------------------------------------------------------------------------------- /lib/package.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require( 'fs' ); 3 | var inherits = require( 'inherits' ); 4 | var EventEmitter = require( 'events' ).EventEmitter; 5 | var _ = require( 'underscore' ); 6 | var async = require( 'async' ); 7 | var glob = require( 'glob' ); 8 | var globwatcher = require( 'globwatcher' ).globwatcher; 9 | var Asset = require( './asset' ); 10 | var resolve = require( 'resolve' ); 11 | var log = require( 'npmlog' ); 12 | 13 | module.exports = Package; 14 | 15 | inherits( Package, EventEmitter ); 16 | 17 | function Package( options ) { 18 | 19 | _.extend( this, _.pick( options, 20 | 'id', 21 | 'package', 22 | 'path', 23 | 'dependencies', 24 | 'assetSrcPathsByType', 25 | 'assetGlobsByType', 26 | 'assetTransformsByType' 27 | ) ); 28 | 29 | this.dependencies = []; 30 | this.dependentParcels = []; 31 | this.assetsByType = {}; 32 | 33 | EventEmitter.call( this ); 34 | } 35 | 36 | Package.prototype.createAllAssets = function( assetTypes ) { 37 | var _this = this; 38 | 39 | _this.assetsByType = {}; 40 | assetTypes.forEach( function( thisAssetType ) { _this.assetsByType[ thisAssetType ] = []; } ); 41 | 42 | Object.keys( this.assetSrcPathsByType ).forEach( function( assetType ) { 43 | _this.assetSrcPathsByType[ assetType ].forEach( function( thisAssetSrcPath ) { 44 | var thisAsset = _this.createAsset( thisAssetSrcPath, assetType ); 45 | } ); 46 | } ); 47 | }; 48 | 49 | Package.prototype.createAsset = function( thisAssetSrcPath, assetType, appData ) { 50 | var thisAsset = new Asset( thisAssetSrcPath, assetType, _.clone( this.assetTransformsByType[ assetType ] ), appData ); 51 | 52 | log.verbose( '', assetType + ' asset registered "%s"', path.relative( process.cwd(), thisAssetSrcPath ) ); 53 | 54 | if( ! this.assetsByType[ assetType ] ) this.assetsByType[ assetType ] = []; 55 | this.assetsByType[ assetType ].push( thisAsset ); 56 | 57 | return thisAsset; 58 | }; 59 | 60 | Package.prototype.getAssets = function( types ) { 61 | return _.reduce( this.assetsByType, function( memo, assetsOfThisType, thisAssetType ) { 62 | if( types && ! _.contains( types, thisAssetType ) ) return memo; 63 | 64 | return memo.concat( assetsOfThisType ); 65 | }, [] ); 66 | }; 67 | 68 | Package.prototype.addTransform = function( transform, transformOptions, toAssetTypes, prepend ) { 69 | if( _.isUndefined( prepend ) ) prepend = false; 70 | 71 | var t = transformOptions ? function( file ) { return transform( file, transformOptions ); } : transform; 72 | 73 | toAssetTypes = toAssetTypes || Object.keys( this.assetsByType ); 74 | if( ! _.isArray( toAssetTypes ) ) toAssetTypes = [ toAssetTypes ]; 75 | 76 | // add transform to existing assets 77 | this.getAssets( toAssetTypes ).forEach( function( thisAsset ) { 78 | thisAsset.addTransform( t, prepend ); 79 | } ); 80 | 81 | // and also add it to the package itself so it is added to assets created from this point forward 82 | _.each( _.pick( this.assetTransformsByType, toAssetTypes ), function( transformsForThisAssetType ) { 83 | if( prepend ) transformsForThisAssetType.unshift( t ); 84 | else transformsForThisAssetType.push( t ); 85 | } ); 86 | }; 87 | 88 | Package.prototype.setDependencies = function( dependencies ) { 89 | this.dependencies = dependencies; 90 | }; 91 | 92 | Package.prototype.addDependentParcel = function( parcel ) { 93 | this.dependentParcels = _.union( this.dependentParcels, parcel ); 94 | }; 95 | 96 | Package.prototype.createWatchers = function( assetTypes, packageFilter, appTransforms, appTransformDirs ) { 97 | this._createPackageJsonWatcher( assetTypes, packageFilter, appTransforms, appTransformDirs ); 98 | this._createAssetGlobWatchers(); 99 | }; 100 | 101 | Package.prototype.destroy = function() { 102 | this._destroyAssetGlobWatchers(); 103 | this.assetJsonWatcher.close(); 104 | }; 105 | 106 | /********************* Private instance methods *********************/ 107 | 108 | Package.prototype._createPackageJsonWatcher = function( assetTypes, packageFilter, appTransforms, appTransformDirs ) { 109 | var _this = this; 110 | 111 | this.assetJsonWatcher = globwatcher( path.resolve( this.path, "package.json" ) ); 112 | this.assetJsonWatcher.on( 'changed', function( srcPath ) { 113 | log.info( 'watch', 'package.json changed "%s"', path.relative( process.cwd(), srcPath ) ); 114 | 115 | fs.readFile( srcPath, 'utf8', function( err, packageJson ) { 116 | if( err ) return _this.emit( 'error', err ); 117 | 118 | try { 119 | packageJson = JSON.parse( packageJson ); 120 | } catch( err ) { 121 | return _this.emit( 'error', new Error( 'While parsing "' + srcPath + '", ' + err ) ); 122 | } 123 | 124 | packageJson.__path = _this.path; 125 | 126 | if( packageFilter ) packageJson = packageFilter( packageJson, _this.path ); 127 | 128 | Package.getOptionsFromPackageJson( _this.id, _this.path, packageJson, assetTypes, appTransforms, appTransformDirs, function( err, options ) { 129 | if( err ) return _this.emit( 'error', err ); 130 | 131 | _.extend( _this, options ); 132 | 133 | _this.createAllAssets( assetTypes ); 134 | 135 | _this._destroyAssetGlobWatchers(); 136 | _this._createAssetGlobWatchers(); 137 | 138 | _this._emitEventOnRelevantParcels( 'packageJsonUpdated', _this ); 139 | } ); 140 | } ); 141 | } ); 142 | }; 143 | 144 | Package.prototype._createAssetGlobWatchers = function() { 145 | var _this = this; 146 | 147 | this.assetGlobWatchers = []; 148 | 149 | _.each( _this.assetGlobsByType, function( globs, thisAssetType ) { 150 | var thisWatcher = globwatcher( globs ); 151 | 152 | thisWatcher.on( 'changed', function( srcPath ) { 153 | try { 154 | log.info( 'watch', '"%s" changed', path.relative( process.cwd(), srcPath ) ); 155 | 156 | var asset = _.findWhere( _this.assetsByType[ thisAssetType ], { srcPath : srcPath } ); 157 | if( ! asset ) return _this.emit( 'error', new Error( 'Couldn\'t find changed file ' + srcPath + ' in assets of type ' + thisAssetType ) ); 158 | 159 | _this._emitEventOnRelevantParcels( 'assetUpdated', 'changed', asset, _this ); 160 | } catch( err ) { 161 | return _this.emit( 'error', err ); 162 | } 163 | } ); 164 | 165 | thisWatcher.on( 'added', function( srcPath ) { 166 | try { 167 | log.info( 'watch', '"%s" added', path.relative( process.cwd(), srcPath ) ); 168 | 169 | var asset = _.findWhere( _this.assetsByType[ thisAssetType ], { srcPath : srcPath } ); 170 | // watching is weird... sometimes we get double events. make sure we don't add the same asset twice. 171 | if( asset ) return _this.emit( 'error', new Error( 'Asset ' + srcPath + ' already exists in assets of type ' + thisAssetType ) ); 172 | 173 | asset = _this.createAsset( srcPath, thisAssetType ); 174 | 175 | _this._emitEventOnRelevantParcels( 'assetUpdated', 'added', asset, _this ); 176 | } catch( err ) { 177 | return _this.emit( 'error', err ); 178 | } 179 | } ); 180 | 181 | thisWatcher.on( 'deleted', function( srcPath ) { 182 | try { 183 | log.info( 'watch', '"%s" deleted', path.relative( process.cwd(), srcPath ) ); 184 | 185 | var asset = _.findWhere( _this.assetsByType[ thisAssetType ], { srcPath : srcPath } ); 186 | if( ! asset ) return _this.emit( 'error', new Error( 'Couldn\'t find changed file ' + srcPath + ' in assets of type ' + thisAssetType ) ); 187 | 188 | _this.assetsByType[ thisAssetType ] = _.without( _this.assetsByType[ thisAssetType ], asset ); 189 | 190 | _this._emitEventOnRelevantParcels( 'assetUpdated', 'deleted', asset, _this ); 191 | } catch( err ) { 192 | return _this.emit( 'error', err ); 193 | } 194 | } ); 195 | 196 | _this.assetGlobWatchers.push( thisWatcher ); 197 | } ); 198 | }; 199 | 200 | Package.prototype._destroyAssetGlobWatchers = function() { 201 | this.assetGlobWatchers.forEach( function( thisAssetGlobWatcher ) { 202 | thisAssetGlobWatcher.close(); 203 | } ); 204 | 205 | this.assetGlobWatchers = []; 206 | }; 207 | 208 | Package.prototype._emitEventOnRelevantParcels = function() { 209 | var args = Array.prototype.slice.call( arguments ); 210 | 211 | this.dependentParcels.forEach( function( thisParcel ) { 212 | thisParcel.emit.apply( thisParcel, args ); 213 | } ); 214 | }; 215 | 216 | /********************* Static class methods *********************/ 217 | 218 | Package.getOptionsFromPackageJson = function( packageId, packagePath, packageJson, assetTypes, appTransforms, appTransformDirs, callback ) { 219 | var packageOptions = {}; 220 | 221 | if( appTransforms ) { 222 | var pkgIsInAppTransformsDir = _.find( appTransformDirs, function( thisAppDirPath ) { 223 | var relPath = path.relative( thisAppDirPath, packagePath ); 224 | var needToBackup = relPath.charAt( 0 ) === '.' && relPath.charAt( 1 ) === '.'; 225 | var appTransformsApplyToThisDir = ! needToBackup && relPath.indexOf( 'node_modules' ) === -1; 226 | return appTransformsApplyToThisDir; 227 | } ); 228 | 229 | if( pkgIsInAppTransformsDir ) 230 | packageJson.transforms = appTransforms.concat( packageJson.transforms || [] ); 231 | } 232 | 233 | packageOptions.package = packageJson; 234 | packageOptions.id = packageId; 235 | packageOptions.path = packagePath; 236 | 237 | packageOptions.assetSrcPathsByType = {}; 238 | packageOptions.assetTransformsByType = {}; 239 | packageOptions.assetGlobsByType = {}; 240 | 241 | async.each( assetTypes, function( thisAssetType, nextAssetType ) { 242 | 243 | async.parallel( [ function( nextParallel ) { 244 | packageOptions.assetSrcPathsByType[ thisAssetType ] = []; 245 | 246 | // resolve relative globs to absolute globs 247 | var relativeGlobsOfThisType = packageJson[ thisAssetType ] || []; 248 | if( _.isString( relativeGlobsOfThisType ) ) relativeGlobsOfThisType = [ relativeGlobsOfThisType ]; 249 | var absoluteGlobsOfThisType = relativeGlobsOfThisType.map( function( thisGlob ) { return path.resolve( packagePath, thisGlob ); } ); 250 | packageOptions.assetGlobsByType[ thisAssetType ] = absoluteGlobsOfThisType; 251 | 252 | // resolve absolute globs to actual src files 253 | async.map( absoluteGlobsOfThisType, glob, 254 | function( err, arrayOfResolvedGlobs ) { 255 | if( err ) return nextParallel( err ); 256 | 257 | var assetsOfThisType = _.flatten( arrayOfResolvedGlobs ); 258 | packageOptions.assetSrcPathsByType[ thisAssetType ] = assetsOfThisType; 259 | 260 | nextParallel(); 261 | } ); 262 | }, function( nextParallel ) { 263 | // resolve transform names to actual transform 264 | packageOptions.assetTransformsByType[ thisAssetType ] = []; 265 | 266 | if( packageJson.transforms ) { 267 | if( _.isArray( packageJson.transforms ) ) 268 | transformNames = packageJson.transforms; 269 | else 270 | transformNames = packageJson.transforms[ thisAssetType ] || []; 271 | } 272 | else 273 | transformNames = []; 274 | 275 | async.map( transformNames, function( thisTransformName, nextTransform ) { 276 | if( _.isFunction( thisTransformName ) ) return nextTransform( null, thisTransformName ); 277 | 278 | resolve( thisTransformName, { basedir : packageJson.__path }, function( err, modulePath ) { 279 | if( err ) return nextTransform( err ); 280 | 281 | nextTransform( null, require( modulePath ) ); 282 | } ); 283 | }, function( err, transforms ) { 284 | if( err ) return nextParallel( err ); 285 | 286 | 287 | packageOptions.assetTransformsByType[ thisAssetType ] = transforms; 288 | nextParallel(); 289 | } ); 290 | } ], nextAssetType ); 291 | }, function( err ) { 292 | if( err ) return callback( err ); 293 | 294 | callback( null, packageOptions ); 295 | } ); 296 | }; 297 | -------------------------------------------------------------------------------- /lib/parcel.js: -------------------------------------------------------------------------------- 1 | var inherits = require( 'inherits' ); 2 | var Package = require( './package' ); 3 | var _ = require( 'underscore' ); 4 | var async = require( 'async' ); 5 | var toposort = require( 'toposort' ); 6 | var through2 = require( 'through2' ); 7 | var path = require( 'path' ); 8 | var crypto = require( 'crypto' ); 9 | var fs = require( 'fs' ); 10 | 11 | module.exports = Parcel; 12 | 13 | inherits( Parcel, Package ); 14 | 15 | function Parcel( options ) { 16 | var _this = this; 17 | 18 | Package.call( this, options ); 19 | 20 | this.mainPath = options.mainPath; 21 | this.isParcel = true; 22 | this.bundlePathsByType = {}; 23 | this.parcelAssetsByType = {}; 24 | 25 | this.dependentParcels.push( this ); // parcels depend on themselves! 26 | } 27 | 28 | Parcel.prototype.calcSortedDependencies = function() { 29 | var packagesWithDependencies = []; 30 | 31 | function getEdgesForPackageDependencyGraph( thisPackage, thisTreeLevel, packageTreeLevels ) { 32 | if( _.isUndefined( thisTreeLevel ) ) thisTreeLevel = 0; 33 | if( _.isUndefined( packageTreeLevels ) ) packageTreeLevels = {}; 34 | 35 | if( ! packageTreeLevels[ thisPackage.path ] ) packageTreeLevels[ thisPackage.path ] = thisTreeLevel; 36 | 37 | return thisPackage.dependencies.reduce( function( memo, thisDependentPackage ) { 38 | // these conditionals are to avoid cycles and infinite recursion. 39 | // first, we only traverse each node once to avoid infinite recursion. 40 | if( _.isUndefined( packageTreeLevels[ thisDependentPackage.path ] ) ) { 41 | memo = memo.concat( getEdgesForPackageDependencyGraph( thisDependentPackage, thisTreeLevel + 1, packageTreeLevels ) ); 42 | } 43 | 44 | // second, we keep track of the levels of the nodes in the dependency tree (where 45 | // level 0 is the root node i.e. the parcel itself). nodes can only have dependencies 46 | // on other nodes with a level equal to or greater than their own. done. 47 | if( packageTreeLevels[ thisDependentPackage.path ] >= packageTreeLevels[ thisPackage.path ] ) { 48 | memo = memo.concat( [ [ thisPackage, thisDependentPackage ] ] ); 49 | } 50 | 51 | return memo; 52 | }, [] ); 53 | } 54 | 55 | var edges = getEdgesForPackageDependencyGraph( this ); 56 | var sortedPackages = toposort( edges ).reverse(); 57 | 58 | //sortedPackages = _.union( sortedPackages, Object.keys( packageManifest ) ); // union cuz some packages have no dependencies! 59 | sortedPackages = _.without( sortedPackages, this ); 60 | 61 | this.sortedDependencies = sortedPackages; 62 | }; 63 | 64 | Parcel.prototype.calcParcelAssets = function( assetTypes ) { 65 | memo = {}; 66 | assetTypes.forEach( function( thisAssetType ) { memo[ thisAssetType ] = []; } ); 67 | 68 | var sortedAssets = this.sortedDependencies.concat( this ).reduce( function( memo, thisPackage ) { 69 | var thisPackageAssets = thisPackage.assetsByType; 70 | 71 | _.each( thisPackageAssets, function( assets, thisAssetType ) { 72 | if( _.contains( assetTypes, thisAssetType ) ) 73 | memo[ thisAssetType ] = memo[ thisAssetType ].concat( assets ); 74 | } ); 75 | 76 | return memo; 77 | }, memo ); 78 | 79 | this.parcelAssetsByType = _.extend( {}, this.parcelAssetsByType, sortedAssets ); 80 | }; 81 | 82 | Parcel.prototype.attachWatchListeners = function( bundles ) { 83 | var _this = this; 84 | 85 | this.on( 'assetUpdated', function( eventType, asset ) { 86 | if( _.contains( [ 'added', 'deleted' ], eventType ) ) 87 | this.calcParcelAssets( [ asset.type ] ); 88 | 89 | if( bundles[ asset.type ] ) { 90 | _this.writeBundle( asset.type, bundles[ asset.type ], function( err, bundleWasWritten ) { 91 | if( err ) return _this.emit( 'error', err ); 92 | 93 | if( bundleWasWritten ) _this.emit( 'bundleUpdated', bundles[ asset.type ], asset.type ); 94 | // ... done! 95 | } ); 96 | } 97 | } ); 98 | 99 | this.on( 'packageJsonUpdated', function( thePackage ) { 100 | var bundlesToRewrite = _.pick( bundles, _.without( Object.keys( bundles ), 'script' ) ); 101 | this.calcParcelAssets( Object.keys( bundlesToRewrite ) ); 102 | 103 | async.each( Object.keys( bundlesToRewrite ), function( thisAssetType, nextEach ) { 104 | var thisBundlePath = bundlesToRewrite[ thisAssetType ]; 105 | if( ! thisBundlePath ) return nextEach(); 106 | 107 | _this.writeBundle( thisAssetType, thisBundlePath, function( err, bundleWasWritten ) { 108 | // don't stop writing other bundles if there was an error on this one. errors happen 109 | // frequently with transforms.. like invalid scss, etc. don't stop the show, just 110 | // keep going with our other bundles. 111 | 112 | if( err ) _this.emit( 'error', err ); 113 | else if( bundleWasWritten ) _this.emit( 'bundleWritten', thisBundlePath, thisAssetType, true ); 114 | 115 | nextEach(); 116 | } ); 117 | }, function( err ) { 118 | if( err ) _this.emit( 'error', err ); 119 | 120 | // done ); 121 | } ); 122 | } ); 123 | }; 124 | 125 | Parcel.prototype.writeBundle = function( assetType, dstPath, callback ) { 126 | var _this = this; 127 | 128 | var srcAssets = _this.parcelAssetsByType[ assetType ]; 129 | if( ! srcAssets || srcAssets.length === 0 ) return callback( null, false ); // we don't want to create an empty bundle just because we have no source files 130 | 131 | var bundle = through2(); 132 | var tempBundlePath = path.join( path.dirname( dstPath ), '.temp_' + path.basename( dstPath ) ); 133 | 134 | bundle.pipe( fs.createWriteStream( dstPath ) ).on( 'close', function ( err ) { 135 | // execution resumes here after all the individual asset streams 136 | // have been piped to this bundle. we need to pipe the bundle to the writable 137 | // stream first (before individual assets are piped to bundle stream) 138 | // so that if the high water mark is reached on one of the readable streams 139 | // it doesn't pause (with no way to resume). See github issue #15. 140 | 141 | if( err ) return callback( err, false ); 142 | 143 | // fs.rename( tempBundlePath, dstPath, function( err ) { 144 | // if( err ) console.log( 'yoyoyoo', fs.existsSync( tempBundlePath ), dstPath, srcAssets ); 145 | 146 | // if( err ) return callback( err ); 147 | 148 | //_this.bundlePathsByType[ assetType ] = dstPath; // don't do this. isn't really a property of the parcel so much as an input to parcelify 149 | 150 | callback( null, true ); 151 | // } ); 152 | } ); 153 | 154 | // pipe all our individual style streams to the bundle in order to concatenate them 155 | async.eachSeries( srcAssets, function( thisAsset, nextAsset ) { 156 | var thisAssetStream = thisAsset.createReadStream(); 157 | 158 | thisAssetStream.on( 'error', function( err ) { 159 | nextAsset( new Error( 'While reading or transforming "' + thisAsset.srcPath + '":\n' + err.message ) ); 160 | } ); 161 | 162 | thisAssetStream.on( 'end', function( err ) { 163 | nextAsset(); 164 | } ); 165 | 166 | thisAssetStream.pipe( bundle, { end : false } ); 167 | }, function( err ) { 168 | if( err ) return callback( err, false ); 169 | 170 | bundle.end(); 171 | 172 | // execution will resume up above on the 173 | // `close` event handler for our bundle 174 | } ); 175 | }; 176 | 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parcelify", 3 | "version": "2.2.1", 4 | "description": "Create css bundles from npm packages using the browserify dependency graph.", 5 | "main": "index.js", 6 | "bin": { 7 | "parcelify": "bin/cmd.js" 8 | }, 9 | "dependencies": { 10 | "async": "~0.2.10", 11 | "glob": "~3.2.9", 12 | "globwatcher": "~1.2.3", 13 | "inherits": "~2.0.1", 14 | "minimist": "0.0.8", 15 | "mkdirp": "~0.3.5", 16 | "npmlog": "0.0.6", 17 | "parcel-map": "^3.0.0", 18 | "resolve": "~0.6.1", 19 | "shasum": "~1.0.1", 20 | "stream-combiner": "^0.2.2", 21 | "through2": "~0.6.3", 22 | "toposort": "~0.2.10", 23 | "underscore": "~1.6.0" 24 | }, 25 | "devDependencies": { 26 | "browserify": "^9.0.8", 27 | "sass-css-stream": "^0.1.4", 28 | "tape": "~2.3.2" 29 | }, 30 | "scripts": { 31 | "test": "tape test/test.js" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git://github.com/rotundasoftware/parcelify.git" 36 | }, 37 | "homepage": "https://github.com/rotundasoftware/parcelify", 38 | "keywords": [ 39 | "parcel", 40 | "asset", 41 | "css", 42 | "commonjs", 43 | "browserify" 44 | ], 45 | "author": { 46 | "name": "Rotunda Software", 47 | "email": "support@rotundasoftware.com" 48 | }, 49 | "licenses": [ 50 | { 51 | "type": "MIT", 52 | "url": "https://github.com/rotundasoftware/parcelify/blob/master/LICENSE" 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotundasoftware/parcelify/8ba813a6a1e93bddf398c8d5042e9e7583254dbe/test/.DS_Store -------------------------------------------------------------------------------- /test/cli/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | parcelify.test 5 | 6 | 7 | 8 |

parcelify.test

9 | 10 | 11 | -------------------------------------------------------------------------------- /test/cli/main.js: -------------------------------------------------------------------------------- 1 | myModule = require( 'my-module' ); 2 | console.log( 'What’s in the bag? A shark or something?' ); 3 | -------------------------------------------------------------------------------- /test/cli/node_modules/my-module/index.js: -------------------------------------------------------------------------------- 1 | var testing = true; 2 | -------------------------------------------------------------------------------- /test/cli/node_modules/my-module/myModule.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: green 3 | } 4 | -------------------------------------------------------------------------------- /test/cli/node_modules/my-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "my-module", 3 | "version" : "0.0.0", 4 | "style" : "*.css", 5 | "template" : "*.tmpl", 6 | "main" : "index.js" 7 | } 8 | -------------------------------------------------------------------------------- /test/cli/node_modules/my-module/template.tmpl: -------------------------------------------------------------------------------- 1 |
My generic template
2 | -------------------------------------------------------------------------------- /test/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "parcelify.test", 3 | "version" : "0.0.0", 4 | "dependencies" : { 5 | "my-module" : "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/cli/run-cli-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CURRENTDIR="$( dirname "${BASH_SOURCE[0]}" )" 4 | PARCELIFY="$CURRENTDIR/../../bin/cmd.js" 5 | SCRIPTS="$CURRENTDIR/scripts" 6 | MAINJS="$CURRENTDIR/main.js" 7 | MYMODULE="$CURRENTDIR/node_modules/my-module/" 8 | WATCHBUNDLE="$CURRENTDIR/watchBundle.css" 9 | JSBUNDLE="$CURRENTDIR/jsBundle.js" 10 | CSSBUNDLE="$CURRENTDIR/cssBundle.css" 11 | DEBUG="" 12 | 13 | while getopts "hdpc" arg 14 | do 15 | case $arg in 16 | \?|h) 17 | echo "$0 -hdc" 18 | echo " -h: this help menu." 19 | echo " -c: clean up old bundle files and exit." 20 | echo " -p: specify the path to the parcelify executable." 21 | echo " -d: run the tests with debugging." 22 | echo " : without arguments runs the suite without debugging." 23 | exit 0 24 | ;; 25 | c) 26 | rm $WATCHBUNDLE $JSBUNDLE $CSSBUNDLE $TMPLBUNDLE 27 | exit 0 28 | ;; 29 | d) 30 | DEBUG="-d" 31 | ;; 32 | esac 33 | done 34 | 35 | $SCRIPTS/cli-tests.sh $DEBUG \ 36 | -m $MAINJS \ 37 | -p $PARCELIFY \ 38 | -n $MYMODULE \ 39 | -w $WATCHBUNDLE \ 40 | -j $JSBUNDLE \ 41 | -c $CSSBUNDLE 42 | 43 | -------------------------------------------------------------------------------- /test/cli/scripts/bundle.sh: -------------------------------------------------------------------------------- 1 | ### A test for the parcelify bundle generation functionality 2 | ### This file is only to be sourced from other files, not directly executed. 3 | 4 | ## test to see if we have the necessary variables 5 | if [ -z "$PARCELIFY" ] 6 | then 7 | echo "Could not locate the parcelify executable." 8 | echo "This should not be executed directly, only as part of the cli tests." 9 | exit 1 10 | fi 11 | 12 | function BUNDLE_TEST() { 13 | BUNDLE_FILENAME=$1 # the output filename. 14 | BUNDLE_PARCELIFY_ARG=$2 # one of -j, -c, or -t. 15 | 16 | # Error checking of the passed in arguments. 17 | if [ -z "$BUNDLE_FILENAME" ] 18 | then 19 | echo "Failed to find the bundle filename in BUNDLE_TEST()" 20 | exit 1 21 | fi 22 | if [ -z "$BUNDLE_PARCELIFY_ARG" ] 23 | then 24 | echo "Failed to find the bundle parcelify arg in BUNDLE_TEST()" 25 | exit 1 26 | fi 27 | 28 | # if we have an output around we want to remove it. 29 | if [ -e $BUNDLE_FILENAME ] 30 | then 31 | debug_echo "Cleaning up old bundle file: $BUNDLE_FILENAME." 32 | rm $BUNDLE_FILENAME 33 | fi 34 | 35 | debug_echo "Executing the following parcelify command:" 36 | debug_echo " $PARCELIFY $MAINJS $DEBUG_PARCELIFY $BUNDLE_PARCELIFY_ARG $BUNDLE_FILENAME" 37 | 38 | # execute the parcelify command. 39 | $PARCELIFY $MAINJS $DEBUG_PARCELIFY $BUNDLE_PARCELIFY_ARG $BUNDLE_FILENAME 40 | PARCELIFY_RETURN=$? 41 | debug_echo "executing parcelify returned: $PARCELIFY_RETURN" 42 | 43 | # check our return code. 44 | if [ $PARCELIFY_RETURN != 0 ] 45 | then 46 | echo "parcelify failed to return a non-zero exit code: $PARCELIFY_RETURN" 47 | exit 1 48 | fi 49 | 50 | # make sure we have a bundle file and that it's non-zero. 51 | if [ ! -e $BUNDLE_FILENAME ] 52 | then 53 | echo "Failed to generate bundle file: $BUNDLE_FILENAME" 54 | exit 1 55 | elif [ ! -s $BUNDLE_FILENAME ] 56 | then 57 | echo "Failed to generate a bundle file with any content: $BUNDLE_FILENAME" 58 | exit 1 59 | fi 60 | 61 | ## TODO test content of BUNDLE_FILENAME 62 | } 63 | -------------------------------------------------------------------------------- /test/cli/scripts/cli-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## A function to print out a help message. 4 | function help() { 5 | echo "" 6 | echo "$0 -pmnMjctwdh" 7 | echo " -p path to the parcelify binary, defaults to `which parcelify`" 8 | echo " -m path to the main.js file to bundle, defaults to ./main.js" 9 | echo " -n path to the node_module used by main.js" 10 | echo " -j test with the provided jsBundle" 11 | echo " -c test with the provided cssBundle" 12 | echo " -t test with the provided tmplBundle" 13 | echo " -w test the watch functionality" 14 | echo " -d print debugging information about the testing, passed to parcelify." 15 | echo " -h print this message." 16 | } 17 | 18 | ## Setup a few variables with default values. 19 | CLI_TESTS="$( dirname "${BASH_SOURCE[0]}" )" 20 | PARCELIFY=`which parcelify` 21 | PARENTDIR=".." 22 | NODE_MODULES="$PARENTDIR/node_modules" 23 | MY_MODULE="$NODE_MODULES/my-module/" 24 | MAINJS="$PARENTDIR/main.js" 25 | JSBUNDLE= 26 | CSSBUNDLE= 27 | TMPLBUNDLE= 28 | WATCH= 29 | DEBUG=0 30 | DEBUG_PARCELIFY="" 31 | 32 | ## A function for simplifying printing of debugging statements. 33 | function debug_echo() { 34 | if [ $DEBUG == 1 ] 35 | then 36 | echo "$1" 37 | fi 38 | } 39 | 40 | ## Deal with user provided arguments. 41 | while getopts "p:m:n:j:c:t:w:dh" arg 42 | do 43 | case $arg in 44 | \?) 45 | help 46 | exit 1 47 | ;; 48 | h) 49 | help 50 | exit 0 51 | ;; 52 | p) 53 | PARCELIFY=$OPTARG 54 | ;; 55 | m) 56 | MAINJS=$OPTARG 57 | ;; 58 | n) 59 | MY_MODULE=$OPTARG 60 | ;; 61 | j) 62 | JSBUNDLE=$OPTARG 63 | ;; 64 | c) 65 | CSSBUNDLE=$OPTARG 66 | ;; 67 | t) 68 | TMPLBUNDLE=$OPTARG 69 | ;; 70 | w) 71 | WATCH=$OPTARG 72 | ;; 73 | d) 74 | DEBUG=1 75 | DEBUG_PARCELIFY="-d" 76 | ;; 77 | esac 78 | done 79 | 80 | ## Print out variable debugging information. 81 | debug_echo "Debugging enabled." 82 | debug_echo " Variables:" 83 | debug_echo " CLI_TESTS: $CLI_TESTS" 84 | debug_echo " PARCELIFY: $PARCELIFY" 85 | debug_echo " MY_MODULE: $MY_MODULE" 86 | debug_echo " MAINJS: $MAINJS" 87 | debug_echo " JSBUNDLE: $JSBUNDLE" 88 | debug_echo " CSSBUNDLE: $CSSBUNDLE" 89 | debug_echo " TMPLBUNDLE: $TMPLBUNDLE" 90 | debug_echo " WATCH: $WATCH" 91 | debug_echo " DEBUG: $DEBUG" 92 | debug_echo " DEBUG_PARCELIFY: $DEBUG_PARCELIFY" 93 | debug_echo "" 94 | 95 | ## Do error checking of variables 96 | debug_echo "Error checking variables." 97 | # Do we have a directory for our cli tests? 98 | if [ ! -d "$CLI_TESTS" ] 99 | then 100 | echo "Failed to locate the directory with the cli tests!" 101 | exit 1 102 | fi 103 | # Do we have a parcelify binary? 104 | if [ ! -x "$PARCELIFY" ] 105 | then 106 | echo "Failed to locate the parcelify executable" 107 | help 108 | exit 1 109 | fi 110 | # Do we have a main.js file? 111 | if [ ! -e "$MAINJS" ] 112 | then 113 | echo "Failed to locate the main.js file." 114 | help 115 | exit 1 116 | fi 117 | # Do we know the path to the node_module used by main.js? 118 | if [ ! -d "$MY_MODULE" ] 119 | then 120 | echo "Failed to locate the the node module directory." 121 | help 122 | exit 123 | fi 124 | # See if we have been provided with a task 125 | if [ -z "$JSBUNDLE" -a -z "$CSSBUNDLE" -a -z "$TMPLBUNDLE" -a -z "$WATCH" ] 126 | then 127 | echo "No tests have been provided." 128 | help 129 | exit 1 130 | fi 131 | debug_echo "variables are free of errors." 132 | debug_echo "" 133 | 134 | ## Call the tests: 135 | # jsBundle tests 136 | if [ ! -z "$JSBUNDLE" ] 137 | then 138 | debug_echo "Running the jsBundle tests." 139 | source "$CLI_TESTS/bundle.sh" 140 | BUNDLE_TEST "$JSBUNDLE" "-j" 141 | debug_echo "" 142 | fi 143 | # cssBundle tests 144 | if [ ! -z "$CSSBUNDLE" ] 145 | then 146 | debug_echo "Running the cssBundle tests" 147 | source "$CLI_TESTS/bundle.sh" 148 | BUNDLE_TEST "$CSSBUNDLE" "-c" 149 | debug_echo "" 150 | fi 151 | # tmplBundle tests 152 | if [ ! -z "$TMPLBUNDLE" ] 153 | then 154 | debug_echo "Running the tmplBundle tests." 155 | source "$CLI_TESTS/bundle.sh" 156 | BUNDLE_TEST "$TMPLBUNDLE" "-t" 157 | debug_echo "" 158 | fi 159 | # watch tests 160 | if [ ! -z "$WATCH" ] 161 | then 162 | debug_echo "Running the watch tests." 163 | source "$CLI_TESTS/watch.sh" 164 | WATCH_TEST 165 | debug_echo "" 166 | fi 167 | 168 | debug_echo "All finished, exiting." 169 | 170 | ## Exit when done. 171 | exit 0 172 | -------------------------------------------------------------------------------- /test/cli/scripts/watch.sh: -------------------------------------------------------------------------------- 1 | ### A test for the parcelify watch functionality. 2 | ### This file is only to be sourced from other files, not directly executed. 3 | 4 | ## test to see if we have the necessary variables 5 | if [ -z "$PARCELIFY" ] 6 | then 7 | echo "Could not locate the parcelify executable." 8 | echo "This should not be executed directly, only as part of the cli tests." 9 | exit 1 10 | fi 11 | 12 | function GET_FILE_TIMESTAMP() { 13 | eval "$1=`stat -r $2 | awk '{print $11}'`" # ugly and only tested w/ OSX bash! 14 | } 15 | 16 | function WATCH_TEST() { 17 | debug_echo "Executing the following parcelify command:" 18 | debug_echo " $PARCELIFY $MAINJS $DEBUG_PARCELIFY -w -c $WATCH &" 19 | 20 | $PARCELIFY $MAINJS $DEBUG_PARCELIFY -w -c $WATCH & 21 | 22 | # capture the process id, so that we can end the process in the future. 23 | PARCELIFY_PID=$! 24 | debug_echo " PID: $PARCELIFY_PID" 25 | 26 | # setup a mechanism to kill parcelify if we exit. 27 | trap "kill $PARCELIFY_PID" EXIT 28 | 29 | # wait a few seconds for parcelify to do something. 30 | debug_echo "waiting 1 second for parcelify to output it's file." 31 | ## TODO this is not a solution in that it may take more than 2 seconds to 32 | ## generate an initial bundle file. For now it is good enough to proceed. 33 | sleep 1 34 | 35 | # collect the timestamp of the current bundle file that will be updated. 36 | GET_FILE_TIMESTAMP INITIAL_TIMESTAMP $WATCH 37 | debug_echo "Initial timestamp is $INITIAL_TIMESTAMP" 38 | 39 | # make a name for a temp css file in my_module to hold new content. 40 | TEMPCSSFILE="$MY_MODULE/$RANDOM.css" 41 | 42 | # Unless debugging, update our trap to remove the temp file we are creating. 43 | if [ $DEBUG != 1 ] 44 | then 45 | trap "kill $PARCELIFY_PID; rm $TEMPCSSFILE" EXIT 46 | else 47 | debug_echo "Created tempfile: $TEMPCSSFILE." 48 | debug_echo "!! In debug mode we do not clean up these files upon error !!" 49 | fi 50 | 51 | # add content to the temp css file 52 | debug_echo "Adding a yellow color to h1 tags in tempfile: $TEMPCSSFILE" 53 | echo "h1 { color: yellow }" > $TEMPCSSFILE 54 | 55 | debug_echo "Sleeping for 3 seconds to be sure watchify picks up on it." 56 | sleep 3 57 | 58 | # check our second timestamp 59 | GET_FILE_TIMESTAMP SECOND_TIMESTAMP $WATCH 60 | debug_echo "Second timestamp is $SECOND_TIMESTAMP" 61 | 62 | # do some error testing. 63 | if [ -z "$SECOND_TIMESTAMP" ] 64 | then 65 | echo "Failed to fetch second timestamp from $WATCH" 66 | exit 1 67 | elif [ $SECOND_TIMESTAMP -eq $INITIAL_TIMESTAMP ] 68 | then 69 | echo "$WATCH has not been updated via watchify." 70 | exit 1 71 | elif [ $SECOND_TIMESTAMP -lt $INITIAL_TIMESTAMP ] 72 | then 73 | echo "Second timestamp is less than initial timestamp, this shouldn't be." 74 | exit 1 75 | fi 76 | 77 | # test the contents of the generated file 78 | debug_echo "Testing the contents of $WATCH for the word 'yellow'" 79 | YELLOW=`grep "yellow" $WATCH` 80 | if [ -z "$YELLOW" ] 81 | then 82 | echo "Failed to find the new content in the updated css file: $WATCH" 83 | exit 1 84 | fi 85 | 86 | # we now know that watch picked up on our change. 87 | # Let's remove the tmp file and see if it picks up on that. 88 | debug_echo "Removing temp file $TEMPCSSFILE" 89 | rm $TEMPCSSFILE 90 | 91 | if [ -e $TEMPCSSFILE ] 92 | then 93 | echo "Failed to remove the temporary css file: $TEMPCSSFILE" 94 | exit 1 95 | elif [ $DEBUG != 1 ] 96 | then 97 | # adjust the trap to no longer try to remove the tmpfile. 98 | trap "kill $PARCELIFY_PID" EXIT 99 | fi 100 | 101 | debug_echo "waiting 3 seconds for watchify to pickup on the missing css file." 102 | sleep 3 103 | 104 | # Get a third timestamp and compair with the second timestamp. 105 | GET_FILE_TIMESTAMP THIRD_TIMESTAMP $WATCH 106 | debug_echo "Third timestamp is $THIRD_TIMESTAMP" 107 | 108 | # do some error testing. 109 | if [ -z "$THIRD_TIMESTAMP" ] 110 | then 111 | echo "Failed to fetch third timestamp of file $WATCH" 112 | exit 1 113 | elif [ $THIRD_TIMESTAMP -eq $SECOND_TIMESTAMP ] 114 | then 115 | echo "$WATCH has not been updated via watchify." 116 | exit 1 117 | elif [ $THIRD_TIMESTAMP -lt $SECOND_TIMESTAMP ] 118 | then 119 | echo "Third timestamp is less than initial timestamp, this shouldn't be." 120 | exit 1 121 | fi 122 | 123 | # test that the contents of the generated file 124 | debug_echo "Testing the contents of $WATCH for the lack of the word 'yellow'." 125 | YELLOW=`grep "yellow" $WATCH` 126 | if [ ! -z "$YELLOW" ] 127 | then 128 | echo "Found old content in the updated css file: $WATCH." 129 | exit 1 130 | fi 131 | 132 | ## TODO test watching jsBundles and tmplBundles by 133 | ## extrapolating the common functionality from above. 134 | 135 | } 136 | -------------------------------------------------------------------------------- /test/node_modules/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotundasoftware/parcelify/8ba813a6a1e93bddf398c8d5042e9e7583254dbe/test/node_modules/.DS_Store -------------------------------------------------------------------------------- /test/node_modules/base-css/base.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /test/node_modules/base-css/index.js: -------------------------------------------------------------------------------- 1 | console.log( "in my-other-module" ); -------------------------------------------------------------------------------- /test/node_modules/base-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "*.css" ] 3 | } -------------------------------------------------------------------------------- /test/node_modules/footer/header.css: -------------------------------------------------------------------------------- 1 | p.footer { 2 | color: green; 3 | } -------------------------------------------------------------------------------- /test/node_modules/footer/index.js: -------------------------------------------------------------------------------- 1 | require( 'footer' ); -------------------------------------------------------------------------------- /test/node_modules/footer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "*.css" ] 3 | } -------------------------------------------------------------------------------- /test/node_modules/header/header.css: -------------------------------------------------------------------------------- 1 | p.header { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /test/node_modules/header/index.js: -------------------------------------------------------------------------------- 1 | require( 'base-css' ); -------------------------------------------------------------------------------- /test/node_modules/header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "*.css" ] 3 | } -------------------------------------------------------------------------------- /test/node_modules/my-module/index.js: -------------------------------------------------------------------------------- 1 | console.log( "in my-module" ); -------------------------------------------------------------------------------- /test/node_modules/my-module/myModule.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 18px; 3 | } -------------------------------------------------------------------------------- /test/node_modules/my-module/myModule.tmpl: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/node_modules/my-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : "myModule.css", 3 | "template" : "*.tmpl" 4 | } -------------------------------------------------------------------------------- /test/node_modules/my-other-module/index.js: -------------------------------------------------------------------------------- 1 | console.log( "in my-other-module" ); -------------------------------------------------------------------------------- /test/node_modules/my-other-module/myOtherModuleBlue.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /test/node_modules/my-other-module/myOtherModuleRed.css: -------------------------------------------------------------------------------- 1 | h3 { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /test/node_modules/my-other-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "myOtherModuleRed.css", "myOtherModuleBlue.css" ] 3 | } -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "parcel.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "BSD-2-Clause" 11 | } 12 | -------------------------------------------------------------------------------- /test/page1/empty.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/page1/main.js: -------------------------------------------------------------------------------- 1 | require( 'my-module' ); 2 | 3 | console.log('page 1!!!'); 4 | -------------------------------------------------------------------------------- /test/page1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "view": "view.html", 3 | "main": "main.js", 4 | "style": "*.css" 5 | } 6 | -------------------------------------------------------------------------------- /test/page2/index.js: -------------------------------------------------------------------------------- 1 | require( 'my-module' ); 2 | 3 | console.log('page 2!'); 4 | -------------------------------------------------------------------------------- /test/page2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "view": "render.jade", 3 | "style": "*.css" 4 | } 5 | -------------------------------------------------------------------------------- /test/page2/styles.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /test/page3/index.js: -------------------------------------------------------------------------------- 1 | require( 'my-other-module' ); 2 | console.log('page 2!'); 3 | -------------------------------------------------------------------------------- /test/page3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "*.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/page4/main.js: -------------------------------------------------------------------------------- 1 | require( 'my-module' ); 2 | 3 | console.log('page 1!'); 4 | -------------------------------------------------------------------------------- /test/page4/main.scss: -------------------------------------------------------------------------------- 1 | body { 2 | h3 { 3 | color: red; 4 | } 5 | } -------------------------------------------------------------------------------- /test/page4/main.tmpl: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/page4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "view": "view.html", 3 | "main": "main.js", 4 | "style": "*.scss", 5 | "template": "*.tmpl", 6 | "transforms": [ 7 | "sass-css-stream" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/page5/main.js: -------------------------------------------------------------------------------- 1 | console.log('page 5'); 2 | -------------------------------------------------------------------------------- /test/page6/main.js: -------------------------------------------------------------------------------- 1 | console.log( 'boop' ); -------------------------------------------------------------------------------- /test/page6/main.scss: -------------------------------------------------------------------------------- 1 | div.beep { 2 | div.boop { 3 | color : green; 4 | } 5 | } -------------------------------------------------------------------------------- /test/page6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "main.scss" ] 3 | } -------------------------------------------------------------------------------- /test/page7/main.css: -------------------------------------------------------------------------------- 1 | div.beep { 2 | color : green; 3 | } -------------------------------------------------------------------------------- /test/page7/main.js: -------------------------------------------------------------------------------- 1 | require( 'base-css' ); 2 | require( 'header' ); 3 | require( 'footer' ); -------------------------------------------------------------------------------- /test/page7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style" : [ "main.css" ] 3 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var browserify = require( 'browserify' ); 3 | var parcelify = require('../'); 4 | var os = require('os'); 5 | var path = require('path'); 6 | var fs = require('fs'); 7 | var mkdirp = require('mkdirp'); 8 | var tmpdir = (os.tmpdir || os.tmpDir)(); 9 | 10 | test( 'page1', function( t ) { 11 | t.plan( 2 ); 12 | 13 | var mainPath = __dirname + '/page1/main.js'; 14 | 15 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 16 | 17 | var options = { 18 | bundles : { 19 | // script : path.join( dstDir, 'bundle.js' ), 20 | style : path.join( dstDir, 'bundle.css' ) 21 | } 22 | }; 23 | 24 | mkdirp.sync( dstDir ); 25 | 26 | var b = browserify( mainPath ); 27 | var p = parcelify( b, options ); 28 | 29 | b.bundle(); 30 | 31 | p.on( 'done', function() { 32 | t.deepEqual( 33 | fs.readdirSync( dstDir ).sort(), 34 | [ 'bundle.css' ] 35 | ); 36 | 37 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'h1 {\n\tfont-size: 18px;\n}body {\n color: red;\n}\n' ); 38 | } ); 39 | } ); 40 | 41 | test( 'page2', function( t ) { 42 | t.plan( 2 ); 43 | 44 | var mainPath = __dirname + '/page2/index.js'; 45 | 46 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 47 | var options = { 48 | bundles : { 49 | style : path.join( dstDir, 'bundle.css' ) 50 | } 51 | }; 52 | 53 | mkdirp.sync( dstDir ); 54 | 55 | var b = browserify( mainPath ); 56 | var p = parcelify( b, options ); 57 | 58 | b.bundle(); 59 | 60 | p.on( 'done', function() { 61 | t.deepEqual( 62 | fs.readdirSync( dstDir ).sort(), 63 | [ 'bundle.css' ] 64 | ); 65 | 66 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'h1 {\n\tfont-size: 18px;\n}h2 {\n\tfont-weight: bold;\n}' ); 67 | } ); 68 | } ); 69 | 70 | test( 'page3', function( t ) { 71 | t.plan( 2 ); 72 | 73 | var mainPath = __dirname + '/page3/index.js'; 74 | 75 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 76 | var options = { 77 | bundles : { 78 | style : path.join( dstDir, 'bundle.css' ) 79 | } 80 | }; 81 | 82 | mkdirp.sync( dstDir ); 83 | 84 | var b = browserify( mainPath ); 85 | var p = parcelify( b, options ); 86 | 87 | b.bundle(); 88 | 89 | p.on( 'done', function() { 90 | t.deepEqual( 91 | fs.readdirSync( dstDir ).sort(), 92 | [ 'bundle.css' ] 93 | ); 94 | 95 | // makes sure when multiple files are listed in a package.json style property, they are concatenated in the right order 96 | // (in this case in my-other-module, style is [ "myOtherModuleRed.css", "myOtherModuleBlue.css" ] ) 97 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'h3 {\n\tcolor: red;\n}h2 {\n\tcolor: blue;\n}' ); 98 | } ); 99 | } ); 100 | 101 | test( 'page4', function( t ) { 102 | t.plan( 3 ); 103 | 104 | var mainPath = __dirname + '/page4/main.js'; 105 | 106 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 107 | var options = { 108 | bundles : { 109 | script : path.join( dstDir, 'bundle.js' ), 110 | style : path.join( dstDir, 'bundle.css' ), 111 | template : path.join( dstDir, 'bundle.tmpl' ) 112 | } 113 | }; 114 | 115 | mkdirp.sync( dstDir ); 116 | 117 | var b = browserify( mainPath ); 118 | var p = parcelify( b, options ); 119 | 120 | b.bundle(); 121 | 122 | p.on( 'done', function() { 123 | t.deepEqual( 124 | fs.readdirSync( dstDir ).sort(), 125 | [ 'bundle.css', 'bundle.tmpl' ] 126 | ); 127 | 128 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'h1 {\n\tfont-size: 18px;\n}body h3 {\n color: red; }\n' ); 129 | t.deepEqual( fs.readFileSync( options.bundles.template, 'utf8' ), '\n\n' ); 130 | } ); 131 | } ); 132 | 133 | // test a parcel with no package.json and no options 134 | test( 'page5', function( t ) { 135 | t.plan( 1 ); 136 | 137 | var mainPath = __dirname + '/page5/main.js'; 138 | 139 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 140 | 141 | mkdirp.sync( dstDir ); 142 | 143 | var b = browserify( mainPath ); 144 | var p = parcelify( b ); 145 | 146 | b.bundle(); 147 | 148 | p.on( 'done', function() { 149 | t.deepEqual( 150 | fs.readdirSync( dstDir ).sort(), 151 | [] 152 | ); 153 | } ); 154 | 155 | } ); 156 | 157 | 158 | // default transforms 159 | test( 'page6', function( t ) { 160 | t.plan( 2 ); 161 | 162 | var mainPath = __dirname + '/page6/main.js'; 163 | 164 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 165 | 166 | var options = { 167 | bundles : { 168 | style : path.join( dstDir, 'bundle.css' ) 169 | }, 170 | 171 | appTransforms : [ require( 'sass-css-stream' ) ], 172 | appTransformDirs : [ './test/page6/' ] 173 | }; 174 | 175 | mkdirp.sync( dstDir ); 176 | 177 | var b = browserify( mainPath ); 178 | var p = parcelify( b, options ); 179 | 180 | b.bundle(); 181 | 182 | p.on( 'done', function() { 183 | t.deepEqual( 184 | fs.readdirSync( dstDir ).sort(), 185 | [ 'bundle.css' ] 186 | ); 187 | 188 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'div.beep div.boop {\n color: green; }\n' ); 189 | } ); 190 | } ); 191 | 192 | 193 | // ordering 194 | test( 'page7', function( t ) { 195 | t.plan( 2 ); 196 | 197 | var mainPath = __dirname + '/page7/main.js'; 198 | 199 | var dstDir = path.resolve( tmpdir, 'parcelify-test-' + Math.random() ); 200 | 201 | var options = { 202 | bundles : { 203 | style : path.join( dstDir, 'bundle.css' ) 204 | } 205 | }; 206 | 207 | mkdirp.sync( dstDir ); 208 | 209 | var b = browserify( mainPath ); 210 | var p = parcelify( b, options ); 211 | 212 | b.bundle(); 213 | 214 | p.on( 'done', function() { 215 | t.deepEqual( 216 | fs.readdirSync( dstDir ).sort(), 217 | [ 'bundle.css' ] 218 | ); 219 | 220 | t.deepEqual( fs.readFileSync( options.bundles.style, 'utf8' ), 'p {\n\tcolor: red;\n}p.header {\n\tcolor: blue;\n}p.footer {\n\tcolor: green;\n}div.beep {\n\tcolor : green;\n}' ); 221 | } ); 222 | } ); -------------------------------------------------------------------------------- /test/watch-test-output/bundle.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 18px; 3 | }body { 4 | color: red; 5 | } 6 | -------------------------------------------------------------------------------- /test/watch-test-output/bundle.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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o