diff --git a/download.js b/download.js index 37110d01..83f28e23 100644 --- a/download.js +++ b/download.js @@ -1,5 +1,6 @@ var downloadLogger, jqueryUis, _ = require( "underscore" ), + Builder = require( "./lib/builder" ), fs = require( "fs" ), Handlebars = require( "handlebars" ), JqueryUi = require( "./lib/jquery-ui" ), @@ -83,7 +84,7 @@ Frontend.prototype = { create: function( fields, response, callback ) { try { - var build, components, packer, start, theme, + var builder, components, jqueryUi, packer, start, theme, themeVars = null; if ( fields.theme !== "none" ) { themeVars = querystring.parse( fields.theme ); @@ -99,10 +100,11 @@ Frontend.prototype = { }); components = Object.keys( _.omit( fields, "scope", "theme", "theme-folder-name", "version" ) ); start = new Date(); - build = JqueryUi.find( fields.version ).build( components, { + jqueryUi = JqueryUi.find( fields.version ); + builder = new Builder( jqueryUi, components, { scope: fields.scope }); - packer = new Packer( build, theme, { + packer = new Packer( builder, theme, { scope: fields.scope }); response.setHeader( "Content-Type", "application/zip" ); @@ -116,9 +118,9 @@ Frontend.prototype = { JSON.stringify({ build_size: written, build_time: new Date() - start, - components: build.components, + components: builder.components, theme_name: theme.name, - version: build.pkg.version + version: jqueryUi.pkg.version }) ); return callback(); diff --git a/lib/builder.1.10.0.js b/lib/builder.1.10.0.js index fb38796c..e9de886f 100644 --- a/lib/builder.1.10.0.js +++ b/lib/builder.1.10.0.js @@ -15,7 +15,7 @@ stripBanner = util.stripBanner; /** * Builder 1.10 */ -function Builder_1_10_0( build, jqueryUi, components, options ) { +function Builder_1_10_0( build, jqueryUi, components, options, callback ) { var _bundleCss, baseCss, baseCssMin, cssComponentFileNames, existingCss, jsComponentFileNames, selectedDemoRe, selectedRe, files = jqueryUi.files(), min = function( file ) { @@ -200,7 +200,7 @@ function Builder_1_10_0( build, jqueryUi, components, options ) { // Ad hoc build.jqueryCore = files.jqueryCore; - return build; + callback( null, build ); } module.exports = Builder_1_10_0; diff --git a/lib/builder.1.11.0.js b/lib/builder.1.11.0.js index 0ef0f1fd..6b155208 100644 --- a/lib/builder.1.11.0.js +++ b/lib/builder.1.11.0.js @@ -1,9 +1,11 @@ var _basename, demoIndexTemplate, docsTemplate, flatten, stripBanner, + async = require( "async" ), banner = require( "./banner" ), Files = require( "./files" ), fs = require( "fs" ), handlebars = require( "handlebars" ), path = require( "path" ), + rjs = require( "./rjs" ), sqwish = require( "sqwish" ), ThemeRoller = require( "./themeroller" ), util = require( "./util" ); @@ -23,8 +25,8 @@ path.basename = function() { /** * Builder 1.11 */ -function Builder_1_11_0( build, jqueryUi, components, options ) { - var _bundleCss, baseCss, baseCssMin, cssComponentFileNames, docsCategories, existingCss, jsComponentFileNames, selectedDemoRe, selectedRe, +function Builder_1_11_0( build, jqueryUi, components, options, callback ) { + var _bundleCss, baseCss, baseCssMin, cssComponentFileNames, docsCategories, existingCss, selectedDemoRe, selectedRe, files = jqueryUi.files(), min = function( file ) { return files.min( file ); @@ -69,41 +71,6 @@ function Builder_1_11_0( build, jqueryUi, components, options ) { return (/images/).test( file.path ); }); - // I18n files - if ( components.indexOf( "datepicker" ) >= 0 ) { - build.i18nFiles = files.i18nFiles; - build.i18nMinFiles = files.i18nFiles.map( min ); - build.bundleI18n = Files({ - path: "jquery-ui-i18n.js", - data: files.i18nFiles.reduce(function( sum, file ) { - return sum + stripBanner( file ); - }, banner( jqueryUi.pkg, files.i18nFiles.paths().map( path.basename ) ) ) - }); - build.bundleI18nMin = Files({ - path: "jquery-ui-i18n.min.js", - data: banner( jqueryUi.pkg, files.i18nFiles.paths().map( path.basename ), { minify: true }) + stripBanner( files.min( build.bundleI18n[ 0 ] ) ) - }); - } else { - build.i18nFiles = build.i18nMinFiles = build.bundleI18n = build.bundleI18nMin = Files(); - } - - // Bundle JS (and minified) - jsComponentFileNames = components.map(function( component ) { - return component + ".js"; - }); - build.bundleJs = Files({ - path: "jquery-ui.js", - data: build.components.reduce(function( sum, component ) { - return sum + stripBanner( files.get( "ui/" + component + ".js" ) ); - }, banner( jqueryUi.pkg, jsComponentFileNames ) ) - }); - build.bundleJsMin = Files({ - path: "jquery-ui.min.js", - data: build.components.reduce(function( sum, component ) { - return sum + stripBanner( files.min( files.get( "ui/" + component + ".js" ) ) ); - }, banner( jqueryUi.pkg, jsComponentFileNames, { minify: true } ) ) - }); - // Bundle CSS (and minified) existingCss = function( component ) { return files.get( "themes/base/" + component + ".css" ) !== undefined; @@ -208,7 +175,84 @@ function Builder_1_11_0( build, jqueryUi, components, options ) { // Ad hoc build.jqueryCore = files.jqueryCore; - return build; + // I18n files + function i18nFiles( callback ) { + if ( components.indexOf( "datepicker" ) >= 0 ) { + build.i18nFiles = files.i18nFiles; + build.i18nMinFiles = files.i18nFiles.map( min ); + async.series([ + function( callback ) { + rjs({ + files: files, + include: files.i18nFiles.rename( /ui\//, "" ).map(function( file ) { + return file.path; + }), + exclude: [ "jquery", "jqueryui/core", "jqueryui/datepicker" ], + jquery: files.jqueryCore[ 0 ].path + }, function( error, data ) { + if ( error ) { + return callback( error ); + } + build.bundleI18n = Files({ + path: "jquery-ui-i18n.js", + data: banner( jqueryUi.pkg, files.i18nFiles.paths().map( path.basename ) ) + data + }); + callback(); + }); + }, + function( callback ) { + build.bundleI18nMin = Files({ + path: "jquery-ui-i18n.min.js", + data: banner( jqueryUi.pkg, files.i18nFiles.paths().map( path.basename ), { minify: true }) + stripBanner( files.min( build.bundleI18n[ 0 ] ) ) + }); + callback(); + } + ], callback ); + } else { + build.i18nFiles = build.i18nMinFiles = build.bundleI18n = build.bundleI18nMin = Files(); + callback(); + } + } + + // Bundle JS (and minified) + function bundleJs( callback ) { + var jsComponentFileNames = components.map(function( component ) { + return component + ".js"; + }); + async.series([ + function( callback ) { + rjs({ + files: files, + include: jsComponentFileNames, + exclude: [ "jquery" ], + jquery: files.jqueryCore[ 0 ].path + }, function( error, data ) { + if ( error ) { + return callback( error ); + } + build.bundleJs = Files({ + path: "jquery-ui.js", + data: banner( jqueryUi.pkg, jsComponentFileNames ) + data + }); + callback(); + }); + }, + function( callback ) { + build.bundleJsMin = Files({ + path: "jquery-ui.min.js", + data: banner( jqueryUi.pkg, jsComponentFileNames, { minify: true } ) + stripBanner( files.min( build.bundleJs[ 0 ] ) ) + }); + callback(); + } + ], callback ); + } + + async.series([ + i18nFiles, + bundleJs + ], function( error ) { + callback( error, build ); + }); } module.exports = Builder_1_11_0; diff --git a/lib/builder.js b/lib/builder.js index 1dd726c1..3fcec9a6 100644 --- a/lib/builder.js +++ b/lib/builder.js @@ -1,6 +1,4 @@ var cache, - Builder_1_10_0 = require( "./builder.1.10.0.js" ), - Builder_1_11_0 = require( "./builder.1.11.0.js" ), Cache = require( "./cache" ), semver = require( "semver" ); @@ -22,23 +20,47 @@ function Builder( jqueryUi, components, options ) { } Builder.prototype = { - build: function() { + build: function( callback ) { var cacheKey = this.jqueryUi.pkg.version + JSON.stringify( this.expandComponents( this.components ) ), cached = cache.get( cacheKey ); if ( cached ) { - return cached; + + // if we have data, call the callback, otherwise push ours + if ( cached.data ) { + callback( null, cached.data ); + } else { + cached.callbacks.push( callback ); + } + return true; + } + + cached = { + callbacks: [ callback ] + }; + cache.set( cacheKey, cached ); + + function done( err, data ) { + var callbacks = cached.callbacks; + if ( !err ) { + cached.data = data; + delete cached.callbacks; + } + callbacks.forEach(function( callback ) { + callback( err, data ); + }); + delete cached.callbacks; + if ( err ) { + cache.destroy( cacheKey ); + } } // FIXME s/1.11.0pre/1.11.0 if ( semver.gte( this.jqueryUi.pkg.version, "1.11.0pre" ) ) { - cached = Builder_1_11_0( this, this.jqueryUi, this.components, this.options ); + require( "./builder.1.11.0" )( this, this.jqueryUi, this.components, this.options, done ); } else { - cached = Builder_1_10_0( this, this.jqueryUi, this.components, this.options ); + require( "./builder.1.10.0" )( this, this.jqueryUi, this.components, this.options, done ); } - - cache.set( cacheKey, cached ); - return cached; }, expandComponents: function( components ) { diff --git a/lib/jquery-ui.js b/lib/jquery-ui.js index 6065768f..b2faec5b 100644 --- a/lib/jquery-ui.js +++ b/lib/jquery-ui.js @@ -87,15 +87,6 @@ JqueryUi.find = function( version ) { }; JqueryUi.prototype = { - build: function( components, options ) { - var builder; - if ( !Builder ) { - Builder = require( "./builder" ); - } - builder = new Builder( this, components, options ); - return builder.build(); - }, - categories: function() { if ( !this._categories ) { var map = {}; diff --git a/lib/packer.js b/lib/packer.js index 08859013..5a669045 100644 --- a/lib/packer.js +++ b/lib/packer.js @@ -23,8 +23,8 @@ function stripThemeImport( src ) { /** - * Packer( build, theme, options ) - * - build [ instanceof Builder ]: jQuery UI build object. + * Packer( builder, theme, options ) + * - builder [ instanceof Builder ]: jQuery UI builder object. * - theme [ instanceof ThemeRoller ]: theme object. * - options: details below. * @@ -35,14 +35,14 @@ function stripThemeImport( src ) { * - skipDocs [ Boolean ]: Skip doc files on package. Default is false. * - skipTheme [ Boolean ]: Skip theme files on package. Default is false. */ -function Packer( build, theme, options ) { +function Packer( builder, theme, options ) { this.options = options = options || {}; if ( typeof options.bundleSuffix === "undefined" ) { options.bundleSuffix = ".custom"; } - this.basedir = "jquery-ui-" + build.pkg.version + options.bundleSuffix; - this._build = build; + this.basedir = "jquery-ui-" + builder.jqueryUi.pkg.version + options.bundleSuffix; + this.builder = builder; this.theme = theme; if ( options.skipTheme ) { @@ -74,7 +74,8 @@ Packer.prototype = { * } */ pack: function( callback ) { - var add = function( file ) { + var build, + add = function( file ) { if ( arguments.length === 2 ) { file = { path: arguments[ 0 ], @@ -87,162 +88,181 @@ Packer.prototype = { }); }, basedir = this.basedir, - build = this._build, + builder = this.builder, options = this.options, output = [], theme = this.theme; - // Common and component files (includes baseThemeFiles) - build.commonFiles.into( "development-bundle/" ).forEach( add ); - build.componentFiles.into( "development-bundle/" ).forEach( add ); + function _build( callback ) { + builder.build(function( error, _build ) { + if ( error ) { + return callback( error ); + } + build = _build; + callback(); + }); + } - build.componentMinFiles - .rename( /^ui/, "ui/minified" ) - .into( "development-bundle/" ).forEach( add ); + function pack( callback ) { + // Common and component files (includes baseThemeFiles) + build.commonFiles.into( "development-bundle/" ).forEach( add ); + build.componentFiles.into( "development-bundle/" ).forEach( add ); - // Base theme (baseThemeFiles have been included by componentFiles) - build.baseThemeMinFiles - .rename( /^themes\/base/, "themes/base/minified" ) - .into( "development-bundle/" ).forEach( add ); + build.componentMinFiles + .rename( /^ui/, "ui/minified" ) + .into( "development-bundle/" ).forEach( add ); - build.baseThemeImages - .rename( /^themes\/base\/images/, "themes/base/minified/images" ) - .into( "development-bundle/" ).forEach( add ); + // Base theme (baseThemeFiles have been included by componentFiles) + build.baseThemeMinFiles + .rename( /^themes\/base/, "themes/base/minified" ) + .into( "development-bundle/" ).forEach( add ); - // I18n - build.i18nFiles.into( "development-bundle/" ).forEach( add ); - build.i18nMinFiles - .rename( /^ui\/i18n/, "ui/minified/i18n" ) - .into( "development-bundle/" ).forEach( add ); - build.bundleI18n.into( "development-bundle/ui/i18n/" ).forEach( add ); - build.bundleI18nMin.into( "development-bundle/ui/minified/i18n/" ).forEach( add ); + build.baseThemeImages + .rename( /^themes\/base\/images/, "themes/base/minified/images" ) + .into( "development-bundle/" ).forEach( add ); - // Bundle JS (and minified) - build.bundleJs - .rename( /^jquery-ui/, "jquery-ui" + options.bundleSuffix ) - .into( "development-bundle/ui/" ).forEach( add ); + // I18n + build.i18nFiles.into( "development-bundle/" ).forEach( add ); + build.i18nMinFiles + .rename( /^ui\/i18n/, "ui/minified/i18n" ) + .into( "development-bundle/" ).forEach( add ); + build.bundleI18n.into( "development-bundle/ui/i18n/" ).forEach( add ); + build.bundleI18nMin.into( "development-bundle/ui/minified/i18n/" ).forEach( add ); - build.bundleJs - .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) - .into( "js/" ).forEach( add ); + // Bundle JS (and minified) + build.bundleJs + .rename( /^jquery-ui/, "jquery-ui" + options.bundleSuffix ) + .into( "development-bundle/ui/" ).forEach( add ); - build.bundleJsMin - .rename( /^jquery-ui/, "jquery-ui" + options.bundleSuffix ) - .into( "development-bundle/ui/minified/" ).forEach( add ); + build.bundleJs + .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) + .into( "js/" ).forEach( add ); - build.bundleJsMin - .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) - .into( "js/" ).forEach( add ); + build.bundleJsMin + .rename( /^jquery-ui/, "jquery-ui" + options.bundleSuffix ) + .into( "development-bundle/ui/minified/" ).forEach( add ); - // Bundle CSS (and minified) - build.bundleCss( build.baseTheme ).into( "development-bundle/themes/base/" ).forEach( add ); - build.bundleCssMin( build.baseThemeMin ).into( "development-bundle/themes/base/minified/" ).forEach( add ); - if ( !this.options.skipTheme ) { - build.bundleCss( theme ) - .tee(function( clone ) { - clone.into( "development-bundle/themes/" + theme.folderName() + "/" ).forEach( add ); - }) + build.bundleJsMin .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) - .into( "css/" + theme.folderName() + "/" ).forEach( add ); + .into( "js/" ).forEach( add ); - build.bundleCssMin( theme ) - .tee(function( clone ) { - clone.into( "development-bundle/themes/" + theme.folderName() + "/minified/" ).forEach( add ); - }) - .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) - .into( "css/" + theme.folderName() + "/" ).forEach( add ); - } + // Bundle CSS (and minified) + build.bundleCss( build.baseTheme ).into( "development-bundle/themes/base/" ).forEach( add ); + build.bundleCssMin( build.baseThemeMin ).into( "development-bundle/themes/base/minified/" ).forEach( add ); + if ( !options.skipTheme ) { + build.bundleCss( theme ) + .tee(function( clone ) { + clone.into( "development-bundle/themes/" + theme.folderName() + "/" ).forEach( add ); + }) + .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) + .into( "css/" + theme.folderName() + "/" ).forEach( add ); - // Demo files - build.demoFiles.into( "development-bundle/" ).forEach( add ); + build.bundleCssMin( theme ) + .tee(function( clone ) { + clone.into( "development-bundle/themes/" + theme.folderName() + "/minified/" ).forEach( add ); + }) + .rename( /^jquery-ui/, "jquery-ui-" + build.pkg.version + options.bundleSuffix ) + .into( "css/" + theme.folderName() + "/" ).forEach( add ); + } - // Doc files - if ( !options.skipDocs ) { - build.docFiles.into( "development-bundle/" ).forEach( add ); - } + // Demo files + build.demoFiles.into( "development-bundle/" ).forEach( add ); - // Test files - if ( options.addTests ) { - build.testFiles.into( "development-bundle/" ).forEach( add ); - } + // Doc files + if ( !options.skipDocs ) { + build.docFiles.into( "development-bundle/" ).forEach( add ); + } + + // Test files + if ( options.addTests ) { + build.testFiles.into( "development-bundle/" ).forEach( add ); + } - // Ad hoc - build.jqueryCore.into( "js/" ).forEach( add ); - add( "index.html", indexTemplate({ - bundleCss: "jquery-ui-" + build.pkg.version + options.bundleSuffix + ".css", - bundleJs: "jquery-ui-" + build.pkg.version + options.bundleSuffix + ".js", - jqueryCore: build.jqueryCore[ 0 ].path, - ui: build.components.reduce(function( sum, component ) { - sum[ component ] = true; - return sum; - }, {}), - themeFolderName: theme.folderName(), - version: build.pkg.version - })); + // Ad hoc + build.jqueryCore.into( "js/" ).forEach( add ); + add( "index.html", indexTemplate({ + bundleCss: "jquery-ui-" + build.pkg.version + options.bundleSuffix + ".css", + bundleJs: "jquery-ui-" + build.pkg.version + options.bundleSuffix + ".js", + jqueryCore: build.jqueryCore[ 0 ].path, + ui: build.components.reduce(function( sum, component ) { + sum[ component ] = true; + return sum; + }, {}), + themeFolderName: theme.folderName(), + version: build.pkg.version + })); - // Custom theme files - if ( !this.options.skipTheme ) { - build.baseThemeExceptThemeOrImages.forEach(function( file ) { - if ( theme.isNull && (/all.css/).test( file.path ) ) { - add( "development-bundle/themes/" + theme.folderName() + "/" + path.basename( file.path ), stripThemeImport( file.data ) ); + // Custom theme files + if ( !options.skipTheme ) { + build.baseThemeExceptThemeOrImages.forEach(function( file ) { + if ( theme.isNull && (/all.css/).test( file.path ) ) { + add( "development-bundle/themes/" + theme.folderName() + "/" + path.basename( file.path ), stripThemeImport( file.data ) ); + } else { + add( file.path.replace( "themes/base", "development-bundle/themes/" + theme.folderName() ), options.scope ? util.scope( file.data.toString( "utf8" ), options.scope ) : file.data ); + } + }); + // minified custom theme + build.baseThemeMinFiles.filter(function( file ) { + return !(/theme\W/).test( file.path ); + }).map(function( file ) { + return options.scope ? { path: file.path, data: util.scope( file.data, options.scope ) } : file; + }).rename( /^themes\/base/, "themes/" + theme.folderName() + "/minified" ).into( "development-bundle/" ).forEach( add ); + } + if ( !theme.isNull ) { + // FIXME s/1.11.0pre/1.11.0 + if ( semver.gte( build.pkg.version, "1.11.0pre" ) ) { + add( "development-bundle/themes/" + theme.folderName() + "/theme.css", theme.css() ); + add( "development-bundle/themes/" + theme.folderName() + "/minified/theme.min.css", banner( build.pkg, null, { minify: true } ) + sqwish.minify( util.stripBanner({ data: theme.css() }) ) ); } else { - add( file.path.replace( "themes/base", "development-bundle/themes/" + theme.folderName() ), options.scope ? util.scope( file.data.toString( "utf8" ), options.scope ) : file.data ); + add( "development-bundle/themes/" + theme.folderName() + "/jquery.ui.theme.css", theme.css() ); + add( "development-bundle/themes/" + theme.folderName() + "/minified/jquery.ui.theme.min.css", banner( build.pkg, null, { minify: true } ) + sqwish.minify( util.stripBanner({ data: theme.css() }) ) ); } - }); - // minified custom theme - build.baseThemeMinFiles.filter(function( file ) { - return !(/theme\W/).test( file.path ); - }).map(function( file ) { - return options.scope ? { path: file.path, data: util.scope( file.data, options.scope ) } : file; - }).rename( /^themes\/base/, "themes/" + theme.folderName() + "/minified" ).into( "development-bundle/" ).forEach( add ); - } - if ( !theme.isNull ) { - // FIXME s/1.11.0pre/1.11.0 - if ( semver.gte( build.pkg.version, "1.11.0pre" ) ) { - add( "development-bundle/themes/" + theme.folderName() + "/theme.css", theme.css() ); - add( "development-bundle/themes/" + theme.folderName() + "/minified/theme.min.css", banner( build.pkg, null, { minify: true } ) + sqwish.minify( util.stripBanner({ data: theme.css() }) ) ); - } else { - add( "development-bundle/themes/" + theme.folderName() + "/jquery.ui.theme.css", theme.css() ); - add( "development-bundle/themes/" + theme.folderName() + "/minified/jquery.ui.theme.min.css", banner( build.pkg, null, { minify: true } ) + sqwish.minify( util.stripBanner({ data: theme.css() }) ) ); } - } - // Custom theme image files - if ( theme.isNull ) { - callback( null, output ); - } - else { - async.series([ - function( callback ) { - var themeImages = Files(); - if ( semver.gte( build.pkg.version, "1.10.0" ) ) { - themeImages.push({ - path: "animated-overlay.gif", - data: build.get( "themes/base/images/animated-overlay.gif" ).data - }); + // Custom theme image files + if ( theme.isNull ) { + callback( null, output ); + } + else { + async.series([ + function( callback ) { + var themeImages = Files(); + if ( semver.gte( build.pkg.version, "1.10.0" ) ) { + themeImages.push({ + path: "animated-overlay.gif", + data: build.get( "themes/base/images/animated-overlay.gif" ).data + }); + } + if ( themeImagesCache[ theme.name ] ) { + // Cached + callback( null, themeImages.concat( themeImagesCache[ theme.name ] ) ); + } else { + // Not cached, fetch them + theme.generateImages(function( err, imageFiles ) { + if ( err ) { + callback( err, null ); + return; + } + callback( null, themeImages.concat( imageFiles ) ); + }); + } } - if ( themeImagesCache[ theme.name ] ) { - // Cached - callback( null, themeImages.concat( themeImagesCache[ theme.name ] ) ); - } else { - // Not cached, fetch them - theme.generateImages(function( err, imageFiles ) { - if ( err ) { - callback( err, null ); - return; - } - callback( null, themeImages.concat( imageFiles ) ); - }); + ], function( err, themeImages ) { + if ( err ) { + callback( err, null ); } - } - ], function( err, themeImages ) { - if ( err ) { - callback( err, null ); - } - themeImages[ 0 ].into( [ "css/" + theme.folderName() + "/images/", "development-bundle/themes/" + theme.folderName() + "/images/", "development-bundle/themes/" + theme.folderName() + "/minified/images/" ] ).forEach( add ); - callback( null, output ); - }); + themeImages[ 0 ].into( [ "css/" + theme.folderName() + "/images/", "development-bundle/themes/" + theme.folderName() + "/images/", "development-bundle/themes/" + theme.folderName() + "/minified/images/" ] ).forEach( add ); + callback( null, output ); + }); + } } + + async.series([ + _build, + pack + ], function( error ) { + callback( error, output ); + }); }, filename: function() { diff --git a/lib/rjs.js b/lib/rjs.js new file mode 100644 index 00000000..581e0729 --- /dev/null +++ b/lib/rjs.js @@ -0,0 +1,221 @@ +var fileApi, files, mutex, prim, rjs, + _ = require( "underscore" ), + fs = require( "fs" ), + logger = require( "simple-log" ).init( "download.jqueryui.com" ), + path = require( "path" ), + requirejs = require( "requirejs" ); + +fileApi = { + backSlashRegExp: /\\/g, + exclusionRegExp: /^\./, + + absPath: function(fileName) { + // path.charAt( 0 ) must be / or requirejs' nameToUrl will be calculated wrong. + return "/"; + }, + + copyDir: function( srcDir, destDir, regExpFilter ) { + var destPaths; + srcDir = path.normalize( srcDir ); + destDir = path.normalize( destDir ); + destPaths = fileApi.getFilteredFileList( srcDir, regExpFilter ).map(function( src ) { + var dest = src.replace( srcDir, destDir ); + fileApi.copyFile( src, dest ); + return dest; + }); + return destPaths.length ? destPaths : null; + }, + + copyFile: function( src, dest ) { + // Ignore root slash + src = src.substr( 1 ); + dest = dest.substr( 1 ); + + files[ dest ] = { + path: dest, + data: files[ src ].data + }; + return true; + }, + + deleteFile: function( path ) { + // Ignore root slash + path = path.substr( 1 ); + delete files[ path ]; + }, + + exists: function( path ) { + // Ignore root slash + path = path.substr( 1 ); + return path in files; + }, + + getFilteredFileList: function( startDir, regExpFilters, makeUnixPaths ) { + var regExp, regExpInclude, regExpExclude; + + regExpInclude = regExpFilters.include || regExpFilters; + regExpExclude = regExpFilters.exclude || null; + + if ( regExpExclude ) { + throw new Error( "exclude filter not supported" ); + } + + regExp = new RegExp( path.join( startDir, ".*" ) + ( regExpInclude ).toString().replace( /^\//, "" ).replace( /\/$/, "" ) ); + + return Object.keys( files ).filter(function( path ) { + return regExp.test( "/" + path ); + }).map(function( path ) { + return "/" + path; + }); + }, + + normalize: function( fileName ) { + return path.normalize( fileName ); + }, + + readFile: function( path ) { + // Ignore root slash + path = path.substr( 1 ); + + try { + return files[ path ].data.toString( "utf8" ); + } catch ( err ) { + err.message = "File not found: " + path + "\n" + err.message; + throw err; + } + }, + + readFileAsync: function( path ) { + var deferred = prim(); + try { + deferred.resolve( fileApi.readFile( path ) ); + } catch ( error ) { + deferred.reject( error ); + } + return deferred.promise; + }, + + renameFile: function( from, to ) { + from = path.normalize( from ); + to = path.normalize( to ); + + fileApi.copyFile( from, to ); + + // Ignore root slash + from = from.substr( 1 ); + + delete files[ from ]; + return true; + }, + + saveFile: function( _path, data ) { + _path = path.normalize( _path ); + + // Ignore root slash + _path = _path.substr( 1 ); + + files[ _path ] = { + path: _path, + data: data + }; + }, + + saveUtf8File: function( fileName, fileContents ) { + fileApi.saveFile( fileName, fileContents ); + } +}; + +rjs = function( attributes, callback ) { + var localCallback; + + if ( mutex ) { + throw new Error( "Concurrent calls not supported" ); + } + mutex = true; + localCallback = function( error, results ) { + mutex = false; + callback( error, results ); + }; + + attributes = attributes || {}; + if ( !( "files" in attributes ) ) { + throw new Error( "missing attributes.files" ); + } + if ( !( "include" in attributes ) ) { + throw new Error( "missing attributes.include" ); + } + if ( !( "jquery" in attributes ) ) { + throw new Error( "missing attributes.jquery" ); + } + if ( !attributes.include.length ) { + return localCallback( null, "" ); + } + attributes.exclude = attributes.exclude || []; + + files = _.clone( attributes.files.cache ); + + fileApi.saveFile( "/dist/tmp/main.js", "require([\n\t\"jqueryui/" + attributes.include.map(function( filename ) { + return filename.replace( /\.js$/, "" ); + }).join( "\",\n\t\"jqueryui/" ) + "\"\n]);" ); + + requirejs.define( "node/file", [ "prim" ], function( _prim ) { + prim = _prim; + return fileApi; + }); + + requirejs.define( "node/print", [], function() { + return function print( msg ) { + logger.log( msg ); + if ( msg.substring( 0, 5 ) === "Error" ) { + throw new Error( msg ); + } + }; + }); + + requirejs.optimize({ + dir: "dist/build", + appDir: "ui", + baseUrl: ".", + optimize: "none", + optimizeCss: "none", + paths: { + jquery: "../" + attributes.jquery.replace( /\.js$/, "" ), + jqueryui: ".", + tmp: "../dist/tmp" + }, + modules: [{ + name: "../output", + include: [ "tmp/main" ], + exclude: attributes.exclude, + create: true + }], + wrap: { + start: "(function( $ ) {", + end: "})( jQuery );" + }, + logLevel: 2, + onBuildWrite: function( id, path, contents ) { + if ( (/define\([\s\S]*?factory/).test( contents ) ) { + // Remove UMD wrapper + contents = contents.replace( /\(function\( factory[\s\S]*?\(function\( \$ \) \{/, "" ); + contents = contents.replace( /\}\)\);\s*?$/, "" ); + } + else if ( (/^require\(\[/).test( contents ) ) { + // Replace require with comment `//` instead of null string, because of the mysterious semicolon + contents = contents.replace( /^require[\s\S]*?\]\);$/, "// mysterious semicolon: " ); + } + return contents; + } + }, function() { + + // Remove `define("main" ...)` and `define("jquery-ui" ...)` + var contents = fileApi.readFile( "/dist/output.js" ).replace( /define\("(tmp\/main|\.\.\/output)", function\(\)\{\}\);/g, "" ); + + // Remove the mysterious semicolon `;` character left from require([...]); + contents = contents.replace( /\/\/ mysterious semicolon.*/g, "" ); + + localCallback( null, contents ); + }, localCallback ); +}; + +module.exports = rjs; diff --git a/package.json b/package.json index f2a148dc..613c7e98 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "handlebars": "1.0.12", "lzma": "1.2.1", "optimist": "0.3.4", + "requirejs": "2.1.8", "rimraf": "2.0.2", "semver": "1.1.0", "simple-log": "1.1.0", diff --git a/test/packer.js b/test/packer.js index 406ad010..81441a9f 100644 --- a/test/packer.js +++ b/test/packer.js @@ -18,7 +18,7 @@ function filePresent( files, filepath ) { function pack( jqueryUi, components, theme, callback ) { var builder = new Builder( jqueryUi, components ), - packer = new Packer( builder.build(), theme ); + packer = new Packer( builder, theme ); packer.pack(function( err, files ) { if ( err ) { callback( err, null ); @@ -561,7 +561,7 @@ var tests = { scope = "#wrapper"; test.expect( filesToCheck.length ); builder = new Builder( this.jqueryUi, components, { scope: scope } ); - packer = new Packer( builder.build(), this.theme, { scope: scope } ); + packer = new Packer( builder, this.theme, { scope: scope } ); packer.pack(function( err, files ) { if ( err ) { test.ok( false, err.message ); diff --git a/test/packer.legacy.1.10.js b/test/packer.legacy.1.10.js index 0e7e2f63..a4afd2e0 100644 --- a/test/packer.legacy.1.10.js +++ b/test/packer.legacy.1.10.js @@ -18,7 +18,7 @@ function filePresent( files, filepath ) { function pack( jqueryUi, components, theme, callback ) { var builder = new Builder( jqueryUi, components ), - packer = new Packer( builder.build(), theme ); + packer = new Packer( builder, theme ); packer.pack(function( err, files ) { if ( err ) { callback( err, null ); @@ -536,7 +536,7 @@ var tests = { scope = "#wrapper"; test.expect( filesToCheck.length ); builder = new Builder( this.jqueryUi, components, { scope: scope } ); - packer = new Packer( builder.build(), this.theme, { scope: scope } ); + packer = new Packer( builder, this.theme, { scope: scope } ); packer.pack(function( err, files ) { if ( err ) { test.ok( false, err.message ); diff --git a/test/packer.legacy.1.9.js b/test/packer.legacy.1.9.js index c4217231..cbf2f349 100644 --- a/test/packer.legacy.1.9.js +++ b/test/packer.legacy.1.9.js @@ -18,7 +18,7 @@ function filePresent( files, filepath ) { function pack( jqueryUi, components, theme, callback ) { var builder = new Builder( jqueryUi, components ), - packer = new Packer( builder.build(), theme ); + packer = new Packer( builder, theme ); packer.pack(function( err, files ) { if ( err ) { callback( err, null );