diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..d3d29d3 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["babel-plugin-add-module-exports"], + "presets": ["es2015"] +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..650fa8e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,56 @@ +{ + "parser": "babel-eslint", + "extends": "airbnb", + "env": { + "browser": true, + "es6": true, + "node": true + }, + "ecmaFeatures": { + "jsx": true, + "classes": true, + "modules": true + }, + "parserOptions": { + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + 1, + 4 + ], + "linebreak-style": [ + 2, + "windows" + ], + "quotes": [ + 2, + "single" + ], + "semi": [ + 2, + "always" + ], + "strict": [ + 1, + "function" + ], + "no-console": 0, + "no-unused-vars": [ + 1 + ] + }, + "globals" : { + "document": false, + "escape": false, + "navigator": false, + "unescape": false, + "window": false, + "describe": true, + "before": true, + "it": true, + "expect": true, + "sinon": true + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57d55d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +npm-debug.log + +\.idea/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..6e92ac0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +src +test +webpack.config.js \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..757480b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - 7 + - 6 + - "0.10" + - "0.12" + - "iojs" +env: + - TEST_SUITE=unit +script: + - npm run build + - npm run $TEST_SUITE diff --git a/README.md b/README.md index 2fbe759..0798975 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# NestedObjectAssign \ No newline at end of file +# NestedObjectAssign +This package extends the functionality given by Object.assign() to also include the values of nested objects. + +## Usage +Add the empty object first, then any objects you want merged into it after. unlimited amount of params. + +Example: `nestedObjectAssign({}, defaults, object1, object2, object3)` + +## Tests +Tests are done using mocha. to run tests, simply type `npm run tests`. \ No newline at end of file diff --git a/dist/nestedObjectAssign.web.js b/dist/nestedObjectAssign.web.js new file mode 100644 index 0000000..743f5dc --- /dev/null +++ b/dist/nestedObjectAssign.web.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("nestedObjectAssign",[],t):"object"==typeof exports?exports.nestedObjectAssign=t():e.nestedObjectAssign=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return e[o].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e){for(var t=arguments.length,n=Array(t>1?t-1:0),f=1;f { + gulp.watch([sources], ['webpack:build-node-dev']) +}); + +// Build for web +gulp.task('build-web', ['webpack:build-web']); + +// Build for web + watch +gulp.task('build-web-dev', ['webpack:build-web-dev'], () => { + gulp.watch([sources], ['webpack:build-web-dev']) +}); + +// Run Babel only +gulp.task('build-babel', ['clean', 'lint'], () => + gulp.src([sources]) + .pipe($.babel()) + // Output files + .pipe(gulp.dest(libFolder)) +); + +// Lint javascript +gulp.task('lint', () => + gulp.src(sources) + .pipe($.eslint()) + .pipe($.eslint.format()) + .pipe($.eslint.failOnError()) +); + +// Clean folder +gulp.task('clean', () => + del([`${libFolder}/**/*`]) +); + +// Webpack helper +gulp.task('webpack:build-web', done => { + var env = {'BUILD_ENV':'PROD', 'TARGET_ENV': 'WEB'}; + var taskName = 'webpack:build-web'; + // run webpack + webpack(webpackConfig(env), onBuild(done, taskName)); +}); + +// Webpack watch helper +// create a single instance of the compiler to allow caching +var webDevCompiler = null; +gulp.task('webpack:build-web-dev', done => { + var env = {'BUILD_ENV':'DEV', 'TARGET_ENV': 'WEB'}; + var taskName = 'webpack:build-web-dev'; + // build dev compiler + if(!webDevCompiler){ + webDevCompiler = webpack(webpackConfig(env)); + } + // run webpack + webDevCompiler.run(onBuild(done, taskName)); +}); + +// Webpack helper +gulp.task('webpack:build-node', done => { + var env = {'BUILD_ENV':'PROD', 'TARGET_ENV': 'NODE'}; + var taskName = 'webpack:build-node'; + // run webpack + webpack(webpackConfig(env), onBuild(done, taskName)); +}); + +// Webpack watch helper +// create a single instance of the compiler to allow caching +var nodeDevCompiler = null; +gulp.task('webpack:build-node-dev', done => { + var env = {'BUILD_ENV':'DEV', 'TARGET_ENV': 'NODE'}; + var taskName = 'webpack:build-node-dev'; + // build dev compiler + if(!nodeDevCompiler){ + nodeDevCompiler = webpack(webpackConfig(env)); + } + // run webpack + nodeDevCompiler.run(onBuild(done, taskName)); +}); + +function onBuild(done, taskName){ + return (err, stats) => { + if(err) + throw new gutil.PluginError(taskName, err); + $.util.log(`${taskName}`, stats.toString({colors: true})); + done && done(); + } +} + +// Sets environment variable +function setEnv(buildEnv){ + $.env({ + vars: { + BUILD_ENV: buildEnv + } + }); +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..6572211 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +var pkg = require('./package.json'); +var libFile = pkg.library['bundle-node'] ? pkg.library['dist-node'] : pkg.library['entry']; +module.exports = require('./lib/' + libFile); \ No newline at end of file diff --git a/lib/nestedObjectAssign.js b/lib/nestedObjectAssign.js new file mode 100644 index 0000000..30f9cff --- /dev/null +++ b/lib/nestedObjectAssign.js @@ -0,0 +1,57 @@ +module.exports = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + eval("'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = nestedObjectAssign;\n\nvar _isObject = __webpack_require__(1);\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction nestedObjectAssign(target) {\n for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n sources[_key - 1] = arguments[_key];\n }\n\n if (!sources.length) return target;\n\n var source = sources.shift();\n\n if ((0, _isObject.isObject)(target) && (0, _isObject.isObject)(source)) {\n for (var key in source) {\n if ((0, _isObject.isObject)(source[key])) {\n if (!target[key]) Object.assign(target, _defineProperty({}, key, {}));\n\n nestedObjectAssign(target[key], source[key]);\n } else {\n Object.assign(target, _defineProperty({}, key, source[key]));\n }\n }\n }\n\n return nestedObjectAssign.apply(undefined, [target].concat(sources));\n}\nmodule.exports = exports['default'];\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvbmVzdGVkT2JqZWN0QXNzaWduLmpzP2ZmYWMiXSwibmFtZXMiOlsibmVzdGVkT2JqZWN0QXNzaWduIiwidGFyZ2V0Iiwic291cmNlcyIsImxlbmd0aCIsInNvdXJjZSIsInNoaWZ0Iiwia2V5IiwiT2JqZWN0IiwiYXNzaWduIl0sIm1hcHBpbmdzIjoiOzs7OztrQkFFd0JBLGtCOztBQUZ4Qjs7OztBQUVlLFNBQVNBLGtCQUFULENBQTRCQyxNQUE1QixFQUErQztBQUFBLHNDQUFSQyxPQUFRO0FBQVJBLGVBQVE7QUFBQTs7QUFDMUQsUUFBSSxDQUFDQSxRQUFRQyxNQUFiLEVBQ0ksT0FBT0YsTUFBUDs7QUFFSixRQUFNRyxTQUFTRixRQUFRRyxLQUFSLEVBQWY7O0FBRUEsUUFBSSx3QkFBU0osTUFBVCxLQUFvQix3QkFBU0csTUFBVCxDQUF4QixFQUF5QztBQUNyQyxhQUFLLElBQU1FLEdBQVgsSUFBa0JGLE1BQWxCLEVBQXlCO0FBQ3JCLGdCQUFJLHdCQUFTQSxPQUFPRSxHQUFQLENBQVQsQ0FBSixFQUEwQjtBQUN0QixvQkFBSSxDQUFDTCxPQUFPSyxHQUFQLENBQUwsRUFDSUMsT0FBT0MsTUFBUCxDQUFjUCxNQUFkLHNCQUF3QkssR0FBeEIsRUFBOEIsRUFBOUI7O0FBRUpOLG1DQUFtQkMsT0FBT0ssR0FBUCxDQUFuQixFQUFnQ0YsT0FBT0UsR0FBUCxDQUFoQztBQUNILGFBTEQsTUFNSztBQUNEQyx1QkFBT0MsTUFBUCxDQUFjUCxNQUFkLHNCQUF3QkssR0FBeEIsRUFBOEJGLE9BQU9FLEdBQVAsQ0FBOUI7QUFDSDtBQUNKO0FBQ0o7O0FBRUQsV0FBT04scUNBQW1CQyxNQUFuQixTQUE4QkMsT0FBOUIsRUFBUDtBQUNIIiwiZmlsZSI6IjAuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge2lzT2JqZWN0fSBmcm9tICcuL2lzT2JqZWN0JztcclxuXHJcbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIG5lc3RlZE9iamVjdEFzc2lnbih0YXJnZXQsIC4uLnNvdXJjZXMpe1xyXG4gICAgaWYgKCFzb3VyY2VzLmxlbmd0aClcclxuICAgICAgICByZXR1cm4gdGFyZ2V0O1xyXG5cclxuICAgIGNvbnN0IHNvdXJjZSA9IHNvdXJjZXMuc2hpZnQoKTtcclxuXHJcbiAgICBpZiAoaXNPYmplY3QodGFyZ2V0KSAmJiBpc09iamVjdChzb3VyY2UpKXtcclxuICAgICAgICBmb3IgKGNvbnN0IGtleSBpbiBzb3VyY2Upe1xyXG4gICAgICAgICAgICBpZiAoaXNPYmplY3Qoc291cmNlW2tleV0pKXtcclxuICAgICAgICAgICAgICAgIGlmICghdGFyZ2V0W2tleV0pXHJcbiAgICAgICAgICAgICAgICAgICAgT2JqZWN0LmFzc2lnbih0YXJnZXQsIHtba2V5XToge319KTtcclxuXHJcbiAgICAgICAgICAgICAgICBuZXN0ZWRPYmplY3RBc3NpZ24odGFyZ2V0W2tleV0sIHNvdXJjZVtrZXldKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBlbHNlIHtcclxuICAgICAgICAgICAgICAgIE9iamVjdC5hc3NpZ24odGFyZ2V0LCB7W2tleV06IHNvdXJjZVtrZXldfSk7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIG5lc3RlZE9iamVjdEFzc2lnbih0YXJnZXQsIC4uLnNvdXJjZXMpO1xyXG59XG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL25lc3RlZE9iamVjdEFzc2lnbi5qcyJdLCJzb3VyY2VSb290IjoiIn0="); + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + eval("'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nexports.isObject = isObject;\nfunction isObject(item) {\n return item && (typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object' && !Array.isArray(item);\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaXNPYmplY3QuanM/YzBhOCJdLCJuYW1lcyI6WyJpc09iamVjdCIsIml0ZW0iLCJBcnJheSIsImlzQXJyYXkiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O1FBQWdCQSxRLEdBQUFBLFE7QUFBVCxTQUFTQSxRQUFULENBQWtCQyxJQUFsQixFQUF1QjtBQUMxQixXQUFRQSxRQUFRLFFBQU9BLElBQVAseUNBQU9BLElBQVAsT0FBZ0IsUUFBeEIsSUFBb0MsQ0FBQ0MsTUFBTUMsT0FBTixDQUFjRixJQUFkLENBQTdDO0FBQ0giLCJmaWxlIjoiMS5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBmdW5jdGlvbiBpc09iamVjdChpdGVtKXtcclxuICAgIHJldHVybiAoaXRlbSAmJiB0eXBlb2YgaXRlbSA9PT0gJ29iamVjdCcgJiYgIUFycmF5LmlzQXJyYXkoaXRlbSkpO1xyXG59XG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL2lzT2JqZWN0LmpzIl0sInNvdXJjZVJvb3QiOiIifQ=="); + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..907afc7 --- /dev/null +++ b/package.json @@ -0,0 +1,79 @@ +{ + "name": "nestedObjectAssign", + "version": "1.0.3", + "description": "Package to support nested merging of objects & properties, using Object.Assign", + "main": "./index.js", + "scripts": { + "prepublish": "npm run build-all", + "preversion": "npm run build-all && npm run unit", + "version": "git add .", + "postversion": "git push && git push --tags", + "build": "gulp build", + "build-dev": "gulp build-dev", + "build-web": "gulp build-web", + "build-web-dev": "gulp build-web-dev", + "build-all": "gulp", + "unit": "mocha --compilers js:babel-core/register --colors ./test/*.spec.js", + "unit-watch": "mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js", + "test": "npm run unit-watch" + }, + "repository": { + "type": "git", + "url": "https://github.com/Geta/NestedObjectAssign.git" + }, + "keywords": [ + "es6", + "npm", + "nested", + "object", + "assign" + ], + "author": { + "name": "Geta AS / Eirik Horvath", + "url": "https://github.com/Geta" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/Geta/NestedObjectAssign/issues" + }, + "files": [ + "README.md", + "index.js", + "lib", + "dist" + ], + "homepage": "https://github.com/Geta/NestedObjectAssignl", + "devDependencies": { + "babel": "^6.3.26", + "babel-core": "^6.4.0", + "babel-eslint": "^5.0.0-beta6", + "babel-loader": "^6.2.1", + "babel-plugin-add-module-exports": "^0.1.2", + "babel-preset-es2015": "^6.3.13", + "chai": "^3.4.1", + "clean-webpack-plugin": "^0.1.8", + "del": "^2.2.0", + "eslint": "^1.10.3", + "eslint-config-airbnb": "^4.0.0", + "eslint-loader": "^1.2.0", + "eslint-plugin-react": "^3.16.1", + "eslint-plugin-standard": "^1.3.1", + "gulp": "^3.9.0", + "gulp-babel": "^6.1.1", + "gulp-env": "^0.2.0", + "gulp-eslint": "^1.1.1", + "gulp-load-plugins": "^1.2.0", + "gulp-util": "^3.0.6", + "mocha": "^2.3.4", + "object-assign": "^4.0.1", + "webpack": "^1.12.11", + "webpack-node-externals": "^0.4.1" + }, + "library": { + "name": "nestedObjectAssign", + "entry": "nestedObjectAssign.js", + "dist-node": "nestedObjectAssign.js", + "dist-web": "nestedObjectAssign.web.js", + "bundle-node": true + } +} diff --git a/src/isObject.js b/src/isObject.js new file mode 100644 index 0000000..caf0150 --- /dev/null +++ b/src/isObject.js @@ -0,0 +1,3 @@ +export function isObject(item){ + return (item && typeof item === 'object' && !Array.isArray(item)); +} \ No newline at end of file diff --git a/src/nestedObjectAssign.js b/src/nestedObjectAssign.js new file mode 100644 index 0000000..e3986d0 --- /dev/null +++ b/src/nestedObjectAssign.js @@ -0,0 +1,24 @@ +import {isObject} from './isObject'; + +export default function nestedObjectAssign(target, ...sources){ + if (!sources.length) + return target; + + const source = sources.shift(); + + if (isObject(target) && isObject(source)){ + for (const key in source){ + if (isObject(source[key])){ + if (!target[key]) + Object.assign(target, {[key]: {}}); + + nestedObjectAssign(target[key], source[key]); + } + else { + Object.assign(target, {[key]: source[key]}); + } + } + } + + return nestedObjectAssign(target, ...sources); +} \ No newline at end of file diff --git a/test/nestedObjectAssign.spec.js b/test/nestedObjectAssign.spec.js new file mode 100644 index 0000000..7347b58 --- /dev/null +++ b/test/nestedObjectAssign.spec.js @@ -0,0 +1,43 @@ +import { expect } from 'chai'; +import nestedObjectAssign from '../index.js'; + +var mockData = { + default: { + heading: 'title', + body: { + paragraph: 'p', + heading: 'h1' + } + }, + first: { + body: { + span: 'span', + header: 'header' + } + }, + second: { + body: { + heading2: 'h2' + } + } + +}; + +var expectedData = { + heading: 'title', + body: { + paragraph: 'p', + heading: 'h1', + span: 'span', + header: 'header', + heading2: 'h2' + } +}; + +describe('Given an instance of nestedObjectAssign', function() { + describe('when i merge the mockData', function() { + it('it should be equal to expectedData', () => { + expect(JSON.stringify(nestedObjectAssign({}, mockData.default, mockData.first, mockData.second))).to.be.equal(JSON.stringify(expectedData)); + }); + }); +}); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..e174234 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,113 @@ +var webpack = require('webpack'); +var path = require('path'); +var assign = require('object-assign'); +var nodeExternals = require('webpack-node-externals'); +var CleanWebpackPlugin = require('clean-webpack-plugin'); +var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; + +module.exports = function getConfig(options){ + + var options = options || {}; + + var isProd = (options.BUILD_ENV || process.env.BUILD_ENV) === 'PROD'; + var isWeb = (options.TARGET_ENV || process.env.TARGET_ENV) === 'WEB'; + + // get library details from JSON config + var libraryDesc = require('./package.json').library; + var libraryName = libraryDesc.name; + + // determine output file name + var outputName = buildLibraryOutputName(libraryDesc, isWeb, isProd); + var outputFolder = isWeb ? 'dist' : 'lib'; + + // get base config + var config; + + // for the web + if(isWeb){ + config = assign(getBaseConfig(isProd), { + output: { + path: path.join(__dirname, outputFolder), + filename: outputName, + library: libraryName, + libraryTarget: 'umd', + umdNamedDefine: true + } + }); + } + + // for the backend + else { + config = assign(getBaseConfig(isProd), { + output: { + path: path.join(__dirname, outputFolder), + filename: outputName, + library: libraryName, + libraryTarget: 'commonjs2' + }, + target: 'node', + node: { + __dirname: true, + __filename: true + }, + externals: [nodeExternals()] + }); + } + + config.plugins.push(new CleanWebpackPlugin([outputFolder])); + + return config; +} + +/** + * Build base config + * @param {Boolean} isProd [description] + * @return {[type]} [description] + */ +function getBaseConfig(isProd) { + + // get library details from JSON config + var libraryDesc = require('./package.json').library; + var libraryEntryPoint = path.join('src', libraryDesc.entry); + + // generate webpack base config + return { + entry: path.join(__dirname, libraryEntryPoint), + output: { + // ommitted - will be filled according to target env + }, + module: { + preLoaders: [ + {test: /\.js$/, exclude: /(node_modules|bower_components)/, loader: "eslint-loader"} + ], + loaders: [ + {test: /\.js$/, exclude: /(node_modules|bower_components)/, loader: "babel-loader"}, + ] + }, + eslint: { + configFile: './.eslintrc' + }, + resolve: { + root: path.resolve('./src'), + extensions: ['', '.js'] + }, + devtool: isProd ? null : '#eval-source-map', + debug: !isProd, + plugins: isProd ? [ + new webpack.DefinePlugin({'process.env': {'NODE_ENV': '"production"'}}), + new UglifyJsPlugin({ minimize: true }) + // Prod plugins here + ] : [ + new webpack.DefinePlugin({'process.env': {'NODE_ENV': '"development"'}}) + // Dev plugins here + ] + }; +} + +function buildLibraryOutputName(libraryDesc, isWeb, isProd){ + if(isWeb){ + return libraryDesc["dist-web"] || [libraryDesc.name, 'web', (isProd ? 'min.js' : 'js')].join('.'); + } else { + return libraryDesc["dist-node"] || [libraryDesc.name, (isProd ? 'min.js' : 'js')].join('.'); + } +} \ No newline at end of file