├── .meteor ├── packages ├── .gitignore ├── platforms └── .id ├── test ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .finished-upgraders │ ├── .id │ ├── packages │ └── versions ├── packages │ └── numtel:lazy-bundles ├── public │ ├── mockup.less │ ├── globber │ │ └── anything.js │ ├── mockup.coffee │ └── mockup.html ├── private │ ├── mockup.less │ ├── mockup.coffee │ └── mockup.html ├── test-glob.publicbundles.json ├── test.privatebundles.json ├── test.publicbundles.json ├── ci.sh ├── test-minified.publicbundles.json └── server │ ├── router.js │ └── index.js ├── .gitignore ├── .versions ├── .travis.yml ├── package.js ├── private-bundle-plugin.js ├── LICENSE ├── README.md └── plugin └── compile-bundles.js /.meteor/packages: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /test/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /test/packages/numtel:lazy-bundles: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | browser 2 | server 3 | -------------------------------------------------------------------------------- /test/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.2 2 | -------------------------------------------------------------------------------- /test/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /test/public/mockup.less: -------------------------------------------------------------------------------- 1 | h1.horses { color: red; } 2 | -------------------------------------------------------------------------------- /test/private/mockup.less: -------------------------------------------------------------------------------- 1 | h1.cheese { color: orange; } 2 | -------------------------------------------------------------------------------- /test/public/globber/anything.js: -------------------------------------------------------------------------------- 1 | console.log('something'); 2 | -------------------------------------------------------------------------------- /test/public/mockup.coffee: -------------------------------------------------------------------------------- 1 | tester = -> 2 | console.log 'horses' 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.*~ 2 | *.swp 3 | *.swo 4 | settings.json 5 | .build* 6 | .npm 7 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | meteor@1.1.6 2 | numtel:lazy-bundles@1.0.1 3 | underscore@1.0.3 4 | -------------------------------------------------------------------------------- /test/private/mockup.coffee: -------------------------------------------------------------------------------- 1 | tester = -> 2 | console.log 'cheese' 3 | 4 | -------------------------------------------------------------------------------- /test/test-glob.publicbundles.json: -------------------------------------------------------------------------------- 1 | { 2 | "public-glob-test": [ 3 | "globber/*.js" 4 | ] 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/private/mockup.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/public/mockup.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/test.privatebundles.json: -------------------------------------------------------------------------------- 1 | { 2 | "private-bundle-test": [ 3 | "mockup.html", 4 | "mockup.less", 5 | "mockup.coffee" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/test.publicbundles.json: -------------------------------------------------------------------------------- 1 | { 2 | "public-bundle-test": [ 3 | "mockup.html", 4 | "mockup.less", 5 | "mockup.coffee" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd ${0%/*} 3 | if (RUN_ONCE=1 meteor | tee /dev/tty | grep -q "All tests pass!") 4 | then 5 | exit 0 6 | else 7 | exit 1 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | before_install: 6 | - "curl https://install.meteor.com/ | /bin/sh" 7 | script: 8 | - "./test/ci.sh" 9 | -------------------------------------------------------------------------------- /test/test-minified.publicbundles.json: -------------------------------------------------------------------------------- 1 | { 2 | "#settings": { 3 | "minify": true 4 | }, 5 | "public-minified-test": [ 6 | "mockup.html", 7 | "mockup.less", 8 | "mockup.coffee" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | itx4ee1x4ps6a1t8f278 8 | -------------------------------------------------------------------------------- /test/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | uui3efui6yzqrvpp9x 8 | -------------------------------------------------------------------------------- /test/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | less 11 | coffeescript 12 | numtel:lazy-bundles 13 | iron:router 14 | http 15 | nooitaf:colors 16 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'numtel:lazy-bundles', 3 | summary: 'Create bundles for lazy-loading components, optionally with authentication', 4 | version: '1.0.1', 5 | git: 'https://github.com/numtel/meteor-lazy-bundles.git' 6 | }); 7 | 8 | Package.registerBuildPlugin({ 9 | name: 'compileBundles', 10 | use: [ 'underscore@1.0.3' ], 11 | sources: [ 12 | 'plugin/compile-bundles.js' 13 | ], 14 | npmDependencies: { 15 | 'glob': '5.0.10' 16 | } 17 | }); 18 | 19 | Package.onUse(function(api){ 20 | api.versionsFrom('1.0.2.1'); 21 | api.addFiles('private-bundle-plugin.js', 'server'); 22 | api.export('registerIRPrivateBundles', 'server'); 23 | }); 24 | -------------------------------------------------------------------------------- /test/server/router.js: -------------------------------------------------------------------------------- 1 | 2 | // iron:router plugin must be installed 3 | registerIRPrivateBundles(); 4 | 5 | // Initialize iron:router Plugin on server-side 6 | Router.plugin('privateBundles', { 7 | // Optional: Set to true to output file load errors to console 8 | debug: true, 9 | // Optional: Route path (without leading slash), Default '_loadSource' 10 | route: 'authBundle', 11 | // Optional: Determine if a route should serve the file requested. 12 | // If not specified, all requests will be granted. 13 | // ALL requests granted means that your private directory would 14 | // not be private any more! 15 | // @context - Same as Router.route context 16 | // @param {string} path - Filename requested 17 | // @return boolean - true to serve file, false to serve nothing 18 | allow: function(path){ 19 | // Sample authentication scheme 20 | // Require query parameter 'allow' to have value 'yes' 21 | return this.params.query.allow === 'yes'; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /test/.meteor/versions: -------------------------------------------------------------------------------- 1 | autopublish@1.0.3 2 | autoupdate@1.2.1 3 | base64@1.0.3 4 | binary-heap@1.0.3 5 | blaze@2.1.2 6 | blaze-tools@1.0.3 7 | boilerplate-generator@1.0.3 8 | callback-hook@1.0.3 9 | check@1.0.5 10 | coffeescript@1.0.6 11 | ddp@1.1.0 12 | deps@1.0.7 13 | ejson@1.0.6 14 | fastclick@1.0.3 15 | geojson-utils@1.0.3 16 | html-tools@1.0.4 17 | htmljs@1.0.4 18 | http@1.1.0 19 | id-map@1.0.3 20 | insecure@1.0.3 21 | iron:controller@1.0.7 22 | iron:core@1.0.7 23 | iron:dynamic-template@1.0.7 24 | iron:layout@1.0.7 25 | iron:location@1.0.7 26 | iron:middleware-stack@1.0.7 27 | iron:router@1.0.7 28 | iron:url@1.0.7 29 | jquery@1.11.3_2 30 | json@1.0.3 31 | launch-screen@1.0.2 32 | less@1.0.14 33 | livedata@1.0.13 34 | logging@1.0.7 35 | meteor@1.1.6 36 | meteor-platform@1.2.2 37 | minifiers@1.1.5 38 | minimongo@1.0.8 39 | mobile-status-bar@1.0.3 40 | mongo@1.1.0 41 | nooitaf:colors@0.0.2 42 | numtel:lazy-bundles@1.0.1 43 | observe-sequence@1.0.6 44 | ordered-dict@1.0.3 45 | random@1.0.3 46 | reactive-dict@1.1.0 47 | reactive-var@1.0.5 48 | reload@1.1.3 49 | retry@1.0.3 50 | routepolicy@1.0.5 51 | session@1.1.0 52 | spacebars@1.0.6 53 | spacebars-compiler@1.0.6 54 | templating@1.1.1 55 | tracker@1.0.7 56 | ui@1.0.6 57 | underscore@1.0.3 58 | url@1.0.4 59 | webapp@1.2.0 60 | webapp-hashing@1.0.3 61 | -------------------------------------------------------------------------------- /private-bundle-plugin.js: -------------------------------------------------------------------------------- 1 | // numtel:lazy-bundles 2 | // MIT License, ben@latenightsketches.com 3 | var path = Npm.require('path'); 4 | var fs = Npm.require('fs'); 5 | 6 | var BASE_DIR = 'assets/app'; 7 | 8 | // Provide iron:router plugin for loading private bundles with an 9 | // authentication lamdba function 10 | // Call this function to install the 'privateBundles' plugin 11 | // Not installed by default, just in case a different router is used. 12 | registerIRPrivateBundles = function() { 13 | Iron.Router.plugins.privateBundles = function (router, options) { 14 | options = options || {}; 15 | var route = '/' + (options.route || '_loadSource'); 16 | router.route(route, function(){ 17 | var query = this.params.query; 18 | var filename = path.join(BASE_DIR, query.f); 19 | if(filename.substr(0, BASE_DIR.length) !== BASE_DIR){ 20 | // Security breach trying to access file outside of BASE_DIR 21 | this.response.end(); 22 | }else if(options.allow && !options.allow.call(this, query.f)){ 23 | this.response.end(); 24 | }else{ 25 | fs.readFile(filename, function(err, data){ 26 | if(err){ 27 | options.debug && console.log(err); 28 | this.response.end(); 29 | }else{ 30 | this.response.end(data); 31 | } 32 | }.bind(this)); 33 | } 34 | }, { where: 'server' }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ben@latenightsketches.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | Copyright © Maxime QUANDALLE 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy of 27 | this software and associated documentation files (the "Software"), to deal in 28 | the Software without restriction, including without limitation the rights to 29 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 30 | of the Software, and to permit persons to whom the Software is furnished to do 31 | so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in all 34 | copies or substantial portions of the Software. 35 | 36 | The Software is provided "as is", without warranty of any kind, express or 37 | implied, including but not limited to the warranties of merchantability, fitness 38 | for a particular purpose and noninfringement. In no event shall the authors or 39 | copyright holders be liable for any claim, damages or other liability, whether 40 | in an action of contract, tort or otherwise, arising from, out of or in 41 | connection with the software or the use or other dealings in the Software 42 | -------------------------------------------------------------------------------- /test/server/index.js: -------------------------------------------------------------------------------- 1 | var cases = { 2 | 'public-bundle-test.js': [ 3 | /class="horses"/, // From public/mockup.html 4 | /console.log\('horses'\)/, // From compiled version of public/mockup.coffee 5 | ], 6 | 'public-bundle-test.css': [ 7 | /color: red/, // From compiled version of public/mockup.less 8 | ], 9 | 'authBundle?allow=yes&f=private-bundle-test.js': [ 10 | /class="cheese"/, // From private/mockup.html 11 | /console.log\('cheese'\)/, // From compiled version of private/mockup.coffee 12 | ], 13 | 'authBundle?allow=yes&f=private-bundle-test.css': [ 14 | /color: orange/, // From compiled version of private/mockup.less 15 | ], 16 | 'public-minified-test.js': [ 17 | // Check for contents without newlines 18 | /function\(\)\{return HTML.Raw\('/, 19 | /class="horses"/, 20 | /function\(\)\{return console.log\("horses"\)\}\}/ 21 | ], 22 | 'public-minified-test.css': [ 23 | // Check for contents without newlines 24 | /h1.horses\{color:red\}/ 25 | ], 26 | 'public-glob-test.js': [ 27 | /console.log\('something'\)/, // From public/globber/anything.js 28 | ] 29 | }; 30 | 31 | function performTests() { 32 | var allStatus = []; 33 | for(var file in cases) { 34 | try { 35 | var content = HTTP.get(Meteor.absoluteUrl(file)).content; 36 | } catch (err) { 37 | throw new Meteor.error(500, err); 38 | } 39 | var caseStatus = { 40 | file: file, 41 | passed: 0, 42 | count: cases[file].length, 43 | result: null 44 | }; 45 | 46 | for(var i = 0; i < cases[file].length; i++) { 47 | if(content.match(cases[file][i]) !== null) { 48 | caseStatus.passed++; 49 | } 50 | } 51 | 52 | caseStatus.result = caseStatus.passed === caseStatus.count ? 'pass' : 'fail'; 53 | 54 | allStatus.push(caseStatus); 55 | } 56 | 57 | return allStatus; 58 | } 59 | 60 | Meteor.startup(function() { 61 | // Give a few seconds for the server to be ready 62 | Meteor.setTimeout(function() { 63 | var allPass = true; 64 | 65 | performTests().forEach(function(result) { 66 | console.log(result.file.bold); 67 | if(result.result === 'pass') { 68 | console.log((result.count + ' Passed!').green); 69 | } else { 70 | allPass = false; 71 | console.log((result.passed + ' / ' + result.count + ' passed').red); 72 | } 73 | }); 74 | 75 | console.log(('\n' + (allPass ? 'All tests pass!' : 'Test Failure')).bold); 76 | 77 | if('RUN_ONCE' in process.env) { 78 | Meteor.setTimeout(function() { 79 | process.kill(process.env.METEOR_PARENT_PID, 'SIGINT'); 80 | }, 1000); 81 | } 82 | 83 | }, 1000); 84 | }); 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numtel:lazy-bundles [![Build Status](https://travis-ci.org/numtel/meteor-lazy-bundles.svg?branch=master)](https://travis-ci.org/numtel/meteor-lazy-bundles) 2 | 3 | By default, Meteor does not perform any special handling on files placed inside the `public` and `private` directories of your application. 4 | 5 | With this package, you can place `xxx.publicbundles.json` or `xxx.privatebundles.json` files in your application directory with a description of bundles of source files to process for client-side lazy-loading. 6 | 7 | Source files will be transpiled and collected into a single `.js` and `.css` file for each bundle. 8 | 9 | > This package supersedes [`numtel:publicsources`](https://github.com/numtel/meteor-publicsources) and [`numtel:privatesources`](https://github.com/numtel/meteor-privatesources). 10 | 11 | ## Installation 12 | 13 | ``` 14 | meteor add numtel:lazy-bundles 15 | ``` 16 | 17 | ## Public Bundle Descriptions 18 | 19 | Inside of a `xxx.publicbundles.json` file in the root of your application, you may describe bundles of source files to serve to the client. The filename may be different than `xxx.publicbundles.json` as long as it includes the extension `.publicbundles.json`. (e.g. `myapp.publicbundles.json` is also valid) 20 | 21 | Filenames specified inside of each bundle are relative to the application's `public` directory. 22 | 23 | The following example will serve `admin.js`, `admin.js.map`, `admin.css`, and `admin.css.map`: 24 | 25 | ```javascript 26 | { 27 | "admin": [ 28 | "admin/templates.html", 29 | "admin/styles.less", 30 | "admin/main.coffee" 31 | ] 32 | } 33 | ``` 34 | 35 | * Listed filenames may be glob patterns in order to include multiple files without manually specifying each filename, as defined by [`glob` NPM package](https://github.com/isaacs/node-glob). See [`test/test-glob.publicbundles.json`](test/test-glob.publicbundles.json) for an example using a glob. 36 | * Bundle names may contain slashes to simulate a directory path 37 | * `.html` files are automatically sorted to the beginning of a bundle but other template filetypes are not. Be sure to include template files like `.jade` first in the bundle so that subsequent scripts will have access to apply helpers, events and handlers. 38 | 39 | ## Private Bundle Descriptions 40 | 41 | Private bundles may be specified exactly the same as public bundles except with the `.privatebundles.json` extension. 42 | 43 | Filenames specified inside of each private bundle are relative to the application's `private` directory. 44 | 45 | To obtain asset data directly from your application's server code, you may use the built-in method, `Assets.getText()`. Otherwise, load the files using the provided `iron:router` plugin described below. 46 | 47 | ## Minified Output 48 | 49 | For production output, bundles may be minified using the available setting. 50 | 51 | See [`test/test-minified.publicbundles.json`](test/test-minified.publicbundles.json) for a minified bundle example. 52 | 53 | ## Iron Router integration 54 | 55 | Combine with the [`miro:preloader` package](https://github.com/MiroHibler/meteor-preloader) to dynamically load the bundled `.js` and `.css` files when browsing to a route 56 | 57 | ```javascript 58 | // Route example using miro:preloader 59 | Router.route('/', function () { 60 | this.render('hello'); 61 | }, { 62 | controller: 'PreloadController', 63 | preload: { 64 | styles: 'hello.css', 65 | sync: 'hello.js' 66 | } 67 | }); 68 | ``` 69 | 70 | ### Private bundle authentication plugin 71 | 72 | Private bundles can be loaded on the client using the provided `iron:router` plugin. 73 | 74 | See [`test/server/router.js`](test/server/router.js) for an example of how to configure the authentication plugin. 75 | 76 | ## Notes 77 | 78 | * If adding a new source handler package (e.g. `mquandalle:jade`), you must restart Meteor for this package to recognize the change. 79 | * If using a glob, Meteor must be restarted to search for new/deleted files. 80 | 81 | ## Test application 82 | 83 | Test cases for this package are not executed using the normal `meteor test-packages` command. Instead, start the Meteor app inside of the `test` directory and see the results in the console. 84 | 85 | ```bash 86 | $ git clone https://github.com/numtel/meteor-lazy-bundles 87 | 88 | $ cd meteor-lazy-bundles/test 89 | 90 | # Test results will be output to the console 91 | $ meteor 92 | ``` 93 | 94 | Test cases are described in [`test/server/index.js`](test/server/index.js). 95 | 96 | ## License 97 | 98 | MIT 99 | 100 | Portions copyright Maxime Quandalle @mquandalle 101 | -------------------------------------------------------------------------------- /plugin/compile-bundles.js: -------------------------------------------------------------------------------- 1 | // numtel:lazy-bundles 2 | // MIT License, ben@latenightsketches.com 3 | 4 | var fs = Npm.require('fs'); 5 | var path = Npm.require('path'); 6 | var glob = Npm.require('glob'); 7 | 8 | // With the NPM dependency (glob), cannot simply require files from 9 | // meteor/tools directory because the Npm.require root directory has changed 10 | var toolDir = path.dirname(process.mainModule.filename); 11 | // Assume never more than 100 directories deep 12 | var rootRelPath = _.range(100).map(function() { return '..' }).join('/'); 13 | // Determine meteor/tools relative directory path 14 | var relToolDir = path.join(rootRelPath, toolDir); 15 | 16 | function requireMeteorTool(file) { 17 | return Npm.require(path.join(relToolDir, file + '.js')) 18 | } 19 | 20 | // Load required meteor build tools 21 | var compiler = requireMeteorTool('compiler'); 22 | var isopackets = requireMeteorTool('isopackets'); 23 | var projectContextModule = requireMeteorTool('project-context'); 24 | var PackageSource = requireMeteorTool('package-source'); 25 | 26 | // Obtain array of loaded packages to make build plugins available to bundles 27 | var loadedPackages = []; 28 | if(process.publicsourcesProjectContext){ 29 | var projectContext = process.publicsourcesProjectContext; 30 | }else{ 31 | var projectContext = new projectContextModule.ProjectContext({ 32 | projectDir: process.cwd() 33 | }); 34 | process.publicsourcesProjectContext = projectContext; 35 | projectContext.prepareProjectForBuild(); 36 | } 37 | if(projectContext.isopackCache !== null) { 38 | for(var packageName in projectContext.isopackCache._isopacks){ 39 | loadedPackages.push(packageName); 40 | } 41 | } 42 | 43 | // Begin code borrowed from mquandalle:bower/plugin/handler.js 44 | function loadJSONContent(compileStep, content) { 45 | try { 46 | return JSON.parse(content); 47 | } catch (e) { 48 | compileStep.error({ 49 | message: "Syntax error in " + compileStep.inputPath, 50 | line: e.line, 51 | column: e.column 52 | }); 53 | } 54 | } 55 | // End code from mquandalle:bower 56 | 57 | function isHtmlExt(n){ return n.substr(-5).toLowerCase() === '.html' } 58 | // Used with Array.prototype.sort() in array of filenames to put '.html' first 59 | function sortHTMLFirst(a, b){ 60 | var aHtml = isHtmlExt(a); 61 | var bHtml = isHtmlExt(b); 62 | if(aHtml && bHtml) return 0; 63 | else if(aHtml) return -1; 64 | else if(bHtml) return 1; 65 | return 0; 66 | } 67 | 68 | function generateBundleHandler(relativeRootPath) { 69 | return function(compileStep, bundles){ 70 | 71 | var bundles = 72 | loadJSONContent(compileStep, compileStep.read().toString('utf8')); 73 | 74 | // Default settings 75 | var minify = false; 76 | 77 | // Settings provided in bundle description file 78 | if('#settings' in bundles) { 79 | minify = minify || bundles['#settings'].minify; 80 | 81 | delete bundles['#settings']; 82 | } 83 | 84 | for(var name in bundles) { 85 | var prefixLength = path.join(process.cwd(), relativeRootPath).length + 1; 86 | // Load list of all files included in this bundle, expanding globs 87 | var bundleSources = _.flatten(bundles[name].map(function(pattern) { 88 | return glob.sync(path.join(process.cwd(), relativeRootPath, pattern)); 89 | })).map(function(file) { 90 | return file.substr(prefixLength); 91 | }).sort(sortHTMLFirst); 92 | 93 | var packageSource = new PackageSource; 94 | packageSource.initFromOptions('resources', { 95 | kind: 'plugin', 96 | sourceRoot: path.join(process.cwd(), relativeRootPath), 97 | serveRoot: process.cwd(), 98 | use: loadedPackages, 99 | npmDependencies: [], 100 | npmDir: path.join(process.cwd(), 'node_modules'), 101 | sources: bundleSources 102 | }); 103 | // initFromOptions() defaults to 'os' architecture 104 | packageSource.architectures[0].arch = 'web.browser'; 105 | 106 | var compiled = compiler.compile(packageSource, { 107 | packageMap: projectContext.packageMap, 108 | isopackCache: projectContext.isopackCache, 109 | includeCordovaUnibuild: false 110 | }).unibuilds[0]; 111 | 112 | var scripts = compiled.prelinkFiles; 113 | var styleSheets = compiled.resources.filter(function(resource) { 114 | return resource.type === 'css'; 115 | }); 116 | 117 | if(minify) { 118 | var minifiers = isopackets.load('minifiers').minifiers; 119 | scripts.forEach(function(jsFile) { 120 | var result = minifiers.UglifyJS.minify(jsFile.source, { 121 | fromString: true 122 | }); 123 | 124 | // Replace original source with minified source 125 | jsFile.source = result.code; 126 | delete jsFile.sourceMap; 127 | }); 128 | 129 | styleSheets.forEach(function(styleSheet) { 130 | var result = 131 | minifiers.CssTools.minifyCss(styleSheet.data.toString()); 132 | 133 | // Replace original source with minified source 134 | styleSheet.data = new Buffer(result); 135 | delete styleSheet.sourceMap; 136 | }); 137 | } 138 | 139 | // Handle scripts 140 | scripts.forEach(function(script) { 141 | compileStep.addAsset({ 142 | path: name + '.js', 143 | data: script.source 144 | }); 145 | if(script.sourceMap) { 146 | compileStep.addAsset({ 147 | path: name + '.js.map', 148 | data: script.sourceMap.replace(script.servePath, name + '.js') 149 | }); 150 | } 151 | }); 152 | 153 | // Handle stylesheets 154 | styleSheets.forEach(function(stylesheet) { 155 | compileStep.addAsset({ 156 | path: name + '.css', 157 | data: stylesheet.data 158 | }); 159 | if(stylesheet.sourceMap) { 160 | compileStep.addAsset({ 161 | path: name + '.css.map', 162 | data: stylesheet.sourceMap.replace( 163 | stylesheet.servePath, name + '.css') 164 | }); 165 | } 166 | }); 167 | } 168 | } 169 | } 170 | 171 | Plugin.registerSourceHandler( 172 | 'publicbundles.json', 173 | { archMatching: 'web' }, 174 | generateBundleHandler('public') 175 | ); 176 | 177 | Plugin.registerSourceHandler( 178 | 'privatebundles.json', 179 | { archMatching: 'os' }, 180 | generateBundleHandler('private') 181 | ); 182 | 183 | --------------------------------------------------------------------------------