diff --git a/.gitignore b/.gitignore index a0d231d..7324306 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ node_modules # Logs & System *.log *.pid + +# IDE +.idea diff --git a/.travis.yml b/.travis.yml index 9a56ac8..6edae9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,10 @@ script: - npm run it after_success: - test $TRAVIS_BRANCH = "master" && npm run coveralls +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/19547337979e0c84f48d + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/README.md b/README.md index f758e2c..e28a45a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://travis-ci.org/nahuelio/squarebox.svg?branch=master)](https://travis-ci.org/nahuelio/squarebox) [![Coverage Status](https://coveralls.io/repos/github/nahuelio/squarebox/badge.svg)](https://coveralls.io/github/nahuelio/squarebox) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sqbox/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg?style=flat)]() [![Version](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/mit-license.php) [![GitHub stars](https://img.shields.io/github/stars/nahuelio/squarebox.svg?style=social&label=Stars)]() @@ -15,42 +16,48 @@ Experimental ES6/CommonJS/AMD Module Bundler ```npm install [-g] squarebox``` -#### Usage +### Usage -CLI Commands +```sqbox [command] [options]``` -``` -global option: -c (sqbox.js|.sqboxrc|URL) | Default: ./.sqboxrc -contextual help - sqbox [command] help +#### Commands -* sqbox help - global help (list of commands and general usage) -* sbox bundle --options -* sbox clean -* sbox visualize -``` +* **help** - Global Help +* **bundle help** - Contextual help for command ```bundle``` +* **bundle** - Bundles your project + * Options + * [-c, --config] (sqbox.js, .sqboxrc, or a remote url) | Default: [currentDirectory]/.sqboxrc + * TODO Overrides... +* **clean** - Clean destination folder + * Options + * ...TODO +* **graph** - Generates a graphical report of your current bundling strategy + * Options + * ...TODO -Programmatic API +#### Programmatic API ``` const sqbox = require('squarebox'); +// Or ES6: import sqbox from 'squarebox'; sqbox.clean([opts]) .bundle([config]) - .visualize([opts]); + .graph([opts]); ``` #### Official Documentation ``` -TODO +TODO: Official Website / APIDocs ``` -#### Configuration +#### Contribute ``` TODO ``` -#### Contribute +#### License ``` TODO diff --git a/lib/bin/commands.json b/lib/bin/commands.json index e4301ca..a1f7ceb 100644 --- a/lib/bin/commands.json +++ b/lib/bin/commands.json @@ -17,7 +17,8 @@ "override": { "default": false }, "config": { "default": ".sqboxrc" }, "url": {}, - "target": { "default": "./dist" } + "target": { "default": "./dist" }, + "logLevel": { "alias": "lv", "default": "output" } } }, { "name": "bundle", @@ -28,10 +29,12 @@ "override": { "default": false }, "config": { "default": ".sqboxrc" }, "url": { "alias": "u" }, + "basePath": { "alias": "b", "default": "./" }, "scan": { "alias": "s", "default": "." }, "exclude": { "alias": "x" }, "extensions": { "alias": "e", "default": ".js,.jsx,.es6,.es" }, "alias": { "alias": "a", "default": {} }, + "external": { "alias": "l", "default": "" }, "target": { "alias": "t", "default": "dist>umd:./dist" }, "logLevel": { "alias": "lv", "default": "output" } } @@ -44,6 +47,7 @@ "override": { "default": false }, "config": { "default": ".sqboxrc" }, "url": {}, - "target": { "default": "./dist" } + "target": { "default": "./dist" }, + "logLevel": { "alias": "lv", "default": "output" } } }] diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index 8624bf0..22d5ad3 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -1,10 +1,12 @@ /** * @module bin * @author Patricio Ferreira <3dimentionar@gmail.com> +* @Notes Think about the API require('sqbox').clean({}).bundle({}).graph({}); **/ import 'util/mixins'; import extend from 'extend'; import Collection from 'util/adt/collection'; +import StackAsync from 'util/adt/stack-async'; import Factory from 'util/factory/factory'; import Command from 'command'; import CommandsList from './commands.json'; @@ -23,21 +25,22 @@ class SquareBox extends Command { /** * Constructor * @public - * @param {Object} [args = {}] - Constructor arguments + * @param {Symbol} _enforcer - private enforcer symbol + * @param {Object} [...args] - Constructor arguments * @return {bin.SquareBox} **/ - constructor(...args) { + constructor(_enforcer, ...args) { super(...args); - return SquareBox.isPrivate(this.attachEvents()); + return SquareBox.isPrivate(_enforcer, this.attachEvents()); } /** - * Attaches Events + * Attach Events * @public * @return {bin.SquareBox} **/ attachEvents() { - this.once(SquareBox.events.done, this.after); + this.stack.on(StackAsync.events.push, _.bind(this.onCommand, this)); return this; } @@ -48,7 +51,6 @@ class SquareBox extends Command { * @return {bin.SquareBox} **/ before() { - super.before(); this.commander.read(); return this; } @@ -61,44 +63,57 @@ class SquareBox extends Command { **/ run() { this.before(); - this.configuration.parse().then(_.bind(this.onConfiguration, this)); + this.configuration.parse() + .then(_.bind(this.push, this, this, this.options.path)) + .catch(_.bind(this.after, this)); return this; } /** - * Configuration Loaded and Parsed Handler + * Subcommand push Handler * @public - * @param {visitors.Configuration} configuration - configuration visitor reference + * @param {util.adt.StackAsync} stack - stack reference + * @param {Command} command - command offered * @return {bin.SquareBox} **/ - onConfiguration() { - const { path } = this.options; - //console.log(_.pick(this, Command.options)); - // TODO: Factory.get(path, this.params()).run(); - return this; + onCommand(stack, command) { + let dependents = command.getDependsOn(); + return (dependents.length > 0) ? _.reduce(dependents, this.push, this, this) : this.after(); } /** - * Constructor Validation + * After Run * @public - * @throws {Error} Private violation - * @param {Symbol} pte - constructor enforcer + * @override + * @param {Error} [err] - error reference * @return {bin.SquareBox} **/ - isPrivate(pte) { - if(!_.isEqual(pte, enforcer)) throw new Error('Private Violation'); + after(err, results) { + if(_.defined(err)) logger(err.message).fatal(); + if(this.stack.isEmpty()) return this.complete(results); + this.stack.pop().then(_.bind(this.after, this, null)).catch(_.bind(this.after, this)); return this; } /** - * Register Command Factories + * SquareBox Complete Handler * @public - * @override + * @param {Array} results - all Results * @return {bin.SquareBox} **/ - register() { - super.register(); - Factory.registerAll(this.constructor.commands); + complete(results) { + return super.after(); + } + + /** + * Constructor Validation + * @public + * @throws {Error} Private violation + * @param {Symbol} pte - constructor enforcer + * @return {bin.SquareBox} + **/ + isPrivate(pte) { + if(!_.isEqual(pte, enforcer)) throw new Error('Private Violation'); return this; } @@ -110,24 +125,25 @@ class SquareBox extends Command { static commands = Collection.new(CommandsList); /** - * SquareBox visitors + * Command Visitors * @static * @override - * @type {Array} + * @type {util.adt.Collection} **/ - static visitors = [ + static visitors = Collection.new(Command.visitors.toJSON().concat([ 'visitors/commander', 'visitors/configuration' - ].concat(Command.visitors); + ])); /** * Static enforcer validation * @static + * @param {Symbol} _enforcer - private enforcer symbol * @param {bin.SquareBox} instance - squarebox instance reference * @return {bin.SquareBox} **/ - static isPrivate(instance) { - return instance.isPrivate(enforcer); + static isPrivate(_enforcer, instance) { + return instance.isPrivate(_enforcer); } /** @@ -144,7 +160,7 @@ class SquareBox extends Command { * @return {bin.SquareBox} **/ static run(cwd = process.cwd()) { - return this.new({ cwd }).run(); + return this.new(enforcer, { cwd }).run(); } } diff --git a/lib/bundle/bundle.es6 b/lib/bundle/bundle.es6 index 5f228bc..ce93afc 100644 --- a/lib/bundle/bundle.es6 +++ b/lib/bundle/bundle.es6 @@ -4,7 +4,11 @@ **/ import _ from 'underscore'; import extend from 'extend'; +import Collection from 'util/adt/collection'; import Command from 'command'; +import Metadata from 'bundle/task/metadata/metadata'; +import Factory from 'util/factory/factory'; +import logger from 'util/logger/logger'; /** * Class Bundle @@ -12,18 +16,107 @@ import Command from 'command'; **/ class Bundle extends Command { + /** + * Constructor + * @public + * @override + * @param {Object} [args = {}] constructor attributes + * @return {bundle.Bundle} + **/ + constructor(args = {}) { + return super(extend(true, args, { + files: Collection.new(), + bundles: Collection.new([], { interface: Metadata }) + })); + } + /** * Run * @public * @override + * @param {Function} resolve asynchronous promise's resolve + * @param {Function} reject asynchronous promise's reject + * @return {bundle.Bundle} + **/ + run(resolve, reject) { + this.before().actions() + .then(_.bind(this.after, this)) + .catch(_.bind(this.after, this)); + return this; + } + + /** + * Asynchronous Actions Run + * @public + * @return {Promise} + **/ + actions() { + return _.reduce([this.read, this.write], this.action, Promise.resolve()); + } + + /** + * Action Run + * @public + * @param {Promise} memo memoized promise that chains asynchronous actions synchronously + * @param {Function} action current asynchronous action + * @return {Promise} + **/ + action(memo, action) { + return memo.then(action); + } + + /** + * After Run + * @public + * @override + * @param {Array|Error} result result reference * @return {bundle.Bundle} **/ - run() { - // TODO - console.log('Bundle.run()...'); - return super.run(); + after(result) { + if(_.instanceOf(result, Error)) { + logger(result).warn(); + return this.emit(Command.events.error, result); + } + return this.done(); } + /** + * List of commands that depends on + * @static + * @override + * @type {Array} + **/ + static dependsOn = Command.dependsOn.concat([ + 'clean/clean' + ]); + + /** + * Command options + * @static + * @override + * @type {Array} + **/ + static options = Command.options.concat([ + 'bundles', + 'file', + 'files', + 'extension', + 'aliases', + 'excludes', + 'targets' + ]); + + /** + * List of visitors + * @static + * @override + * @type {util.adt.Collection} + **/ + static visitors = Collection.new(Command.visitors.toJSON().concat([ + 'bundle/task/reader/reader', + 'bundle/task/writer/writer' + ])); + } export default Bundle; diff --git a/lib/bundle/format/amd/amd.es6 b/lib/bundle/format/amd/amd.es6 new file mode 100644 index 0000000..eea4e37 --- /dev/null +++ b/lib/bundle/format/amd/amd.es6 @@ -0,0 +1,165 @@ +/** +* @module bundle.format.amd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Format from 'bundle/format/format'; +import * as Helpers from 'bundle/format/amd/helpers'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class Amd +* @extends {bundle.format.Format} +**/ +class Amd extends Format { + + /** + * AMD AST Query + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + amd(ctx, ...args) { + return this.query(ctx, ...args); + } + + /** + * AMD AST Query by a given type + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + amdByType(ctx, ast, ...args) { + return this.queryByType(ctx, ast, ...args); + } + + /** + * AMD AST QUery by a given element + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + amdByElement(ctx, ast, ...args) { + return this.queryByElement(ctx, ast, ...args); + } + + /** + * AMD Resolves Import Specifier for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + amdResolveImportSpecifier(ctx, dependency, metadata, node, ast) { + if(!_.defined(dependency.import)) dependency.import = { modules: Collection.new() }; + Helpers.add(dependency.import.modules, node); + return dependency; + } + + /** + * AMD Resolves Import Path for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + amdResolveImportPath(ctx, dependency, metadata, node, ast) { + dependency.import.path = ctx.reader.file(Helpers.resolvePath(ctx, ast, node), false); + return dependency; + } + + /** + * AMD Resolves Parent for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + amdResolveParent(ctx, dependency, metadata) { + return super.resolveParent(dependency, metadata); + } + + /** + * AMD Resolves AST Injection for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + amdResolveAst(ctx, dependency, metadata) { + return super.resolveAst(dependency, ''); + } + + /** + * ASTQ AMD Import Declaration Query + * @static + * @property QAMD_ImportDeclaration + * @type {String} + **/ + static QAMD_ImportDeclaration = ` + /ExpressionStatement /CallExpression [/Identifier [@name == 'define']] + `; + + /** + * ASTQ AMD Import Specifier Query + * @static + * @property QAMD_ImportSpecifier + * @type {String} + **/ + static QAMD_ImportSpecifier = ` + /ArrayExpression /:elements Literal + `; + + /** + * ASTQ ES6 Import Path Query + * @static + * @property QES6_ImportPath + * @type {String} + **/ + static QAMD_ImportPath = `/FunctionExpression`; + + /** + * ASTQ AMD Export Declaration Query + * @static + * @property QAMD_ExportDeclaration + * @type {String} + **/ + static QAMD_ExportDeclaration = '/TODO'; + + /** + * ASTQ AMD Export Specifier Query + * @static + * @property QAMD_ExportSpecifier + * @type {String} + **/ + static QAMD_ExportSpecifier = `/TODO`; + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'AmdVisitor'; + } + +} + +export default Amd; diff --git a/lib/bundle/format/amd/helpers.es6 b/lib/bundle/format/amd/helpers.es6 new file mode 100644 index 0000000..8db51cb --- /dev/null +++ b/lib/bundle/format/amd/helpers.es6 @@ -0,0 +1,79 @@ +/** +* @module bundle.format.amd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; + +/** +* Retrieves module for a given dependency module list by id. Returns null if module is not found. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {String} id module id to retrieve +* @return {Object} +**/ +export const get = (modules, id) => { + if(!_.defined(id)) return true; + return modules.findWhere({ id }); +}; + +/** +* Resolves Module for a given astq node if it's a named require. +* @public +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveAliased = (node = {}) => { + // TODO + return node; +}; + +/** +* Resolves Module for a given astq node if it doesn't have named require. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveNamed = (modules, node = {}) => { + return { id: node.value }; +}; + +/** +* Resolve Dependency path based on a given node +* @public +* @param {bundle.type.Type} type current type in used +* @param {astq.Node} [node = {}] node to resolve +* @return {String} +**/ +export const resolvePath = (type, ast, node) => { + let out = ''; + //console.log('GIVEN NODE: ', node); + //console.log('PATHS: ', type.collectByElement(ast, 'ImportPath', undefined, ['amd']).get(0)); + return out; +}; + +/** +* Adds a new module into a given dependency module list based on current node ast. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} node current node +* @return {util.adt.Collection} +**/ +export const add = (modules, node) => { + //console.log('AMD Specifier', node.value); + let _module = resolveNamed(modules, resolveAliased(node)); + if(!get(modules, _module.id)) modules.add(_module); + return modules; +}; + +/** +* Removes an existing module from a given dependency module list by module +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {Object} module module to remove +* @return {util.adt.Collection} +**/ +export const remove = (modules, module) => { + // TODO + return modules; +}; diff --git a/lib/bundle/format/amd/template.es6 b/lib/bundle/format/amd/template.es6 new file mode 100644 index 0000000..9c02861 --- /dev/null +++ b/lib/bundle/format/amd/template.es6 @@ -0,0 +1,43 @@ +/** +* Draft +* @version 1.0.0 +* @module bundle.format.amd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; + +/** +* Import Template +* @private +* @property __it__ +* @type {Function} +**/ +const __it__ = _.template(`/** <<%= name %>> **/ +define(["<%= dependencies.join('", "') %>"], function(<%= ids.join(', ') %>) { + <%= content %> +});`); + +/** +* Export Template +* @todo Support for "Bindings" and review multiple exports +* @private +* @property __et__ +* @type {Function} +**/ +const __et__ = _.template(`return <%= exports %>;`); + +/** +* Imports +* @static +* @param {{ name: "", dependencies: [], ids: [], content: "" }} attrs parameters to the template +* @return {Function} +**/ +export const imports = (attrs = {}) => { return __it__(attrs); }; + +/** +* Exports +* @static +* @param {{ exports: {} }} attrs parameters to the template +* @return {Function} +**/ +export const exports = (attrs = {}) => { return __et__(attrs); }; diff --git a/lib/bundle/format/cjs/cjs.es6 b/lib/bundle/format/cjs/cjs.es6 new file mode 100644 index 0000000..418d6d4 --- /dev/null +++ b/lib/bundle/format/cjs/cjs.es6 @@ -0,0 +1,172 @@ +/** +* @module bundle.format.cjs +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Format from 'bundle/format/format'; +import * as Helpers from 'bundle/format/cjs/helpers'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class CommonJs +* @extends {bundle.format.Format} +**/ +class CommonJs extends Format { + + /** + * CommonJs AST Query + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + cjs(ctx, ...args) { + return this.query(ctx, ...args); + } + + /** + * CommonJs AST Query by a given type + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + cjsByType(ctx, ast, ...args) { + return this.queryByType(ctx, ast, ...args); + } + + /** + * CommonJs AST QUery by a given element + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + cjsByElement(ctx, ast, ...args) { + return this.queryByElement(ctx, ast, ...args); + } + + /** + * CommonJs Resolves Import Specifier for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @return {Object} + **/ + cjsResolveImportSpecifier(ctx, dependency, metadata, node) { + if(!_.defined(dependency.import)) dependency.import = { modules: Collection.new() }; + Helpers.add(dependency.import.modules, node); + return dependency; + } + + /** + * CommonJs Resolves Import Path for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + cjsResolveImportPath(ctx, dependency, metadata, node, ast) { + dependency.import.path = ctx.reader.file(Helpers.resolvePath(ast), false); + return dependency; + } + + /** + * CommonJs Resolves Parent for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + cjsResolveParent(ctx, dependency, metadata) { + return super.resolveParent(dependency, metadata); + } + + /** + * CommonJs Resolves AST Injection for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + cjsResolveAst(ctx, dependency, metadata) { + return super.resolveAst(dependency, ''); + } + + /** + * ASTQ CJS Import Declaration Query + * @static + * @property QCJS_ImportDeclaration + * @type {String} + **/ + static QCJS_ImportDeclaration = ` + /VariableDeclaration [/VariableDeclarator [/CallExpression [/Identifier [@name == 'require']]]], + /ExpressionStatement [/AssignmentExpression [/CallExpression [/Identifier [@name == 'require']]]], + /ExpressionStatement [/CallExpression [/Identifier [@name == 'require']]] + `; + + /** + * ASTQ CJS Import Specifier Query + * @FIXME: require('hello'); + * @static + * @property QCJS_ImportSpecifier + * @type {String} + **/ + static QCJS_ImportSpecifier = ` + /VariableDeclarator /Identifier, + /AssignmentExpression /Identifier, + /CallExpression + `; + + /** + * ASTQ CJS Import Declaration Query + * @static + * @property QCJS_ExportDeclaration + * @type {String} + **/ + static QCJS_ExportDeclaration = ` + /ExpressionStatement /AssignmentExpression [ + @operator == '=' && + @left != 'undefined' && + /MemberExpression [ + @property != 'undefined' && + /Identifier [@name == 'exports'] + ] + ] + `; + + /** + * ASTQ CJS Export Specifier Query + * @static + * @property QCJS_ExportSpecifierDeclaration + * @type {String} + **/ + static QCJS_ExportSpecifier = ` + /TODO + `; + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'CjsVisitor'; + } + +} + +export default CommonJs; diff --git a/lib/bundle/format/cjs/helpers.es6 b/lib/bundle/format/cjs/helpers.es6 new file mode 100644 index 0000000..31cd9b0 --- /dev/null +++ b/lib/bundle/format/cjs/helpers.es6 @@ -0,0 +1,86 @@ +/** +* @module bundle.format.cjs +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; + +/** +* Retrieves module for a given dependency module list by id. Returns null if module is not found. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {String} id module id to retrieve +* @return {Object} +**/ +export const get = (modules, id) => { + if(!_.defined(id)) return true; + return modules.findWhere({ id }); +}; + +/** +* Resolves Module for a given astq node if it's a named require. +* i.e: const { version } = require('jquery'); +* @public +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveAliased = (node = {}) => { + // TODO + return node; +}; + +/** +* Resolves Module for a given astq node if it doesn't have named require. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveNamed = (modules, node = {}) => { + return { id: (node.name ? node.name : node.arguments[0].value) }; +}; + +/** +* Resolve Dependency path based on a given node +* @public +* @param {astq.Node} [node = {}] node to resolve +* @return {String} +**/ +export const resolvePath = (node) => { + let out = ''; + // CASE: let $ = require('jquery'); + if(node.declarations && node.declarations[0] && node.declarations[0].init) + out = node.declarations[0].init.arguments[0].value; + // CASE: LibB = require('dependencies/lib-b'); + if(node.expression && node.expression.right) + out = node.expression.right.arguments[0].value; + // CASE: require('hello'); + if(node.expression && node.expression.arguments) + out = node.expression.arguments[0].value; + // TODO: CASE: case const { version } = require('jquery'); + return out; +}; + +/** +* Adds a new module into a given dependency module list based on current node ast. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} node current node +* @return {util.adt.Collection} +**/ +export const add = (modules, node) => { + let _module = resolveNamed(modules, resolveAliased(node)); + if(!get(modules, _module.id)) modules.add(_module); + return modules; +}; + +/** +* Removes an existing module from a given dependency module list by module +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {Object} module module to remove +* @return {util.adt.Collection} +**/ +export const remove = (modules, module) => { + // TODO + return modules; +}; diff --git a/lib/bundle/format/cjs/template.es6 b/lib/bundle/format/cjs/template.es6 new file mode 100644 index 0000000..a193cf5 --- /dev/null +++ b/lib/bundle/format/cjs/template.es6 @@ -0,0 +1,60 @@ +/** +* Draft +* @version 1.0.0 +* @module bundle.format.cjs +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; + +/** +* CommonJs Export Cases +* --------------------- +* +* 1) module.exports = {o1}; +* 2) var m = module; m.exports = {o2}; +**/ + +/** +* Single Import Template +* @private +* @property __sit__ +* @type {Function} +**/ +const __sit__ = _.template(`var <%= id %> = require('<%= dependency %>');`); + +/** +* Imports Template +* @private +* @property __it__ +* @type {Function} +**/ +const __it__ = _.template(`/** <%= name %> **/ +<% print(_.reduce(dependencies, function(memo, dependency, ix) { + memo += (__sit__({ id: ids[ix], dependency }) + '\n'); return memo; +}, '')); %>\n<%= content %>`); + +/** +* Exports Template +* @todo Support for "bindings" and review multiple exports +* @private +* @property __et__ +* @type {Function} +**/ +const __et__ = _.template(`module.exports = <%= exports %>`); + +/** +* Imports +* @static +* @param {{ name: "", dependencies: [], ids: [], content: "" }} attrs parameters to the template +* @return {Function} +**/ +export const imports = (attrs = {}) => { return it(extend({}, attrs, { _, __sit__ })); }; + +/** +* Exports +* @static +* @param {{ exports: {} }} attrs parameters to the template +* @return {Function} +**/ +export const exports = (attrs = {}) => { return __et__(attrs); }; diff --git a/lib/bundle/format/es6/es6.es6 b/lib/bundle/format/es6/es6.es6 new file mode 100644 index 0000000..a2cb19e --- /dev/null +++ b/lib/bundle/format/es6/es6.es6 @@ -0,0 +1,158 @@ +/** +* @module bundle.format.es6 +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Format from 'bundle/format/format'; +import * as Helpers from 'bundle/format/es6/helpers'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class Es6 +* @extends {bundle.format.Format} +**/ +class Es6 extends Format { + + /** + * ES6 AST Query + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + es6(ctx, ...args) { + return this.query(ctx, ...args); + } + + /** + * ES6 AST Query by a given type + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + es6ByType(ctx, ast, ...args) { + return this.queryByType(ctx, ast, ...args); + } + + /** + * ES6 AST QUery by a given element + * @param {util.visitor.Visited} ctx context visited + * @param {astq.Node} ast ast to query + * @param {Any} [args...] additional arguments + * @return {Any} + **/ + es6ByElement(ctx, ast, ...args) { + return this.queryByElement(ctx, ast, ...args); + } + + /** + * ES6 Resolves Import Specifier for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + es6ResolveImportSpecifier(ctx, dependency, metadata, node) { + if(!_.defined(dependency.import)) dependency.import = { modules: Collection.new() }; + Helpers.add(dependency.import.modules, node); + return dependency; + } + + /** + * ES6 Resolves Import Path for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency new dependency to add + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + es6ResolveImportPath(ctx, dependency, metadata, node, ast) { + dependency.import.path = ctx.reader.file(ast.source.value, false); + return dependency; + } + + /** + * ES6 Resolves Parent for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + es6ResolveParent(ctx, dependency, metadata) { + return super.resolveParent(dependency, metadata); + } + + /** + * ES6 Resolves AST Injection for a given dependency and returns it + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} node current node ast + * @param {astq.Node} ast current original source ast reference + * @return {Object} + **/ + es6ResolveAst(ctx, dependency, metadata) { + return super.resolveAst(dependency, ''); + } + + /** + * ASTQ ES6 Import Declaration Query + * @static + * @property QES6_ImportDeclaration + * @type {String} + **/ + static QES6_ImportDeclaration = `/ImportDeclaration`; + + /** + * ASTQ ES6 Import Specifier Query + * @static + * @property QES6_ImportSpecifierDeclaration + * @type {String} + **/ + static QES6_ImportSpecifier = ` + /ImportDefaultSpecifier, + /ImportSpecifier, + /ImportNamespaceSpecifier, + /Literal + `; + + /** + * ASTQ ES6 Export Declaration Query + * @static + * @property QES6_ExportDeclaration + * @type {String} + **/ + static QES6_ExportDeclaration = `/ExportDeclaration`; + + /** + * ASTQ ES6 Export Specifier Query + * @static + * @property QES6_ExportSpecifierDeclaration + * @type {String} + **/ + static QES6_ExportSpecifier = `/ExportDefaultSpecifier`; + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'Es6Visitor'; + } + +} + +export default Es6; diff --git a/lib/bundle/format/es6/helpers.es6 b/lib/bundle/format/es6/helpers.es6 new file mode 100644 index 0000000..565006d --- /dev/null +++ b/lib/bundle/format/es6/helpers.es6 @@ -0,0 +1,75 @@ +/** +* @module bundle.format.es6 +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; + +/** +* Retrieves module for a given dependency module list by id. Returns null if module is not found. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {String} id module id to retrieve +* @return {Object} +**/ +export const get = (modules, id) => { + if(!_.defined(id)) return true; + return modules.findWhere({ id }); +}; + +/** +* Resolves Module for a given astq node if it's default import. +* @public +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveDefault = (node = {}) => { + return (node.local && !node.imported) ? { id: node.local.name } : node; +}; + +/** +* Resolves Module for a given astq node if it's named import. +* @public +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveAliased = (node = {}) => { + return (node.local && node.imported) ? { id: node.imported.name, alias: node.local.name } : node; +}; + +/** +* Resolves Module for a given astq node if it doesn't have named imports (default or aliased). +* Only if module collection is empty (means no named imports has been detected). +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} [node = {}] node to resolve +* @return {Object|astq.Node} +**/ +export const resolveUnnamed = (modules, node = {}) => { + return (node.type === 'Literal' && modules.isEmpty()) ? { id: node.value } : node; +}; + + +/** +* Adds a new module into a given dependency module list based on current node ast. +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {astq.Node} node current node +* @return {util.adt.Collection} +**/ +export const add = (modules, node) => { + let _module = resolveUnnamed(modules, resolveAliased(resolveDefault(node))); + if(!get(modules, _module.id)) modules.add(_module); + return modules; +}; + +/** +* Removes an existing module from a given dependency module list by module +* @public +* @param {util.adt.Collection} modules current list of dependency modules +* @param {Object} module module to remove +* @return {util.adt.Collection} +**/ +export const remove = (modules, module) => { + // TODO + return modules; +}; diff --git a/lib/bundle/format/es6/template.es6 b/lib/bundle/format/es6/template.es6 new file mode 100644 index 0000000..588ea21 --- /dev/null +++ b/lib/bundle/format/es6/template.es6 @@ -0,0 +1,82 @@ +/** +* Draft +* @version 1.0.0 +* @module bundle.format.es6 +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; + +/** +* ES6 Export Cases: +* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export +* ---------------------- +* let o1 = { name: 'export1' }, +* o2 = { name: 'export2' }, +* o3 = { name: 'export3' }; +* const complex = { complex1: 1 }; +* +* - export { o1 }; +* - export { o2 as a2 }; +* - export let a1 = o1, a3 = o3; +* - export { complex as default }; +* +* - export * from 'libs/amd-lib'; +* - export { i1, i2 } from 'libs/cjs-lib'; +* - export { o1 as i3, o2 as i4 } from 'libs/cjs-lib'; +* +* Extra Cases: +* - export default complex; +* - export default function() {} +* - export default function func() {} +**/ + +/** +* Single Import Template +* @private +* @property __sit__ +* @type {Function} +**/ +const __sit__ = _.template(`import <%= id %> from '<%= dependency %>'`); + +/** +* Import Template +* @private +* @property __it__ +* @type {Function} +**/ +const __it__ = _.template(`/** <%= name %> **/ +<% print(_.reduce(dependencies, function(memo, dependency, ix) { +memo += (__sit__({ id: ids[ix], dependency }) + '\n'); return memo; +}, '') + <%= content %>)`); + +/** +* Exports Template +* @todo Support for: +* - export {export} +* - export default {export} +* @private +* @property __it__ +* @type {Function} +**/ +const __et__ = _.template(`export <%= export %>`); + +/** +* Imports +* @static +* @param {{ name: "", dependencies: [], ids: [], content: "" }} attrs parameters to the template +* @return {Function} +**/ +export const imports = (attrs = {}) => { + return __it__(extend({}, attrs, { _, __sit__ })); +}; + +/** +* Exports +* @static +* @param {{ exports: {} }} attrs parameters to the template +* @return {Function} +**/ +export const exports = (attrs = {}) => { + return __et__(extend({}, attrs)); +}; diff --git a/lib/bundle/format/format.es6 b/lib/bundle/format/format.es6 new file mode 100644 index 0000000..20e1b34 --- /dev/null +++ b/lib/bundle/format/format.es6 @@ -0,0 +1,170 @@ +/** +* @module bundle.format +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import extend from 'extend'; +import AstQ from 'astq'; // documentation - https://www.npmjs.com/package/astq +import Visitor from 'util/visitor/visitor'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class Format +* @Note: Subjected to refactor formats ast -> format (formats and templates), ast -> query (astq wrapper visitor) +* @extends {util.visitor.Visitor} +**/ +class Format extends Visitor { + + /** + * Constructor + * @public + * @override + * @return {bundle.format.Format} + **/ + constructor() { + return super({ astq: new AstQ() }); + } + + /** + * Resolves Query Type of element to query + * @public + * @param {String} format the format + * @param {String} type element type + * @return {String} + **/ + which(format, type) { + const prop = `Q${format}_${type}`; + if(_.defined(this.constructor[prop])) return this.constructor[prop]; + logger(`Format ${format} -> Type ${type} not found.`).fatal(); + } + + /** + * Method Wrapper for querying Abstract Syntax Tree (AST) + * @public + * @param {Object} [ast = {}] AST to query + * @param {String} [expr = ''] astq expression + * @param {Function} [cb] optional callback on result + * @param {Any} [...args] additional arguments + * @return {Any} + **/ + query(ctx, ast = {}, expr = '', cb, ...args) { + if(!_.defined(cb)) cb = () => {}; + return this.onQuery(this.astq.query(ast, expr, ...args), cb); + } + + /** + * Method wrapper for querying Abstract Syntax Tree (AST) by a given type + * @public + * @param {astq.Node} [ast] astq node to query + * @param {Any} [...args] additional arguments + * @return {Any} + **/ + queryByType(ctx, ast, ...args) { + return this.query(ctx, ast, this.which(this.getName(), Format.types[ctx.getName()]), ...args); + } + + /** + * Method wrapper for querying Abstract Syntax Tree (AST) by a given element + * @public + * @param {astq.Node} [ast] astq node to query + * @param {String} element element name to query + * @param {Any} [...args] additional arguments + * @return {Any} + **/ + queryByElement(ctx, ast, element, ...args) { + return this.query(ctx, ast, this.which(this.getName(), Format.elements[element]), ...args); + } + + /** + * Default Query Result Handler + * @public + * @param {Array} out - ast query result + * @param {Function} cb - callback on result + * @return {Any} + **/ + onQuery(out = [], cb) { + return this.onResult(out, cb); + } + + /** + * Default Result Handler + * @public + * @param {Array} results ast query result list + * @param {Function} callback callback + * @return {Any} + **/ + onResult(results, callback) { + callback(Collection.new(results), this.getName()); + return results; + } + + /** + * Generic AST Injection for a given dependency and returns it + * @public + * @param {Object} dependency dependency to inject ast + * @param {astq.Node} ast ast root node for current dependency + * @return {Object} + **/ + resolveAst(dependency, ast) { + return extend(false, dependency, { input: ast }); + } + + /** + * Generic Parent Resolution for a given dependency and returns it + * @public + * @param {Object} dependency dependency to resolve parent + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @return {Object} + **/ + resolveParent(dependency, metadata) { + let name = metadata.getName(); + return extend(false, dependency, { parent: _.defined(name) ? name : _.uuid() }); + } + + /** + * Retrieve Format Name + * @public + * @return {String} + **/ + getName() { + return _s.strLeftBack(this.name, 'Visitor').toUpperCase(); + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'FormatVisitor'; + } + + /** + * Query types + * @public + * @property types + * @type {Object} + **/ + static types = { + import: 'ImportDeclaration', + export: 'ExportDeclaration' + }; + + /** + * Query elements + * @public + * @property elements + * @type {Object} + **/ + static elements = { + ImportSpecifier: 'ImportSpecifier', + ImportId: 'ImportId', + ImportPath: 'ImportPath', + ExportSpecifier: 'ExportSpecifier', + }; + +} + +export default Format; diff --git a/lib/bundle/format/iife/iife.es6 b/lib/bundle/format/iife/iife.es6 new file mode 100644 index 0000000..0906722 --- /dev/null +++ b/lib/bundle/format/iife/iife.es6 @@ -0,0 +1,30 @@ +/** +* @module bundle.format.iife +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Format from 'bundle/format/format'; +import logger from 'util/logger/logger'; + +/** +* Class Iife +* @extends {bundle.format.Format} +**/ +class Iife extends Format { + + /** + * Iife AST Query + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} o - object to query + * @param {String} expr - json path query + * @return {Any} + **/ + iife(ctx, o, expr) { + return this.query(o, expr); + } + +} + +export default Iife; diff --git a/lib/bundle/format/iife/template.es6 b/lib/bundle/format/iife/template.es6 new file mode 100644 index 0000000..8e02493 --- /dev/null +++ b/lib/bundle/format/iife/template.es6 @@ -0,0 +1,47 @@ +/** +* Draft +* @version 1.0.0 +* @module bundle.format.iife +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; + +/** +* Import Template +* @private +* @property __it__ +* @type {Function} +**/ +const __it__ = _.template(`/** <%= name %> **/ +(function(<%= ids.join(', ') %>) { + <%= content %> +})(<%= ids.join(', ') %>);`); + +/** +* Export Template +* @private +* @property __et__ +* @type {Function} +**/ +const __et__ = _.template(``); + +/** +* Imports +* @static +* @param {{ name: "", dependencies: [], ids: [], content: "" }} m parameters to the template +* @return {Function} +**/ +export const imports = (attrs = {}) => { + return __it__(extend({}, attrs)); +}; + +/** +* Exports +* @static +* @param {{ exports: {} }} attrs parameters to the template +* @return {Function} +**/ +export const exports = (attrs = {}) => { + return __et__(extend({}, attrs)); +}; diff --git a/lib/bundle/format/umd/template.es6 b/lib/bundle/format/umd/template.es6 new file mode 100644 index 0000000..ed6dbbe --- /dev/null +++ b/lib/bundle/format/umd/template.es6 @@ -0,0 +1,80 @@ +/** +* Draft +* @version 1.0.0 +* @module bundle.format.umd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; + +/** +* Import Template +* @private +* @property __it__ +* @type {Function} +**/ +const __it__ = _.template(`/** <%= name %> **/ + (function(root, factory) { + if(typeof define === 'function' && define.amd) { + define(["<%= dependencies.join('", "') %>"], factory); + } else if(typeof module === 'object' && module.exports) { + module.exports = factory(<% print(_.map(dependencies, __gd__, '', this).join(', ')); %>); + } else { + <% print(_.reduce(ids, __gi__, '', this, dependencies)); %> + } + })(this, function(<%= ids.join(', ') %>) { + <%= content %> + });`); + +/** +* Global Dependency Ids Import +* @private +* @static +* @param {Array} dependencies list of dependency paths +* @param {String} memo memoized output +* @param {String} id current dependency id +* @param {Number} ix current dependency index +* @return {String} +**/ +const __gi__ = (dependencies, memo, id, ix) => { + return (memo += `root[${id}] = root[${dependencies[ix]}];\n`); +}; + +/** +* Global Dependencies Import +* @private +* @static +* @param {String} dependency current dependency path +* @return {String} +**/ +const __gd__ = (dependency) => { + return `require('${dependency}')`; +}; + +/** +* Export Template +* @private +* @property name +* @type {Function} +**/ +const __et__ = _.template(``); + +/** +* Imports +* @static +* @param {{ name: "", dependencies: [], ids: [], content: "" }} m bundle metadata +* @return {Function} +**/ +export const _imports = (attrs = {}) => { + return __it__(extend(false, {}, attrs, { __gi, __gd })); +}; + +/** +* Exports +* @static +* @param {{ exports: {} }} attrs parameters to the template +* @return {Function} +**/ +export const exports = (attrs = {}) => { + return __et__(extend({}, attrs)); +}; diff --git a/lib/bundle/format/umd/umd.es6 b/lib/bundle/format/umd/umd.es6 new file mode 100644 index 0000000..70ac255 --- /dev/null +++ b/lib/bundle/format/umd/umd.es6 @@ -0,0 +1,30 @@ +/** +* @module bundle.format.umd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Format from 'bundle/format/format'; +import logger from 'util/logger/logger'; + +/** +* Class Umd +* @extends {bundle.format.Format} +**/ +class Umd extends Format { + + /** + * UMD AST Query + * @public + * @param {util.visitor.Visited} ctx context visited + * @param {Object} o - object to query + * @param {String} expr - json path query + * @return {Any} + **/ + umd(ctx, o, expr) { + return this.query(o, expr); + } + +} + +export default Umd; diff --git a/lib/bundle/task/metadata/dependency.es6 b/lib/bundle/task/metadata/dependency.es6 new file mode 100644 index 0000000..5f3a96b --- /dev/null +++ b/lib/bundle/task/metadata/dependency.es6 @@ -0,0 +1,70 @@ +/** +* @module bundle.task.metadata +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Visited from 'util/visitor/visited'; + +/** +* Class Dependency +* @extends {util.visitor.Visited} +**/ +class Dependency extends Visited { + + /** + * Constructor + * @public + * @param {Any} [...args] constructor arguments + * @return {bundle.task.metadata.Dependency} + **/ + constructor(...args) { + super(_.object(Dependency.properties, [_.uuid()])); + return this.registerAll().parse(...args); + } + + /** + * Parse Strategy + * @public + * @param {Object} [attrs = {}] attributes to parse + * @return {bundle.task.metadata.Dependency} + **/ + parse(attrs = {}) { + return extend(true, this, _.pick(attrs, this.constructor.properties)); + } + + /** + * Returns a json representation of the instance of this class + * @public + * @override + * @return {Object} + **/ + toJSON() { + return { + id: this.id, + parent: this.parent, + import: this.import, + export: this.export, + input: this.input, + output: this.output + }; + } + + /** + * Property Definition + * @static + * @property properties + * @type {Array} + **/ + static properties = [ + 'id', + 'parent', + 'import', + 'export', + 'input', + 'output' + ]; + +} + +export default Dependency; diff --git a/lib/bundle/task/metadata/metadata.es6 b/lib/bundle/task/metadata/metadata.es6 new file mode 100644 index 0000000..8598a4d --- /dev/null +++ b/lib/bundle/task/metadata/metadata.es6 @@ -0,0 +1,100 @@ +/** +* @module bundle.task.metadata +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Collection from 'util/adt/collection'; +import Visited from 'util/visitor/visited'; +import Dependency from 'bundle/task/metadata/dependency'; + +/** +* Class Metadata +* @version 1.0.0 +* @extends {util.visitor.Visited} +* +* @desc +* This is the general ADT used by squarebox to perform operations on it, as a result of collecting metadata +* by the AST parsing library and later on, used by the AST writer library to generate output. +* Here the general structure specs: +* +* @example +* [bundle.task.metadata.Metadata] => { +* path: {}, - File path where the annotation was found +* params: {annotationCapturedParams}, - required name: {unique bundle name} +* input: {ast}, +* dependents: [{ [bundle.task.metadata.Dependency] - 1-dimensional array - no nesting +* id: {uuid}, +* parent: {id|bundleName}, - if top level. +* import: { path: {}, modules: [{ id: {string}, alias: {string} }] }, +* export: [{ id: {object} }, ...], +* input: {ast}, +* output: {ast} +* }, { +* id: {uuid}, +* parent: {id}, +* import: { path: {}, modules: [{ id: {string}, alias: {string} }] }, +* export: [{ id: {object} }, ...], +* input: {ast}, +* output: {ast} +* }] +* } +**/ +class Metadata extends Visited { + + /** + * Constructor + * @public + * @param {Any} [...args] constructor arguments + * @return {bundle.task.metadata.Metadata} + **/ + constructor(...args) { + super({ dependencies: Collection.new([], { interface: Dependency }) }); + return this.registerAll().parse(...args); + } + + /** + * Parse Strategy + * @public + * @param {Object} attrs metadata attributes to parse + * @return {bundle.task.metadata.Metadata} + **/ + parse(attrs = {}) { + this.dependencies.set(attrs.dependencies); + return extend(true, this, _.pick(attrs, this.constructor.properties)); + } + + /** + * Retrieves bundle name + * @public + * @return {String} + **/ + getName() { + return _.defined(this.params) && _.defined(this.params.name) ? this.params.name : null; + } + + /** + * Property Definition + * @static + * @property properties + * @type {Array} + **/ + static properties = [ + 'path', + 'input', + 'params' + ]; + + /** + * Compound Property Definition + * @static + * @property compound + * @type {Array} + **/ + static compound = [ + 'dependencies' + ]; + +} + +export default Metadata; diff --git a/lib/bundle/task/reader/helpers.es6 b/lib/bundle/task/reader/helpers.es6 new file mode 100644 index 0000000..4715901 --- /dev/null +++ b/lib/bundle/task/reader/helpers.es6 @@ -0,0 +1,110 @@ +/** +* @module bundle.task.reader +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import fs from 'fs-extra'; +import extend from 'extend'; +import glob from 'glob'; +import * as acorn from 'acorn'; +import Collection from 'util/adt/collection'; + +/** +* Default Acorn Options +* @private +* @property acornOptions +* @type {Object} +**/ +const acornOptions = { + ecmaVersion: 8, + sourceType: 'module' +}; + +/** +* Default Glob Options +* @private +* @property globOptions +* @type {Object} +**/ +const globOptions = { + strict: true, + nosort: true, + nodir: true +}; + +/** +* Performs a look up over the parsed files by using the file path +* and retrieves the file reference if it's found, returns null otherwise. +* @private +* @param {String|util.adt.Collection} [input = []] single path or a collection of paths +* @return {Object} +**/ +const get = (list, input = []) => { + let paths = _.isString(input) ? Collection.new([input]) : input; + return list.find((file) => paths.contains(file.path)); +}; + + +/** +* Add a new file into the parsed file list +* @public +* @param {util.adt.Collection} list parsed file list +* @param {String} path file path to remove +* @param {Boolean} [skipParse = false] skip ast parsing +* @return {util.adt.Collection} +**/ +export const add = (list, path, skipParse = false) => { + list.add(!skipParse ? parse(path, acornOptions) : path); + return list; +}; + +/** +* Remove existing parsed file from the list by path +* @public +* @param {util.adt.Collection} list parsed file list +* @param {String} path file path to remove +* @return {util.adt.Collection} +**/ +export const remove = (list, path) => { + return list.removeBy((file) => (file === path)); +}; + +/** +* Perform a file look up over the list of parsed files, for a given input and retrieves it when found. +* If the file is not found, this method will add it and returns the reference. +* @private +* @param {util.adt.Collection} files list of parsed files +* @param {String} input file path to resolve +* @param {Any} [...args] additional and optional arguments +* @return {util.adt.Collection} +**/ +export const resolve = (list, input) => { + let found = get(list, input); + if(!_.defined(found)) add(list, input); + return list; +}; + +/** +* Read a single file by pattern or explicit path +* @public +* @param {String} cwd base path +* @param {String} path path to read files from (pattern or explicit path) +* @param {Array} [ignore = []] optional ignore list of directories/files +* @return {util.adt.Collection} +**/ +export const read = (cwd, path, ignore = []) => { + return Collection.new(glob.sync(path, _.defaults({ cwd, ignore }, globOptions))); +}; + +/** +* Retrieve a given file from the files list +* @public +* @param {String} path file path +* @param {Object} [options = {}] parser options +* @param {Array} [comments = []] comments initial array +* @return {Object} +**/ +export const parse = (path, options = {}, comments = []) => { + let opts = extend(false, { onComment: comments }, options); + return { path, comments, input: acorn.parse(fs.readFileSync(path, { encoding: 'utf8' }), opts) }; +}; diff --git a/lib/bundle/task/reader/reader.es6 b/lib/bundle/task/reader/reader.es6 new file mode 100644 index 0000000..2039962 --- /dev/null +++ b/lib/bundle/task/reader/reader.es6 @@ -0,0 +1,124 @@ +/** +* @module bundle.task.reader +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import * as Helpers from 'bundle/task/reader/helpers'; +import Collection from 'util/adt/collection'; +import Task from 'bundle/task/task'; + +/** +* Class Reader +* @desc +* Responsible for traversing javascript files from directories and subdirectories to build an AST +* representation in order to query for AST elements and apply transformation. +* @extends {bundle.task.Task} +**/ +class Reader extends Task { + + /** + * Read Strategy + * Switch parameters + * @public + * @param {util.visitor.Visited} vi - visited instance reference + * @return {Promise} + **/ + read(vi) { + return this.all(this.scan).types.pop({}, false, this); + } + + /** + * Read and parse all files by an optional resolver function. + * If no custom resolver is provided, the default resolver will be used. + * @public + * @param {String} pattern source pattern to read file/s from + * @param {Function} [resolver = this.helper.resolve] resolver function that manipulates parsed files + * @return {bundle.task.reader.Reader} + **/ + all(pattern, resolver = this.helper.resolve) { + let readFiles = this.helper.read(this.cwd, this.file(pattern, true), this.excludes()) + readFiles.reduce(resolver, this.externals(path)); + return this; + } + + /** + * Filter parsed files by path + * @public + * @param {String} path file path + * @return {Array} + **/ + allByPath(path) { + return Collection.new(this.files.filter((file) => file.path.indexOf(path) !== -1)); + } + + /** + * Resolve pattern with external configuration. + * If a pattern matches an external dependency, + * it will be added to the collection with the flag external set to true. + * @public + * @param {util.adt.Collection} files list of files read + * @param {String} path source pattern/path to resolve + * @return {String} + **/ + externals(path) { + if(_.contains(this.external, path)) this.helper.add(this.files, { path, external: true }, true); + return this.files; + } + + /** + * Type execution Handler + * @public + * @override + * @param {String} [eventName = Task.events.execute] event name to emit + * @return {bundle.task.reader.Reader} + **/ + onType(eventName = Task.events.execute) { + this.emit(Reader.events.read, this); + return this; + } + + /** + * Retrieves Task Name + * @public + * @return {String} + **/ + getName() { + return 'read'; + } + + /** + * Reader's Helper + * @public + * @type {bundle.task.reader.helpers} + **/ + get helper() { + return Helpers; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'Reader'; + } + + /** + * Events + * @static + * @property events + * @type {Object} + **/ + static events = { + + /** + * @event read + **/ + read: 'bundle:task:reader:read' + + }; + +} + +export default Reader; diff --git a/lib/bundle/task/task.es6 b/lib/bundle/task/task.es6 new file mode 100644 index 0000000..fc35cd9 --- /dev/null +++ b/lib/bundle/task/task.es6 @@ -0,0 +1,121 @@ +/** +* @module bundle.task +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import extend from 'extend'; +import Collection from 'util/adt/collection'; +import StackAsync from 'util/adt/stack-async'; +import Factory from 'util/factory/factory'; +import Visitor from 'util/visitor/visitor'; +import logger from 'util/logger/logger'; + +/** +* Class Task +* @extends {util.visitor.Visitor} +**/ +class Task extends Visitor { + + /** + * Constructor + * @public + * @override + * @param {bundle.Bundle} bundle Bundle command reference + * @return {bundle.task.Task} + **/ + constructor(bundle) { + super(extend(_.pick(bundle, bundle.constructor.options), { types: StackAsync.new([]) })); + return this.attachEvents().registerAll(); + } + + /** + * Attach Events + * @public + * @return {bundle.task.Task} + **/ + attachEvents() { + this.types.on(StackAsync.events.next, _.bind(this.onType, this)); + return this; + } + + /** + * Registers and load all types for this task + * @public + * @return {bundle.task.Task} + **/ + registerAll() { + return this.constructor.types.reduce(this.register, this, this); + } + + /** + * Register and load a signle type for this task + * @public + * @param {bundle.task.Task} memo memoized reference of this task + * @param {String} path current type path to the factory + * @return {bundle.task.Task} + **/ + register(memo, path) { + memo.types.push(Factory.register(path).get(path, this)); + return memo; + } + + /** + * Default Type execution Handler + * @public + * @param {String} [eventName = Task.events.execute] event name to emit + * @return {bundle.task} + **/ + onType(eventName = Task.events.execute) { + this.emit(eventName, this); + return this; + } + + /** + * Retrieves Task Name + * @public + * @return {String} + **/ + getName() { + return 'task'; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'TaskVisitor'; + } + + /** + * Supported Types + * @static + * @property types + * @type {util.adt.Collection} + **/ + static types = Collection.new([ + 'bundle/types/export/export', + 'bundle/types/import/import', + 'bundle/types/annotation/annotation' + ]); + + /** + * Events + * @static + * @property events + * @type {Object} + **/ + static events = { + + /** + * @event execute + **/ + execute: 'bundle:task:execute' + + }; + +} + +export default Task; diff --git a/lib/bundle/task/writer/writer.es6 b/lib/bundle/task/writer/writer.es6 new file mode 100644 index 0000000..1d398d8 --- /dev/null +++ b/lib/bundle/task/writer/writer.es6 @@ -0,0 +1,75 @@ +/** +* @module bundle.writer +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import StackAsync from 'util/adt/stack-async'; +import Task from 'bundle/task/task'; + +/** +* Class Writer +* @desc +* Responsible for writing bundle files with dependency information gathered from a Reader source. +* @extends {bundle.task.Task} +**/ +class Writer extends Task { + + /** + * Write Strategy + * @public + * @param {util.visitor.Visited} vi - visited instance reference + * @return {Promise} + **/ + write(vi) { + return this.types.pop({}, false, this); + } + + /** + * Type execution Handler + * @public + * @override + * @param {String} [eventName = Task.events.execute] event name to emit + * @return {bundle.task} + **/ + onType(eventName = Task.events.execute) { + this.emit(Writer.events.write, this); + return this; + } + + /** + * Retrieves Task Name + * @public + * @return {String} + **/ + getName() { + return 'write'; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'Writer'; + } + + /** + * Events + * @static + * @property events + * @type {Object} + **/ + static events = { + + /** + * @event write + **/ + write: 'bundle:task:writer:write' + + }; + +} + +export default Writer; diff --git a/lib/bundle/types/annotation/annotation.es6 b/lib/bundle/types/annotation/annotation.es6 new file mode 100644 index 0000000..c47e3b6 --- /dev/null +++ b/lib/bundle/types/annotation/annotation.es6 @@ -0,0 +1,75 @@ +/** +* @module bundle.types.annotation +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Type from 'bundle/types/type'; +import * as Helpers from 'bundle/types/annotation/helpers'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class Annotation +* @extends {bundle.types.Type} +**/ +class Annotation extends Type { + + /** + * Annotation Read strategy + * @public + * @return {Promise} + **/ + read() { + this.annotations().reduce(this.create, this.reader.bundles, this); + return this.resolve(this); + } + + /** + * Read all annotations of all files captured + * @public + * @return {util.adt.Collection} + **/ + annotations() { + return this.reader.files.reduce(this.annotation, Collection.new(), this); + } + + /** + * Create Bundle Metadata + * @public + * @param {util.adt.Collection} memo memoized list of bundles + * @param {Object} meta data extracted from annotation + * @return {util.adt.Collection} + **/ + create(memo, meta) { + if(!Helpers.containsBy(memo, meta)) memo.add(meta); + return memo; + } + + /** + * Read annotations from a single file + * @public + * @param {util.adt.Collection} memo memoized collection of files used to store parsed annotations + * @param {Object} captured current captured file metadata + * @return {util.adt.Collection} + **/ + annotation(memo, captured) { + const { comments, path, input } = captured; + let annotation = this.comments(comments, Helpers.match); + if(_.defined(annotation)) memo.add({ path, input, params: Helpers.extract(annotation) }); + return memo; + } + + /** + * Type Name + * @public + * @property name + * @type {String} + **/ + get name() { + return 'Annotation'; + } + +} + +export default Annotation; diff --git a/lib/bundle/types/annotation/helpers.es6 b/lib/bundle/types/annotation/helpers.es6 new file mode 100644 index 0000000..8e7e766 --- /dev/null +++ b/lib/bundle/types/annotation/helpers.es6 @@ -0,0 +1,80 @@ +/** +* @module bundle.types.annotation +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import json5 from 'json5'; + +/** +* Annotation name +* @private +* @property annotation +* @type {String} +**/ +const annotation = '@sqbox'; + +/** +* Metadata Contain Matcher +* Will return true if the metadata with a given name already exists inside the collection. +* @public +* @param {util.adt.Collection} collection list of bundles to perform look up +* @param {Object} meta bundle metadata to evaluate +* @return {Boolean} +**/ +export const containsBy = (collection, meta) => { + return _.defined(collection.find((current) => (matchPath(current, meta) && matchParams(current, meta)))); +}; + +/** +* Metadata Path Matcher +* Will return true if a metadata object exists with the same path and the given path. +* @public +* @param {Object} current current metadata +* @param {String} path given metadata path to compare +* @return {Boolean} +**/ +export const matchPath = (current, path) => { return (current.path === path); }; + +/** +* Metadata Params Matcher +* Will return true if a metadata object exists with the same params and the given params. +* @public +* @param {Object} current current metadata +* @param {Object} params given metadata params to compare +* @return {Boolean} +**/ +export const matchParams = (current, params = {}) => { + return _.defined(params.name) && (params.name === current.params.name); +}; + +/** +* Annotation Matcher: returns true if annotation was found, false otherwise +* @public +* @param {String} expr annotation found to be match +* @return {Boolean} +**/ +export const match = (expr) => { + return _s.startsWith(expr, `${annotation}(`) && _s.endsWith(expr, ')'); +}; + +/** +* Annotation Metadata Extraction +* Will extract, parse and return annotation information using json5 specification +* @public +* @param {String} expr annotation found to be match +* @return {Boolean} +**/ +export const extract = (expr = '') => { + return json5.parse(_s.rtrim(_s.ltrim(expr, `${annotation}(`), ')')); +}; + +/** +* Returns true if the metadata extracted is valid, false otherwise +* @public +* @param {Object} meta annotation metadata information extracted +* @return {Boolean} +**/ +export const valid = (meta) => { + return (_.defined(meta.name) && _.isString(meta.name) && meta.name.length > 0); +}; diff --git a/lib/bundle/types/common/collector.es6 b/lib/bundle/types/common/collector.es6 new file mode 100644 index 0000000..cf9a771 --- /dev/null +++ b/lib/bundle/types/common/collector.es6 @@ -0,0 +1,115 @@ +/** +* @module bundle.types.common +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import Collection from 'util/adt/collection'; +import Visitor from 'util/visitor/visitor'; +import Type from 'bundle/types/type'; +import Format from 'bundle/format/format'; + +/** +* Class Collector +* @extends {util.visitor.Visitor} +**/ +class Collector extends Visitor { + + /** + * Iterate over the list of formats and executes astq query based on the format. + * Will collect and flatten query results into a single list of astq result objects. + * @public + * @param {astq.Node} ast current source ast to detect + * @param {Array} methods list of query methods to execute + * @param {Function} [cb = () => {}] optional callback + * @param {Any} [...args] additional arguments + * @return {Array} + **/ + iterate(ast, methods, ...args) { + return Collection.new(_.chain(methods).map((method) => method(ast, ...args)).flatten().uniq().value()); + } + + /** + * Generic Strategy to execute astq queries on a given list of formats. + * By default all the formats will be used with the following order: es6, commonjs and amd. + * @public + * @param {bundle.types.Type} ctx type context + * @param {astq.Node} ast current source ast to detect + * @param {String} expr ast query expression + * @param {Function} [cb] optional callback + * @param {Array} [formats = Type.formats] list formats + * @param {Any} [...args] additional arguments + * @return {Array} + **/ + collect(ctx, ast, expr, cb, formats = Type.formats, ...args) { + return this.iterate(ast, this.formatsByType(formats, ctx), expr, cb, ...args); + } + + /** + * Filter Formats by Name + * @public + * @param {Array} [formats = []] list of formats to filter + * @param {bundle.types.Type} ctx type context + * @return {Array} + **/ + formatsByType(formats = [], ctx) { + return _.chain(formats).map((format) => _.defined(ctx[format]) ? ctx[format] : null).compact().value(); + } + + /** + * Generic Strategy to execute astq queries on a given list of formats + * By default all the formats will be used with the following order: es6, commonjs and amd. + * @public + * @param {bundle.types.Type} ctx type context + * @param {astq.Node} ast current source ast to detect + * @param {Function} [cb] optional callback + * @param {Array} [formats = Type.formats] list formats + * @param {Any} [...args] additional arguments + * @return {Array} + **/ + collectByType(ctx, ast, cb, formats = Type.formats, ...args) { + return this.iterate(ast, this.formatsByName(formats, ctx, 'ByType'), cb, ...args); + } + + /** + * Generic Strategy to execute astq queries on a given list of formats + * By default all the formats will be used with the following order: es6, commonjs and amd. + * @public + * @param {bundle.types.Type} ctx type context + * @param {astq.Node} ast current source ast to detect + * @param {String} element element name to query + * @param {Function} [cb] optional callback + * @param {Array} [formats = Type.formats] list formats + * @param {Any} [...args] additional arguments + * @return {Array} + **/ + collectByElement(ctx, ast, element, cb, formats = Type.formats, ...args) { + return this.iterate(ast, this.formatsByName(formats, ctx, 'ByElement'), element, cb, ...args); + } + + /** + * Filter Formats by Type + * @public + * @param {Array} [formats = []] list of formats + * @param {bundle.types.Type} ctx type context + * @param {String} type predicate type qualifier + * @return {Array} + **/ + formatsByName(formats = [], ctx, type) { + return _.chain(formats).map((format) => { + let method = `${format}${type}`; + return _.defined(ctx[method]) ? ctx[method] : null; + }).compact().value(); + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'CollectorVisitor'; + } + +} + +export default Collector; diff --git a/lib/bundle/types/common/comment.es6 b/lib/bundle/types/common/comment.es6 new file mode 100644 index 0000000..79a67a1 --- /dev/null +++ b/lib/bundle/types/common/comment.es6 @@ -0,0 +1,59 @@ +/** +* @module bundle.types.common +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import Visitor from 'util/visitor/visitor'; + +/** +* Class Comments +* @extends {util.visitor.Visitor} +**/ +class Comments extends Visitor { + + /** + * Iterates over the comments and trims comments characters from the value + * @private + * @param {Array} comments - collection of comments + * @return {Array} + **/ + _clean(comments) { + return _.map(comments, (comment) => _.trimSpecial(comment.value)); + } + + /** + * Perform a look up of comments and filter comments based on a given predicate + * @private + * @param {Array} comments list of all comments + * @param {Function} [predicate = () => false] predicate walker + * @return {Array} + **/ + _search(comments = [], predicate = () => false) { + return _.find(this._clean(comments), predicate); + } + + /** + * Generic Strategy that filters and collects comments gathered from source code + * @public + * @param {bundle.types.Type} ctx type context + * @param {Array} [comments = []] collection of comments + * @param {Function} [predicate = () => false] predicate used to filter comments + * @return {Object} + **/ + comments(ctx, comments = [], predicate = () => false) { + return this._search(comments, predicate); + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'CommentsVisitor'; + } + +} + +export default Comments; diff --git a/lib/bundle/types/export/export.es6 b/lib/bundle/types/export/export.es6 new file mode 100644 index 0000000..a75b762 --- /dev/null +++ b/lib/bundle/types/export/export.es6 @@ -0,0 +1,74 @@ +/** +* @module bundle.types.export +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Type from 'bundle/types/type'; +import Collection from 'util/adt/collection'; +import logger from 'util/logger/logger'; + +/** +* Class Export +* @extends {bundle.types.Type} +**/ +class Export extends Type { + + /** + * Export Read strategy + * @public + * @override + * @return {Promise} + **/ + read() { + this.exports(); + return this.resolve(this); + } + + /** + * Read Exports + * @public + * @param {util.adt.Collection} bundles all bundles captured by annotations + * @return {bundle.types.import.Import} + **/ + exports() { + this.reader.bundles.reduce(this.export, Collection.new(), this); + return this; + } + + /** + * Read Exports on a single file + * @public + * @param {util.adt.Collection} memo memoized collection to augment + * @param {bundle.task.metadata.Metadata} metadata instance of meta found as a bundle + * @return {util.adt.Collection} + **/ + export(memo, metadata) { + const { path, input } = metadata; + //this.collectByType(target.ast, ['es6'], _.bind(Helpers.onExport, Helpers)); + return memo; + } + + /** + * Export Write strategy + * @public + * @override + * @return {Promise} + **/ + write() { + return this.resolve(this); + } + + /** + * Type Name + * @public + * @property name + * @type {String} + **/ + get name() { + return 'Export'; + } + +} + +export default Export; diff --git a/lib/bundle/types/import/import.es6 b/lib/bundle/types/import/import.es6 new file mode 100644 index 0000000..818cafe --- /dev/null +++ b/lib/bundle/types/import/import.es6 @@ -0,0 +1,171 @@ +/** +* @module bundle.types.import +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import extend from 'extend'; +import Type from 'bundle/types/type'; +import Collection from 'util/adt/collection'; +import Format from 'bundle/format/format'; +import logger from 'util/logger/logger'; + +/** +* Class Import +* @extends {bundle.types.Type} +**/ +class Import extends Type { + + /** + * Import Read strategy + * @public + * @override + * @return {Promise} + **/ + read() { + let result = this.reader.bundles.reduce(this.readBundle, true, this); + return result ? this.resolve(this) : this.reject(this); + } + + /** + * Read all bundles + * @public + * @param {Boolean} memo memoized boolean result + * @param {bundle.task.metadata.Metadata} metadata instance of meta found as a bundle + * @param {Number} ix current metadata index + * @param {util.adt.Collection} collection original metadata collection + * @return {Boolean} + **/ + readBundle(memo, metadata) { + return this.readDependencies(memo, metadata, metadata.input); + } + + /** + * Recursive Strategy to query dependencies and attach them into the metadata. + * @public + * @param {Boolean} memo memoized boolean result + * @param {bundle.task.metadata.Metadata} metadata current bundle reference + * @param {astq.Node} input current ast node used to collect + * @param {Object} [file] current parsed file + * @return {Boolean} + **/ + readDependencies(memo, metadata, input, file) { + this.collectByType(input, _.bind(this.dependency, this, metadata)); + return metadata.dependencies.reduce(_.bind(this.onDependenciesRead, this, metadata), memo); + } + + /** + * Resolves & creates dependency + * @public + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {util.adt.Collection} dependencies list of dependencies found + * @param {String} format current format + * @return {util.adt.Collection} + **/ + dependency(metadata, dependencies, format) { + return dependencies.reduce((metadata, ast) => { + metadata.dependencies.add(this.createDependency(metadata, ast, format)); + return metadata; + }, metadata, this); + } + + /** + * Creates Dependency + * @public + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} ast current dependency ast reference + * @param {String} format current format being resolved + * @return {Object} + **/ + createDependency(metadata, ast, format) { + //if(format === 'AMD') console.log('AMD AST: ', ast); + return this.collectByElement(ast, Format.elements.ImportSpecifier) + .reduce(_.bind(this.resolveDependency, this, format, metadata, ast), {}); + } + + /** + * Resolve Dependency + * @param {String} format current format being resolved + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {astq.Node} ast current original source ast reference + * @param {Object} dependency current new dependency + * @param {astq.Node} node current dependency node reference + **/ + resolveDependency(format, metadata, ast, dependency, node) { + return Import.by(format, this).reduce((dependency, resolve) => { + return resolve(dependency, metadata, node, ast); + }, dependency); + } + + /** + * Dependencies Query Result Handler + * @public + * @param {bundle.task.metadata.Metadata} metadata current metadata + * @param {Boolean} memo memoized boolean result + * @param {bundle.task.metadata.Dependency} dependency current dependency + * @return {Boolean} + **/ + onDependenciesRead(metadata, memo, dependency) { + //console.log('------------------------'); + let out = this.reader.all(dependency.import) + .allByPath(dependency.import.path); + // .reduce((memo, file) => { + // console.log(file.path); + // let out = this.readDependencies(memo, metadata, file.input, file) + // console.log('------'); + // return out; + // }, memo); + if(dependency.import.path.indexOf('amd-lib') !== -1) { + //console.log('I: ', out.get(0).input.body[0].expression.arguments); + //console.log('******************'); + this.collectByType(out.get(0).input, _.bind(this.dependency, this, metadata), ['amd']); + //console.log(metadata.path, metadata.dependencies._collection); + } + return memo; + } + + /** + * Import Write strategy + * @public + * @override + * @return {Promise} + **/ + write() { + return this.resolve(this); + } + + /** + * Type Name + * @public + * @property name + * @type {String} + **/ + get name() { + return 'Import'; + } + + /** + * Resolvers by Format + * @static + * @param {String} format + * @return {Array} + **/ + static by = (format, instance) => { + return Collection.new(_.map(Import.elements, (name) => instance[`${format.toLowerCase()}${name}`])); + } + + /** + * Import Element Resolvers + * @static + * @property elements + * @type {Array} + **/ + static elements = [ + 'ResolveImportSpecifier', + 'ResolveImportPath', + 'ResolveParent', + 'ResolveAst' + ]; + +} + +export default Import; diff --git a/lib/bundle/types/type.es6 b/lib/bundle/types/type.es6 new file mode 100644 index 0000000..9c05f72 --- /dev/null +++ b/lib/bundle/types/type.es6 @@ -0,0 +1,110 @@ +/** +* @module bundle.types +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import extend from 'extend'; +import * as Comments from 'bundle/types/common/comment'; +import Collection from 'util/adt/collection'; +import Visited from 'util/visitor/visited'; +import logger from 'util/logger/logger'; + +/** +* Class Type +* @extends {util.visitor.Visited} +**/ +class Type extends Visited { + + /** + * Constructor + * @public + * @override + * @return {bundle.types.Type} + **/ + constructor() { + return super().registerAll(); + } + + /** + * Resolves Task Action + * @public + * @param {String} task current task name + * @param {Any} [...args] list of arguments + * @return {Promise} + **/ + action(task, ...args) { + return _.defined(this[task]) ? this[task](...args) : this.resolve(this); + } + + /** + * Injects a given task into this type + * @public + * @param {Function} resolve asynchronous promise's resolve + * @param {Function} reject asynchronous promise's reject + * @param {bundle.task.Task} task current task reference + * @return {bundle.types.Type} + **/ + inject(resolve, reject, task) { + return extend(false, this, { resolve, reject, [task.name.toLowerCase()]: task }); + } + + /** + * Default Asynchronous next strategy + * @public + * @param {Function} resolve asynchronous promise's resolve + * @param {Function} reject asynchronous promise's reject + * @param {bundle.task.Task} task current task reference + * @param {Any} [...args] optional additional arguments + * @return {Promise} + **/ + next(resolve, reject, task, ...args) { + return this.inject(resolve, reject, task).action(task.getName(), ...args); + } + + /** + * Retrieves element type name + * @public + * @return {String} + **/ + getName() { + return _s.strLeft(this.name, 'Visitor').toLowerCase(); + } + + /** + * Type Name + * @public + * @property name + * @type {String} + **/ + get name() { + return 'Type'; + } + + /** + * Default Formats + * @static + * @property formats + * @type {Array} + **/ + static formats = ['es6', 'cjs', 'amd']; + + /** + * List of Visitors + * @static + * @property visitors + * @type {util.adt.Collection} + **/ + static visitors = Collection.new(Visited.visitors.toJSON().concat([ + 'bundle/types/common/collector', + 'bundle/types/common/comment', + 'bundle/format/es6/es6', + 'bundle/format/cjs/cjs', + 'bundle/format/amd/amd', + 'bundle/format/iife/iife', + 'bundle/format/umd/umd' + ])); + +} + +export default Type; diff --git a/lib/clean/clean.es6 b/lib/clean/clean.es6 index 0f9eaea..d050b33 100644 --- a/lib/clean/clean.es6 +++ b/lib/clean/clean.es6 @@ -16,14 +16,22 @@ class Clean extends Command { * Run * @public * @override + * @param {Function} resolve asynchronous promise's resolve + * @param {Function} reject asynchronous promise's reject * @return {clean.Clean} **/ - run() { - // TODO - console.log('Clean.run()...'); - return super.run(); + run(resolve, reject) { + return super.run(resolve, reject); } + /** + * List of commands that depends on + * @static + * @property dependsOn + * @type {Array} + **/ + static dependsOn = Command.dependsOn.concat([]); + } export default Clean; diff --git a/lib/command.es6 b/lib/command.es6 index 4dedf69..d9fb74c 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -5,8 +5,11 @@ import { EventEmitter } from 'events'; import _ from 'util/mixins'; import extend from 'extend'; +import Collection from 'util/adt/collection'; import Factory from 'util/factory/factory'; +import StackAsync from 'util/adt/stack-async'; import Visited from 'util/visitor/visited'; +import logger from 'util/logger/logger'; /** * Class Command @@ -20,12 +23,13 @@ class Command extends Visited { /** * Constructor * @public + * @override * @param {Object} [args = {}] - constructor arguments * @return {Command} **/ constructor(args = {}) { super(); - return this.settings(args).register().acceptAll(); + return extend(true, this.settings(args).registerAll(this.dirname), { stack: StackAsync.new([]) }); } /** @@ -36,41 +40,34 @@ class Command extends Visited { * @return {Command} **/ settings(options) { - return extend(true, this, this.constructor.defaults, _.pick(options, this.constructor.options)); + return extend(true, this, _.defaults(_.pick(options, this.constructor.options), this.constructor.defaults)); } /** - * Registers Visitors + * Push a dependent subcommand onto this command * @public + * @param {Command} memo - memoized version of this command + * @param {String} path - dependent command factory path * @return {Command} **/ - register() { - Factory.basePath(this.dirname).registerAll(this.constructor.visitors); - return this; - } - - /** - * Accepts All Visitors - * @public - * @return {command.Command} - **/ - acceptAll() { - return _.reduce(this.constructor.visitors, (memo, v) => memo.accept(Factory.get(v, this)), this); + push(memo, path) { + let opts = extend(true, { parent: memo }, _.pick(memo.toJSON(), this.constructor.options)); + memo.stack.push(Factory.register(path).get(path, opts)); + return memo; } /** * Proxified asynchronous next strategy - * FIXME: Implement when the command gets rejected. * @public * @override - * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations - * @param resolve {Function} asynchronous promise's resolve - * @param reject {Function} asynchronous promise's reject + * @param {Function} resolve asynchronous promise's resolve + * @param {Function} reject asynchronous promise's reject * @return {Promise} **/ - next(adt, resolve, reject) { - this.once(Command.events.done, resolve); - return this.run(); + next(resolve, reject) { + this.once(Command.events.done, resolve) + .once(Command.events.error, reject); + return this.run(resolve, reject); } /** @@ -79,15 +76,18 @@ class Command extends Visited { * @return {Command} **/ before() { + if(_.defined(this.getParent())) this.getParent().emit(Command.events.pending, this); return this.pending(); } /** * Default Run * @public + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject * @return {Command} **/ - run() { + run(resolve, reject) { return this.before().after(); } @@ -97,15 +97,18 @@ class Command extends Visited { * @return {Command} **/ after() { - return this.done(); + this.done(); + if(_.defined(this.getParent())) this.getParent().emit(Command.events.done, this); + return this; } /** * Command Pending exeuction state * @public - * @return {command.Command} + * @return {Command} **/ pending() { + logger(`[${this.constructor.name}] Started`).out(); this.emit(Command.events.pending, this); return this; } @@ -113,95 +116,59 @@ class Command extends Visited { /** * Command Done exeuction state * @public - * @return {command.Command} + * @return {Command} **/ done() { + logger(`[${this.constructor.name}] Finished`).out(); this.emit(Command.events.done, this); return this; } /** - * Retrieves source - * @public - * @return {Object} - **/ - source() { - return this.source; - } - - /** - * Retrieves and resolves scan directory (glob) - * @public - * @return {String} - **/ - scan() { - return this.source().scan; - } - - /** - * Retrieves and resolves excluded folders + * Retrieves command depedents * @public * @return {Array} **/ - exclude() { - return this.source().exclude; + getDependsOn() { + return this.constructor.dependsOn; } /** - * Retrieves list of extensions to scan - * @public - * @return {Array} - **/ - extensions() { - return this.source().extensions; - } - - /** - * Retrieves aliases for modules - * @public - * @return {Object} - **/ - alias() { - return this.source().alias; - } - - /** - * Retrieves target + * Retrieves command options * @public * @return {Object} **/ - target() { - return this.target; + getOptions() { + return this.options; } /** - * Retrieve targets by using a given predicate passed by parameter + * Retrieves parent command * @public - * @param {Function} predicate - predicate to walk over the targets - * @return {Array} + * @return {Command} **/ - targets(predicate) { - return _.defined(predicate) && _.isFunction(predicate) ? _.map(this.target, predicate, this) : []; + getParent() { + return this.parent; } /** - * Retrieves command options - * @public - * @return {Object} + * Command Visitors + * @static + * @override + * @type {util.adt.Collection} **/ - getOptions() { - return this.options; - } + static visitors = Collection.new(Visited.visitors.toJSON().concat([ + 'visitors/async/async', + 'visitors/command/properties' + ])); /** - * Command Visitors + * List of commands that depends on * @static + * @property dependsOn * @type {Array} **/ - static visitors = [ - 'visitors/formatter/json', - 'visitors/async/async' - ]; + static dependsOn = []; /** * Command Defaults @@ -223,11 +190,15 @@ class Command extends Visited { 'env', 'dirname', 'cwd', + 'basePath', 'scan', 'exclude', 'extensions', 'alias', - 'target' + 'external', + 'target', + 'logLevel', + 'parent' ]; /** @@ -236,8 +207,9 @@ class Command extends Visited { * @type {Object} **/ static events = { - pending: 'commands:command:pending', - done: 'commands:command:done' + pending: 'command:pending', + error: 'command:error', + done: 'command:done' }; } diff --git a/lib/util/adt/collection.es6 b/lib/util/adt/collection.es6 index 1beed2c..930a00e 100644 --- a/lib/util/adt/collection.es6 +++ b/lib/util/adt/collection.es6 @@ -143,7 +143,7 @@ class Collection extends EventEmitter { } /** - * Returns true if the a given element existis in this collection, false otherwise + * Returns true if the a given element exists in this collection, false otherwise * @public * @param element {Any} element to evaluate * @return {Boolean} diff --git a/lib/util/adt/queue-async.es6 b/lib/util/adt/queue-async.es6 index f485e3c..ea90f83 100644 --- a/lib/util/adt/queue-async.es6 +++ b/lib/util/adt/queue-async.es6 @@ -66,12 +66,13 @@ class QueueAsync extends Queue { * @override * @param {Object} [opts = {}] - additional options * @param {Boolean} [next = false] - async queue already started + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - async poll(opts = {}, next = false) { + async poll(opts = {}, next = false, ...args) { if(!next) this._resetLast(); - const res = await this.next(opts); - return this.onNext(res, opts); + const res = await this.next(opts, ...args); + return this.onNext(res, opts, ...args); } /** @@ -79,12 +80,13 @@ class QueueAsync extends Queue { * @public * @emits {QueueAsync.events.next} - when opts.silent is false or undefined * @param {Object} [opts] - additional options + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - next(opts) { + next(opts, ...args) { let element = super.poll(opts); if(!opts.silent) this.emit(QueueAsync.events.next, element); - return element.execute(this); + return element.execute(this, ...args); } /** @@ -92,11 +94,12 @@ class QueueAsync extends Queue { * @public * @param {Promise} res - current promise (resolved or rejected) * @param {Object} [opts] - additional options + * @param {Any} [...args] additonal arguments * @return {Any} **/ - onNext(res, opts) { + onNext(res, opts, ...args) { this._last.push(res); - return this.isEmpty() ? this.end(opts) : this.poll(opts, true); + return this.isEmpty() ? this.end(opts) : this.poll(opts, true, ...args); } /** diff --git a/lib/util/adt/stack-async.es6 b/lib/util/adt/stack-async.es6 index bc69f46..8916aab 100644 --- a/lib/util/adt/stack-async.es6 +++ b/lib/util/adt/stack-async.es6 @@ -39,8 +39,8 @@ class StackAsync extends Stack { * Default instanciation strategy for new elements added in this collection * @private * @override - * @param e {Any} element to instanciate - * @param opts {Object} additional options + * @param {Any} e element to instanciate + * @param {Object} opts additional options * @return {Any} **/ _new(e, opts) { @@ -64,14 +64,15 @@ class StackAsync extends Stack { * @public * @async * @override - * @param [opts = {}] {Object} additional options + * @param {Object} [opts = {}] additional options * @param {Boolean} [next = false] - async queue already started + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - async pop(opts = {}, next = false) { + async pop(opts = {}, next = false, ...args) { if(!next) this._resetLast(); - const res = await this.next(opts); - return this.onNext(res, opts); + const res = await this.next(opts, ...args); + return this.onNext(res, opts, ...args); } /** @@ -79,31 +80,33 @@ class StackAsync extends Stack { * @public * @emits {StackAsync.events.next} - when opts.silent = false or undefined * @param [opts] {Object} additional options + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - next(opts) { + next(opts, ...args) { let element = super.pop(opts); if(!opts.silent) this.emit(StackAsync.events.next, element); - return element.execute(this); + return element.execute(this, ...args); } /** * Retrieves and removes the head of this queue, or returns null if this queue is empty * @public - * @param res {Promise} promise reference with resolution (resolved or rejected) - * @param [opts] {Object} additional options + * @param {Promise} res promise reference with resolution (resolved or rejected) + * @param {Object} [opts] additional options + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - onNext(res, opts) { + onNext(res, opts, ...args) { this._last.push(res); - return this.isEmpty() ? this.end(opts) : this.pop(opts, true); + return this.isEmpty() ? this.end(opts) : this.pop(opts, true, ...args); } /** * Asynchronous Queue end * @public * @emits {StackAsync.events.end} - when opts.silent is false or undefined - * @param [opts = {}] {Object} additional options + * @param {Object} [opts = {}] additional options * @return {util.adt.StackAsync} **/ end(opts) { diff --git a/lib/util/mixins.es6 b/lib/util/mixins.es6 index 54864a0..69a5045 100644 --- a/lib/util/mixins.es6 +++ b/lib/util/mixins.es6 @@ -46,6 +46,18 @@ _.mixin({ }, this).flatten().filter((v, k) => _.defined(v)).value(); }, + /** + * Generates and return a UUID (Universally Unique Identifier) + * @public + * @return {String} + **/ + uuid: function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = (c === 'x') ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }, + /** * Return true if a given object is a real object (Not an array or a Date for example), false otherwise. * @public @@ -69,6 +81,18 @@ _.mixin({ return (_.isRealObject(o) || _.isArray(o)); }, + /** + * Returns true if a given object's constructor is a native type, false otherwise. + * @public + * @param [o] {Object} + * @return {Boolean} + **/ + isNative: function(o) { + if(!_.defined(o) || !_.defined(o.constructor)) return true; + let _c = _s.strRightBack(_s.strLeft(o.constructor.toString(), '('), 'function '); + return _.contains(['String', 'Number', 'Boolean', 'Object', 'Array'], _c); + }, + /** * Returns an array of all method names of a given object. * @public @@ -101,6 +125,17 @@ _.mixin({ **/ defined: function(o) { return (!_.isUndefined(o) && !_.isNull(o)); + }, + + /** + * Trims Globally any special characters from a given expression as string + * Special characters include: '*,\\,\n,\t,\r' + * @public + * @param {String} [expr = ''] expression to trim + * @return {String} + **/ + trimSpecial: function(expr = '') { + return _s.replaceAll(_s.clean(expr), /(\*|\n|\\|\t|\r)*/g, ''); } }); diff --git a/lib/util/visitor/visited.es6 b/lib/util/visitor/visited.es6 index ecd64ae..2c7bf58 100644 --- a/lib/util/visitor/visited.es6 +++ b/lib/util/visitor/visited.es6 @@ -6,6 +6,8 @@ import { EventEmitter } from 'events'; import _ from 'underscore'; import extend from 'extend'; import Visitor from 'util/visitor/visitor'; +import Factory from 'util/factory/factory'; +import Collection from 'util/adt/collection'; import InterfaceException from 'util/exception/proxy/interface'; /** @@ -25,6 +27,28 @@ class Visited extends EventEmitter { return extend(true, this, ...args); } + /** + * Default all visitors registration + * @public + * @param {String} dirname - factory base directory name + * @return {util.visitor.Visited} + **/ + registerAll(dirname) { + if(_.defined(dirname)) Factory.basePath(dirname); + return this.constructor.visitors.reduce(this.register, this, this); + } + + /** + * Default single visitor registration + * @public + * @param {util.visitor.Visited} memo memoized reference of the instance of this class + * @param {String} path visitor path to register and load. + * @return {util.visitor.Visited} + **/ + register(memo, path) { + return memo.accept(Factory.register(path).get(path, this)); + } + /** * Returns true if a given visitor is defined and an instance of util.visitor.Visitor, false otherwise * @public @@ -45,6 +69,16 @@ class Visited extends EventEmitter { return this.validate(visitor) ? visitor.visit(this) : this; } + /** + * Default list of visitors + * @static + * @property visitors + * @type {util.adt.Collection} + **/ + static visitors = Collection.new([ + 'visitors/formatter/json' + ]); + /** * Static Constructor * @static diff --git a/lib/visitors/async/async.es6 b/lib/visitors/async/async.es6 index c9b7038..944f0d8 100644 --- a/lib/visitors/async/async.es6 +++ b/lib/visitors/async/async.es6 @@ -28,12 +28,12 @@ class Asynchronous extends Visitor { * Note: This method was designed (and it's most likely) to be overriden by * {@link util.visitor.Visited} subclasses that use this visitor. * @public - * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations * @param resolve {Function} asynchronous promise's resolve * @param reject {Function} asynchronous promise's reject + * @param {Any} [...args] additonal arguments * @return {visitors.async.Asynchronous} **/ - next(adt, resolve, reject) { + next(resolve, reject) { resolve(); return this; } @@ -43,10 +43,11 @@ class Asynchronous extends Visitor { * @public * @param {util.visitor.Visited} [ctx] - context reference * @param {visitors.async.Asynchronous} adt - reference to adt using this interface on their elements + * @param {Any} [...args] additonal arguments * @return {Promise} **/ - execute(ctx, adt) { - return new Promise((resolve, reject) => ctx.next(adt, resolve, reject)); + execute(ctx, adt, ...args) { + return new Promise((resolve, reject) => ctx.next(resolve, reject, ...args)); } /** diff --git a/lib/visitors/command/properties.es6 b/lib/visitors/command/properties.es6 new file mode 100644 index 0000000..1c4f260 --- /dev/null +++ b/lib/visitors/command/properties.es6 @@ -0,0 +1,91 @@ +/** +* @module visitors.command +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'util/mixins'; +import _s from 'underscore.string'; +import path from 'path'; +import Visitor from 'util/visitor/visitor'; + +/** +* Class Properties +* @extends {util.visitor.Visitor} +**/ +class Properties extends Visitor { + + /** + * Resolve a single file or path expression realtive to the base path specified in the configuration + * @public + * @param {bundle.Command} ctx current command + * @param {String} input relative path to resolve + * @param {Boolean} [ext = false] using use extensions + * @return {String} + **/ + file(ctx, input, ext = false) { + let expr = ext ? `${input}.+(${this.extensions.join('|')})` : input; + return path.resolve(this.basePath, this.aliases(ctx, expr)); + } + + /** + * Resolve extension based on input and applies it to the path + * @public + * @param {bundle.Command} ctx current command + * @param {String} input resolved path including the extension + * @param {String} path path to attach the extension into from the input + * @return {String} + **/ + extension(ctx, input, path) { + return (input && input.indexOf(path) !== -1) ? `${path}${_s.strRightBack(input, path)}` : path; + } + + /** + * Resolve alias for a given input path by replacing, using the alias configuration, + * any alias expression that match the begining of the input with final path specified. + * If no matches are found, the path will remain intact. + * @public + * @param {bundle.Command} ctx current command + * @param {String} input input path to resolve + * @return {String} + **/ + aliases(ctx, input) { + return _.reduce(this.alias, (memo, replace, alias) => { + return _s.startsWith(memo, alias) ? _s.replaceAll(memo, alias, replace) : memo; + }, input, this); + } + + /** + * Resolve excludes + * @public + * @param {bundle.Command} ctx current command + * @return {Array} + **/ + excludes(ctx) { + return _.reduce(this.exclude, (memo, pattern) => { + memo.push(this.file(ctx, pattern, false)); + return memo; + }, []); + } + + /** + * Retrieve targets by using a given predicate passed by parameter + * @public + * @param {bundle.Command} ctx current command + * @param {Function} predicate - predicate to walk over the targets + * @return {Array} + **/ + targets(ctx, predicate) { + return _.defined(predicate) && _.isFunction(predicate) ? _.map(this.target, predicate, this) : []; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'PropertiesVisitor'; + } + +} + +export default Properties; diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index bff0ee6..9f48742 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -9,7 +9,7 @@ import yargs from 'yargs'; import chalk from 'chalk'; import Factory from 'util/factory/factory'; import Visitor from 'util/visitor/visitor'; -import logger from 'util/logger/logger'; +import logger, { Logger } from 'util/logger/logger'; /** * Class Commander @@ -60,7 +60,18 @@ class Commander extends Visitor { * @return {visitors.Commander} **/ onParse(err, argv, output) { - return this.emit(Commander.events.parse, err, argv, output); + return this._logger(argv.logLevel).emit(Commander.events.parse, err, argv, output); + } + + /** + * Sets Logger Level + * @public + * @param {String} lvl - Logger level + * @return {visitors.Commander} + **/ + _logger(lvl) { + logger.level(Logger.level[lvl]); + return this; } /** diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index ef46199..82366a8 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -7,7 +7,7 @@ import extend from 'extend'; import Factory from 'util/factory/factory'; import Visitor from 'util/visitor/visitor'; import QueueAsync from 'util/adt/queue-async'; -import logger, { Logger } from 'util/logger/logger'; +import logger from 'util/logger/logger'; /** * Class Configuration @@ -92,17 +92,6 @@ class Configuration extends Visitor { return this; } - /** - * Sets Logger level - * @private - * @param {String} level - logger level - * @return {visitors.Configuration} - **/ - _logger(level) { - logger.level(Logger.level[level]); - return this; - } - /** * Visit Strategy * @public @@ -146,7 +135,6 @@ class Configuration extends Visitor { if(_.defined(result.warn)) return this.onParseError(result.warn); this._source(result.source) ._target(result.target) - ._logger(result.logLevel) ._override(this.command.options); return this; } @@ -198,6 +186,7 @@ class Configuration extends Visitor { **/ static formatters = [ 'visitors/configuration/formatter/alias', + 'visitors/configuration/formatter/external', 'visitors/configuration/formatter/exclude', 'visitors/configuration/formatter/extensions', 'visitors/configuration/formatter/target' @@ -208,7 +197,18 @@ class Configuration extends Visitor { * @static * @type {Array} **/ - static cliOptions = ['scan', 'exclude', 'extensions', 'alias', 'target', 'destination', 'format']; + static cliOptions = [ + 'basePath', + 'scan', + 'exclude', + 'extensions', + 'alias', + 'external', + 'target', + 'destination', + 'format', + 'logLevel' + ]; /** * Configuration Events diff --git a/lib/visitors/configuration/formatter/external.es6 b/lib/visitors/configuration/formatter/external.es6 new file mode 100644 index 0000000..1a2c09b --- /dev/null +++ b/lib/visitors/configuration/formatter/external.es6 @@ -0,0 +1,15 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* External Formatter +* @static +* @param {String} [input = ''] - input to format +* @return {Object} +**/ +export default (input = '') => { + return (_.isString(input) && input.length > 0) ? input.split(',') : []; +}; diff --git a/lib/visitors/formatter/json.es6 b/lib/visitors/formatter/json.es6 index b6b7a90..ce477b4 100644 --- a/lib/visitors/formatter/json.es6 +++ b/lib/visitors/formatter/json.es6 @@ -32,10 +32,10 @@ class Json extends Visitor { * @return {Object} **/ _filterObject(m, v, k) { - if(_.isAdt(v)) return this._clean(v, v); - if(!_.isFunction(v)) { m[k] = v; return m; } - if(_.isArray(m)) m.splice(k, 1) - if(_.isRealObject(m)) delete m[k]; + if(_.isNative(v)) { + m[k] = v; + return m; + } return m; } diff --git a/lib/visualize/graph.es6 b/lib/visualize/graph.es6 index 22b6811..14f16db 100644 --- a/lib/visualize/graph.es6 +++ b/lib/visualize/graph.es6 @@ -16,14 +16,24 @@ class Graph extends Command { * Run * @public * @override + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject * @return {visualize.Graph} **/ - run() { - // TODO - console.log('Graph.run()...'); - return super.run(); + run(resolve, reject) { + return super.run(resolve, reject); } + /** + * List of commands that depends on + * @static + * @property dependsOn + * @type {Array} + **/ + static dependsOn = Command.dependsOn.concat([ + 'bundle/bundle' + ]); + } export default Graph; diff --git a/package.json b/package.json index 16e4d6c..fe945f7 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ }, "dependencies": { "acorn": "4.0.11", + "astq": "2.0.2", + "json5": "0.5.1", "babel-plugin-module-resolver": "2.5.0", "babel-polyfill": "6.23.0", "babel-preset-es2015": "6.22.0", @@ -57,6 +59,7 @@ "babel-register": "6.23.0", "chalk": "1.1.3", "d3": "4.7.3", + "escodegen": "1.8.1", "extend": "3.0.0", "fs-extra": "2.0.0", "glob": "7.1.1", diff --git a/test/integration/project-a/package.json b/test/integration/project-a/package.json index e51003a..d6b3033 100644 --- a/test/integration/project-a/package.json +++ b/test/integration/project-a/package.json @@ -2,7 +2,7 @@ "name": "project-a", "version": "1.0.0", "private": true, - "description": "SquareBox & Babel", + "description": "SquareBox & Babel - Integration Test Project", "keywords": [ "squarebox", "babel" diff --git a/test/integration/project-b/package.json b/test/integration/project-b/package.json index 8499597..ed53ec1 100644 --- a/test/integration/project-b/package.json +++ b/test/integration/project-b/package.json @@ -2,7 +2,7 @@ "name": "project-b", "version": "1.0.0", "private": true, - "description": "SquareBox & Typescript", + "description": "SquareBox & Typescript - Integration Test Project", "keywords": [ "squarebox", "typescript" diff --git a/test/integration/project-c/package.json b/test/integration/project-c/package.json index a50cbf6..2fdc667 100644 --- a/test/integration/project-c/package.json +++ b/test/integration/project-c/package.json @@ -2,7 +2,7 @@ "name": "project-c", "version": "1.0.0", "private": true, - "description": "SquareBox & Legacy ES5", + "description": "SquareBox & Legacy ES5 - Integration Test Project", "keywords": [ "squarebox", "legacy" diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index 9b16533..984cf78 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -4,6 +4,10 @@ **/ import SquareBox from 'bin/sqbox'; import Commander from 'visitors/commander'; +import Command from 'command'; +import Graph from 'visualize/graph'; +import Bundle from 'bundle/bundle'; +import Clean from 'clean/clean'; describe('bin.SquareBox', function() { @@ -13,13 +17,13 @@ describe('bin.SquareBox', function() { }); beforeEach(() => { - if(this.sqbox) this.mockProto = this.sandbox.mock(this.sqbox); + this.mockProto = this.sandbox.mock(SquareBox.prototype); this.mockCommander = this.sandbox.mock(Commander.prototype); this.input = [process.argv[0], this.cwd]; }); afterEach(() => { - if(this.mockProto) this.mockProto.verify(); + this.mockProto.verify(); this.mockCommander.verify(); this.sandbox.restore(); @@ -36,9 +40,20 @@ describe('bin.SquareBox', function() { describe('constructor()', () => { - it('Should get an instance', () => { - this.sqbox = require('bin/sqbox').default.new(); - assert.instanceOf(this.sqbox, SquareBox); + it('Should throw Error: Violation Error', () => { + assert.throws(() => new SquareBox, 'Private Violation'); + }); + + }); + + describe('static->run()', () => { + + it('Should get a new instance and run it (with custom cwd)', () => { + const expRun = this.mockProto.expects('run') + .once() + .returns(sinon.match.instanceOf(SquareBox)); + + SquareBox.run(__dirname); }); }); @@ -48,20 +63,39 @@ describe('bin.SquareBox', function() { it('Should run the command', () => { this.input = this.input.concat([ 'sqbox', - 'bundle', + 'graph', '--config', 'test/specs/.sqboxrc', - '--s', './source/**', - '--x', './source/dependencies/**,./source/package/**', + '--b', './source', + '--s', '**/*', + '--x', 'dependencies/**,package/**', '--e', '.js,.es6', '--a', 'common:./path/common', - '--t', 'add>umd:./dist/umd,other>cjs:./dist/cjs' + '--l', 'jquery,react', + '--t', 'add>umd:./dist/umd,other>cjs:./dist/cjs', + '--lv', 'silent' ]); - this.mockCommander.expects('_args') + const stubCommandAfter = this.sandbox.stub(Command.prototype, 'getParent', () => this.sqbox); + + const stubRun = (construct) => { + return (resolve, reject) => { + resolve({}); + return construct.prototype; + }; + }; + + const stubCleanRun = this.sandbox.stub(Clean.prototype, 'run', stubRun(Clean)); + const stubBundleRun = this.sandbox.stub(Bundle.prototype, 'run', stubRun(Bundle)); + const stubGraphRun = this.sandbox.stub(Graph.prototype, 'run', stubRun(Graph)); + + const expArgs = this.mockCommander.expects('_args') .once() .returns(this.input); - assert.instanceOf(this.sqbox.run(), SquareBox); + this.sqbox = SquareBox.run(); + assert.instanceOf(this.sqbox, SquareBox); + + Command.prototype.getParent.restore(); }); }); diff --git a/test/lib/bundle/bundle.spec.es6 b/test/lib/bundle/bundle.spec.es6 new file mode 100644 index 0000000..26afeb5 --- /dev/null +++ b/test/lib/bundle/bundle.spec.es6 @@ -0,0 +1,71 @@ +/** +* @module bundle +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Bundle from 'bundle/bundle'; + +describe('bundle.Bundle', function() { + + before(() => { + this.params = { + basePath: './test/specs/es6', + scan: '**/*', + extensions: ['js', 'es6', 'es'], + exclude: [], + alias: { dependencies: "libs/dependencies" }, + external: ['jquery'], + target: { + iife: { destination: './test/specs/dist/iife', format: 'iife' }, + umd: { destination: './test/specs/dist/umd', format: 'umd' }, + cjs: { destination: './test/specs/dist/cjs', format: 'cjs' }, + amd: { destination: './test/specs/dist/amd', format: 'amd' } + } + }; + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockInstance = this.sandbox.mock(Bundle); + }); + + afterEach(() => { + this.mockInstance.verify(); + + this.sandbox.restore(); + + delete this.mockInstance; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + this.bundle = Bundle.new(this.params); + assert.instanceOf(this.bundle, Bundle); + }); + + }); + + describe('run()', () => { + + it('Should execute command run over specs/es6', () => { + this.bundle.run(); + }); + + }); + + describe('toJSON()', () => { + + it('Should return a json representation', () => { + const exp = this.bundle.toJSON(); + assert.property(exp, 'cwd'); + assert.property(exp, 'scan'); + assert.property(exp, 'target'); + }); + + }); + +}); diff --git a/test/lib/bundle/format/amd/template.spec.es6 b/test/lib/bundle/format/amd/template.spec.es6 new file mode 100644 index 0000000..1c23b28 --- /dev/null +++ b/test/lib/bundle/format/amd/template.spec.es6 @@ -0,0 +1,33 @@ +/** +* @module bundle.format.amd +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import * as Amd from 'bundle/format/amd/template'; + +describe('bundle.format.amd.Template', function() { + + before(() => { + this.input = { + name: 'bundle', + dependencies: ['react', 'common/module'], + ids: ['React', 'Module'], + content: 'return MyModule;' + }; + }); + + after(() => { + delete this.input; + }); + + describe('template()', () => { + + it('Should output amd imports', () => { + const result = Amd.imports(this.input); + assert.include(result, `/** <${this.input.name}> **/`); + assert.include(result, `define(`); + assert.include(result, `return MyModule;`); + }); + + }); + +}); diff --git a/test/lib/bundle/format/cjs/template.spec.es6 b/test/lib/bundle/format/cjs/template.spec.es6 new file mode 100644 index 0000000..baae2e5 --- /dev/null +++ b/test/lib/bundle/format/cjs/template.spec.es6 @@ -0,0 +1,23 @@ +/** +* @module bundle.format.cjs +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +//import * as Cjs from 'bundle/format/cjs/template'; + +describe.skip('bundle.format.cjs.Template', function() { + + before(() => { + this.input = { + name: 'bundle', + files: [], + dependencies: ['react', 'common/module'], + ids: ['React', 'Module'], + content: 'return MyModule;' + }; + }); + + after(() => { + delete this.input; + }); + +}); diff --git a/test/lib/command.spec.es6 b/test/lib/command.spec.es6 index c94b1bb..ba202fe 100644 --- a/test/lib/command.spec.es6 +++ b/test/lib/command.spec.es6 @@ -11,12 +11,15 @@ describe('Command', function() { }); beforeEach(() => { - this.mockCommand = this.sandbox.mock(Command); + this.mockInstance = this.sandbox.mock(Command); }); afterEach(() => { + this.mockInstance.verify(); + this.sandbox.restore(); - delete this.mockCommand; + + delete this.mockInstance; }); after(() => { diff --git a/test/lib/util/factory/factory.spec.es6 b/test/lib/util/factory/factory.spec.es6 index 7695568..e002f32 100644 --- a/test/lib/util/factory/factory.spec.es6 +++ b/test/lib/util/factory/factory.spec.es6 @@ -3,7 +3,7 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import factory from 'util/factory/factory'; -import logger from 'util/logger/logger'; +import logger, { Logger } from 'util/logger/logger'; import Command from 'command'; import Collection from 'util/adt/collection'; @@ -91,8 +91,10 @@ describe('util.factory.Factory', function() { }); it('Should return false: resolved path doesn\'t exists (fs.statSync)', () => { + logger.level(Logger.level.output); const expLoggerOutput = this.mockLogger.expects('_stdout').atLeast(1).returns(logger); assert.isFalse(factory._validate('file-unexistent')); + logger.level(Logger.level.silent); }); }); diff --git a/test/lib/util/logger/logger.spec.es6 b/test/lib/util/logger/logger.spec.es6 index 6c71bcf..3587d20 100644 --- a/test/lib/util/logger/logger.spec.es6 +++ b/test/lib/util/logger/logger.spec.es6 @@ -36,12 +36,14 @@ describe('util.logger.Logger', function() { }); it('Should adds to the buffer by using constructor function', () => { + logger.level(Logger.level.debug); const expOut = this.mockProto.expects('_stdout') .once() .withArgs(sinon.match.string) .returns(logger); logger('hello')('world').out(logger.magenta); + logger.level(Logger.level.silent); }); }); diff --git a/test/lib/visitors/async/async.spec.es6 b/test/lib/visitors/async/async.spec.es6 index 979e671..ca6f9e7 100644 --- a/test/lib/visitors/async/async.spec.es6 +++ b/test/lib/visitors/async/async.spec.es6 @@ -43,7 +43,7 @@ describe('visitors.async.Asynchronous', function() { it('Should call promise\'s resolve as a default strategy', () => { const resolveSpy = this.sandbox.spy(); const exp = Asynchronous.new({}); - assert.instanceOf(exp.next({}, resolveSpy), Asynchronous); + assert.instanceOf(exp.next(resolveSpy), Asynchronous); assert.isTrue(resolveSpy.calledOnce); }); diff --git a/test/lib/visitors/commander.spec.es6 b/test/lib/visitors/commander.spec.es6 index 84538c9..a6e0ce4 100644 --- a/test/lib/visitors/commander.spec.es6 +++ b/test/lib/visitors/commander.spec.es6 @@ -7,6 +7,7 @@ import Factory from 'util/factory/factory'; import yargs from 'yargs'; import Command from 'command'; import chalk from 'chalk'; +import logger, { Logger } from 'util/logger/logger'; describe('visitors.Commander', function() { @@ -181,6 +182,15 @@ describe('visitors.Commander', function() { }); + describe('_logger()', () => { + + it('Should apply logger configuration option to the singleton logger', () => { + assert.instanceOf(this.commander._logger('debug'), Commander); + logger.level(Logger.level.silent); + }); + + }); + describe('onParse()', () => { it('Should execute onParse handler', () => { diff --git a/test/lib/visitors/configuration.spec.es6 b/test/lib/visitors/configuration.spec.es6 index eadaa94..cb102b7 100644 --- a/test/lib/visitors/configuration.spec.es6 +++ b/test/lib/visitors/configuration.spec.es6 @@ -107,7 +107,7 @@ describe('visitors.Configuration', function() { describe('_format()', () => { it('Should apply formatter to the current configuration option', () => { - const options = { scan: './src/**', extensions: '.js,.es6' }; + const options = { basePath: './src', scan: '**/*', extensions: '.js,.es6' }; const expTransform = ['.js', '.es6']; const formatterPath = 'visitors/configuration/formatter/extensions'; const expFormatterPath = this.mockProto.expects('formatterPath') @@ -135,7 +135,7 @@ describe('visitors.Configuration', function() { }); it('Should NOT apply formatter to the current configuration option', () => { - const options = { scan: './src/**' }; + const options = { basePath: './src', scan: '**/*' }; const formatterPath = 'visitors/configuration/formatter/scan'; const expFormatterPath = this.mockProto.expects('formatterPath') .once() @@ -161,7 +161,7 @@ describe('visitors.Configuration', function() { describe('_source()', () => { it('Should apply source configuration options to the current command', () => { - const options = { scan: './src/**', extensions: ['.js', '.es6'], unrecognized: true }; + const options = { basePath: './src', scan: '**/*', extensions: ['.js', '.es6'], unrecognized: true }; assert.instanceOf(this.configuration._source(options), Configuration); assert.property(this.command, 'scan'); @@ -187,22 +187,13 @@ describe('visitors.Configuration', function() { }); - describe('_logger()', () => { - - it('Should apply logger configuration option to the singleton logger', () => { - assert.instanceOf(this.configuration._logger('silent'), Configuration); - logger.level(Logger.level.output); - }); - - }); - describe('_override()', () => { it('Should override configuration options with cli options', () => { - const options = { scan: './src/**', extensions: ['.js', '.es6'], unrecognized: true }; + const options = { basePath: './src', scan: '**/*', extensions: ['.js', '.es6'], unrecognized: true }; const filtered = _.omit(options, 'unrecognized'); const expFormat = this.mockProto.expects('_format') - .exactly(2) + .exactly(3) .withArgs(filtered, sinon.match.any, sinon.match.string) .returns(filtered); @@ -262,7 +253,7 @@ describe('visitors.Configuration', function() { it('Should perform options transformations', () => { const expResult = { - source: { scan: './src/**' }, + source: { basePath: './src', scan: '**/*' }, target: { cjs: { destination: './dist', format: 'cjs' } }, logLevel: 'silent' }; @@ -274,10 +265,6 @@ describe('visitors.Configuration', function() { .once() .withArgs(expResult.target) .returns(this.configuration); - const expLogger = this.mockProto.expects('_logger') - .once() - .withArgs(expResult.logLevel) - .returns(this.configuration); const expOverride = this.mockProto.expects('_override') .once() .withArgs(this.command.options) @@ -295,7 +282,6 @@ describe('visitors.Configuration', function() { .returns(this.configuration); const expSource = this.mockProto.expects('_source').never(); const expTarget = this.mockProto.expects('_target').never(); - const expLogger = this.mockProto.expects('_logger').never(); const expOverride = this.mockProto.expects('_override').never(); assert.instanceOf(this.configuration.onOptions(expResult), Configuration); @@ -306,7 +292,7 @@ describe('visitors.Configuration', function() { describe('parse()', () => { it('Should create methods and resolve configuration option source', () => { - const expResult = { source: { scan: './src/**' } }; + const expResult = { source: { basePath: './src', scan: '**/*' } }; const queueStubPromise = this.sandbox.stub().returnsPromise(); const expCreate = this.mockProto.expects('_create') .exactly(2) diff --git a/test/lib/visitors/configuration/formatter/external.spec.es6 b/test/lib/visitors/configuration/formatter/external.spec.es6 new file mode 100644 index 0000000..07817c4 --- /dev/null +++ b/test/lib/visitors/configuration/formatter/external.spec.es6 @@ -0,0 +1,30 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import external from 'visitors/configuration/formatter/external'; + +describe('visitors.configuration.formatter.External', function() { + + describe('external()', () => { + + it('Should transform parameter external', () => { + const input = 'jquery,react'; + const exp = external(input); + + assert.isArray(exp); + assert.oneOf('jquery', exp); + assert.oneOf('react', exp); + }); + + it('Should NOT transform parameter external', () => { + let exp = external(); + assert.isTrue(_.isEmpty(exp)); + + exp = external({}); + assert.isTrue(_.isEmpty(exp)); + }); + + }); + +}); diff --git a/test/specs/.sqbox.js b/test/specs/.sqbox.js index d78e20d..deba292 100644 --- a/test/specs/.sqbox.js +++ b/test/specs/.sqbox.js @@ -4,18 +4,19 @@ **/ module.exports = { source: { - scan: './src/**', - exclude: ['./src/dependencies/**'], - extensions: ['.js', '.es6', '.es'], + basePath: './es6', + scan: '**/*', + exclude: ['dependencies/**'], + extensions: ['js', 'es6', 'es'], alias: { - common: 'shared/common', - libraries: 'libs' - } + dependencies: 'libs/dependencies' + }, + external: ['jquery'] }, target: { global: { destination: './dist/global', - format: 'ifie' + format: 'iife' }, umd: { destination: './dist/umd', @@ -29,6 +30,5 @@ module.exports = { destination: './dist/amd', format: 'amd' } - }, - logLevel: 'debug' + } }; diff --git a/test/specs/.sqboxrc b/test/specs/.sqboxrc index 2d6e97e..acaf617 100644 --- a/test/specs/.sqboxrc +++ b/test/specs/.sqboxrc @@ -1,17 +1,18 @@ { "source": { - "scan": "./src/**", - "exclude": ["./src/dependencies/**"], - "extensions": [".js", ".es6"], + "basePath": "./es6", + "scan": "**/*", + "exclude": ["dependencies/**"], + "extensions": ["js", "es6", "es"], "alias": { - "common": "shared/common", - "libraries": "libs" - } + "dependencies": "libs/dependencies" + }, + "external": ["jquery"] }, "target": { "global": { "destination": "./dist/global", - "format": "ifie" + "format": "iife" }, "umd": { "destination": "./dist/umd", @@ -25,6 +26,5 @@ "destination": "./dist/amd", "format": "amd" } - }, - "logLevel": "debug" + } } diff --git a/test/specs/es6/common/c-a.es6 b/test/specs/es6/common/c-a.es6 new file mode 100644 index 0000000..9714862 --- /dev/null +++ b/test/specs/es6/common/c-a.es6 @@ -0,0 +1,3 @@ +import CommonB from 'common/common-b'; + +export default () => { console.log('common/CommonA'); }; diff --git a/test/specs/es6/common/c-b.es6 b/test/specs/es6/common/c-b.es6 new file mode 100644 index 0000000..5c17099 --- /dev/null +++ b/test/specs/es6/common/c-b.es6 @@ -0,0 +1 @@ +export default () => { console.log('common/CommonB'); }; diff --git a/test/specs/es6/common/common.es6 b/test/specs/es6/common/common.es6 new file mode 100644 index 0000000..f5d5cf5 --- /dev/null +++ b/test/specs/es6/common/common.es6 @@ -0,0 +1,5 @@ +/** +* @sqbox({ name: "common" }) +**/ +import commonA from 'common/c-a'; +import commonB from 'common/c-b'; diff --git a/test/specs/es6/libs.es6 b/test/specs/es6/libs.es6 new file mode 100644 index 0000000..a880d23 --- /dev/null +++ b/test/specs/es6/libs.es6 @@ -0,0 +1,9 @@ +/** +* @sqbox({ name: "libs" }) +**/ +import CommonJsLib from 'libs/cjs-lib'; +import * as AmdLib from 'libs/amd-lib'; +// import { component } from 'libs/other'; +// import First, { A, B as C } from 'libs/combination'; +// import Second, { B as C } from 'libs/alias'; +// import 'libs/cjs-lib'; diff --git a/test/specs/es6/libs/amd-lib.js b/test/specs/es6/libs/amd-lib.js new file mode 100644 index 0000000..2984c83 --- /dev/null +++ b/test/specs/es6/libs/amd-lib.js @@ -0,0 +1,12 @@ +/** +* Simulation of a AMD Library +**/ +// define(['jquery', 'dependencies/lib-a'], function($, LibA) { +// return { type: 'Amd', jquery: $ }; +// }); + +define(['require', 'exports', 'dependencies/lib-a'], function(require, exports, LibA) { + exports.something = function() { + return require('dependencies/lib-a'); + }; +}); diff --git a/test/specs/es6/libs/cjs-lib.js b/test/specs/es6/libs/cjs-lib.js new file mode 100644 index 0000000..13b03e2 --- /dev/null +++ b/test/specs/es6/libs/cjs-lib.js @@ -0,0 +1,8 @@ +/** +* Simulation of a CommonJs Library +**/ +let $ = require('jquery'); +LibB = require('dependencies/lib-b'); +require('hello'); + +module.exports = { type: 'CommonJs', jquery: $ }; diff --git a/test/specs/es6/libs/dependencies/lib-a.js b/test/specs/es6/libs/dependencies/lib-a.js new file mode 100644 index 0000000..2fb36b8 --- /dev/null +++ b/test/specs/es6/libs/dependencies/lib-a.js @@ -0,0 +1,3 @@ +define([], function() { + console.log('Library A'); +}); diff --git a/test/specs/es6/libs/dependencies/lib-b.js b/test/specs/es6/libs/dependencies/lib-b.js new file mode 100644 index 0000000..520dddf --- /dev/null +++ b/test/specs/es6/libs/dependencies/lib-b.js @@ -0,0 +1,3 @@ +module.exports = function() { + console.log('Library B'); +}; diff --git a/test/specs/es6/module-a/s-a-1.es6 b/test/specs/es6/module-a/s-a-1.es6 new file mode 100644 index 0000000..fbd3c4d --- /dev/null +++ b/test/specs/es6/module-a/s-a-1.es6 @@ -0,0 +1,4 @@ +import CommonA from 'common/common-a'; +import CommonB from 'common/common-b'; + +export default () => { console.log('module-a/SourceA'); }; diff --git a/test/specs/es6/module-a/s-a-2.es6 b/test/specs/es6/module-a/s-a-2.es6 new file mode 100644 index 0000000..48490d5 --- /dev/null +++ b/test/specs/es6/module-a/s-a-2.es6 @@ -0,0 +1,3 @@ +import CommonB from 'common/common-b'; + +export default () => { console.log('module-a/SourceA'); }; diff --git a/test/specs/es6/module-a/source-a.es6 b/test/specs/es6/module-a/source-a.es6 new file mode 100644 index 0000000..5a0d733 --- /dev/null +++ b/test/specs/es6/module-a/source-a.es6 @@ -0,0 +1,3 @@ +// @sqbox({ name: "module-a" }) +import SourceA1 from 'module-a/s-a-1'; +import SourceA2 from 'module-a/s-a-2'; diff --git a/test/specs/es6/module-b/s-b.es6 b/test/specs/es6/module-b/s-b.es6 new file mode 100644 index 0000000..d1da304 --- /dev/null +++ b/test/specs/es6/module-b/s-b.es6 @@ -0,0 +1,3 @@ +import CommonA from 'common/common-a'; + +export default () => { console.log('module-b/SourceB'); }; diff --git a/test/specs/es6/module-b/source-b.es6 b/test/specs/es6/module-b/source-b.es6 new file mode 100644 index 0000000..5d7ffba --- /dev/null +++ b/test/specs/es6/module-b/source-b.es6 @@ -0,0 +1,4 @@ +/** +* @sqbox({ name: 'module-b' }) +**/ +import SourceB from 'module-b/s-b'; diff --git a/test/specs/schema.md b/test/specs/schema.md new file mode 100644 index 0000000..d26602c --- /dev/null +++ b/test/specs/schema.md @@ -0,0 +1,68 @@ +### Complex Scheme of use cases + +@sqbox({ name: 'libs' }) +file: libs + - requires lib-a + - requires lib-b + +---- +result: ['lib-a', 'lib-b'] +---- + +@sqbox({ name: 'common' }) +file: common-a + - requires common-b + +@sqbox({ name: 'common' }) +file: common-b + - requires lib-a + +@sqbox({ name: 'common' }) +file: common-c + - no requires + +---- +result: ['common-a', 'common-b', 'common-c', (a) => 'libs'] +---- + +@sqbox({ name: 'module-b', boxed: ['common'] }) +file: source-b - doesn't need common-b + - requires lib-b + - requires common-a + +---- +result: ['source-b', 'common-a', (a) => 'libs'] +---- + +@sqbox({ name: 'module-a' }) +file: source-a-1 - doesn't need common-c + - requires lib-a + - requires common-a + - requires common-b + +@sqbox({ name: 'module-a', boxed: ['common'] }) +file: source-a-2 -> doesn't need common-a, common-b + - requires lib-b + - requires common-c + - requires source-a-1 + +---- +result: ['source-a-1', 'source-a-2', 'common-a', 'common-b', 'common-c', (a) => 'libs'] +---- + +#### Common Pipelines + +source - compile - optimization + +source (es6 - import/export) -> Es5 -> sqbox + +- use of transformation via babel-plugin-transform-amd +- use of transformation via babel-plugin-transform-cjs + +source (typescript - import/export | referenceId) -> Es5 -> sqbox + +- use transformation from typescript + +source (es5 | require | define) -> sqbox + +- No Transformation