├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── gen-bundles.sh ├── gulpfile.js ├── package.json ├── server ├── server.go └── template │ └── synthesized.html └── src ├── bundle-index.html ├── display-results.js ├── index.html ├── moment ├── app.js ├── bundled-optimized.html ├── bundled-unoptimized.html ├── package.json ├── unbundled.html └── webbundle.html └── three ├── app.js ├── bundled-optimized.html ├── bundled-unoptimized.html ├── package.json ├── unbundled.html └── webbundle.html /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | temp 3 | *.pem 4 | node_modules 5 | package-lock.json 6 | src/*/package-lock.json -------------------------------------------------------------------------------- /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 2017 Google Inc. 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 | # Browser module loading tests 2 | 3 | This sample takes moment.js and three.js, and prepares them for loading in the 4 | browser with ECMAScript modules. 5 | 6 | ## Development 7 | 8 | ### Cloning this repository 9 | 10 | Standard stuff :) Hit the "Clone or download" button in the project's GitHub 11 | landing page and go from there. 12 | 13 | ### Installing dependencies 14 | 15 | NPM packages need to be installed on the root and both `src/` subdirectories. 16 | 17 | ```sh 18 | npm i 19 | ``` 20 | 21 | Gulp needs to be available globally. You can install it by doing: 22 | 23 | ```sh 24 | npm i -g gulp 25 | ``` 26 | 27 | ### Building and developing 28 | 29 | ```sh 30 | gulp build 31 | ``` 32 | 33 | ### Running the HTTP server 34 | 35 | First add `cert.pem` and `key.pem` files for TLS. If you don't have these, you 36 | can use [simplehttp2server](https://github.com/GoogleChrome/simplehttp2server) 37 | to generate them for you. Place them at the root of the clone. 38 | 39 | Then: 40 | 41 | ```sh 42 | go run server/server.go 43 | ``` 44 | 45 | Or, if you don't have `go` command, you can use the built in HTTP server instead: 46 | 47 | ```sh 48 | gulp serve 49 | ``` 50 | 51 | HTTP server command line options: 52 | - `--http1`: serve over HTTP/1.1 instead of HTTP/2 53 | - `--push`: use HTTP/2 push when serving 54 | - `--preload`: inject `` tags for JS dependencies when serving 55 | 56 | E.g., to serve over HTTP/1.1 with preload enabled: 57 | 58 | ```sh 59 | go run server/server.go --http1 --preload 60 | ``` 61 | 62 | ### Bundled / unbundled tests 63 | 64 | The bundled / unbundled test cases are served at the following URLs: 65 | 66 | * moment.js 67 | * bundled, optimized: https://localhost:44333/moment/bundled-optimized.html 68 | * bundled, unoptimized: https://localhost:44333/moment/bundled-unoptimized.html 69 | * unbundled: https://localhost:44333/moment/unbundled.html 70 | * three.js 71 | * bundled, optimized: https://localhost:44333/three/bundled-optimized.html 72 | * bundled, unoptimized: https://localhost:44333/three/bundled-unoptimized.html 73 | * unbundled: https://localhost:44333/three/unbundled.html 74 | 75 | These tests load the files only once, so the results may be noisy. At the 76 | toplevel test page https://localhost:44333/ you can run the unbundled test cases 77 | repeatedly (25 times) and see the median time. 78 | 79 | ### Synthesized module tree tests 80 | 81 | In addition to the real-world library test cases, this HTTP server provides 82 | a benchmark for artificial module tree shapes. This is served at 83 | https://localhost:44333/synthesized/ and it accepts the following query 84 | parameters: 85 | 86 | - `depth` (default: 5): height of the module dependency tree 87 | - `branch` (default: 2): number of child modules non-leaf modules have 88 | - `delay=n` (optional): sleep n milliseconds in response handler 89 | - `cacheable` (optional): make JavaScript resources cacheable 90 | 91 | E.g., this loads a module whose dependency tree is a perfect binary tree of 92 | depth 10 (2047 modules in total): 93 | https://localhost:44333/synthesized/?depth=10&branch=2 94 | 95 | Note: Currently, --push and --preload options are not supported in synthesized 96 | tests. 97 | 98 | ### [Experimental] WebBundle tests 99 | 100 | [Web Bundle](https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html) 101 | is a file format for encapsulating one or more HTTP resources. It allows 102 | distributing a large number of module scripts as a single HTTP resource. 103 | 104 | You need [WebBundle Go tools](https://github.com/WICG/webpackage/tree/master/go/bundle) 105 | to generate Web Bundles, which can be installed by this command: 106 | ``` 107 | go get -u github.com/WICG/webpackage/go/bundle/cmd/... 108 | ``` 109 | Then, this command will generate Web Bundles for the moment.js / three.js tests: 110 | ``` 111 | ./gen-bundles.sh 112 | ``` 113 | 114 | As of April 2020, Web Bundles support is implemented only in Chromium-based 115 | browsers, behind an experimental feature flag. To enable Web Bundles in Chrome, 116 | turn on `chrome://flags/#web-bundles` flag. 117 | 118 | After enabling the flag, drag and drop `samples-module-loading-comparison.wbn` 119 | into Chrome to open it. An index page will be displayed from which you can 120 | choose a benchmark to run. 121 | 122 | `gen-bundles.sh` also generates `dist/moment/momentjs.wbn` and 123 | `dist/three/threejs.wbn`. They only bundle the module scripts for each test. 124 | These can be used with `dist/{moment,three}/webbundle.html` to test 125 | [Subresource loading with Web Bundles](https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md). 126 | (Note: this is a proposal in very early stage; experimental implementation 127 | is not landed in any browsers as of April 2020.) 128 | -------------------------------------------------------------------------------- /gen-bundles.sh: -------------------------------------------------------------------------------- 1 | # All-in-one WebBundle bundling all files under dist/. 2 | gen-bundle -baseURL https://googlechromelabs.github.io/samples-module-loading-comparison/ \ 3 | -dir dist \ 4 | -primaryURL https://googlechromelabs.github.io/samples-module-loading-comparison/bundle-index.html \ 5 | -o samples-module-loading-comparison.wbn 6 | 7 | # Moment.js subresource WebBundle 8 | gen-bundle -baseURL https://moment.js/ \ 9 | -dir dist/moment/unbundled/ \ 10 | -primaryURL https://moment.js/app.js \ 11 | -o dist/moment/momentjs.wbn 12 | 13 | # Three.js subresource WebBundle 14 | gen-bundle -baseURL https://three.js/ \ 15 | -dir dist/three/unbundled/ \ 16 | -primaryURL https://three.js/app.js \ 17 | -o dist/three/threejs.wbn 18 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Module loading benchmark sample. 4 | * Copyright 2017 Google Inc. All rights reserved. 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 | 20 | const path = require('path'); 21 | const fs = require('fs'); 22 | const url = require('url'); 23 | 24 | const gulp = require('gulp'); 25 | const babel = require('gulp-babel'); 26 | const rollup = require('rollup'); 27 | const rollupEach = require('gulp-rollup-each'); 28 | const nodeResolve = require('rollup-plugin-node-resolve'); 29 | const rename = require('gulp-rename'); 30 | 31 | const resolveFrom = require('resolve-from'); 32 | const merge = require('merge-stream'); 33 | 34 | const webpackStream = require('webpack-stream'); 35 | const webpack = require('webpack'); 36 | 37 | const spdy = require('spdy'); 38 | const zlib = require('zlib'); 39 | 40 | const del = require('del'); 41 | const { task, series, parallel } = require('gulp'); 42 | 43 | const projects = ['moment', 'three']; 44 | let files = {}; 45 | const pushFiles = {}; 46 | const cache = {}; 47 | 48 | let preload = false; 49 | let http1 = false; 50 | let push = false; 51 | 52 | 53 | const wrapTaskFn = (fn) => (complete) => { 54 | const result = fn() 55 | if (typeof result === 'undefined') { 56 | complete() 57 | } 58 | return result 59 | } 60 | 61 | const gulpTask = (taskName, dependencies, taskFn) => { 62 | if (typeof dependencies === 'function') { 63 | taskFn = dependencies 64 | dependencies = [] 65 | } 66 | console.log(taskName) 67 | if (!dependencies.length) { 68 | return task(taskName, wrapTaskFn(taskFn)) 69 | } 70 | return task(taskName, series(parallel(...dependencies.map(n => task(n))), wrapTaskFn(taskFn))) 71 | } 72 | 73 | // Rollup plugin for listing all the module files. 74 | function _listModules(project) { 75 | return { 76 | ongenerate(args, rendered) { 77 | files[project] = []; 78 | args.bundle.modules.forEach(module => { 79 | const rel = path.relative(path.join(__dirname, 'src', project), module.id); 80 | files[project].push(rel); 81 | }); 82 | } 83 | }; 84 | } 85 | 86 | // Babel plugin for rewriting imports to browser-loadable relative paths. 87 | function _rewriteImports(project) { 88 | return function(babel) { 89 | const t = babel.types; 90 | const buildRoot = path.join(__dirname, 'src', project); 91 | 92 | return { 93 | visitor: { 94 | ImportDeclaration: function(nodePath, state) { 95 | const fileRoot = path.dirname(state.file.opts.filename); 96 | 97 | const moduleArg = nodePath.node.source; 98 | if (moduleArg && moduleArg.type === 'StringLiteral') { 99 | const source = nodePath.node.source.value; 100 | 101 | let relative = null; 102 | if (source.startsWith('./') || source.startsWith('../')) { 103 | relative = path.relative(fileRoot, resolveFrom(fileRoot, source)); 104 | } else { 105 | relative = path.relative(fileRoot, resolveFrom(buildRoot, source)); 106 | } 107 | 108 | // Special handling for the GLSL files in three.js. 109 | if (relative.endsWith('.glsl')) { 110 | relative = relative.replace('.glsl', '.js'); 111 | } 112 | 113 | if (relative.startsWith('../')) { 114 | nodePath.node.source = t.stringLiteral(relative); 115 | } else { 116 | nodePath.node.source = t.stringLiteral('./' + relative); 117 | } 118 | } 119 | }, 120 | } 121 | }; 122 | } 123 | } 124 | 125 | // Transform three.js GLSL files into JS. 126 | // From three.js rollup.config.js. 127 | function _glsl() { 128 | return { 129 | transform(code, id) { 130 | if (/\.glsl$/.test(id) === false) return; 131 | var transformedCode = 'export default ' + JSON.stringify( 132 | code 133 | .replace( /[ \t]*\/\/.*\n/g, '' ) // remove // 134 | .replace( /[ \t]*\/\*[\s\S]*?\*\//g, '' ) // remove /* */ 135 | .replace( /\n{2,}/g, '\n' ) // # \n+ to \n 136 | ) + ';'; 137 | return { 138 | code: transformedCode, 139 | map: { mappings: '' } 140 | }; 141 | } 142 | }; 143 | } 144 | 145 | // Delete all generated files. 146 | gulpTask('clean', () => del(['dist', 'temp'])); 147 | 148 | // Obtain list of dependency JS files. 149 | gulpTask('scan', () => { 150 | return Promise.all(projects.map(project => { 151 | return rollup.rollup({ 152 | entry: path.join('src', project, 'app.js'), 153 | plugins: [ 154 | nodeResolve(), 155 | _glsl(), 156 | _listModules(project) 157 | ], 158 | }).then(bundle => bundle.generate({ format: 'es' })); 159 | })); 160 | }); 161 | 162 | // Rename GLSL files, transform them, and move them together with the other JS. 163 | // Special handling for the GLSL files in three.js. 164 | gulpTask('glsl', ['scan'], () => { 165 | const tasks = ['three'].map(project => { 166 | const glslFiles = files[project] 167 | .filter(f => /\.glsl$/.test(f)) 168 | .map(f => path.join(__dirname, 'src', project, f)); 169 | return gulp.src(glslFiles, {base: path.join(__dirname, 'src', project)}) 170 | .pipe(rollupEach({ 171 | plugins: [_glsl()] 172 | }, { 173 | format: 'es' 174 | })) 175 | .pipe(rename(path => path.extname = '.js')) 176 | .pipe(gulp.dest(path.join('temp', project, 'unbundled'))); 177 | }); 178 | return merge(...tasks); 179 | }); 180 | 181 | // Copy HTML to all three builds. 182 | gulpTask('html', ['clean'], () => { 183 | return merge(gulp.src('src/**/*.html').pipe(gulp.dest('dist')), 184 | gulp.src('src/*.js').pipe(gulp.dest('dist'))); 185 | }); 186 | 187 | // Create unbundled build. 188 | gulpTask('unbundled', ['glsl', 'html'], () => { 189 | const tasks = projects.map(project => { 190 | const jsFiles = files[project] 191 | .filter(f => /\.js$/.test(f)) 192 | .map(f => path.join(__dirname, 'src', project, f)); 193 | 194 | return gulp.src(jsFiles, {base: path.join(__dirname, 'src', project)}) 195 | .pipe(babel({ 196 | babelrc: false, 197 | plugins: [_rewriteImports(project)] 198 | })) 199 | .pipe(gulp.dest(path.join('temp', project, 'unbundled'))); 200 | }); 201 | return merge(...tasks); 202 | }); 203 | 204 | // Create optimized bundled build. 205 | gulpTask('bundled-optimized', ['unbundled'], () => { 206 | return Promise.all(projects.map(project => { 207 | return rollup.rollup({ 208 | entry: path.join('temp', project, 'unbundled', 'app.js'), 209 | treeshake: true, 210 | }) 211 | .then(bundle => { 212 | bundle.write({ 213 | format: 'iife', 214 | moduleName: 'app', 215 | dest: path.join('temp', project, 'bundled-optimized', 'app.js'), 216 | }); 217 | }); 218 | })); 219 | }); 220 | 221 | // Create unoptimized bundled build. 222 | gulpTask('bundled-unoptimized', ['unbundled'], () => { 223 | const tasks = projects.map(project => { 224 | return gulp.src(path.join('temp', project, 'unbundled', 'app.js'), 225 | {base: path.join('temp', project, 'unbundled')}) 226 | .pipe(webpackStream({ 227 | output: { 228 | filename: 'app.js' 229 | }, 230 | plugins: [ 231 | new webpack.LoaderOptionsPlugin({ 232 | minimize: false, 233 | debug: false 234 | }) 235 | ] 236 | }, webpack)) 237 | .pipe(gulp.dest(path.join('temp', project, 'bundled-unoptimized'))); 238 | }); 239 | return merge(...tasks); 240 | }); 241 | 242 | // Minify all three builds. 243 | gulpTask('minify', ['unbundled', 'bundled-unoptimized', 'bundled-optimized'], () => { 244 | const tasks = projects.map(project => { 245 | return gulp.src(`temp/${project}/**/*.js`, {base: path.join('temp', project)}) 246 | .pipe(babel({ 247 | babelrc: false, 248 | presets: [ 249 | ['minify', { 250 | builtIns: false, 251 | evaluate: false, 252 | mangle: false, 253 | }] 254 | ], 255 | // presets: ['babili'], 256 | })) 257 | .pipe(gulp.dest(path.join('dist', project))); 258 | }); 259 | return merge(...tasks); 260 | }); 261 | 262 | // Meta build task for creating all builds. 263 | // Also generates JSON file with full list of served JS files. 264 | gulpTask('build', ['html', 'minify'], () => { 265 | const jsFiles = {}; 266 | projects.forEach(project => { 267 | jsFiles[project] = []; 268 | files[project].forEach(file => { 269 | // This is a bit hacky, but we need it to work with the three.js custom build. 270 | if (/\.glsl$/.test(file)) { 271 | jsFiles[project].push(file.replace(/\.glsl$/, '.js')); 272 | } else { 273 | jsFiles[project].push(file); 274 | } 275 | }); 276 | }); 277 | fs.writeFileSync(path.join(__dirname, 'dist', 'filelist.json'), JSON.stringify(jsFiles)); 278 | }); 279 | 280 | // Auxiliary method for loading all served content into memory. 281 | function _cacheEverything() { 282 | const filelist = JSON.parse(fs.readFileSync(path.join(__dirname, 'dist', 'filelist.json'))); 283 | 284 | console.log('HTTP server: loading files into memory...'); 285 | projects.forEach(project => { 286 | const jsFiles = filelist[project]; 287 | const unbundledHtml = path.join(project, 'unbundled.html'); 288 | const unbundledJs = path.join(project, 'unbundled', 'app.js'); 289 | 290 | let unbundledHtmlContent = fs.readFileSync(path.join(__dirname, 'dist', unbundledHtml)); 291 | if (preload) { 292 | let unbundledHtmlString = unbundledHtmlContent.toString(); 293 | let links = ''; 294 | jsFiles.slice(0).reverse().forEach(file => { 295 | const relative = path.join(project, 'unbundled', file); 296 | links += ` \n`; 297 | }); 298 | unbundledHtmlString = unbundledHtmlString.replace('', `${links}`); 299 | unbundledHtmlContent = Buffer.from(unbundledHtmlString); 300 | } 301 | cache[unbundledHtml] = zlib.gzipSync(unbundledHtmlContent); 302 | pushFiles[unbundledJs] = []; 303 | jsFiles.forEach(file => { 304 | const relative = path.join(project, 'unbundled', file); 305 | cache[relative] = zlib.gzipSync(fs.readFileSync(path.join(__dirname, 'dist', relative))); 306 | if (relative !== unbundledJs) { 307 | pushFiles[unbundledJs].push(relative); 308 | } 309 | }); 310 | 311 | ['bundled-unoptimized', 'bundled-optimized'].forEach(c => { 312 | const html = path.join(project, `${c}.html`); 313 | const js = path.join(project, c, 'app.js'); 314 | cache[html] = zlib.gzipSync(fs.readFileSync(path.join(__dirname, 'dist', html))); 315 | cache[js] = zlib.gzipSync(fs.readFileSync(path.join(__dirname, 'dist', js))); 316 | }); 317 | }); 318 | cache['index.html'] = zlib.gzipSync(fs.readFileSync(path.join(__dirname, 'dist', 'index.html'))); 319 | cache['display-results.js'] = zlib.gzipSync(fs.readFileSync(path.join(__dirname, 'dist', 'display-results.js'))); 320 | console.log('HTTP server: done loading.'); 321 | } 322 | 323 | // Auxiliary method for handling an HTTP request. 324 | function _onRequest(request, response) { 325 | let url = request.url; 326 | if (url.endsWith('/')) { 327 | url += 'index.html'; 328 | } 329 | url = url.replace(/^\/(r\/\d+\/)?/, ''); // Strip randomized prefix (/r/[0-9]+) 330 | 331 | console.log(`HTTP server: request for ${request.url}`); 332 | if (cache[url]) { 333 | if (push && pushFiles[url] && response.push) { 334 | // Reverse order so that main dependency comes first. 335 | pushFiles[url].slice(0).reverse().forEach(file => { 336 | console.log(`HTTP server: pushing /${file}`); 337 | const pushed = response.push(`/${file}`, { 338 | status: 200, 339 | method: 'GET', 340 | request: { 341 | accept: '*/*' 342 | }, 343 | response: { 344 | 'content-type': 'application/javascript', 345 | 'content-encoding': 'gzip', 346 | 'vary': 'Accept-Encoding' 347 | } 348 | }); 349 | pushed.on('error', err => console.log('HTTP server: push error ', err)); 350 | pushed.end(cache[file]); 351 | }); 352 | } 353 | response.writeHead(200, { 354 | 'content-type': url.endsWith('.js') ? 'application/javascript' : 'text/html', 355 | 'content-encoding' : 'gzip', 356 | 'vary': 'Accept-Encoding' 357 | }); 358 | response.end(cache[url]); 359 | } else { 360 | response.writeHead(404); 361 | response.end(); 362 | } 363 | } 364 | 365 | // Build task for serving generated builds over HTTP. Should be called after a 366 | // successful build. 367 | // Takes the following optional command line parameters: 368 | // * --http1: Serve over HTTP/1.1 instead of HTTP/2. 369 | // * --push: Use HTTP/2 push to push dependencies with the JS entry point. 370 | // * --preload: Add to HTML for all JS dependencies. 371 | gulpTask('serve', () => { 372 | const opts = { 373 | key: fs.readFileSync(path.join(__dirname, 'key.pem')), 374 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')), 375 | }; 376 | 377 | if (process.argv.find(item => item === '--http1')) { 378 | http1 = true; 379 | } 380 | if (process.argv.find(item => item === '--preload')) { 381 | preload = true; 382 | } 383 | if (process.argv.find(item => item === '--push')) { 384 | push = true; 385 | } 386 | 387 | if (http1) { 388 | console.log('HTTP server: running in HTTP 1 mode.'); 389 | opts['spdy'] = { protocols: ['http/1.1', 'http/1.0'] }; 390 | } else { 391 | console.log('HTTP server: running in HTTP 2 mode.'); 392 | opts['spdy'] = { protocols: ['h2', 'http/1.1', 'http/1.0'] }; 393 | } 394 | 395 | if (push && !http1) { 396 | console.log('HTTP server: using HTTP 2 push.'); 397 | } else if (push && http1) { 398 | console.log('HTTP server: no push support on HTTP 1.'); 399 | } 400 | 401 | if (preload) { 402 | console.log('HTTP server: using .'); 403 | } 404 | 405 | _cacheEverything(); 406 | 407 | server = spdy.createServer(opts, _onRequest); 408 | console.log('HTTP server: listening...') 409 | server.listen(44333); 410 | }); 411 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-loading-benchmarks", 3 | "version": "0.1.0", 4 | "description": "Performance measurement for ECMAScript browser module loading.", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "author": "Google", 8 | "scripts": { 9 | "build": "gulp build", 10 | "serve": "gulp serve", 11 | "bundle": "./gen-bundles.sh", 12 | "postinstall": "cd src/moment && npm install && cd ../three && npm install" 13 | }, 14 | "engines": { 15 | "node": ">=12.0.0" 16 | }, 17 | "dependencies": { 18 | "babel-core": "^6.24.1", 19 | "babili": "^0.1.4", 20 | "del": "^3.0.0", 21 | "merge-stream": "^1.0.1", 22 | "resolve-from": "^3.0.0", 23 | "rollup": "^0.42.0", 24 | "rollup-plugin-node-resolve": "^3.0.0", 25 | "spdy": "^3.4.7", 26 | "webpack": "^3.0.0", 27 | "webpack-stream": "^3.2.0" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.13.10", 31 | "babel-preset-minify": "^0.5.1", 32 | "gulp": "^4.0.2", 33 | "gulp-babel": "^8.0.0", 34 | "gulp-rename": "^2.0.0", 35 | "gulp-replace": "^1.0.0", 36 | "gulp-rollup-each": "^3.0.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Module loading benchmark sample. 4 | * Copyright 2017 Google Inc. All rights reserved. 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 | package main 20 | 21 | import ( 22 | "bytes" 23 | "compress/gzip" 24 | "encoding/json" 25 | "flag" 26 | "fmt" 27 | "html/template" 28 | "io" 29 | "io/ioutil" 30 | "log" 31 | "mime" 32 | "net/http" 33 | "net/url" 34 | "path" 35 | "regexp" 36 | "strconv" 37 | "strings" 38 | "time" 39 | ) 40 | 41 | var ( 42 | httpAddrFlag = flag.String("http", ":44333", "Listen address") 43 | preloadFlag = flag.Bool("preload", false, "Add to HTML for all JS dependencies") 44 | modulePreloadFlag = flag.Bool("modulepreload", false, "Add to HTML for all JS dependencies") 45 | pushFlag = flag.Bool("push", false, "Use HTTP/2 push to push dependencies with the JS entry point") 46 | http1Flag = flag.Bool("http1", false, "Serve over HTTP/1.1 instead of HTTP/2") 47 | gzipFlag = flag.Bool("gzip", false, "Use Content-Encoding: gzip") 48 | ) 49 | 50 | var cache = map[string][]byte{} 51 | var pushFiles = map[string][]string{} 52 | var synthesizedTemplate *template.Template 53 | 54 | func main() { 55 | flag.Parse() 56 | 57 | // Load all served content into memory. 58 | err := cacheEverything() 59 | if err != nil { 60 | fmt.Println("error:", err) 61 | return 62 | } 63 | 64 | http.HandleFunc("/", onRequest) 65 | http.HandleFunc("/synthesized/", handleSynthesized) 66 | http.HandleFunc("/synthesized/a.js", handleJs) 67 | 68 | if *http1Flag { 69 | fmt.Printf("Server running at http://localhost%v\n", *httpAddrFlag) 70 | log.Fatal(http.ListenAndServe(*httpAddrFlag, nil)) 71 | } else { 72 | fmt.Printf("Server running at https://localhost%v\n", *httpAddrFlag) 73 | log.Fatal(http.ListenAndServeTLS(*httpAddrFlag, "cert.pem", "key.pem", nil)) 74 | } 75 | } 76 | 77 | func cacheEverything() error { 78 | jsonBytes, err := ioutil.ReadFile(path.Join("dist", "filelist.json")) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | var filelist map[string]interface{} 84 | err = json.Unmarshal(jsonBytes, &filelist) 85 | if err != nil { 86 | return err 87 | } 88 | for project, v := range filelist { 89 | jsfiles := v.([]interface{}) 90 | unbundledHtml := path.Join(project, "unbundled.html") 91 | unbundledJs := path.Join(project, "unbundled", "app.js") 92 | unbundledHtmlContent, err := ioutil.ReadFile(path.Join("dist", unbundledHtml)) 93 | 94 | if *preloadFlag { 95 | links := "" 96 | for i := range jsfiles { 97 | relative := path.Join(project, "unbundled", jsfiles[len(jsfiles)-1-i].(string)) 98 | links += " \n" 99 | } 100 | unbundledHtmlContent = []byte(strings.Replace(string(unbundledHtmlContent), "", links+"", 1)) 101 | } 102 | if *modulePreloadFlag { 103 | links := "" 104 | for i := range jsfiles { 105 | relative := path.Join(project, "unbundled", jsfiles[len(jsfiles)-1-i].(string)) 106 | links += " \n" 107 | } 108 | unbundledHtmlContent = []byte(strings.Replace(string(unbundledHtmlContent), "", links+"", 1)) 109 | } 110 | 111 | cache[unbundledHtml], err = encodeContent(unbundledHtmlContent) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | for i := range jsfiles { 117 | file := jsfiles[len(jsfiles)-1-i] // Iterate in reverse order 118 | relative := path.Join(project, "unbundled", file.(string)) 119 | cache[relative], err = readFileAndEncode(path.Join("dist", relative)) 120 | if err != nil { 121 | return err 122 | } 123 | if relative != unbundledJs { 124 | pushFiles[unbundledJs] = append(pushFiles[unbundledJs], relative) 125 | } 126 | } 127 | 128 | for _, c := range []string{"bundled-unoptimized", "bundled-optimized"} { 129 | html := path.Join(project, c+".html") 130 | js := path.Join(project, c, "app.js") 131 | cache[html], err = readFileAndEncode(path.Join("dist", html)) 132 | if err != nil { 133 | return err 134 | } 135 | cache[js], err = readFileAndEncode(path.Join("dist", js)) 136 | if err != nil { 137 | return err 138 | } 139 | } 140 | } 141 | cache["index.html"], err = readFileAndEncode(path.Join("dist", "index.html")) 142 | cache["display-results.js"], err = readFileAndEncode(path.Join("dist", "display-results.js")) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | synthesizedTemplate = template.Must(template.ParseFiles("server/template/synthesized.html")) 148 | return nil 149 | } 150 | 151 | func readFileAndEncode(path string) ([]byte, error) { 152 | content, err := ioutil.ReadFile(path) 153 | if err != nil { 154 | return nil, err 155 | } 156 | return encodeContent(content) 157 | } 158 | 159 | func encodeContent(content []byte) ([]byte, error) { 160 | if !*gzipFlag { 161 | return content, nil 162 | } 163 | var buf bytes.Buffer 164 | gz := gzip.NewWriter(&buf) 165 | _, err := gz.Write(content) 166 | gz.Close() 167 | return buf.Bytes(), err 168 | } 169 | 170 | var randomizedPrefixRegexp = regexp.MustCompile(`^/(r/\d+/)?`) 171 | 172 | func onRequest(w http.ResponseWriter, r *http.Request) { 173 | upath := r.URL.Path 174 | if upath[len(upath)-1] == '/' { 175 | upath += "index.html" 176 | } 177 | upath = randomizedPrefixRegexp.ReplaceAllLiteralString(upath, "") 178 | 179 | content, ok := cache[upath] 180 | if !ok { 181 | http.ServeFile(w, r, path.Join("dist", r.URL.Path)) 182 | return 183 | } 184 | 185 | pushIfPossible(w, r, upath) 186 | 187 | ctype := mime.TypeByExtension(path.Ext(upath)) 188 | if ctype != "" { 189 | w.Header().Set("Content-type", ctype) 190 | } 191 | if *gzipFlag { 192 | w.Header().Set("Content-Encoding", "gzip") 193 | } else if strings.HasPrefix(r.URL.Path, "/r/") { 194 | // Add randomized comment so that it won't hit content-based cache. 195 | io.WriteString(w, "// "+r.URL.Path+"\n") 196 | } 197 | w.Write(content) 198 | } 199 | 200 | func pushIfPossible(w http.ResponseWriter, r *http.Request, path string) { 201 | if !*pushFlag { 202 | return 203 | } 204 | pushes, ok := pushFiles[path] 205 | if !ok { 206 | return 207 | } 208 | pusher, ok := w.(http.Pusher) 209 | if !ok { 210 | return 211 | } 212 | options := &http.PushOptions{ 213 | Header: http.Header{ 214 | "Accept-Encoding": r.Header["Accept-Encoding"], 215 | }, 216 | } 217 | for _, file := range pushes { 218 | if err := pusher.Push("/"+file, options); err != nil { 219 | fmt.Printf("Failed to push %s: %v\n", file, err) 220 | } 221 | } 222 | } 223 | 224 | func handleSynthesized(w http.ResponseWriter, r *http.Request) { 225 | query, err := url.ParseQuery(r.URL.RawQuery) 226 | if err != nil { 227 | http.Error(w, err.Error(), http.StatusInternalServerError) 228 | return 229 | } 230 | 231 | // TODO: Support -preload and -push in synthesized tests 232 | 233 | scriptUrl := "a.js" 234 | 235 | if len(query["depth"]) > 0 { 236 | scriptUrl += "?depth=" + query["depth"][0] 237 | } else { 238 | scriptUrl += "?depth=5" 239 | } 240 | 241 | if len(query["branch"]) > 0 { 242 | scriptUrl += "&branch=" + query["branch"][0] 243 | } 244 | 245 | if len(query["cacheable"]) > 0 { 246 | scriptUrl += "&cacheable" 247 | } 248 | 249 | if len(query["delay"]) > 0 { 250 | scriptUrl += "&delay=" + query["delay"][0] 251 | } 252 | 253 | w.Header().Set("Content-Type", "text/html") 254 | 255 | synthesizedTemplate.Execute(w, map[string]string{"ScriptUrl": scriptUrl}) 256 | } 257 | 258 | // Query parameters: 259 | // cacheable (optional) - add Cache-Control: max-age=86400 260 | // delay=n (optional) - sleep n milliseconds in response handler 261 | func handleJs(w http.ResponseWriter, r *http.Request) { 262 | const header = ` 263 | // Bogus script 264 | (function() { 265 | function notActuallyCalled(arg) { 266 | return 'This string not actually used: ' + arg; 267 | } 268 | ` 269 | const footer = ` 270 | })(); 271 | ` 272 | query, err := url.ParseQuery(r.URL.RawQuery) 273 | if err != nil { 274 | http.Error(w, err.Error(), http.StatusInternalServerError) 275 | return 276 | } 277 | 278 | w.Header().Set("Content-Type", "application/javascript") 279 | if len(query["cacheable"]) > 0 { 280 | w.Header().Set("Cache-Control", "max-age=86400") 281 | } 282 | 283 | if len(query["depth"]) > 0 { 284 | depth, err := strconv.Atoi(query["depth"][0]) 285 | if err != nil { 286 | http.Error(w, err.Error(), http.StatusInternalServerError) 287 | } 288 | if depth > 0 { 289 | query.Set("depth", strconv.Itoa(depth-1)) 290 | 291 | branch := 2 292 | if len(query["branch"]) > 0 { 293 | branch, err = strconv.Atoi(query["branch"][0]) 294 | if err != nil { 295 | http.Error(w, err.Error(), http.StatusInternalServerError) 296 | } 297 | } 298 | 299 | params := query.Encode() 300 | if branch == 1 { 301 | fmt.Fprintf(w, "import {} from './a.js?%v';\n", params) 302 | } else { 303 | for i := 0; i < branch; i++ { 304 | fmt.Fprintf(w, "import {} from './a.js?%v&n=%d';\n", params, i) 305 | } 306 | } 307 | } 308 | } 309 | 310 | fmt.Fprint(w, header) 311 | 312 | for i := 0; i < 10; i++ { 313 | // just 100 bytes 314 | fmt.Fprintf(w, ` 315 | function fib%d(n) { 316 | if (n < 2) 317 | return 1; 318 | return fib%d(n-2) + fib%d(n-1); 319 | } 320 | `, i, i, i) 321 | } 322 | fmt.Fprint(w, footer) 323 | 324 | if len(query["delay"]) > 0 { 325 | delay, err := strconv.Atoi(query["delay"][0]) 326 | if err != nil { 327 | http.Error(w, err.Error(), http.StatusInternalServerError) 328 | } 329 | time.Sleep(time.Duration(delay) * time.Millisecond) 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /server/template/synthesized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Synthesized module tree test 5 | 6 | 7 |
8 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/bundle-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Module scripts loading test 5 | 6 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/display-results.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Module loading benchmark sample. 4 | * Copyright 2017 Google Inc. All rights reserved. 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 | 20 | window.onload = function() { 21 | let results = { onload: Math.round(performance.now()) }; 22 | let timings = performance.getEntriesByType('resource').filter( 23 | rt => rt.name.indexOf('.js') >= 0); 24 | results.nmodule = timings.length; 25 | results.firstFetchStart = Math.round(Math.min.apply(null, timings.map(rt => rt.fetchStart))); 26 | results.lastResponseEnd = Math.round(Math.max.apply(null, timings.map(rt => rt.responseEnd))); 27 | 28 | const items = [['nmodule', 'Number of modules', ''], 29 | ['onload', 'Time to onload', ' ms'], 30 | ['firstFetchStart', "First module's fetchStart", ' ms'], 31 | ['lastResponseEnd', "Last module's responseEnd", ' ms']]; 32 | let table = ''; 33 | for (let [name, title, unit] of items) 34 | table += ``; 35 | table += '
${title}:${results[name]}${unit}
'; 36 | document.getElementById('benchmark-results').innerHTML = table; 37 | } 38 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Module scripts loading test 9 | 19 | 20 | 21 | 22 |

Module scripts loading test

23 |

24 | All tests are median of 25 runs. 25 |

26 |
27 | 28 |
29 | 30 |
31 | 32 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/moment/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Module loading benchmark sample. 4 | * Copyright 2017 Google Inc. All rights reserved. 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 | 20 | // This code imports the moment.js library and outputs the current weekday to 21 | // the console. 22 | 23 | import moment from 'moment/src/moment'; 24 | 25 | function testMoment() { 26 | console.log(`Today is ${moment().format('dddd')}.`); 27 | } 28 | 29 | if (!window.noRun) { 30 | testMoment(); 31 | } 32 | -------------------------------------------------------------------------------- /src/moment/bundled-optimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - moment.js, bundled, tree-shaking (using rollup) 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/moment/bundled-unoptimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - moment.js, bundled, no tree-shaking (using webpack) 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/moment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "moment": "2.24.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/moment/unbundled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - moment.js, unbundled 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/moment/webbundle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Import test - moment.js, Subresource WebBundle 10 | 11 | 12 | 13 | 14 |
15 | 16 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/three/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Module loading benchmark sample. 4 | * Copyright 2017 Google Inc. All rights reserved. 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 | 20 | // This code imports the three.js library and creates a spinning wireframe. 21 | // It's a very slightly modified version of the 22 | // [starter example in the three.js docs](https://github.com/mrdoob/three.js/blob/dev/README.md). 23 | 24 | import * as THREE from './node_modules/three/src/Three.js'; 25 | 26 | var scene, camera, renderer; 27 | var geometry, material, mesh; 28 | 29 | if (!window.noRun) { 30 | init(); 31 | animate(); 32 | } 33 | 34 | function init() { 35 | scene = new THREE.Scene(); 36 | 37 | camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000); 38 | camera.position.z = 1000; 39 | 40 | geometry = new THREE.BoxGeometry(200, 200, 200); 41 | material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }); 42 | 43 | mesh = new THREE.Mesh(geometry, material); 44 | scene.add(mesh); 45 | 46 | renderer = new THREE.WebGLRenderer(); 47 | renderer.setSize(window.innerWidth, window.innerHeight); 48 | 49 | document.body.appendChild(renderer.domElement); 50 | } 51 | 52 | function animate() { 53 | requestAnimationFrame(animate); 54 | 55 | mesh.rotation.x += 0.01; 56 | mesh.rotation.y += 0.02; 57 | 58 | renderer.render(scene, camera); 59 | } 60 | -------------------------------------------------------------------------------- /src/three/bundled-optimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - three.js, bundled, tree-shaking (using rollup) 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/three/bundled-unoptimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - three.js, bundled, no tree-shaking (using webpack) 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/three/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "three": "0.85.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/three/unbundled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Import test - three.js, unbundled 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/three/webbundle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Import test - three.js, Subresource WebBundle 10 | 11 | 12 | 13 | 14 |
15 | 16 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------