├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── chromedriver ├── run.sh ├── test-all.sh ├── test-browsers.sh ├── test-node.sh └── update-env.sh ├── bower.json ├── data.json ├── gulpfile.js ├── lib ├── args.js ├── browser-profile.js ├── browser.js ├── data-store.js ├── iframe.js ├── node-profile.js ├── node-test.js ├── node.js ├── redirect-prerelease.html ├── redirect-stable.html ├── runner.js ├── user-agent.js ├── worker-test.js └── worker.js ├── notes.json ├── package-lock.json ├── package.json ├── report ├── index.js └── report.css ├── tasks ├── bench.handlebars ├── build.js ├── driver.js ├── local.js ├── node.js ├── profile.handlebars ├── report.handlebars ├── report.js ├── sauce.js ├── server.js └── vm.js ├── tests ├── .eslintrc ├── arrow-args │ ├── arrow-args.es5 │ └── arrow-args.es6 ├── arrow-declare │ ├── arrow-declare.es5 │ └── arrow-declare.es6 ├── arrow │ ├── arrow.es5 │ └── arrow.es6 ├── bindings-compound │ ├── bindings-compound.es5 │ └── bindings-compound.es6 ├── bindings │ ├── bindings.es5 │ └── bindings.es6 ├── classes │ ├── classes.es5 │ └── classes.es6 ├── defaults │ ├── defaults.es5 │ └── defaults.es6 ├── destructuring-simple │ ├── destructuring-simple.es5 │ └── destructuring-simple.es6 ├── destructuring │ ├── destructuring.es5 │ └── destructuring.es6 ├── for-of-array │ ├── for-of-array.es5 │ └── for-of-array.es6 ├── for-of-object │ ├── for-of-object.es5 │ └── for-of-object.es6 ├── generator │ ├── generator.es5 │ └── generator.es6 ├── map-set-lookup │ ├── map-set-lookup.es5 │ └── map-set-lookup.es6 ├── map-set-object │ ├── map-set-object.es5 │ └── map-set-object.es6 ├── map-set │ ├── map-set.es5 │ └── map-set.es6 ├── map-string │ ├── map-string.es5 │ └── map-string.es6 ├── new-target │ ├── defaults.es5 │ └── defaults.es6 ├── object-assign │ ├── object-assign.es5 │ └── object-assign.es6 ├── object-literal-ext │ ├── object-literal-ext.es5 │ └── object-literal-ext.es6 ├── regex-u │ ├── regex-u.es5 │ └── regex-u.es6 ├── rest │ ├── rest.es5 │ └── rest.es6 ├── spread-generator │ ├── spread-generator.es5 │ └── spread-generator.es6 ├── spread-literal │ ├── spread-literal.es5 │ └── spread-literal.es6 ├── spread │ ├── spread.es5 │ └── spread.es6 ├── super │ ├── super.es5 │ └── super.es6 ├── template_string │ ├── template_string.es5 │ └── template_string.es6 └── template_string_tag │ ├── template_string_tag.es5 │ └── template_string_tag.es6 └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "ecmaFeatures": { 6 | "arrowFunctions": true, 7 | "binaryLiterals": true, 8 | "blockBindings": true, 9 | "classes": true, 10 | "defaultParams": true, 11 | "destructuring": true, 12 | "forOf": true, 13 | "generators": true, 14 | "modules": true, 15 | "objectLiteralComputedProperties": true, 16 | "objectLiteralDuplicateProperties": true, 17 | "objectLiteralShorthandMethods": true, 18 | "objectLiteralShorthandProperties": true, 19 | "octalLiterals": true, 20 | "regexUFlag": true, 21 | "regexYFlag": true, 22 | "spread": true, 23 | "superInFunctions": true, 24 | "templateStrings": true, 25 | "unicodeCodePointEscapes": true, 26 | "globalReturn": true 27 | }, 28 | "rules": { 29 | // Possible Errors // 30 | //-----------------// 31 | 32 | "comma-dangle": [2, "never"], 33 | "no-cond-assign": [2, "except-parens"], 34 | 35 | "no-console": 0, 36 | 37 | "no-constant-condition": 2, 38 | "no-control-regex": 2, 39 | 40 | // Allow for debugging 41 | "no-debugger": 1, 42 | 43 | "no-dupe-args": 2, 44 | "no-dupe-keys": 2, 45 | "no-duplicate-case": 2, 46 | "no-empty": 2, 47 | "no-empty-class": 2, 48 | "no-ex-assign": 2, 49 | "no-extra-boolean-cast": 2, 50 | "no-extra-parens": 0, 51 | "no-extra-semi": 2, 52 | "no-func-assign": 2, 53 | 54 | // Stylistic... might consider disallowing in the future 55 | "no-inner-declarations": 0, 56 | 57 | "no-invalid-regexp": 2, 58 | "no-irregular-whitespace": 2, 59 | "no-negated-in-lhs": 2, 60 | "no-obj-calls": 2, 61 | "no-regex-spaces": 2, 62 | "no-reserved-keys": 0, 63 | "no-sparse-arrays": 0, 64 | 65 | // Optimizer and coverage will handle/highlight this and can be useful for debugging 66 | "no-unreachable": 1, 67 | 68 | "use-isnan": 2, 69 | "valid-jsdoc": 0, 70 | "valid-typeof": 2, 71 | 72 | 73 | // Best Practices // 74 | //----------------// 75 | "block-scoped-var": 0, 76 | "complexity": 0, 77 | "consistent-return": 0, 78 | "curly": 2, 79 | "default-case": 1, 80 | "dot-notation": [2, {"allowKeywords": true}], 81 | "eqeqeq": 0, 82 | "guard-for-in": 1, 83 | "no-alert": 2, 84 | "no-caller": 2, 85 | "no-div-regex": 1, 86 | "no-else-return": 0, 87 | "no-empty-label": 2, 88 | "no-eq-null": 0, 89 | "no-eval": 2, 90 | "no-extend-native": 2, 91 | "no-extra-bind": 2, 92 | "no-fallthrough": 2, 93 | "no-floating-decimal": 2, 94 | "no-implied-eval": 2, 95 | "no-iterator": 2, 96 | "no-labels": 2, 97 | "no-lone-blocks": 2, 98 | "no-loop-func": 2, 99 | "no-multi-spaces": 2, 100 | "no-multi-str": 1, 101 | "no-native-reassign": 2, 102 | "no-new": 2, 103 | "no-new-func": 2, 104 | "no-new-wrappers": 2, 105 | "no-octal": 2, 106 | "no-octal-escape": 2, 107 | "no-param-reassign": 0, 108 | "no-process-env": 2, 109 | "no-proto": 2, 110 | "no-redeclare": 2, 111 | "no-return-assign": 2, 112 | "no-script-url": 2, 113 | "no-self-compare": 2, 114 | "no-sequences": 2, 115 | "no-throw-literal": 2, 116 | "no-unused-expressions": 2, 117 | "no-void": 0, 118 | "no-warning-comments": 1, 119 | "no-with": 2, 120 | "radix": 2, 121 | "vars-on-top": 0, 122 | "wrap-iife": 2, 123 | "yoda": 0, 124 | 125 | 126 | // Strict // 127 | //--------// 128 | "strict": 0, 129 | 130 | 131 | // Variables // 132 | //-----------// 133 | "no-catch-shadow": 2, 134 | "no-delete-var": 2, 135 | "no-label-var": 2, 136 | "no-shadow": 0, 137 | "no-shadow-restricted-names": 2, 138 | "no-undef": 2, 139 | "no-undef-init": 2, 140 | "no-undefined": 0, 141 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 142 | "no-use-before-define": 0, 143 | 144 | 145 | // Node.js // 146 | //---------// 147 | // Others left to environment defaults 148 | "no-mixed-requires": 0, 149 | "no-path-concat": 0, 150 | 151 | 152 | // Stylistic // 153 | //-----------// 154 | "indent": 0, 155 | "brace-style": [2, "1tbs", {"allowSingleLine": true}], 156 | "camelcase": 2, 157 | "comma-spacing": [2, {"before": false, "after": true}], 158 | "comma-style": [2, "last"], 159 | "consistent-this": [1, "self"], 160 | "eol-last": 2, 161 | "func-names": 0, 162 | "func-style": [2, "declaration"], 163 | "key-spacing": [2, { 164 | "beforeColon": false, 165 | "afterColon": true 166 | }], 167 | "max-nested-callbacks": 0, 168 | "new-cap": 2, 169 | "new-parens": 2, 170 | "newline-after-var": 0, 171 | "no-array-constructor": 2, 172 | "no-continue": 0, 173 | "no-inline-comments": 0, 174 | "no-lonely-if": 2, 175 | "no-mixed-spaces-and-tabs": 2, 176 | "no-multiple-empty-lines": 0, 177 | "no-nested-ternary": 1, 178 | "no-new-object": 2, 179 | "no-spaced-func": 2, 180 | "no-ternary": 0, 181 | "no-trailing-spaces": 2, 182 | "no-underscore-dangle": 0, 183 | "no-wrap-func": 2, 184 | "one-var": 0, 185 | "operator-assignment": 0, 186 | "padded-blocks": 0, 187 | "quote-props": 0, 188 | "quotes": [2, "single", "avoid-escape"], 189 | "semi": 2, 190 | "semi-spacing": [2, {"before": false, "after": true}], 191 | "sort-vars": 0, 192 | "space-after-keywords": [2, "always"], 193 | "space-before-blocks": [2, "always"], 194 | "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}], 195 | "space-in-brackets": 0, 196 | "space-in-parens": [2, "never"], 197 | "space-infix-ops": 2, 198 | "space-return-throw-case": 2, 199 | "space-unary-ops": 2, 200 | "spaced-line-comment": 2, 201 | "wrap-regex": 1, 202 | 203 | "no-var": 0 204 | } 205 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | site/ 4 | bower_components/ 5 | v8.log 6 | browsers/ 7 | nohup.out 8 | npm-debug.log 9 | sc_42.txt 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "13" 5 | 6 | env: 7 | - CXX=g++-4.8 8 | 9 | addons: 10 | firefox: "72.0" 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | packages: 15 | - g++-4.8 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Kevin Decker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # six-speed 2 | 3 | ES6 polyfill vs. feature performance tests. 4 | 5 | Report is located at http://kpdecker.github.io/six-speed/ 6 | 7 | ## Usage 8 | 9 | ``` 10 | npm run test:sauce 11 | ``` 12 | 13 | Test against all registered Sauce Labs browsers. 14 | 15 | ``` 16 | npm run test:node 17 | ``` 18 | 19 | Tests against the current node version. 20 | 21 | ``` 22 | npm run profile:node -- --testName=$name --type=$type --count=$iterationCount 23 | ``` 24 | 25 | Profiles a given test within the current Node environment. Type may be one of: 26 | - babel 27 | - babel-runtime 28 | - babel-loose 29 | - typescript 30 | - es5 31 | - es6 32 | 33 | 34 | ``` 35 | npm run build 36 | ``` 37 | 38 | Builds test files into build directory. 39 | 40 | 41 | ``` 42 | npm start 43 | ``` 44 | 45 | Starts a server instance for manual browser testing. Tests may be accessed via `http://machineName:9999/` and the `#` component may be used to filter the tests to be executed, i.e. `http://machineName:9999/#promises` 46 | 47 | Profiling of specific tests may be done through `http://machineName:9999/profile.html?testName=$testName&type=$type&count=$number`, i.e. `http://localhost:9999/profile.html?testName=generator&type=babel&count=1000000`. 48 | 49 | Firefox browsers need to use `/moz/index.html` and `/moz/profile.html` respectively to enable all supported ES6 features. 50 | 51 | ``` 52 | npm run report 53 | ``` 54 | 55 | Generates the data report. 56 | 57 | 58 | ## Testing methodology 59 | 60 | For each of the ES6 features in question, an ES5 implementation of that functionality was written along with an ES6 version. It should be noted that the functionality is frequently the same, but in some cases the "common" vs. "correct" version was written, i.e. using `x[key] = value` vs. `defineProperty` which is faster but can be hit but a particular nasty edge case for those who deem it fun to extend `Object.prototype`. 61 | 62 | Babel, in both loose+runtime and runtime mode, and Babel was then used to compile the ES6 version to an ES5 compliant version, utilizing the runtime over polyfill to maintain test isolation and avoid native implementations where possible. 63 | 64 | All of these test instances were then benchmarked in the given JavaScript engine using [Benchmark.js](http://benchmarkjs.com/) and then the operations per second compared to the ES5 implementation. Cross browser and cross execution comparisons are avoided as much as possible to isolate environmental issues when executing on VMs in the cloud. 65 | 66 | ## Test Steps 67 | 68 | 1. `./bin/test-all.sh` 69 | 2. `npm run report` 70 | 3. Checkin changes to site sub-repository. 71 | 72 | ### VM Setup 73 | 74 | The Windows 10 VM used must be manually setup to ensure the proper state prior to testing. This can be done with this command: 75 | 76 | ``` 77 | mkdir browsers 78 | ./node_modules/.bin/browser-downloader vm ./browsers 79 | ``` 80 | 81 | After this the image should be restarted a few times until all setup and update processes have completed and then a snapshot named `six-speed` taken from the idle desktop screen. The `test-all.sh` script will check that the VM image is up to date and will halt execution if the image is not setup properly, as a sanity check. 82 | 83 | ## Adding a custom browser to the report 84 | 85 | - Run the server (`npm run build && npm start`) 86 | - Visit the test url in your browser to perform the tests 87 | - This updates `data.json` to include the results 88 | - Update `notes.json` to include the useragent and engine 89 | - Run the report builder (`npm run report`) 90 | - Report is generated in `site/index.html` 91 | 92 | ## Links 93 | 94 | - [V8 Harmony Features](https://code.google.com/p/v8/issues/list?q=label:Harmony) 95 | - [Firefox ES6 Meta Bug](https://bugzilla.mozilla.org/show_bug.cgi?id=694100) 96 | - [WebKit ES6 Meta Bug](https://bugs.webkit.org/show_bug.cgi?id=80559) 97 | 98 | 99 | ## Thanks 100 | 101 | Thanks to [BrowserStack](browserstack.com) and [Sauce Labs](https://saucelabs.com/) for providing open source accounts which the majority of this testing was performed on. 102 | -------------------------------------------------------------------------------- /bin/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpdecker/six-speed/9dbb10d7bea730e0d984e426fb8d97928d087f92/bin/chromedriver -------------------------------------------------------------------------------- /bin/run.sh: -------------------------------------------------------------------------------- 1 | rm nohup.out 2 | nohup time ./bin/test-all.sh & 3 | 4 | sleep 5 5 | tail -f nohup.out 6 | -------------------------------------------------------------------------------- /bin/test-all.sh: -------------------------------------------------------------------------------- 1 | . ~/.nvm/nvm.sh 2 | 3 | caffeinate ./bin/test-browsers.sh 4 | caffeinate ./bin/test-node.sh 5 | -------------------------------------------------------------------------------- /bin/test-browsers.sh: -------------------------------------------------------------------------------- 1 | . ~/.nvm/nvm.sh 2 | 3 | nvm use 6 4 | 5 | rm -rf node_modules 6 | yarn 7 | 8 | ./node_modules/.bin/gulp test:local 9 | ./node_modules/.bin/gulp test:vm 10 | -------------------------------------------------------------------------------- /bin/test-node.sh: -------------------------------------------------------------------------------- 1 | . ~/.nvm/nvm.sh 2 | 3 | nvm use 6 4 | rm -rf node_modules 5 | yarn 6 | ./node_modules/.bin/gulp test:node 7 | 8 | nvm use 7 9 | rm -rf node_modules 10 | yarn 11 | ./node_modules/.bin/gulp test:node 12 | -------------------------------------------------------------------------------- /bin/update-env.sh: -------------------------------------------------------------------------------- 1 | . ~/.nvm/nvm.sh 2 | 3 | nvm install 6 4 | npm install -g yarn 5 | yarn 6 | 7 | mkdir ~/browsers 8 | rm -rf ~.browsers/*.app browsers/*.dmg ~.browsers/*.app browsers/*.dmg.etag 9 | ./node_modules/.bin/browser-downloader ~/browsers 10 | if [ $? -ne 0 ]; then 11 | echo "Download failed"; 12 | exit 1; 13 | fi 14 | 15 | devices=`hdiutil info | grep partition_scheme | awk '{print $1}'` 16 | for x in $devices; do 17 | hdiutil detach $x 18 | done 19 | 20 | nvm install 7 21 | npm install -g yarn 22 | 23 | nvm install 4 24 | npm install -g yarn 25 | 26 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es6-perf", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/kpdecker/six-speed", 5 | "authors": [ 6 | "kpdecker " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "bootstrap": "~3.4.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const del = require('del'); 2 | const Gulp = require('gulp'); 3 | 4 | require('./tasks/build'); 5 | require('./tasks/local'); 6 | require('./tasks/node'); 7 | require('./tasks/report'); 8 | require('./tasks/sauce'); 9 | require('./tasks/vm'); 10 | require('./tasks/server'); 11 | 12 | 13 | function watchFiles() { 14 | Gulp.task('watch', Gulp.series('build', 'report'), () => { 15 | Gulp.watch(['lib/*.js', 'tests/**'], Gulp.series('build')); 16 | Gulp.watch(['tasks/report.*', 'report/**', 'data.json', 'notes.json'], Gulp.series('report')); 17 | }); 18 | } 19 | 20 | const watch = Gulp.series(watchFiles); 21 | 22 | Gulp.task('clean', async (callback) => { 23 | return del(['build/**'], callback); 24 | }); 25 | 26 | Gulp.task('test', Gulp.series('test:node')); 27 | -------------------------------------------------------------------------------- /lib/args.js: -------------------------------------------------------------------------------- 1 | const minimist = require('minimist'); 2 | 3 | const knownOptions = { 4 | string: ['testName', 'type', 'count'] 5 | }; 6 | 7 | module.exports = minimist(process.argv.slice(2), knownOptions); 8 | -------------------------------------------------------------------------------- /lib/browser-profile.js: -------------------------------------------------------------------------------- 1 | /*global document, location, SixSpeed */ 2 | const queryParams = {}; 3 | location.search.replace(/^\?/, '').split(/&/g).forEach(pair => { 4 | pair = pair.split(/=/); 5 | queryParams[pair[0]] = pair[1]; 6 | }); 7 | 8 | const testName = queryParams.testName; 9 | const testType = queryParams.type; 10 | const iterationCount = parseInt(queryParams.count, 0); 11 | 12 | if (testName && testType && iterationCount) { 13 | document.getElementById('info').appendChild(document.createTextNode(`Profile ${testName} ${testType} executing ${iterationCount} operations`)); 14 | } else { 15 | document.getElementById('info').appendChild(document.createTextNode('Must specify testName, type, and count parameters')); 16 | } 17 | 18 | function doIt() { 19 | SixSpeed.profile(testName, testType, iterationCount); 20 | } 21 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | /*global document, location, navigator, SixSpeed, XMLHttpRequest */ 2 | const log = document.createElement('pre'); 3 | document.body.appendChild(log); 4 | 5 | const tag = (/tag=([^&]*)/.exec(location.search) || [])[1]; 6 | 7 | const grep = location.hash.replace(/^#/, ''); 8 | var vms = {}; 9 | 10 | SixSpeed.bench({ 11 | grep, 12 | log(message) { 13 | log.appendChild(document.createTextNode(`${message}\n`)); 14 | 15 | const request = new XMLHttpRequest(); 16 | request.onreadystatechange = () => {}; 17 | request.open('POST', '/debug', true); 18 | request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 19 | request.send(`browser=${encodeURIComponent(navigator.userAgent)}${tag ? `&tag=${tag}` : ''}&message=${encodeURIComponent(message)}`); 20 | }, 21 | testDone() { 22 | // Sending this frequently, the data store will handle deduping, etc. 23 | const request = new XMLHttpRequest(); 24 | request.onreadystatechange = () => {}; 25 | request.open('POST', '/log', true); 26 | request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 27 | request.send(`browser=${encodeURIComponent(navigator.userAgent)}${tag ? `&tag=${tag}` : ''}&data=${encodeURIComponent(JSON.stringify(SixSpeed.stats))}`); 28 | }, 29 | done() { 30 | const request = new XMLHttpRequest(); 31 | request.onreadystatechange = () => {}; 32 | request.open('POST', '/done', true); 33 | request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 34 | request.send(); 35 | }, 36 | 37 | runTest 38 | }); 39 | -------------------------------------------------------------------------------- /lib/data-store.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Fs = require('fs'); 3 | const dataFile = `${__dirname}/../data.json`; 4 | const notesFile = `${__dirname}/../notes.json`; 5 | 6 | module.exports.load = () => JSON.parse(Fs.readFileSync(dataFile).toString()); 7 | 8 | module.exports.notes = () => JSON.parse(Fs.readFileSync(notesFile).toString()); 9 | 10 | module.exports.store = function(browser, tag, version, stats) { 11 | const data = this.load(); 12 | 13 | tag = tag || version; 14 | 15 | data[browser] = data[browser] || {}; 16 | data[browser][tag] = data[browser][tag] || {stats: {}}; 17 | data[browser][tag].version = version; 18 | 19 | _.extend(data[browser][tag].stats, stats); 20 | 21 | Fs.writeFileSync(dataFile, JSON.stringify(data, undefined, 2)); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/iframe.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | var vms = {}; 3 | 4 | function runTest(name, type, complete) { 5 | function doIt() { 6 | vm.contentWindow.SixSpeed.benchTest(name, type, result => { 7 | complete(result); 8 | }, true); 9 | } 10 | 11 | let vm = vms[type]; 12 | if (!vm) { 13 | vm = vms[type] = document.createElement('iframe'); 14 | vm.src = `${type}.html`; 15 | vm.onload = () => { 16 | doIt(); 17 | vm.onload = undefined; 18 | }; 19 | document.body.appendChild(vm); 20 | } else { 21 | doIt(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/node-profile.js: -------------------------------------------------------------------------------- 1 | /*global SixSpeed */ 2 | const Args = require('./args'); 3 | const Path = require('path'); 4 | 5 | if (Args.type === 'babel') { 6 | require('@babel/polyfill'); 7 | } 8 | require('./runner'); 9 | 10 | const testFile = Path.join(__dirname, '..', 'build/tests', `${Args.testName}__${Args.type}`); 11 | require(testFile); 12 | 13 | console.log('Running', `${Args.testName}-${Args.type}`, 'for', Args.count, 'iterations'); 14 | 15 | SixSpeed.profile(Args.testName, Args.type, parseInt(Args.count, 10)); 16 | -------------------------------------------------------------------------------- /lib/node-test.js: -------------------------------------------------------------------------------- 1 | /*global SixSpeed */ 2 | const Fs = require('fs'); 3 | const Path = require('path'); 4 | const $type = process.argv[2]; 5 | 6 | if ($type === 'babel') { 7 | require('@babel/polyfill'); 8 | } 9 | require('./runner'); 10 | 11 | const testDir = Path.join(__dirname, '..', 'build/tests'); 12 | Fs.readdirSync(testDir).forEach(test => { 13 | if (!test.includes(`${$type}.js`)) { 14 | return; 15 | } 16 | 17 | try { 18 | require(Path.join(testDir, test)); 19 | } catch (err) { 20 | // NOP: Init errors should have been caught bo the master process init 21 | } 22 | }); 23 | 24 | process.on('message', ({name}) => { 25 | SixSpeed.benchTest(name, $type, result => { 26 | process.send({result}); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | /*global SixSpeed */ 2 | const _ = require('lodash'); 3 | 4 | const ChildProcess = require('child_process'); 5 | const DataStore = require('./data-store'); 6 | const Fs = require('fs'); 7 | const Path = require('path'); 8 | 9 | const nodeTestPath = require.resolve('./node-test'); 10 | 11 | require('./runner'); 12 | 13 | const testDir = Path.join(__dirname, '..', 'build/tests'); 14 | const browserLog = []; 15 | 16 | Fs.readdirSync(testDir).forEach(test => { 17 | try { 18 | require(Path.join(testDir, test)); 19 | } catch (err) { 20 | const msg = `Failed to load ${test} ${err}`; 21 | browserLog.push(msg); 22 | console.log(msg); 23 | } 24 | }); 25 | 26 | var vms = {}; 27 | 28 | SixSpeed.bench({ 29 | concurrency: 2, 30 | 31 | done() { 32 | let tag = process.versions.node; 33 | if (/^(0\.\d+)/.exec(tag)) { 34 | tag = RegExp.$1; 35 | } else if (/^(\d+\.)/.exec(tag)) { 36 | tag = `${RegExp.$1}x`; 37 | } 38 | DataStore.store('node', tag, process.versions.node, SixSpeed.stats); 39 | 40 | _.each(vms, vm => { 41 | vm.kill(); 42 | }); 43 | }, 44 | 45 | runTest(name, type, complete) { 46 | let vm = vms[type]; 47 | if (!vm) { 48 | vm = vms[type] = ChildProcess.fork(nodeTestPath, [type]); 49 | } 50 | 51 | vm.once('message', ({result}) => { 52 | complete(result); 53 | }); 54 | vm.send({name}); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /lib/redirect-prerelease.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/redirect-stable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/runner.js: -------------------------------------------------------------------------------- 1 | const Async = require('async'); 2 | const Benchmark = require('benchmark'); 3 | 4 | const SixSpeed = { 5 | tests: {}, 6 | stats: {}, 7 | log: [], 8 | 9 | running: false, 10 | ran: false, 11 | 12 | profile(testName, testType, count) { 13 | if (!SixSpeed.tests[testName]) { 14 | throw new Error(`Unknown test: ${testName}`); 15 | } 16 | if (!SixSpeed.tests[testName][testType]) { 17 | throw new Error(`Unknown test type: ${testType} for test ${testName}`); 18 | } 19 | 20 | SixSpeed.tests[testName][testType](fn => { 21 | if (fn.defer) { 22 | (function exec(i) { 23 | fn.fn({ 24 | resolve() { 25 | if (i < count) { 26 | exec(i + 1); 27 | } else { 28 | console.log('done'); 29 | } 30 | } 31 | }); 32 | }(0)); 33 | } else { 34 | for (let i = 0; i < count; i++) { 35 | fn(); 36 | } 37 | 38 | console.log('done'); 39 | } 40 | }, testName, testType, testRequire, assertEqual); 41 | }, 42 | 43 | bench(options) { 44 | let grep = options.grep; 45 | if (grep && typeof grep === 'string') { 46 | grep = new RegExp(`.*${grep}.*`); 47 | } 48 | 49 | function log(message) { 50 | SixSpeed.log.push(message); 51 | (options.log || console.log)(message); 52 | } 53 | 54 | SixSpeed.running = true; 55 | 56 | const tests = SixSpeed.tests; 57 | const testOrder = Object.keys(tests).sort((a, b) => { if (a === 'promises') { return -1; } else if (b === 'promises') { return 1; } else { return a.localeCompare(b); }}); 58 | Async.forEachSeries(testOrder, (testName, done) => { 59 | if (grep && !grep.test(testName)) { 60 | return done(); 61 | } 62 | 63 | const test = tests[testName]; 64 | const hz = {}; 65 | const elapsed = {}; 66 | const count = {}; 67 | 68 | const result = { 69 | types: Object.keys(test) 70 | }; 71 | 72 | log(`running ${testName} ${JSON.stringify(result.types)}`); 73 | 74 | Async.forEachLimit(result.types, options.concurrency || 1, (testType, done) => { 75 | let counter = 0; 76 | (function runAttempt() { 77 | const iteration = counter; 78 | if (counter > 3) { 79 | log(` cancelled ${testName} ${testType} ${iteration}`); 80 | return done(); 81 | } 82 | counter++; 83 | 84 | log(` running ${testName} ${testType} ${iteration}`); 85 | options.runTest(testName, testType, result => { 86 | hz[testType] = result.result; 87 | elapsed[testType] = result.elapsed; 88 | count[testType] = result.count; 89 | 90 | if (typeof hz[testType] === 'number' && isFinite(hz[testType])) { 91 | log(` failed ${testName} ${testType} ${iteration}`); 92 | hz[testType] = 'Failed due to infinite benchmark'; 93 | runAttempt(); 94 | } else { 95 | log(` complete ${testName} ${testType} ${iteration} ${result.result}`); 96 | done(); 97 | } 98 | }); 99 | }()); 100 | }, 101 | () => { 102 | const supportsES6 = 'es6' in hz; 103 | const baseline = hz.es5; 104 | delete hz.es5; 105 | 106 | const stats = SixSpeed.stats[testName] = { 107 | supportsES6, 108 | baseline, 109 | relative: {}, 110 | raw: {}, 111 | elapsed, 112 | count, 113 | errors: {} 114 | }; 115 | Object.entries(hz).forEach(([frequency, testName]) => { 116 | if (typeof baseline !== 'number' || !isFinite(baseline)) { 117 | stats.errors[testName] = `baseline failed: ${baseline}`; 118 | } else if (typeof frequency === 'number') { 119 | hz[testName] = `${((frequency / baseline) * 100).toFixed(5)}% (${Benchmark.formatNumber(frequency.toFixed(0))} ops/sec)`; 120 | 121 | stats.relative[testName] = frequency / baseline; 122 | stats.raw[testName] = frequency; 123 | } else { 124 | stats.errors[testName] = frequency; 125 | } 126 | }); 127 | 128 | if (!supportsES6) { 129 | hz.es6 = 'unsupported'; 130 | } 131 | 132 | log(`${testName} - Baseline ${typeof baseline === 'number' ? `is ${Benchmark.formatNumber(baseline.toFixed(0))} ops/sec` : `errored ${baseline}`}`); 133 | log(`Percentage of baseline: ${JSON.stringify(hz, undefined, 2)}`); 134 | log(`Duration: ${JSON.stringify(elapsed, undefined, 2)}`); 135 | log(`Count: ${JSON.stringify(count, undefined, 2)}`); 136 | 137 | if (options.testDone) { 138 | options.testDone(); 139 | } 140 | done(); 141 | }); 142 | }, 143 | () => { 144 | SixSpeed.running = false; 145 | SixSpeed.ran = true; 146 | 147 | if (options.done) { 148 | options.done(); 149 | } 150 | }); 151 | }, 152 | 153 | benchTest(test, type, callback, async) { 154 | try { 155 | SixSpeed.tests[test][type](fn => { 156 | const bench = new Benchmark(`${test}-${type}`, fn); 157 | bench.on('complete', () => { 158 | callback({result: bench.error ? `${bench.error}` : bench.hz, elapsed: bench.times.elapsed, count: bench.count}); 159 | }); 160 | bench.run({async}); 161 | }, test, type, testRequire, assertEqual); 162 | } catch (err) { 163 | callback({result: `${err}`}); 164 | } 165 | } 166 | }; 167 | 168 | function assertEqual(a, b) { 169 | if (a !== b) { 170 | throw new Error(`AssertError - Expect ${a} to equal ${b}`); 171 | } 172 | } 173 | 174 | function testRequire(name) { 175 | let lib 176 | try { 177 | lib = require(name); 178 | } 179 | catch (e) { 180 | throw new Error(`Unsupported test library: ${name}`); 181 | } 182 | 183 | return lib 184 | } 185 | 186 | if (typeof global !== 'undefined') { 187 | global.SixSpeed = SixSpeed; 188 | } 189 | -------------------------------------------------------------------------------- /lib/user-agent.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.parse = userAgent => { 3 | let browserName = userAgent; 4 | let browserVersion = 'unknown'; 5 | 6 | if (userAgent.match(/Edg\/(\S+)/)) { 7 | browserName = 'edge'; 8 | browserVersion = RegExp.$1; 9 | } else if (userAgent.match(/MSIE ([\.\d]+)/)) { 10 | browserName = 'ie'; 11 | browserVersion = RegExp.$1; 12 | } else if (userAgent.match(/Trident\/.*rv:([\.\d]+)/)) { 13 | browserName = 'ie'; 14 | browserVersion = RegExp.$1; 15 | } else if (userAgent.match(/Firefox\/(\S+)/)) { 16 | browserName = 'firefox'; 17 | browserVersion = RegExp.$1; 18 | } else if (userAgent.match(/Chrome\/(\S+)/)) { 19 | browserName = 'chrome'; 20 | browserVersion = RegExp.$1; 21 | } else if (userAgent.match(/AppleWebKit\/(\S+)/)) { 22 | // Some strain of webkit 23 | browserName = 'webkit'; 24 | browserVersion = RegExp.$1; 25 | 26 | // Check to see if the Safari version matches. If so then we are running a formal 27 | // release. 28 | // Isn't user agent parsing fun 29 | if (userAgent.match(/Safari\/(\S+)/) 30 | && RegExp.$1 === browserVersion 31 | && userAgent.match(/Version\/(\S+)/)) { 32 | browserName = 'safari'; 33 | browserVersion = RegExp.$1; 34 | } 35 | } 36 | 37 | return {name: browserName, version: browserVersion}; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/worker-test.js: -------------------------------------------------------------------------------- 1 | 2 | onmessage = ({data}) => { 3 | SixSpeed.benchTest(data.name, $type, result => { 4 | postMessage({result}); 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | /*global Worker */ 2 | var vms = {}; 3 | 4 | function runTest(name, type, complete) { 5 | if (window.Worker !== undefined) { 6 | function doIt() { 7 | vm.onmessage = ({data}) => { 8 | vm.onmessage = undefined; 9 | complete(data.result || data); 10 | }; 11 | vm.postMessage({name}); 12 | } 13 | 14 | let vm = vms[type]; 15 | if (!vm) { 16 | vm = vms[type] = new Worker(`${type}.js`); 17 | } 18 | doIt(); 19 | } else { 20 | SixSpeed.benchTest(name, type, result => { 21 | setTimeout(() => { 22 | complete(result); 23 | }, 0); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /notes.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": { 3 | "node": ["v8"], 4 | "chrome": ["v8"], 5 | "firefox": ["SpiderMonkey"], 6 | "ie": ["Chakra"], 7 | "edge": ["v8"], 8 | "safari": ["JavaScriptCore"], 9 | "webkit": ["JavaScriptCore"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "six-speed", 3 | "version": "1.0.0", 4 | "description": "ES6 polyfill vs. feature performance tests", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/kpdecker/six-speed" 8 | }, 9 | "keywords": [ 10 | "es6", 11 | "benchmark", 12 | "performance", 13 | "polyfill" 14 | ], 15 | "main": "index.js", 16 | "scripts": { 17 | "test": "gulp test", 18 | "test:sauce": "gulp test:sauce", 19 | "test:node": "gulp test:node", 20 | "start": "gulp server", 21 | "profile:node": "gulp profile:node", 22 | "report": "gulp report", 23 | "build": "gulp build" 24 | }, 25 | "author": "Kevin Decker ", 26 | "license": "MIT", 27 | "dependencies": { 28 | "@babel/core": "^7.19.3", 29 | "@babel/plugin-transform-runtime": "^7.19.1", 30 | "@babel/polyfill": "^7.12.1", 31 | "@babel/runtime": "^7.19.0", 32 | "@hapi/hapi": "^20.2.1", 33 | "@hapi/inert": "^7.0.0", 34 | "applescript": "^1.0.0", 35 | "async": "^3.2.4", 36 | "babel-loader": "^8.2.5", 37 | "benchmark": "^2.1.4", 38 | "bluebird": "^3.7.2", 39 | "bootstrap": "^3.4.1", 40 | "browser-downloader": "^2.1.0", 41 | "del": "^6.1.1", 42 | "esprima": "^4.0.1", 43 | "fancy-log": "^2.0.0", 44 | "gulp": "^4.0.2", 45 | "handlebars": "^4.7.7", 46 | "imports-loader": "^4.0.1", 47 | "jquery": "^3.6.1", 48 | "lodash": "^4.17.21", 49 | "microtime": "^3.1.1", 50 | "minimist": "^1.2.6", 51 | "node-libs-browser": "^2.2.1", 52 | "plugin-error": "^2.0.0", 53 | "sauce-tunnel": "^2.5.0", 54 | "through2": "^4.0.2", 55 | "typescript": "^4.8.4", 56 | "user-home": "^3.0.0", 57 | "vinyl": "^2.2.1", 58 | "webdriverio": "^4.14.1", 59 | "webpack": "^5.74.0" 60 | }, 61 | "devDependencies": { 62 | "@babel/preset-env": "^7.19.3", 63 | "@babel/runtime-corejs3": "^7.19.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /report/index.js: -------------------------------------------------------------------------------- 1 | /*global REPORT_DATA, localStorage */ 2 | require('@babel/polyfill'); 3 | 4 | const hasStorage = (() => { 5 | try { 6 | localStorage.setItem('_test', '1'); 7 | } catch (e) { 8 | return false; 9 | } 10 | 11 | localStorage.removeItem('_test'); 12 | return true; 13 | })(); 14 | 15 | const filter = hasStorage 16 | ? JSON.parse(localStorage.getItem('filter') || '{}') 17 | : {}; 18 | 19 | $(() => { 20 | $('[data-toggle="tooltip"]').tooltip(); 21 | 22 | renderList('engine', REPORT_DATA.engines); 23 | renderList('implementation', REPORT_DATA.implementations); 24 | filterUI(); 25 | }); 26 | 27 | function renderList(id, list) { 28 | list = list.map(({dash, selector, name}) => dash 29 | ? `` 30 | : `
  • 31 | ${name} 32 |
  • `).join(''); 33 | 34 | $(`.js-${id}-list`) 35 | .html(list) 36 | .on('click', 'a', e => { 37 | const $target = $(e.target); 38 | const clazz = $target.data(id); 39 | 40 | filter[id] = filter[id] !== clazz ? clazz : undefined; 41 | 42 | filterUI(); 43 | saveFilters(); 44 | 45 | e.preventDefault(); 46 | }); 47 | } 48 | 49 | function filterUI() { 50 | // Adjust the colspan if we need to 51 | if (/version/.test(filter.engine)) { 52 | $('table').addClass('version-filter'); 53 | toggleColspan('data-old-colspan', 'colspan'); 54 | } else { 55 | $('table').removeClass('version-filter'); 56 | toggleColspan('colspan', 'data-old-colspan'); 57 | } 58 | 59 | // Update the column headers 60 | toggleMatching($('thead').find('th'), filter.engine); 61 | 62 | // Update the row headers 63 | toggleMatching($('tbody th'), filter.implementation); 64 | 65 | // Update the cells 66 | let toShow = ''; 67 | if (filter.implementation) { 68 | toShow += `.${filter.implementation}`; 69 | } 70 | if (filter.engine) { 71 | toShow += `.${filter.engine}`; 72 | } 73 | toggleMatching($('tbody td'), toShow); 74 | 75 | // Update the selected indicators 76 | $('.dropdown').find('.glyphicon').remove(); 77 | if (filter.engine) { 78 | $(`[data-engine="${filter.engine}"]`).append(''); 79 | } 80 | if (filter.implementation) { 81 | $(`[data-implementation="${filter.implementation}"]`).append(''); 82 | } 83 | } 84 | 85 | function saveFilters() { 86 | if (hasStorage) { 87 | localStorage.setItem('filter', JSON.stringify(filter)); 88 | } 89 | } 90 | 91 | function toggleColspan(to, from) { 92 | $(`thead > tr:first-of-type > th[${from}]`).each(function() { 93 | const $el = $(this); 94 | // Node is distinct in that all of it's tested versions are stable. 95 | if ($el.text() !== 'node') { 96 | $el.attr(to, $el.attr(from)) 97 | .removeAttr(from); 98 | } 99 | }); 100 | } 101 | 102 | function toggleMatching($el, filterClass) { 103 | if (filterClass) { 104 | if (!(/\./.test(filterClass))) { 105 | filterClass = `.${filterClass}`; 106 | } 107 | 108 | $el.filter(`${filterClass}`).show(); 109 | $el.filter(`:not([rowspan],${filterClass})`).hide(); 110 | } else { 111 | $el.show(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /report/report.css: -------------------------------------------------------------------------------- 1 | a, a:visited { 2 | color: #3352ff; 3 | text-decoration: none; 4 | } 5 | 6 | .dropdown-menu .glyphicon-ok { 7 | font-size: 0.75em; 8 | padding-right: 10px; 9 | } 10 | 11 | [data-toggle="tooltip"] > .glyphicon { 12 | font-size: 0.75em; 13 | vertical-align: text-top; 14 | color: #2C2E3A; 15 | } 16 | .tooltip-inner { 17 | white-space: pre-wrap; 18 | text-align: left; 19 | } 20 | 21 | .table thead { 22 | position: sticky; 23 | top: 0; 24 | z-index: 1; 25 | background: #fff; 26 | } 27 | 28 | .table > tbody > tr > td, 29 | .table > tbody > tr > th, 30 | .table > tfoot > tr > td, 31 | .table > tfoot > tr > th, 32 | .table > thead > tr > td, 33 | .table > thead > tr > th { 34 | padding: 6px; 35 | font-size: 1.2rem; 36 | } 37 | 38 | 39 | .feature-row { 40 | margin-top: 5px; 41 | border-top: 1px solid #cdcdcd; 42 | } 43 | 44 | .browser-first { 45 | border-left: 2px solid #cdcdcd; 46 | } 47 | .version-filter .browser-first { 48 | border-left: none; 49 | } 50 | 51 | .test-error { 52 | background: #F99; 53 | } 54 | 55 | .test-link { 56 | display: block; 57 | font-size: 0.9em; 58 | padding-top: 5px; 59 | } 60 | 61 | .test-ok { 62 | background: #CFC; 63 | } 64 | .test-faster { 65 | background: #9F9; 66 | } 67 | 68 | .test-slow { 69 | background: #FFB; 70 | } 71 | 72 | 73 | .test-no-support { 74 | background: #eee; 75 | } -------------------------------------------------------------------------------- /tasks/bench.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{#each scripts}} 8 | 9 | {{/each}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /tasks/build.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Babel = require('@babel/core'); 3 | const Esprima = require('esprima'); 4 | const Fs = require('fs'); 5 | const Gulp = require('gulp'); 6 | const Vinyl = require('vinyl'); 7 | const PluginError = require('plugin-error'); 8 | const Handlebars = require('handlebars'); 9 | const Path = require('path'); 10 | const Through = require('through2'); 11 | const TypeScript = require('typescript'); 12 | const webpack = require('webpack'); 13 | const benchTemplate = Handlebars.compile(Fs.readFileSync(`${__dirname}/bench.handlebars`).toString()); 14 | const profileTemplate = Handlebars.compile(Fs.readFileSync(`${__dirname}/profile.handlebars`).toString()); 15 | const Log = require('fancy-log'); 16 | 17 | const closureExterns = 18 | '/** @param {function()} fn */ function test(fn) {}\n' + 19 | '/** @param {...*} var_args */ function assertEqual(var_args) {}\n'; 20 | 21 | Gulp.task('build:webpack', async (callback) => { 22 | return webpack({ 23 | mode: 'production', 24 | entry: './lib/runner', 25 | output: { 26 | path: Path.resolve(__dirname, '../build/'), 27 | filename: 'runner.js' 28 | }, 29 | externals: { 30 | lodash: '_', 31 | benchmark: 'Benchmark' 32 | }, 33 | module: { 34 | rules: [{ 35 | test: /\.jsx?$/, 36 | exclude: /node_modules|vendor/, 37 | loader: 'babel-loader' 38 | }] 39 | } 40 | }, (err, stats) => { 41 | if (err) { 42 | throw new PluginError('webpack', err); 43 | } 44 | Log('[webpack]', stats.toString({timings: true, chunks: false})); 45 | callback(); 46 | }); 47 | }); 48 | 49 | Gulp.task('build:tests', async () => { 50 | const scripts = [ 51 | 'runner.js' 52 | ]; 53 | 54 | return Gulp.src('tests/*') 55 | .pipe(Through.obj(function(testDir, dirEnc, dirCallback) { 56 | if (!testDir.isDirectory()) { 57 | return dirCallback(); 58 | } 59 | 60 | const testName = Path.basename(testDir.path); 61 | 62 | Gulp.src(`${testDir.path}/*.*`) 63 | .pipe(Through.obj(({path, contents}, enc, fileCallback) => { 64 | const self = this; 65 | const ext = Path.extname(path).replace(/^\./, ''); 66 | const content = contents.toString(); 67 | 68 | function createFile(testType, src) { 69 | const fileName = `tests/${testName}__${testType}.js`; 70 | 71 | if (testType !== 'es6') { 72 | try { 73 | // If esprima can parse, then assume that it should work under es5 74 | Esprima.parse(src); 75 | } catch (err) { 76 | if (!(/Unexpected token/.test(err)) && !(/Invalid regular expression/.test(err)) 77 | && !(/Use of future reserved word in strict mode/.test(err))) { 78 | throw new Error(err); 79 | } 80 | return; 81 | } 82 | } 83 | 84 | src = `function(test, testName, testType, require, assertEqual) {${src}}`; 85 | scripts.push(fileName); 86 | self.push(new Vinyl({ 87 | path: fileName, 88 | contents: Buffer.from( 89 | `"use strict";\nSixSpeed.tests[${JSON.stringify(testName)}] = SixSpeed.tests[${JSON.stringify(testName)}] || {};\nSixSpeed.tests[${JSON.stringify(testName)}][${JSON.stringify(testType)}] = ${src};\n`) 90 | })); 91 | } 92 | 93 | if (ext === 'es6') { 94 | const babel = Babel.transform(content, { 95 | presets: [ 96 | ['@babel/preset-env'] 97 | ] 98 | }).code; 99 | 100 | const babelRuntime = Babel.transform(content, { 101 | presets: [ 102 | ['@babel/preset-env'] 103 | ], 104 | plugins: [ 105 | ['@babel/plugin-transform-runtime'] 106 | ] 107 | }).code; 108 | 109 | const babelLoose = Babel.transform(content, { 110 | presets: [ 111 | ['@babel/preset-env', { loose: true }] 112 | ], 113 | plugins: [ 114 | ['@babel/plugin-transform-runtime'] 115 | ] 116 | }).code; 117 | 118 | createFile('babel', babel); 119 | if (babel !== babelRuntime) { 120 | createFile('babel-runtime', babelRuntime); 121 | } 122 | if (babel !== babelLoose) { 123 | createFile('babel-loose', babelLoose); 124 | } 125 | 126 | createFile('babel', babel); 127 | if (babel !== babelRuntime) { 128 | createFile('babel-runtime', babelRuntime); 129 | } 130 | if (babel !== babelLoose) { 131 | createFile('babel-loose', babelLoose); 132 | } 133 | 134 | createFile('typescript', TypeScript.transpile(content, { 135 | module: TypeScript.ModuleKind.CommonJS, 136 | downlevelIteration: true 137 | })); 138 | } 139 | createFile(ext, content); 140 | 141 | fileCallback(); 142 | }, 143 | cb => { 144 | cb(); 145 | dirCallback(); 146 | })); 147 | })) 148 | .pipe(Gulp.dest('build/')); 149 | }); 150 | 151 | Gulp.task('build:browser-runner', () => 152 | Gulp.src([ 153 | 'lib/redirect-stable.html', 154 | 'lib/redirect-prerelease.html', 155 | 'lib/browser.js', 156 | 'lib/browser-profile.js', 157 | 'lib/iframe.js', 158 | 'lib/worker.js', 159 | 'lib/worker-test.js', 160 | require.resolve('benchmark'), 161 | require.resolve('lodash/lodash'), 162 | require.resolve('@babel/polyfill/dist/polyfill') 163 | ]) 164 | .pipe(Gulp.dest('build')) 165 | ); 166 | 167 | Gulp.task('build:browser', Gulp.series('build:browser-runner', 'build:webpack', 'build:tests', async () => { 168 | const scripts = [ 169 | 'lodash.js', 170 | 'benchmark.js', 171 | 'runner.js' 172 | ]; 173 | 174 | return Gulp.src('build/tests/*.*') 175 | .pipe(Through.obj((testDir, dirEnc, callback) => { 176 | if (!testDir.isDirectory()) { 177 | scripts.push(`tests/${Path.basename(testDir.path)}`); 178 | } 179 | return callback(); 180 | }, function(callback) { 181 | const types = {}; 182 | _.each(scripts, script => { 183 | if ((/.*__(.*)\.js$/).exec(script)) { 184 | let type = types[RegExp.$1]; 185 | if (!type) { 186 | type = types[RegExp.$1] = []; 187 | 188 | type.push('lodash.js'); 189 | type.push('benchmark.js'); 190 | if (RegExp.$1 === 'babel') { 191 | type.push('polyfill.js'); 192 | } 193 | type.push('runner.js'); 194 | } 195 | type.push(script); 196 | } 197 | }); 198 | scripts.push('worker.js'); 199 | scripts.push('browser.js'); 200 | 201 | this.push(new Vinyl({ 202 | path: 'index.html', 203 | contents: Buffer.from(benchTemplate({scripts})) 204 | })); 205 | 206 | // We need a special mime type to enable all of the features on Firefox. 207 | const mozScripts = _.map(scripts, script => `../${script}`); 208 | mozScripts[mozScripts.length - 2] = '../iframe.js'; 209 | this.push(new Vinyl({ 210 | path: 'moz/index.html', 211 | contents: Buffer.from(benchTemplate({ 212 | scripts: mozScripts, 213 | jsType: 'application/javascript;version=1.7' 214 | })) 215 | })); 216 | 217 | _.each(types, (scripts, name) => { 218 | const workerScripts = scripts.concat('worker-test.js'); 219 | this.push(new Vinyl({ 220 | path: `${name}.js`, 221 | contents: Buffer.from( 222 | `$type = ${JSON.stringify(name)};\n${workerScripts.map(script => `try { importScripts(${JSON.stringify(script)}); } catch (err) { console.log(${JSON.stringify(script)} + err); }`).join('\n')}`) 223 | })); 224 | 225 | // We need a special mime type to enable all of the features on Firefox. 226 | const mozScripts = _.map(scripts, script => `../${script}`); 227 | this.push(new Vinyl({ 228 | path: `moz/${name}.html`, 229 | contents: Buffer.from(benchTemplate({scripts: mozScripts, jsType: 'application/javascript;version=1.7'})) 230 | })); 231 | }); 232 | 233 | 234 | scripts[scripts.length - 1] = 'browser-profile.js'; 235 | this.push(new Vinyl({ 236 | path: 'profile.html', 237 | contents: Buffer.from(profileTemplate({scripts})) 238 | })); 239 | 240 | // We need a special mime type to enable all of the features on Firefox. 241 | this.push(new Vinyl({ 242 | path: 'moz/profile.html', 243 | contents: Buffer.from(profileTemplate({ 244 | scripts: _.map(scripts, script => `../${script}`), 245 | jsType: 'application/javascript;version=1.7' 246 | })) 247 | })); 248 | 249 | callback(); 250 | })) 251 | .pipe(Gulp.dest('build/')); 252 | })); 253 | 254 | Gulp.task('build', Gulp.series('build:browser')); 255 | -------------------------------------------------------------------------------- /tasks/driver.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-process-env */ 2 | 3 | const _ = require('lodash'); 4 | 5 | const PluginError = require('plugin-error'); 6 | const WebdriverIO = require('webdriverio'); 7 | const UserAgent = require('../lib/user-agent'); 8 | const Log = require('fancy-log'); 9 | 10 | const browserOptions = { 11 | chrome: { 12 | chromeOptions: { 13 | args: [ 14 | // Defaults from Sauce Labs 15 | 'disable-webgl', 16 | 'blacklist-webgl', 17 | 'blacklist-accelerated-compositing', 18 | 'disable-accelerated-2d-canvas', 19 | 'disable-accelerated-compositing', 20 | 'disable-accelerated-layers', 21 | 'disable-accelerated-plugins', 22 | 'disable-accelerated-video', 23 | 'disable-accelerated-video-decode', 24 | 'disable-gpu', 25 | 'test-type', 26 | 27 | // Our own exec flags 28 | 'enable-javascript-harmony' 29 | ] 30 | } 31 | } 32 | }; 33 | 34 | module.exports.test = (remote, config, done) => { 35 | const options = _.defaults({ 36 | desiredCapabilities: _.merge({ 37 | name: `SixSpeed - ${config.browserName}`, 38 | public: 'public', 39 | build: process.env.TRAVIS_BUILD_ID, 40 | 41 | loggingPrefs: { 42 | 'browser': 'WARNING' 43 | }, 44 | recordVideo: false, 45 | 'webdriver.remote.quietExceptions': true 46 | }, config, browserOptions[config.browserName]) 47 | }, remote); 48 | 49 | let userAgent; 50 | let browserId; 51 | let browserLog; 52 | let stats; 53 | const testServer = remote.testServer || 'http://localhost:9999/'; 54 | const indexFile = config.browserName === 'firefox' ? 'moz/index.html?tag=stable' : 'index.html?tag=stable'; 55 | 56 | const client = WebdriverIO 57 | .remote(options) 58 | .init() 59 | .url(testServer + indexFile) 60 | .execute(() => /*global navigator */ 61 | navigator.userAgent, 62 | (err, {value}) => { 63 | if (err) { 64 | throw new PluginError('test:sauce', `${config.browserName} ${err}`); 65 | } 66 | 67 | userAgent = UserAgent.parse(value); 68 | browserId = `${userAgent.name} ${userAgent.version}`; 69 | }); 70 | 71 | (function exec(timeout) { 72 | /*global SixSpeed */ 73 | client.pause(Math.max(timeout, 15000)) 74 | .execute(() => !SixSpeed.running && SixSpeed.ran, 75 | (err, {value}) => { 76 | if (err) { 77 | throw new PluginError('test:sauce', `${browserId} ${err}`); 78 | } 79 | 80 | if (!value) { 81 | exec(timeout / 2); 82 | } else { 83 | cleanup(); 84 | } 85 | }); 86 | }(60 * 1000)); 87 | 88 | function cleanup() { 89 | client 90 | .log('browser', (err, {value}) => { 91 | if (err) { 92 | // Not supported under IE so just log and move on. 93 | Log('test:sauce', browserId, err); 94 | } else { 95 | browserLog = value; 96 | } 97 | }) 98 | .execute(() => SixSpeed.stats, 99 | (err, {value}) => { 100 | if (err) { 101 | throw new PluginError('test:sauce', `${browserId} ${err}`); 102 | } 103 | 104 | stats = value; 105 | }) 106 | .end() 107 | .call(() => { 108 | // Log for the user 109 | _.each(browserLog, message => { 110 | Log(browserId, message.source || '', '-', message.message); 111 | }); 112 | _.each(_.keys(stats).sort(), name => { 113 | const stat = stats[name]; 114 | 115 | Log(browserId, name, _.map(stat.relative, (relative, type) => `${type}: ${(relative * 100).toFixed(5)}%`).join(' ')); 116 | }); 117 | 118 | done(); 119 | }); 120 | } 121 | }; 122 | -------------------------------------------------------------------------------- /tasks/local.js: -------------------------------------------------------------------------------- 1 | const Async = require('async'); 2 | const AppleScript = require('applescript'); 3 | const ChildProcess = require('child_process'); 4 | const Gulp = require('gulp'); 5 | const Path = require('path'); 6 | const Server = require('./server'); 7 | const userhome = require('user-home'); 8 | const safariStableRedirect = Path.resolve(Path.join(__dirname, '..', 'build/redirect-stable.html')); 9 | const safariPrereleaseRedirect = Path.resolve(Path.join(__dirname, '..', 'build/redirect-prerelease.html')); 10 | 11 | const chromeArgs = [ 12 | // Defaults from Sauce Labs 13 | '--disable-webgl', 14 | '--blacklist-webgl', 15 | '--blacklist-accelerated-compositing', 16 | '--disable-accelerated-2d-canvas', 17 | '--disable-accelerated-compositing', 18 | '--disable-accelerated-layers', 19 | '--disable-accelerated-plugins', 20 | '--disable-accelerated-video', 21 | '--disable-accelerated-video-decode', 22 | '--disable-gpu', 23 | '--test-type', 24 | 25 | // Our own exec flags 26 | '--enable-javascript-harmony', 27 | '--enable-benchmarking', 28 | '--disable-background-timer-throttling' 29 | ]; 30 | 31 | const browsers = [ 32 | { 33 | path: `${userhome}/browsers/Google Chrome.app/Contents/MacOS/Google Chrome`, 34 | app: `${userhome}/browsers/Google Chrome.app`, 35 | args: chromeArgs.concat('http://localhost:9999/?tag=stable') 36 | }, 37 | { 38 | path: `${userhome}/browsers/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary`, 39 | app: `${userhome}/browsers/Google Chrome Canary.app`, 40 | args: chromeArgs.concat('http://localhost:9999/?tag=prerelease') 41 | }, 42 | { 43 | path: `${userhome}/browsers/Firefox.app/Contents/MacOS/firefox`, 44 | app: `${userhome}/browsers/Firefox.app`, 45 | args: ['http://localhost:9999/moz/?tag=stable'] 46 | }, 47 | { 48 | path: `${userhome}/browsers/FirefoxNightly.app/Contents/MacOS/firefox`, 49 | app: `${userhome}/browsers/FirefoxNightly.app`, 50 | args: ['http://localhost:9999/moz/?tag=prerelease'] 51 | }, 52 | { 53 | path: '/Applications/Safari.app/Contents/MacOS/Safari', 54 | app: '/Applications/Safari.app', 55 | args: [safariStableRedirect] 56 | }, 57 | { 58 | path: `${userhome}/browsers/WebKit.app/Contents/MacOS/WebKit`, 59 | app: `${userhome}/browsers/WebKit.app`, 60 | args: [safariPrereleaseRedirect] 61 | } 62 | ]; 63 | 64 | Gulp.task('test:local', Gulp.series('build:browser', async (callback) => { 65 | return Async.eachSeries(browsers, runProcess, () => { 66 | callback(); 67 | }); 68 | })); 69 | 70 | function runProcess({app, path, args}, callback) { 71 | let child; 72 | const appPath = Path.resolve(app); 73 | Server.start(() => { 74 | child = ChildProcess.spawn(path, args, {stdio: 'inherit'}); 75 | 76 | if (!(/firefox/.test(path))) { 77 | setTimeout(() => { 78 | execAppleScript(`tell application "${appPath}" to activate`, () => {}); 79 | }, 3000); 80 | } 81 | }, () => { 82 | function killServer() { 83 | Server.stop(() => { 84 | callback(); 85 | }); 86 | } 87 | 88 | if (/Safari|WebKit/.test(path)) { 89 | execAppleScript(`tell application "${appPath}" to close (every tab of window 1)`, () => { 90 | execAppleScript(`tell application "${appPath}" to quit`, killServer); 91 | }); 92 | } else { 93 | child.kill(); 94 | killServer(); 95 | } 96 | }); 97 | } 98 | 99 | function execAppleScript(script, cb) { 100 | console.log('Running script', script); 101 | AppleScript.execString(script, cb); 102 | } 103 | -------------------------------------------------------------------------------- /tasks/node.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Args = require('../lib/args'); 3 | const ChildProcess = require('child_process'); 4 | const Gulp = require('gulp'); 5 | const PluginError = require('plugin-error'); 6 | 7 | Gulp.task('test:node', Gulp.series('build:tests', async (callback) => { 8 | return findStagingArgs(args => { 9 | args.push('lib/node'); 10 | runNode(args, callback); 11 | }); 12 | })); 13 | 14 | Gulp.task('profile:node', Gulp.series('build:tests', (callback) => { 15 | return findStagingArgs(args => { 16 | args.push('--prof'); 17 | args.push('lib/node-profile'); 18 | args.push(`--testName=${Args.testName}`); 19 | args.push(`--type=${Args.type}`); 20 | args.push(`--count=${Args.count}`); 21 | 22 | runNode(args, callback); 23 | }); 24 | })); 25 | 26 | function findStagingArgs(callback) { 27 | ChildProcess.exec('node --v8-options | grep "in progress"', (err, stdout) => { 28 | if (err && err.code !== 1) { 29 | throw new PluginError('test:node', err); 30 | } 31 | 32 | // Run with everything enabled, per https://iojs.org/en/es6.html 33 | const args = _.compact(stdout.replace(/\n$/, '').split(/\n/g).map(line => { 34 | if (/(--\w+)/.exec(line)) { 35 | return RegExp.$1; 36 | } 37 | })); 38 | args.push('--harmony'); 39 | callback(args); 40 | }); 41 | } 42 | 43 | function runNode(args, callback) { 44 | const test = ChildProcess.spawn('node', args, {stdio: 'inherit'}); 45 | test.on('close', code => { 46 | if (code) { 47 | throw new PluginError('test:node', `Exited with code: ${code}`); 48 | } 49 | 50 | callback(); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /tasks/profile.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
    8 | 9 | {{#each scripts}} 10 | 11 | {{/each}} 12 | 13 | 14 | -------------------------------------------------------------------------------- /tasks/report.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 42 | 43 |
    44 | 45 | 46 | 47 | 48 | 49 | {{#each browsers}} 50 | 51 | {{/each}} 52 | 53 | 54 | 55 | {{#each browsers}} 56 | {{#each versions}} 57 | 58 | {{/each}} 59 | {{/each}} 60 | 61 | 62 | 63 | 64 | {{#each tests as |test|}} 65 | {{#each types}} 66 | 67 | {{#if @first}} 68 | 72 | {{/if}} 73 | 74 | 75 | {{#each results}} 76 | 77 | {{/each}} 78 | 79 | {{/each}} 80 | {{/each}} 81 | 82 |
    Performance of ES6 features relative to the ES5 baseline operations per second.
    {{name}}
    {{display}}
    69 | {{test.display}} 70 | tests 71 | {{name}}{{{text}}}
    83 |
    84 |
    85 |
    86 |
    87 |

    Testing methodology

    88 |
    89 | 90 |
    91 |

    92 | Run on {{date}} using babel {{babelVersion}}, babel-runtime {{babelRuntimeVersion}} and TypeScript {{typescriptVersion}}. 93 | 94 |

    95 | For each of the ES6 features in question, a ES5 implementation of that functionality was written along with a ES6 version. It should be noted that the functionality is frequently the same, but in some cases the "common" vs. "correct" version was written, i.e. using x[key] = value vs. defineProperty which is faster but can be hit but a particular nasty edge case for those who deem it fun to extend Object.prototype. 96 |

    97 |

    98 | Babel, in both loose+runtime and runtime mode, and Babel were then used to compile the ES6 version to a ES5 compliant version, utilizing the runtime over polyfill to maintain test isolation and avoid native implementations where possible. 99 |

    100 |

    101 | All of these test instances were then benchmarked in the given JavaScript engine using Benchmark.js and then the operations per second compared to the ES5 implementation. Cross browser and cross execution comparisions are avoided as much as possible to isolate environmental issues when executing on VMs in the cloud. Identical indicates that the tested implementation was +/- 10% of the ES5 implementation. 102 |

    103 |
    104 |
    105 |
    106 | 107 | 113 | 114 | 117 | 118 | 119 | 120 | 121 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /tasks/report.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Babel = require('@babel/core'); 3 | const BabelRuntimePackage = require('@babel/plugin-transform-runtime'); 4 | const DataStore = require('../lib/data-store'); 5 | const Fs = require('fs'); 6 | const Gulp = require('gulp'); 7 | const Log = require('fancy-log'); 8 | const PluginError = require('plugin-error'); 9 | const Handlebars = require('handlebars'); 10 | const Path = require('path'); 11 | const webpack = require('webpack'); 12 | const pkg = require('../package.json').dependencies; 13 | 14 | Gulp.task('report:static', async () => { 15 | return Gulp.src('report/*.css') 16 | .pipe(Gulp.dest('site/')) 17 | }); 18 | 19 | Gulp.task('report:bootstrap:fonts', async () => { 20 | return Gulp.src(['bower_components/bootstrap/fonts/*'], {base: 'bower_components/bootstrap'}) 21 | .pipe(Gulp.dest('site/')) 22 | }); 23 | 24 | Gulp.task('report:bootstrap:css', async () => { 25 | return Gulp.src(['bower_components/bootstrap/dist/css/*'], {base: 'bower_components/bootstrap/dist'}) 26 | .pipe(Gulp.dest('site/')) 27 | }); 28 | 29 | Gulp.task('report:webpack', async (callback) => { 30 | return webpack({ 31 | mode: 'production', 32 | entry: { 33 | report: './report/index.js' 34 | }, 35 | output: { 36 | path: Path.resolve(__dirname, '../site/'), 37 | filename: '[name].js' 38 | }, 39 | module: { 40 | rules: [{ 41 | test: /\.jsx?$/, 42 | exclude: /node_modules|vendor|bower_components/, 43 | use: [{ 44 | loader: 'babel-loader', 45 | }] 46 | }, { 47 | test: /bootstrap\/js/, 48 | use: [{ 49 | loader: 'imports', 50 | options: { 51 | jQuery: 'jquery' 52 | } 53 | }] 54 | }] 55 | }, 56 | resolve: { 57 | alias: { 58 | root: Path.join(__dirname, '..', 'bower_components') 59 | } 60 | }, 61 | }, (err, stats) => { 62 | if (err) { 63 | throw new PluginError('webpack', err); 64 | } 65 | Log('[webpack]', stats.toString()); 66 | callback(); 67 | }); 68 | }); 69 | 70 | function render() { 71 | const data = DataStore.load(); 72 | const notes = DataStore.notes(); 73 | 74 | // And the browsers tested 75 | let browserTags = []; 76 | 77 | let familyTags = []; 78 | let browsers = _.map(data, (browserData, browserName) => { 79 | const tags = _.keys(browserData); 80 | const family = notes.family[browserName].map(tag => `js-family-${tag}`).join(' '); 81 | let versionTag = ''; 82 | 83 | const fullVersions = _.map(tags, tag => { 84 | // A bit of a hack here, but we treat all node releases that we are testing as stable 85 | let tagName = tag; 86 | if (/^\d/.test(tag)) { 87 | tagName = 'stable'; 88 | } 89 | 90 | browserTags = browserTags.concat(tagName); 91 | 92 | tagName = ` js-version-${tagName}`; 93 | versionTag += tagName; 94 | 95 | const versionName = browserData[tag].version; 96 | let displayName = versionName; 97 | if (browserName !== 'node' && browserName !== 'webkit') { 98 | displayName = parseFloat(versionName); 99 | } 100 | 101 | return { 102 | id: tag, 103 | name: versionName, 104 | display: displayName, 105 | tag: family + tagName 106 | }; 107 | }); 108 | 109 | familyTags = _.union(familyTags, notes.family[browserName]); 110 | return { 111 | name: browserName, 112 | versions: fullVersions, 113 | tag: family + versionTag 114 | }; 115 | }); 116 | browsers = _.filter(browsers, ({versions}) => versions.length > 0); 117 | 118 | // Pull out all of the tests that were actually run 119 | let implementations = []; 120 | let tests = _.map(data, browserData => _.flatten(_.map(browserData, ({stats}) => _.keys(stats)))); 121 | tests = _.flatten(tests); 122 | tests = _.uniq(tests); 123 | tests = tests.sort(); 124 | 125 | tests = _.map(tests, test => { 126 | let types = []; 127 | 128 | // Figure out what types this particular test has 129 | _.each(data, browserData => { 130 | _.each(browserData, versionData => { 131 | const stats = versionData.stats[test] || {}; 132 | types = _.union(types, _.keys(stats.relative), _.keys(stats.errors)); 133 | }); 134 | }); 135 | types = types.sort((a, b) => { 136 | // Push anything with es prefix to the end of the list 137 | if (/^es/.test(a)) { 138 | a = `zz${a}`; 139 | } 140 | if (/^es/.test(b)) { 141 | b = `zz${b}`; 142 | } 143 | return a.localeCompare(b); 144 | }); 145 | 146 | // Save these results to the full implementation list 147 | implementations = _.union(implementations, types); 148 | _.each(browsers, ({name, versions}) => { 149 | const browserData = data[name]; 150 | const firstVersion = true; 151 | 152 | _.each(versions, ({id}) => { 153 | const versionData = browserData[id]; 154 | const elapsed = versionData.stats[test] || {}; 155 | 156 | // Look for elapsed times that have a high variance 157 | if (elapsed != undefined || elapsed != null) { 158 | const types = Object.keys(elapsed); 159 | const average = types.reduce((prev, curr) => prev + elapsed[curr], 0) / types.length; 160 | 161 | if (types.find((type) => elapsed[type] / average > 2 || elapsed[type] / average < 0.5)) { 162 | console.warn('Elapsed outlier detected', name, id, test, elapsed); 163 | } 164 | } 165 | }); 166 | }); 167 | 168 | // And then collect the results for each type 169 | types = _.map(types, type => { 170 | const results = []; 171 | const typeClazz = `js-impl-${type.replace(/-.*$/, '')}`; 172 | _.each(browsers, ({name, versions}) => { 173 | const browserData = data[name]; 174 | let firstVersion = true; 175 | 176 | _.each(versions, ({id, tag}) => { 177 | const versionData = browserData[id]; 178 | const stats = versionData.stats[test] || {}; 179 | let speed = (stats.relative || {})[type]; 180 | const error = (stats.errors || {})[type]; 181 | let text = ''; 182 | let clazz = 'test-no-support'; 183 | let tip = ''; 184 | if (speed && !error) { 185 | if (speed.toFixed(1) === '1.0' || speed.toFixed(1) === '1.1' || speed.toFixed(1) === '0.9') { 186 | text = 'Identical'; 187 | clazz = 'test-ok'; 188 | } else if (speed > 1) { 189 | text = `${speed.toFixed(speed > 3 ? 0 : 1)}x faster`; 190 | clazz = 'test-faster'; 191 | } else { 192 | speed = 1 / speed; 193 | text = `${speed.toFixed(speed > 3 ? 0 : 1)}x slower`; 194 | clazz = 'test-slow'; 195 | } 196 | } else if (error && !(/SyntaxError|(Promise|Symbol)/.test(error))) { 197 | text = (/AssertError/).test(error) ? 'Incorrect' : 'Error'; 198 | clazz = 'test-error'; 199 | tip = error; 200 | } 201 | 202 | if (firstVersion) { 203 | clazz += ' browser-first'; 204 | firstVersion = false; 205 | } 206 | 207 | if (tip) { 208 | text = `${text} `; 209 | } 210 | 211 | results.push({ 212 | text, 213 | clazz: `${tag} ${typeClazz} ${clazz}` 214 | }); 215 | }); 216 | }); 217 | 218 | return { 219 | name: type, 220 | clazz: typeClazz, 221 | results 222 | }; 223 | }); 224 | 225 | return { 226 | name: test, 227 | display: test.replace(/_/g, ' '), 228 | types 229 | }; 230 | }); 231 | 232 | implementations = _.map(implementations, impl => impl.replace(/-.*$/, '')); 233 | implementations = _.uniq(implementations.sort()); 234 | implementations = _.map(implementations, implementation => ({ 235 | name: implementation, 236 | selector: `js-impl-${implementation}` 237 | })); 238 | 239 | const reportData = { 240 | engines: _.union(_.uniq(browserTags).map(tag => ({ 241 | name: _.capitalize(tag), 242 | selector: `js-version-${tag}` 243 | })), 244 | [{dash: true}], 245 | familyTags.sort().map(tag => ({ 246 | name: _.capitalize(tag), 247 | selector: `js-family-${tag}` 248 | }))), 249 | implementations 250 | }; 251 | 252 | const template = Handlebars.compile(Fs.readFileSync(`${__dirname}/report.handlebars`).toString()); 253 | return template({ 254 | browsers, 255 | tests, 256 | date: new Date().toLocaleDateString(), 257 | babelVersion: Babel.version, 258 | babelRuntimeVersion: pkg['@babel/runtime'].replace('^', ''), 259 | typescriptVersion: pkg.typescript.replace('^', ''), 260 | jqueryVersion: pkg.jquery.replace('^', ''), 261 | bootstrapVersion: pkg.bootstrap.replace('^', ''), 262 | 263 | reportData: JSON.stringify(reportData) 264 | }); 265 | } 266 | 267 | Gulp.task('report', Gulp.series('report:static', 'report:bootstrap:fonts', 'report:bootstrap:css', 'report:webpack', async () => { 268 | const report = render(); 269 | try { 270 | Fs.statSync('site'); 271 | } catch(e) { 272 | Fs.mkdirSync('site'); 273 | } 274 | return Fs.writeFileSync('site/index.html', report); 275 | })); 276 | -------------------------------------------------------------------------------- /tasks/sauce.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-process-env */ 2 | const _ = require('lodash'); 3 | 4 | const Async = require('async'); 5 | const Driver = require('./driver'); 6 | const Gulp = require('gulp'); 7 | const PluginError = require('plugin-error'); 8 | const SauceTunnel = require('sauce-tunnel'); 9 | const Server = require('./server'); 10 | const Log = require('fancy-log'); 11 | 12 | const browsers = [ 13 | { 14 | browserName: 'internet explorer' 15 | } 16 | ]; 17 | 18 | Gulp.task('test:sauce', Gulp.series('build:browser', async (callback) => { 19 | const user = process.env.SAUCE_USERNAME; 20 | const pass = process.env.SAUCE_ACCESS_KEY; 21 | const tunnelId = process.env.TRAVIS_JOB_ID || 42; 22 | 23 | return Server.start(() => { 24 | startTunnel(user, pass, tunnelId, tunnel => { 25 | Async.eachLimit(browsers, 5, (config, done) => { 26 | config = _.defaults({ 27 | 'tunnel-identifier': tunnelId 28 | }, config); 29 | 30 | const remote = { 31 | port: 4445, 32 | user, 33 | key: pass 34 | }; 35 | 36 | Driver.test(remote, config, done); 37 | }, 38 | () => { 39 | tunnel.stop(() => { 40 | Server.stop(() => { 41 | callback(); 42 | }); 43 | }); 44 | }); 45 | }); 46 | }); 47 | })); 48 | 49 | function startTunnel(user, pass, tunnelId, done) { 50 | const tunnel = new SauceTunnel(user, pass, tunnelId, true, []); 51 | tunnel.on('log:error', data => { 52 | Log(data); 53 | }); 54 | tunnel.on('verbose:debug', data => { 55 | Log(data); 56 | }); 57 | tunnel.start(success => { 58 | if (!success) { 59 | //throw new PluginError('test:sauce', 'Tunnel failed to open'); 60 | console.log('Tunnel failed to open'); 61 | } 62 | 63 | done(tunnel); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /tasks/server.js: -------------------------------------------------------------------------------- 1 | const DataStore = require('../lib/data-store'); 2 | const Gulp = require('gulp'); 3 | const PluginError = require('plugin-error'); 4 | const Hapi = require('@hapi/hapi'); 5 | const Inert = require('@hapi/inert'); 6 | const UserAgent = require('../lib/user-agent'); 7 | const Log = require('fancy-log'); 8 | 9 | let server; 10 | 11 | Gulp.task('server', callback => { // eslint-disable-line no-unused-vars 12 | exports.start(() => {}); 13 | }); 14 | 15 | exports.start = async (startup, testComplete) => { 16 | const server = await Hapi.server({ 17 | port: 9999, 18 | host: 'localhost', 19 | }); 20 | await server.register(Inert); 21 | 22 | // Simple endpoint to allow for sending remote data back to the server. 23 | server.route({ 24 | method: 'POST', 25 | path: '/log', 26 | handler({payload}, h) { 27 | const userAgent = UserAgent.parse(payload.browser); 28 | const data = JSON.parse(payload.data); 29 | 30 | Log('Storing data for browser', userAgent.name, userAgent.version, `{${Object.keys(data).join(', ')}}`); 31 | DataStore.store(userAgent.name, payload.tag, userAgent.version, data); 32 | 33 | return {}; 34 | } 35 | }); 36 | 37 | server.route({ 38 | method: 'POST', 39 | path: '/debug', 40 | handler({payload}, h) { 41 | const userAgent = UserAgent.parse(payload.browser); 42 | const message = payload.message; 43 | 44 | Log('[debug]', userAgent.name, userAgent.version, message); 45 | 46 | return {}; 47 | } 48 | }); 49 | 50 | server.route({ 51 | method: 'POST', 52 | path: '/done', 53 | handler(request, h) { 54 | if (testComplete) { 55 | testComplete(); 56 | } 57 | 58 | return {}; 59 | } 60 | }); 61 | 62 | server.route({ 63 | method: 'GET', 64 | path: '/{param*}', 65 | handler: { 66 | directory: { 67 | path: 'build' 68 | } 69 | } 70 | }); 71 | 72 | server.start(err => { 73 | if (err) { 74 | throw new PluginError('server', err); 75 | } 76 | 77 | Log('Server running at:', server.info.uri); 78 | startup(server.info.uri); 79 | }); 80 | }; 81 | 82 | exports.stop = done => { 83 | server.stop(done); 84 | }; 85 | -------------------------------------------------------------------------------- /tasks/vm.js: -------------------------------------------------------------------------------- 1 | const ChildProcess = require('child_process'); 2 | const Gulp = require('gulp'); 3 | const Log = require('fancy-log'); 4 | const Server = require('./server'); 5 | const userhome = require('user-home'); 6 | 7 | const RUN_USER = 'vmrun -gu IEUser -gp Passw0rd! '; 8 | 9 | Gulp.task('test:vm:edge', Gulp.series('build:browser', async (callback) => { 10 | return runVM('stable', runEdge, (err) => { 11 | if (err) { 12 | return callback(err); 13 | } 14 | 15 | runVM('preview', runEdge, callback); 16 | }); 17 | })); 18 | 19 | function runVM(branch, run, callback) { 20 | const vmx = `${userhome}/browsers/edge/${branch}/MSEdge - Win10_preview.vmx`; 21 | const tag = branch === 'preview' ? '/?tag=prerelease' : ''; 22 | Server.start(uri => { 23 | loadSnapshot(vmx) 24 | .then(() => startVM(vmx)) 25 | .then(() => setExperimental(vmx)) 26 | .then(() => run(vmx, `${uri}${tag}`)) 27 | .catch(cleanup); 28 | }, () => { 29 | cleanup(); 30 | }); 31 | 32 | function cleanup(err) { 33 | // Kill the vm 34 | stopVM(vmx) 35 | .catch(() => {}) 36 | .then(() => { 37 | Server.stop(() => { 38 | callback(err); 39 | }); 40 | }); 41 | } 42 | } 43 | 44 | // Some of this sourced from the excellent https://gist.github.com/neovov/5372144 45 | function startVM(vmx) { 46 | return run(`vmrun start "${vmx}"`) 47 | .then(delay(10)); 48 | } 49 | 50 | function delay(seconds) { 51 | return () => new Promise(resolve => { 52 | setTimeout(() => { 53 | resolve(); 54 | }, seconds * 1000); 55 | }); 56 | } 57 | 58 | function loadSnapshot(vmx) { 59 | return run(`vmrun listSnapshots "${vmx}"`) 60 | .then(snapshots => { 61 | if (!/six-speed/.test(snapshots)) { 62 | return Promise.reject(new Error('No six-speed snapshot in VM, please setup per README')); 63 | } 64 | 65 | return run(`vmrun revertToSnapshot "${vmx}" six-speed`); 66 | }); 67 | } 68 | 69 | function setExperimental(vmx) { 70 | // Enable Edge experimental features 71 | const key = 'HKCU\\SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge\\ExperimentalFeatures'; 72 | return run(`${RUN_USER}runProgramInGuest "${vmx}" "C:\\Windows\\System32\\reg.exe" ADD "${key}" /v ExperimentalJS /t REG_DWORD /d 1 /f`); 73 | } 74 | function runEdge(vmx, uri) { 75 | return run(`${RUN_USER}runProgramInGuest "${vmx}" -interactive -activeWindow "C:\\Windows\\explorer.exe" microsoft-edge:${uri}`); 76 | } 77 | function stopVM(vmx) { 78 | return run(`vmrun stop "${vmx}" hard`); 79 | } 80 | 81 | function run(command, options, counter = 0) { 82 | return new Promise((resolve, reject) => { 83 | Log('[vm]', 'run', command); 84 | ChildProcess.exec(command, options, (err, stdout, stderr) => { 85 | if (counter < 5 86 | && (/The specified guest user must be logged in interactively to perform this operation/.test(stdout) 87 | || (/The VMware Tools are not running in the virtual machine/).test(stdout) 88 | || nonZero(/reg.exe/, command, stdout))) { 89 | // Allow retries if there is something that might be waiting for background processes like updates 90 | counter++; 91 | Log('[vm]', 'retry', counter, command); 92 | setTimeout(() => { 93 | resolve(run(command, options, counter)); 94 | }, 10 * 1000 * counter); 95 | 96 | return; 97 | } 98 | 99 | /* istanbul ignore if */ 100 | if (err 101 | && !(/The virtual machine is not powered on/.test(stdout)) 102 | 103 | // Complete hack, but we want to ignore explorer error codes as they 104 | // occur when the command actually completed. 105 | && !nonZero(/explorer.exe/, command, stdout) 106 | && !nonZero(/taskkill/, command, stdout)) { 107 | Log('[vm]', err, stdout, stderr); 108 | reject(err); 109 | } else { 110 | setTimeout(() => { 111 | resolve(stdout); 112 | }, 5000); 113 | } 114 | }); 115 | }); 116 | } 117 | 118 | function nonZero(exe, command, stdout) { 119 | return (/Guest program exited with non-zero exit code/).test(stdout) 120 | && (exe).test(command); 121 | } 122 | 123 | Gulp.task('test:vm', Gulp.series('build:browser', 'test:vm:edge')); 124 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "test": false 4 | } 5 | } -------------------------------------------------------------------------------- /tests/arrow-args/arrow-args.es5: -------------------------------------------------------------------------------- 1 | var obj = { 2 | value: 42, 3 | fn: function() { 4 | var args = arguments; 5 | return function() { 6 | return args[0]; 7 | }; 8 | } 9 | }; 10 | 11 | var fn = obj.fn(1); 12 | assertEqual(fn(), 1); 13 | 14 | test(function() { 15 | fn(); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/arrow-args/arrow-args.es6: -------------------------------------------------------------------------------- 1 | 2 | var obj = { 3 | value: 42, 4 | fn: function() { 5 | return () => arguments[0]; 6 | } 7 | }; 8 | 9 | var fn = obj.fn(1); 10 | assertEqual(fn(), 1); 11 | 12 | test(function() { 13 | fn(); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/arrow-declare/arrow-declare.es5: -------------------------------------------------------------------------------- 1 | var obj = { 2 | value: 42, 3 | fn: function() { 4 | return function() { 5 | return obj.value; 6 | }; 7 | } 8 | }; 9 | 10 | assertEqual(obj.fn()(), 42); 11 | 12 | test(function() { 13 | obj.fn(); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/arrow-declare/arrow-declare.es6: -------------------------------------------------------------------------------- 1 | 2 | var obj = { 3 | value: 42, 4 | fn: function() { 5 | return () => this.value; 6 | } 7 | }; 8 | 9 | assertEqual(obj.fn()(), 42); 10 | 11 | test(function() { 12 | obj.fn(); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/arrow/arrow.es5: -------------------------------------------------------------------------------- 1 | var obj = { 2 | value: 42, 3 | fn: function() { 4 | return function() { 5 | return obj.value; 6 | }; 7 | } 8 | }; 9 | 10 | var fn = obj.fn(); 11 | assertEqual(fn(), 42); 12 | 13 | test(function() { 14 | fn(); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/arrow/arrow.es6: -------------------------------------------------------------------------------- 1 | 2 | var obj = { 3 | value: 42, 4 | fn: function() { 5 | return () => this.value; 6 | } 7 | }; 8 | 9 | var fn = obj.fn(); 10 | assertEqual(fn(), 42); 11 | 12 | test(function() { 13 | fn(); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/bindings-compound/bindings-compound.es5: -------------------------------------------------------------------------------- 1 | var b = 2; 2 | 3 | assertEqual(fn(), 3); 4 | 5 | function fn() { 6 | var a = 1; 7 | a += b; 8 | 9 | return a; 10 | } 11 | test(fn); 12 | -------------------------------------------------------------------------------- /tests/bindings-compound/bindings-compound.es6: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const b = 2; 4 | 5 | function fn() { 6 | 7 | let a = 1; 8 | a += b; 9 | 10 | return a; 11 | } 12 | 13 | assertEqual(fn(), 3); 14 | test(fn); 15 | -------------------------------------------------------------------------------- /tests/bindings/bindings.es5: -------------------------------------------------------------------------------- 1 | var a = 1, 2 | b = 2; 3 | 4 | assertEqual(a+b, 3); 5 | 6 | test(function() { 7 | return a + b; 8 | }); 9 | -------------------------------------------------------------------------------- /tests/bindings/bindings.es6: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const a = 1; 4 | let b = 2; 5 | 6 | assertEqual(a+b, 3); 7 | 8 | test(function() { 9 | return a + b; 10 | }); 11 | -------------------------------------------------------------------------------- /tests/classes/classes.es5: -------------------------------------------------------------------------------- 1 | function C() { 2 | this.foo = 'bar'; 3 | } 4 | C.prototype.bar = function() { 5 | }; 6 | 7 | assertEqual(new C().foo, 'bar'); 8 | 9 | test(function() { 10 | return new C(); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/classes/classes.es6: -------------------------------------------------------------------------------- 1 | class C { 2 | constructor() { 3 | this.foo = 'bar'; 4 | } 5 | bar() { 6 | } 7 | } 8 | 9 | assertEqual(new C().foo, 'bar'); 10 | 11 | test(function() { 12 | return new C(); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/defaults/defaults.es5: -------------------------------------------------------------------------------- 1 | function fn(arg, other) { 2 | arg = arg === undefined ? 1 : arg; 3 | other = other === undefined ? 3 : other; 4 | return other; 5 | } 6 | 7 | assertEqual(fn(), 3); 8 | assertEqual(fn(1, 2), 2); 9 | 10 | test(function() { 11 | fn(); 12 | fn(2); 13 | fn(2, 4); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/defaults/defaults.es6: -------------------------------------------------------------------------------- 1 | function fn(arg = 1, other = 3) { 2 | return other; 3 | } 4 | 5 | 6 | assertEqual(fn(), 3); 7 | assertEqual(fn(1, 2), 2); 8 | 9 | test(function() { 10 | fn(); 11 | fn(2); 12 | fn(2, 4); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/destructuring-simple/destructuring-simple.es5: -------------------------------------------------------------------------------- 1 | var data = { 2 | a: 'foo', 3 | b: {c: 'd'}, 4 | arr: [1, 2, 3] 5 | }; 6 | 7 | function fn() { 8 | var a = data.a, 9 | b = data.b; 10 | return a; 11 | } 12 | 13 | assertEqual(fn(), 'foo'); 14 | test(fn); 15 | -------------------------------------------------------------------------------- /tests/destructuring-simple/destructuring-simple.es6: -------------------------------------------------------------------------------- 1 | var data = { 2 | a: 'foo', 3 | b: {c: 'd'}, 4 | arr: [1, 2, 3] 5 | }; 6 | 7 | function fn() { 8 | var {a, b} = data; 9 | return a; 10 | } 11 | 12 | assertEqual(fn(), 'foo'); 13 | 14 | test(fn); 15 | -------------------------------------------------------------------------------- /tests/destructuring/destructuring.es5: -------------------------------------------------------------------------------- 1 | var data = { 2 | a: 'foo', 3 | b: {c: 'd'}, 4 | arr: [1, 2, 3] 5 | }; 6 | 7 | function fn() { 8 | var a = data.a, 9 | b = data.b.c, 10 | c = data.arr[1]; 11 | return c; 12 | } 13 | 14 | assertEqual(fn(), 2); 15 | test(fn); 16 | -------------------------------------------------------------------------------- /tests/destructuring/destructuring.es6: -------------------------------------------------------------------------------- 1 | var data = { 2 | a: 'foo', 3 | b: {c: 'd'}, 4 | arr: [1, 2, 3] 5 | }; 6 | 7 | function fn() { 8 | var {a, b:{c:b}, arr:[, c]} = data; 9 | return c; 10 | } 11 | 12 | assertEqual(fn(), 2); 13 | test(fn); 14 | -------------------------------------------------------------------------------- /tests/for-of-array/for-of-array.es5: -------------------------------------------------------------------------------- 1 | var data = [1,2,3]; 2 | 3 | function fn() { 4 | var ret = ''; 5 | for (var i = 0; i < data.length; i++) { 6 | ret += data[i]; 7 | } 8 | return ret; 9 | } 10 | 11 | assertEqual(fn(), '123'); 12 | 13 | test(fn); 14 | -------------------------------------------------------------------------------- /tests/for-of-array/for-of-array.es6: -------------------------------------------------------------------------------- 1 | var data = [1,2,3]; 2 | 3 | function fn() { 4 | var ret = ''; 5 | for (var value of data) { 6 | ret += value; 7 | } 8 | return ret; 9 | } 10 | 11 | assertEqual(fn(), '123'); 12 | 13 | test(fn); 14 | -------------------------------------------------------------------------------- /tests/for-of-object/for-of-object.es5: -------------------------------------------------------------------------------- 1 | var data = {'a': 'b', 'c': 'd'}; 2 | 3 | function fn() { 4 | var ret = ''; 5 | Object.keys(data).forEach(function(name) { 6 | ret += data[name]; 7 | }); 8 | return ret; 9 | } 10 | 11 | assertEqual(fn(), 'bd'); 12 | test(fn); -------------------------------------------------------------------------------- /tests/for-of-object/for-of-object.es6: -------------------------------------------------------------------------------- 1 | var data = {'a': 'b', 'c': 'd'}; 2 | data[Symbol.iterator] = function() { 3 | var array = Object.keys(data), 4 | nextIndex = 0; 5 | 6 | return { 7 | next: function() { 8 | return nextIndex < array.length ? 9 | {value: data[array[nextIndex++]], done: false} : 10 | {done: true}; 11 | } 12 | }; 13 | }; 14 | 15 | function fn() { 16 | var ret = ''; 17 | for (var value of data) { 18 | ret += value; 19 | } 20 | return ret; 21 | } 22 | 23 | assertEqual(fn(), 'bd'); 24 | test(fn); 25 | -------------------------------------------------------------------------------- /tests/generator/generator.es5: -------------------------------------------------------------------------------- 1 | function generator() { 2 | var i = 0; 3 | return { 4 | next: function() { 5 | i++; 6 | if (i >= 3) { 7 | return {done: true}; 8 | } else { 9 | return { 10 | value: i, 11 | done: false 12 | }; 13 | } 14 | } 15 | }; 16 | } 17 | 18 | function fn() { 19 | var iterator = generator(); 20 | iterator.next(); 21 | iterator.next(); 22 | return iterator.next().done; 23 | } 24 | 25 | assertEqual(fn(), true); 26 | test(fn); 27 | -------------------------------------------------------------------------------- /tests/generator/generator.es6: -------------------------------------------------------------------------------- 1 | function * generator() { 2 | yield 1; 3 | yield 2; 4 | } 5 | 6 | function fn() { 7 | var iterator = generator(); 8 | iterator.next(); 9 | iterator.next(); 10 | return iterator.next().done; 11 | } 12 | 13 | assertEqual(fn(), true); 14 | test(fn); 15 | -------------------------------------------------------------------------------- /tests/map-set-lookup/map-set-lookup.es5: -------------------------------------------------------------------------------- 1 | var keys = [], 2 | values = [], 3 | set = [], 4 | key = {}; 5 | 6 | for (var i = 0; i < 500; i++) { 7 | keys.push(i); 8 | values.push(i); 9 | set.push(i); 10 | } 11 | 12 | keys.push(key); 13 | values.push('bar'); 14 | set.push(key); 15 | 16 | function fn() { 17 | return set.indexOf(key) >= 0 && keys.indexOf(key) >= 0; 18 | } 19 | 20 | assertEqual(fn(), true); 21 | test(fn); 22 | -------------------------------------------------------------------------------- /tests/map-set-lookup/map-set-lookup.es6: -------------------------------------------------------------------------------- 1 | var map = new Map(), 2 | set = new Set(), 3 | key = {}; 4 | 5 | for (var i = 0; i < 500; i++) { 6 | map.set(i, i); 7 | set.add(i); 8 | } 9 | 10 | map.set(key, 'bar'); 11 | set.add(key); 12 | 13 | function fn() { 14 | return map.has(key) && set.has(key); 15 | } 16 | 17 | assertEqual(fn(), true); 18 | test(fn); 19 | -------------------------------------------------------------------------------- /tests/map-set-object/map-set-object.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var keys = [], 3 | values = [], 4 | set = [], 5 | key = {}; 6 | 7 | for (var i = 0; i < 500; i++) { 8 | keys.push(i); 9 | values.push(i); 10 | set.push(i); 11 | } 12 | 13 | keys.push(key); 14 | values.push('bar'); 15 | set.push(key); 16 | 17 | return set.indexOf(key) >= 0 && keys.indexOf(key) >= 0; 18 | } 19 | 20 | assertEqual(fn(), true); 21 | test(fn); 22 | -------------------------------------------------------------------------------- /tests/map-set-object/map-set-object.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var map = new Map(), 3 | set = new Set(), 4 | key = {}; 5 | 6 | for (var i = 0; i < 500; i++) { 7 | map.set(i, i); 8 | set.add(i); 9 | } 10 | 11 | map.set(key, 'bar'); 12 | set.add(key); 13 | 14 | return map.has(key) && set.has(key); 15 | } 16 | 17 | assertEqual(fn(), true); 18 | test(fn); 19 | -------------------------------------------------------------------------------- /tests/map-set/map-set.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var map = {}, 3 | set = []; 4 | 5 | for (var i = 0; i < 250; i++) { 6 | map[i] = i; 7 | set.push(i); 8 | } 9 | 10 | map.foo = 'bar'; 11 | set.push('bar'); 12 | return ('foo' in map) && set.indexOf('bar') >= 0; 13 | } 14 | 15 | assertEqual(fn(), true); 16 | test(fn); 17 | -------------------------------------------------------------------------------- /tests/map-set/map-set.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var map = new Map(), 3 | set = new Set(); 4 | 5 | for (var i = 0; i < 250; i++) { 6 | map.set(i, i); 7 | set.add(i); 8 | } 9 | 10 | map.set('foo', 'bar'); 11 | set.add('bar'); 12 | 13 | return map.has('foo') && set.has('bar'); 14 | } 15 | 16 | assertEqual(fn(), true); 17 | test(fn); 18 | -------------------------------------------------------------------------------- /tests/map-string/map-string.es5: -------------------------------------------------------------------------------- 1 | var map = {}; 2 | 3 | for (var i = 0; i < 500; i++) { 4 | map[i] = i; 5 | } 6 | 7 | function fn() { 8 | return map['499'] === 499; 9 | } 10 | 11 | assertEqual(fn(), true); 12 | test(fn); 13 | -------------------------------------------------------------------------------- /tests/map-string/map-string.es6: -------------------------------------------------------------------------------- 1 | var map = new Map(); 2 | 3 | for (var i = 0; i < 500; i++) { 4 | map.set(i + '', i); 5 | } 6 | 7 | function fn() { 8 | return map.get('499') === 499; 9 | } 10 | 11 | assertEqual(fn(), true); 12 | test(fn); 13 | -------------------------------------------------------------------------------- /tests/new-target/defaults.es5: -------------------------------------------------------------------------------- 1 | function Fn() { 2 | return !!(this && this.constructor === Fn); 3 | } 4 | 5 | assertEqual(typeof Fn(), 'boolean'); 6 | assertEqual(typeof (new Fn()), 'object'); 7 | 8 | test(function() { 9 | return (Fn() || new Fn()); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/new-target/defaults.es6: -------------------------------------------------------------------------------- 1 | function Fn() { 2 | return (new.target === Fn); 3 | } 4 | 5 | assertEqual(typeof Fn(), 'boolean'); 6 | assertEqual(typeof (new Fn()), 'object'); 7 | 8 | test(function() { 9 | return (Fn() || new Fn()); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/object-assign/object-assign.es5: -------------------------------------------------------------------------------- 1 | var obj = { 2 | a: 1, 3 | b: true, 4 | c: function () {}, 5 | d: null, 6 | e: 'e' 7 | }; 8 | 9 | var fn = function (src) { 10 | var o = {}; 11 | var keys = Object.keys(src); 12 | for (var i = 0; i < keys.length; ++i) { 13 | var key = keys[i]; 14 | o[key] = src[key]; 15 | } 16 | return o; 17 | }; 18 | 19 | var r = fn(obj); 20 | assertEqual(r.a, obj.a); 21 | assertEqual(r.b, obj.b); 22 | assertEqual(r.c, obj.c); 23 | assertEqual(r.d, obj.d); 24 | assertEqual(r.e, obj.e); 25 | 26 | test(function () { 27 | fn(obj); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/object-assign/object-assign.es6: -------------------------------------------------------------------------------- 1 | const obj = { 2 | a: 1, 3 | b: true, 4 | c: function () {}, 5 | d: null, 6 | e: 'e' 7 | }; 8 | 9 | const fn = function (src) { 10 | return Object.assign({}, src); 11 | }; 12 | 13 | const r = fn(obj); 14 | assertEqual(r.a, obj.a); 15 | assertEqual(r.b, obj.b); 16 | assertEqual(r.c, obj.c); 17 | assertEqual(r.d, obj.d); 18 | assertEqual(r.e, obj.e); 19 | 20 | test(function () { 21 | fn(obj); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/object-literal-ext/object-literal-ext.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var name = 'foo'; 3 | var ret = { 4 | 'bizz buzz': function() { 5 | return 1; 6 | }, 7 | name: name 8 | }; 9 | ret[name] = 'bar'; 10 | ret[name + 'foo'] = 'foo'; 11 | return ret; 12 | } 13 | 14 | assertEqual(fn().foofoo, 'foo'); 15 | test(fn); 16 | -------------------------------------------------------------------------------- /tests/object-literal-ext/object-literal-ext.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var name = 'foo'; 3 | return { 4 | 'bizz buzz'() { 5 | return 1; 6 | }, 7 | name, 8 | [name]: 'bar', 9 | [name + 'foo']: 'foo' 10 | }; 11 | } 12 | 13 | assertEqual(fn().foofoo, 'foo'); 14 | test(fn); 15 | -------------------------------------------------------------------------------- /tests/regex-u/regex-u.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return '𠮷'.match(/^.$/); 3 | } 4 | 5 | // Not asserting as this isn't quite an accurate test under es5 6 | test(fn); 7 | -------------------------------------------------------------------------------- /tests/regex-u/regex-u.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return '𠮷'.match(/^.$/u); 3 | } 4 | 5 | assertEqual(!!fn(), true); 6 | test(fn); 7 | -------------------------------------------------------------------------------- /tests/rest/rest.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return arguments[1]; 3 | } 4 | 5 | assertEqual(fn(), undefined); 6 | assertEqual(fn(2), undefined); 7 | assertEqual(fn(2, 4), 4); 8 | 9 | test(function() { 10 | fn(); 11 | fn(2); 12 | fn(2, 4); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/rest/rest.es6: -------------------------------------------------------------------------------- 1 | function fn(foo, ...args) { 2 | return args[0]; 3 | } 4 | 5 | assertEqual(fn(), undefined); 6 | assertEqual(fn(2), undefined); 7 | assertEqual(fn(2, 4), 4); 8 | 9 | test(function() { 10 | fn(); 11 | fn(2); 12 | fn(2, 4); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/spread-generator/spread-generator.es5: -------------------------------------------------------------------------------- 1 | function generator() { 2 | var i = 0; 3 | return { 4 | next: function() { 5 | i++; 6 | if (i >= 4) { 7 | return {done: true}; 8 | } else { 9 | return { 10 | value: i, 11 | done: false 12 | }; 13 | } 14 | } 15 | }; 16 | } 17 | 18 | function fn() { 19 | var iterator = generator(); 20 | var args = [iterator.next().value, iterator.next().value, iterator.next().value]; 21 | iterator.next(); 22 | 23 | return Math.max.apply(Math, args); 24 | } 25 | 26 | assertEqual(fn(), 3); 27 | test(fn); 28 | -------------------------------------------------------------------------------- /tests/spread-generator/spread-generator.es6: -------------------------------------------------------------------------------- 1 | function *generate() { 2 | yield 1; 3 | yield 2; 4 | yield 3; 5 | } 6 | 7 | function fn() { 8 | return Math.max(... generate()); 9 | } 10 | assertEqual(fn(), 3); 11 | test(fn); 12 | -------------------------------------------------------------------------------- /tests/spread-literal/spread-literal.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | var ret = [1]; 3 | ret.push(1, 2, 3); 4 | return ret; 5 | } 6 | 7 | assertEqual(fn()[3], 3); 8 | test(fn); 9 | -------------------------------------------------------------------------------- /tests/spread-literal/spread-literal.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return [1, ... [1, 2, 3]]; 3 | } 4 | assertEqual(fn()[3], 3); 5 | test(fn); 6 | -------------------------------------------------------------------------------- /tests/spread/spread.es5: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return Math.max.apply(Math, [1,2,3]); 3 | } 4 | 5 | assertEqual(fn(), 3); 6 | test(fn); 7 | -------------------------------------------------------------------------------- /tests/spread/spread.es6: -------------------------------------------------------------------------------- 1 | function fn() { 2 | return Math.max(...[1,2,3]); 3 | } 4 | 5 | assertEqual(fn(), 3); 6 | test(fn); 7 | -------------------------------------------------------------------------------- /tests/super/super.es5: -------------------------------------------------------------------------------- 1 | function C() { 2 | this.foo = 'bar'; 3 | } 4 | C.prototype.bar = function() { 5 | return 41; 6 | }; 7 | 8 | 9 | function D() { 10 | C.call(this); 11 | this.baz = 'bat'; 12 | } 13 | D.prototype = Object.create(C.prototype); 14 | D.prototype.bar = function() { 15 | return C.prototype.bar.call(this) + 1; 16 | }; 17 | function fn() { 18 | var d = new D(); 19 | return d.bar(); 20 | } 21 | 22 | assertEqual(fn(), 42); 23 | test(fn); 24 | -------------------------------------------------------------------------------- /tests/super/super.es6: -------------------------------------------------------------------------------- 1 | 2 | class C { 3 | constructor() { 4 | this.foo = 'bar'; 5 | } 6 | bar() { 7 | return 41; 8 | } 9 | } 10 | class D extends C { 11 | constructor() { 12 | super(); 13 | this.baz = 'bat'; 14 | } 15 | bar() { 16 | return super.bar() + 1; 17 | } 18 | } 19 | function fn() { 20 | var d = new D(); 21 | return d.bar(); 22 | } 23 | 24 | assertEqual(fn(), 42); 25 | test(fn); 26 | -------------------------------------------------------------------------------- /tests/template_string/template_string.es5: -------------------------------------------------------------------------------- 1 | var data = [1,2,3]; 2 | function fn() { 3 | return data[0] + ' ' + (data[1] + data[2]); 4 | } 5 | 6 | assertEqual(fn(), '1 5'); 7 | test(fn); 8 | -------------------------------------------------------------------------------- /tests/template_string/template_string.es6: -------------------------------------------------------------------------------- 1 | var data = [1,2,3]; 2 | function fn() { 3 | return `${data[0]} ${data[1] + data[2]}`; 4 | } 5 | 6 | assertEqual(fn(), '1 5'); 7 | test(fn); 8 | -------------------------------------------------------------------------------- /tests/template_string_tag/template_string_tag.es5: -------------------------------------------------------------------------------- 1 | var data = [1, 2, 3]; 2 | function tag(strings, value1, value2) { 3 | return strings[0] + value1 + strings[1] + value2 + strings[2]; 4 | } 5 | 6 | function fn() { 7 | return tag(['', ' ', ''], data[0], data[1] + data[2]); 8 | } 9 | 10 | assertEqual(fn(), '1 5'); 11 | test(fn); 12 | -------------------------------------------------------------------------------- /tests/template_string_tag/template_string_tag.es6: -------------------------------------------------------------------------------- 1 | var data = [1, 2, 3]; 2 | function tag(strings, value1, value2) { 3 | return strings[0] + value1 + strings[1] + value2 + strings[2]; 4 | } 5 | 6 | function fn() { 7 | return tag`${data[0]} ${data[1] + data[2]}`; 8 | } 9 | 10 | assertEqual(fn(), '1 5'); 11 | test(fn); 12 | --------------------------------------------------------------------------------