├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bin └── ymb.js ├── defaults ├── build.default.json └── gulpfile.default.js ├── docs └── plugins.md ├── examples ├── plugin │ ├── app │ │ ├── build.json │ │ ├── build │ │ │ └── game.js │ │ └── src │ │ │ ├── config │ │ │ └── config.js │ │ │ ├── game │ │ │ ├── Game.js │ │ │ └── layout │ │ │ │ ├── css │ │ │ │ └── game.layout.css │ │ │ │ ├── game.Layout.js │ │ │ │ └── ymtpl │ │ │ │ └── game.layout.ymtpl │ │ │ ├── package │ │ │ └── package.game.js │ │ │ ├── user │ │ │ ├── base │ │ │ │ └── user.Base.js │ │ │ └── player │ │ │ │ └── user.Player.js │ │ │ └── util │ │ │ └── providePackage │ │ │ └── util.providePackage.js │ └── index.html └── standalone │ ├── app │ ├── build.json │ ├── build │ │ └── all.js │ └── src │ │ ├── config │ │ └── config.js │ │ ├── game │ │ └── Game.js │ │ ├── package │ │ └── package.game.js │ │ └── user │ │ ├── base │ │ └── user.Base.js │ │ └── player │ │ └── user.Player.js │ └── index.html ├── index.js ├── lib ├── cli │ ├── configure.js │ ├── help.js │ ├── index.js │ └── runGulp.js ├── plugins │ ├── css │ │ ├── images.js │ │ └── toModules.js │ ├── js │ │ ├── callWithContext.js │ │ ├── closure.js │ │ ├── extractFromCommonJs.js │ │ └── extractFromContext.js │ ├── modules │ │ ├── async.js │ │ ├── async │ │ │ └── src │ │ │ │ ├── defineStubs.js │ │ │ │ ├── modules │ │ │ │ └── system │ │ │ │ │ └── moduleLoader │ │ │ │ │ ├── executeInSandbox │ │ │ │ │ └── system.moduleLoader.executeInSandbox.js │ │ │ │ │ ├── load │ │ │ │ │ └── system.moduleLoader.createLoadFunction.js │ │ │ │ │ └── system.ModuleLoader.js │ │ │ │ └── preloader.js │ │ ├── helpers.js │ │ ├── init.js │ │ ├── map.js │ │ ├── map │ │ │ ├── build.js │ │ │ ├── fallback.js │ │ │ ├── fallback │ │ │ │ └── src │ │ │ │ │ └── fallback.js │ │ │ ├── initial.js │ │ │ └── util │ │ │ │ ├── extractDeclarationData.js │ │ │ │ └── generateAlias.js │ │ ├── minify.js │ │ ├── namespace.js │ │ ├── namespace │ │ │ └── src │ │ │ │ └── namespace.js │ │ ├── plus.js │ │ ├── plus │ │ │ └── src │ │ │ │ ├── modules │ │ │ │ └── vow.js │ │ │ │ └── modulesPlus.js │ │ ├── setup.js │ │ ├── store.js │ │ ├── store │ │ │ ├── async.js │ │ │ └── solid.js │ │ └── ym.js │ ├── public.js │ ├── templates │ │ ├── compile.js │ │ └── toModules.js │ └── util │ │ ├── eachInStream.js │ │ ├── join.js │ │ └── pipeChain.js └── resolveBuildConfig.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | npm-debug.log 4 | _stuff/ 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .DS_Store 3 | .idea 4 | _stuff/ 5 | examples/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0 (2017/03) 2 | === 3 | - Get rid of `hashes.json` in favor of generating hashes from short aliases 4 | - Performance optimizations: **~2 times faster** build and watch/rebuild 5 | - Cleaner and shorter gulpfile with `gulp-if` instead of confusing `util.pipeChain` 6 | - Migrate to **Gulp 4.0** 7 | - Better watch/rebuild with `{ since: gulp.lastRun() }` instead of `plg.cached` and `plg.remember` 8 | - Preferred local installation instead of global 9 | - Disable usage of default gulpfile without explicit copying 10 | - Requiring flat ymb dependencies in the local gulpfile with **npm 3.x** 11 | - `ymb watch` renamed to `ymb dev` 12 | - Prettify command line output 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | © YANDEX LLC, 2014 The Source Code called “ymb” available at https://github.com/yandex/ymb is subject to the terms of the Mozilla Public License, v. 2.0 (hereinafter - MPL). 2 | 3 | The text of MPL is the following: 4 | 5 | Mozilla Public License 6 | Version 2.0 7 | 1. Definitions 8 | 9 | 1.1. “Contributor” 10 | 11 | means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 12 | 1.2. “Contributor Version” 13 | 14 | means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution. 15 | 1.3. “Contribution” 16 | 17 | means Covered Software of a particular Contributor. 18 | 1.4. “Covered Software” 19 | 20 | means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 21 | 1.5. “Incompatible With Secondary Licenses” 22 | 23 | means 24 | 25 | that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or 26 | 27 | that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 28 | 29 | 1.6. “Executable Form” 30 | 31 | means any form of the work other than Source Code Form. 32 | 1.7. “Larger Work” 33 | 34 | means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 35 | 1.8. “License” 36 | 37 | means this document. 38 | 1.9. “Licensable” 39 | 40 | means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 41 | 1.10. “Modifications” 42 | 43 | means any of the following: 44 | 45 | any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or 46 | 47 | any new file in Source Code Form that contains any Covered Software. 48 | 49 | 1.11. “Patent Claims” of a Contributor 50 | 51 | means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 52 | 1.12. “Secondary License” 53 | 54 | means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 55 | 1.13. “Source Code Form” 56 | 57 | means the form of the work preferred for making modifications. 58 | 1.14. “You” (or “Your”) 59 | 60 | means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 61 | 62 | 2. License Grants and Conditions 63 | 2.1. Grants 64 | 65 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 66 | 67 | under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and 68 | 69 | under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 70 | 71 | 2.2. Effective Date 72 | 73 | The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 74 | 2.3. Limitations on Grant Scope 75 | 76 | The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: 77 | 78 | for any code that a Contributor has removed from Covered Software; or 79 | 80 | for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or 81 | 82 | under Patent Claims infringed by Covered Software in the absence of its Contributions. 83 | 84 | This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 85 | 2.4. Subsequent Licenses 86 | 87 | No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 88 | 2.5. Representation 89 | 90 | Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 91 | 2.6. Fair Use 92 | 93 | This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 94 | 2.7. Conditions 95 | 96 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 97 | 3. Responsibilities 98 | 3.1. Distribution of Source Form 99 | 100 | All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form. 101 | 3.2. Distribution of Executable Form 102 | 103 | If You distribute Covered Software in Executable Form then: 104 | 105 | such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and 106 | 107 | You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License. 108 | 109 | 3.3. Distribution of a Larger Work 110 | 111 | You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 112 | 3.4. Notices 113 | 114 | You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 115 | 3.5. Application of Additional Terms 116 | 117 | You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 118 | 4. Inability to Comply Due to Statute or Regulation 119 | 120 | If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 121 | 5. Termination 122 | 123 | 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 124 | 125 | 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 126 | 127 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 128 | 6. Disclaimer of Warranty 129 | 130 | Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 131 | 7. Limitation of Liability 132 | 133 | Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 134 | 8. Litigation 135 | 136 | Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims. 137 | 9. Miscellaneous 138 | 139 | This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 140 | 10. Versions of the License 141 | 10.1. New Versions 142 | 143 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 144 | 10.2. Effect of New Versions 145 | 146 | You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 147 | 10.3. Modified Versions 148 | 149 | If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 150 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 151 | 152 | If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. 153 | Exhibit A - Source Code Form License Notice 154 | 155 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 156 | 157 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 158 | 159 | You may add additional accurate notices of copyright ownership. 160 | Exhibit B - “Incompatible With Secondary Licenses” Notice 161 | 162 | This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. 163 | 164 | 165 | A copy of the MPL is also available at http://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ymb 2 | ====== 3 | 4 | Builder for projects based on [ym modules](https://www.npmjs.org/package/ym). Built on top of [gulp@^4](https://www.npmjs.org/package/gulp) task runner. 5 | 6 | Requirements 7 | ------------ 8 | ymb works with Node.js 0.10+. 9 | 10 | Getting Started 11 | --------------- 12 | **CLI usage** 13 | 14 | You can install `ymb` globally using Node Package Manager (npm): 15 | 16 | npm install --save-dev ymb 17 | 18 | Then you can use `node_modules/.bin/ymb` console command to build your project, create configuration files or setup auto-build task (watch) for any file changes. 19 | Builder will then try to find your `build.json` and `gulpfile.js` configuration files or use default ones. 20 | 21 | ```bash 22 | node_modules/.bin/ymb configure [DIR=.] [build.json] [gulpfile.js] [-f] # Makes a copy of default `build.json` and/or `gulpfile.js` in specified directory. 23 | node_modules/.bin/ymb dev [DIR=.] [-m ] # Runs gulp task `dev` that will rebuild project on any change of source files specified in `build.json`. 24 | node_modules/.bin/ymb [build] [DIR=.] [-m ] # Runs gulp task `build` described by local or default `gulpfile.js`. 25 | ``` 26 | 27 | **Explaining build.json** 28 | 29 | `build.json` contains build settings. You can copy the default one into your project dir with `ymb configure .` and then override it. 30 | 31 | ```javascript 32 | { 33 | // Globs for files to be processed. Related to project dir. 34 | "src": { 35 | "js": "src/**/*.js", 36 | "css": "src/**/*.css", 37 | "templates": "src/**/*.ymtpl" 38 | }, 39 | 40 | // Path that build results will be placed into. Related to project dir. 41 | "dest": "build/", 42 | 43 | // Name of the output JavaScript file when `store` set to `solid`. 44 | "concatTo": "all.js", 45 | 46 | // Output files optimization. 47 | "minify": true, 48 | 49 | // And you can use object to set minify options. See more https://www.npmjs.com/package/gulp-uglify#options 50 | // "minify": { 51 | // "extension": "js" // Extension of target files 52 | // } 53 | 54 | // Set to `false` to prevent inline data URI for images in CSS. Add `#no-datauri` to image path to prevent for particular file. 55 | // You will need to route static handler for `build/{publicDir}/images` on server side. 56 | "dataUriImages": true, 57 | "publicDir": "public", 58 | 59 | // If you want to make a plugin for existing project providing `ym` modular system (i.e. `Yandex Maps API`), 60 | // set `target` option to `plugin`. Option `namespace` should contain name of the main project global namespace. 61 | // 62 | // Otherwise you can run your project as standalone. 63 | // Then builder will inject `ym` modular system and helper modules into build output. 64 | // In this case option `namespace` specifies the variable name to be exported into the `global` scope, 65 | // that will contain `modules` property referencing the modular system. 66 | "target": "plugin", 67 | "namespace": "ym", 68 | 69 | // Defines how to store your build data: `solid` for single file and `async` for running with `yms`. 70 | // More information about `yms` and examples of async projects is here: https://www.npmjs.org/package/yms 71 | "store": "solid", 72 | 73 | // You can specify array of modules to be preloaded along with ones specified in GET params and `ready` method. 74 | "preload": [], 75 | 76 | // You can insert another async layer, when information about certain modules ("modules map") is not included into `init.js`, 77 | // but can be loaded with another HTTP request to `yms` server. These features are experimental and may cause issues. 78 | "asyncMap": false, 79 | "initialMap": null, 80 | 81 | // You can use different modes overriding global options, but don't forget to specify one of them when running builder. 82 | // "modes": { 83 | // "debug": { 84 | // "default": true, 85 | // "minify": false 86 | // } 87 | // } 88 | } 89 | ``` 90 | 91 | **Explaining gulpfile.js** 92 | 93 | If you want more flexibility you can also override default `gulpfile.js` by cloning it locally with `ymb configure . gulpfile.js`. Read more about gulpfiles on [gulp project page](https://github.com/gulpjs/gulp/). 94 | 95 | **Plugins usage** 96 | 97 | If you're already using `gulp` and have your own `gulpfile.js` you can require our plugins directly and use them in your tasks. 98 | 99 | Check out [**ymb plugins documentation**](docs/plugins.md). 100 | 101 | ```javascript 102 | var gulp = require('gulp'), 103 | ymbPlugins = require('ymb').plugins; 104 | 105 | gulp.task('templates', function () { 106 | gulp.src(/* .. */) 107 | .pipe(/* .. */) 108 | .pipe(ymbPlugins.templates.compile()) 109 | .pipe(/* .. */); 110 | }); 111 | ``` 112 | -------------------------------------------------------------------------------- /bin/ymb.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli'); -------------------------------------------------------------------------------- /defaults/build.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "js": "src/**/*.js", 4 | "css": "src/**/*.css", 5 | "templates": "src/**/*.ymtpl" 6 | }, 7 | "dest": "build/", 8 | "namespace": "ym", 9 | "target": "plugin", 10 | "serverUrl": null, 11 | "asyncMap": false, 12 | "initialMap": null, 13 | "fallbackFilter": null, 14 | "fallbackModule": null, 15 | "store": "solid", 16 | "preload": [], 17 | "concatTo": "all.js", 18 | "minify": { 19 | "extension": "js" 20 | }, 21 | "dataUriImages": true, 22 | "publicDir": "public" 23 | } 24 | -------------------------------------------------------------------------------- /defaults/gulpfile.default.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | ymb = require('ymb'), 4 | plg = ymb.plugins; 5 | 6 | var cfg = ymb.resolveBuildConfig(); 7 | 8 | gulp.task('build', build); 9 | gulp.task('rebuild', gulp.series(clean, build)); 10 | gulp.task('dev', gulp.series('rebuild', watch)); 11 | 12 | function clean (cb) { 13 | ymb.del(path.resolve(cfg.dest), { force: true }) 14 | .then(function () { cb(); }); 15 | } 16 | 17 | function build () { 18 | var isAsync = cfg.store == 'async', 19 | isStandalone = cfg.target == 'standalone', 20 | needMinify = Boolean(cfg.minify), 21 | srcOpts = { since: gulp.lastRun(build) }, 22 | js, css, templates, modules; 23 | 24 | js = gulp.src(cfg.src.js, srcOpts); 25 | 26 | css = gulp.src(cfg.src.css, srcOpts) 27 | .pipe(plg.css.images(cfg)) 28 | .pipe(plg.css.optimize(cfg)) 29 | .pipe(plg.css.toModules(cfg)); 30 | 31 | templates = gulp.src(cfg.src.templates, srcOpts) 32 | .pipe(plg.templates.compile(cfg)) 33 | .pipe(plg.templates.toModules(cfg)); 34 | 35 | return ymb.es.merge(js, css, templates) 36 | .pipe(plg.modules.setup(cfg)) 37 | .pipe(plg.modules.ym(cfg)) 38 | .pipe(plg.if(isStandalone, plg.modules.plus(cfg))) 39 | .pipe(plg.if(isStandalone, plg.modules.helpers(cfg))) 40 | .pipe(plg.if(isAsync, plg.modules.map(cfg))) 41 | .pipe(plg.if(isAsync, plg.modules.async(cfg))) 42 | .pipe(plg.if(isStandalone, plg.modules.namespace(cfg))) 43 | .pipe(plg.modules.init(cfg)) 44 | .pipe(plg.modules.store(cfg)) 45 | .pipe(plg.if(needMinify, plg.modules.minify(cfg))) 46 | .pipe(gulp.dest(path.resolve(cfg.dest))); 47 | } 48 | 49 | function watch () { 50 | return gulp.watch([ 51 | cfg.src.js, 52 | cfg.src.css, 53 | cfg.src.templates 54 | ], build); 55 | } -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 |
3 |
css.toModules()stream.Transform
4 |

Wraps CSS code into an ym module, that will use system.provideCss to register it in browser.

5 |
6 |
js.closure()stream.Transform
7 |

Wraps code in a simple JS closure.

8 |
9 |
modules.async(cfg)stream.Transform
10 |

Adds support for asynchronous loading of modules. 11 | Requires files needed for current target, joins and wraps them into a function, 12 | that will be called with needed params in a code added by yms server.

13 |
14 |
modules.helpers()stream.Transform
15 |

Injects modules from ym-helpers package.

16 |
17 |
modules.init()stream.Transform
18 |

Joins all 'init#' parts into a single init.js file with the right order.

19 |
20 |
modules.map(cfg)stream.Transform
21 |

Collects information about modules in the src path. 22 | Check out child plugins for more info.

23 |
24 |
modules.minify()stream.Transform
25 |

Runs uglify() on JS files.

26 |
27 |
modules.namespace()stream.Transform
28 |

Adds project namespace into the global context.

29 |
30 |
modules.plus()stream.Transform
31 |

Injects vow and Modules Plus code. 32 | Modules Plus is an additional layer to modular system ym that introduces: 33 | — promises support 34 | — dynamic dependencies and "predictor" 35 | — modules Definition interface 36 | — asynchronous storages 37 | — providing of packages 38 | — "map fallbacks" support 39 | — synchronous define and require 40 | — etc.

41 |
42 |
modules.setup(cfg)stream.Transform
43 |

Injects main JS object ym with base project params as a part of init.js.

44 |
45 |
modules.store(cfg)stream.Transform
46 |

Stores built modules and info files into a specified folder in a disk. 47 | Check out child plugins for more info.

48 |
49 |
modules.ym()stream.Transform
50 |

In case of standalone project injects ym modular system source code as part of init.js contents. 51 | In case of plugin project only refers a property from a main project namespace.

52 |
53 |
templates.compile()stream.Transform
54 |

Pre-compiles HTML templates using template.Parser module from ym-helpers package.

55 |
56 |
templates.toModules()stream.Transform
57 |

Wraps HTML code into an ym module.

58 |
59 |
util.eachInStream()stream.Transform
60 |

Helper for running handlers only for non empty buffers in the stream.

61 |
62 |
util.join()stream.Transform
63 |

Allows to join stream as an "incoming branch" for some other main stream.

64 |
65 |
util.pipeChain()stream.Transform
66 |

Allows to create a chain of plugins where streams will be piped from one plugin to another. 67 | Later this chain can be injected anywhere in a pipeline, 68 | so pipeline contents will get inside the first plugin and the go out from the last one.

69 |
70 |
71 | 72 | ## css.toModules() ⇒ stream.Transform 73 | Wraps CSS code into an `ym` module, that will use `system.provideCss` to register it in browser. 74 | 75 | **Kind**: global function 76 | **Returns**: stream.Transform - Stream 77 | 78 | ## js.closure() ⇒ stream.Transform 79 | Wraps code in a simple JS closure. 80 | 81 | **Kind**: global function 82 | **Returns**: stream.Transform - Stream 83 | 84 | ## modules.async(cfg) ⇒ stream.Transform 85 | Adds support for asynchronous loading of modules. 86 | Requires files needed for current target, joins and wraps them into a function, 87 | that will be called with needed params in a code added by `yms` server. 88 | 89 | **Kind**: global function 90 | **Returns**: stream.Transform - Stream 91 | 92 | | Param | Type | Description | 93 | | --- | --- | --- | 94 | | cfg | Object | Config | 95 | 96 | 97 | ## modules.helpers() ⇒ stream.Transform 98 | Injects modules from `ym-helpers` package. 99 | 100 | **Kind**: global function 101 | **Returns**: stream.Transform - Stream 102 | **See**: https://www.npmjs.com/package/ym-helpers 103 | 104 | ## modules.init() ⇒ stream.Transform 105 | Joins all 'init#' parts into a single `init.js` file with the right order. 106 | 107 | **Kind**: global function 108 | **Returns**: stream.Transform - Stream 109 | 110 | ## modules.map(cfg) ⇒ stream.Transform 111 | Collects information about modules in the src path. 112 | Check out child plugins for more info. 113 | 114 | **Kind**: global function 115 | **Returns**: stream.Transform - Stream 116 | 117 | | Param | Type | Description | 118 | | --- | --- | --- | 119 | | cfg | Object | Config | 120 | 121 | 122 | ## modules.minify() ⇒ stream.Transform 123 | Runs `uglify()` on JS files. 124 | 125 | **Kind**: global function 126 | **Returns**: stream.Transform - Stream 127 | 128 | ## modules.namespace() ⇒ stream.Transform 129 | Adds project namespace into the global context. 130 | 131 | **Kind**: global function 132 | **Returns**: stream.Transform - Stream 133 | 134 | ## modules.plus() ⇒ stream.Transform 135 | Injects `vow` and `Modules Plus` code. 136 | `Modules Plus` is an additional layer to modular system `ym` that introduces: 137 | — promises support 138 | — dynamic dependencies and "predictor" 139 | — modules `Definition` interface 140 | — asynchronous storages 141 | — providing of packages 142 | — "map fallbacks" support 143 | — synchronous `define` and `require` 144 | — etc. 145 | 146 | **Kind**: global function 147 | **Returns**: stream.Transform - Stream 148 | **See** 149 | 150 | - https://www.npmjs.com/package/vow 151 | - https://www.npmjs.com/package/ym 152 | 153 | 154 | ## modules.setup(cfg) ⇒ stream.Transform 155 | Injects main JS object `ym` with base project params as a part of `init.js`. 156 | 157 | **Kind**: global function 158 | **Returns**: stream.Transform - Stream 159 | 160 | | Param | Type | Description | 161 | | --- | --- | --- | 162 | | cfg | Object | Config | 163 | 164 | 165 | ## modules.store(cfg) ⇒ stream.Transform 166 | Stores built modules and info files into a specified folder in a disk. 167 | Check out child plugins for more info. 168 | 169 | **Kind**: global function 170 | **Returns**: stream.Transform - Stream 171 | 172 | | Param | Type | Description | 173 | | --- | --- | --- | 174 | | cfg | Object | Config | 175 | 176 | 177 | ## modules.ym() ⇒ stream.Transform 178 | In case of standalone project injects `ym` modular system source code as part of `init.js` contents. 179 | In case of plugin project only refers a property from a main project namespace. 180 | 181 | **Kind**: global function 182 | **Returns**: stream.Transform - Stream 183 | **See**: https://www.npmjs.com/package/ym 184 | 185 | ## templates.compile() ⇒ stream.Transform 186 | Pre-compiles HTML templates using `template.Parser` module from `ym-helpers` package. 187 | 188 | **Kind**: global function 189 | **Returns**: stream.Transform - Stream 190 | **See**: https://www.npmjs.com/package/ym-helpers 191 | 192 | ## templates.toModules() ⇒ stream.Transform 193 | Wraps HTML code into an `ym` module. 194 | 195 | **Kind**: global function 196 | **Returns**: stream.Transform - Stream 197 | 198 | ## util.eachInStream() ⇒ stream.Transform 199 | Helper for running handlers only for non empty buffers in the stream. 200 | 201 | **Kind**: global function 202 | **Returns**: stream.Transform - Stream 203 | 204 | ## util.join() ⇒ stream.Transform 205 | Allows to join stream as an "incoming branch" for some other main stream. 206 | 207 | **Kind**: global function 208 | **Returns**: stream.Transform - Stream 209 | 210 | ## util.pipeChain() ⇒ stream.Transform 211 | Allows to create a chain of plugins where streams will be piped from one plugin to another. 212 | Later this chain can be injected anywhere in a pipeline, 213 | so pipeline contents will get inside the first plugin and the go out from the last one. 214 | 215 | **Kind**: global function 216 | **Returns**: stream.Transform - Stream 217 | -------------------------------------------------------------------------------- /examples/plugin/app/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "plugin", 3 | "namespace": "ymaps", 4 | "concatTo": "game.js", 5 | "minify": false 6 | } -------------------------------------------------------------------------------- /examples/plugin/app/build/game.js: -------------------------------------------------------------------------------- 1 | (function (global){ 2 | 3 | 4 | var ym = {"project":{"namespace":"ymaps","jsonpPrefix":"","loadLimit":500},"env":{}}; 5 | 6 | ym.modules = global['ymaps'].modules; 7 | 8 | ym.modules.define('config', function (provide) { 9 | provide({ 10 | location: { 11 | default: 'ru', 12 | ru: 'Russia', 13 | us: 'USA', 14 | uk: 'United Kingdom' 15 | } 16 | }); 17 | }); 18 | ym.modules.define('Game', [ 19 | 'util.defineClass', 'user.Player', 'game.Layout' 20 | ], function (provide, defineClass, Player, GameLayout) { 21 | function Game () { 22 | this._players = []; 23 | } 24 | 25 | defineClass(Game, { 26 | addPlayer: function (name, from) { 27 | this._players.push(new Player(name, from)); 28 | }, 29 | 30 | start: function () { 31 | for (var i = 0, l = this._players.length; i < l; i++) { 32 | this._players[i].play(); 33 | } 34 | 35 | this._setupLayout(); 36 | }, 37 | 38 | _setupLayout: function () { 39 | var layout = new GameLayout({ playerNames: this._getPlayerNames() }); 40 | 41 | layout.setParentElement(document.body); 42 | }, 43 | 44 | _getPlayerNames: function () { 45 | var names = []; 46 | 47 | for (var i = 0, l = this._players.length; i < l; i++) { 48 | names.push(this._players[i].getName()); 49 | } 50 | 51 | return names.join(', '); 52 | } 53 | }); 54 | 55 | provide(Game); 56 | }); 57 | ym.modules.define('game.layout.css', ['system.provideCss'], function (provide, provideCss) { 58 | provideCss(".ym-game{background:#b8a0ff;display:inline-block;color:#fff;font-family:Arial,serif;font-size:14px;padding:20px}", provide); 59 | }); 60 | ym.modules.define('game.Layout', [ 61 | 'templateLayoutFactory', 62 | 'game.layout.ymtpl', 63 | 'game.layout.css' 64 | ], function (provide, templateLayoutFactory, template) { 65 | var GameLayout = templateLayoutFactory.createClass(template); 66 | 67 | provide(GameLayout); 68 | }); 69 | ym.modules.define('game.layout.ymtpl', function (provide) { 70 | provide([0,"
Players in game: ",2001,["playerNames",[]],0,".
"]); 71 | }); 72 | ym.modules.define('package.game', ['util.providePackage', 'Game'], function (provide, providePackage) { 73 | providePackage(this, arguments); 74 | }); 75 | ym.modules.define('user.Base', ['util.defineClass', 'config'], function (provide, defineClass, config) { 76 | function BaseUser (name, location) { 77 | this._name = name; 78 | this._location = location || config.location.default; 79 | } 80 | 81 | defineClass(BaseUser, { 82 | getName: function () { 83 | return this._name; 84 | }, 85 | 86 | getLocation: function () { 87 | return config.location[this._location]; 88 | } 89 | }); 90 | 91 | provide(BaseUser); 92 | }); 93 | ym.modules.define('user.Player', ['util.defineClass', 'user.Base'], function (provide, defineClass, BaseUser) { 94 | function Player () { 95 | Player.superclass.constructor.apply(this, arguments); 96 | } 97 | 98 | defineClass(Player, BaseUser, { 99 | play: function () { 100 | console.log(this.getName() + ' from ' + this.getLocation() + ' started playing.'); 101 | } 102 | }); 103 | 104 | provide(Player); 105 | }); 106 | ym.modules.define('util.providePackage', ['system.mergeImports'], function (provide, mergeImports) { 107 | provide(function (srcPackage, packageArgs) { 108 | var packageProvide = packageArgs[0], 109 | packageModules = Array.prototype.slice.call(packageArgs, 1), 110 | ns = mergeImports.joinImports(srcPackage.name, {}, srcPackage.deps, packageModules); 111 | 112 | packageProvide(ns); 113 | }); 114 | }); 115 | 116 | })(this); -------------------------------------------------------------------------------- /examples/plugin/app/src/config/config.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('config', function (provide) { 2 | provide({ 3 | location: { 4 | default: 'ru', 5 | ru: 'Russia', 6 | us: 'USA', 7 | uk: 'United Kingdom' 8 | } 9 | }); 10 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/game/Game.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('Game', [ 2 | 'util.defineClass', 'user.Player', 'game.Layout' 3 | ], function (provide, defineClass, Player, GameLayout) { 4 | function Game () { 5 | this._players = []; 6 | } 7 | 8 | defineClass(Game, { 9 | addPlayer: function (name, from) { 10 | this._players.push(new Player(name, from)); 11 | }, 12 | 13 | start: function () { 14 | for (var i = 0, l = this._players.length; i < l; i++) { 15 | this._players[i].play(); 16 | } 17 | 18 | this._setupLayout(); 19 | }, 20 | 21 | _setupLayout: function () { 22 | var layout = new GameLayout({ playerNames: this._getPlayerNames() }); 23 | 24 | layout.setParentElement(document.body); 25 | }, 26 | 27 | _getPlayerNames: function () { 28 | var names = []; 29 | 30 | for (var i = 0, l = this._players.length; i < l; i++) { 31 | names.push(this._players[i].getName()); 32 | } 33 | 34 | return names.join(', '); 35 | } 36 | }); 37 | 38 | provide(Game); 39 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/game/layout/css/game.layout.css: -------------------------------------------------------------------------------- 1 | .ym-game { 2 | background: #b8a0ff; 3 | display: inline-block; 4 | color: white; 5 | font-family: Arial, serif; 6 | font-size: 14px; 7 | padding: 20px; 8 | } -------------------------------------------------------------------------------- /examples/plugin/app/src/game/layout/game.Layout.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('game.Layout', [ 2 | 'templateLayoutFactory', 3 | 'game.layout.ymtpl', 4 | 'game.layout.css' 5 | ], function (provide, templateLayoutFactory, template) { 6 | var GameLayout = templateLayoutFactory.createClass(template); 7 | 8 | provide(GameLayout); 9 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/game/layout/ymtpl/game.layout.ymtpl: -------------------------------------------------------------------------------- 1 |
Players in game: {{playerNames}}.
-------------------------------------------------------------------------------- /examples/plugin/app/src/package/package.game.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('package.game', ['util.providePackage', 'Game'], function (provide, providePackage) { 2 | providePackage(this, arguments); 3 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/user/base/user.Base.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('user.Base', ['util.defineClass', 'config'], function (provide, defineClass, config) { 2 | function BaseUser (name, location) { 3 | this._name = name; 4 | this._location = location || config.location.default; 5 | } 6 | 7 | defineClass(BaseUser, { 8 | getName: function () { 9 | return this._name; 10 | }, 11 | 12 | getLocation: function () { 13 | return config.location[this._location]; 14 | } 15 | }); 16 | 17 | provide(BaseUser); 18 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/user/player/user.Player.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('user.Player', ['util.defineClass', 'user.Base'], function (provide, defineClass, BaseUser) { 2 | function Player () { 3 | Player.superclass.constructor.apply(this, arguments); 4 | } 5 | 6 | defineClass(Player, BaseUser, { 7 | play: function () { 8 | console.log(this.getName() + ' from ' + this.getLocation() + ' started playing.'); 9 | } 10 | }); 11 | 12 | provide(Player); 13 | }); -------------------------------------------------------------------------------- /examples/plugin/app/src/util/providePackage/util.providePackage.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('util.providePackage', ['system.mergeImports'], function (provide, mergeImports) { 2 | provide(function (srcPackage, packageArgs) { 3 | var packageProvide = packageArgs[0], 4 | packageModules = Array.prototype.slice.call(packageArgs, 1), 5 | ns = mergeImports.joinImports(srcPackage.name, {}, srcPackage.deps, packageModules); 6 | 7 | packageProvide(ns); 8 | }); 9 | }); -------------------------------------------------------------------------------- /examples/plugin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/standalone/app/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "standalone", 3 | "minify": false 4 | } -------------------------------------------------------------------------------- /examples/standalone/app/src/config/config.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('config', function (provide) { 2 | provide({ 3 | location: { 4 | default: 'ru', 5 | ru: 'Russia', 6 | us: 'USA', 7 | uk: 'United Kingdom' 8 | } 9 | }); 10 | }); -------------------------------------------------------------------------------- /examples/standalone/app/src/game/Game.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('Game', ['util.defineClass', 'user.Player'], function (provide, defineClass, Player) { 2 | function Game () { 3 | this._players = []; 4 | } 5 | 6 | defineClass(Game, { 7 | addPlayer: function (name, from) { 8 | this._players.push(new Player(name, from)); 9 | }, 10 | 11 | start: function () { 12 | for (var i = 0, l = this._players.length; i < l; i++) { 13 | this._players[i].play(); 14 | } 15 | 16 | this._setupView(); 17 | }, 18 | 19 | _setupView: function () { 20 | document.body.innerHTML += 'Players in game: ' + this._getPlayerNames(); 21 | }, 22 | 23 | _getPlayerNames: function () { 24 | var names = []; 25 | 26 | for (var i = 0, l = this._players.length; i < l; i++) { 27 | names.push(this._players[i].getName()); 28 | } 29 | 30 | return names.join(', '); 31 | } 32 | }); 33 | 34 | provide(Game); 35 | }); -------------------------------------------------------------------------------- /examples/standalone/app/src/package/package.game.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('package.game', ['util.providePackage', 'Game'], function (provide, providePackage) { 2 | providePackage(this, arguments); 3 | }); -------------------------------------------------------------------------------- /examples/standalone/app/src/user/base/user.Base.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('user.Base', ['util.defineClass', 'config'], function (provide, defineClass, config) { 2 | function BaseUser (name, location) { 3 | this._name = name; 4 | this._location = location || config.location.default; 5 | } 6 | 7 | defineClass(BaseUser, { 8 | getName: function () { 9 | return this._name; 10 | }, 11 | 12 | getLocation: function () { 13 | return config.location[this._location]; 14 | } 15 | }); 16 | 17 | provide(BaseUser); 18 | }); -------------------------------------------------------------------------------- /examples/standalone/app/src/user/player/user.Player.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('user.Player', ['util.defineClass', 'user.Base'], function (provide, defineClass, BaseUser) { 2 | function Player () { 3 | Player.superclass.constructor.apply(this, arguments); 4 | } 5 | 6 | defineClass(Player, BaseUser, { 7 | play: function () { 8 | console.log(this.getName() + ' from ' + this.getLocation() + ' started playing.'); 9 | } 10 | }); 11 | 12 | provide(Player); 13 | }); -------------------------------------------------------------------------------- /examples/standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | gulp: require('gulp'), 3 | es: require('event-stream'), 4 | del: require('del'), 5 | plugins: require('./lib/plugins/public'), 6 | resolveBuildConfig: require('./lib/resolveBuildConfig') 7 | }; -------------------------------------------------------------------------------- /lib/cli/configure.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | vow = require('vow'), 3 | fs = require('vow-fs'); 4 | 5 | module.exports = configure; 6 | 7 | var rootPath = path.resolve(__dirname, '../../'); 8 | 9 | function configure (dir, target, force) { 10 | if (!target || target == 'build.json') { 11 | copyDefault(rootPath + '/defaults/build.default.json', dir, 'build.json', force); 12 | } else if (target == 'gulpfile') { 13 | copyDefault(rootPath + '/defaults/gulpfile.default.js', dir, 'gulpfile.js', force); 14 | } 15 | } 16 | 17 | function copyDefault (defaultFilePath, dir, filename, force) { 18 | fs.exists(dir + '/' + filename).then(function (exists) { 19 | if (exists && !force) { 20 | return vow.reject('Can\'t configure. File `' + filename + '` already exists.'); 21 | } 22 | }).then(function () { 23 | return fs.copy(defaultFilePath, dir + '/' + filename); 24 | }).then(function () { 25 | console.log('Now you have your copy of default `' + filename + '`.'); 26 | }).fail(function (e) { 27 | console.error('Error: ' + (e.message || e)); 28 | }); 29 | } -------------------------------------------------------------------------------- /lib/cli/help.js: -------------------------------------------------------------------------------- 1 | var packageInfo = require('../../package.json'); 2 | 3 | module.exports = help; 4 | 5 | function help () { 6 | console.log([ 7 | packageInfo.name + ' ' + packageInfo.version, 8 | packageInfo.description, 9 | 'Tries to find `build.json` and `gulpfile.js` in specified project DIR or uses default ones.', 10 | '', 11 | 'Usage:', 12 | '\tymb [build] [DIR=.] [-m ]\t\t# Runs gulp task `build` described by local or default `gulpfile.js`.', 13 | '\tymb dev [DIR=.] [-m ]\t\t# Runs gulp task `dev` that will rebuild project on any change of source files specified in `build.json`.', 14 | '\tymb configure [DIR=.] [build.json] [gulpfile.js] [-f] # Makes a copy of default `build.json` and/or `gulpfile.js` in specified directory.', 15 | '\tymb help\t\t\t\t# Displays this message.' 16 | ].join('\n')); 17 | } -------------------------------------------------------------------------------- /lib/cli/index.js: -------------------------------------------------------------------------------- 1 | var minimist = require('minimist'); 2 | 3 | var ACTIONS = ['help', 'configure', 'build', 'dev', 'clean']; 4 | 5 | var args = parseArgs(); 6 | 7 | if (args.action == 'help') { 8 | require('./help')(); 9 | 10 | } else if (args.action == 'configure') { 11 | require('./configure')(args.projectDir, args.target, args.force); 12 | 13 | } else if (args.action == 'build' || args.action == 'dev' || args.action == 'clean') { 14 | require('./runGulp')(args.projectDir, args.action); 15 | } 16 | 17 | function parseArgs () { 18 | var args = minimist(process.argv), 19 | firstArgIsAction = ACTIONS.indexOf(args._[2]) != -1, 20 | action = (args.help || args.h) ? 'help' : (firstArgIsAction ? args._[2] : 'build'), 21 | cwd = (firstArgIsAction ? args._[3] : args._[2]) || process.cwd(), 22 | target = args._.slice(firstArgIsAction ? 4 : 3).join(' ').match(/(build\.json|gulpfile)/g); 23 | 24 | return { 25 | action: action, 26 | projectDir: cwd, 27 | force: args.f || args.force, 28 | target: target 29 | }; 30 | } -------------------------------------------------------------------------------- /lib/cli/runGulp.js: -------------------------------------------------------------------------------- 1 | var childProcess = require('child_process'), 2 | path = require('path'), 3 | fs = require('vow-fs'), 4 | minimist = require('minimist'), 5 | clc = require('cli-color'); 6 | 7 | module.exports = runGulp; 8 | 9 | var ymbRoot = path.resolve(__dirname, '../../'); 10 | var gulpBin = path.resolve(process.cwd(), 'node_modules/.bin/gulp'); 11 | 12 | function runGulp(dir, task) { 13 | dir = path.resolve(dir); 14 | 15 | fs.exists(gulpBin) 16 | .then(function (exists) { 17 | if (!exists) { 18 | return Promise.reject( 19 | clc.red('[error] ') + 20 | clc.magenta(gulpBin) + 21 | ' not found. You need to have ' + 22 | clc.cyan('gulp@^4 (gulpjs/gulp#4.0)') + 23 | ' installed locally.' 24 | ); 25 | } 26 | 27 | return fs.exists(dir + '/gulpfile.js'); 28 | }) 29 | .then(function (exists) { 30 | return exists ? dir + '/gulpfile.js' : ymbRoot + '/defaults/gulpfile.default.js'; 31 | }) 32 | .then(function (gulpfile) { 33 | var t = Date.now(), 34 | args = minimist(process.argv), 35 | mode = args.mode || args.m || 'default'; 36 | 37 | process.chdir(dir); 38 | console.log('Builder ' + clc.cyan.bold('ymb') + ' started from ' + clc.magenta(dir)); 39 | 40 | childProcess.spawn(gulpBin, [ 41 | '--color', 42 | '--gulpfile=' + gulpfile, 43 | '--mode=' + mode, 44 | task 45 | ], { 46 | stdio: 'inherit' 47 | }); 48 | }) 49 | .catch(console.error); 50 | } 51 | -------------------------------------------------------------------------------- /lib/plugins/css/images.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | _ = require('lodash'), 3 | vow = require('vow'), 4 | fs = require('vow-fs'), 5 | md5 = require('md5-jkmyers'), 6 | eachInStream = require('../util/eachInStream'); 7 | 8 | module.exports = cssImagesPlugin; 9 | 10 | var PLUGIN_NAME = 'ym-css-images', 11 | DEST_DIR = 'images/', 12 | NO_DATA_URI_MARK = '#no-datauri'; 13 | 14 | var infoByPath = {}, 15 | currentCfg = null; 16 | 17 | /** 18 | * Makes a search for references to images in CSS files, reads images files, 19 | * moves them into images build folder and replaces its references with Data URI representation (if needed). 20 | * @alias "css.images" 21 | * @param {Object} cfg Config 22 | * @returns {stream.Transform} Stream 23 | */ 24 | function cssImagesPlugin (cfg) { 25 | currentCfg = cfg; 26 | 27 | return eachInStream(function (file, encoding, cb) { 28 | var content = file.contents.toString(), 29 | matchRegExp = /[\w\-\/\.]+\.(svg|gif|png|jpg|jpeg|cur)/igm, 30 | replaceRegExp = /[\w\-\/\.]+\.(svg|gif|png|jpg|jpeg|cur)(#[\w\-]+)?/igm, 31 | srcImages = content.match(matchRegExp); 32 | 33 | if (!srcImages) { 34 | cb(null, file); 35 | return; 36 | } 37 | 38 | var modulePath = path.dirname(file.path), 39 | destPath = path.resolve(process.cwd(), currentCfg.dest, currentCfg.publicDir, DEST_DIR); 40 | 41 | fs.makeDir(destPath).then(function () { 42 | return processImages(modulePath, srcImages, destPath); 43 | }).then(function () { 44 | file.contents = new Buffer(content.replace(replaceRegExp, function (fileName) { 45 | var info = infoByPath[path.join(modulePath, fileName.split('#')[0])]; 46 | 47 | return !currentCfg.dataUriImages || _.endsWith(fileName, NO_DATA_URI_MARK) ? 48 | (DEST_DIR + info.md5filename) : 49 | info.dataUri; 50 | })); 51 | 52 | cb(null, file); 53 | }).fail(function (err) { 54 | console.log('[warn] Image processing error from file ' + file.path); 55 | console.error(err); 56 | cb(null, file) 57 | }).done(); 58 | }, PLUGIN_NAME); 59 | } 60 | 61 | function processImages (modulePath, srcImages, destPath) { 62 | var processPromises = []; 63 | 64 | _.each(srcImages, function (imagePath) { 65 | imagePath = path.join(modulePath, imagePath); 66 | 67 | if (!infoByPath[imagePath]) { 68 | processPromises.push( 69 | fs.read(imagePath).then(function (content) { 70 | var base64 = content.toString('base64'), 71 | md5hash = md5(base64), 72 | md5filename = md5hash + path.extname(imagePath); 73 | 74 | infoByPath[imagePath] = { 75 | md5filename: md5filename, 76 | dataUri: content.toString('utf8').match(/\w*\ 0 && preload.load.split(','), 109 | promise = modulesNames ? ym.modules.require(modulesNames) : vow.resolve(); 110 | 111 | if (preload.onError) { 112 | promise.fail(function (err) { 113 | ym.modules.nextTick(function () { 114 | callUserMethod(0, preload.onError, err); 115 | }); 116 | }); 117 | } 118 | 119 | return vow.all([getMergeImports(), promise, configPromise]).spread(function (mergeImports, modulesValues) { 120 | if (isNotEmpty(modulesValues)) { 121 | mergeImports.joinImports('package.ymaps', ym.ns, modulesNames, modulesValues); 122 | } 123 | 124 | if (preload.onLoad) { 125 | ym.modules.nextTick(function () { 126 | callUserMethod(0, preload.onLoad, ym.ns); 127 | }); 128 | } 129 | }); 130 | } 131 | 132 | function callUserMethod (i, callback, value) { 133 | // Если функция обработчик описана ниже подключения АПИ, то в ситуации поднятия АПИ из кеша и синхронного 134 | // в результате этого выполнения кода, получаем ошибку при вызове несуществующей функции. Стабильно 135 | // повторяется в браузере Opera. 136 | var callbackData = getMethodByPath(global, callback); 137 | 138 | if (callbackData) { 139 | callbackData.method.call(callbackData.context, value); 140 | } else { 141 | window.setTimeout(function () { 142 | callUserMethod(++i, callback, value); 143 | }, Math.pow(2, i)); 144 | } 145 | } 146 | 147 | function getMethodByPath (parentNs, path) { 148 | var subObj = parentNs; 149 | path = path.split('.'); 150 | var i = 0, l = path.length - 1; 151 | for (; i < l; i++) { 152 | subObj = subObj[path[i]]; 153 | if(!subObj){ 154 | return undefined; 155 | } 156 | } 157 | return { 158 | method: subObj[path[l]], 159 | context: subObj 160 | }; 161 | } 162 | 163 | function isNotEmpty (obj) { 164 | return obj && obj.length; 165 | } 166 | })(this); -------------------------------------------------------------------------------- /lib/plugins/modules/helpers.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | cache = require('gulp-cached'), 4 | ymHelpers = require('ym-helpers'), 5 | join = require('../util/join'); 6 | 7 | module.exports = helpersPlugin; 8 | 9 | /** 10 | * Injects modules from `ym-helpers` package. 11 | * @alias "modules.helpers" 12 | * @see https://www.npmjs.com/package/ym-helpers 13 | * @returns {stream.Transform} Stream 14 | */ 15 | function helpersPlugin (ctx) { 16 | return join( 17 | gulp.src(path.resolve(ymHelpers.getSrcDir(), '**/*.js')) 18 | .pipe(cache(ctx.name || 'ymb#helpers')) 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/plugins/modules/init.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | _ = require('lodash'), 3 | File = require('vinyl'), 4 | eachInStream = require('../util/eachInStream'); 5 | 6 | module.exports = initPlugin; 7 | 8 | var ORDER = ['setup', 'ym', 'plus', 'map', 'async', 'fallback', 'namespace']; 9 | 10 | var parts = null; 11 | 12 | /** 13 | * Joins all 'init#' parts into a single `init.js` file with the right order. 14 | * @alias "modules.init" 15 | * @returns {stream.Transform} Stream 16 | */ 17 | function initPlugin () { 18 | parts = []; 19 | 20 | return eachInStream(processFile, createInitFile); 21 | } 22 | 23 | function processFile (file, _, cb) { 24 | if (file.relative.indexOf('init#') === 0) { 25 | var partName = file.relative.replace('init#', ''), 26 | index = ORDER.indexOf(partName); 27 | 28 | if (index == -1) { 29 | index = parseInt(partName); 30 | } 31 | 32 | parts.push({ 33 | content: Buffer.concat([new Buffer('\n'), file.contents, new Buffer('\n')]), 34 | order: !isNaN(index) ? index : Infinity 35 | }); 36 | 37 | cb(); 38 | } else { 39 | cb(null, file); 40 | } 41 | } 42 | 43 | function createInitFile (cb) { 44 | var buffers = _(parts).sortBy('order').map('content').value(); 45 | 46 | this.push(new File({ 47 | path: path.join(process.cwd(), 'init.js'), 48 | contents: Buffer.concat(buffers) 49 | })); 50 | 51 | cb(); 52 | } -------------------------------------------------------------------------------- /lib/plugins/modules/map.js: -------------------------------------------------------------------------------- 1 | var mapBuild = require('./map/build'), 2 | mapInitial = require('./map/initial'), 3 | mapFallback = require('./map/fallback'), 4 | pipeChain = require('../util/pipeChain'); 5 | 6 | module.exports = mapPlugin; 7 | 8 | /** 9 | * Collects information about modules in the src path. 10 | * Check out child plugins for more info. 11 | * @alias "modules.map" 12 | * @param {Object} cfg Config 13 | * @returns {stream.Transform} Stream 14 | */ 15 | function mapPlugin (cfg) { 16 | return pipeChain([ 17 | mapBuild(cfg), 18 | mapInitial(cfg), 19 | cfg.asyncMap && mapFallback(cfg) 20 | ]); 21 | } -------------------------------------------------------------------------------- /lib/plugins/modules/map/build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | es = require('event-stream'), 4 | File = require('vinyl'), 5 | vinylFile = require('vinyl-file'), 6 | _ = require('lodash'), 7 | clc = require('cli-color'), 8 | eachInStream = require('../../util/eachInStream'), 9 | generateAlias = require('./util/generateAlias'), 10 | extractDeclarationData = require('./util/extractDeclarationData'); 11 | 12 | module.exports = mapBuildPlugin; 13 | 14 | var currentConfig = null, 15 | modulesInfo = {}, 16 | cacheMode = false; 17 | 18 | /** 19 | * @ignore 20 | * Executes modules' content and retrieves declaration params (module name, dependencies list, etc.). 21 | * Assigns aliases to modules and builds strings of dependencies' aliases. 22 | * Creates file `map.json` with all that information. 23 | * @alias "modules.mapBuild" 24 | * @param {Object} cfg Config 25 | * @returns {stream.Transform} Stream 26 | */ 27 | function mapBuildPlugin (cfg) { 28 | cacheMode = !_.isEmpty(modulesInfo); 29 | currentConfig = cfg; 30 | 31 | return eachInStream(processFile, createMapJson); 32 | } 33 | 34 | function processFile (file, encoding, cb) { 35 | if (!isModule(file)) { 36 | cb(null, file); 37 | return; 38 | } 39 | 40 | var declarationData = extractDeclarationData(file, currentConfig.namespace), 41 | name = declarationData.name, 42 | alias = modulesInfo[name] ? modulesInfo[name].alias : generateAlias(); 43 | 44 | if (!name) { 45 | console.error(clc.red('[warn] ') + 'Cannot resolve module name from file ' + clc.magenta(file.relative)); 46 | cb(null, file); 47 | return; 48 | } 49 | 50 | if (!cacheMode && modulesInfo[name]) { 51 | console.error( 52 | clc.red('[error] ') + 53 | 'Duplicate module declaration: `' + 54 | clc.cyan(name) + 55 | '` from file: ' + 56 | clc.magenta(file.relative) 57 | ); 58 | process.exit(1); 59 | } 60 | 61 | if (declarationData.images) { 62 | var lodashedName = name.replace(/\./g, '_'); 63 | 64 | _.forEach(declarationData.images, _.bind(function (params, name) { 65 | var imageFile = vinylFile.readSync(path.join(path.dirname(file.path), params.src)); 66 | 67 | imageFile.path = 'images/' + lodashedName + '_' + name; 68 | 69 | if (path.extname(name) == '') { 70 | imageFile.path += path.extname(params.src); 71 | } 72 | 73 | imageFile.imageName = name; 74 | imageFile.skipDataUri = params.skipDataUri; 75 | 76 | this.push(imageFile); 77 | }, this)); 78 | } 79 | 80 | modulesInfo[name] = { 81 | alias: alias, 82 | declarationData: declarationData 83 | }; 84 | 85 | file.isModule = true; 86 | file.moduleName = name; 87 | file.moduleAlias = alias; 88 | 89 | cb(null, file); 90 | } 91 | 92 | function isModule (file) { 93 | return file.relative.indexOf('init#') == -1; 94 | } 95 | 96 | function createMapJson (cb) { 97 | this.push(new File({ 98 | path: path.join(process.cwd(), 'map.json'), 99 | contents: new Buffer(JSON.stringify(buildMap(), null, '\t')) 100 | })); 101 | 102 | cb(); 103 | } 104 | 105 | function buildMap () { 106 | return _.map(modulesInfo, function (info, name) { 107 | var decl = info.declarationData, 108 | row = [ 109 | name, 110 | info.alias, 111 | buildDependenciesString(decl.depends) 112 | ]; 113 | 114 | if (decl.key) { 115 | row[3] = decl.key; 116 | row[4] = decl.storage; 117 | } 118 | 119 | if (decl.dynamicDepends) { 120 | row[5] = _.mapValues(decl.dynamicDepends, function (func) { return func.toString(); }); 121 | } 122 | 123 | return row; 124 | }); 125 | } 126 | 127 | function buildDependenciesString (deps) { 128 | if (typeof deps == 'function') { 129 | return deps.toString(); 130 | } 131 | 132 | return _.map(deps, function (name) { 133 | if (!name) { 134 | return ''; 135 | } 136 | 137 | return typeof modulesInfo[name] == 'object' ? 138 | modulesInfo[name].alias : 139 | '=' + name + '='; 140 | }).join(''); 141 | } 142 | -------------------------------------------------------------------------------- /lib/plugins/modules/map/fallback.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | footer = require('gulp-footer'), 4 | concat = require('gulp-concat'), 5 | ymHelpers = require('ym-helpers'), 6 | join = require('../../util/join'), 7 | closure = require('../../js/closure'); 8 | 9 | module.exports = mapFallbackPlugin; 10 | 11 | var YM_HELPERS_SRC_DIR = ymHelpers.getSrcDir(); 12 | 13 | /** 14 | * @ignore 15 | * Adds "modules map fallback" for specified filter. 16 | * Map fallbacks allow asynchronous lazy loading information about modules (parts of map.js file) 17 | * by a module name filter. 18 | * @alias "modules.mapFallback" 19 | * @param {Object} cfg Config 20 | * @returns {stream.Transform} Stream 21 | */ 22 | function mapFallbackPlugin (cfg) { 23 | return join( 24 | gulp.src([ 25 | // TODO Sync modules. 26 | path.resolve(YM_HELPERS_SRC_DIR, 'util/id/*.js'), 27 | path.resolve(YM_HELPERS_SRC_DIR, 'util/querystring/*.js'), 28 | path.resolve(YM_HELPERS_SRC_DIR, 'util/script/*.js'), 29 | path.resolve(YM_HELPERS_SRC_DIR, 'util/jsonp/*.js'), 30 | 31 | path.resolve(__dirname, 'fallback/src/fallback.js') 32 | ]) 33 | .pipe(concat('init#fallback')) 34 | .pipe(footer('\nym.modules.fallbacks.register(' + buildFilter(cfg) + ', fallback);')) 35 | .pipe(closure()) 36 | ); 37 | } 38 | 39 | function buildFilter (cfg) { 40 | if (cfg.target == 'standalone') { 41 | return "'*'"; 42 | } 43 | 44 | if (cfg.fallbackFilter) { 45 | return cfg.fallbackFilter; 46 | } 47 | 48 | return '/^' + cfg.namespace.replace('.', '\.') + '\./'; 49 | } -------------------------------------------------------------------------------- /lib/plugins/modules/map/fallback/src/fallback.js: -------------------------------------------------------------------------------- 1 | var promise = null; 2 | 3 | function fallback (module, filter) { 4 | return promise || (promise = request(filter)); 5 | } 6 | 7 | function request (filter) { 8 | filter = encodeURIComponent(filter); 9 | 10 | return ym.modules.require(['util.jsonp', 'util.querystring', 'util.extend', 'system.ModuleLoader']) 11 | .spread(function (jsonp, querystring, extend, ModuleLoader) { 12 | var server = ym.env.server, 13 | url = server.url + '/map.js', 14 | requestParams = { 15 | filter: filter, 16 | mode: server.params.mode, 17 | version: server.version 18 | }, 19 | paddingKey = 'ym_map_fallback'; 20 | 21 | if (!server.params.short_jsonp_padding) { 22 | var cacheFactors = extend({ url: url }, requestParams), 23 | cacheKey = querystring.stringify(cacheFactors, '_', '=', { 24 | encodeURIComponent: function (str) { return str; } 25 | }); 26 | 27 | paddingKey += '_' + cacheKey.replace(/[:\/\.\?\&\\]/g, '_'); 28 | } 29 | 30 | return jsonp({ 31 | url: url, 32 | requestParams: requestParams, 33 | paddingKey: paddingKey 34 | }).then(function (map) { 35 | (new ModuleLoader(map, server)).defineAll(); 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /lib/plugins/modules/map/initial.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | path = require('path'), 3 | File = require('vinyl'), 4 | gulpIf = require('gulp-if'), 5 | eachInStream = require('../../util/eachInStream'); 6 | 7 | module.exports = mapInitialPlugin; 8 | 9 | /** 10 | * @ignore 11 | * Extracts information about initial modules from file `map.json` 12 | * (that is previously created by `modules/map/build` plugin) 13 | * and injects it into stream as part of `init.js` contents. 14 | * @alias "modules.mapInitial" 15 | * @param {Object} cfg Config 16 | * @returns {stream.Transform} Stream 17 | */ 18 | function mapInitialPlugin (cfg) { 19 | return gulpIf(isMap, eachInStream(function (file, _, cb) { 20 | var initialPart = preparePart(file.contents, cfg.asyncMap, cfg.initialMap); 21 | 22 | this.push(new File({ 23 | path: path.join(process.cwd(), 'init#map'), 24 | contents: new Buffer('ym.project.initialMap = ' + initialPart + ';') 25 | })); 26 | 27 | if (!cfg.asyncMap) { 28 | cb(); 29 | } else { 30 | cb(null, file); 31 | } 32 | })); 33 | } 34 | 35 | function isMap (file) { 36 | return file.relative == 'map.json'; 37 | } 38 | 39 | function preparePart (contents, asyncMap, initialModules) { 40 | var map = JSON.parse(contents); 41 | 42 | if (asyncMap) { 43 | map = extractPart(map, initialModules); 44 | } 45 | 46 | return stringifyMap(map); 47 | } 48 | 49 | function extractPart (map, initialModules) { 50 | if (!initialModules || !initialModules.length) { 51 | return []; 52 | } 53 | 54 | var moduleNames = _(map) 55 | .map(function (moduleInfo) { 56 | if (_.contains(initialModules, moduleInfo[0])) { 57 | return buildDepsTree(map, moduleInfo[0], moduleInfo[2]); 58 | } 59 | 60 | return null; 61 | }) 62 | .compact() 63 | .flatten() 64 | .value(); 65 | 66 | return _.filter(map, function (moduleInfo) { 67 | return _.contains(moduleNames, moduleInfo[0]); 68 | }); 69 | } 70 | 71 | function buildDepsTree (map, moduleName, depsAliases, tree) { 72 | tree || (tree = []); 73 | tree.push(moduleName); 74 | 75 | for (var i = 0, depAlias, depInfo; i < depsAliases.length; i += 2) { 76 | depAlias = depsAliases.substr(i, 2); 77 | depInfo = _.find(map, function (moduleInfo) { return moduleInfo[1] == depAlias; }); 78 | buildDepsTree(map, depInfo[0], depInfo[2], tree); 79 | } 80 | 81 | return tree; 82 | } 83 | 84 | // TODO shared with yms 85 | function stringifyMap (map) { 86 | var stack = []; 87 | 88 | _.each(map, function (moduleInfo) { 89 | stack.push('[' + _.map(moduleInfo, function (value, index) { 90 | if (index == 2 && value.slice(0, 8) == 'function') { 91 | return value; 92 | } else if (index == 5 && typeof value == 'object') { 93 | return '{ ' + _.map(value, function (v, k) { return k + ': ' + v; }).join(', ') + ' }'; 94 | } else { 95 | return '\'' + value + '\''; 96 | } 97 | }).join(', ') + ']'); 98 | }); 99 | 100 | return '[\n ' + stack.join(',\n ') + '\n]'; 101 | } 102 | -------------------------------------------------------------------------------- /lib/plugins/modules/map/util/extractDeclarationData.js: -------------------------------------------------------------------------------- 1 | var clc = require('cli-color'); 2 | 3 | module.exports = extractDeclarationData; 4 | 5 | function extractDeclarationData (file, namespace) { 6 | var info = {}, 7 | api = { 8 | modules: { 9 | importImages: function (hash) { 10 | info.images = hash; 11 | }, 12 | 13 | define: function (name, depends, fn) { 14 | if (typeof name == 'object') { 15 | var decl = name; 16 | 17 | info.name = decl.name; 18 | info.key = decl.key; 19 | info.storage = decl.storage; 20 | info.dynamicDepends = decl.dynamicDepends; 21 | info.depends = decl.depends; 22 | } else { 23 | info.name = name; 24 | info.depends = depends; 25 | } 26 | } 27 | } 28 | }; 29 | 30 | try { 31 | var contents = file.contents.toString(), 32 | declStart = contents.search(/,\s*function\s*\(\s*provide(,|\))/m), 33 | // Shorter files are processed 2x faster. 34 | short = declStart != -1 ? contents.substr(0, declStart) + ', null)' : null; 35 | 36 | // We should support both user namespace and `ym` scope variables. I.e. for default helpers. 37 | (new Function(namespace, 'ym', short || contents))(api, api); 38 | } catch (err) { 39 | console.error(clc.red('[error] ') + 'Module file not valid ' + clc.magenta(file.path)); 40 | console.error(err); 41 | } 42 | 43 | return info; 44 | } -------------------------------------------------------------------------------- /lib/plugins/modules/map/util/generateAlias.js: -------------------------------------------------------------------------------- 1 | module.exports = generateAlias; 2 | 3 | var ALPHABET_1 = '0123456789$-_.!*(),:;@qjizQJIZ', 4 | ALPHABET_2 = 'abcdefghklmnoprstuvwxyABCDEFGHKLMNOPRSTUVWXY' + ALPHABET_1, 5 | SIZE_1 = ALPHABET_1.length, 6 | SIZE_2 = ALPHABET_2.length, 7 | MAX_SIZE = SIZE_1 * SIZE_2; 8 | 9 | var currentIndex = 0; 10 | 11 | function generateAlias () { 12 | if (currentIndex + 1 >= MAX_SIZE) { 13 | throw new Error('Possible aliases combinations amount exceeded.'); 14 | } 15 | 16 | var symbolIndex1 = Math.floor(currentIndex / SIZE_2), 17 | symbolIndex2 = currentIndex % SIZE_2; 18 | 19 | currentIndex++; 20 | 21 | return ALPHABET_1[symbolIndex1] + ALPHABET_2[symbolIndex2]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/plugins/modules/minify.js: -------------------------------------------------------------------------------- 1 | var gulpIf = require('gulp-if'), 2 | uglify = require('gulp-uglify'), 3 | clc = require('cli-color'); 4 | moment = require('moment'); 5 | 6 | module.exports = minifyPlugin; 7 | 8 | /** 9 | * Runs `uglify()`. 10 | * @alias "modules.minify" 11 | * @returns {stream.Transform} Stream 12 | */ 13 | function minifyPlugin(cfg) { 14 | var options = typeof cfg.minify == 'object' ? cfg.minify : {}, 15 | loggedFirst = false; 16 | 17 | return gulpIf(function (file) { 18 | var condition = file.relative.match(new RegExp('.' + (options.extension || 'js') + '$')); 19 | 20 | if (condition && !loggedFirst) { 21 | console.log( 22 | '[' + clc.blackBright(moment().format('HH:mm:ss')) + 23 | '] Running ' + clc.cyan('uglify-js') + '...' 24 | ); 25 | loggedFirst = true; 26 | } 27 | 28 | return condition; 29 | }, uglify(options)); 30 | } 31 | -------------------------------------------------------------------------------- /lib/plugins/modules/namespace.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | header = require('gulp-header'), 4 | rename = require('gulp-rename'), 5 | join = require('../util/join'); 6 | 7 | module.exports = namespacePlugin; 8 | 9 | /** 10 | * Adds project namespace into the global context. 11 | * @alias "modules.namespace" 12 | * @returns {stream.Transform} Stream 13 | */ 14 | function namespacePlugin () { 15 | return join( 16 | gulp.src(path.resolve(__dirname, 'namespace/src/namespace.js')) 17 | .pipe(rename('init#namespace')) 18 | ); 19 | } -------------------------------------------------------------------------------- /lib/plugins/modules/namespace/src/namespace.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | if (!ym.project.namespace) { 3 | return; 4 | } 5 | 6 | if (typeof setupAsync == 'function') { 7 | ym.envCallbacks.push(function (env) { 8 | if (env.namespace !== false) { 9 | registerNamespace(global, env.namespace || ym.project.namespace, ym.ns); 10 | } 11 | }); 12 | } else { 13 | registerNamespace(global, ym.project.namespace, ym.ns); 14 | } 15 | 16 | function registerNamespace (parentNs, path, data) { 17 | if (path) { 18 | var subObj = parentNs; 19 | path = path.split('.'); 20 | var i = 0, l = path.length - 1, name; 21 | for (; i < l; i++) { 22 | if (path[i]) { 23 | subObj = subObj[name = path[i]] || (subObj[name] = {}); 24 | } 25 | } 26 | subObj[path[l]] = data; 27 | return subObj[path[l]]; 28 | } else { 29 | return data; 30 | } 31 | } 32 | })(this); -------------------------------------------------------------------------------- /lib/plugins/modules/plus.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | order = require('gulp-order'), 4 | concat = require('gulp-concat'), 5 | header = require('gulp-header'), 6 | footer = require('gulp-footer'), 7 | rename = require('gulp-rename'), 8 | es = require('event-stream'), 9 | extractFromCommonJs = require('../js/extractFromCommonJs'), 10 | extractFromContext = require('../js/extractFromContext'), 11 | join = require('../util/join'); 12 | 13 | module.exports = plusPlugin; 14 | 15 | /** 16 | * Injects `vow` and `Modules Plus` code. 17 | * `Modules Plus` is an additional layer to modular system `ym` that introduces: 18 | * — promises support 19 | * — dynamic dependencies and "predictor" 20 | * — modules `Definition` interface 21 | * — asynchronous storages 22 | * — providing of packages 23 | * — "map fallbacks" support 24 | * — synchronous `define` and `require` 25 | * — etc. 26 | * @alias "modules.plus" 27 | * @see https://www.npmjs.com/package/vow 28 | * @see https://www.npmjs.com/package/ym 29 | * @returns {stream.Transform} Stream 30 | */ 31 | function plusPlugin () { 32 | var vowStream = gulp.src(require.resolve('vow')) 33 | .pipe(header('var define, modules;')) 34 | .pipe(extractFromCommonJs('vow')), 35 | 36 | defineVowStream = gulp.src(path.resolve(__dirname, 'plus/src/modules/vow.js')) 37 | .pipe(footer('\nym.ns.vow = ym.vow;')) 38 | .pipe(rename('defineVow.js')), 39 | 40 | plusStream = gulp.src(path.resolve(__dirname, 'plus/src/modulesPlus.js')) 41 | .pipe(extractFromContext('modules')) 42 | .pipe(footer('\nym.ns.modules = ym.modules;')); 43 | 44 | return join( 45 | es.merge(vowStream, defineVowStream, plusStream) 46 | .pipe(order(['vow.js', 'defineVow.js', 'modulesPlus.js'])) 47 | .pipe(concat('init#plus')) 48 | ); 49 | } -------------------------------------------------------------------------------- /lib/plugins/modules/plus/src/modules/vow.js: -------------------------------------------------------------------------------- 1 | ym.modules.define('vow', [], function (provide) { provide(ym.vow); }); -------------------------------------------------------------------------------- /lib/plugins/modules/plus/src/modulesPlus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Специальная прослойка, которая добавляет промисы, фоллбеки, динамические зависимости и алиасы в модульную систему. 3 | */ 4 | (function (global, modulesSystem, undef) { 5 | var WATCH_RESOLVING_TIMEOUT = 10; // sec. 6 | 7 | var vow = ym.vow, 8 | 9 | slice = Array.prototype.slice, 10 | 11 | moduleByAliases = {}, 12 | entries = {}, 13 | 14 | keyNotFoundError = function (storage, key) { 15 | return new Error("The key \"" + key + "\" isn't declared in \"" + storage + "\" storage."); 16 | }, 17 | dynamicDependNotFoundError = function (dynamicDepend) { 18 | return new Error("The dynamic depend \"" + dynamicDepend + "\" not found."); 19 | }, 20 | noFallbackError = function (moduleName) { 21 | return new Error('Undefined module `' + moduleName + '` with no matching fallback.'); 22 | }, 23 | 24 | api; 25 | 26 | api = { 27 | fallbacks: new FallbackManager(), 28 | 29 | define: function (moduleName, depends, callback, context) { 30 | var _this = this, 31 | storage, key, dynamicDepends; 32 | 33 | if (typeof depends == 'function' && typeof callback != 'function') { 34 | callback = depends; 35 | context = callback; 36 | depends = []; 37 | } else if (typeof moduleName == 'object') { 38 | var data = moduleName; 39 | 40 | moduleName = data.name; 41 | depends = data.depends; 42 | callback = data.declaration; 43 | context = data.context; 44 | dynamicDepends = data.dynamicDepends; 45 | 46 | storage = data.storage; 47 | key = data.key; 48 | } 49 | 50 | if (!entries.hasOwnProperty(moduleName)) { 51 | entries[moduleName] = { name: moduleName }; 52 | } 53 | 54 | if (typeof depends == 'function') { 55 | depends = depends.call({ name: moduleName }, ym); 56 | } 57 | 58 | entries[moduleName].callback = callback; 59 | entries[moduleName].context = context; 60 | 61 | if (storage && key) { 62 | if (typeof key != 'string') { 63 | for (var i = 0, l = key.length; i < l; i++) { 64 | this._createKeyStorageRef(moduleName, key[i], storage); 65 | } 66 | } else { 67 | this._createKeyStorageRef(moduleName, key, storage); 68 | } 69 | 70 | entries[moduleName].key = key; 71 | entries[moduleName].storage = storage; 72 | } 73 | 74 | if (dynamicDepends) { 75 | entries[moduleName].dynamicDepends = dynamicDepends; 76 | } 77 | 78 | var onModuleLoad = api._createPatchedCallback(moduleName); 79 | 80 | if (depends != null) { 81 | var deps = []; 82 | for (var i = 0, l = depends.length; i < l; i++) { 83 | deps[i] = this._processModuleName(depends[i]); 84 | } 85 | 86 | deps = this.fallbacks.addRetrievers(deps); 87 | // Often dependecy module is simply defined after its dependant so we don't need fallbacks anymore. 88 | this.nextTick(function () { 89 | _this.fallbacks.removeRetrievers(modulesSystem.getDependencies(moduleName)); 90 | }); 91 | 92 | modulesSystem.define(moduleName, deps, onModuleLoad); 93 | } else { 94 | modulesSystem.define(moduleName, onModuleLoad); 95 | } 96 | 97 | return this; 98 | }, 99 | 100 | require: function (moduleNames, successCallback, errorCallback, context, afterRetrieve) { 101 | var deferred = vow.defer(), 102 | promise = deferred.promise(), 103 | data = undef; 104 | 105 | if (arguments.length == 3 && typeof errorCallback != 'function') { 106 | context = errorCallback; 107 | errorCallback = null; 108 | } else if (!moduleNames.hasOwnProperty('length') && typeof moduleNames == 'object') { 109 | var obj = moduleNames; 110 | moduleNames = obj.modules; 111 | successCallback = obj.successCallback; 112 | errorCallback = obj.errorCallback; 113 | context = obj.context; 114 | if (obj.hasOwnProperty('data')) { 115 | data = obj.data; 116 | } 117 | } 118 | 119 | moduleNames = (typeof moduleNames == 'string' || !moduleNames.hasOwnProperty('length')) ? [moduleNames] : moduleNames; 120 | 121 | var moduleNamesLength = moduleNames.length, 122 | result = this._processModuleList(moduleNames, data); 123 | 124 | moduleNames = result.list; 125 | 126 | if (ym.env.debug && !afterRetrieve) { 127 | this.watchResolving(moduleNames); 128 | } 129 | 130 | if (!result.error) { 131 | modulesSystem.require(moduleNames, function () { 132 | // TODO потенцально опасный код. 133 | // Для сокращения ожидания и кол-ва кода основной запрос и все динамические зависимости обрабатываются одним require. 134 | // Чтобы модули из динамических зависимостей отрабатывали раньше, чем явно запрощенные модули, они добалявляются в начало массива. 135 | // Если вдруг в модульной системе измениться порядок выполнения модулей, то что-то может сломаться. 136 | var array = slice.call(arguments, arguments.length - moduleNamesLength); 137 | deferred.resolve(array); 138 | successCallback && successCallback.apply(context || global, array); 139 | }, function (err) { 140 | if (!afterRetrieve) { 141 | api.fallbacks.retrieve(moduleNames).then(function () { 142 | deferred.resolve(api.require(moduleNames, successCallback, errorCallback, context, true)); 143 | }).fail(function (err) { 144 | deferred.reject(err); 145 | }); 146 | } else { 147 | deferred.reject(err); 148 | } 149 | }); 150 | } else { 151 | deferred.reject(result.error); 152 | } 153 | 154 | if (errorCallback && !afterRetrieve) { 155 | promise.fail(function (err) { 156 | errorCallback.call(context || global, err); 157 | }); 158 | } 159 | 160 | return promise; 161 | }, 162 | 163 | defineSync: function (moduleName, module) { 164 | // Этот метод пока не светится наружу. 165 | var storage, key; 166 | if (typeof moduleName == 'object') { 167 | var data = moduleName; 168 | module = data.module; 169 | storage = data.storage; 170 | key = data.key; 171 | moduleName = data.name; 172 | } 173 | 174 | if (api.isDefined(moduleName)) { 175 | var entry = entries[moduleName]; 176 | entry.name = moduleName; 177 | entry.module = module; 178 | entry.callback = function (provide) { 179 | provide(module); 180 | }; 181 | entry.context = null; 182 | } else { 183 | entries[moduleName] = { 184 | name: moduleName, 185 | module: module 186 | }; 187 | // Добавляем в модульную систему, чтобы можно было обращатьсяв зависимостях. 188 | api.define(moduleName, function (provide) { 189 | provide(module); 190 | }); 191 | } 192 | 193 | if (key && storage) { 194 | entries[moduleName].key = key; 195 | entries[moduleName].storage = storage; 196 | this._createKeyStorageRef(moduleName, key, storage); 197 | } 198 | }, 199 | 200 | requireSync: function (name, data) { 201 | // Этот метод пока не светится наружу. 202 | var definition = this.getDefinition(name), 203 | result = null; 204 | if (definition) { 205 | result = definition.getModuleSync.apply(definition, slice.call(arguments, 1)); 206 | } 207 | return result; 208 | }, 209 | 210 | // This method is being called with context of a module. 211 | providePackage: function (provide) { 212 | var module = this, 213 | depsValues = Array.prototype.slice.call(arguments, 1); 214 | 215 | api.require(['system.mergeImports']).spread(function (mergeImports) { 216 | provide(mergeImports.joinImports(module.name, {}, module.deps, depsValues)); 217 | }); 218 | }, 219 | 220 | getDefinition: function (name) { 221 | var result = null; 222 | name = this._processModuleName(name); 223 | 224 | if (entries.hasOwnProperty(name)) { 225 | result = new Definition(entries[name]); 226 | } 227 | 228 | return result; 229 | }, 230 | 231 | getState: function (name) { 232 | return modulesSystem.getState(this._processModuleName(name)); 233 | }, 234 | 235 | isDefined: function (name) { 236 | return modulesSystem.isDefined(this._processModuleName(name)); 237 | }, 238 | 239 | setOptions: function (options) { 240 | return modulesSystem.setOptions(options); 241 | }, 242 | 243 | flush: function () { 244 | return modulesSystem.flush(); 245 | }, 246 | 247 | nextTick: function (func) { 248 | return modulesSystem.nextTick(func); 249 | }, 250 | 251 | watchResolving: function (moduleNames) { 252 | if (!(typeof console == 'object' && typeof console.warn == 'function')) { 253 | return; 254 | } 255 | 256 | var _this = this; 257 | 258 | if (typeof this._failCounter == 'undefined') { 259 | this._failCounter = 0; 260 | } 261 | 262 | setTimeout(function () { 263 | if (_this._failCounter == 0) { 264 | setTimeout(function () { 265 | _this._failCounter = 0; 266 | }, 150); 267 | } 268 | 269 | for (var i = 0, l = moduleNames.length; i < l; i++) { 270 | if (_this.getState(moduleNames[i]) != 'RESOLVED') { 271 | _this._failCounter++; 272 | 273 | if (_this._failCounter == 5) { 274 | setTimeout(function () { 275 | console.warn('Timeout: Totally ' + _this._failCounter + 276 | ' modules were required but not resolved within ' + WATCH_RESOLVING_TIMEOUT + ' sec.'); 277 | }, 100); 278 | } else if (_this._failCounter > 5) { 279 | continue; 280 | } 281 | 282 | console.warn('Timeout: Module `' + moduleNames[i] + '` was required ' + 283 | 'but is still ' + _this.getState(moduleNames[i]) + ' within ' + WATCH_RESOLVING_TIMEOUT + ' sec.'); 284 | } 285 | } 286 | }, WATCH_RESOLVING_TIMEOUT * 1000); 287 | }, 288 | 289 | _createPatchedCallback: function (moduleName) { 290 | var _modulesPlus = this; 291 | 292 | return function () { 293 | var entry = entries[moduleName], 294 | array = slice.call(arguments, 0), 295 | callback = entry.callback, 296 | context = entry.context; 297 | 298 | if (ym.env.debug) { 299 | _modulesPlus.watchResolving([moduleName]); 300 | } 301 | 302 | array[0] = api._patchProvideFunction(array[0], moduleName); 303 | 304 | callback && callback.apply(context || this, array); 305 | }; 306 | }, 307 | 308 | _processModuleList: function (moduleList, data, ignoreCurrentNode) { 309 | var state = { 310 | list: [] 311 | }; 312 | 313 | for (var i = 0, l = moduleList.length; i < l; i++) { 314 | var moduleName = this._processModuleName(moduleList[i]); 315 | 316 | if (!moduleName) { 317 | state.error = keyNotFoundError(moduleList[i].storage, moduleList[i].key); 318 | break; 319 | } 320 | 321 | if (typeof data != 'undefined') { 322 | var depends = modulesSystem.getDependencies(moduleName), 323 | entry = entries[moduleName]; 324 | if (depends) { 325 | var dependsResult = this._processModuleList(depends, data, true); 326 | if (dependsResult.error) { 327 | state.error = dependsResult.error; 328 | break; 329 | } else { 330 | state.list = state.list.concat(dependsResult.list); 331 | } 332 | } 333 | 334 | if (entry && entry.dynamicDepends) { 335 | var dynamicDepends = []; 336 | for (var key in entry.dynamicDepends) { 337 | var depend = entry.dynamicDepends[key](data); 338 | // TOOD обсудить в ревью 339 | if (this._isDepend(depend)) { 340 | dynamicDepends.push(depend); 341 | } 342 | } 343 | var dependsResult = this._processModuleList(dynamicDepends, data); 344 | if (dependsResult.error) { 345 | state.error = dependsResult.error; 346 | break; 347 | } else { 348 | state.list = state.list.concat(dependsResult.list); 349 | } 350 | } 351 | 352 | if (this.fallbacks.isRetriever(moduleName)) { 353 | this.fallbacks.addRetrieverData(moduleName, data); 354 | } 355 | } 356 | 357 | if (!ignoreCurrentNode) { 358 | state.list.push(moduleName); 359 | } 360 | } 361 | 362 | return state; 363 | }, 364 | 365 | _createKeyStorageRef: function (moduleName, key, storage) { 366 | if (!moduleByAliases.hasOwnProperty(storage)) { 367 | moduleByAliases[storage] = {}; 368 | } 369 | moduleByAliases[storage][key] = moduleName; 370 | }, 371 | 372 | _processModuleName: function (moduleName) { 373 | if (typeof moduleName != 'string') { 374 | var storage = moduleName.storage; 375 | if (moduleByAliases.hasOwnProperty(storage)) { 376 | moduleName = moduleByAliases[storage][moduleName.key] || null; 377 | } else { 378 | moduleName = null; 379 | } 380 | } 381 | return moduleName; 382 | }, 383 | 384 | _patchProvideFunction: function (provide, moduleName) { 385 | var patchedProvide = function (module, error) { 386 | var entry = entries[moduleName]; 387 | entry.module = module; 388 | provide(module, error); 389 | if (!error) { 390 | delete entry.callback; 391 | delete entry.context; 392 | } 393 | }; 394 | patchedProvide.provide = patchedProvide; 395 | patchedProvide.dynamicDepends = { 396 | getValue: function (key, data) { 397 | var deferred = vow.defer(), 398 | entry = entries[moduleName]; 399 | if (entry.dynamicDepends && entry.dynamicDepends.hasOwnProperty(key)) { 400 | var depend = entry.dynamicDepends[key](data); 401 | deferred.resolve( 402 | api._isDepend(depend) ? 403 | api.getDefinition(depend).getModule(data) : 404 | [depend] 405 | ); 406 | } else { 407 | deferred.reject(dynamicDependNotFoundError(key)); 408 | } 409 | return deferred.promise(); 410 | }, 411 | 412 | getValueSync: function (key, data) { 413 | var result = undef, 414 | entry = entries[moduleName]; 415 | if (entry.dynamicDepends && entry.dynamicDepends.hasOwnProperty(key)) { 416 | var depend = entry.dynamicDepends[key](data); 417 | result = api._isDepend(depend) ? 418 | api.getDefinition(depend).getModuleSync(data) : 419 | depend; 420 | } 421 | return result; 422 | } 423 | }; 424 | return patchedProvide; 425 | }, 426 | 427 | _isDepend: function (depend) { 428 | return (typeof depend == 'string') || (depend && depend.key && depend.storage); 429 | } 430 | }; 431 | 432 | function Definition (entry) { 433 | this.entry = entry; 434 | } 435 | 436 | Definition.prototype.getModuleKey = function () { 437 | return this.entry.key; 438 | }; 439 | 440 | Definition.prototype.getModuleStorage = function () { 441 | return this.entry.storage; 442 | }; 443 | 444 | Definition.prototype.getModuleName = function () { 445 | return this.entry.name; 446 | }; 447 | 448 | Definition.prototype.getModuleSync = function (data) { 449 | if (arguments.length > 0) { 450 | var dynamicDepends = this.entry.dynamicDepends; 451 | for (var key in dynamicDepends) { 452 | var depend = dynamicDepends[key](data); 453 | if (api._isDepend(depend) && !api.getDefinition(depend).getModuleSync(data)) { 454 | return undef; 455 | } 456 | } 457 | } 458 | return this.entry.module; 459 | }; 460 | 461 | Definition.prototype.getModule = function (data) { 462 | var params = { 463 | modules: [ 464 | this.entry.name 465 | ] 466 | }; 467 | if (data) { 468 | params.data = data; 469 | } 470 | return api.require(params); 471 | }; 472 | 473 | var RETRIEVER_PREFIX = '_retriever@'; 474 | 475 | function FallbackManager () { 476 | this._fallbacks = []; 477 | this._retrieversData = {}; 478 | } 479 | 480 | FallbackManager.prototype.register = function (filter, fallback) { 481 | if (!filter || filter == '*') { 482 | this._fallbacks.push({ 483 | filter: filter || '*', 484 | func: fallback 485 | }); 486 | } else { 487 | this._fallbacks.unshift({ 488 | filter: filter, 489 | func: fallback 490 | }); 491 | } 492 | }; 493 | 494 | FallbackManager.prototype.retrieve = function (moduleNames) { 495 | if (typeof moduleNames == 'string') { 496 | moduleNames = [moduleNames]; 497 | } 498 | 499 | var definePromises = []; 500 | 501 | for (var i = 0, l = moduleNames.length; i < l; i++) { 502 | var deferred = vow.defer(), 503 | moduleName = moduleNames[i]; 504 | 505 | definePromises[i] = deferred.promise(); 506 | 507 | if (api.isDefined(moduleName)) { 508 | deferred.resolve(true); 509 | 510 | continue; 511 | } 512 | 513 | var fallback = this.find(moduleName); 514 | 515 | if (!fallback) { 516 | deferred.reject(noFallbackError(moduleName)); 517 | 518 | break; 519 | } 520 | 521 | deferred.resolve(fallback.func(moduleName, fallback.filter)); 522 | } 523 | 524 | return vow.all(definePromises); 525 | }; 526 | 527 | FallbackManager.prototype.find = function (moduleName) { 528 | for (var i = 0, l = this._fallbacks.length; i < l; i++) { 529 | var filter = this._fallbacks[i].filter; 530 | 531 | if (filter === '*') { 532 | return this._fallbacks[i]; 533 | } 534 | 535 | if (typeof filter == 'function' && filter(moduleName)) { 536 | return this._fallbacks[i]; 537 | } 538 | 539 | if (moduleName.match(filter)) { 540 | return this._fallbacks[i]; 541 | } 542 | } 543 | 544 | return null; 545 | }; 546 | 547 | FallbackManager.prototype.addRetrievers = function (moduleNames) { 548 | var res = []; 549 | 550 | for (var i = 0, l = moduleNames.length, moduleName, retrieverName; i < l; i++) { 551 | moduleName = moduleNames[i]; 552 | 553 | if (api.isDefined(moduleName)) { 554 | res.push(moduleName); 555 | continue; 556 | } 557 | 558 | retrieverName = RETRIEVER_PREFIX + moduleName; 559 | res.push(retrieverName); 560 | 561 | if (!api.isDefined(retrieverName)) { 562 | this._defineRetriever(retrieverName); 563 | } 564 | } 565 | 566 | return res; 567 | }; 568 | 569 | FallbackManager.prototype.removeRetrievers = function (deps) { 570 | for (var i = 0, l = deps.length, moduleName; i < l; i++) { 571 | if (this.isRetriever(deps[i]) && !this._retrieversData[deps[i]]) { 572 | moduleName = deps[i].replace(RETRIEVER_PREFIX, ''); 573 | 574 | if (api.isDefined(moduleName)) { 575 | deps[i] = moduleName; 576 | } 577 | } 578 | } 579 | }; 580 | 581 | FallbackManager.prototype.isRetriever = function (moduleName) { 582 | return moduleName.indexOf(RETRIEVER_PREFIX) === 0; 583 | }; 584 | 585 | FallbackManager.prototype.addRetrieverData = function (retrieverName, data) { 586 | if (!this._retrieversData[retrieverName]) { 587 | this._retrieversData[retrieverName] = []; 588 | } 589 | 590 | this._retrieversData[retrieverName].push(data); 591 | }; 592 | 593 | FallbackManager.prototype._defineRetriever = function (retrieverName) { 594 | var _this = this; 595 | 596 | api.define(retrieverName, [], function (provide) { 597 | var moduleName = this.name.replace(RETRIEVER_PREFIX, ''); 598 | 599 | _this.retrieve(moduleName) 600 | .then(function () { return _this._requireAfterRetrieve(moduleName); }) 601 | .spread(provide) 602 | .fail(provide); 603 | }); 604 | }; 605 | 606 | FallbackManager.prototype._requireAfterRetrieve = function (moduleName) { 607 | var data = this._retrieversData[RETRIEVER_PREFIX + moduleName]; 608 | 609 | if (!data) { 610 | return api.require(moduleName); 611 | } 612 | 613 | // Same module with different data could be required in parallel so we must handle all available data each time. 614 | var multipleRequires = []; 615 | 616 | for (var i = 0, l = data.length; i < l; i++) { 617 | multipleRequires.push(api.require({ 618 | modules: [moduleName], 619 | data: data[i] 620 | })); 621 | } 622 | 623 | return vow.all(multipleRequires) 624 | .then(function (multiple) { return multiple[0]; }); 625 | }; 626 | 627 | global.modules = api; 628 | })(this, ym.modules); 629 | -------------------------------------------------------------------------------- /lib/plugins/modules/setup.js: -------------------------------------------------------------------------------- 1 | var file = require('gulp-file'); 2 | 3 | module.exports = setupPlugin; 4 | 5 | 6 | /** 7 | * Injects main JS object `ym` with base project params as a part of `init.js`. 8 | * @alias "modules.setup" 9 | * @param {Object} cfg Config 10 | * @returns {stream.Transform} Stream 11 | */ 12 | function setupPlugin (cfg) { 13 | var ym = {}; 14 | 15 | ym.project = { 16 | preload: cfg.preload, 17 | namespace: cfg.namespace, 18 | jsonpPrefix: cfg.jsonpPrefix || '', 19 | loadLimit: 500 20 | }; 21 | 22 | ym.ns = {}; 23 | 24 | ym.env = {}; 25 | ym.envCallbacks = []; 26 | 27 | return file('init#setup', 'var ym = ' + JSON.stringify(ym, null, '\t') + ';'); 28 | } 29 | -------------------------------------------------------------------------------- /lib/plugins/modules/store.js: -------------------------------------------------------------------------------- 1 | var storeAsync = require('./store/async'), 2 | storeSolid = require('./store/solid'); 3 | 4 | module.exports = storePlugin; 5 | 6 | /** 7 | * Stores built modules and info files into a specified folder in a disk. 8 | * Check out child plugins for more info. 9 | * @alias "modules.store" 10 | * @param {Object} cfg Config 11 | * @returns {stream.Transform} Stream 12 | */ 13 | function storePlugin (cfg) { 14 | return cfg.store == 'async' ? storeAsync(cfg) : storeSolid(cfg); 15 | } -------------------------------------------------------------------------------- /lib/plugins/modules/store/async.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | md5 = require('md5-jkmyers'), 3 | gulpIf = require('gulp-if'), 4 | eachInStream = require('../../util/eachInStream'), 5 | clc = require('cli-color'), 6 | moment = require('moment'); 7 | 8 | module.exports = storeAsyncPlugin; 9 | 10 | var count; 11 | 12 | /** 13 | * @ignore 14 | * Renames modules regarding to MD5 hash sum of their short alias. 15 | * Used to store for asynchronous modules loading. 16 | * @alias "modules.storeAsync" 17 | * @returns {stream.Transform} Stream 18 | */ 19 | function storeAsyncPlugin () { 20 | count = 0; 21 | return gulpIf(isModule, eachInStream(processModule, printResults)); 22 | } 23 | 24 | function processModule (file, enc, cb) { 25 | count++; 26 | file.path = createPath(file.base, md5(file.moduleAlias)); 27 | cb(null, file); 28 | } 29 | 30 | function isModule (file) { 31 | return file.isModule; 32 | } 33 | 34 | function createPath (base, hash) { 35 | return path.join(base, hash[0], hash[1], hash); 36 | } 37 | 38 | function printResults (cb) { 39 | console.log( 40 | '[' + clc.blackBright(moment().format('HH:mm:ss')) + 41 | '] Stored ' + clc.green.bold(count) + ' asynchronous module(s)' 42 | ); 43 | cb(); 44 | } -------------------------------------------------------------------------------- /lib/plugins/modules/store/solid.js: -------------------------------------------------------------------------------- 1 | var concat = require('gulp-concat'), 2 | remember = require('gulp-remember'), 3 | order = require('gulp-order'), 4 | closure = require('../../js/closure'), 5 | pipeChain = require('../../util/pipeChain'); 6 | 7 | module.exports = storeSolidPlugin; 8 | 9 | /** 10 | * @ignore 11 | * Creates a single file of all concatenated modules preceded by a `init.js` contents. 12 | * Used for synchronous single-file projects. 13 | * @alias "modules.storeSolid" 14 | * @returns {stream.Transform} Stream 15 | */ 16 | function storeSolidPlugin (cfg) { 17 | return pipeChain( 18 | remember(cfg.name || 'ymb#default'), 19 | order(['init.js', '*']), 20 | concat(cfg.concatTo), 21 | closure(cfg) 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/plugins/modules/ym.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | file = require('gulp-file'), 4 | footer = require('gulp-footer'), 5 | rename = require('gulp-rename'), 6 | join = require('../util/join'), 7 | extractFromCommonJs = require('../js/extractFromCommonJs'); 8 | 9 | module.exports = ymPlugin; 10 | 11 | /** 12 | * In case of standalone project injects `ym` modular system source code as part of `init.js` contents. 13 | * In case of plugin project only refers a property from a main project namespace. 14 | * @alias "modules.ym" 15 | * @see https://www.npmjs.com/package/ym 16 | * @returns {stream.Transform} Stream 17 | */ 18 | function ymPlugin (cfg) { 19 | return cfg.target == 'plugin' ? 20 | file('init#ym', 'ym.modules = global[\'' + cfg.namespace + '\'].modules;') : 21 | join( 22 | gulp.src(require.resolve('ym')) 23 | .pipe(extractFromCommonJs('modules')) 24 | .pipe(footer([ 25 | 'ym.modules.setOptions({', 26 | ' trackCircularDependencies: true,', 27 | ' allowMultipleDeclarations: false', 28 | '});', 29 | 'ym.ns.modules = ym.modules;' 30 | ].join('\n'))) 31 | .pipe(rename('init#ym')) 32 | ); 33 | } -------------------------------------------------------------------------------- /lib/plugins/public.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | js: { 3 | closure: require('./js/closure') 4 | }, 5 | 6 | css: { 7 | images: require('./css/images'), 8 | optimize: require('gulp-csso'), 9 | toModules: require('./css/toModules') 10 | }, 11 | 12 | templates: { 13 | compile: require('./templates/compile'), 14 | toModules: require('./templates/toModules') 15 | }, 16 | 17 | modules: { 18 | setup: require('./modules/setup'), 19 | ym: require('./modules/ym'), 20 | plus: require('./modules/plus'), 21 | map: require('./modules/map'), 22 | async: require('./modules/async'), 23 | helpers: require('./modules/helpers'), 24 | namespace: require('./modules/namespace'), 25 | init: require('./modules/init'), 26 | // fallback: require('./modules/fallback'), 27 | minify: require('./modules/minify'), 28 | store: require('./modules/store') 29 | }, 30 | 31 | util: { 32 | pipeChain: require('./util/pipeChain'), 33 | eachInStream: require('./util/eachInStream'), 34 | join: require('./util/join') 35 | }, 36 | 37 | cache: require('gulp-cached'), 38 | remember: require('gulp-remember'), 39 | if: require('gulp-if') 40 | }; -------------------------------------------------------------------------------- /lib/plugins/templates/compile.js: -------------------------------------------------------------------------------- 1 | var vowNode = require('vow-node'), 2 | ymHelpers = require('ym-helpers'), 3 | eachInStream = require('../util/eachInStream'); 4 | 5 | module.exports = compilePlugin; 6 | 7 | var PLUGIN_NAME = 'ym-templates-compile'; 8 | 9 | var parserPromise = vowNode.promisify(ymHelpers.require)(['template.Parser']).then(function (TemplateParser) { 10 | return new TemplateParser(); 11 | }); 12 | 13 | /** 14 | * Pre-compiles HTML templates using `template.Parser` module from `ym-helpers` package. 15 | * @alias "templates.compile" 16 | * @see https://www.npmjs.com/package/ym-helpers 17 | * @returns {stream.Transform} Stream 18 | */ 19 | function compilePlugin () { 20 | return eachInStream(function (file, encoding, cb) { 21 | parserPromise.then(function (templateParser) { 22 | var tree = templateParser.parse(file.contents.toString()), 23 | str = JSON.stringify(tree); 24 | 25 | file.contents = new Buffer(str); 26 | 27 | cb(null, file); 28 | }).done(); 29 | }, PLUGIN_NAME); 30 | } -------------------------------------------------------------------------------- /lib/plugins/templates/toModules.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | fs = require('vow-fs'), 3 | eachInStream = require('../util/eachInStream'); 4 | 5 | module.exports = toModulesPlugin; 6 | 7 | var PLUGIN_NAME = 'ym-templates-to-modules'; 8 | 9 | /** 10 | * Wraps HTML code into an `ym` module. 11 | * @alias "templates.toModules" 12 | * @returns {stream.Transform} Stream 13 | */ 14 | function toModulesPlugin () { 15 | return eachInStream(function (file, encoding, cb) { 16 | checkFromModuleJson(file) 17 | .then(function (json) { 18 | var moduleName = json && json.name || path.basename(file.path), 19 | deps = parseDeps(json && json.depends); 20 | 21 | file.contents = Buffer.concat([ 22 | new Buffer('ym.modules.define(\'' + moduleName + '\', ' + deps + ', function (provide) {\nprovide('), 23 | file.contents, 24 | new Buffer(');\n});') 25 | ]); 26 | 27 | cb(null, file); 28 | }).done(); 29 | }, PLUGIN_NAME); 30 | } 31 | 32 | function checkFromModuleJson (file) { 33 | var filename = path.dirname(file.path) + '/module.json'; 34 | 35 | return fs.read(filename) 36 | .then(function (contents) { 37 | try { 38 | return JSON.parse(contents); 39 | } catch (e) { 40 | console.error('JSON parsing of ' + filename + 'failed: ', e); 41 | } 42 | }) 43 | .fail(function () { return null; }); 44 | } 45 | 46 | function parseDeps (deps) { 47 | // TODO func depends 48 | 49 | return JSON.stringify(deps || []); 50 | } -------------------------------------------------------------------------------- /lib/plugins/util/eachInStream.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'), 2 | PluginError = require('gulp-util').PluginError; 3 | 4 | module.exports = eachInStream; 5 | 6 | /** 7 | * Helper for running handlers only for non empty buffers in the stream. 8 | * @alias "util.eachInStream" 9 | * @returns {stream.Transform} Stream 10 | */ 11 | function eachInStream (bufferHandler, endHandler, pluginName) { 12 | if (typeof pluginName != 'string') { 13 | pluginName = typeof endHandler == 'string' ? endHandler : 'ym-plugin'; 14 | } 15 | endHandler = typeof endHandler == 'function' ? endHandler : undefined; 16 | 17 | return through.obj(function (file, encoding, cb) { 18 | if (file.isNull()) { 19 | cb(null, file); 20 | 21 | } else if (file.isStream()) { 22 | cb(new PluginError(pluginName, 'Streaming not supported', { 23 | fileName: file.path, 24 | showStack: false 25 | })); 26 | 27 | } else if (file.isBuffer()) { 28 | bufferHandler.call(this, file, encoding, cb); 29 | } 30 | }, endHandler); 31 | } -------------------------------------------------------------------------------- /lib/plugins/util/join.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'), 2 | es = require('event-stream'); 3 | 4 | module.exports = join; 5 | 6 | /** 7 | * Allows to join stream as an "incoming branch" for some other main stream. 8 | * @alias "util.join" 9 | * @returns {stream.Transform} Stream 10 | */ 11 | function join (joinStream) { 12 | var inputStream = through.obj(); 13 | 14 | return es.duplex(inputStream, es.merge(joinStream, inputStream)); 15 | } -------------------------------------------------------------------------------- /lib/plugins/util/pipeChain.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'), 2 | es = require('event-stream'); 3 | 4 | module.exports = pipeChain; 5 | 6 | /** 7 | * Allows to create a chain of plugins where streams will be piped from one plugin to another. 8 | * Later this chain can be injected anywhere in a pipeline, 9 | * so pipeline contents will get inside the first plugin and the go out from the last one. 10 | * @alias "util.pipeChain" 11 | * @returns {stream.Transform} Stream 12 | */ 13 | function pipeChain () { 14 | var inputStream = through.obj(); 15 | 16 | if (!arguments.length) { 17 | return inputStream; 18 | } 19 | 20 | var plugins = 'length' in arguments[0] ? arguments[0] : arguments, 21 | outputStream = inputStream; 22 | 23 | for (var i = 0, l = plugins.length; i < l; i++) { 24 | if (!plugins[i]) { 25 | continue; 26 | } 27 | 28 | outputStream = outputStream.pipe(plugins[i]); 29 | } 30 | 31 | return es.duplex(inputStream, outputStream); 32 | } -------------------------------------------------------------------------------- /lib/resolveBuildConfig.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | fs = require('fs'), 3 | _ = require('lodash'), 4 | extend = require('extend'), 5 | minimist = require('minimist'), 6 | clc = require('cli-color'), 7 | moment = require('moment'), 8 | config = require('../defaults/build.default.json'); 9 | 10 | module.exports = resolveBuildConfig; 11 | 12 | function resolveBuildConfig () { 13 | var localConfig; 14 | 15 | try { 16 | localConfig = fs.readFileSync(path.resolve('build.json')); 17 | } catch (err) { 18 | if (err.code != 'ENOENT') { 19 | throw err; 20 | } 21 | } 22 | 23 | if (localConfig) { 24 | extend(true, config, JSON.parse(localConfig)); 25 | } 26 | 27 | var args = minimist(process.argv), 28 | hasModes = 'modes' in config, 29 | argsMode = args.mode || args.m || 'default', 30 | mode = argsMode != 'default' ? argsMode : (hasModes && _.findKey(config.modes, 'default')), 31 | missingMode = hasModes && !mode, 32 | wrongMode = mode && (!hasModes || typeof config.modes[mode] != 'object'); 33 | 34 | if (missingMode || wrongMode) { 35 | throw new Error('You should specify one of modes listed in your `build.json`.'); 36 | } 37 | 38 | if (mode) { 39 | extend(true, config, config.modes[mode], { mode: mode }); 40 | } 41 | 42 | console.log( 43 | '[' + clc.blackBright(moment().format('HH:mm:ss')) + '] ' + 44 | 'Using ' + 45 | clc.magenta(localConfig ? 'build.json' : 'build.default.json') + 46 | ' with mode ' + 47 | clc.cyan(mode || 'default') 48 | ); 49 | 50 | return config; 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ymb", 3 | "version": "2.0.5", 4 | "description": "Builder for projects based on ym modules. Built on top of gulp task runner.", 5 | "author": "Alexander Zinchuk ", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/yandex/ymb" 9 | }, 10 | "bugs": { 11 | "type": "git", 12 | "url": "https://github.com/yandex/ymb/issues" 13 | }, 14 | "keywords": [ 15 | "ym", 16 | "modules", 17 | "builder" 18 | ], 19 | "engines": { 20 | "node": "^4.2.0" 21 | }, 22 | "dependencies": { 23 | "cli-color": "^1.1.0", 24 | "del": "^2.2.0", 25 | "event-stream": "3.3.4", 26 | "extend": "^3.0.0", 27 | "gulp": "github:gulpjs/gulp#89acc5c17e115ec03ec6b17341c3ffdf5e2db837", 28 | "gulp-cached": "^1.1.0", 29 | "gulp-concat": "^2.4.1", 30 | "gulp-csso": "^1.1.0", 31 | "gulp-file": "^0.2.0", 32 | "gulp-footer": "^1.0.5", 33 | "gulp-header": "^1.1.1", 34 | "gulp-if": "^2.0.0", 35 | "gulp-order": "^1.1.1", 36 | "gulp-remember": "^0.3.0", 37 | "gulp-rename": "^1.2.0", 38 | "gulp-uglify": "^1.0.1", 39 | "gulp-util": "^3.0.1", 40 | "gulp-wrapper": "^1.0.0", 41 | "lodash": "^4.6.1", 42 | "md5-jkmyers": "0.0.1", 43 | "minimist": "^1.1.0", 44 | "moment": "^2.17.1", 45 | "through2": "^2.0.1", 46 | "vinyl": "^1.1.1", 47 | "vinyl-file": "^2.0.0", 48 | "vow": "ajaxy/vow", 49 | "vow-fs": "^0.3.2", 50 | "vow-node": "^0.3.0", 51 | "ym": "ajaxy/ym", 52 | "ym-helpers": "^1.0.0-rc" 53 | }, 54 | "bin": { 55 | "ymb": "./bin/ymb.js" 56 | }, 57 | "scripts": { 58 | "docs": "jsdoc2md \"lib/plugins/**/*.js\" > docs/plugins.md" 59 | }, 60 | "preferGlobal": false, 61 | "license": "MPL-2.0", 62 | "devDependencies": { 63 | "jsdoc-to-markdown": "^1.1.1" 64 | } 65 | } 66 | --------------------------------------------------------------------------------