├── .gitignore ├── .jshintrc ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .gitignore ├── expected ├── amd │ ├── custom-ext.es3 │ ├── custom-ext.js │ ├── inner │ │ └── first.js │ ├── outer.es3 │ ├── outer.js │ ├── reexport-es3.js │ ├── reexport.es3 │ └── reexport.js ├── bundledAmd │ └── myModule.js ├── bundledNamedAmd │ └── myModule.js ├── cjs │ ├── inner │ │ └── first.js │ ├── outer.js │ └── reexport.js ├── namedAmd │ ├── inner │ │ └── first.js │ ├── outer.js │ ├── reexport-es3.js │ └── reexport.js ├── namedAmdCustom │ └── subdir │ │ └── custom-name.js └── umd │ └── myModule.js ├── fixtures ├── bundle.js ├── custom-ext.es6 ├── file.answer ├── inner │ └── first.js ├── outer.js ├── reexport-es3.js ├── reexport.js └── subdir │ └── custom-name.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "boss": true, 4 | "curly": true, 5 | "debug": false, 6 | "devel": true, 7 | "eqeqeq": true, 8 | "evil": true, 9 | "forin": false, 10 | "immed": false, 11 | "laxbreak": false, 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": false, 15 | "nonew": false, 16 | "nomen": false, 17 | "onevar": false, 18 | "plusplus": false, 19 | "regexp": false, 20 | "undef": true, 21 | "sub": true, 22 | "strict": false, 23 | "white": false, 24 | "eqnull": true, 25 | "esnext": true, 26 | "unused": true 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | +The Ember team and community are committed to everyone having a safe and inclusive experience. 2 | + 3 | +**Our Community Guidelines / Code of Conduct can be found here**: 4 | + 5 | +http://emberjs.com/guidelines/ 6 | + 7 | +For a history of updates, see the page history here: 8 | + 9 | +https://github.com/emberjs/website/commits/master/source/guidelines.html.erb 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Edward Faulkner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #broccoli-es6modules 2 | 3 | [![Dependency Status](https://david-dm.org/ember-cli/broccoli-es6modules.svg)](https://david-dm.org/ember-cli/broccoli-es6modules) 4 | [![devDependency Status](https://david-dm.org/ember-cli/broccoli-es6modules/dev-status.svg)](https://david-dm.org/ember-cli/broccoli-es6modules#info=devDependencies) 5 | 6 | **Unmaintained! This project is no longer being maintained by the Ember CLI team.** 7 | Ember CLI has switched over to the [broccoli-babel-transpiler](https://github.com/babel/broccoli-babel-transpiler) and [ember-cli-babel](https://github.com/babel/ember-cli-babel) plugins. 8 | If you would like to take off maintenance of this project please let us know! 9 | 10 | ES6Modules is a broccoli filter that transpiles source code in a 11 | project from ES6 modules to ES5 modules in AMD, CJS, or UMD styles. 12 | 13 | ES6Modules has two modes of transpilation: 1-to-1 (per-file) and n-to-1 (bundled); 14 | 15 | ## 1-to-1 transpiles 16 | 17 | 1-to-1 mode transpiles every file in a tree from ES6 to the format specified 18 | as the `format` option. 19 | 20 | For example, if you have the following directory: 21 | 22 | ```shell 23 | src/ 24 | ├── lib 25 | │   ├── promise.js 26 | │   ├── rsvp.js 27 | │   └── utils.js 28 | └── main.js 29 | ``` 30 | 31 | And convert the files using ES6Modules: 32 | 33 | ```javascript 34 | var tree = './src'; 35 | var ES6Modules = require('broccoli-es6modules'); 36 | var amdFiles = new ES6Modules(tree, { 37 | format: 'amd' 38 | }); 39 | ``` 40 | 41 | You will have the following tree in your compiled output 42 | 43 | 44 | ```shell 45 | ├── lib 46 | │   ├── promise.js 47 | │   ├── rsvp.js 48 | │   └── utils.js 49 | └── main.js 50 | ``` 51 | 52 | And each file's contents will be converted from ES6 module syntax to AMD style. 53 | 54 | ## n-to-1 bundled transpiles 55 | 56 | n-to-1 mode begins transpiling at a single entry point and walks the dependency graph starting 57 | with the imported statements in the entry point. 58 | 59 | This will result in a single, bundled file for your library containing any 60 | files referenced by `import` statements. Enable this mode by supplying a 61 | `bundleOptions` option with (at least) a `name` for your resulting file and a 62 | file to be the `entry` point: 63 | 64 | For example, if you have the following directory: 65 | 66 | ```shell 67 | src/ 68 | ├── lib 69 | │   ├── promise.js 70 | │   ├── rsvp.js 71 | │   └── utils.js 72 | └── main.js 73 | ``` 74 | 75 | And convert these files using ES6Modules: 76 | 77 | ```javascript 78 | var tree = './src'; 79 | var ES6Modules = require('broccoli-es6modules'); 80 | var amdFiles = new ES6Modules(tree, { 81 | format: 'amd', 82 | bundleOptions: { 83 | entry: 'main.js', 84 | name: 'myLib' 85 | } 86 | }); 87 | ``` 88 | 89 | You will have the following tree in your compiled output 90 | 91 | 92 | ```shell 93 | └── myLib.js 94 | ``` 95 | 96 | The contents of that file will be any code imported from `main.js`'s import process. 97 | 98 | ## Options 99 | 100 | ### format 101 | The ES5 module format to convert to. Available options are: 102 | 103 | * ['amd'](http://requirejs.org/docs/whyamd.html#amd) 104 | * ['namedAmd'](http://requirejs.org/docs/whyamd.html#namedmodules) 105 | * ['cjs'](http://requirejs.org/docs/whyamd.html#commonjs) 106 | * ['umd'](https://github.com/umdjs/umd) 107 | 108 | 109 | In `namedAmd` the file path (with '.js' removed) of the file relative to the tree root 110 | is used as the module's name. 111 | 112 | So, if you have the following tree: 113 | 114 | ``` 115 | 116 | ├── inner 117 | │   └── first.js 118 | └── outer.js 119 | ``` 120 | 121 | You will have the following module names passed to AMD's `define` call: 122 | 'bundle', 'inner/first', and 'outer'. 123 | 124 | Because this strategy combined with UMD would result in _many_ properties being set on 125 | the `window` object in the browser, `umd` format will throw an error if used without also 126 | providing `bundleOptions`. 127 | 128 | ### formatModuleName 129 | 130 | An optional function for `namedAmd` module format to customize the module name passed to esperanto. 131 | 132 | For example if you have `foo/bar.js`: 133 | 134 | ``` 135 | export default function() { 136 | } 137 | ``` 138 | 139 | and a `formatModuleName` option of: 140 | 141 | ``` 142 | formatModuleName: function(moduleName) { 143 | return moduleName.replace('foo/', ''); 144 | } 145 | ``` 146 | 147 | 148 | Esperanto will give you: 149 | 150 | ``` 151 | define('bar', ['exports'], function(exports) { 152 | 153 | 'use strict'; 154 | 155 | exports['default'] = function() { 156 | } 157 | 158 | }); 159 | ``` 160 | 161 | ### esperantoOptions 162 | ES6Modules wraps the [esperanto](http://esperantojs.org/) library. All [options described for 163 | esperanto](https://github.com/esperantojs/esperanto/wiki/Converting-a-single-module#options) 164 | can be provided here. All defaults are identical to those used by esperanto. 165 | 166 | Because the ES6Modules uses each file's name as its module name, the esperanto `amdName` and 167 | `sourceMapSource` options are ignored. 168 | 169 | ### bundleOptions 170 | ES6Modules wraps the [esperanto](http://esperantojs.org/) library. All [options described for 171 | esperanto bundling](https://github.com/esperantojs/esperanto/wiki/Bundling-multiple-ES6-modules#other-formats-and-options) 172 | can be provided here. All defaults are identical to those used by esperanto. 173 | 174 | The value you provide for `esperantoOptions` will be passed to result of bundling, resulting 175 | in a single output file. 176 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var CachingWriter = require('broccoli-caching-writer'); 2 | var esperanto = require('esperanto'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var fs = require('fs'); 6 | var helpers = require('broccoli-kitchen-sink-helpers'); 7 | var walkSync = require('walk-sync'); 8 | var RSVP = require('rsvp'); 9 | 10 | var writeFile = RSVP.denodeify(fs.writeFile); 11 | 12 | var formatToFunctionName = { 13 | amd: 'toAmd', 14 | cjs: 'toCjs', 15 | umd: 'toUmd', 16 | namedAmd: 'toAmd' 17 | }; 18 | 19 | var umdMesssage = "broccoli-es6modules cannot export to unbundled UMD format. The plugin uses each file's "+ 20 | "filename as its module name. This strategy used with UMD will expose many properties on " + 21 | "the global object, which you likely don't want. " + 22 | "To use UMD, please also supply bundleOptions with a name for the bundled module.\n" + 23 | "If you do want to expose this many global properties, please open an issue " + 24 | "(https://github.com/ember-cli/broccoli-es6modules) "+ 25 | "and tells us your use case."; 26 | 27 | 28 | /** 29 | ES6Module Plugin. 30 | 31 | ES6Module inherits from CachingWriter. It's two main hooks are `init` (called 32 | when a new instance is created) and `updateCache` (called when any file in the 33 | tree is changed, added, or removed). 34 | 35 | See https://github.com/ember-cli/broccoli-es6modules/blob/master/README.md 36 | for more details 37 | */ 38 | module.exports = CachingWriter.extend({ 39 | /* 40 | Called when ES6Modules is `new`ed. Creates a new cache for transpiled files 41 | and determine what method to delegate to esperanto for transpiling files. 42 | */ 43 | init: function() { 44 | this._super.apply(this, arguments); 45 | this._transpilerCache = {}; 46 | this.toFormat = esperanto[formatToFunctionName[this.format]]; 47 | 48 | if (this.format === 'umd' && !this.bundleOptions) { 49 | throw new Error(umdMesssage); 50 | } 51 | }, 52 | 53 | /** 54 | A flag used by ES6Module's parent class CachingWriter to enforce 55 | passing only a single tree (and not an array of trees) to a plugin. 56 | 57 | ES6Modules only works on a single tree at a time, so this is `true` 58 | and should be considered read-only (i.e. if you set it to `false` 59 | you're going to have a bad time). 60 | */ 61 | enforceSingleInputTree: true, 62 | 63 | /* 64 | The module format to transpile to. 65 | available types are: 66 | 67 | * 'cjs' 68 | * 'amd' 69 | * 'umd' 70 | * 'namedAmd' 71 | 72 | Defaults to 'namedAmd' 73 | */ 74 | format: 'namedAmd', 75 | 76 | /* 77 | Used by broccoli's serve command when showing relative compile time of 78 | any given tree. 79 | */ 80 | description: 'ES6Modules', 81 | 82 | /* 83 | ES6Modules has two modes: 1-to-1 per-file transpilation and n-to-1 bundle 84 | transpilation. 85 | 86 | In 1-to-1 transpiling every file in a tree or 87 | accepting a single file that is the entry point for transpilation. 88 | 89 | When bundleOptions are provided, ES6Modules will start the transpilation 90 | process at the `entry` option and emit only a single file that contains 91 | the content of all the files imported, recursively, as a result of imports 92 | in the `entry` file. 93 | 94 | See http://esperantojs.org/ for a list of options you can pass for bundling. 95 | */ 96 | bundleOptions: null, 97 | 98 | /* 99 | The options to pass to esperanto per file (in per-file mode) 100 | or the entire bundle (in bundling mode). 101 | 102 | Some defaults are provided: 103 | 104 | * For per-file transpilations if the format is 'namedAmd', the 'amdName' option passed to the transpiler 105 | for each file will be the file's relative file path, with '.js' stripped from it. 106 | * For per-file transpilations if `sourceMap` option is provided, the `sourceMapSource` option is passed 107 | to the transpiler for each file as the relative file path, with '.js' stripped from it. 108 | 109 | So, if you have the following tree: 110 | ├── inner 111 | │   └── first.js 112 | └── outer.js 113 | 114 | You will have the following module names passed to AMD's `define` call: 115 | 'bundle', 'inner/first', and 'outer'. 116 | */ 117 | esperantoOptions: null, 118 | 119 | 120 | /** 121 | 122 | Used to keep a map of files that have been previously transpiled 123 | and their resulting transpiled source. 124 | 125 | On subsequent calls to this plugin a cached version of a file 126 | is returned if the source hasn't been changed. 127 | 128 | @private 129 | */ 130 | _transpilerCache: null, 131 | 132 | /** 133 | 134 | A hook from CachingWriter's lifecycle, called whenever any file in the 135 | source tree has been changed, added, or removed. 136 | 137 | Depending on mode (per-file or bundled), will compile all files in the tree 138 | or will create a bundle starting at the entry point. 139 | 140 | */ 141 | updateCache: function(inDir, outDir) { 142 | return this.bundleOptions ? this._updateBundle(inDir, outDir) : this._updateEachFile(inDir, outDir); 143 | }, 144 | 145 | /** 146 | A hook called if ES6Modules is being used in a n-to-1 bundle. 147 | 148 | Begins importing at an entry point and adds a single bundled 149 | module to the output tree. 150 | */ 151 | _updateBundle: function(inDir, outDir){ 152 | var name = this.bundleOptions.name; 153 | var opts = this._generateEsperantoOptions(name); 154 | var transpilerName = formatToFunctionName[this.format]; 155 | var targetExtension = this.targetExtension; 156 | 157 | return esperanto.bundle({ 158 | base: inDir, 159 | entry: this.bundleOptions.entry 160 | }).then(function(bundle) { 161 | var compiledModule = bundle[transpilerName](opts); 162 | var fullOutputPath = path.join(outDir, name + '.' + targetExtension); 163 | 164 | return writeFile(fullOutputPath, compiledModule.code); 165 | }); 166 | }, 167 | 168 | /** 169 | A hook called if ES6Modules is being used in a 1-to-1 per-per file mode 170 | (the default). 171 | 172 | Creates a new cache then delegates to `handleFile` for every file, which 173 | checks the old cache for presence of transpiled code and inserts the cached 174 | code (or a newly transpiled code) into the new cache. 175 | 176 | Finally, the old cache is overwritten by the new cache. 177 | */ 178 | _updateEachFile: function(inDir, outDir){ 179 | // this object is passed through the caching process 180 | // and populated with newly generated or previously cached 181 | // values. It becomes the new cache; 182 | var _newTranspilerCache = {}; 183 | 184 | walkSync(inDir) 185 | .forEach(function(relativePath) { 186 | if (this._shouldProcessFile(relativePath)) { 187 | this._handleFile(inDir, outDir, relativePath, _newTranspilerCache); 188 | } 189 | }, this); 190 | 191 | this._transpilerCache = _newTranspilerCache; 192 | }, 193 | 194 | /** 195 | Normalizes module name, input path, and output path 196 | then calls transpileThroughCache to get a transpiled 197 | version of the ES6 source. 198 | */ 199 | _handleFile: function(inDir, outDir, relativePath, newCache) { 200 | var ext = this._matchingFileExtension(relativePath); 201 | var moduleName = relativePath.slice(0, relativePath.length - (ext.length + 1)); 202 | var fullInputPath = path.join(inDir, relativePath); 203 | var fullOutputPath = path.join(outDir, moduleName + '.' + this.targetExtension); 204 | 205 | var entry = this._transpileThroughCache( 206 | moduleName, 207 | fs.readFileSync(fullInputPath, 'utf-8'), 208 | newCache 209 | ); 210 | 211 | mkdirp.sync(path.dirname(fullOutputPath)); 212 | fs.writeFileSync(fullOutputPath, entry.output); 213 | }, 214 | 215 | /** 216 | Called on every file in a tree when used in per-file mode. 217 | 218 | First this checks whether the file contents have been previously 219 | transpiled by checking the previous cache. If the file has been transpiled, 220 | adds the previous transpiled code into the new cache. If it has not been transpiled 221 | it adds passed the source code along to a transpiler and adds the resulting value 222 | to the new cache. 223 | */ 224 | _transpileThroughCache: function(moduleName, source, newCache) { 225 | var key = helpers.hashStrings([moduleName, source]); 226 | var entry = this._transpilerCache[key]; 227 | 228 | if (entry) { 229 | return newCache[key] = entry; 230 | } 231 | try { 232 | return newCache[key] = { 233 | output: this.toFormat( 234 | source, 235 | this._generateEsperantoOptions(moduleName) 236 | ).code 237 | }; 238 | } catch(err) { 239 | err.file = moduleName; 240 | throw err; 241 | } 242 | }, 243 | 244 | _generateEsperantoOptions: function(moduleName) { 245 | var providedOptions = this.esperantoOptions || {}; 246 | var options = {}; 247 | 248 | if (this.format === 'namedAmd') { 249 | if (typeof this.formatModuleName === 'function') { 250 | moduleName = this.formatModuleName(moduleName); 251 | } 252 | options.amdName = moduleName; 253 | } 254 | 255 | if (this.format === 'umd') { 256 | options.name = moduleName; 257 | } 258 | 259 | if (providedOptions.sourceMap) { 260 | options.sourceMapSource = moduleName; 261 | } 262 | 263 | for (var keyName in providedOptions) { 264 | options[keyName] = providedOptions[keyName]; 265 | } 266 | 267 | 268 | return options; 269 | }, 270 | extensions: ['js'], 271 | targetExtension: 'js', 272 | _matchingFileExtension: function(relativePath) { 273 | for (var i = 0; i < this.extensions.length; i++) { 274 | var ext = this.extensions[i]; 275 | if (relativePath.slice(-ext.length - 1) === '.' + ext) { 276 | return ext; 277 | } 278 | } 279 | return null; 280 | }, 281 | _shouldProcessFile: function(relativePath) { 282 | return !!this._matchingFileExtension(relativePath); 283 | } 284 | }); 285 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broccoli-es6modules", 3 | "version": "1.2.3", 4 | "description": "An es6 module transpiler & concatenator for broccoli.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": "https://github.com/ember-cli/broccoli-es6modules", 10 | "author": "Edward Faulkner ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "broccoli-caching-writer": "^1.0.0", 14 | "broccoli-kitchen-sink-helpers": "^0.2.7", 15 | "esperanto": "0.7.4", 16 | "mkdirp": "^0.5.0", 17 | "rsvp": "^3.0.16", 18 | "walk-sync": "^0.1.3" 19 | }, 20 | "devDependencies": { 21 | "broccoli": "^0.13.3", 22 | "chai": "^1.10.0", 23 | "mocha": "^2.0.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | actual -------------------------------------------------------------------------------- /test/expected/amd/custom-ext.es3: -------------------------------------------------------------------------------- 1 | define(['exports', 'npm:vendor/monster', 'ember'], function (exports, monster, Ember) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = Ember['default'].Route.extend({ 6 | actions: { 7 | checkCookie: function() { 8 | if (monster['default'].get('magical')) { 9 | alert('you have a magic cookie'); 10 | } 11 | } 12 | } 13 | }); 14 | 15 | }); -------------------------------------------------------------------------------- /test/expected/amd/custom-ext.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'npm:vendor/monster', 'ember'], function (exports, monster, Ember) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = Ember['default'].Route.extend({ 6 | actions: { 7 | checkCookie: function() { 8 | if (monster['default'].get('magical')) { 9 | alert('you have a magic cookie'); 10 | } 11 | } 12 | } 13 | }); 14 | 15 | }); -------------------------------------------------------------------------------- /test/expected/amd/inner/first.js: -------------------------------------------------------------------------------- 1 | define(['exports', '../something'], function (exports, Something) { 2 | 3 | 'use strict'; 4 | 5 | exports.meaningOfLife = meaningOfLife; 6 | exports.boom = boom; 7 | 8 | function meaningOfLife() { 9 | new Something['default'](); 10 | throw new Error(42); 11 | } 12 | 13 | function boom() { 14 | throw new Error('boom'); 15 | } 16 | 17 | }); -------------------------------------------------------------------------------- /test/expected/amd/outer.es3: -------------------------------------------------------------------------------- 1 | define(['exports', 'npm:vendor/monster', 'ember'], function (exports, monster, Ember) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = Ember['default'].Route.extend({ 6 | actions: { 7 | checkCookie: function() { 8 | if (monster['default'].get('magical')) { 9 | alert('you have a magic cookie'); 10 | } 11 | } 12 | } 13 | }); 14 | 15 | }); -------------------------------------------------------------------------------- /test/expected/amd/outer.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'npm:vendor/monster', 'ember'], function (exports, monster, Ember) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = Ember['default'].Route.extend({ 6 | actions: { 7 | checkCookie: function() { 8 | if (monster['default'].get('magical')) { 9 | alert('you have a magic cookie'); 10 | } 11 | } 12 | } 13 | }); 14 | 15 | }); -------------------------------------------------------------------------------- /test/expected/amd/reexport-es3.js: -------------------------------------------------------------------------------- 1 | define('reexport-es3', ['exports', 'inner/first'], function (exports, meaningOfLife) { 2 | 3 | 'use strict'; 4 | 5 | 6 | 7 | exports.meaningOfLife = meaningOfLife['default']; 8 | 9 | }); -------------------------------------------------------------------------------- /test/expected/amd/reexport.es3: -------------------------------------------------------------------------------- 1 | define(['exports', 'inner/first'], function (exports, meaningOfLife) { 2 | 3 | 'use strict'; 4 | 5 | Object.defineProperty(exports, 'meaningOfLife', { enumerable: true, get: function () { return meaningOfLife['default']; }}); 6 | 7 | }); -------------------------------------------------------------------------------- /test/expected/amd/reexport.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'inner/first'], function (exports, meaningOfLife) { 2 | 3 | 'use strict'; 4 | 5 | Object.defineProperty(exports, 'meaningOfLife', { enumerable: true, get: function () { return meaningOfLife['default']; }}); 6 | 7 | }); -------------------------------------------------------------------------------- /test/expected/bundledAmd/myModule.js: -------------------------------------------------------------------------------- 1 | define(['something'], function (Something) { 2 | 3 | 'use strict'; 4 | 5 | function meaningOfLife() { 6 | new Something(); 7 | throw new Error(42); 8 | } 9 | 10 | function boom() { 11 | throw new Error('boom'); 12 | } 13 | 14 | var x = meaningOfLife + 42; 15 | 16 | 17 | var bundle = x; 18 | 19 | return bundle; 20 | 21 | }); -------------------------------------------------------------------------------- /test/expected/bundledNamedAmd/myModule.js: -------------------------------------------------------------------------------- 1 | define('myModule', ['something'], function (Something) { 2 | 3 | 'use strict'; 4 | 5 | function meaningOfLife() { 6 | new Something(); 7 | throw new Error(42); 8 | } 9 | 10 | function boom() { 11 | throw new Error('boom'); 12 | } 13 | 14 | var x = meaningOfLife + 42; 15 | 16 | 17 | var bundle = x; 18 | 19 | return bundle; 20 | 21 | }); -------------------------------------------------------------------------------- /test/expected/cjs/inner/first.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.meaningOfLife = meaningOfLife; 4 | exports.boom = boom; 5 | 6 | var Something = require('../something'); 7 | 8 | function meaningOfLife() { 9 | new Something['default'](); 10 | throw new Error(42); 11 | } 12 | 13 | function boom() { 14 | throw new Error('boom'); 15 | } -------------------------------------------------------------------------------- /test/expected/cjs/outer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var monster = require('npm:vendor/monster'); 4 | var Ember = require('ember'); 5 | 6 | exports['default'] = Ember['default'].Route.extend({ 7 | actions: { 8 | checkCookie: function() { 9 | if (monster['default'].get('magical')) { 10 | alert('you have a magic cookie'); 11 | } 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /test/expected/cjs/reexport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, 'meaningOfLife', { enumerable: true, get: function () { return meaningOfLife['default']; }}); 4 | 5 | var meaningOfLife = require('inner/first'); -------------------------------------------------------------------------------- /test/expected/namedAmd/inner/first.js: -------------------------------------------------------------------------------- 1 | define('inner/first', ['exports', 'something'], function (exports, Something) { 2 | 3 | 'use strict'; 4 | 5 | exports.meaningOfLife = meaningOfLife; 6 | exports.boom = boom; 7 | 8 | function meaningOfLife() { 9 | new Something['default'](); 10 | throw new Error(42); 11 | } 12 | 13 | function boom() { 14 | throw new Error('boom'); 15 | } 16 | 17 | }); -------------------------------------------------------------------------------- /test/expected/namedAmd/outer.js: -------------------------------------------------------------------------------- 1 | define('outer', ['exports', 'npm:vendor/monster', 'ember'], function (exports, monster, Ember) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = Ember['default'].Route.extend({ 6 | actions: { 7 | checkCookie: function() { 8 | if (monster['default'].get('magical')) { 9 | alert('you have a magic cookie'); 10 | } 11 | } 12 | } 13 | }); 14 | 15 | }); -------------------------------------------------------------------------------- /test/expected/namedAmd/reexport-es3.js: -------------------------------------------------------------------------------- 1 | define('reexport-es3', ['exports', 'inner/first'], function (exports, meaningOfLife) { 2 | 3 | 'use strict'; 4 | 5 | 6 | 7 | exports.meaningOfLife = meaningOfLife['default']; 8 | 9 | }); -------------------------------------------------------------------------------- /test/expected/namedAmd/reexport.js: -------------------------------------------------------------------------------- 1 | define('reexport', ['exports', 'inner/first'], function (exports, meaningOfLife) { 2 | 3 | 'use strict'; 4 | 5 | Object.defineProperty(exports, 'meaningOfLife', { enumerable: true, get: function () { return meaningOfLife['default']; }}); 6 | 7 | }); -------------------------------------------------------------------------------- /test/expected/namedAmdCustom/subdir/custom-name.js: -------------------------------------------------------------------------------- 1 | define('custom-name', ['exports'], function (exports) { 2 | 3 | 'use strict'; 4 | 5 | exports['default'] = function() { 6 | } 7 | 8 | }); -------------------------------------------------------------------------------- /test/expected/umd/myModule.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('something')) : 3 | typeof define === 'function' && define.amd ? define(['something'], factory) : 4 | global.myModule = factory(global.Something) 5 | }(this, function (Something) { 'use strict'; 6 | 7 | function meaningOfLife() { 8 | new Something(); 9 | throw new Error(42); 10 | } 11 | 12 | function boom() { 13 | throw new Error('boom'); 14 | } 15 | 16 | var x = meaningOfLife + 42; 17 | 18 | 19 | var bundle = x; 20 | 21 | return bundle; 22 | 23 | })); -------------------------------------------------------------------------------- /test/fixtures/bundle.js: -------------------------------------------------------------------------------- 1 | import {meaningOfLife} from "./inner/first" 2 | var x = meaningOfLife + 42; 3 | 4 | 5 | export default x; -------------------------------------------------------------------------------- /test/fixtures/custom-ext.es6: -------------------------------------------------------------------------------- 1 | import monster from 'npm:vendor/monster'; 2 | import Ember from 'ember'; 3 | 4 | export default Ember.Route.extend({ 5 | actions: { 6 | checkCookie: function() { 7 | if (monster.get('magical')) { 8 | alert('you have a magic cookie'); 9 | } 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /test/fixtures/file.answer: -------------------------------------------------------------------------------- 1 | the answer for everything is 42 2 | -------------------------------------------------------------------------------- /test/fixtures/inner/first.js: -------------------------------------------------------------------------------- 1 | import Something from '../something'; 2 | 3 | export function meaningOfLife() { 4 | new Something(); 5 | throw new Error(42); 6 | } 7 | 8 | export function boom() { 9 | throw new Error('boom'); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/outer.js: -------------------------------------------------------------------------------- 1 | import monster from 'npm:vendor/monster'; 2 | import Ember from 'ember'; 3 | 4 | export default Ember.Route.extend({ 5 | actions: { 6 | checkCookie: function() { 7 | if (monster.get('magical')) { 8 | alert('you have a magic cookie'); 9 | } 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /test/fixtures/reexport-es3.js: -------------------------------------------------------------------------------- 1 | import meaningOfLife from 'inner/first'; 2 | 3 | export { 4 | meaningOfLife 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/reexport.js: -------------------------------------------------------------------------------- 1 | import meaningOfLife from 'inner/first'; 2 | 3 | export { 4 | meaningOfLife 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/subdir/custom-name.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | } 3 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* global describe, afterEach, it, expect */ 2 | 3 | var expect = require('chai').expect; // jshint ignore:line 4 | var ES6 = require('..'); 5 | var RSVP = require('rsvp'); 6 | 7 | RSVP.on('error', function(err){ 8 | throw err; 9 | }); 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | var broccoli = require('broccoli'); 14 | var mkdirp = require('mkdirp'); 15 | 16 | var fixtures = path.join(__dirname, 'fixtures'); 17 | var builder; 18 | 19 | describe('broccoli-es6modules', function() { 20 | describe('caching', function() { 21 | it('avoids a transpile of any unchanged file', function() { 22 | var tree = new ES6(fixtures, { 23 | format: 'amd', 24 | esperantoOptions: { 25 | strict: true 26 | } 27 | }); 28 | 29 | // initial build should cache results 30 | builder = new broccoli.Builder(tree); 31 | return builder.build().then(function(){ 32 | // subsequent builds will never call transpiler 33 | var results = []; 34 | var _toFormat = tree.toFormat; 35 | tree.toFormat = function(code, opts){ 36 | results.push(code); 37 | return _toFormat.call(this, code, opts); 38 | } 39 | 40 | 41 | return builder.build().then(function() { 42 | expect(results).to.be.empty(); 43 | }); 44 | }); 45 | }); 46 | 47 | it('re-transpiles any changed files', function() { 48 | var tree = new ES6(fixtures, { 49 | format: 'amd', 50 | esperantoOptions: { 51 | strict: true 52 | } 53 | }); 54 | 55 | // initial build should cache results 56 | builder = new broccoli.Builder(tree); 57 | 58 | return builder.build().then(function(){ 59 | var results = []; 60 | var _toFormat = tree.toFormat; 61 | tree.toFormat = function(code, opts){ 62 | results.push(code); 63 | return _toFormat.call(this, code, opts); 64 | } 65 | 66 | var fileName = 'outer.js'; 67 | var code = 'var x="x";' 68 | 69 | var originalCode = readFile(fileName); 70 | touch(fileName, code); 71 | 72 | return builder.build().then(function() { 73 | expect(results).to.contain(code); 74 | }).finally(function(){ 75 | // restore contents 76 | touch(fileName, originalCode); 77 | }); 78 | }); 79 | }); 80 | }); 81 | 82 | it('transpiles every file', function() { 83 | var tree = new ES6(fixtures, { 84 | format: 'amd', 85 | esperantoOptions: { 86 | strict: true 87 | } 88 | }); 89 | 90 | builder = new broccoli.Builder(tree); 91 | return builder.build().then(function(result) { 92 | expectFile('outer.js', 'amd').in(result); 93 | expectFile('reexport.js', 'amd').in(result); 94 | expectFile('inner/first.js', 'amd').in(result); 95 | }); 96 | }); 97 | 98 | it('uses esperantoOptions if provided', function() { 99 | var tree = new ES6(fixtures, { 100 | esperantoOptions: { 101 | _evilES3SafeReExports: true, 102 | strict: true, 103 | absolutePaths: true 104 | } 105 | }); 106 | 107 | builder = new broccoli.Builder(tree); 108 | return builder.build().then(function(result) { 109 | expectFile('reexport-es3.js', 'amd').in(result); 110 | }); 111 | }); 112 | 113 | it('compiles to amd with names if format = namedAmd', function() { 114 | var tree = new ES6(fixtures, { 115 | format: 'namedAmd', 116 | esperantoOptions: { 117 | strict: true, 118 | absolutePaths: true 119 | } 120 | }); 121 | 122 | builder = new broccoli.Builder(tree); 123 | return builder.build().then(function(result) { 124 | expectFile('outer.js', 'namedAmd').in(result); 125 | expectFile('reexport.js', 'namedAmd').in(result); 126 | expectFile('inner/first.js', 'namedAmd').in(result); 127 | }); 128 | }); 129 | 130 | it('allows customizing namedAmd module name with function', function() { 131 | var tree = new ES6(fixtures, { 132 | format: 'namedAmd', 133 | formatModuleName: function(moduleName) { 134 | return moduleName.replace('subdir/', ''); 135 | }, 136 | esperantoOptions: { 137 | strict: true, 138 | absolutePaths: true 139 | } 140 | }); 141 | 142 | builder = new broccoli.Builder(tree); 143 | return builder.build().then(function(result) { 144 | expectFile('subdir/custom-name.js', 'namedAmdCustom').in(result); 145 | }); 146 | }); 147 | 148 | it('sets sourceMapSource if source maps are enabled', function() { 149 | var tree = new ES6(fixtures, { 150 | esperantoOptions: { 151 | sourceMap: 'inline' 152 | } 153 | }); 154 | 155 | var result = tree._generateEsperantoOptions('some-path/here'); 156 | 157 | expect(result.sourceMapSource).to.equal('some-path/here'); 158 | }); 159 | 160 | it('compiles to cjs if format = cjs', function() { 161 | var tree = new ES6(fixtures, { 162 | format: 'cjs', 163 | esperantoOptions: { 164 | strict: true 165 | } 166 | }); 167 | builder = new broccoli.Builder(tree); 168 | return builder.build().then(function(result) { 169 | expectFile('outer.js', 'cjs').in(result); 170 | expectFile('reexport.js', 'cjs').in(result); 171 | expectFile('inner/first.js', 'cjs').in(result); 172 | }); 173 | }); 174 | 175 | it('warns that you cannot compile to umd without bundling', function() { 176 | expect(function(){ 177 | new ES6(fixtures, { 178 | format: 'umd' 179 | }); 180 | }).to.throw(/cannot export to unbundled UMD/); 181 | 182 | }); 183 | 184 | it('compiles to a bundled amd format when bundling options are provided', function(){ 185 | var tree = new ES6(fixtures, { 186 | format: 'amd', 187 | bundleOptions: { 188 | entry: 'bundle.js', 189 | name: 'myModule' 190 | } 191 | }); 192 | 193 | builder = new broccoli.Builder(tree); 194 | return builder.build().then(function(result) { 195 | expectFile('myModule.js', 'bundledAmd').in(result); 196 | }); 197 | }); 198 | 199 | it('compiles to a bundled and named amd format when bundling options are provided', function(){ 200 | var tree = new ES6(fixtures, { 201 | format: 'namedAmd', 202 | bundleOptions: { 203 | entry: 'bundle.js', 204 | name: 'myModule' 205 | } 206 | }); 207 | 208 | builder = new broccoli.Builder(tree); 209 | return builder.build().then(function(result) { 210 | expectFile('myModule.js', 'bundledNamedAmd').in(result); 211 | }); 212 | }); 213 | 214 | it('compiles to a bundled and named umd format when bundling options are provided', function(){ 215 | var tree = new ES6(fixtures, { 216 | format: 'umd', 217 | bundleOptions: { 218 | entry: 'bundle.js', 219 | name: 'myModule' 220 | } 221 | }); 222 | 223 | builder = new broccoli.Builder(tree); 224 | return builder.build().then(function(result) { 225 | expectFile('myModule.js', 'umd').in(result); 226 | }); 227 | }); 228 | 229 | it('compiles with custom source extensions', function(){ 230 | var tree = new ES6(fixtures, { 231 | format: 'amd', 232 | extensions: ['es6'], 233 | esperantoOptions: { 234 | strict: true 235 | } 236 | }); 237 | 238 | builder = new broccoli.Builder(tree); 239 | return builder.build().then(function(result) { 240 | expectFile('custom-ext.js', 'amd').in(result); 241 | }); 242 | }); 243 | 244 | it('compiles with more than one source extension', function(){ 245 | var tree = new ES6(fixtures, { 246 | format: 'amd', 247 | extensions: ['es6', 'js'], 248 | esperantoOptions: { 249 | strict: true 250 | } 251 | }); 252 | 253 | builder = new broccoli.Builder(tree); 254 | return builder.build().then(function(result) { 255 | expectFile('custom-ext.js', 'amd').in(result); 256 | expectFile('reexport.js', 'amd').in(result); 257 | expectFile('outer.js', 'amd').in(result); 258 | }); 259 | }); 260 | 261 | it('compiles using custom target extensions', function(){ 262 | var tree = new ES6(fixtures, { 263 | format: 'amd', 264 | targetExtension: 'es3', 265 | esperantoOptions: { 266 | strict: true 267 | } 268 | }); 269 | 270 | builder = new broccoli.Builder(tree); 271 | return builder.build().then(function(result) { 272 | expectFile('reexport.es3', 'amd').in(result); 273 | expectFile('outer.es3', 'amd').in(result); 274 | }); 275 | }); 276 | 277 | it('compiles using custom target extensions and source extensions', function(){ 278 | var tree = new ES6(fixtures, { 279 | format: 'amd', 280 | extensions: ['es6', 'js'], 281 | targetExtension: 'es3', 282 | esperantoOptions: { 283 | strict: true 284 | } 285 | }); 286 | 287 | builder = new broccoli.Builder(tree); 288 | return builder.build().then(function(result) { 289 | expectFile('custom-ext.es3', 'amd').in(result); 290 | expectFile('reexport.es3', 'amd').in(result); 291 | expectFile('outer.es3', 'amd').in(result); 292 | }); 293 | }); 294 | 295 | afterEach(function() { 296 | if (builder) { 297 | return builder.cleanup(); 298 | } 299 | }); 300 | }); 301 | 302 | 303 | function readFile(filename){ 304 | var file = path.join(__dirname, 'fixtures', filename); 305 | return fs.readFileSync(file, 'utf-8'); 306 | } 307 | 308 | 309 | function touch(filename, content) { 310 | var file = path.join(__dirname, 'fixtures', filename); 311 | fs.writeFileSync(file, content, 'utf-8'); 312 | } 313 | 314 | function expectSource(expectedContent) { 315 | function inner(actualContent) { 316 | expect(actualContent).to.equal(expectedContent); 317 | } 318 | return { in: inner }; 319 | } 320 | 321 | function expectFile(filename, format) { 322 | function inner(result) { 323 | var actualContent = fs.readFileSync(path.join(result.directory, filename), 'utf-8'); 324 | mkdirp.sync(path.dirname(path.join(__dirname, 'actual', filename))); 325 | fs.writeFileSync(path.join(__dirname, 'actual', filename), actualContent); 326 | 327 | var expectedContent; 328 | try { 329 | expectedContent = fs.readFileSync(path.join(__dirname, 'expected', format, filename), 'utf-8'); 330 | } catch (err) { 331 | console.warn("Missing expected file: " + path.join(__dirname, 'expected', format, filename)); 332 | } 333 | 334 | expect(actualContent).to.equal(expectedContent, "discrepancy in " + filename); 335 | } 336 | return { in: inner }; 337 | } 338 | --------------------------------------------------------------------------------