Comparing version 0.5.0 to 0.5.1
85
index.js
@@ -74,7 +74,7 @@ var path = require('path'); | ||
var existingPackages = options.existingPackages || {}; | ||
var assetTypes = Object.keys( options.bundles ); | ||
var assetTypes = _.without( Object.keys( options.bundles ), 'script' ); | ||
parcelMap( browerifyInstance, { keys : assetTypes }, function( err, parcelMap ) { | ||
if( err ) return callback( err ); | ||
instantiateParcelAndPackagesFromMap( mainPath, parcelMap, existingPackages, assetTypes, function( err, thisParcel, packagesThatWereCreated ) { | ||
@@ -93,7 +93,3 @@ if( err ) return callback( err ); | ||
// we are done copying packages and collecting our asset streams. Now write our bundles to disk. | ||
async.each( _.union( assetTypes, 'script' ), function( thisAssetType, nextEach ) { | ||
if( ! options.bundles[ thisAssetType ] ) return nextEach(); | ||
thisParcel.writeBundle( thisAssetType, options.bundles[ thisAssetType ], nextEach ); | ||
}, nextSeries ); | ||
thisParcel.writeBundles( options.bundles, nextSeries ); | ||
}, function( nextSeries ) { | ||
@@ -105,3 +101,3 @@ var thisParcelIsNew = _.contains( packagesThatWereCreated, thisParcel ); | ||
// work in situations where we have multiple parcelify instances running that share common bundles | ||
_.each( packagesThatWereCreated, function( thisPackage ) { thisPackage.createAssetWatchers(); } ); | ||
_.each( packagesThatWereCreated, function( thisPackage ) { thisPackage.createWatchers( assetTypes ); } ); | ||
if( thisParcelIsNew ) thisParcel.attachWatchListeners( options.bundles ); | ||
@@ -138,3 +134,4 @@ } | ||
async.waterfall( [ function( nextWaterfall ) { | ||
getPackageOptionsFromPackageJson( thisPackageId, parcelMap.packages[ thisPackageId ], assetTypes, nextWaterfall ); | ||
var packageJson = parcelMap.packages[ thisPackageId ]; | ||
Package.getOptionsFromPackageJson( thisPackageId, packageJson.__dirname, packageJson, assetTypes, nextWaterfall ); | ||
}, function( packageOptions, nextWaterfall ) { | ||
@@ -192,71 +189,1 @@ var thisPackage; | ||
} | ||
function getPackageOptionsFromPackageJson( packageId, packageJson, assetTypes, callback ) { | ||
var packageOptions = {}; | ||
packageOptions.package = packageJson; | ||
packageOptions.id = packageId; | ||
packageOptions.path = packageJson.__dirname; | ||
packageOptions.assetSrcPathsByType = {}; | ||
packageOptions.assetTransformsByType = {}; | ||
packageOptions.assetGlobsByType = {}; | ||
if( packageJson.view ) { | ||
packageOptions.view = path.resolve( packageOptions.path, packageJson.view ); | ||
packageOptions.isParcel = true; | ||
} | ||
async.each( assetTypes, function( thisAssetType, nextAssetType ) { | ||
async.parallel( [ function( nextParallel ) { | ||
packageOptions.assetSrcPathsByType[ thisAssetType ] = []; | ||
// resolve relative globs to absolute globs | ||
var relativeGlobsOfThisType = packageJson[ thisAssetType ] || []; | ||
if( _.isString( relativeGlobsOfThisType ) ) relativeGlobsOfThisType = [ relativeGlobsOfThisType ]; | ||
var absoluteGlobsOfThisType = relativeGlobsOfThisType.map( function( thisGlob ) { return path.resolve( packageOptions.path, thisGlob ); } ); | ||
packageOptions.assetGlobsByType[ thisAssetType ] = absoluteGlobsOfThisType; | ||
// resolve absolute globs to actual src files | ||
async.map( absoluteGlobsOfThisType, glob, | ||
function( err, arrayOfResolvedGlobs ) { | ||
if( err ) return nextParallel( err ); | ||
var assetsOfThisType = _.flatten( arrayOfResolvedGlobs ); | ||
packageOptions.assetSrcPathsByType[ thisAssetType ] = assetsOfThisType; | ||
nextParallel(); | ||
} ); | ||
}, function( nextParallel ) { | ||
// resolve transform names to actual tranforms | ||
packageOptions.assetTransformsByType[ thisAssetType ] = []; | ||
if( packageJson.transforms ) { | ||
if( _.isArray( packageJson.transforms ) ) | ||
transformNames = packageJson.transforms; | ||
else | ||
transformNames = packageJson.transforms[ thisAssetType ] || []; | ||
} | ||
else | ||
transformNames = []; | ||
async.map( transformNames, function( thisTransformName, nextTransform ) { | ||
resolve( thisTransformName, { basedir : packageJson.__dirname }, function( err, modulePath ) { | ||
if( err ) return nextTransform( err ); | ||
nextTransform( null, require( modulePath ) ); | ||
} ); | ||
}, function( err, transforms ) { | ||
if( err ) return nextParallel( err ); | ||
packageOptions.assetTransformsByType[ thisAssetType ] = transforms; | ||
nextParallel(); | ||
} ); | ||
} ], nextAssetType ); | ||
}, function( err ) { | ||
if( err ) return callback( err ); | ||
callback( null, packageOptions ); | ||
} ); | ||
} |
var fs = require( 'fs' ); | ||
var resolve = require( 'resolve' ); | ||
var async = require( 'async' ); | ||
@@ -42,5 +41,4 @@ var mkdirp = require( 'mkdirp' ); | ||
return stream.pipe( combine.apply( null, transforms.map( function( thisTransform ) { | ||
console.log( "doing some transform" ); | ||
return thisTransform( _this.srcPath ); | ||
} ) ) ); | ||
}; |
@@ -7,4 +7,6 @@ var path = require('path'); | ||
var async = require( 'async' ); | ||
var glob = require( 'glob' ); | ||
var globwatcher = require( 'globwatcher' ).globwatcher; | ||
var Asset = require( './asset' ); | ||
var resolve = require( 'resolve' ); | ||
@@ -90,26 +92,44 @@ module.exports = Package; | ||
Package.prototype.createAssetWatchers = function() { | ||
Package.prototype.createWatchers = function( assetTypes ) { | ||
this._createPackageJsonWatcher( assetTypes ); | ||
this._createAssetGlobWatchers(); | ||
}; | ||
/********************* Private instance methods *********************/ | ||
Package.prototype._createPackageJsonWatcher = function( assetTypes ) { | ||
var _this = this; | ||
this.assetGlobWatchers = []; | ||
var assetJsonWatcher = globwatcher( path.resolve( this.path, "package.json" ) ); | ||
assetJsonWatcher.on( 'changed', function( srcPath ) { | ||
fs.readFile( srcPath, 'utf8', function( err, packageJson ) { | ||
if( err ) return console.log( 'Watch error: ' + err ); | ||
function emitAssetUpdatedEventOnRelevantParcels( eventType, asset ) { | ||
var allRelevantParcels = _this.dependentParcels; | ||
if( _this.isParcel ) allRelevantParcels.push( _this ); // we also want to trigger the same behavior on the parcel itself. | ||
try { | ||
packageJson = JSON.parse( packageJson ); | ||
} catch( err ) { | ||
return console.log( 'Watch error: ' + err ); | ||
} | ||
allRelevantParcels.forEach( function( thisParcel ) { | ||
thisParcel.emit( 'assetUpdated', eventType, asset ); | ||
Package.getOptionsFromPackageJson( _this.packageId, _this.path, packageJson, assetTypes, function( err, options ) { | ||
if( err ) return console.log( 'Watch error: ' + err ); | ||
_.extend( _this, options ); | ||
_this.createAllAssets( assetTypes ); | ||
_this._destroyAssetGlobWatchers(); | ||
_this._createAssetGlobWatchers(); | ||
_this._emitEventOnRelevantParcels( 'packageJsonUpdated' ); | ||
} ); | ||
} ); | ||
} | ||
} ); | ||
}; | ||
// TODO: implement listener for package.json files | ||
// var assetJsonWatcher = globwatcher( path.resolve( this.path, "package.json" ) ); | ||
// assetJsonWatcher.on( 'changed', function( srcPath ) { | ||
// // load new package.json | ||
// // evaluate globs | ||
// // recreate assets | ||
// // recreate glob listeners | ||
// // notify parcel so it can re-process itself | ||
// } ); | ||
Package.prototype._createAssetGlobWatchers = function() { | ||
var _this = this; | ||
this.assetGlobWatchers = []; | ||
_.each( _this.assetGlobsByType, function( globs, thisAssetType ) { | ||
@@ -123,3 +143,3 @@ var thisWatcher = globwatcher( globs ); | ||
emitAssetUpdatedEventOnRelevantParcels( 'changed', asset ); | ||
emitEventOnRelevantParcels( 'assetUpdated', 'changed', asset ); | ||
} catch( err ) { | ||
@@ -139,3 +159,3 @@ return console.log( 'Watch error: ' + err ); | ||
emitAssetUpdatedEventOnRelevantParcels( 'added', asset ); | ||
emitEventOnRelevantParcels( 'assetUpdated', 'added', asset ); | ||
} catch( err ) { | ||
@@ -153,3 +173,3 @@ return console.log( 'Watch error: ' + err ); | ||
emitAssetUpdatedEventOnRelevantParcels( 'deleted', asset ); | ||
emitEventOnRelevantParcels( 'assetUpdated', 'deleted', asset ); | ||
} catch( err ) { | ||
@@ -164,4 +184,97 @@ return console.log( 'Watch error: ' + err ); | ||
Package.prototype._destroyAssetGlobWatchers = function() { | ||
this.assetGlobWatchers.forEach( function( thisAssetGlobWatcher ) { | ||
thisAssetGlobWatcher.close(); | ||
} ); | ||
this.assetGlobWatchers = []; | ||
}; | ||
Package.prototype._emitEventOnRelevantParcels = function() { | ||
var args = Array.prototype.slice.call( arguments ); | ||
var allRelevantParcels = this.dependentParcels; | ||
if( this.isParcel ) allRelevantParcels.push( this ); // we also want to trigger the same behavior on the parcel itself. | ||
allRelevantParcels.forEach( function( thisParcel ) { | ||
thisParcel.emit.apply( thisParcel, args ); | ||
} ); | ||
}; | ||
/********************* Static class methods *********************/ | ||
Package.getOptionsFromPackageJson = function( packageId, packagePath, packageJson, assetTypes, callback ) { | ||
var packageOptions = {}; | ||
packageOptions.package = packageJson; | ||
packageOptions.id = packageId; | ||
packageOptions.path = packagePath; | ||
packageOptions.assetSrcPathsByType = {}; | ||
packageOptions.assetTransformsByType = {}; | ||
packageOptions.assetGlobsByType = {}; | ||
if( packageJson.view ) { | ||
packageOptions.view = path.resolve( packageOptions.path, packageJson.view ); | ||
packageOptions.isParcel = true; | ||
} | ||
async.each( assetTypes, function( thisAssetType, nextAssetType ) { | ||
async.parallel( [ function( nextParallel ) { | ||
packageOptions.assetSrcPathsByType[ thisAssetType ] = []; | ||
// resolve relative globs to absolute globs | ||
var relativeGlobsOfThisType = packageJson[ thisAssetType ] || []; | ||
if( _.isString( relativeGlobsOfThisType ) ) relativeGlobsOfThisType = [ relativeGlobsOfThisType ]; | ||
var absoluteGlobsOfThisType = relativeGlobsOfThisType.map( function( thisGlob ) { return path.resolve( packagePath, thisGlob ); } ); | ||
packageOptions.assetGlobsByType[ thisAssetType ] = absoluteGlobsOfThisType; | ||
// resolve absolute globs to actual src files | ||
async.map( absoluteGlobsOfThisType, glob, | ||
function( err, arrayOfResolvedGlobs ) { | ||
if( err ) return nextParallel( err ); | ||
var assetsOfThisType = _.flatten( arrayOfResolvedGlobs ); | ||
packageOptions.assetSrcPathsByType[ thisAssetType ] = assetsOfThisType; | ||
nextParallel(); | ||
} ); | ||
}, function( nextParallel ) { | ||
// resolve transform names to actual tranforms | ||
packageOptions.assetTransformsByType[ thisAssetType ] = []; | ||
if( packageJson.transforms ) { | ||
if( _.isArray( packageJson.transforms ) ) | ||
transformNames = packageJson.transforms; | ||
else | ||
transformNames = packageJson.transforms[ thisAssetType ] || []; | ||
} | ||
else | ||
transformNames = []; | ||
async.map( transformNames, function( thisTransformName, nextTransform ) { | ||
resolve( thisTransformName, { basedir : packageJson.__dirname }, function( err, modulePath ) { | ||
if( err ) return nextTransform( err ); | ||
nextTransform( null, require( modulePath ) ); | ||
} ); | ||
}, function( err, transforms ) { | ||
if( err ) return nextParallel( err ); | ||
packageOptions.assetTransformsByType[ thisAssetType ] = transforms; | ||
nextParallel(); | ||
} ); | ||
} ], nextAssetType ); | ||
}, function( err ) { | ||
if( err ) return callback( err ); | ||
callback( null, packageOptions ); | ||
} ); | ||
}; | ||
/********************* Utility functions *********************/ | ||
function renameFileExtension( file, toExt ) { | ||
return file.replace( new RegExp( path.extname( file ) + "$" ), toExt ); | ||
} |
@@ -64,10 +64,25 @@ var inherits = require( 'inherits' ); | ||
Parcel.prototype.calcParcelAssets = function( assetTypes ) { | ||
memo = {}; | ||
assetTypes.forEach( function( thisAssetType ) { memo[ thisAssetType ] = []; } ); | ||
var sortedAssets = this.sortedDependencies.concat( this ).reduce( function( memo, thisPackage ) { | ||
var thisPackageAssets = thisPackage.assetsByType; | ||
_.each( thisPackageAssets, function( assets, thisAssetType ) { | ||
if( _.contains( assetTypes, thisAssetType ) ) | ||
memo[ thisAssetType ] = memo[ thisAssetType ].concat( assets ); | ||
} ); | ||
return memo; | ||
}, memo ); | ||
this.parcelAssetsByType = _.extend( {}, this.parcelAssetsByType, sortedAssets ); | ||
}; | ||
Parcel.prototype.attachWatchListeners = function( bundles ) { | ||
var _this = this; | ||
// for watching | ||
this.on( 'assetUpdated', function( eventType, asset ) { | ||
console.log( eventType ); | ||
console.log( asset ); | ||
if( _.contains( [ 'added', 'deleted' ], eventType ) ) | ||
@@ -79,14 +94,15 @@ this.calcParcelAssets( [ asset.type ] ); | ||
if( err ) throw new Error( 'Error during watch.' ); | ||
// ... done | ||
// ... done! | ||
} ); | ||
} | ||
} ); | ||
}; | ||
Parcel.prototype.createPackageOutputDirectories = function( dstDir, callback ) { | ||
async.each( this.sortedDependencies, function( thisPackage, nextPackage ) { | ||
var thisPackageId = thisPackage.id; | ||
var packageDirectoryPath = path.join( dstDir, thisPackageId ); | ||
thisPackage.createOutputDirectory( packageDirectoryPath, nextPackage ); | ||
}, callback ); | ||
this.on( 'packageJsonUpdated', function() { | ||
var bundlesToRewrite = _.pick( bundles, _.without( Object.keys( bundles ), 'script' ) ); | ||
this.calcParcelAssets( Object.keys( bundlesToRewrite ) ); | ||
_this.writeBundles( bundlesToRewrite, function() { | ||
// ... done! | ||
} ); | ||
} ); | ||
}; | ||
@@ -115,3 +131,11 @@ | ||
Parcel.prototype.writeBundles = function( bundles, callback ) { | ||
var _this = this; | ||
async.each( Object.keys( bundles ), function( thisAssetType, nextEach ) { | ||
_this.writeBundle( thisAssetType, bundles[ thisAssetType ], nextEach ); | ||
}, callback ); | ||
}; | ||
Parcel.prototype.writeBundle = function( assetType, dstPath, callback ) { | ||
@@ -161,127 +185,1 @@ var _this = this; | ||
// Parcel.prototype.writeJsBundle = function( dstPath, callback ) { | ||
// var _this = this; | ||
// if( ! _this.outputDirectoryPath ) callback( new Error( 'Attempt to write bundle but no output directory has been created for the parcel.' ) ); | ||
// var jsBundle = through2(); | ||
// var tempJsBundlePath = path.join( _this.outputDirectoryPath, '.bundle_temp.js' ); | ||
// var jsBundleShasum; | ||
// this.jsBundleStream.pipe( jsBundle ); | ||
// // pipe the bundle output to both a temporary file and crypto at the same time. need | ||
// // the temporary file in order to empty the output, or something? not really sure. | ||
// async.parallel( [ function( nextParallel ) { | ||
// jsBundle.pipe( crypto.createHash( 'sha1' ) ).pipe( concat( function( buf ) { | ||
// jsBundleShasum = buf.toString( 'hex' ); | ||
// nextParallel(); | ||
// } ) ); | ||
// }, function( nextParallel ) { | ||
// jsBundle.pipe( fs.createWriteStream( tempJsBundlePath ) ).on( 'close', nextParallel ); | ||
// } ], function( err ) { | ||
// if( err ) return callback( err ); | ||
// if( ! dstPath ) dstPath = path.join( _this.outputDirectoryPath, path.basename( _this.path ) + '_bundle_' + jsBundleShasum + '.js' ); | ||
// fs.rename( tempJsBundlePath, dstPath, function( err ) { | ||
// if( err ) return callback( err ); | ||
// this.jsBundlePath = dstPath; | ||
// callback( null ); | ||
// } ); | ||
// } ); | ||
// }; | ||
// Parcel.prototype.writeCssBundle = function( dstPath, callback ) { | ||
// var _this = this; | ||
// if( ! _this.outputDirectoryPath ) callback( new Error( 'Attempt to write bundle but no output directory has been created for the parcel.' ) ); | ||
// var cssBundle = through2(); | ||
// var cssBundleShasum; | ||
// var tempCssBundlePath = path.join( _this.outputDirectoryPath, '.bundle_temp.css' ); | ||
// var destCssBundlePath; | ||
// var styleStreams = _.pluck( this.parcelAssetsByType.style, 'stream' ); | ||
// if( styleStreams.length === 0 ) return callback(); | ||
// async.series( [ function( nextSeries ) { | ||
// // pipe all our style streams to the css bundle in order | ||
// async.eachSeries( styleStreams, function( thisStyleStream, nextStyleStream ) { | ||
// thisStyleStream.pipe( cssBundle, { end : false } ); | ||
// thisStyleStream.on( 'end', nextStyleStream ); | ||
// }, function( err ) { | ||
// if( err ) return nextSeries( err ); | ||
// cssBundle.end(); | ||
// nextSeries(); | ||
// } ); | ||
// }, function( nextSeries ) { | ||
// // pipe our bundle to both a temporary file and crypto at the same time. need | ||
// // the temporary file in order to empty the output, or something? not really sure. | ||
// async.parallel( [ function( nextParallel ) { | ||
// cssBundle.pipe( crypto.createHash( 'sha1' ) ).pipe( concat( function( buf ) { | ||
// cssBundleShasum = buf.toString( 'hex' ); | ||
// nextParallel(); | ||
// } ) ); | ||
// }, function( nextParallel ) { | ||
// cssBundle.pipe( fs.createWriteStream( tempCssBundlePath ) ).on( 'close', nextParallel ); | ||
// } ], nextSeries ); | ||
// }, function( nextSeries ) { | ||
// // default dstPath to include a shasum of file's conents | ||
// if( ! dstPath ) dstPath = path.join( _this.outputDirectoryPath, path.basename( _this.path ) + '_bundle_' + cssBundleShasum + '.css' ); | ||
// fs.rename( tempCssBundlePath, dstPath, function( err ) { | ||
// if( err ) return nextSeries( err ); | ||
// nextSeries(); | ||
// } ); | ||
// } ], function( err ) { | ||
// if( err ) return callback( err ); | ||
// this.cssBundlePath = dstPath; | ||
// return callback( null ); | ||
// } ); | ||
// }; | ||
// Parcel.prototype.writeAssetsJson = function( callback ) { | ||
// var content = { | ||
// 'script' : this.jsBundlePath | ||
// }; | ||
// if( this.cssBundlePath ) | ||
// content.style = this.cssBundlePath; | ||
// else | ||
// content.style = _.pluck( this.parcelAssetsByType.style, 'dstPath' ); | ||
// fs.writeFile( path.join( this.outputDirectoryPath, 'assets.json' ), JSON.stringify( content, null, 4 ), function( err ) { | ||
// if( err ) return callback( err ); | ||
// return callback(); | ||
// } ); | ||
// }; | ||
Parcel.prototype.calcParcelAssets = function( assetTypes ) { | ||
memo = {}; | ||
assetTypes.forEach( function( thisAssetType ) { memo[ thisAssetType ] = []; } ); | ||
var sortedAssets = this.sortedDependencies.concat( this ).reduce( function( memo, thisPackage ) { | ||
var thisPackageAssets = thisPackage.assetsByType; | ||
_.each( thisPackageAssets, function( assets, thisAssetType ) { | ||
if( _.contains( assetTypes, thisAssetType ) ) | ||
memo[ thisAssetType ] = memo[ thisAssetType ].concat( assets ); | ||
} ); | ||
return memo; | ||
}, memo ); | ||
this.parcelAssetsByType = _.extend( {}, this.parcelAssetsByType, sortedAssets ); | ||
}; |
{ | ||
"name": "parcelify", | ||
"version": "0.5.0", | ||
"description": "Bundle css and client side templates in npm modules", | ||
"version": "0.5.1", | ||
"description": "Add css and template bundles to browserify output", | ||
"main": "index.js", | ||
@@ -6,0 +6,0 @@ "bin": { |
# Parcelify | ||
Parcelify is a browserify wrapper that creates a css bundle (and optionally template bundle) from assets in npm modules. | ||
Parcelify is a browserify wrapper that creates a css and / or template bundle from assets in npm modules. | ||
* Uses browserify / js requires to resolve dependencies | ||
* Supports transforms like sass and less | ||
* Intelligent watch mode | ||
* Easy to use command line interface or robust API | ||
## How dat work? | ||
@@ -80,2 +75,16 @@ | ||
``` | ||
{ | ||
"name": "myModule", | ||
"description": "Example package.json for hypothetical myModule.", | ||
"version": "1.5.0", | ||
"style" : "*.scss", | ||
"template" : [ "templates/part_1.tmpl", "templates/part_2.tmpl" ], | ||
"transforms" : [ "sass-css-stream" ], | ||
"devDependencies" : { | ||
"sass-css-stream": "0.0.1" | ||
} | ||
} | ||
``` | ||
## API | ||
@@ -82,0 +91,0 @@ |
@@ -6,3 +6,8 @@ { | ||
"template": "*.tmpl", | ||
"transforms" : [ "sass-css-stream" ] | ||
"transforms": [ | ||
"sass-css-stream" | ||
], | ||
"dependencies": { | ||
"sass-css-stream": "0.0.1" | ||
} | ||
} |
@@ -9,3 +9,3 @@ var test = require('tape'); | ||
test( 'page1', function( t ) { | ||
test( 'page1', function( t ) { | ||
t.plan( 2 ); | ||
@@ -12,0 +12,0 @@ |
133
36969
42
704