├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.html ├── package.json ├── src ├── json.ts ├── metadata.ts ├── render.ts ├── results.template ├── run.ts └── web_server.ts ├── third_party ├── angular │ ├── README.md │ ├── config.patch │ └── main.js ├── angularjs │ ├── LICENSE │ └── angular.js ├── externs.js ├── react │ ├── LICENSE │ ├── README.md │ ├── minifier.patch │ ├── react-dom.production.min.js │ └── react.production.min.js ├── todomvc │ ├── LICENSE │ ├── README.md │ ├── react │ │ ├── .gitignore │ │ ├── bundle.js │ │ ├── externs.js │ │ ├── index.html │ │ ├── js │ │ │ ├── app.jsx │ │ │ ├── footer.jsx │ │ │ ├── todoItem.jsx │ │ │ ├── todoModel.js │ │ │ └── utils.js │ │ ├── node_modules │ │ │ ├── classnames │ │ │ │ └── index.js │ │ │ ├── director │ │ │ │ └── build │ │ │ │ │ └── director.js │ │ │ ├── react │ │ │ │ └── dist │ │ │ │ │ ├── JSXTransformer.js │ │ │ │ │ └── react-with-addons.js │ │ │ ├── todomvc-app-css │ │ │ │ └── index.css │ │ │ └── todomvc-common │ │ │ │ ├── base.css │ │ │ │ └── base.js │ │ ├── package.json │ │ └── readme.md │ ├── test.ts │ └── vanillajs │ │ ├── .gitignore │ │ ├── bundle.js │ │ ├── externs.js │ │ ├── index.html │ │ ├── js │ │ ├── app.js │ │ ├── controller.js │ │ ├── helpers.js │ │ ├── model.js │ │ ├── store.js │ │ ├── template.js │ │ └── view.js │ │ ├── node_modules │ │ ├── todomvc-app-css │ │ │ └── index.css │ │ └── todomvc-common │ │ │ ├── base.css │ │ │ └── base.js │ │ ├── package.json │ │ ├── readme.md │ │ └── test │ │ ├── ControllerSpec.js │ │ └── SpecRunner.html └── vue │ ├── LICENSE │ └── vue.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /out/ 3 | /build/ 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": false, 4 | "proseWrap": "always" 5 | } 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-min-bench 2 | 3 | This project attempts to create a repeatable benchmark comparing various 4 | JavaScript minifiers and options. 5 | 6 | [Read a longer blog post about what minifiers do and why this benchmark is useful](http://neugierig.org/software/blog/2019/04/js-minifiers.html). 7 | 8 | [View the current results](https://evmar.github.io/js-min-bench/). 9 | 10 | Note: This is not an officially supported Google product. 11 | 12 | ## Methodogy 13 | 14 | I collected sample programs and archived them in this repo. The test runner then 15 | runs specific minifier versions against those sample programs and reports their 16 | results. Because the inputs and minifiers are pinned to specific versions, the 17 | reported output sizes should be repeatable. The timing numbers will depend on 18 | the machine the tools are run on, but their relative values are likely more or 19 | less representative of any machine. 20 | 21 | ### Testing 22 | 23 | A trivial "minifier" that took an input program and produced a 0 byte output 24 | file would get the best score on this benchmark. The only way to verify that a 25 | minifier doesn't (accidentally) cheat in this manner is by verifying that the 26 | resulting application still works after minification. For this reason, the input 27 | programs here should be _applications_, not frameworks or libraries, and for 28 | each application we need end-to-end tests. The benchmarker then can run the test 29 | suite for each output. 30 | 31 | Unfortunately, I realized the need for testing too late, so this idea is 32 | currently only implemented for the `todomvc` results here. 33 | 34 | ## Run it yourself 35 | 36 | ### Dependencies 37 | 38 | You need: 39 | 40 | * nodejs (to run the benchmarker and uglify) 41 | * yarn (to download more dependencies): `npm i -g yarn` 42 | * [brotli](https://github.com/google/brotli) (a recent version) 43 | 44 | ### Setup 45 | 46 | ```sh 47 | # Install dependencies: 48 | $ yarn 49 | # Build code: 50 | $ yarn run build 51 | ``` 52 | 53 | ### Run 54 | 55 | ```sh 56 | # Run benchmark, write index.html: 57 | $ yarn run bench 58 | ``` 59 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js-min-bench minifier comparison 6 | 54 | 55 | 56 | 57 |

js-min-bench minifier comparison

58 |

59 | This page compares JavaScript minifiers for various inputs across various 60 | settings. 61 |

62 |

Note: the input programs are not comparable against one another 63 | because they do wildly different things. Don't compare "angularjs" 64 | against "react", but rather for a given input program compare the different 65 | minifiers (uglify vs closure etc.). 66 |

67 |

68 | See the README 69 | for more background on the methodology. Click on rows/columns of the results 70 | to see their definitions. 71 |

72 |

Note: many of these are marked 'untested'. See the discussion 73 | in the README of why you should treat those results with suspicion. 74 |

75 | 76 |
77 |

test results

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |
input+tool+variantsizegzipbrotliruntime
angular-hello
raw 1,246,962226,37818.2%198,55215.9%
uglify 500,98640.2%112,1529.0%102,9818.3%1.2s
+ compress-mangle215,41617.3%62,3625.0%58,3694.7%4.4s
terser 500,98840.2%112,1539.0%102,9828.3%1.0s
+ compress-mangle215,64617.3%62,3085.0%58,3044.7%4.4s
closure 273,56421.9%80,6096.5%75,4026.0%3.6s
+ advanced221,01117.7%73,6365.9%69,8535.6%4.1s
j8tfailed (hover for details)0.0s
angularjs
raw 1,270,821309,87524.4%267,37021.0%
uglify 323,82025.5%83,2436.6%77,3676.1%0.7s
+ compress-mangle176,08513.9%59,9604.7%56,8584.5%4.8s
terser 323,82625.5%83,2486.6%77,3706.1%1.6s
+ compress-mangle175,91413.8%60,2814.7%57,1034.5%6.7s
closure 176,71713.9%62,0884.9%58,5484.6%3.1s
+ advanced149,00911.7%59,4464.7%56,7834.5%4.4s
j8tfailed (hover for details)0.0s
fake-10mb-angular
raw 10,166,5682,475,36424.3%267,3802.6%
uglify 2,590,56025.5%662,1996.5%78,4370.8%3.9s
+ compress-mangle1,408,77113.9%474,4744.7%57,8860.6%20.9s
terser 2,590,60825.5%662,2356.5%78,4380.8%3.9s
+ compress-mangle1,407,30513.8%477,3104.7%57,7770.6%23.4s
closure 1,403,41413.8%487,3804.8%70,4340.7%16.9s
+ advanced1,186,31911.7%468,2294.6%58,4200.6%25.2s
j8tfailed (hover for details)0.0s
react
raw 31,1248,92628.7%8,48427.3%
uglify 13,30642.8%3,79612.2%3,63711.7%0.3s
+ compress-mangle6,52020.9%2,7368.8%2,5818.3%0.6s
terser 13,30642.8%3,79612.2%3,63711.7%0.3s
+ compress-mangle6,50420.9%2,7838.9%2,6218.4%0.6s
closure 6,50020.9%2,7899.0%2,6398.5%0.6s
+ advanced5,35717.2%2,4387.8%2,2807.3%0.8s
j8tfailed (hover for details)0.0s
react-dom
raw 427,605100,80023.6%90,16921.1%
uglify 221,56351.8%45,48710.6%42,1549.9%0.6s
+ compress-mangle101,70123.8%32,0307.5%30,3767.1%3.0s
terser 221,56351.8%45,48710.6%42,1549.9%0.6s
+ compress-mangle102,80324.0%32,7267.7%30,9497.2%3.1s
closure 93,69421.9%30,6617.2%28,7746.7%1.9s
+ advanced74,72017.5%27,1456.3%26,3556.2%2.5s
j8tfailed (hover for details)0.0s
todomvc-react
raw703,818159,41522.7%137,28619.5%
uglify342,02248.6%77,76811.0%69,7249.9%0.8s
+ compress-mangle193,87527.5%58,4978.3%53,6417.6%3.7s
terser342,02248.6%77,76811.0%69,7249.9%0.8s
+ compress-mangle193,99727.6%58,5778.3%53,7527.6%3.7s
closure189,91727.0%58,8988.4%54,1767.7%2.0s
+ advanced148,00721.0%54,4707.7%51,0057.2%3.0s
j8tfailed (hover for details)0.0s
todomvc-vanillajs
raw31,6139,04928.6%8,55227.1%
uglify18,35858.1%5,26216.6%4,94015.6%0.2s
+ compress-mangle13,76543.5%4,47914.2%4,16413.2%0.5s
terser18,35858.1%5,26216.6%4,94015.6%0.3s
+ compress-mangle13,77443.6%4,51114.3%4,18913.3%0.5s
closure13,68243.3%4,54314.4%4,24513.4%0.6s
+ advanced11,82637.4%4,27313.5%3,99312.6%0.9s
j8tfailed (hover for details)0.0s
vue
raw 280,47276,23327.2%70,02325.0%
uglify 173,16561.7%49,29617.6%46,47516.6%0.5s
+ compress-mangle103,42636.9%37,68613.4%36,26212.9%2.6s
terser 173,16661.7%49,29717.6%46,47416.6%0.6s
+ compress-mangle103,46136.9%37,75013.5%36,29112.9%2.6s
closure 102,60136.6%38,56513.8%36,66313.1%1.7s
+ advanced91,59132.7%37,23513.3%35,56912.7%2.0s
j8tfailed (hover for details)0.0s
136 | 137 |
138 | 139 |

tool details

140 |
raw
raw input file, as baseline for comparison
uglify
uglifyjs 3.5.6
$ node_modules/.bin/uglifyjs %%in%% -o %%out%%
compress-mangle
$ node_modules/.bin/uglifyjs %%in%% -o %%out%% --compress --mangle
141 |
142 |
terser
terser 3.17.0
$ node_modules/.bin/terser %%in%% -o %%out%%
compress-mangle
$ node_modules/.bin/terser %%in%% -o %%out%% --compress --mangle
143 |
144 |
closure
Google Closure Compiler 20190415
$ node_modules/google-closure-compiler-linux/compiler --jscomp_off=checkVars --warning_level=QUIET --language_out=ECMASCRIPT_2015 --js_output_file=%%out%% %%in%%
advanced
$ node_modules/google-closure-compiler-linux/compiler --jscomp_off=checkVars --warning_level=QUIET --language_out=ECMASCRIPT_2015 -O advanced third_party/externs.js %%externs%% --js_output_file=%%out%% %%in%%
145 |
146 |
j8t
j8t (work in progress)
$ ../j8t/target/release/j8t %%in%% > %%out%%
147 |
148 | 149 | 150 |

output details

151 |
152 |
gzip
153 |
gzip -9 size in bytes, supported by ~all browsers
154 | 155 |
brotli
156 |
157 | brotli -9 size in bytes, supported by 158 | many browsers 159 |
160 | 161 |
runtime
162 |
time taken to generate output, in seconds, not including compression
163 |
164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bench", 3 | "version": "1.0.0", 4 | "description": "compare js minifiers", 5 | "main": "run.js", 6 | "dependencies": { 7 | "chai": "^4.2.0", 8 | "commander": "^2.12.2", 9 | "mocha": "^6.1.4", 10 | "puppeteer": "^1.14.0", 11 | "terser": "^3.17.0", 12 | "uglify-js": "^3.5.6" 13 | }, 14 | "devDependencies": { 15 | "@types/chai": "^4.1.7", 16 | "@types/mocha": "^5.2.6", 17 | "@types/node": "^8.0.58", 18 | "@types/puppeteer": "^1.12.3", 19 | "prettier": "1.9.2", 20 | "typescript": "^3.4.4" 21 | }, 22 | "optionalDependencies": { 23 | "google-closure-compiler-linux": "^20190415.0.0", 24 | "google-closure-compiler-osx": "^20190415.0.0" 25 | }, 26 | "scripts": { 27 | "test": "echo \"Error: no test specified\" && exit 1", 28 | "fmt": "prettier --write *.md src/*.ts third_party/**/*.md third_party/*/test.ts", 29 | "watch": "tsc -w", 30 | "build": "tsc", 31 | "render": "node build/src/render.js > index.html", 32 | "bench": "node build/src/run.js && node build/src/render.js > index.html" 33 | }, 34 | "author": "", 35 | "license": "Apache-2.0" 36 | } 37 | -------------------------------------------------------------------------------- /src/json.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export interface Result { 18 | input: string; 19 | tool: string; 20 | variant?: string; 21 | time: number; 22 | size: number; 23 | gzSize: number; 24 | brSize: number; 25 | failure?: string; 26 | untested?: boolean; 27 | } 28 | -------------------------------------------------------------------------------- /src/metadata.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // TODO: this duplicates some logic from google-closure-compiler npm package, 18 | // maybe there's a better way to reuse it? 19 | 20 | function getClosurePath(): string { 21 | const module = 22 | process.platform === 'darwin' 23 | ? 'google-closure-compiler-osx' 24 | : 'google-closure-compiler-linux'; 25 | return require(module); 26 | } 27 | 28 | const closureCommand = [ 29 | getClosurePath(), 30 | '--jscomp_off=checkVars', 31 | '--warning_level=QUIET', 32 | '--language_out=ECMASCRIPT_2015' 33 | ].join(' '); 34 | 35 | export interface Test { 36 | webroot: string; 37 | test: string; 38 | } 39 | export interface JSFileMetadata { 40 | bundlePath: string; 41 | desc: string; 42 | /** Path to project README; defaults to README.md alongside bundle. */ 43 | readme?: string; 44 | version?: string; 45 | transform?: string; 46 | externs?: string; 47 | test?: Test; 48 | } 49 | export const js: {[name: string]: JSFileMetadata} = { 50 | angularjs: { 51 | bundlePath: 'third_party/angularjs/angular.js', 52 | desc: 'angularjs 1.6.6 minified bundle', 53 | version: '1.6.6' 54 | }, 55 | 'fake-10mb-angular': { 56 | transform: 'angularjs 10x', 57 | bundlePath: 'fake-10mb-angular.js', 58 | desc: 59 | 'angularjs 1.6.6 minified, artificially repeated until input file >10mb', 60 | version: '1.6.6' 61 | }, 62 | 'angular-hello': { 63 | bundlePath: 'third_party/angular/main.js', 64 | desc: 65 | 'angular5 + cli hello world ' + 66 | '(note: closure-optimized build is much smaller)' 67 | }, 68 | react: { 69 | bundlePath: 'third_party/react/react.production.min.js', 70 | desc: 'react production bundle' 71 | }, 72 | 'react-dom': { 73 | bundlePath: 'third_party/react/react-dom.production.min.js', 74 | desc: 'react-dom production bundle' 75 | }, 76 | vue: { 77 | bundlePath: 'third_party/vue/vue.js', 78 | desc: 'vue.js 2.5.3', 79 | version: '2.5.3' 80 | }, 81 | 'todomvc-vanillajs': { 82 | bundlePath: 'third_party/todomvc/vanillajs/bundle.js', 83 | externs: 'third_party/todomvc/vanillajs/externs.js', 84 | desc: 'todomvc vanillajs', 85 | readme: 'third_party/todomvc/README.md', 86 | test: { 87 | webroot: 'third_party/todomvc/vanillajs', 88 | test: 'build/third_party/todomvc/test.js' 89 | } 90 | }, 91 | 'todomvc-react': { 92 | bundlePath: 'third_party/todomvc/react/bundle.js', 93 | externs: 'third_party/todomvc/react/externs.js', 94 | desc: 'todomvc react', 95 | readme: 'third_party/todomvc/README.md', 96 | test: { 97 | webroot: 'third_party/todomvc/react', 98 | test: 'build/third_party/todomvc/test.js' 99 | } 100 | } 101 | }; 102 | 103 | export interface ToolMetadata { 104 | id: string; 105 | name: string; 106 | variants: Array<{id?: string; desc?: string; command: string}>; 107 | } 108 | export const tools: ToolMetadata[] = [ 109 | { 110 | id: 'raw', 111 | name: 'baseline input file', 112 | variants: [ 113 | { 114 | command: 'cp %%in%% %%out%%' 115 | } 116 | ] 117 | }, 118 | { 119 | id: 'uglify', 120 | name: 'uglifyjs 3.5.6', 121 | variants: [ 122 | {command: 'node_modules/.bin/uglifyjs %%in%% -o %%out%%'}, 123 | { 124 | id: 'compress-mangle', 125 | desc: '--compress and --mangle flags', 126 | command: 127 | 'node_modules/.bin/uglifyjs %%in%% -o %%out%% --compress --mangle' 128 | } 129 | ] 130 | }, 131 | { 132 | id: 'terser', 133 | name: 'terser 3.17.0', 134 | variants: [ 135 | {command: 'node_modules/.bin/terser %%in%% -o %%out%%'}, 136 | { 137 | id: 'compress-mangle', 138 | desc: '--compress and --mangle flags', 139 | command: 140 | 'node_modules/.bin/terser %%in%% -o %%out%% --compress --mangle' 141 | } 142 | ] 143 | }, 144 | { 145 | id: 'closure', 146 | name: 147 | "Google Closure Compiler 20190415", 148 | variants: [ 149 | { 150 | command: `${closureCommand} --js_output_file=%%out%% %%in%%` 151 | }, 152 | { 153 | id: 'advanced', 154 | desc: 'advanced mode + externs', 155 | command: `${closureCommand} -O advanced third_party/externs.js %%externs%% --js_output_file=%%out%% %%in%%` 156 | } 157 | ] 158 | }, 159 | { 160 | id: 'j8t', 161 | name: "j8t (work in progress)", 162 | variants: [ 163 | { 164 | command: '../j8t/target/release/j8t %%in%% > %%out%%' 165 | } 166 | ] 167 | } 168 | ]; 169 | -------------------------------------------------------------------------------- /src/render.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | import * as path from 'path'; 19 | import {Result} from './json'; 20 | import * as metadata from './metadata'; 21 | 22 | function rollup(data: T[], key: K): Map { 23 | let map = new Map(); 24 | for (let t of data) { 25 | let row = map.get(t[key]); 26 | if (!row) { 27 | row = []; 28 | map.set(t[key], row); 29 | } 30 | row.push(t); 31 | } 32 | return map; 33 | } 34 | 35 | function min(data: number[]): number { 36 | return data.reduce((a, b) => Math.min(a, b)); 37 | } 38 | 39 | function max(data: number[]): number { 40 | return data.reduce((a, b) => Math.max(a, b)); 41 | } 42 | 43 | function sizeCells(size: number, bestSize: number, worstSize: number): string { 44 | const best = size === bestSize ? ' class=best' : ''; 45 | return ( 46 | `${size.toLocaleString()}` + 47 | `${ 48 | size === worstSize ? '' : (size * 100 / worstSize).toFixed(1) + '%' 49 | }` 50 | ); 51 | } 52 | 53 | function resultsTable(allResults: Result[]): string { 54 | let html = ``; 55 | html += 56 | `` + 57 | `` + 58 | `` + 59 | `\n`; 60 | for (const [input, results] of rollup(allResults, 'input').entries()) { 61 | const meta = metadata.js[input]; 62 | const readmePath = 63 | meta.readme || path.join(path.dirname(meta.bundlePath), 'README.md'); 64 | const readmeUrl = `https://github.com/evmar/js-min-bench/tree/master/${readmePath}`; 65 | 66 | html += ``; 67 | const candidates = results.filter(r => !r.failure); 68 | const sizes = ([] as number[]).concat( 69 | ...candidates.map(c => [c.size, c.gzSize, c.brSize]) 70 | ); 71 | const bestSize = min(sizes); 72 | const worstSize = max(sizes); 73 | const bestTime = min( 74 | candidates.filter(r => r.tool !== 'raw').map(r => r.time) 75 | ); 76 | let lastTool = ''; 77 | for (const result of results) { 78 | html += ``; 79 | if (result.tool != lastTool) { 80 | const untestedMsg = result.untested 81 | ? ` ` 82 | : ''; 83 | html += 84 | ``; 86 | lastTool = result.tool; 87 | } else { 88 | html += ``; 89 | } 90 | 91 | if (!result.failure) { 92 | html += sizeCells(result.size, bestSize, worstSize); 93 | html += sizeCells(result.gzSize, bestSize, worstSize); 94 | html += sizeCells(result.brSize, bestSize, worstSize); 95 | } else { 96 | html += ``; 99 | } 100 | 101 | if (result.tool === 'raw') { 102 | html += ``; 103 | } else { 104 | let best = result.time === bestTime ? ' class=best' : ''; 105 | let time = (result.time / 1000).toFixed(1); 106 | html += `\n`; 107 | } 108 | } 109 | } 110 | html += `
input+tool+variantsizegzipbrotliruntime
${input}
` + 85 | `${result.tool}${untestedMsg}+ ${result.variant}failed (hover for details)${time}s
\n`; 111 | return html; 112 | } 113 | 114 | /** Redacts "/home/username/.../ bit from a command line. */ 115 | function redactCommand(cmd: string): string { 116 | return cmd.replace(/^.*\/js-min-bench\//, ''); 117 | } 118 | 119 | function toolDetails(): string { 120 | let html = '
'; 121 | html += 122 | `
raw
` + `
raw input file, as baseline for comparison
`; 123 | for (const tool of metadata.tools.slice(1)) { 124 | html += 125 | `
${tool.id}
` + 126 | `
${tool.name}
` + 127 | `$ ${redactCommand(tool.variants[0].command)}
`; 128 | if (tool.variants.length > 1) { 129 | html += `
`; 130 | for (const variant of tool.variants.slice(1)) { 131 | html += 132 | `
${variant.id}
` + 133 | `
$ ${redactCommand(variant.command)}
\n`; 134 | } 135 | html += `
`; 136 | } 137 | html += '
\n'; 138 | } 139 | html += '
\n'; 140 | return html; 141 | } 142 | 143 | function main() { 144 | const allResults: Result[] = JSON.parse( 145 | fs.readFileSync('out/results.json', 'utf8') 146 | ); 147 | 148 | const template = fs.readFileSync('src/results.template', 'utf8'); 149 | const templateData: {[k: string]: string} = { 150 | resultsTable: resultsTable(allResults), 151 | toolDetails: toolDetails() 152 | }; 153 | console.log(template.replace(/%%(\w+)%%/g, (_, f) => templateData[f])); 154 | } 155 | 156 | main(); 157 | -------------------------------------------------------------------------------- /src/results.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js-min-bench minifier comparison 6 | 54 | 55 | 56 | 57 |

js-min-bench minifier comparison

58 |

59 | This page compares JavaScript minifiers for various inputs across various 60 | settings. 61 |

62 |

Note: the input programs are not comparable against one another 63 | because they do wildly different things. Don't compare "angularjs" 64 | against "react", but rather for a given input program compare the different 65 | minifiers (uglify vs closure etc.). 66 |

67 |

68 | See the README 69 | for more background on the methodology. Click on rows/columns of the results 70 | to see their definitions. 71 |

72 |

Note: many of these are marked 'untested'. See the discussion 73 | in the README of why you should treat those results with suspicion. 74 |

75 | 76 |
77 |

test results

78 | %%resultsTable%% 79 |
80 | 81 |

tool details

82 | %%toolDetails%% 83 | 84 |

output details

85 |
86 |
gzip
87 |
gzip -9 size in bytes, supported by ~all browsers
88 | 89 |
brotli
90 |
91 | brotli -9 size in bytes, supported by 92 | many browsers 93 |
94 | 95 |
runtime
96 |
time taken to generate output, in seconds, not including compression
97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /src/run.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | import * as childProcess from 'child_process'; 19 | import {Result} from './json'; 20 | import * as metadata from './metadata'; 21 | import * as commander from 'commander'; 22 | import * as Mocha from 'mocha'; 23 | import {WebServer} from './web_server'; 24 | 25 | /** 26 | * The mocha typings are missing the 'unloadFiles' method, 27 | * which is needed so we can run the same test suite against multiple 28 | * different bundle configurations. 29 | */ 30 | declare global { 31 | interface Mocha { 32 | unloadFiles(): void; 33 | } 34 | } 35 | 36 | /** Synchronously executes a subcommand. */ 37 | function exec(cmd: string) { 38 | childProcess.execSync(cmd, {stdio: 'inherit'}); 39 | } 40 | 41 | /** gzips a bundle and returns the gzipped size. */ 42 | function gzip(path: string): number { 43 | exec(`gzip -k -9 -f ${path}`); 44 | return fs.statSync(`${path}.gz`).size; 45 | } 46 | 47 | /** brotli compresses a bundle and returns the compressed file size. */ 48 | function brotli(path: string): number { 49 | const brotli = process.env['BROTLI'] || 'brotli'; 50 | exec(`${brotli} -k -9 -f ${path}`); 51 | return fs.statSync(`${path}.br`).size; 52 | } 53 | 54 | function gen10xAngular(path: string): string { 55 | const ngPath = metadata.js['angularjs'].bundlePath; 56 | const ngJS = fs.readFileSync(ngPath, 'utf-8'); 57 | let data = ngJS; 58 | while (data.length < 10 * 1000 * 1000) { 59 | data += ngJS; 60 | } 61 | 62 | const outPath = `out/${path}`; 63 | fs.writeFileSync(outPath, data); 64 | return outPath; 65 | } 66 | 67 | /** 68 | * Runs a test suite with a JS bundle substituted in. 69 | * @return a failure message if failed, undefined on success. 70 | */ 71 | async function runTests( 72 | test: metadata.Test, 73 | bundlePath: string 74 | ): Promise { 75 | const server = new WebServer(test.webroot); 76 | const port = 9000; 77 | server.remaps.set('/bundle.js', bundlePath); 78 | await server.run(port); 79 | 80 | const mocha = new Mocha(); 81 | mocha.addFile(test.test); 82 | mocha.reporter('progress'); 83 | const failures = await new Promise(resolve => { 84 | mocha.run(failures => { 85 | resolve(failures); 86 | }); 87 | }); 88 | mocha.unloadFiles(); 89 | await server.stop(); 90 | 91 | if (failures > 0) { 92 | console.warn(`run test manually via\n$ ${server.cmdline()}`); 93 | return 'test failure'; 94 | } 95 | } 96 | 97 | async function main() { 98 | commander 99 | .option( 100 | '--tools [regex]', 101 | 'regex to match tools to run', 102 | arg => new RegExp(arg) 103 | ) 104 | .option( 105 | '--inputs [regex]', 106 | 'regex to match inputs to run', 107 | arg => new RegExp(arg) 108 | ) 109 | .option('--no-tests', 'skip running tests') 110 | .parse(process.argv); 111 | const toolFilter = commander.tools; 112 | const inputFilter = commander.inputs; 113 | 114 | try { 115 | fs.mkdirSync('out'); 116 | } catch (e) { 117 | if (e.code != 'EEXIST') throw e; 118 | } 119 | 120 | let inputs = Object.keys(metadata.js); 121 | inputs.sort(); 122 | 123 | let results: Result[] = []; 124 | for (const input of inputs) { 125 | if (inputFilter && !inputFilter.test(input)) continue; 126 | const {bundlePath, transform, test, externs} = metadata.js[input]; 127 | let inputPath = bundlePath; 128 | if (transform) { 129 | if (transform === 'angularjs 10x') { 130 | inputPath = gen10xAngular(inputPath); 131 | } else { 132 | throw new Error(`unknown transform ${transform}`); 133 | } 134 | } 135 | for (const {id: tool, variants} of metadata.tools) { 136 | for (const {id: variant, command} of variants) { 137 | const toolVariant = tool + (variant ? `-${variant}` : ''); 138 | if (toolFilter && !toolFilter.test(toolVariant)) continue; 139 | console.log(`${input} ${toolVariant}`); 140 | const out = `out/${input}.${toolVariant}`; 141 | const cmd = command 142 | .replace('%%in%%', inputPath) 143 | .replace('%%out%%', out) 144 | .replace('%%externs%%', externs || ''); 145 | 146 | const result: Result = { 147 | input, 148 | tool, 149 | variant, 150 | time: 0, 151 | size: 0, 152 | gzSize: 0, 153 | brSize: 0 154 | }; 155 | 156 | const start = Date.now(); 157 | try { 158 | exec(cmd); 159 | } catch (e) { 160 | const end = Date.now(); 161 | result.time = Date.now() - start; 162 | result.failure = e.toString(); 163 | results.push(result); 164 | continue; 165 | } 166 | result.time = Date.now() - start; 167 | 168 | if (commander.tests && test) { 169 | const failureMsg = await runTests(test, out); 170 | if (failureMsg) { 171 | result.failure = failureMsg; 172 | results.push(result); 173 | continue; 174 | } 175 | } else { 176 | result.untested = true; 177 | console.warn('warning: no test'); 178 | } 179 | 180 | result.size = fs.statSync(out).size; 181 | result.gzSize = gzip(out); 182 | result.brSize = brotli(out); 183 | 184 | results.push(result); 185 | } 186 | } 187 | } 188 | 189 | fs.writeFileSync('out/results.json', JSON.stringify(results)); 190 | } 191 | 192 | main().catch(err => { 193 | console.error(err); 194 | process.exitCode = 1; 195 | }); 196 | -------------------------------------------------------------------------------- /src/web_server.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as url from 'url'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import * as commander from 'commander'; 6 | 7 | export class WebServer { 8 | server = http.createServer(this.handler.bind(this)); 9 | listening: Promise = new Promise(resolve => { 10 | this.server.on('listening', () => { 11 | resolve(); 12 | }); 13 | }); 14 | remaps = new Map(); 15 | 16 | constructor(private root: string) {} 17 | 18 | private handler(req: http.IncomingMessage, res: http.ServerResponse) { 19 | const reqUrl = url.parse(req.url || '/'); 20 | let reqPath = path.normalize(reqUrl.path || '/'); 21 | if (!reqPath.startsWith('/')) return serveError(400, 'bad path'); 22 | 23 | if (reqPath.endsWith('/')) reqPath += 'index.html'; 24 | 25 | const remap = this.remaps.get(reqPath); 26 | if (remap) { 27 | reqPath = path.normalize(remap); 28 | } else { 29 | reqPath = path.join(this.root, reqPath); 30 | } 31 | 32 | const file = fs.createReadStream(reqPath); 33 | file.on('error', err => { 34 | serveError(500, err.toString()); 35 | }); 36 | file.pipe(res); 37 | 38 | function serveError(status: number, msg: string) { 39 | res.statusCode = status; 40 | res.end(msg); 41 | } 42 | } 43 | 44 | run(port: number) { 45 | this.server.listen(port); 46 | return this.listening; 47 | } 48 | 49 | stop(): Promise { 50 | return new Promise(resolve => { 51 | this.server.close(() => { 52 | resolve(); 53 | }); 54 | }); 55 | } 56 | 57 | cmdline(): string { 58 | let cmd = `node build/src/web_server.js --root=${this.root}`; 59 | for (const [src, dst] of this.remaps) { 60 | cmd += ` --remap=${src}=${dst}`; 61 | } 62 | return cmd; 63 | } 64 | } 65 | 66 | if (require.main === module) { 67 | commander 68 | .option('--root ', 'root dir to serve') 69 | .option('--remap ', 'remap request path') 70 | .parse(process.argv); 71 | 72 | const server = new WebServer(commander.root || '.'); 73 | if (commander.remap) { 74 | const [src, dst] = commander.remap.split('='); 75 | server.remaps.set(src, dst); 76 | } 77 | server.run(9000); 78 | console.log('listening on :9000'); 79 | } 80 | -------------------------------------------------------------------------------- /third_party/angular/README.md: -------------------------------------------------------------------------------- 1 | # Angular sample app 2 | 3 | This is extracted from Angular 5's autogenerated basic app, which was generated 4 | using Angular CLI version 1.6.3. 5 | 6 | ## Reconstructing the JS 7 | 8 | ```sh 9 | $ npm install -g @angular/cli 10 | $ ng new hello 11 | $ cd hello 12 | $ ng eject --target=production 13 | $ edit webpack.config.js 14 | ### apply the changes in config.patch, which remove chunk splitting and uglify. 15 | $ ./node_modules/.bin/webpack 16 | $ cp dist/main.*.js ../main.js 17 | $ rm -rf hello # >250mb! 18 | ``` 19 | -------------------------------------------------------------------------------- /third_party/angular/config.patch: -------------------------------------------------------------------------------- 1 | --- webpack.config.orig.js 2018-01-05 13:28:16.173813484 -0800 2 | +++ webpack.config.js 2018-01-05 13:28:44.057820232 -0800 3 | @@ -105,11 +105,9 @@ 4 | }, 5 | "entry": { 6 | "main": [ 7 | + "./src/polyfills.ts", 8 | "./src/main.ts" 9 | ], 10 | - "polyfills": [ 11 | - "./src/polyfills.ts" 12 | - ], 13 | "styles": [ 14 | "./src/styles.css" 15 | ] 16 | @@ -476,19 +474,6 @@ 17 | } 18 | }), 19 | new BaseHrefWebpackPlugin({}), 20 | - new CommonsChunkPlugin({ 21 | - "name": [ 22 | - "inline" 23 | - ], 24 | - "minChunks": null 25 | - }), 26 | - new CommonsChunkPlugin({ 27 | - "name": [ 28 | - "main" 29 | - ], 30 | - "minChunks": 2, 31 | - "async": "common" 32 | - }), 33 | new ExtractTextPlugin({ 34 | "filename": "[name].[contenthash:20].bundle.css" 35 | }), 36 | @@ -525,31 +510,6 @@ 37 | "pattern": /^(MIT|ISC|BSD.*)$/ 38 | }), 39 | new PurifyPlugin(), 40 | - new UglifyJsPlugin({ 41 | - "test": /\.js$/i, 42 | - "extractComments": false, 43 | - "sourceMap": false, 44 | - "cache": false, 45 | - "parallel": false, 46 | - "uglifyOptions": { 47 | - "output": { 48 | - "ascii_only": true, 49 | - "comments": false, 50 | - "webkit": true 51 | - }, 52 | - "ecma": 5, 53 | - "warnings": false, 54 | - "ie8": false, 55 | - "mangle": { 56 | - "safari10": true 57 | - }, 58 | - "compress": { 59 | - "typeofs": false, 60 | - "pure_getters": true, 61 | - "passes": 3 62 | - } 63 | - } 64 | - }), 65 | new AngularCompilerPlugin({ 66 | "mainPath": "main.ts", 67 | "platform": 0, 68 | -------------------------------------------------------------------------------- /third_party/angularjs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2017 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /third_party/externs.js: -------------------------------------------------------------------------------- 1 | /** @externs */ 2 | 3 | /** 4 | * Copyright 2017 Google LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | var exports; 20 | var module; 21 | var global; 22 | var define; 23 | var Proxy; 24 | -------------------------------------------------------------------------------- /third_party/react/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-present, Facebook, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/react/README.md: -------------------------------------------------------------------------------- 1 | ## Building 2 | 3 | 1. `git clone` the react repo 4 | 2. `yarn` 5 | 3. turn off the production minifier via `minifier.patch` 6 | 4. `yarn run build --type=umd_prod` 7 | 5. copy relevant files from `build/dist` 8 | 9 | The current files came from react master at 10 | dd8b387b69d73f9e8ed4f995ccb3cd927c0d33e3. 11 | -------------------------------------------------------------------------------- /third_party/react/minifier.patch: -------------------------------------------------------------------------------- 1 | diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js 2 | index 1b3c0d3..5edc5bd 100644 3 | --- a/scripts/rollup/build.js 4 | +++ b/scripts/rollup/build.js 5 | @@ -234,7 +234,7 @@ function getPlugins( 6 | }, 7 | }, 8 | // Apply dead code elimination and/or minification. 9 | - isProduction && 10 | + isProduction && false && 11 | closure( 12 | Object.assign({}, closureOptions, { 13 | // Don't let it create global variables in the browser. 14 | -------------------------------------------------------------------------------- /third_party/todomvc/LICENSE: -------------------------------------------------------------------------------- 1 | Everything in this repo is MIT License unless otherwise specified. 2 | 3 | Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /third_party/todomvc/README.md: -------------------------------------------------------------------------------- 1 | # todomvc 2 | 3 | [todomvc](https://github.com/tastejs/todomvc) is the same todo app implemented 4 | in a bunch of different frameworks. js-min-bench runs these apps to verify 5 | whehter a given minified JS bundle breaks the app or not. 6 | 7 | ## Relation to upstream 8 | 9 | This directory contains git-subtree imports of the todomvc apps. 10 | 11 | They were imported via e.g. 12 | 13 | ```sh 14 | $ git remote add todomvc https://github.com/tastejs/todomvc.git 15 | $ git checkout -b todomvc todomvc/master 16 | $ git subtree split -P examples/vanillajs -b todomvc-vanillajs 17 | $ git checkout master 18 | $ git subtree add -P third_party/todomvc/vanillajs todomvc-vanillajs --squash 19 | ``` 20 | 21 | They were then modified by hand to use a single bundle for their JS, as 22 | described in the following sections. 23 | 24 | ### vanillajs 25 | 26 | The scripts were concatenated with 'cat' and the HTML file changed to use it. 27 | 28 | A command like this extracts all of the source scripts in order: 29 | 30 | ```sh 31 | $ cat $(sed -ne 's/.*script src="\(.*\)".*/\1/p' index.html) > bundle.js 32 | ``` 33 | 34 | ### react 35 | 36 | The jsx was manually transpiled to js via 37 | 38 | ```sh 39 | $ path/to/tsc --jsx react -t es2017 --allowjs *.jsx 40 | ``` 41 | 42 | then the scripts were concatenated as in vanillajs. 43 | 44 | I manually deleted a line containing `@nosideeffects` to fix Closure 45 | compilation. It appears 46 | [this was removed in upstream React a while ago](https://github.com/facebook/react/pull/8882), 47 | but maybe the todomvc version of React is older than that? 48 | -------------------------------------------------------------------------------- /third_party/todomvc/react/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/director 2 | !node_modules/director/build/director.js 3 | 4 | node_modules/react 5 | !node_modules/react/dist/react-with-addons.js 6 | !node_modules/react/dist/JSXTransformer.js 7 | node_modules/todomvc-app-css 8 | !node_modules/todomvc-app-css/index.css 9 | 10 | node_modules/todomvc-common 11 | !node_modules/todomvc-common/base.css 12 | !node_modules/todomvc-common/base.js 13 | -------------------------------------------------------------------------------- /third_party/todomvc/react/externs.js: -------------------------------------------------------------------------------- 1 | /** @externs */ 2 | 3 | /** 4 | * Copyright 2019 Google LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | var React, Router; 20 | var classNames; 21 | 22 | // Passed to the classNames function as keys: 23 | var completed, editing; 24 | -------------------------------------------------------------------------------- /third_party/todomvc/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Double-click to edit a todo

13 |

Created by petehunt

14 |

Part of TodoMVC

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /third_party/todomvc/react/js/app.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | /*global React, Router*/ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | app.ALL_TODOS = 'all'; 12 | app.ACTIVE_TODOS = 'active'; 13 | app.COMPLETED_TODOS = 'completed'; 14 | var TodoFooter = app.TodoFooter; 15 | var TodoItem = app.TodoItem; 16 | 17 | var ENTER_KEY = 13; 18 | 19 | var TodoApp = React.createClass({ 20 | getInitialState: function () { 21 | return { 22 | nowShowing: app.ALL_TODOS, 23 | editing: null, 24 | newTodo: '' 25 | }; 26 | }, 27 | 28 | componentDidMount: function () { 29 | var setState = this.setState; 30 | var router = Router({ 31 | '/': setState.bind(this, {nowShowing: app.ALL_TODOS}), 32 | '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}), 33 | '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS}) 34 | }); 35 | router.init('/'); 36 | }, 37 | 38 | handleChange: function (event) { 39 | this.setState({newTodo: event.target.value}); 40 | }, 41 | 42 | handleNewTodoKeyDown: function (event) { 43 | if (event.keyCode !== ENTER_KEY) { 44 | return; 45 | } 46 | 47 | event.preventDefault(); 48 | 49 | var val = this.state.newTodo.trim(); 50 | 51 | if (val) { 52 | this.props.model.addTodo(val); 53 | this.setState({newTodo: ''}); 54 | } 55 | }, 56 | 57 | toggleAll: function (event) { 58 | var checked = event.target.checked; 59 | this.props.model.toggleAll(checked); 60 | }, 61 | 62 | toggle: function (todoToToggle) { 63 | this.props.model.toggle(todoToToggle); 64 | }, 65 | 66 | destroy: function (todo) { 67 | this.props.model.destroy(todo); 68 | }, 69 | 70 | edit: function (todo) { 71 | this.setState({editing: todo.id}); 72 | }, 73 | 74 | save: function (todoToSave, text) { 75 | this.props.model.save(todoToSave, text); 76 | this.setState({editing: null}); 77 | }, 78 | 79 | cancel: function () { 80 | this.setState({editing: null}); 81 | }, 82 | 83 | clearCompleted: function () { 84 | this.props.model.clearCompleted(); 85 | }, 86 | 87 | render: function () { 88 | var footer; 89 | var main; 90 | var todos = this.props.model.todos; 91 | 92 | var shownTodos = todos.filter(function (todo) { 93 | switch (this.state.nowShowing) { 94 | case app.ACTIVE_TODOS: 95 | return !todo.completed; 96 | case app.COMPLETED_TODOS: 97 | return todo.completed; 98 | default: 99 | return true; 100 | } 101 | }, this); 102 | 103 | var todoItems = shownTodos.map(function (todo) { 104 | return ( 105 | 115 | ); 116 | }, this); 117 | 118 | var activeTodoCount = todos.reduce(function (accum, todo) { 119 | return todo.completed ? accum : accum + 1; 120 | }, 0); 121 | 122 | var completedCount = todos.length - activeTodoCount; 123 | 124 | if (activeTodoCount || completedCount) { 125 | footer = 126 | ; 132 | } 133 | 134 | if (todos.length) { 135 | main = ( 136 |
137 | 144 |
151 | ); 152 | } 153 | 154 | return ( 155 |
156 |
157 |

todos

158 | 166 |
167 | {main} 168 | {footer} 169 |
170 | ); 171 | } 172 | }); 173 | 174 | var model = new app.TodoModel('react-todos'); 175 | 176 | function render() { 177 | React.render( 178 | , 179 | document.getElementsByClassName('todoapp')[0] 180 | ); 181 | } 182 | 183 | model.subscribe(render); 184 | render(); 185 | })(); 186 | -------------------------------------------------------------------------------- /third_party/todomvc/react/js/footer.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | /*global React */ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | app.TodoFooter = React.createClass({ 12 | render: function () { 13 | var activeTodoWord = app.Utils.pluralize(this.props.count, 'item'); 14 | var clearButton = null; 15 | 16 | if (this.props.completedCount > 0) { 17 | clearButton = ( 18 | 23 | ); 24 | } 25 | 26 | var nowShowing = this.props.nowShowing; 27 | return ( 28 | 59 | ); 60 | } 61 | }); 62 | })(); 63 | -------------------------------------------------------------------------------- /third_party/todomvc/react/js/todoItem.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark: false */ 2 | /*jshint white: false */ 3 | /*jshint trailing: false */ 4 | /*jshint newcap: false */ 5 | /*global React */ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | var ESCAPE_KEY = 27; 12 | var ENTER_KEY = 13; 13 | 14 | app.TodoItem = React.createClass({ 15 | handleSubmit: function (event) { 16 | var val = this.state.editText.trim(); 17 | if (val) { 18 | this.props.onSave(val); 19 | this.setState({editText: val}); 20 | } else { 21 | this.props.onDestroy(); 22 | } 23 | }, 24 | 25 | handleEdit: function () { 26 | this.props.onEdit(); 27 | this.setState({editText: this.props.todo.title}); 28 | }, 29 | 30 | handleKeyDown: function (event) { 31 | if (event.which === ESCAPE_KEY) { 32 | this.setState({editText: this.props.todo.title}); 33 | this.props.onCancel(event); 34 | } else if (event.which === ENTER_KEY) { 35 | this.handleSubmit(event); 36 | } 37 | }, 38 | 39 | handleChange: function (event) { 40 | if (this.props.editing) { 41 | this.setState({editText: event.target.value}); 42 | } 43 | }, 44 | 45 | getInitialState: function () { 46 | return {editText: this.props.todo.title}; 47 | }, 48 | 49 | /** 50 | * This is a completely optional performance enhancement that you can 51 | * implement on any React component. If you were to delete this method 52 | * the app would still work correctly (and still be very performant!), we 53 | * just use it as an example of how little code it takes to get an order 54 | * of magnitude performance improvement. 55 | */ 56 | shouldComponentUpdate: function (nextProps, nextState) { 57 | return ( 58 | nextProps.todo !== this.props.todo || 59 | nextProps.editing !== this.props.editing || 60 | nextState.editText !== this.state.editText 61 | ); 62 | }, 63 | 64 | /** 65 | * Safely manipulate the DOM after updating the state when invoking 66 | * `this.props.onEdit()` in the `handleEdit` method above. 67 | * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate 68 | * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate 69 | */ 70 | componentDidUpdate: function (prevProps) { 71 | if (!prevProps.editing && this.props.editing) { 72 | var node = React.findDOMNode(this.refs.editField); 73 | node.focus(); 74 | node.setSelectionRange(node.value.length, node.value.length); 75 | } 76 | }, 77 | 78 | render: function () { 79 | return ( 80 |
  • 84 |
    85 | 91 | 94 |
    96 | 104 |
  • 105 | ); 106 | } 107 | }); 108 | })(); 109 | -------------------------------------------------------------------------------- /third_party/todomvc/react/js/todoModel.js: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | var app = app || {}; 6 | 7 | (function () { 8 | 'use strict'; 9 | 10 | var Utils = app.Utils; 11 | // Generic "model" object. You can use whatever 12 | // framework you want. For this application it 13 | // may not even be worth separating this logic 14 | // out, but we do this to demonstrate one way to 15 | // separate out parts of your application. 16 | app.TodoModel = function (key) { 17 | this.key = key; 18 | this.todos = Utils.store(key); 19 | this.onChanges = []; 20 | }; 21 | 22 | app.TodoModel.prototype.subscribe = function (onChange) { 23 | this.onChanges.push(onChange); 24 | }; 25 | 26 | app.TodoModel.prototype.inform = function () { 27 | Utils.store(this.key, this.todos); 28 | this.onChanges.forEach(function (cb) { cb(); }); 29 | }; 30 | 31 | app.TodoModel.prototype.addTodo = function (title) { 32 | this.todos = this.todos.concat({ 33 | id: Utils.uuid(), 34 | title: title, 35 | completed: false 36 | }); 37 | 38 | this.inform(); 39 | }; 40 | 41 | app.TodoModel.prototype.toggleAll = function (checked) { 42 | // Note: it's usually better to use immutable data structures since they're 43 | // easier to reason about and React works very well with them. That's why 44 | // we use map() and filter() everywhere instead of mutating the array or 45 | // todo items themselves. 46 | this.todos = this.todos.map(function (todo) { 47 | return Utils.extend({}, todo, {completed: checked}); 48 | }); 49 | 50 | this.inform(); 51 | }; 52 | 53 | app.TodoModel.prototype.toggle = function (todoToToggle) { 54 | this.todos = this.todos.map(function (todo) { 55 | return todo !== todoToToggle ? 56 | todo : 57 | Utils.extend({}, todo, {completed: !todo.completed}); 58 | }); 59 | 60 | this.inform(); 61 | }; 62 | 63 | app.TodoModel.prototype.destroy = function (todo) { 64 | this.todos = this.todos.filter(function (candidate) { 65 | return candidate !== todo; 66 | }); 67 | 68 | this.inform(); 69 | }; 70 | 71 | app.TodoModel.prototype.save = function (todoToSave, text) { 72 | this.todos = this.todos.map(function (todo) { 73 | return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}); 74 | }); 75 | 76 | this.inform(); 77 | }; 78 | 79 | app.TodoModel.prototype.clearCompleted = function () { 80 | this.todos = this.todos.filter(function (todo) { 81 | return !todo.completed; 82 | }); 83 | 84 | this.inform(); 85 | }; 86 | 87 | })(); 88 | -------------------------------------------------------------------------------- /third_party/todomvc/react/js/utils.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | app.Utils = { 7 | uuid: function () { 8 | /*jshint bitwise:false */ 9 | var i, random; 10 | var uuid = ''; 11 | 12 | for (i = 0; i < 32; i++) { 13 | random = Math.random() * 16 | 0; 14 | if (i === 8 || i === 12 || i === 16 || i === 20) { 15 | uuid += '-'; 16 | } 17 | uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) 18 | .toString(16); 19 | } 20 | 21 | return uuid; 22 | }, 23 | 24 | pluralize: function (count, word) { 25 | return count === 1 ? word : word + 's'; 26 | }, 27 | 28 | store: function (namespace, data) { 29 | if (data) { 30 | return localStorage.setItem(namespace, JSON.stringify(data)); 31 | } 32 | 33 | var store = localStorage.getItem(namespace); 34 | return (store && JSON.parse(store)) || []; 35 | }, 36 | 37 | extend: function () { 38 | var newObj = {}; 39 | for (var i = 0; i < arguments.length; i++) { 40 | var obj = arguments[i]; 41 | for (var key in obj) { 42 | if (obj.hasOwnProperty(key)) { 43 | newObj[key] = obj[key]; 44 | } 45 | } 46 | } 47 | return newObj; 48 | } 49 | }; 50 | })(); 51 | -------------------------------------------------------------------------------- /third_party/todomvc/react/node_modules/classnames/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2015 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | /* global define */ 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | var hasOwn = {}.hasOwnProperty; 12 | 13 | function classNames () { 14 | var classes = ''; 15 | 16 | for (var i = 0; i < arguments.length; i++) { 17 | var arg = arguments[i]; 18 | if (!arg) continue; 19 | 20 | var argType = typeof arg; 21 | 22 | if (argType === 'string' || argType === 'number') { 23 | classes += ' ' + arg; 24 | } else if (Array.isArray(arg)) { 25 | classes += ' ' + classNames.apply(null, arg); 26 | } else if (argType === 'object') { 27 | for (var key in arg) { 28 | if (hasOwn.call(arg, key) && arg[key]) { 29 | classes += ' ' + key; 30 | } 31 | } 32 | } 33 | } 34 | 35 | return classes.substr(1); 36 | } 37 | 38 | if (typeof module !== 'undefined' && module.exports) { 39 | module.exports = classNames; 40 | } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { 41 | // register as 'classnames', consistent with npm package name 42 | define('classnames', function () { 43 | return classNames; 44 | }); 45 | } else { 46 | window.classNames = classNames; 47 | } 48 | }()); 49 | -------------------------------------------------------------------------------- /third_party/todomvc/react/node_modules/director/build/director.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // 4 | // Generated on Tue Dec 16 2014 12:13:47 GMT+0100 (CET) by Charlie Robbins, Paolo Fragomeni & the Contributors (Using Codesurgeon). 5 | // Version 1.2.6 6 | // 7 | 8 | (function (exports) { 9 | 10 | /* 11 | * browser.js: Browser specific functionality for director. 12 | * 13 | * (C) 2011, Charlie Robbins, Paolo Fragomeni, & the Contributors. 14 | * MIT LICENSE 15 | * 16 | */ 17 | 18 | var dloc = document.location; 19 | 20 | function dlocHashEmpty() { 21 | // Non-IE browsers return '' when the address bar shows '#'; Director's logic 22 | // assumes both mean empty. 23 | return dloc.hash === '' || dloc.hash === '#'; 24 | } 25 | 26 | var listener = { 27 | mode: 'modern', 28 | hash: dloc.hash, 29 | history: false, 30 | 31 | check: function () { 32 | var h = dloc.hash; 33 | if (h != this.hash) { 34 | this.hash = h; 35 | this.onHashChanged(); 36 | } 37 | }, 38 | 39 | fire: function () { 40 | if (this.mode === 'modern') { 41 | this.history === true ? window.onpopstate() : window.onhashchange(); 42 | } 43 | else { 44 | this.onHashChanged(); 45 | } 46 | }, 47 | 48 | init: function (fn, history) { 49 | var self = this; 50 | this.history = history; 51 | 52 | if (!Router.listeners) { 53 | Router.listeners = []; 54 | } 55 | 56 | function onchange(onChangeEvent) { 57 | for (var i = 0, l = Router.listeners.length; i < l; i++) { 58 | Router.listeners[i](onChangeEvent); 59 | } 60 | } 61 | 62 | //note IE8 is being counted as 'modern' because it has the hashchange event 63 | if ('onhashchange' in window && (document.documentMode === undefined 64 | || document.documentMode > 7)) { 65 | // At least for now HTML5 history is available for 'modern' browsers only 66 | if (this.history === true) { 67 | // There is an old bug in Chrome that causes onpopstate to fire even 68 | // upon initial page load. Since the handler is run manually in init(), 69 | // this would cause Chrome to run it twise. Currently the only 70 | // workaround seems to be to set the handler after the initial page load 71 | // http://code.google.com/p/chromium/issues/detail?id=63040 72 | setTimeout(function() { 73 | window.onpopstate = onchange; 74 | }, 500); 75 | } 76 | else { 77 | window.onhashchange = onchange; 78 | } 79 | this.mode = 'modern'; 80 | } 81 | else { 82 | // 83 | // IE support, based on a concept by Erik Arvidson ... 84 | // 85 | var frame = document.createElement('iframe'); 86 | frame.id = 'state-frame'; 87 | frame.style.display = 'none'; 88 | document.body.appendChild(frame); 89 | this.writeFrame(''); 90 | 91 | if ('onpropertychange' in document && 'attachEvent' in document) { 92 | document.attachEvent('onpropertychange', function () { 93 | if (event.propertyName === 'location') { 94 | self.check(); 95 | } 96 | }); 97 | } 98 | 99 | window.setInterval(function () { self.check(); }, 50); 100 | 101 | this.onHashChanged = onchange; 102 | this.mode = 'legacy'; 103 | } 104 | 105 | Router.listeners.push(fn); 106 | 107 | return this.mode; 108 | }, 109 | 110 | destroy: function (fn) { 111 | if (!Router || !Router.listeners) { 112 | return; 113 | } 114 | 115 | var listeners = Router.listeners; 116 | 117 | for (var i = listeners.length - 1; i >= 0; i--) { 118 | if (listeners[i] === fn) { 119 | listeners.splice(i, 1); 120 | } 121 | } 122 | }, 123 | 124 | setHash: function (s) { 125 | // Mozilla always adds an entry to the history 126 | if (this.mode === 'legacy') { 127 | this.writeFrame(s); 128 | } 129 | 130 | if (this.history === true) { 131 | window.history.pushState({}, document.title, s); 132 | // Fire an onpopstate event manually since pushing does not obviously 133 | // trigger the pop event. 134 | this.fire(); 135 | } else { 136 | dloc.hash = (s[0] === '/') ? s : '/' + s; 137 | } 138 | return this; 139 | }, 140 | 141 | writeFrame: function (s) { 142 | // IE support... 143 | var f = document.getElementById('state-frame'); 144 | var d = f.contentDocument || f.contentWindow.document; 145 | d.open(); 146 | d.write(" 43 | 44 | 45 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/app.js: -------------------------------------------------------------------------------- 1 | /*global app, $on */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /** 6 | * Sets up a brand new Todo list. 7 | * 8 | * @param {string} name The name of your new to do list. 9 | */ 10 | function Todo(name) { 11 | this.storage = new app.Store(name); 12 | this.model = new app.Model(this.storage); 13 | this.template = new app.Template(); 14 | this.view = new app.View(this.template); 15 | this.controller = new app.Controller(this.model, this.view); 16 | } 17 | 18 | var todo = new Todo('todos-vanillajs'); 19 | 20 | function setView() { 21 | todo.controller.setView(document.location.hash); 22 | } 23 | $on(window, 'load', setView); 24 | $on(window, 'hashchange', setView); 25 | })(); 26 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/controller.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 'use strict'; 3 | 4 | /** 5 | * Takes a model and view and acts as the controller between them 6 | * 7 | * @constructor 8 | * @param {object} model The model instance 9 | * @param {object} view The view instance 10 | */ 11 | function Controller(model, view) { 12 | var self = this; 13 | self.model = model; 14 | self.view = view; 15 | 16 | self.view.bind('newTodo', function (title) { 17 | self.addItem(title); 18 | }); 19 | 20 | self.view.bind('itemEdit', function (item) { 21 | self.editItem(item.id); 22 | }); 23 | 24 | self.view.bind('itemEditDone', function (item) { 25 | self.editItemSave(item.id, item.title); 26 | }); 27 | 28 | self.view.bind('itemEditCancel', function (item) { 29 | self.editItemCancel(item.id); 30 | }); 31 | 32 | self.view.bind('itemRemove', function (item) { 33 | self.removeItem(item.id); 34 | }); 35 | 36 | self.view.bind('itemToggle', function (item) { 37 | self.toggleComplete(item.id, item.completed); 38 | }); 39 | 40 | self.view.bind('removeCompleted', function () { 41 | self.removeCompletedItems(); 42 | }); 43 | 44 | self.view.bind('toggleAll', function (status) { 45 | self.toggleAll(status.completed); 46 | }); 47 | } 48 | 49 | /** 50 | * Loads and initialises the view 51 | * 52 | * @param {string} '' | 'active' | 'completed' 53 | */ 54 | Controller.prototype.setView = function (locationHash) { 55 | var route = locationHash.split('/')[1]; 56 | var page = route || ''; 57 | this._updateFilterState(page); 58 | }; 59 | 60 | /** 61 | * An event to fire on load. Will get all items and display them in the 62 | * todo-list 63 | */ 64 | Controller.prototype.showAll = function () { 65 | var self = this; 66 | self.model.read(function (data) { 67 | self.view.render('showEntries', data); 68 | }); 69 | }; 70 | 71 | /** 72 | * Renders all active tasks 73 | */ 74 | Controller.prototype.showActive = function () { 75 | var self = this; 76 | self.model.read({ completed: false }, function (data) { 77 | self.view.render('showEntries', data); 78 | }); 79 | }; 80 | 81 | /** 82 | * Renders all completed tasks 83 | */ 84 | Controller.prototype.showCompleted = function () { 85 | var self = this; 86 | self.model.read({ completed: true }, function (data) { 87 | self.view.render('showEntries', data); 88 | }); 89 | }; 90 | 91 | /** 92 | * An event to fire whenever you want to add an item. Simply pass in the event 93 | * object and it'll handle the DOM insertion and saving of the new item. 94 | */ 95 | Controller.prototype.addItem = function (title) { 96 | var self = this; 97 | 98 | if (title.trim() === '') { 99 | return; 100 | } 101 | 102 | self.model.create(title, function () { 103 | self.view.render('clearNewTodo'); 104 | self._filter(true); 105 | }); 106 | }; 107 | 108 | /* 109 | * Triggers the item editing mode. 110 | */ 111 | Controller.prototype.editItem = function (id) { 112 | var self = this; 113 | self.model.read(id, function (data) { 114 | self.view.render('editItem', {id: id, title: data[0].title}); 115 | }); 116 | }; 117 | 118 | /* 119 | * Finishes the item editing mode successfully. 120 | */ 121 | Controller.prototype.editItemSave = function (id, title) { 122 | var self = this; 123 | title = title.trim(); 124 | 125 | if (title.length !== 0) { 126 | self.model.update(id, {title: title}, function () { 127 | self.view.render('editItemDone', {id: id, title: title}); 128 | }); 129 | } else { 130 | self.removeItem(id); 131 | } 132 | }; 133 | 134 | /* 135 | * Cancels the item editing mode. 136 | */ 137 | Controller.prototype.editItemCancel = function (id) { 138 | var self = this; 139 | self.model.read(id, function (data) { 140 | self.view.render('editItemDone', {id: id, title: data[0].title}); 141 | }); 142 | }; 143 | 144 | /** 145 | * By giving it an ID it'll find the DOM element matching that ID, 146 | * remove it from the DOM and also remove it from storage. 147 | * 148 | * @param {number} id The ID of the item to remove from the DOM and 149 | * storage 150 | */ 151 | Controller.prototype.removeItem = function (id) { 152 | var self = this; 153 | self.model.remove(id, function () { 154 | self.view.render('removeItem', id); 155 | }); 156 | 157 | self._filter(); 158 | }; 159 | 160 | /** 161 | * Will remove all completed items from the DOM and storage. 162 | */ 163 | Controller.prototype.removeCompletedItems = function () { 164 | var self = this; 165 | self.model.read({ completed: true }, function (data) { 166 | data.forEach(function (item) { 167 | self.removeItem(item.id); 168 | }); 169 | }); 170 | 171 | self._filter(); 172 | }; 173 | 174 | /** 175 | * Give it an ID of a model and a checkbox and it will update the item 176 | * in storage based on the checkbox's state. 177 | * 178 | * @param {number} id The ID of the element to complete or uncomplete 179 | * @param {object} checkbox The checkbox to check the state of complete 180 | * or not 181 | * @param {boolean|undefined} silent Prevent re-filtering the todo items 182 | */ 183 | Controller.prototype.toggleComplete = function (id, completed, silent) { 184 | var self = this; 185 | self.model.update(id, { completed: completed }, function () { 186 | self.view.render('elementComplete', { 187 | id: id, 188 | completed: completed 189 | }); 190 | }); 191 | 192 | if (!silent) { 193 | self._filter(); 194 | } 195 | }; 196 | 197 | /** 198 | * Will toggle ALL checkboxes' on/off state and completeness of models. 199 | * Just pass in the event object. 200 | */ 201 | Controller.prototype.toggleAll = function (completed) { 202 | var self = this; 203 | self.model.read({ completed: !completed }, function (data) { 204 | data.forEach(function (item) { 205 | self.toggleComplete(item.id, completed, true); 206 | }); 207 | }); 208 | 209 | self._filter(); 210 | }; 211 | 212 | /** 213 | * Updates the pieces of the page which change depending on the remaining 214 | * number of todos. 215 | */ 216 | Controller.prototype._updateCount = function () { 217 | var self = this; 218 | self.model.getCount(function (todos) { 219 | self.view.render('updateElementCount', todos.active); 220 | self.view.render('clearCompletedButton', { 221 | completed: todos.completed, 222 | visible: todos.completed > 0 223 | }); 224 | 225 | self.view.render('toggleAll', {checked: todos.completed === todos.total}); 226 | self.view.render('contentBlockVisibility', {visible: todos.total > 0}); 227 | }); 228 | }; 229 | 230 | /** 231 | * Re-filters the todo items, based on the active route. 232 | * @param {boolean|undefined} force forces a re-painting of todo items. 233 | */ 234 | Controller.prototype._filter = function (force) { 235 | var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1); 236 | 237 | // Update the elements on the page, which change with each completed todo 238 | this._updateCount(); 239 | 240 | // If the last active route isn't "All", or we're switching routes, we 241 | // re-create the todo item elements, calling: 242 | // this.show[All|Active|Completed](); 243 | if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { 244 | this['show' + activeRoute](); 245 | } 246 | 247 | this._lastActiveRoute = activeRoute; 248 | }; 249 | 250 | /** 251 | * Simply updates the filter nav's selected states 252 | */ 253 | Controller.prototype._updateFilterState = function (currentPage) { 254 | // Store a reference to the active route, allowing us to re-filter todo 255 | // items as they are marked complete or incomplete. 256 | this._activeRoute = currentPage; 257 | 258 | if (currentPage === '') { 259 | this._activeRoute = 'All'; 260 | } 261 | 262 | this._filter(); 263 | 264 | this.view.render('setFilter', currentPage); 265 | }; 266 | 267 | // Export to window 268 | window.app = window.app || {}; 269 | window.app.Controller = Controller; 270 | })(window); 271 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/helpers.js: -------------------------------------------------------------------------------- 1 | /*global NodeList */ 2 | (function (window) { 3 | 'use strict'; 4 | 5 | // Get element(s) by CSS selector: 6 | window.qs = function (selector, scope) { 7 | return (scope || document).querySelector(selector); 8 | }; 9 | window.qsa = function (selector, scope) { 10 | return (scope || document).querySelectorAll(selector); 11 | }; 12 | 13 | // addEventListener wrapper: 14 | window.$on = function (target, type, callback, useCapture) { 15 | target.addEventListener(type, callback, !!useCapture); 16 | }; 17 | 18 | // Attach a handler to event for all elements that match the selector, 19 | // now or in the future, based on a root element 20 | window.$delegate = function (target, selector, type, handler) { 21 | function dispatchEvent(event) { 22 | var targetElement = event.target; 23 | var potentialElements = window.qsa(selector, target); 24 | var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; 25 | 26 | if (hasMatch) { 27 | handler.call(targetElement, event); 28 | } 29 | } 30 | 31 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur 32 | var useCapture = type === 'blur' || type === 'focus'; 33 | 34 | window.$on(target, type, dispatchEvent, useCapture); 35 | }; 36 | 37 | // Find the element's parent with the given tag name: 38 | // $parent(qs('a'), 'div'); 39 | window.$parent = function (element, tagName) { 40 | if (!element.parentNode) { 41 | return; 42 | } 43 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { 44 | return element.parentNode; 45 | } 46 | return window.$parent(element.parentNode, tagName); 47 | }; 48 | 49 | // Allow for looping on nodes by chaining: 50 | // qsa('.foo').forEach(function () {}) 51 | NodeList.prototype.forEach = Array.prototype.forEach; 52 | })(window); 53 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/model.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 'use strict'; 3 | 4 | /** 5 | * Creates a new Model instance and hooks up the storage. 6 | * 7 | * @constructor 8 | * @param {object} storage A reference to the client side storage class 9 | */ 10 | function Model(storage) { 11 | this.storage = storage; 12 | } 13 | 14 | /** 15 | * Creates a new todo model 16 | * 17 | * @param {string} [title] The title of the task 18 | * @param {function} [callback] The callback to fire after the model is created 19 | */ 20 | Model.prototype.create = function (title, callback) { 21 | title = title || ''; 22 | callback = callback || function () {}; 23 | 24 | var newItem = { 25 | title: title.trim(), 26 | completed: false 27 | }; 28 | 29 | this.storage.save(newItem, callback); 30 | }; 31 | 32 | /** 33 | * Finds and returns a model in storage. If no query is given it'll simply 34 | * return everything. If you pass in a string or number it'll look that up as 35 | * the ID of the model to find. Lastly, you can pass it an object to match 36 | * against. 37 | * 38 | * @param {string|number|object} [query] A query to match models against 39 | * @param {function} [callback] The callback to fire after the model is found 40 | * 41 | * @example 42 | * model.read(1, func); // Will find the model with an ID of 1 43 | * model.read('1'); // Same as above 44 | * //Below will find a model with foo equalling bar and hello equalling world. 45 | * model.read({ foo: 'bar', hello: 'world' }); 46 | */ 47 | Model.prototype.read = function (query, callback) { 48 | var queryType = typeof query; 49 | callback = callback || function () {}; 50 | 51 | if (queryType === 'function') { 52 | callback = query; 53 | return this.storage.findAll(callback); 54 | } else if (queryType === 'string' || queryType === 'number') { 55 | query = parseInt(query, 10); 56 | this.storage.find({ id: query }, callback); 57 | } else { 58 | this.storage.find(query, callback); 59 | } 60 | }; 61 | 62 | /** 63 | * Updates a model by giving it an ID, data to update, and a callback to fire when 64 | * the update is complete. 65 | * 66 | * @param {number} id The id of the model to update 67 | * @param {object} data The properties to update and their new value 68 | * @param {function} callback The callback to fire when the update is complete. 69 | */ 70 | Model.prototype.update = function (id, data, callback) { 71 | this.storage.save(data, callback, id); 72 | }; 73 | 74 | /** 75 | * Removes a model from storage 76 | * 77 | * @param {number} id The ID of the model to remove 78 | * @param {function} callback The callback to fire when the removal is complete. 79 | */ 80 | Model.prototype.remove = function (id, callback) { 81 | this.storage.remove(id, callback); 82 | }; 83 | 84 | /** 85 | * WARNING: Will remove ALL data from storage. 86 | * 87 | * @param {function} callback The callback to fire when the storage is wiped. 88 | */ 89 | Model.prototype.removeAll = function (callback) { 90 | this.storage.drop(callback); 91 | }; 92 | 93 | /** 94 | * Returns a count of all todos 95 | */ 96 | Model.prototype.getCount = function (callback) { 97 | var todos = { 98 | active: 0, 99 | completed: 0, 100 | total: 0 101 | }; 102 | 103 | this.storage.findAll(function (data) { 104 | data.forEach(function (todo) { 105 | if (todo.completed) { 106 | todos.completed++; 107 | } else { 108 | todos.active++; 109 | } 110 | 111 | todos.total++; 112 | }); 113 | callback(todos); 114 | }); 115 | }; 116 | 117 | // Export to window 118 | window.app = window.app || {}; 119 | window.app.Model = Model; 120 | })(window); 121 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint eqeqeq:false */ 2 | (function (window) { 3 | 'use strict'; 4 | 5 | /** 6 | * Creates a new client side storage object and will create an empty 7 | * collection if no collection already exists. 8 | * 9 | * @param {string} name The name of our DB we want to use 10 | * @param {function} callback Our fake DB uses callbacks because in 11 | * real life you probably would be making AJAX calls 12 | */ 13 | function Store(name, callback) { 14 | callback = callback || function () {}; 15 | 16 | this._dbName = name; 17 | 18 | if (!localStorage.getItem(name)) { 19 | var todos = []; 20 | 21 | localStorage.setItem(name, JSON.stringify(todos)); 22 | } 23 | 24 | callback.call(this, JSON.parse(localStorage.getItem(name))); 25 | } 26 | 27 | /** 28 | * Finds items based on a query given as a JS object 29 | * 30 | * @param {object} query The query to match against (i.e. {foo: 'bar'}) 31 | * @param {function} callback The callback to fire when the query has 32 | * completed running 33 | * 34 | * @example 35 | * db.find({foo: 'bar', hello: 'world'}, function (data) { 36 | * // data will return any items that have foo: bar and 37 | * // hello: world in their properties 38 | * }); 39 | */ 40 | Store.prototype.find = function (query, callback) { 41 | if (!callback) { 42 | return; 43 | } 44 | 45 | var todos = JSON.parse(localStorage.getItem(this._dbName)); 46 | 47 | callback.call(this, todos.filter(function (todo) { 48 | for (var q in query) { 49 | if (query[q] !== todo[q]) { 50 | return false; 51 | } 52 | } 53 | return true; 54 | })); 55 | }; 56 | 57 | /** 58 | * Will retrieve all data from the collection 59 | * 60 | * @param {function} callback The callback to fire upon retrieving data 61 | */ 62 | Store.prototype.findAll = function (callback) { 63 | callback = callback || function () {}; 64 | callback.call(this, JSON.parse(localStorage.getItem(this._dbName))); 65 | }; 66 | 67 | /** 68 | * Will save the given data to the DB. If no item exists it will create a new 69 | * item, otherwise it'll simply update an existing item's properties 70 | * 71 | * @param {object} updateData The data to save back into the DB 72 | * @param {function} callback The callback to fire after saving 73 | * @param {number} id An optional param to enter an ID of an item to update 74 | */ 75 | Store.prototype.save = function (updateData, callback, id) { 76 | var todos = JSON.parse(localStorage.getItem(this._dbName)); 77 | 78 | callback = callback || function() {}; 79 | 80 | // If an ID was actually given, find the item and update each property 81 | if (id) { 82 | for (var i = 0; i < todos.length; i++) { 83 | if (todos[i].id === id) { 84 | for (var key in updateData) { 85 | todos[i][key] = updateData[key]; 86 | } 87 | break; 88 | } 89 | } 90 | 91 | localStorage.setItem(this._dbName, JSON.stringify(todos)); 92 | callback.call(this, todos); 93 | } else { 94 | // Generate an ID 95 | updateData.id = new Date().getTime(); 96 | 97 | todos.push(updateData); 98 | localStorage.setItem(this._dbName, JSON.stringify(todos)); 99 | callback.call(this, [updateData]); 100 | } 101 | }; 102 | 103 | /** 104 | * Will remove an item from the Store based on its ID 105 | * 106 | * @param {number} id The ID of the item you want to remove 107 | * @param {function} callback The callback to fire after saving 108 | */ 109 | Store.prototype.remove = function (id, callback) { 110 | var todos = JSON.parse(localStorage.getItem(this._dbName)); 111 | 112 | for (var i = 0; i < todos.length; i++) { 113 | if (todos[i].id == id) { 114 | todos.splice(i, 1); 115 | break; 116 | } 117 | } 118 | 119 | localStorage.setItem(this._dbName, JSON.stringify(todos)); 120 | callback.call(this, todos); 121 | }; 122 | 123 | /** 124 | * Will drop all storage and start fresh 125 | * 126 | * @param {function} callback The callback to fire after dropping the data 127 | */ 128 | Store.prototype.drop = function (callback) { 129 | var todos = []; 130 | localStorage.setItem(this._dbName, JSON.stringify(todos)); 131 | callback.call(this, todos); 132 | }; 133 | 134 | // Export to window 135 | window.app = window.app || {}; 136 | window.app.Store = Store; 137 | })(window); 138 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/template.js: -------------------------------------------------------------------------------- 1 | /*jshint laxbreak:true */ 2 | (function (window) { 3 | 'use strict'; 4 | 5 | var htmlEscapes = { 6 | '&': '&', 7 | '<': '<', 8 | '>': '>', 9 | '"': '"', 10 | '\'': ''', 11 | '`': '`' 12 | }; 13 | 14 | var escapeHtmlChar = function (chr) { 15 | return htmlEscapes[chr]; 16 | }; 17 | 18 | var reUnescapedHtml = /[&<>"'`]/g; 19 | var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source); 20 | 21 | var escape = function (string) { 22 | return (string && reHasUnescapedHtml.test(string)) 23 | ? string.replace(reUnescapedHtml, escapeHtmlChar) 24 | : string; 25 | }; 26 | 27 | /** 28 | * Sets up defaults for all the Template methods such as a default template 29 | * 30 | * @constructor 31 | */ 32 | function Template() { 33 | this.defaultTemplate 34 | = '
  • ' 35 | + '
    ' 36 | + '' 37 | + '' 38 | + '' 39 | + '
    ' 40 | + '
  • '; 41 | } 42 | 43 | /** 44 | * Creates an
  • HTML string and returns it for placement in your app. 45 | * 46 | * NOTE: In real life you should be using a templating engine such as Mustache 47 | * or Handlebars, however, this is a vanilla JS example. 48 | * 49 | * @param {object} data The object containing keys you want to find in the 50 | * template to replace. 51 | * @returns {string} HTML String of an
  • element 52 | * 53 | * @example 54 | * view.show({ 55 | * id: 1, 56 | * title: "Hello World", 57 | * completed: 0, 58 | * }); 59 | */ 60 | Template.prototype.show = function (data) { 61 | var i, l; 62 | var view = ''; 63 | 64 | for (i = 0, l = data.length; i < l; i++) { 65 | var template = this.defaultTemplate; 66 | var completed = ''; 67 | var checked = ''; 68 | 69 | if (data[i].completed) { 70 | completed = 'completed'; 71 | checked = 'checked'; 72 | } 73 | 74 | template = template.replace('{{id}}', data[i].id); 75 | template = template.replace('{{title}}', escape(data[i].title)); 76 | template = template.replace('{{completed}}', completed); 77 | template = template.replace('{{checked}}', checked); 78 | 79 | view = view + template; 80 | } 81 | 82 | return view; 83 | }; 84 | 85 | /** 86 | * Displays a counter of how many to dos are left to complete 87 | * 88 | * @param {number} activeTodos The number of active todos. 89 | * @returns {string} String containing the count 90 | */ 91 | Template.prototype.itemCounter = function (activeTodos) { 92 | var plural = activeTodos === 1 ? '' : 's'; 93 | 94 | return '' + activeTodos + ' item' + plural + ' left'; 95 | }; 96 | 97 | /** 98 | * Updates the text within the "Clear completed" button 99 | * 100 | * @param {[type]} completedTodos The number of completed todos. 101 | * @returns {string} String containing the count 102 | */ 103 | Template.prototype.clearCompletedButton = function (completedTodos) { 104 | if (completedTodos > 0) { 105 | return 'Clear completed'; 106 | } else { 107 | return ''; 108 | } 109 | }; 110 | 111 | // Export to window 112 | window.app = window.app || {}; 113 | window.app.Template = Template; 114 | })(window); 115 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/js/view.js: -------------------------------------------------------------------------------- 1 | /*global qs, qsa, $on, $parent, $delegate */ 2 | 3 | (function (window) { 4 | 'use strict'; 5 | 6 | /** 7 | * View that abstracts away the browser's DOM completely. 8 | * It has two simple entry points: 9 | * 10 | * - bind(eventName, handler) 11 | * Takes a todo application event and registers the handler 12 | * - render(command, parameterObject) 13 | * Renders the given command with the options 14 | */ 15 | function View(template) { 16 | this.template = template; 17 | 18 | this.ENTER_KEY = 13; 19 | this.ESCAPE_KEY = 27; 20 | 21 | this.$todoList = qs('.todo-list'); 22 | this.$todoItemCounter = qs('.todo-count'); 23 | this.$clearCompleted = qs('.clear-completed'); 24 | this.$main = qs('.main'); 25 | this.$footer = qs('.footer'); 26 | this.$toggleAll = qs('.toggle-all'); 27 | this.$newTodo = qs('.new-todo'); 28 | } 29 | 30 | View.prototype._removeItem = function (id) { 31 | var elem = qs('[data-id="' + id + '"]'); 32 | 33 | if (elem) { 34 | this.$todoList.removeChild(elem); 35 | } 36 | }; 37 | 38 | View.prototype._clearCompletedButton = function (completedCount, visible) { 39 | this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount); 40 | this.$clearCompleted.style.display = visible ? 'block' : 'none'; 41 | }; 42 | 43 | View.prototype._setFilter = function (currentPage) { 44 | qs('.filters .selected').className = ''; 45 | qs('.filters [href="#/' + currentPage + '"]').className = 'selected'; 46 | }; 47 | 48 | View.prototype._elementComplete = function (id, completed) { 49 | var listItem = qs('[data-id="' + id + '"]'); 50 | 51 | if (!listItem) { 52 | return; 53 | } 54 | 55 | listItem.className = completed ? 'completed' : ''; 56 | 57 | // In case it was toggled from an event and not by clicking the checkbox 58 | qs('input', listItem).checked = completed; 59 | }; 60 | 61 | View.prototype._editItem = function (id, title) { 62 | var listItem = qs('[data-id="' + id + '"]'); 63 | 64 | if (!listItem) { 65 | return; 66 | } 67 | 68 | listItem.className = listItem.className + ' editing'; 69 | 70 | var input = document.createElement('input'); 71 | input.className = 'edit'; 72 | 73 | listItem.appendChild(input); 74 | input.focus(); 75 | input.value = title; 76 | }; 77 | 78 | View.prototype._editItemDone = function (id, title) { 79 | var listItem = qs('[data-id="' + id + '"]'); 80 | 81 | if (!listItem) { 82 | return; 83 | } 84 | 85 | var input = qs('input.edit', listItem); 86 | listItem.removeChild(input); 87 | 88 | listItem.className = listItem.className.replace('editing', ''); 89 | 90 | qsa('label', listItem).forEach(function (label) { 91 | label.textContent = title; 92 | }); 93 | }; 94 | 95 | View.prototype.render = function (viewCmd, parameter) { 96 | var self = this; 97 | var viewCommands = { 98 | showEntries: function () { 99 | self.$todoList.innerHTML = self.template.show(parameter); 100 | }, 101 | removeItem: function () { 102 | self._removeItem(parameter); 103 | }, 104 | updateElementCount: function () { 105 | self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter); 106 | }, 107 | clearCompletedButton: function () { 108 | self._clearCompletedButton(parameter.completed, parameter.visible); 109 | }, 110 | contentBlockVisibility: function () { 111 | self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none'; 112 | }, 113 | toggleAll: function () { 114 | self.$toggleAll.checked = parameter.checked; 115 | }, 116 | setFilter: function () { 117 | self._setFilter(parameter); 118 | }, 119 | clearNewTodo: function () { 120 | self.$newTodo.value = ''; 121 | }, 122 | elementComplete: function () { 123 | self._elementComplete(parameter.id, parameter.completed); 124 | }, 125 | editItem: function () { 126 | self._editItem(parameter.id, parameter.title); 127 | }, 128 | editItemDone: function () { 129 | self._editItemDone(parameter.id, parameter.title); 130 | } 131 | }; 132 | 133 | viewCommands[viewCmd](); 134 | }; 135 | 136 | View.prototype._itemId = function (element) { 137 | var li = $parent(element, 'li'); 138 | return parseInt(li.dataset.id, 10); 139 | }; 140 | 141 | View.prototype._bindItemEditDone = function (handler) { 142 | var self = this; 143 | $delegate(self.$todoList, 'li .edit', 'blur', function () { 144 | if (!this.dataset.iscanceled) { 145 | handler({ 146 | id: self._itemId(this), 147 | title: this.value 148 | }); 149 | } 150 | }); 151 | 152 | $delegate(self.$todoList, 'li .edit', 'keypress', function (event) { 153 | if (event.keyCode === self.ENTER_KEY) { 154 | // Remove the cursor from the input when you hit enter just like if it 155 | // were a real form 156 | this.blur(); 157 | } 158 | }); 159 | }; 160 | 161 | View.prototype._bindItemEditCancel = function (handler) { 162 | var self = this; 163 | $delegate(self.$todoList, 'li .edit', 'keyup', function (event) { 164 | if (event.keyCode === self.ESCAPE_KEY) { 165 | this.dataset.iscanceled = true; 166 | this.blur(); 167 | 168 | handler({id: self._itemId(this)}); 169 | } 170 | }); 171 | }; 172 | 173 | View.prototype.bind = function (event, handler) { 174 | var self = this; 175 | if (event === 'newTodo') { 176 | $on(self.$newTodo, 'change', function () { 177 | handler(self.$newTodo.value); 178 | }); 179 | 180 | } else if (event === 'removeCompleted') { 181 | $on(self.$clearCompleted, 'click', function () { 182 | handler(); 183 | }); 184 | 185 | } else if (event === 'toggleAll') { 186 | $on(self.$toggleAll, 'click', function () { 187 | handler({completed: this.checked}); 188 | }); 189 | 190 | } else if (event === 'itemEdit') { 191 | $delegate(self.$todoList, 'li label', 'dblclick', function () { 192 | handler({id: self._itemId(this)}); 193 | }); 194 | 195 | } else if (event === 'itemRemove') { 196 | $delegate(self.$todoList, '.destroy', 'click', function () { 197 | handler({id: self._itemId(this)}); 198 | }); 199 | 200 | } else if (event === 'itemToggle') { 201 | $delegate(self.$todoList, '.toggle', 'click', function () { 202 | handler({ 203 | id: self._itemId(this), 204 | completed: this.checked 205 | }); 206 | }); 207 | 208 | } else if (event === 'itemEditDone') { 209 | self._bindItemEditDone(handler); 210 | 211 | } else if (event === 'itemEditCancel') { 212 | self._bindItemEditCancel(handler); 213 | } 214 | }; 215 | 216 | // Export to window 217 | window.app = window.app || {}; 218 | window.app.View = View; 219 | }(window)); 220 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/node_modules/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | min-width: 230px; 29 | max-width: 550px; 30 | margin: 0 auto; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | font-weight: 300; 34 | } 35 | 36 | :focus { 37 | outline: 0; 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | .todoapp { 45 | background: #fff; 46 | margin: 130px 0 40px 0; 47 | position: relative; 48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todoapp input::-webkit-input-placeholder { 53 | font-style: italic; 54 | font-weight: 300; 55 | color: #e6e6e6; 56 | } 57 | 58 | .todoapp input::-moz-placeholder { 59 | font-style: italic; 60 | font-weight: 300; 61 | color: #e6e6e6; 62 | } 63 | 64 | .todoapp input::input-placeholder { 65 | font-style: italic; 66 | font-weight: 300; 67 | color: #e6e6e6; 68 | } 69 | 70 | .todoapp h1 { 71 | position: absolute; 72 | top: -155px; 73 | width: 100%; 74 | font-size: 100px; 75 | font-weight: 100; 76 | text-align: center; 77 | color: rgba(175, 47, 47, 0.15); 78 | -webkit-text-rendering: optimizeLegibility; 79 | -moz-text-rendering: optimizeLegibility; 80 | text-rendering: optimizeLegibility; 81 | } 82 | 83 | .new-todo, 84 | .edit { 85 | position: relative; 86 | margin: 0; 87 | width: 100%; 88 | font-size: 24px; 89 | font-family: inherit; 90 | font-weight: inherit; 91 | line-height: 1.4em; 92 | border: 0; 93 | color: inherit; 94 | padding: 6px; 95 | border: 1px solid #999; 96 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 97 | box-sizing: border-box; 98 | -webkit-font-smoothing: antialiased; 99 | -moz-osx-font-smoothing: grayscale; 100 | } 101 | 102 | .new-todo { 103 | padding: 16px 16px 16px 60px; 104 | border: none; 105 | background: rgba(0, 0, 0, 0.003); 106 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 107 | } 108 | 109 | .main { 110 | position: relative; 111 | z-index: 2; 112 | border-top: 1px solid #e6e6e6; 113 | } 114 | 115 | .toggle-all { 116 | width: 1px; 117 | height: 1px; 118 | border: none; /* Mobile Safari */ 119 | opacity: 0; 120 | position: absolute; 121 | right: 100%; 122 | bottom: 100%; 123 | } 124 | 125 | .toggle-all + label { 126 | width: 60px; 127 | height: 34px; 128 | font-size: 0; 129 | position: absolute; 130 | top: -52px; 131 | left: -13px; 132 | -webkit-transform: rotate(90deg); 133 | transform: rotate(90deg); 134 | } 135 | 136 | .toggle-all + label:before { 137 | content: '❯'; 138 | font-size: 22px; 139 | color: #e6e6e6; 140 | padding: 10px 27px 10px 27px; 141 | } 142 | 143 | .toggle-all:checked + label:before { 144 | color: #737373; 145 | } 146 | 147 | .todo-list { 148 | margin: 0; 149 | padding: 0; 150 | list-style: none; 151 | } 152 | 153 | .todo-list li { 154 | position: relative; 155 | font-size: 24px; 156 | border-bottom: 1px solid #ededed; 157 | } 158 | 159 | .todo-list li:last-child { 160 | border-bottom: none; 161 | } 162 | 163 | .todo-list li.editing { 164 | border-bottom: none; 165 | padding: 0; 166 | } 167 | 168 | .todo-list li.editing .edit { 169 | display: block; 170 | width: 506px; 171 | padding: 12px 16px; 172 | margin: 0 0 0 43px; 173 | } 174 | 175 | .todo-list li.editing .view { 176 | display: none; 177 | } 178 | 179 | .todo-list li .toggle { 180 | text-align: center; 181 | width: 40px; 182 | /* auto, since non-WebKit browsers doesn't support input styling */ 183 | height: auto; 184 | position: absolute; 185 | top: 0; 186 | bottom: 0; 187 | margin: auto 0; 188 | border: none; /* Mobile Safari */ 189 | -webkit-appearance: none; 190 | appearance: none; 191 | } 192 | 193 | .todo-list li .toggle { 194 | opacity: 0; 195 | } 196 | 197 | .todo-list li .toggle + label { 198 | /* 199 | Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 200 | IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ 201 | */ 202 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); 203 | background-repeat: no-repeat; 204 | background-position: center left; 205 | } 206 | 207 | .todo-list li .toggle:checked + label { 208 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); 209 | } 210 | 211 | .todo-list li label { 212 | word-break: break-all; 213 | padding: 15px 15px 15px 60px; 214 | display: block; 215 | line-height: 1.2; 216 | transition: color 0.4s; 217 | } 218 | 219 | .todo-list li.completed label { 220 | color: #d9d9d9; 221 | text-decoration: line-through; 222 | } 223 | 224 | .todo-list li .destroy { 225 | display: none; 226 | position: absolute; 227 | top: 0; 228 | right: 10px; 229 | bottom: 0; 230 | width: 40px; 231 | height: 40px; 232 | margin: auto 0; 233 | font-size: 30px; 234 | color: #cc9a9a; 235 | margin-bottom: 11px; 236 | transition: color 0.2s ease-out; 237 | } 238 | 239 | .todo-list li .destroy:hover { 240 | color: #af5b5e; 241 | } 242 | 243 | .todo-list li .destroy:after { 244 | content: '×'; 245 | } 246 | 247 | .todo-list li:hover .destroy { 248 | display: block; 249 | } 250 | 251 | .todo-list li .edit { 252 | display: none; 253 | } 254 | 255 | .todo-list li.editing:last-child { 256 | margin-bottom: -1px; 257 | } 258 | 259 | .footer { 260 | color: #777; 261 | padding: 10px 15px; 262 | height: 20px; 263 | text-align: center; 264 | border-top: 1px solid #e6e6e6; 265 | } 266 | 267 | .footer:before { 268 | content: ''; 269 | position: absolute; 270 | right: 0; 271 | bottom: 0; 272 | left: 0; 273 | height: 50px; 274 | overflow: hidden; 275 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 276 | 0 8px 0 -3px #f6f6f6, 277 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 278 | 0 16px 0 -6px #f6f6f6, 279 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 280 | } 281 | 282 | .todo-count { 283 | float: left; 284 | text-align: left; 285 | } 286 | 287 | .todo-count strong { 288 | font-weight: 300; 289 | } 290 | 291 | .filters { 292 | margin: 0; 293 | padding: 0; 294 | list-style: none; 295 | position: absolute; 296 | right: 0; 297 | left: 0; 298 | } 299 | 300 | .filters li { 301 | display: inline; 302 | } 303 | 304 | .filters li a { 305 | color: inherit; 306 | margin: 3px; 307 | padding: 3px 7px; 308 | text-decoration: none; 309 | border: 1px solid transparent; 310 | border-radius: 3px; 311 | } 312 | 313 | .filters li a:hover { 314 | border-color: rgba(175, 47, 47, 0.1); 315 | } 316 | 317 | .filters li a.selected { 318 | border-color: rgba(175, 47, 47, 0.2); 319 | } 320 | 321 | .clear-completed, 322 | html .clear-completed:active { 323 | float: right; 324 | position: relative; 325 | line-height: 20px; 326 | text-decoration: none; 327 | cursor: pointer; 328 | } 329 | 330 | .clear-completed:hover { 331 | text-decoration: underline; 332 | } 333 | 334 | .info { 335 | margin: 65px auto 0; 336 | color: #bfbfbf; 337 | font-size: 10px; 338 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 339 | text-align: center; 340 | } 341 | 342 | .info p { 343 | line-height: 1; 344 | } 345 | 346 | .info a { 347 | color: inherit; 348 | text-decoration: none; 349 | font-weight: 400; 350 | } 351 | 352 | .info a:hover { 353 | text-decoration: underline; 354 | } 355 | 356 | /* 357 | Hack to remove background from Mobile Safari. 358 | Can't use it globally since it destroys checkboxes in Firefox 359 | */ 360 | @media screen and (-webkit-min-device-pixel-ratio:0) { 361 | .toggle-all, 362 | .todo-list li .toggle { 363 | background: none; 364 | } 365 | 366 | .todo-list li .toggle { 367 | height: 40px; 368 | } 369 | } 370 | 371 | @media (max-width: 430px) { 372 | .footer { 373 | height: 50px; 374 | } 375 | 376 | .filters { 377 | bottom: 10px; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | }; 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-common": "^1.0.1", 5 | "todomvc-app-css": "^2.0.1" 6 | }, 7 | "devDependencies": { 8 | "jasmine-core": "^2.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/readme.md: -------------------------------------------------------------------------------- 1 | # Vanilla JavaScript TodoMVC Example 2 | 3 | > JavaScript® (often shortened to JS) is a lightweight, interpreted, object-oriented language with first-class functions, most known as the scripting language for Web pages, but used in many non-browser environments as well such as node.js or Apache CouchDB. 4 | 5 | > _[JavaScript - developer.mozilla.org](http://developer.mozilla.org/en-US/docs/JavaScript) 6 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/test/ControllerSpec.js: -------------------------------------------------------------------------------- 1 | /*global app, jasmine, describe, it, beforeEach, expect */ 2 | 3 | describe('controller', function () { 4 | 'use strict'; 5 | 6 | var subject, model, view; 7 | 8 | var setUpModel = function (todos) { 9 | model.read.and.callFake(function (query, callback) { 10 | callback = callback || query; 11 | callback(todos); 12 | }); 13 | 14 | model.getCount.and.callFake(function (callback) { 15 | 16 | var todoCounts = { 17 | active: todos.filter(function (todo) { 18 | return !todo.completed; 19 | }).length, 20 | completed: todos.filter(function (todo) { 21 | return !!todo.completed; 22 | }).length, 23 | total: todos.length 24 | }; 25 | 26 | callback(todoCounts); 27 | }); 28 | 29 | model.remove.and.callFake(function (id, callback) { 30 | callback(); 31 | }); 32 | 33 | model.create.and.callFake(function (title, callback) { 34 | callback(); 35 | }); 36 | 37 | model.update.and.callFake(function (id, updateData, callback) { 38 | callback(); 39 | }); 40 | }; 41 | 42 | var createViewStub = function () { 43 | var eventRegistry = {}; 44 | return { 45 | render: jasmine.createSpy('render'), 46 | bind: function (event, handler) { 47 | eventRegistry[event] = handler; 48 | }, 49 | trigger: function (event, parameter) { 50 | eventRegistry[event](parameter); 51 | } 52 | }; 53 | }; 54 | 55 | beforeEach(function () { 56 | model = jasmine.createSpyObj('model', ['read', 'getCount', 'remove', 'create', 'update']); 57 | view = createViewStub(); 58 | subject = new app.Controller(model, view); 59 | }); 60 | 61 | it('should show entries on start-up', function () { 62 | setUpModel([]); 63 | 64 | subject.setView(''); 65 | 66 | expect(view.render).toHaveBeenCalledWith('showEntries', []); 67 | }); 68 | 69 | describe('routing', function () { 70 | 71 | it('should show all entries without a route', function () { 72 | var todo = {title: 'my todo'}; 73 | setUpModel([todo]); 74 | 75 | subject.setView(''); 76 | 77 | expect(view.render).toHaveBeenCalledWith('showEntries', [todo]); 78 | }); 79 | 80 | it('should show all entries without "all" route', function () { 81 | var todo = {title: 'my todo'}; 82 | setUpModel([todo]); 83 | 84 | subject.setView('#/'); 85 | 86 | expect(view.render).toHaveBeenCalledWith('showEntries', [todo]); 87 | }); 88 | 89 | it('should show active entries', function () { 90 | var todo = {title: 'my todo', completed: false}; 91 | setUpModel([todo]); 92 | 93 | subject.setView('#/active'); 94 | 95 | expect(model.read).toHaveBeenCalledWith({completed: false}, jasmine.any(Function)); 96 | expect(view.render).toHaveBeenCalledWith('showEntries', [todo]); 97 | }); 98 | 99 | it('should show completed entries', function () { 100 | var todo = {title: 'my todo', completed: true}; 101 | setUpModel([todo]); 102 | 103 | subject.setView('#/completed'); 104 | 105 | expect(model.read).toHaveBeenCalledWith({completed: true}, jasmine.any(Function)); 106 | expect(view.render).toHaveBeenCalledWith('showEntries', [todo]); 107 | }); 108 | }); 109 | 110 | it('should show the content block when todos exists', function () { 111 | setUpModel([{title: 'my todo', completed: true}]); 112 | 113 | subject.setView(''); 114 | 115 | expect(view.render).toHaveBeenCalledWith('contentBlockVisibility', { 116 | visible: true 117 | }); 118 | }); 119 | 120 | it('should hide the content block when no todos exists', function () { 121 | setUpModel([]); 122 | 123 | subject.setView(''); 124 | 125 | expect(view.render).toHaveBeenCalledWith('contentBlockVisibility', { 126 | visible: false 127 | }); 128 | }); 129 | 130 | it('should check the toggle all button, if all todos are completed', function () { 131 | setUpModel([{title: 'my todo', completed: true}]); 132 | 133 | subject.setView(''); 134 | 135 | expect(view.render).toHaveBeenCalledWith('toggleAll', { 136 | checked: true 137 | }); 138 | }); 139 | 140 | it('should set the "clear completed" button', function () { 141 | var todo = {id: 42, title: 'my todo', completed: true}; 142 | setUpModel([todo]); 143 | 144 | subject.setView(''); 145 | 146 | expect(view.render).toHaveBeenCalledWith('clearCompletedButton', { 147 | completed: 1, 148 | visible: true 149 | }); 150 | }); 151 | 152 | it('should highlight "All" filter by default', function () { 153 | setUpModel([]); 154 | 155 | subject.setView(''); 156 | 157 | expect(view.render).toHaveBeenCalledWith('setFilter', ''); 158 | }); 159 | 160 | it('should highlight "Active" filter when switching to active view', function () { 161 | setUpModel([]); 162 | 163 | subject.setView('#/active'); 164 | 165 | expect(view.render).toHaveBeenCalledWith('setFilter', 'active'); 166 | }); 167 | 168 | describe('toggle all', function () { 169 | it('should toggle all todos to completed', function () { 170 | var todos = [{ 171 | id: 42, 172 | title: 'my todo', 173 | completed: false 174 | }, { 175 | id: 21, 176 | title: 'another todo', 177 | completed: false 178 | }]; 179 | 180 | setUpModel(todos); 181 | subject.setView(''); 182 | 183 | view.trigger('toggleAll', {completed: true}); 184 | 185 | expect(model.update).toHaveBeenCalledWith(42, {completed: true}, jasmine.any(Function)); 186 | expect(model.update).toHaveBeenCalledWith(21, {completed: true}, jasmine.any(Function)); 187 | }); 188 | 189 | it('should update the view', function () { 190 | var todos = [{ 191 | id: 42, 192 | title: 'my todo', 193 | completed: true 194 | }]; 195 | 196 | setUpModel(todos); 197 | subject.setView(''); 198 | 199 | view.trigger('toggleAll', {completed: false}); 200 | 201 | expect(view.render).toHaveBeenCalledWith('elementComplete', {id: 42, completed: false}); 202 | }); 203 | }); 204 | 205 | describe('new todo', function () { 206 | it('should add a new todo to the model', function () { 207 | setUpModel([]); 208 | 209 | subject.setView(''); 210 | 211 | view.trigger('newTodo', 'a new todo'); 212 | 213 | expect(model.create).toHaveBeenCalledWith('a new todo', jasmine.any(Function)); 214 | }); 215 | 216 | it('should add a new todo to the view', function () { 217 | setUpModel([]); 218 | 219 | subject.setView(''); 220 | 221 | view.render.calls.reset(); 222 | model.read.calls.reset(); 223 | model.read.and.callFake(function (callback) { 224 | callback([{ 225 | title: 'a new todo', 226 | completed: false 227 | }]); 228 | }); 229 | 230 | view.trigger('newTodo', 'a new todo'); 231 | 232 | expect(model.read).toHaveBeenCalled(); 233 | 234 | expect(view.render).toHaveBeenCalledWith('showEntries', [{ 235 | title: 'a new todo', 236 | completed: false 237 | }]); 238 | }); 239 | 240 | it('should clear the input field when a new todo is added', function () { 241 | setUpModel([]); 242 | 243 | subject.setView(''); 244 | 245 | view.trigger('newTodo', 'a new todo'); 246 | 247 | expect(view.render).toHaveBeenCalledWith('clearNewTodo'); 248 | }); 249 | }); 250 | 251 | describe('element removal', function () { 252 | it('should remove an entry from the model', function () { 253 | var todo = {id: 42, title: 'my todo', completed: true}; 254 | setUpModel([todo]); 255 | 256 | subject.setView(''); 257 | view.trigger('itemRemove', {id: 42}); 258 | 259 | expect(model.remove).toHaveBeenCalledWith(42, jasmine.any(Function)); 260 | }); 261 | 262 | it('should remove an entry from the view', function () { 263 | var todo = {id: 42, title: 'my todo', completed: true}; 264 | setUpModel([todo]); 265 | 266 | subject.setView(''); 267 | view.trigger('itemRemove', {id: 42}); 268 | 269 | expect(view.render).toHaveBeenCalledWith('removeItem', 42); 270 | }); 271 | 272 | it('should update the element count', function () { 273 | var todo = {id: 42, title: 'my todo', completed: true}; 274 | setUpModel([todo]); 275 | 276 | subject.setView(''); 277 | view.trigger('itemRemove', {id: 42}); 278 | 279 | expect(view.render).toHaveBeenCalledWith('updateElementCount', 0); 280 | }); 281 | }); 282 | 283 | describe('remove completed', function () { 284 | it('should remove a completed entry from the model', function () { 285 | var todo = {id: 42, title: 'my todo', completed: true}; 286 | setUpModel([todo]); 287 | 288 | subject.setView(''); 289 | view.trigger('removeCompleted'); 290 | 291 | expect(model.read).toHaveBeenCalledWith({completed: true}, jasmine.any(Function)); 292 | expect(model.remove).toHaveBeenCalledWith(42, jasmine.any(Function)); 293 | }); 294 | 295 | it('should remove a completed entry from the view', function () { 296 | var todo = {id: 42, title: 'my todo', completed: true}; 297 | setUpModel([todo]); 298 | 299 | subject.setView(''); 300 | view.trigger('removeCompleted'); 301 | 302 | expect(view.render).toHaveBeenCalledWith('removeItem', 42); 303 | }); 304 | }); 305 | 306 | describe('element complete toggle', function () { 307 | it('should update the model', function () { 308 | var todo = {id: 21, title: 'my todo', completed: false}; 309 | setUpModel([todo]); 310 | subject.setView(''); 311 | 312 | view.trigger('itemToggle', {id: 21, completed: true}); 313 | 314 | expect(model.update).toHaveBeenCalledWith(21, {completed: true}, jasmine.any(Function)); 315 | }); 316 | 317 | it('should update the view', function () { 318 | var todo = {id: 42, title: 'my todo', completed: true}; 319 | setUpModel([todo]); 320 | subject.setView(''); 321 | 322 | view.trigger('itemToggle', {id: 42, completed: false}); 323 | 324 | expect(view.render).toHaveBeenCalledWith('elementComplete', {id: 42, completed: false}); 325 | }); 326 | }); 327 | 328 | describe('edit item', function () { 329 | it('should switch to edit mode', function () { 330 | var todo = {id: 21, title: 'my todo', completed: false}; 331 | setUpModel([todo]); 332 | 333 | subject.setView(''); 334 | 335 | view.trigger('itemEdit', {id: 21}); 336 | 337 | expect(view.render).toHaveBeenCalledWith('editItem', {id: 21, title: 'my todo'}); 338 | }); 339 | 340 | it('should leave edit mode on done', function () { 341 | var todo = {id: 21, title: 'my todo', completed: false}; 342 | setUpModel([todo]); 343 | 344 | subject.setView(''); 345 | 346 | view.trigger('itemEditDone', {id: 21, title: 'new title'}); 347 | 348 | expect(view.render).toHaveBeenCalledWith('editItemDone', {id: 21, title: 'new title'}); 349 | }); 350 | 351 | it('should persist the changes on done', function () { 352 | var todo = {id: 21, title: 'my todo', completed: false}; 353 | setUpModel([todo]); 354 | 355 | subject.setView(''); 356 | 357 | view.trigger('itemEditDone', {id: 21, title: 'new title'}); 358 | 359 | expect(model.update).toHaveBeenCalledWith(21, {title: 'new title'}, jasmine.any(Function)); 360 | }); 361 | 362 | it('should remove the element from the model when persisting an empty title', function () { 363 | var todo = {id: 21, title: 'my todo', completed: false}; 364 | setUpModel([todo]); 365 | 366 | subject.setView(''); 367 | 368 | view.trigger('itemEditDone', {id: 21, title: ''}); 369 | 370 | expect(model.remove).toHaveBeenCalledWith(21, jasmine.any(Function)); 371 | }); 372 | 373 | it('should remove the element from the view when persisting an empty title', function () { 374 | var todo = {id: 21, title: 'my todo', completed: false}; 375 | setUpModel([todo]); 376 | 377 | subject.setView(''); 378 | 379 | view.trigger('itemEditDone', {id: 21, title: ''}); 380 | 381 | expect(view.render).toHaveBeenCalledWith('removeItem', 21); 382 | }); 383 | 384 | it('should leave edit mode on cancel', function () { 385 | var todo = {id: 21, title: 'my todo', completed: false}; 386 | setUpModel([todo]); 387 | 388 | subject.setView(''); 389 | 390 | view.trigger('itemEditCancel', {id: 21}); 391 | 392 | expect(view.render).toHaveBeenCalledWith('editItemDone', {id: 21, title: 'my todo'}); 393 | }); 394 | 395 | it('should not persist the changes on cancel', function () { 396 | var todo = {id: 21, title: 'my todo', completed: false}; 397 | setUpModel([todo]); 398 | 399 | subject.setView(''); 400 | 401 | view.trigger('itemEditCancel', {id: 21}); 402 | 403 | expect(model.update).not.toHaveBeenCalled(); 404 | }); 405 | }); 406 | }); 407 | -------------------------------------------------------------------------------- /third_party/todomvc/vanillajs/test/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jasmine Spec Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /third_party/vue/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Yuxi (Evan) You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": ["es2015", "dom"], /* Specify library files to be included in the compilation: */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | "outDir": "build/", /* Redirect output structure to the directory. */ 14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | // "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | // "strictNullChecks": true, /* Enable strict null checks. */ 25 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 26 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 27 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 28 | 29 | /* Additional Checks */ 30 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 31 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 32 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 33 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 34 | 35 | /* Module Resolution Options */ 36 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 37 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 38 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 39 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 40 | // "typeRoots": [], /* List of folders to include type definitions from. */ 41 | // "types": [], /* Type declaration files to be included in compilation. */ 42 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 43 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 44 | 45 | /* Source Map Options */ 46 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 47 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 48 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 49 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 50 | 51 | /* Experimental Options */ 52 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 53 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 54 | }, 55 | "include": [ 56 | "src/*", 57 | "third_party/*/test.ts" 58 | ] 59 | } 60 | --------------------------------------------------------------------------------