├── .gitignore ├── lib ├── ignore.js ├── decode.js ├── application-classes.js ├── encode.js └── cycle.js ├── .jshintrc ├── package.json ├── LICENSE ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /test/*temp 4 | /npm-debug.log 5 | /documentation -------------------------------------------------------------------------------- /lib/ignore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal warning ignore list. 3 | */ 4 | module.exports = [ 5 | 6 | // in built buffer class 7 | /^unknown-custom-class .*\:Buffer$/, 8 | 9 | // esprima defines several value objects it does not export 10 | /^unknown-custom-class .*\:WrappingSourceLocation$/, 11 | /^unknown-custom-class .*\:Position$/, 12 | /^unknown-custom-class .*\:SourceLocation/ 13 | ]; -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": false, 7 | "freeze": false, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonbsp": true, 15 | "nonew": true, 16 | "plusplus": false, 17 | "quotmark": "single", 18 | "undef": true, 19 | "unused": true, 20 | "strict": true, 21 | "maxparams": 20, 22 | "maxdepth": 5, 23 | "maxlen": 120, 24 | "scripturl": true, 25 | "node": true, 26 | "jasmine": true 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "persistent-cache-webpack-plugin", 3 | "version": "0.0.1", 4 | "description": "Webpack plugin that persists the compiler cache to the file system", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/bholloway/persistent-cache-webpack-plugin.git" 9 | }, 10 | "keywords": [ 11 | "webpack", 12 | "plugin", 13 | "cache", 14 | "module", 15 | "speed", 16 | "compile", 17 | "persist" 18 | ], 19 | "author": "bholloway", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/bholloway/persistent-cache-webpack-plugin/issues" 23 | }, 24 | "homepage": "https://github.com/bholloway/persistent-cache-webpack-plugin", 25 | "dependencies": { 26 | "cycle": "^1.0.3", 27 | "is-plain-object": "^2.0.1", 28 | "lodash.assign": "^3.2.0", 29 | "lodash.defaults": "^3.1.2", 30 | "require-dir": "^0.3.0" 31 | }, 32 | "peerDependencies": { 33 | "webpack": "^1.12.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/decode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assign = require('lodash.assign'); 4 | 5 | var classes = require('./application-classes'); 6 | 7 | /** 8 | * Decode the given acyclic object, instantiating using any embedded class information. 9 | * @param {object} object The object to decode 10 | * @returns {object} An acyclic object with possibly typed members 11 | */ 12 | function decode(object) { 13 | var result = {}; 14 | 15 | // enumerable properties 16 | for (var key in object) { 17 | var value = object[key]; 18 | 19 | // nested object 20 | if (value && (typeof value === 'object')) { 21 | 22 | // instance 23 | if (value.$class && value.$props) { 24 | result[value] = assign(new classes.getDefinition(value.$class), decode(value.$props)); 25 | } 26 | // plain object 27 | else { 28 | result[value] = decode(value); 29 | } 30 | } 31 | // other 32 | else { 33 | result[value] = value; 34 | } 35 | } 36 | 37 | // complete 38 | return result; 39 | } 40 | 41 | module.exports = decode; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ben Holloway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Persistent Cache Webpack Plugin 2 | 3 | [![NPM](https://nodei.co/npm/persistent-cache-webpack-plugin.png)](http://github.com/bholloway/persistent-cache-webpack-plugin) 4 | 5 | Webpack plugin that persists the compiler cache to the file system 6 | 7 | **IMPORTANT - this plugin is an experiment and may be withdrawn at any time** 8 | 9 | ## Rationale 10 | 11 | Thanks to the Webpack compiler cache, incremental compile can be orders of magnitude faster than the initial compilation the precedes it. 12 | 13 | This plugin persists the compiler cache to the file system in order to also reduce the initial compile time. 14 | 15 | ## Limitations 16 | 17 | This plugin is experimental and you should be aware of the following. 18 | 19 | * Implementation is brittle and its effectiveness is not yet clear. 20 | * The plugin may not be able to serialise all of your cache content, it will warn if it cannot. 21 | * The cache may be large, possibly an **orders of magnitude larger than your project**. 22 | * The file which persists the cache can grow with use and may need **periodic deletion**. 23 | 24 | ## Usage 25 | 26 | The default options will result in silent operation unless the cache cannot be written. 27 | 28 | ```javascript 29 | var PersistentCacheWebpackPlugin = require('persistent-cache-webpack-plugin'); 30 | { 31 | plugins : [ 32 | new PersistentCacheWebpackPlugin({ 33 | file : './webpack.cache.json', 34 | warn : true, 35 | stats : false, 36 | persist: true, 37 | ignore : [] 38 | }) 39 | ] 40 | } 41 | ``` 42 | 43 | ### Options 44 | 45 | * `file` is the path to a file which will persist the cache. 46 | 47 | * `warn` enables feedback on cache properties that failed the serialisation process, use `warn:'verbose'` for extended detail. 48 | 49 | * `stats` enables feedback on the performance of the plugin. 50 | 51 | * `persist` enables serialisation of the cache to disk. Existing cache is utilised either way. 52 | 53 | * `ignore` an Array of RegExp that allows certain warnings to occur without failing serialisation. -------------------------------------------------------------------------------- /lib/application-classes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isPlainObject = require('is-plain-object'); 4 | 5 | module.exports = { 6 | getName : getName, 7 | getDefinition: getDefinition 8 | }; 9 | 10 | /** 11 | * Find the class name of the given instance. 12 | * @param {object} candidate A possible instance to name 13 | * @returns {string} The absolute path to the candidate definition where it is an instance or null otherwise 14 | */ 15 | function getName(candidate) { 16 | var proto = getProto(candidate), 17 | isValid = !!proto && !isPlainObject(candidate) && !Array.isArray(candidate); 18 | if (isValid) { 19 | for (var key in require.cache) { 20 | var exports = require.cache[key].exports, 21 | result = isPlainObject(exports) ? Object.keys(exports).reduce(test.bind(null, key), null) : test(key); 22 | if (result) { 23 | return result; 24 | } 25 | } 26 | } 27 | return null; 28 | 29 | function test(filename, result, field) { 30 | if (result) { 31 | return result; 32 | } else { 33 | var candidate = field ? exports[field] : exports, 34 | qualified = [filename, field].filter(Boolean).join('::'), 35 | isDefinition = (typeof candidate === 'function') && !!candidate.prototype && 36 | (typeof candidate.prototype === 'object') && (candidate.prototype === proto); 37 | return isDefinition && qualified || null; 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Get the class definition by explicit path where already in the require cache. 44 | * @param {string} name The absolute path to the file containing the class 45 | * @returns {null} 46 | */ 47 | function getDefinition(name) { 48 | var split = name.split('::'), 49 | path = split[0], 50 | field = split[1], 51 | exported = (path in require.cache) && require.cache[path].exports, 52 | definition = !!exported && (field ? exported[field] : exported); 53 | return !!definition && !!getProto(definition) && definition || null; 54 | } 55 | 56 | /** 57 | * Retrieve the prototype of the candidate, where appropriate. 58 | * @param {*} candidate A possible instance 59 | * @returns {Object} The prototype of the candidate where it is an instance or null otherwise 60 | */ 61 | function getProto(candidate) { 62 | return !!candidate && (typeof candidate === 'object') && Object.getPrototypeOf(candidate) || null; 63 | } -------------------------------------------------------------------------------- /lib/encode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isPlainObject = require('is-plain-object'); 4 | 5 | var classes = require('./application-classes'); 6 | 7 | /** 8 | * Encode the given acyclic object with class information. 9 | * @param {object} object The object to encode 10 | * @param {Array.} [path] Optional path information for the object where it is nested in another object 11 | * @param {Array.} [exclusions] Optional object list to detect circular references 12 | * @returns {object} An acyclic object with additional encoded inforation 13 | */ 14 | function encode(object, path, exclusions) { 15 | var failed = [], 16 | result = {}; 17 | 18 | // ensure valid path and exclusions 19 | path = path || []; 20 | exclusions = exclusions || []; 21 | 22 | // enumerable properties 23 | for (var key in object) { 24 | var value = object[key]; 25 | 26 | // objects 27 | if (value && (typeof value === 'object') && (exclusions.indexOf(value) < 0)) { 28 | var className = classes.getName(value), 29 | analysis = /function ([^\(]+)\(/.exec(Function.prototype.toString.call(value.constructor)), 30 | qualifiedName = ((key.length < 60) ? key : key.slice(0, 30) + '...' + key.slice(-30)) + 31 | ':' + 32 | (className ? className.split(/[\\\/]/).pop().split('.').shift() : 33 | (analysis && analysis[1] || Object.prototype.toString.apply(value).slice(8, -1))), 34 | propPath = path.concat(qualifiedName); 35 | 36 | // add to exclusions before recursing 37 | exclusions.push(value); 38 | 39 | // depth first 40 | var recursed = encode(value, propPath, exclusions); 41 | 42 | // propagate failures but don't keep them in the tree 43 | if (recursed.$failed) { 44 | failed.push.apply(failed, recursed.$failed); 45 | delete recursed.$failed; 46 | } 47 | 48 | // exclude deleted fields 49 | if (recursed.$deleted) { 50 | failed.push(['read-only-prop ' + qualifiedName + '.' + recursed.$deleted] 51 | .concat(propPath) 52 | .concat(recursed.$deleted)); 53 | } 54 | // encode recognised class 55 | else if (className) { 56 | result[key] = { 57 | $class: className, 58 | $props: recursed 59 | }; 60 | } 61 | // include otherwise 62 | else { 63 | 64 | // unrecognised custom class 65 | if (!isPlainObject(value) && !Array.isArray(value)) { 66 | failed.push(['unknown-custom-class ' + qualifiedName].concat(propPath)); 67 | } 68 | 69 | // default to plain object 70 | result[key] = recursed; 71 | } 72 | } 73 | // include non-objects 74 | else { 75 | result[key] = value; 76 | } 77 | } 78 | 79 | // mark failures 80 | if (failed.length) { 81 | result.$failed = failed; 82 | } 83 | 84 | // complete 85 | return result; 86 | } 87 | 88 | module.exports = encode; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'), 5 | defaults = require('lodash.defaults'), 6 | assign = require('lodash.assign'); 7 | 8 | var IGNORE = require('./lib/ignore'), 9 | cycle = require('./lib/cycle'), 10 | encode = require('./lib/encode'), 11 | decode = require('./lib/decode'); 12 | 13 | function PersistentCacheWebpackPlugin(options) { 14 | this.options = defaults(options || {}, { 15 | webpack: null, 16 | file : './webpack.cache.json', 17 | warn : true, 18 | stats : false, 19 | persist: true, 20 | ignore : [] 21 | }); 22 | } 23 | module.exports = PersistentCacheWebpackPlugin; 24 | 25 | PersistentCacheWebpackPlugin.prototype.apply = function apply(compiler) { 26 | var options = this.options, 27 | stats = { 28 | deserialise: { 29 | fs : {}, 30 | decode: {}, 31 | retrocycle: {} 32 | }, 33 | serialise : { 34 | encode : {}, 35 | decycle: {}, 36 | fs : {} 37 | } 38 | }, 39 | pending; 40 | 41 | compiler.plugin('watch-run', onInit); 42 | compiler.plugin('run', onInit); 43 | compiler.plugin('compilation', onCompilation); 44 | compiler.plugin('after-emit', afterEmit); 45 | 46 | /** 47 | * Deserialise any existing file into pending cache elements. 48 | */ 49 | function onInit(unused, callback) { 50 | var filePath = path.resolve(options.file); 51 | 52 | stats.deserialise.fs.start = Date.now(); 53 | if (fs.existsSync(filePath)) { 54 | fs.readFile(filePath, complete); 55 | } else { 56 | complete(true); 57 | } 58 | 59 | function complete(error, contents) { 60 | stats.deserialise.fs.stop = Date.now(); 61 | 62 | stats.deserialise.decode.start = Date.now(); 63 | pending = !error && contents && cycle.retrocycle(decode(JSON.parse(contents))); 64 | stats.deserialise.decode.stop = Date.now(); 65 | 66 | stats.deserialise.success = !error; 67 | callback(); 68 | } 69 | } 70 | 71 | /** 72 | * Apply the cache items as defaults. 73 | */ 74 | function onCompilation(compilation) { 75 | assign(compilation.cache, pending); 76 | } 77 | 78 | /** 79 | * Serialise the cache to file, don't wait for async. 80 | */ 81 | function afterEmit(compilation, callback) { 82 | var failures; 83 | if (options.persist) { 84 | var cache = compilation.cache, 85 | filePath = path.resolve(options.file); 86 | 87 | stats.serialise.encode.start = Date.now(); 88 | var encoded = encode(cache); 89 | failures = (encoded.$failed || []) 90 | .filter(filterIgnored); 91 | delete encoded.$failed; 92 | stats.serialise.encode.stop = Date.now(); 93 | 94 | stats.failures = failures.length; 95 | 96 | // abort 97 | if (failures.length) { 98 | stats.serialise.fs.start = Date.now(); 99 | fs.unlink(filePath, complete.bind(null, true)); 100 | } 101 | // serialise and write file 102 | else { 103 | stats.serialise.decycle.start = Date.now(); 104 | var decycled = cycle.decycle(encoded); 105 | stats.serialise.decycle.stop = Date.now(); 106 | 107 | stats.serialise.fs.start = Date.now(); 108 | var buffer = new Buffer(JSON.stringify(decycled, null, 2)); 109 | stats.serialise.size = buffer.length; 110 | fs.writeFile(filePath, buffer, complete); 111 | } 112 | } 113 | else { 114 | failures = []; 115 | complete(); 116 | } 117 | 118 | function complete(error) { 119 | stats.serialise.fs.stop = Date.now(); 120 | stats.serialise.success = !error; 121 | options.warn && pushFailures(failures, compilation.warnings, (options.warn === 'verbose')); 122 | options.stats && printStats(stats); 123 | callback(); 124 | } 125 | } 126 | 127 | function filterIgnored(value) { 128 | return !IGNORE.concat(options.ignore).some(testRegex); 129 | 130 | function testRegex(regex) { 131 | return regex.test(value); 132 | } 133 | } 134 | }; 135 | 136 | function pushFailures(failures, array, isVerbose) { 137 | if (failures.length) { 138 | var text = ['persistent-cache-webpack-plugin: failed to serialise the compiler cache'] 139 | .concat(failures.map(eachFailure).filter(firstOccurance)) 140 | .join('\n'); 141 | array.push(text); 142 | } 143 | 144 | function eachFailure(value) { 145 | return isVerbose ? value.map(addIndent).join('\n') : addIndent(value[0], 0); 146 | } 147 | 148 | function firstOccurance(value, i, array) { 149 | return (array.indexOf(value) === i); 150 | } 151 | 152 | function addIndent(value, i) { 153 | return (new Array(4 + i * 2)).join(' ') + value; 154 | } 155 | } 156 | 157 | function printStats(stats) { 158 | var text = [ 159 | 'persistent-cache-webpack-plugin: statistics', 160 | ' deserialise:', 161 | ' success: ' + stats.deserialise.success, 162 | ' size : ' + formatFloat(stats.deserialise.size / 1E+6) + ' MB', 163 | ' time : ' + getTime(stats.deserialise), 164 | ' serialise:', 165 | ' success: ' + stats.serialise.success + ', ' + stats.failures + ' encoder failures', 166 | ' size : ' + formatFloat(stats.serialise.size / 1E+6) + ' MB', 167 | ' time : ' + getTime(stats.serialise) 168 | ].join('\n'); 169 | console.log(text); 170 | 171 | function getTime(collection) { 172 | return collection && Object.keys(collection).filter(isTime).map(eachKey).join(', ') || '-'; 173 | 174 | function isTime(key) { 175 | return collection[key].start && collection[key].stop; 176 | } 177 | 178 | function eachKey(key) { 179 | return formatFloat((collection[key].stop - collection[key].start) / 1E+3) + ' seconds ' + key; 180 | } 181 | } 182 | } 183 | 184 | function formatFloat(value) { 185 | if (isNaN(value)) { 186 | return '-'; 187 | } 188 | else { 189 | var integer = Math.round(value), 190 | decimal = Math.floor((value % 1.0) * 1E+3); 191 | return integer + '.' + String(decimal + '000').slice(0, 3); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/cycle.js: -------------------------------------------------------------------------------- 1 | // IMPORTANT - amended to preserve classes and decycle in place 2 | /* 3 | cycle.js 4 | 2013-02-19 5 | 6 | Public Domain. 7 | 8 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 9 | 10 | This code should be minified before deployment. 11 | See http://javascript.crockford.com/jsmin.html 12 | 13 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 14 | NOT CONTROL. 15 | */ 16 | 17 | /*jslint evil: true, regexp: true */ 18 | 19 | /*members $ref, apply, call, decycle, hasOwnProperty, length, prototype, push, 20 | retrocycle, stringify, test, toString 21 | */ 22 | 23 | var cycle = exports; 24 | 25 | cycle.decycle = function decycle(object) { 26 | 'use strict'; 27 | 28 | // Make a deep copy of an object or array, assuring that there is at most 29 | // one instance of each object or array in the resulting structure. The 30 | // duplicate references (which might be forming cycles) are replaced with 31 | // an object of the form 32 | // {$ref: PATH} 33 | // where the PATH is a JSONPath string that locates the first occurance. 34 | // So, 35 | // var a = []; 36 | // a[0] = a; 37 | // return JSON.stringify(JSON.decycle(a)); 38 | // produces the string '[{"$ref":"$"}]'. 39 | 40 | // JSONPath is used to locate the unique object. $ indicates the top level of 41 | // the object or array. [NUMBER] or [STRING] indicates a child member or 42 | // property. 43 | 44 | var objects = [], // Keep a reference to each unique object or array 45 | paths = []; // Keep the path to each unique object or array 46 | 47 | return (function derez(value, path) { 48 | 49 | // The derez recurses through the object, producing the deep copy. 50 | 51 | var i, // The loop counter 52 | name, // Property name 53 | nu = {}; // The new object or array 54 | 55 | // typeof null === 'object', so go on if this value is really an object but not 56 | // one of the weird builtin objects. 57 | 58 | if (typeof value === 'object' && value !== null && 59 | !(value instanceof Boolean) && 60 | !(value instanceof Date) && 61 | !(value instanceof Number) && 62 | !(value instanceof RegExp) && 63 | !(value instanceof String)) { 64 | 65 | // If the value is an object or array, look to see if we have already 66 | // encountered it. If so, return a $ref/path object. This is a hard way, 67 | // linear search that will get slower as the number of unique objects grows. 68 | 69 | for (i = 0; i < objects.length; i += 1) { 70 | if (objects[i] === value) { 71 | return {$ref: paths[i]}; 72 | } 73 | } 74 | 75 | // Otherwise, accumulate the unique value and its path. 76 | 77 | objects.push(value); 78 | paths.push(path); 79 | 80 | // If it is an array, replicate the array. 81 | 82 | // If it is an object, replicate the object. 83 | 84 | for (name in value) { 85 | try { 86 | nu[name] = derez(value[name], 87 | path + '[' + JSON.stringify(name) + ']'); 88 | } catch(error) { 89 | nu[name] = (value[name] && (typeof value[name] === 'object')) ? {$deleted: name} : value[name]; 90 | } 91 | } 92 | return nu; 93 | } 94 | return value; 95 | }(object, '$')); 96 | }; 97 | 98 | 99 | cycle.retrocycle = function retrocycle($) { 100 | 'use strict'; 101 | 102 | // Restore an object that was reduced by decycle. Members whose values are 103 | // objects of the form 104 | // {$ref: PATH} 105 | // are replaced with references to the value found by the PATH. This will 106 | // restore cycles. The object will be mutated. 107 | 108 | // The eval function is used to locate the values described by a PATH. The 109 | // root object is kept in a $ variable. A regular expression is used to 110 | // assure that the PATH is extremely well formed. The regexp contains nested 111 | // * quantifiers. That has been known to have extremely bad performance 112 | // problems on some browsers for very long strings. A PATH is expected to be 113 | // reasonably short. A PATH is allowed to belong to a very restricted subset of 114 | // Goessner's JSONPath. 115 | 116 | // So, 117 | // var s = '[{"$ref":"$"}]'; 118 | // return JSON.retrocycle(JSON.parse(s)); 119 | // produces an array containing a single element which is the array itself. 120 | 121 | var px = 122 | /^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/; 123 | 124 | (function rez(value) { 125 | 126 | // The rez function walks recursively through the object looking for $ref 127 | // properties. When it finds one that has a value that is a path, then it 128 | // replaces the $ref object with a reference to the value that is found by 129 | // the path. 130 | 131 | var i, item, name, path; 132 | 133 | if (value && typeof value === 'object') { 134 | if (Object.prototype.toString.apply(value) === '[object Array]') { 135 | for (i = 0; i < value.length; i += 1) { 136 | item = value[i]; 137 | if (item && typeof item === 'object') { 138 | path = item.$ref; 139 | if (typeof path === 'string' && px.test(path)) { 140 | value[i] = eval(path); 141 | } else { 142 | rez(item); 143 | } 144 | } 145 | } 146 | } else { 147 | for (name in value) { 148 | if (typeof value[name] === 'object') { 149 | item = value[name]; 150 | if (item) { 151 | path = item.$ref; 152 | if (typeof path === 'string' && px.test(path)) { 153 | value[name] = eval(path); 154 | } else { 155 | rez(item); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | }($)); 163 | return $; 164 | }; 165 | --------------------------------------------------------------------------------