├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── polyfill └── require.js └── src ├── BrowserShimLoader.js ├── Chart.js ├── DefaultRouter.js ├── Modularizer.js ├── Package.js ├── Packager.js ├── SymbolicLinkFinder.js ├── TimingData.js ├── consts.js ├── extractSourceMappedStack.js ├── guard.js ├── index.js └── renderReactPage.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | *~ 5 | *.swp 6 | *.swo 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - - - 2 | 3 | **_This project is not actively maintained. Proceed at your own risk!_** 4 | 5 | - - - 6 | 7 | react-page-middleware 8 | =============================================== 9 | Middleware for building full page apps using React, JSX, and CommonJS. 10 | 11 | If you want to get started with server rendered React apps, go directly to 12 | [react-page](http://www.github.com/facebook/react-page/). This project is the 13 | implementation of the router/server-side-page-assembler/packager. 14 | 15 | 16 | ###Features 17 | 18 | - Server-side JavaScript rendering of pages/apps using React. 19 | - Pages rendered on server, seamlessly brought to life in the browser. 20 | - No special glue code to write - "Just works" on client/server. 21 | - CommonJS + React + optional JSX. 22 | 23 |
24 | 25 | ###Requirements 26 | 27 | - node (a more recent version) 28 | - npm 29 | 30 | ###Install 31 | 32 | > Let npm do all the installing - just create a directory structure anywhere as 33 | > follows: 34 | 35 | yourProject/ 36 | ├── package.json # Add npm dependencies here. 37 | ├── server.js # Start web server with `node server.js` 38 | └── src # All your application JS. 39 | ├── index.js # localhost:8080/index.html routed here 40 | └── pages # Configure the page root using pageRouteRoot 41 | └── about.js # localhost:8080/about.html 42 | 43 | > List your dependencies in `package.json`: 44 | 45 | // Shows how to depend on bleeding edge versions. One niceness of 46 | // `react-page-middleware`, is depending on the main React repo as 47 | // `require('React')` Not all JS packagers understand the pure git repo for 48 | // React. 49 | "dependencies": { 50 | "React": "git://github.com/facebook/react.git", 51 | "react-page-middleware": "git://github.com/facebook/react-page-middleware.git", 52 | "connect": "2.8.3" 53 | }, 54 | 55 | > Download your project's dependencies: 56 | 57 | cd yourProject 58 | npm install 59 | 60 | 61 | > Create a `server.js` file that requires `react-page-middleware`, and set the 62 | > proper directory search paths and routing paths. 63 | 64 | var reactMiddleware = require('react-page-middleware'); 65 | var REACT_LOCATION = __dirname + '/node_modules/react-tools/src'; 66 | var ROOT_DIR = __dirname; 67 | var app = connect() 68 | .use(reactMiddleware.provide({ 69 | logTiming: true, 70 | pageRouteRoot: ROOT_DIR, // URLs based in this directory 71 | useSourceMaps: true, // Generate client source maps. 72 | projectRoot: ROOT_DIR, // Search for sources from 73 | ignorePaths: function(p) { // Additional filtering 74 | return p.indexOf('__tests__') !== -1; 75 | } 76 | })) 77 | .use(connect['static'](__dirname + '/src/static_files')); 78 | http.createServer(app).listen(8080); 79 | 80 | 81 | > Run the server and open index.html: 82 | 83 | 84 | node server 85 | open http://localhost:8080/index.html 86 | 87 | 88 | > The [react-page](http://www.github.com/facebook/react-page/) project has a 89 | > much more thorough explanation of the motivation and features. 90 | 91 | 92 | ### JavaScript-centric Routing And Page Rendering For JavaScript. 93 | 94 | The default router is JavaScript-centric. You simply specify the path to the JS 95 | component you want to use to render the entire page. 96 | [react-page](http://www.github.com/facebook/react-page/) for more information 97 | about the routing. 98 | 99 | ### Source Maps 100 | 101 | `react-page-middleware` has them. 102 | 103 | 104 | ### Run and Build on the Fly 105 | 106 | > Just hit your browser's refresh button to run an always-up-to-date version of 107 | > your app. 108 | 109 | - Dynamically packages/compiles your app on each server request. 110 | 111 | ### Purpose 112 | 113 | `react-page-middleware` is a rapid development environment where you can experiment with 114 | entirely new ways of building production web apps powered by React. It provides 115 | a common environment that allows sharing of modules client/server architecture 116 | prototypes. 117 | 118 | In order to use this technology in a production environment, you would need to 119 | audit and verify that the server rendering strategy is safe and suitable for 120 | your purposes. 121 | 122 | - In particular, you would want to ensure that a proper server 123 | sandbox is enforced. However, `react-page` _does_ run your UI rendering code 124 | inside of contextify as a preliminary sandbox. 125 | 126 | - The packaging/transforming features of `react-page` would not be needed in a 127 | production environment where the packages can be prebuilt once, stored in a CDN 128 | and not be repackaged on the fly, but the server rendering feature is very 129 | compelling for production environments where page load performance is of great 130 | concern. 131 | 132 | - Among other things, additional connect middleware should be added to prevent 133 | stack traces from showing up in the client. 134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-page-middleware", 3 | "version": "0.4.1", 4 | "description": "Connect middleware for rendering pages with React JavaScript Library.", 5 | "main": "src/index.js", 6 | "dependencies": { 7 | "async": "0.2.9", 8 | "browser-builtins": "1.0.7", 9 | "chalk": "^1.1.1", 10 | "convert-source-map": "0.2.6", 11 | "es5-shim": "^4.1.0", 12 | "node-haste": "^1.2.8", 13 | "optimist": "0.6.0", 14 | "react-tools": "^0.13.0", 15 | "source-map": "~0.1.22" 16 | }, 17 | "author": { 18 | "name": "Lee Byron", 19 | "email": "leebyron@fb.com", 20 | "url": "http://github.com/leebyron" 21 | }, 22 | "license": "Apache-2.0", 23 | "repository": { 24 | "type": "git", 25 | "url": "http://github.com/reactjs/react-page-middleware.git" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /polyfill/require.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | * @provides commonjs-require 17 | * @requires Function.prototype 18 | * @polyfill 19 | */ 20 | 21 | (function(global) { 22 | 23 | // avoid redefining require() 24 | if (global.require) { 25 | return; 26 | } 27 | 28 | var toString = Object.prototype.toString; 29 | 30 | /** 31 | * module index: { 32 | * mod1: { 33 | * exports: { ... }, 34 | * id: 'mod1', 35 | * dependencies: ['mod1', 'mod2'], 36 | * factory: function() { ... }, 37 | * waitingMap: { mod1: 1, mod3: 1, mod4: 1 }, 38 | * waiting: 2 39 | * } 40 | * } 41 | */ 42 | var modulesMap = {}, 43 | /** 44 | * inverse index: { 45 | * mod1: [modules, waiting for mod1], 46 | * mod2: [modules, waiting for mod2] 47 | * } 48 | */ 49 | dependencyMap = {}, 50 | /** 51 | * modules whose reference counts are set out of order 52 | */ 53 | predefinedRefCounts = {}, 54 | 55 | _counter = 0, 56 | 57 | REQUIRE_WHEN_READY = 1, 58 | USED_AS_TRANSPORT = 2, 59 | 60 | hop = Object.prototype.hasOwnProperty; 61 | 62 | if (__DEV__) { 63 | function _findUnresolvedDependencies(names) { 64 | var unresolved = Array.prototype.slice.call(names); 65 | var visited = {}; 66 | 67 | while (unresolved.length) { 68 | var name = unresolved.shift(); 69 | if (visited[name]) { 70 | continue; 71 | } 72 | visited[name] = true; 73 | 74 | var module = modulesMap[name]; 75 | if (!module || !module.waiting) { 76 | continue; 77 | } 78 | 79 | for (var ii = 0; ii < module.dependencies.length; ii++) { 80 | var dependency = module.dependencies[ii]; 81 | if (!modulesMap[dependency] || modulesMap[dependency].waiting) { 82 | unresolved.push(dependency); 83 | } 84 | } 85 | } 86 | 87 | for (name in visited) if (hop.call(visited, name)) { 88 | unresolved.push(name); 89 | } 90 | return unresolved; 91 | } 92 | 93 | function _formatUnresolvedDependencies(unresolved) { 94 | var messages = []; 95 | for (var ii = 0; ii < unresolved.length; ii++) { 96 | var name = unresolved[ii]; 97 | var message = name; 98 | var module = modulesMap[name]; 99 | if (!module) { 100 | message += ' is not defined'; 101 | } else if (!module.waiting) { 102 | message += ' is ready'; 103 | } else { 104 | var unresolvedDependencies = []; 105 | for (var jj = 0; jj < module.dependencies.length; jj++) { 106 | var dependency = module.dependencies[jj]; 107 | if (!modulesMap[dependency] || modulesMap[dependency].waiting) { 108 | unresolvedDependencies.push(dependency); 109 | } 110 | } 111 | message += ' is waiting for ' + unresolvedDependencies.join(', '); 112 | } 113 | messages.push(message); 114 | } 115 | return messages.join('\n'); 116 | } 117 | } 118 | 119 | /** 120 | * The require function conforming to CommonJS spec: 121 | * http://wiki.commonjs.org/wiki/Modules/1.1.1 122 | * 123 | * To define a CommonJS-compliant module add the providesModule 124 | * Haste header to your file instead of @provides. Your file is going 125 | * to be executed in a separate context. Every variable/function you 126 | * define will be local (private) to that module. To export local members 127 | * use "exports" variable or return the exported value at the end of your 128 | * file. Your code will have access to the "module" object. 129 | * The "module" object will have an "id" property that is the id of your 130 | * current module. "module" object will also have "exports" property that 131 | * is the same as "exports" variable passed into your module context. 132 | * You can require other modules using their ids. 133 | * 134 | * Haste will automatically pick dependencies from require() calls. So 135 | * you don't have to manually specify @requires in your header. 136 | * 137 | * You cannot require() modules from non-CommonJS files. Write a legacy stub 138 | * (@providesLegacy) and use @requires instead. 139 | * 140 | * @example 141 | * 142 | * / ** 143 | * * @providesModule math 144 | * * / 145 | * exports.add = function() { 146 | * var sum = 0, i = 0, args = arguments, l = args.length; 147 | * while (i < l) { 148 | * sum += args[i++]; 149 | * } 150 | * return sum; 151 | * }; 152 | * 153 | * / ** 154 | * * @providesModule increment 155 | * * / 156 | * var add = require('math').add; 157 | * return function(val) { 158 | * return add(val, 1); 159 | * }; 160 | * 161 | * / ** 162 | * * @providesModule program 163 | * * / 164 | * var inc = require('increment'); 165 | * var a = 1; 166 | * inc(a); // 2 167 | * 168 | * module.id == "program"; 169 | * 170 | * 171 | * @param {String} id 172 | * @throws when module is not loaded or not ready to be required 173 | */ 174 | function require(id) { 175 | if (global.ErrorUtils && !global.ErrorUtils.inGuard()) { 176 | return ErrorUtils.applyWithGuard(require, this, arguments); 177 | } 178 | 179 | var module = modulesMap[id], 180 | dep, i, msg; 181 | 182 | if (!modulesMap[id]) { 183 | msg = 'Requiring unknown module "' + id + '"'; 184 | if (__DEV__) { 185 | msg += '. It may not be loaded yet. Did you forget to run arc build?'; 186 | } 187 | throw new Error(msg); 188 | } 189 | 190 | if (module.hasError) { 191 | throw new Error( 192 | 'Requiring module "' + id + '" which threw an exception'); 193 | } 194 | 195 | if (module.waiting) { 196 | msg = 'Requiring module "' + id + '" with unresolved dependencies'; 197 | if (__DEV__) { 198 | var unresolved = _findUnresolvedDependencies([id]); 199 | if (global.console) { 200 | global.console.error(_formatUnresolvedDependencies(unresolved)); 201 | } 202 | var undefinedModules = []; 203 | for (var ii = 0; ii < unresolved.length; ii++) { 204 | var dependency = unresolved[ii]; 205 | if (!modulesMap[dependency]) { 206 | undefinedModules.push(dependency); 207 | } 208 | } 209 | msg += ': ' + undefinedModules.join(', '); 210 | } 211 | throw new Error(msg); 212 | } 213 | 214 | if (!module.exports) { 215 | var exports = module.exports = {}; 216 | var factory = module.factory; 217 | 218 | if (toString.call(factory) === '[object Function]') { 219 | 220 | var args = [], 221 | dependencies = module.dependencies, 222 | length = dependencies.length, 223 | ret; 224 | if (module.special & USED_AS_TRANSPORT) { 225 | length = Math.min(length, factory.length); 226 | } 227 | try { 228 | for (i = 0; i < length; i++) { 229 | dep = dependencies[i]; 230 | args.push(dep === 'module' ? module : 231 | (dep === 'exports' ? exports : require(dep))); 232 | } 233 | ret = factory.apply(module.context || global, args); 234 | } catch (e) { 235 | module.hasError = true; 236 | throw e; 237 | } 238 | if (ret) { 239 | if (__DEV__) { 240 | if (typeof ret != 'object' && typeof ret != 'function') { 241 | throw new Error( 242 | 'Factory for module "' + id + '" returned ' + 243 | 'an invalid value "' + ret + '". ' + 244 | 'Returned value should be either a function or an object.'); 245 | } 246 | } 247 | module.exports = ret; 248 | } 249 | } else { 250 | module.exports = factory; 251 | } 252 | } 253 | 254 | // If ref count is 1, this was the last call, so undefine the module. 255 | // The ref count can be null or undefined, but those are never === 1. 256 | if (module.refcount-- === 1) { 257 | delete modulesMap[id]; 258 | } 259 | 260 | return module.exports; 261 | } 262 | 263 | /** 264 | * The define function conforming to CommonJS proposal: 265 | * http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition 266 | * 267 | * define() allows you to explicitly state dependencies of your module 268 | * in javascript. It's most useful in non-CommonJS files. 269 | * 270 | * define() is used internally by haste as a transport for CommonJS 271 | * modules. So there's no need to use define() if you use providesModule 272 | * 273 | * @example 274 | * / ** 275 | * * @provides alpha 276 | * * / 277 | * 278 | * // Sets up the module with ID of "alpha", that uses require, 279 | * // exports and the module with ID of "beta": 280 | * define("alpha", ["require", "exports", "beta"], 281 | * function (require, exports, beta) { 282 | * exports.verb = function() { 283 | * return beta.verb(); 284 | * //Or: 285 | * return require("beta").verb(); 286 | * } 287 | * }); 288 | * 289 | * / ** 290 | * * @provides alpha 291 | * * / 292 | * // An anonymous module could be defined (module id derived from filename) 293 | * // that returns an object literal: 294 | * 295 | * define(["alpha"], function (alpha) { 296 | * return { 297 | * verb: function(){ 298 | * return alpha.verb() + 2; 299 | * } 300 | * }; 301 | * }); 302 | * 303 | * / ** 304 | * * @provides alpha 305 | * * / 306 | * // A dependency-free module can define a direct object literal: 307 | * 308 | * define({ 309 | * add: function(x, y){ 310 | * return x + y; 311 | * } 312 | * }); 313 | * 314 | * @param {String} id optional 315 | * @param {Array} dependencies optional 316 | * @param {Object|Function} factory 317 | */ 318 | function define(id, dependencies, factory, _special, _context, _refCount) { 319 | if (dependencies === undefined) { 320 | dependencies = []; 321 | factory = id; 322 | id = _uid(); 323 | } else if (factory === undefined) { 324 | factory = dependencies; 325 | if (toString.call(id) === '[object Array]') { 326 | dependencies = id; 327 | id = _uid(); 328 | } else { 329 | dependencies = []; 330 | } 331 | } 332 | 333 | // Non-standard: we allow modules to be undefined. This is designed for 334 | // temporary modules. 335 | var canceler = { cancel: function(){ _undefine(id); } }; 336 | 337 | var record = modulesMap[id]; 338 | 339 | // Nonstandard hack: we call define with null deps and factory, but a 340 | // non-null reference count (e.g. define('name', null, null, 0, null, 4)) 341 | // when this module is defined elsewhere and we just need to update the 342 | // reference count. We use this hack to avoid having to expose another 343 | // global function to increment ref counts. 344 | if (record) { 345 | if (_refCount) { 346 | record.refcount += _refCount; 347 | } 348 | // Calling define() on a pre-existing module does not redefine it 349 | return canceler; 350 | } else if (!dependencies && !factory && _refCount) { 351 | // If this module hasn't been defined yet, store the ref count. We'll use 352 | // it when the module is defined later. 353 | predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount; 354 | return canceler; 355 | } else { 356 | // Defining a new module 357 | record = { id: id }; 358 | record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0); 359 | delete predefinedRefCounts[id]; 360 | } 361 | 362 | if (__DEV__) { 363 | if ( 364 | !factory || 365 | (typeof factory != 'object' && typeof factory != 'function' && 366 | typeof factory != 'string')) { 367 | throw new Error( 368 | 'Invalid factory "' + factory + '" for module "' + id + '". ' + 369 | 'Factory should be either a function or an object.'); 370 | } 371 | 372 | if (toString.call(dependencies) !== '[object Array]') { 373 | throw new Error( 374 | 'Invalid dependencies for module "' + id + '". ' + 375 | 'Dependencies must be passed as an array.'); 376 | } 377 | } 378 | 379 | record.factory = factory; 380 | record.dependencies = dependencies; 381 | record.context = _context; 382 | record.special = _special; 383 | record.waitingMap = {}; 384 | record.waiting = 0; 385 | record.hasError = false; 386 | modulesMap[id] = record; 387 | _initDependencies(id); 388 | 389 | return canceler; 390 | } 391 | 392 | function _undefine(id) { 393 | if (!modulesMap[id]) { 394 | return; 395 | } 396 | 397 | var module = modulesMap[id]; 398 | delete modulesMap[id]; 399 | 400 | for (var dep in module.waitingMap) { 401 | if (module.waitingMap[dep]) { 402 | delete dependencyMap[dep][id]; 403 | } 404 | } 405 | 406 | for (var ii = 0; ii < module.dependencies.length; ii++) { 407 | dep = module.dependencies[ii]; 408 | if (modulesMap[dep]) { 409 | if (modulesMap[dep].refcount-- === 1) { 410 | _undefine(dep); 411 | } 412 | } else if (predefinedRefCounts[dep]) { 413 | predefinedRefCounts[dep]--; 414 | } 415 | // Subtle: we won't account for this one fewer reference if we don't have 416 | // the dependency's definition or reference count yet. 417 | } 418 | } 419 | 420 | /** 421 | * Special version of define that executes the factory as soon as all 422 | * dependencies are met. 423 | * 424 | * define() does just that, defines a module. Module's factory will not be 425 | * called until required by other module. This makes sense for most of our 426 | * library modules: we do not want to execute the factory unless it's being 427 | * used by someone. 428 | * 429 | * On the other hand there are modules, that you can call "entrance points". 430 | * You want to run the "factory" method for them as soon as all dependencies 431 | * are met. 432 | * 433 | * @example 434 | * 435 | * define('BaseClass', [], function() { return ... }); 436 | * // ^^ factory for BaseClass was just stored in modulesMap 437 | * 438 | * define('SubClass', ['BaseClass'], function() { ... }); 439 | * // SubClass module is marked as ready (waiting == 0), factory is just 440 | * // stored 441 | * 442 | * define('OtherClass, ['BaseClass'], function() { ... }); 443 | * // OtherClass module is marked as ready (waiting == 0), factory is just 444 | * // stored 445 | * 446 | * requireLazy(['SubClass', 'ChatConfig'], 447 | * function() { ... }); 448 | * // ChatRunner is waiting for ChatConfig to come 449 | * 450 | * define('ChatConfig', [], { foo: 'bar' }); 451 | * // at this point ChatRunner is marked as ready, and its factory 452 | * // executed + all dependent factories are executed too: BaseClass, 453 | * // SubClass, ChatConfig notice that OtherClass's factory won't be 454 | * // executed unless explicitly required by someone 455 | * 456 | * @param {Array} dependencies 457 | * @param {Object|Function} factory 458 | */ 459 | function requireLazy(dependencies, factory, context) { 460 | return define( 461 | dependencies, 462 | factory, 463 | undefined, 464 | REQUIRE_WHEN_READY, 465 | context, 466 | 1 467 | ); 468 | } 469 | 470 | function _uid() { 471 | return '__mod__' + _counter++; 472 | } 473 | 474 | function _addDependency(module, dep) { 475 | // do not add duplicate dependencies and circ deps 476 | if (!module.waitingMap[dep] && module.id !== dep) { 477 | module.waiting++; 478 | module.waitingMap[dep] = 1; 479 | dependencyMap[dep] || (dependencyMap[dep] = {}); 480 | dependencyMap[dep][module.id] = 1; 481 | } 482 | } 483 | 484 | function _initDependencies(id) { 485 | var modulesToRequire = []; 486 | var module = modulesMap[id]; 487 | var dep, i, subdep; 488 | 489 | // initialize id's waitingMap 490 | for (i = 0; i < module.dependencies.length; i++) { 491 | dep = module.dependencies[i]; 492 | if (!modulesMap[dep]) { 493 | _addDependency(module, dep); 494 | } else if (modulesMap[dep].waiting) { 495 | for (subdep in modulesMap[dep].waitingMap) { 496 | if (modulesMap[dep].waitingMap[subdep]) { 497 | _addDependency(module, subdep); 498 | } 499 | } 500 | } 501 | } 502 | if (module.waiting === 0 && module.special & REQUIRE_WHEN_READY) { 503 | modulesToRequire.push(id); 504 | } 505 | 506 | // update modules depending on id 507 | if (dependencyMap[id]) { 508 | var deps = dependencyMap[id]; 509 | var submodule; 510 | dependencyMap[id] = undefined; 511 | for (dep in deps) { 512 | submodule = modulesMap[dep]; 513 | 514 | // add all deps of id 515 | for (subdep in module.waitingMap) { 516 | if (module.waitingMap[subdep]) { 517 | _addDependency(submodule, subdep); 518 | } 519 | } 520 | // remove id itself 521 | if (submodule.waitingMap[id]) { 522 | submodule.waitingMap[id] = undefined; 523 | submodule.waiting--; 524 | } 525 | if (submodule.waiting === 0 && 526 | submodule.special & REQUIRE_WHEN_READY) { 527 | modulesToRequire.push(dep); 528 | } 529 | } 530 | } 531 | 532 | // run everything that's ready 533 | for (i = 0; i < modulesToRequire.length; i++) { 534 | require(modulesToRequire[i]); 535 | } 536 | } 537 | 538 | function _register(id, exports) { 539 | modulesMap[id] = { id: id }; 540 | modulesMap[id].exports = exports; 541 | } 542 | 543 | // pseudo name used in common-require 544 | // see require() function for more info 545 | _register('module', 0); 546 | _register('exports', 0); 547 | 548 | _register('define', define); 549 | _register('global', global); 550 | _register('require', require); 551 | _register('requireDynamic', require); 552 | _register('requireLazy', requireLazy); 553 | 554 | global.require = require; 555 | global.requireDynamic = require; 556 | global.requireLazy = requireLazy; 557 | 558 | require.__debug = { modules: modulesMap, deps: dependencyMap }; 559 | 560 | // shortcuts for haste transport 561 | var defineHaste = function(id, deps, factory, _special) { 562 | define(id, deps, factory, _special || USED_AS_TRANSPORT); 563 | }; 564 | 565 | /** 566 | * All @providesModule files are wrapped by this function by makehaste. It 567 | * is a convenience function around define() that prepends a bunch of required 568 | * modules (global, require, module, etc) so that we don't have to spit that 569 | * out for every module which would be a lot of extra bytes. 570 | */ 571 | global.__d = function(id, deps, factory, _special) { 572 | deps = ['global', 'require', 'requireDynamic', 'requireLazy', 'module', 573 | 'exports'].concat(deps); 574 | defineHaste(id, deps, factory, _special); 575 | }; 576 | 577 | })(this); 578 | -------------------------------------------------------------------------------- /src/BrowserShimLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | var JS = require('node-haste/lib/resource/JS'); 18 | var ResourceLoader = require('node-haste/lib/loader/ResourceLoader'); 19 | 20 | var browserBuiltins = require('browser-builtins'); 21 | var extract = require('node-haste/lib/parse/extract'); 22 | var path = require('path'); 23 | var util = require('util'); 24 | 25 | var inherits = util.inherits; 26 | 27 | function BrowserShimLoader(buildConfig) { 28 | this.buildConfig = buildConfig || {}; 29 | this.browserBuiltinPaths = {}; 30 | 31 | var realReactMiddlewarePath = path.resolve(__dirname, '..'); 32 | /** 33 | * Store both the "real" path and the path relative to the `projectRoot`, 34 | * which would preserve any symlinks through node_modules. 35 | */ 36 | for (var builtInName in browserBuiltins) { 37 | var realPath = browserBuiltins[builtInName]; 38 | var relativeToMiddleware = path.relative(realReactMiddlewarePath, realPath); 39 | var inTermsOfProjectRoot = path.join( 40 | buildConfig.projectRoot, 41 | 'node_modules', 42 | 'react-page-middleware', 43 | relativeToMiddleware 44 | ); 45 | this.browserBuiltinPaths[inTermsOfProjectRoot] = builtInName; 46 | this.browserBuiltinPaths[realPath] = builtInName; 47 | } 48 | 49 | } 50 | inherits(BrowserShimLoader, ResourceLoader); 51 | BrowserShimLoader.prototype.path = __filename; 52 | 53 | BrowserShimLoader.prototype.getResourceTypes = function() { 54 | return [JS]; 55 | }; 56 | 57 | BrowserShimLoader.prototype.getExtensions = function() { 58 | return ['.js']; 59 | }; 60 | 61 | BrowserShimLoader.prototype.loadFromSource = 62 | function(path, configuration, sourceCode, messages, callback) { 63 | var shimJS = new JS(path); 64 | shimJS.id = this.browserBuiltinPaths[path]; 65 | shimJS.isModule = true; 66 | shimJS.requiredModules = extract.requireCalls(sourceCode); 67 | if (!shimJS.id) { 68 | throw new Error('Cannot find shim for:' + path); 69 | } 70 | callback(messages, shimJS); 71 | }; 72 | 73 | BrowserShimLoader.prototype.matchPath = function(filePath) { 74 | return !!this.browserBuiltinPaths[filePath]; 75 | }; 76 | 77 | module.exports = BrowserShimLoader; 78 | -------------------------------------------------------------------------------- /src/Chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | var chalk = require('chalk'); 19 | 20 | 21 | /** 22 | * Super hacky charing utilities to report timing data. 23 | */ 24 | 25 | var LOG_WIDTH = 130; 26 | 27 | var renderChart = function(title, blocks, datas) { 28 | // Remove spaces from available blocks, and titles. 29 | var totalColorBlocks = blocks - (datas.length - 1) - title.length; 30 | var sum = 0; 31 | for (var d = 0; d < datas.length; d++) { 32 | sum += datas[d].value; 33 | } 34 | var totalColorBlocksRendered = 0; 35 | var chart = title; 36 | for (d = 0; d < datas.length; d++) { 37 | var val = datas[d].value; 38 | var pct = val / sum; 39 | var blocksToRender = Math.floor(pct * totalColorBlocks); 40 | var textLen = Math.min(datas[d].text.length, blocksToRender); 41 | var spacesToRender = blocksToRender - textLen; 42 | var color = val < datas[d].bad/2 ? chalk.black.bgGreen : 43 | val < datas[d].bad ? chalk.black.bgYellow : chalk.black.bgRed; 44 | chart += color(datas[d].text.substr(0, textLen)) 45 | chart += color(times(' ', spacesToRender)); 46 | totalColorBlocksRendered += blocksToRender; 47 | if (d === datas.length - 1) { 48 | var padding = Math.max(totalColorBlocks - totalColorBlocksRendered, 0); 49 | chart += color(times(' ', padding)); 50 | } 51 | chart += ' '; 52 | } 53 | console.log(chart + '\n'); 54 | }; 55 | 56 | var firstBundle = false; 57 | var badFind = function() { 58 | return !firstBundle ? 1100 : 100; 59 | }; 60 | var findMsg = function(time) { 61 | var timeText = 'find/transform ' + time + 'ms'; 62 | var disclaimer = !firstBundle ? ' (WARMING UP ON FIRST LOAD)' : ''; 63 | return timeText + disclaimer; 64 | }; 65 | 66 | var getChartColumns = function() { 67 | return typeof process.stdout.getWindowSize == 'function' && 68 | process.stdout.getWindowSize()[0] - 2 || LOG_WIDTH; 69 | }; 70 | var logPageServeTime = function(timingData) { 71 | var pageStart = timingData.pageStart; 72 | var findEnd = timingData.findEnd; 73 | var concatEnd = timingData.concatEnd; 74 | var markupEnd = timingData.markupEnd; 75 | var serveEnd = timingData.serveEnd; 76 | 77 | var columns = getChartColumns(); 78 | var findTime = findEnd - pageStart; 79 | var concatTime = concatEnd - findEnd; 80 | var renderingTime = markupEnd - findEnd; 81 | var serveTime = serveEnd - markupEnd; 82 | Chart.renderChart('HTML Gen: ', columns, [ 83 | {text: findMsg(findTime), value: findTime, bad: badFind()}, 84 | // Concat time for HTML serving is very fast because we don't need to 85 | // compute source maps - they are computed lazily upon errors for stack traces. 86 | // JS bundles compute them at the same time as concatenation happens. 87 | {text: 'concat JS ' + concatTime + 'ms', value: Math.max(concatTime, 7), bad: 40}, 88 | {text: 'render ' + renderingTime + 'ms', value: renderingTime, bad: 170}, 89 | // Inflate the serve time to make a nicer chart 90 | {text: 'serve ' + serveTime + 'ms', value: serveTime + 15, bad: 20 + 15} 91 | ]); 92 | firstBundle = true; 93 | }; 94 | 95 | var logBundleServeTime = function(timingData) { 96 | var pageStart = timingData.pageStart; 97 | var findEnd = timingData.findEnd; 98 | var concatEnd = timingData.concatEnd; 99 | var sourceMapEnd = timingData.sourceMapEnd; 100 | var serveEnd = timingData.serveEnd; 101 | 102 | var columns = getChartColumns(); 103 | var findTime = findEnd - pageStart; 104 | var concatTime = concatEnd - findEnd; 105 | var sourceMapTime = sourceMapEnd - concatEnd; 106 | var serveTime = serveEnd - sourceMapEnd; 107 | var datas = [ 108 | {text: findMsg(findTime), value: findTime, bad: badFind()}, 109 | { text: (sourceMapTime ? 'concat JS and build srcmap ' : 'concat JS ') + 110 | concatTime + 'ms', 111 | value: concatTime, 112 | bad: sourceMapTime ? 100 : 10 113 | } 114 | ]; 115 | if (sourceMapTime && sourceMapTime > 2) { 116 | datas.push({ 117 | text: 'serialize srcmap ' + sourceMapTime + 'ms', 118 | value: sourceMapTime, 119 | bad: 210 120 | }); 121 | } 122 | datas.push({text: 'serve ' + serveTime + 'ms', value: serveTime + 10, bad: 40}); 123 | Chart.renderChart(' JS Gen: ', columns, datas); 124 | firstBundle = true; 125 | }; 126 | 127 | var times = function(s, n) { 128 | var res = ''; 129 | for (var i = 0; i < n; i++) { 130 | res += s; 131 | } 132 | return res; 133 | }; 134 | 135 | /** 136 | * The bytes are an approximation - in JS, str.length and bytes are not equal. 137 | */ 138 | var logSummary = function(normalizedRequestPath, numModules) { 139 | var columns = getChartColumns(); 140 | var msg = 141 | 'Bundling ' + numModules + ' JS files for ' + normalizedRequestPath + ' - ' + 142 | 'HTML blocks UI, JS Gen does not'; 143 | var padL = Math.floor((columns - msg.length) / 2); 144 | var padR = padL * 2 < columns ? padL : padL; 145 | var formattedMsg = times(' ', padL) + msg + times(' ', padR); 146 | console.log('\n\n' + formattedMsg); 147 | console.log(times('-', columns)); 148 | }; 149 | 150 | 151 | var Chart = { 152 | renderChart: renderChart, 153 | logPageServeTime: logPageServeTime, 154 | logBundleServeTime: logBundleServeTime, 155 | logSummary: logSummary 156 | }; 157 | 158 | module.exports = Chart; 159 | -------------------------------------------------------------------------------- /src/DefaultRouter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | var fs = require('fs'); 19 | var path = require('path'); 20 | var url = require('url'); 21 | 22 | var Chart = require('./Chart'); 23 | var Modularizer = require('./Modularizer'); 24 | var TimingData = require('./TimingData'); 25 | 26 | var consts = require('./consts'); 27 | var renderReactPage = require('./renderReactPage'); 28 | 29 | var convertSourceMap = require('convert-source-map'); 30 | 31 | 32 | /** 33 | * What is a "Router" for `react-page-middleware`? 34 | * 35 | * A `react-page-middleware` `Router` object helps generate entire responses for URLs. 36 | * `react-page-middleware` understands dependencies between files, and 37 | * bundles/transforms your resources for you, but your `Router` fills in some 38 | * of the application specific details - such as how URLs map to a root JS 39 | * module, and how to render a page for a particular type of URL request. 40 | * 41 | * A `Router object must contain: 42 | * 43 | * `decideRoute` method: 44 | * 45 | * - Accepts a URL from a server request. 46 | * - Asynchronously determines a "route" object describing at least two things: 47 | * -- Path to the root JS module corresponding to the request. 48 | * -- The content type of the response. 49 | * -- The determined route may also include as many other things as your router 50 | * would like. 51 | * 52 | * `routePackageHandler` method: 53 | * 54 | * - Accepts a `Package` object (automatically generated for you based on the 55 | * `route` object you previously decided from `decideRoute`. 56 | * - Generates a page response text. 57 | * - Can make great use of the features in the `Package` object, including 58 | * source maps etc. 59 | * - Can use that package to deliver a static resource to the client, or can 60 | * run that package on the server to generate markup on the server. 61 | */ 62 | 63 | 64 | /** 65 | * More formally: 66 | * @typedef Config: Stringmap; 67 | * 68 | * @typedef RouteData { 69 | * contentType: string, // Content-type header field 70 | * rootModulePath: string // Path to "root" JS module 71 | * // (whatever other fields you want: "additional props" etc. 72 | * }; 73 | * 74 | * @typedef Router { 75 | * decideRoute: ( 76 | * buildConfig:Config, 77 | * reqURL:string, 78 | * next:(err, RouteData)->void 79 | * ) -> void, 80 | * routePackageHandler: ( 81 | * buildConfig:Config, 82 | * route:RouteData, 83 | * rootModuleID:string, 84 | * ppackage:Package, 85 | * next:(err, bundleText:string)->void 86 | * ) -> void 87 | * }; 88 | * 89 | */ 90 | 91 | /** 92 | * The "DefaultRouter" contains some nice features worth emulating: 93 | * 94 | * - Looks at `buildConfig` for a field `sourceMapsType` (either 'inline' or 95 | * 'linked') that instructs page generation on how to link source maps - 96 | * should they be sent in the bundle, or downloaded lazily when you open your 97 | * debugger? 98 | * - Looks at `buildConfig.useBrowserBuiltins` to know whether or not to 99 | * include common node.js functionality in the bundle even if ran in the 100 | * browser. 101 | * - Server rendering of pages: Routes `mypage/path/to/component.html` to 102 | * `mypage/path/to/component.js` and expects to find a React component there 103 | * to render. 104 | * - Looks for "tags" in the path, which hint to include additional code in the 105 | * generated bundle: 106 | * -- runModule: Puts `require('YourModule')` at the end of the bundle so it is 107 | * ran automatically. 108 | * -- includeRequire: Controls whether or not to include the require runtime. 109 | * If a previous bundle already defined it you know you don't have to. 110 | */ 111 | 112 | /** 113 | * Define your own custom router: 114 | * - Simply adhere to the above definition. If you use `react-page`, you should 115 | * be able to simply provide your own router inside the `server.js` file 116 | * (pass it to the `react-page-middleware` `provide` method using the object 117 | * key `router`). 118 | */ 119 | 120 | var devBlock = function(buildConfig) { 121 | return '__VERSION__ = 0.44; __DEV__ = ' + (buildConfig.dev ? ' true;\n' : 'false;\n'); 122 | }; 123 | 124 | var keyMirror = function(obj) { 125 | var ret = {}; 126 | var key; 127 | for (key in obj) { 128 | if (!obj.hasOwnProperty(key)) { 129 | continue; 130 | } 131 | ret[key] = key; 132 | } 133 | return ret; 134 | }; 135 | 136 | var JS_TYPE = 'application/javascript; charset=utf-8'; 137 | var MAPS_TYPE = 'text; charset=utf-8'; 138 | var HTML_TYPE = 'text/html; charset=utf-8'; 139 | 140 | /** 141 | * Bundle the require implementation 142 | */ 143 | var REQUIRE_RUNTIME_PATH = path.resolve(__dirname, '..', 'polyfill/require.js'); 144 | var PROCESS_RUNTIME_PATH = require('browser-builtins').process; 145 | var REQUIRE_RUNTIME = fs.readFileSync(REQUIRE_RUNTIME_PATH, 'utf8'); 146 | var PROCESS_RUNTIME = 147 | Modularizer.modularize('process', fs.readFileSync(PROCESS_RUNTIME_PATH, 'utf8')); 148 | 149 | var RouteTypes = keyMirror({ 150 | fullPageRender: null, // requests for `.html` files corresponding to components. 151 | jsBundle: null, // requests for `.bundle` files corresponding to components. 152 | jsMaps: null // requsts for `.map` files corresponding to components. 153 | }); 154 | 155 | /** 156 | * The default router uses the `buildConfig.pageRouteRoot` as a way to prefix 157 | * all component lookups. For example, in the example `server.js`, the 158 | * `pageRouteRoot` is set to the `src/pages` directory, so 159 | * http://localhost:8080/index.html => src/pages/index.js 160 | * http://localhost:8080/about/index.html => src/pages/index.js 161 | * 162 | * The same convention is applied to bundle paths, since each page has exactly 163 | * one bundle. Each generated HTML page automatically pulls in its JS bundle 164 | * from the client (you don't worry about this). 165 | * 166 | * http://localhost:8080/index.bundle => src/pages/index.js(bundled) 167 | * http://localhost:8080/about/index.bundle => src/pages/index.js(bundled) 168 | * 169 | * If no `.bundle` or `.html` is found at the end of the URL, the default router 170 | * will append `index.html` to the end, before performing the routing convention 171 | * listed above. 172 | * 173 | * So http://localhost:8080/some/path 174 | * normalized => http://localhost:8080/some/path/index.html 175 | * rendered => http://localhost:8080/some/path/index.js 176 | * bundled => http://localhost:8080/some/path/index.bundle 177 | * 178 | * @return Route data or null if none applicable. 179 | */ 180 | var _getDefaultRouteData = function(buildConfig, reqURL) { 181 | var reqPath = url.parse(reqURL).pathname; 182 | var hasExtension = consts.HAS_EXT_RE.test(reqPath); 183 | var endsInHTML = consts.PAGE_EXT_RE.test(reqPath); 184 | var endsInBundle = consts.BUNDLE_EXT_RE.test(reqPath); 185 | var endsInMap = consts.MAP_EXT_RE.test(reqPath); 186 | var routeType = endsInHTML || !hasExtension ? RouteTypes.fullPageRender : 187 | endsInBundle ? RouteTypes.jsBundle : 188 | endsInMap ? RouteTypes.jsMaps : null; 189 | 190 | if (!routeType || reqPath.indexOf('..') !== -1) { 191 | return null; 192 | } 193 | 194 | var contentType = routeType === RouteTypes.fullPageRender ? HTML_TYPE : 195 | routeType === RouteTypes.jsBundle ? JS_TYPE : 196 | routeType === RouteTypes.jsMaps ? MAPS_TYPE : null; 197 | 198 | // Normalize localhost/myPage to localhost/myPage/index.html 199 | var indexNormalizedRequestPath = 200 | !hasExtension ? path.join(reqPath, '/index.html') : reqPath; 201 | 202 | var rootModulePath = path.join( 203 | // .html => .js, .bundle => js, .map => .js 204 | indexNormalizedRequestPath.replace(consts.ALL_TAGS_AND_EXT_RE, consts.JS_SRC_EXT) 205 | .replace(consts.BUNDLE_EXT_RE, consts.JS_SRC_EXT) 206 | .replace(consts.MAP_EXT_RE, consts.JS_SRC_EXT) 207 | .replace(consts.LEADING_SLASH_RE, '') 208 | ); 209 | 210 | return { 211 | /** 212 | * The only "first class" routing fields that are expected to be returned 213 | * by all routers. 214 | */ 215 | contentType: contentType, 216 | rootModulePath: rootModulePath, 217 | 218 | /** 219 | * The remaining fields are anticipated by `DefaultRouter`'s particular 220 | * `routePackageHandler`. 221 | */ 222 | type: routeType, 223 | indexNormalizedRequestPath: indexNormalizedRequestPath, 224 | bundleTags: getBundleTagsForRequestPath(indexNormalizedRequestPath, routeType), 225 | additionalProps: {requestParams: url.parse(reqURL, true).query} 226 | }; 227 | }; 228 | 229 | var getBundleTagsForRequestPath = function(indexNormalizedRequestPath, routeType) { 230 | if (routeType === RouteTypes.fullPageRender) { 231 | // Make sure we include the same bundle tags when server rendering as 232 | // what will be downloaded after the initial page load. 233 | return renderReactPage.bundleTagsForFullPage(); 234 | } else { 235 | var allTagsAndExtensionsMatch = 236 | indexNormalizedRequestPath.match(consts.ALL_TAGS_AND_EXT_RE); 237 | if (!allTagsAndExtensionsMatch) { 238 | return []; 239 | } 240 | var tagsAndExtension = allTagsAndExtensionsMatch && allTagsAndExtensionsMatch[1]; 241 | var tagsAndExtensionSplit = tagsAndExtension && tagsAndExtension.split('.'); 242 | return tagsAndExtensionSplit && 243 | tagsAndExtensionSplit.slice(0, tagsAndExtensionSplit.length - 1); 244 | } 245 | }; 246 | 247 | 248 | /** 249 | * @param {object} buildConfig Options for building. 250 | * @param {RouteData} route RouteData specifying root module etc. 251 | * @param {string} Root module ID: we use this with the `runModule` tag. 252 | * @param {Package} ppackage Appends module system etc. 253 | */ 254 | var preparePackage = function(buildConfig, route, rootModuleID, ppackage) { 255 | var devStr = devBlock(buildConfig); 256 | var processCode = buildConfig.useBrowserBuiltins ? 257 | (PROCESS_RUNTIME + '\nprocess = require("process");') : '\nprocess = {env:{}};'; 258 | processCode += 259 | '\nprocess.env.NODE_ENV = "' + (buildConfig.dev ? 'development' : 'production') + '";\n'; 260 | ppackage.unshift(PROCESS_RUNTIME_PATH, processCode, processCode); 261 | if (route.bundleTags.indexOf(consts.INCLUDE_REQUIRE_TAG) !== -1) { 262 | ppackage.unshift(REQUIRE_RUNTIME_PATH, REQUIRE_RUNTIME, REQUIRE_RUNTIME); 263 | } 264 | ppackage.unshift('/dynamically-generated.js', devStr, devStr); 265 | if (route.bundleTags.indexOf(consts.RUN_MODULE_TAG) !== -1) { 266 | var moduleRunnerSource = "require('" + rootModuleID + "');null;"; 267 | ppackage.push("PackageRun" + rootModuleID, moduleRunnerSource, moduleRunnerSource); 268 | } 269 | }; 270 | 271 | var routePackageHandler = function(buildConfig, route, rootModuleID, ppackage, next) { 272 | preparePackage(buildConfig, route, rootModuleID, ppackage); 273 | if (route.type === RouteTypes.fullPageRender) { 274 | renderComponentPackage(buildConfig, route, rootModuleID, ppackage, next); 275 | TimingData.data.serveEnd = Date.now(); 276 | if (buildConfig.logTiming) { 277 | Chart.logPageServeTime(TimingData.data); 278 | } 279 | } else if (route.type === RouteTypes.jsBundle) { 280 | var computedBundle = computeJSBundle(buildConfig, route, ppackage); 281 | next(null, computedBundle); 282 | TimingData.data.serveEnd = Date.now(); 283 | if (buildConfig.logTiming) { 284 | Chart.logBundleServeTime(TimingData.data); 285 | } 286 | } else if (route.type === RouteTypes.jsMaps) { 287 | next(null, computeJSBundleMapsFile(buildConfig, route, ppackage)); 288 | TimingData.data.serveEnd = Date.now(); 289 | if (buildConfig.logTiming) { 290 | Chart.logBundleServeTime(TimingData.data); 291 | } 292 | } else { 293 | next(new Error('unrecognized route')); 294 | } 295 | }; 296 | 297 | var decideRoute = function(buildConfig, reqURL, next) { 298 | if (!buildConfig.pageRouteRoot) { 299 | return next(new Error('Must specify default router root')); 300 | } else { 301 | try { 302 | var routerData = _getDefaultRouteData(buildConfig, reqURL); 303 | return next(null, routerData); 304 | } catch (e) { 305 | return next(e, null); 306 | } 307 | } 308 | }; 309 | 310 | /** 311 | * Handles generating "an entire page" of markup for a given route and 312 | * corresponding `Package`. 313 | * 314 | * - First, generates the markup and renders it server side. Will never generate 315 | * source maps, unless an error occurs - then they are generated lazily. 316 | * - That markup is sent, then another request is made for the JS. If 317 | * `useSourceMaps` is true in the `buildConfig`, then these will be generated 318 | * at this point - it does not block initial page render since server 319 | * rendering happens without them. 320 | * 321 | * @param {object} buildConfig Build config options. 322 | * @param {Route} route Contains information about the component to render 323 | * based on URL. 324 | * @param {String} rootModuleID, id of the root module for given route. 325 | * @param {Package} Contains information about all dependencies for given route. 326 | * @param {function} next When complete. 327 | */ 328 | var renderComponentPackage = function(buildConfig, route, rootModuleID, ppackage, next) { 329 | var jsBundleText = computeJSBundle(buildConfig, route, ppackage); 330 | var props = route.additionalProps || {}; 331 | renderReactPage({ 332 | serverRender: buildConfig.serverRender, 333 | rootModulePath: route.rootModulePath, 334 | rootModuleID: rootModuleID, 335 | props: props, 336 | bundleText: jsBundleText, 337 | ppackage: ppackage, 338 | static: buildConfig['static'], 339 | /** 340 | * @param {Error} err Error that occured. 341 | * @param {string} markup Markup result. 342 | */ 343 | done: function(err, markup) { 344 | TimingData.data.markupEnd = Date.now(); 345 | next(err, markup); 346 | if (buildConfig.logTiming) { 347 | Chart.logSummary(route.indexNormalizedRequestPath, ppackage.resourceCount()); 348 | } 349 | } 350 | }); 351 | }; 352 | 353 | var computeJSBundle = function(buildConfig, route, ppackage) { 354 | var useSourceMaps = buildConfig.useSourceMaps; 355 | var sourceMapsType = buildConfig.sourceMapsType; 356 | var inlineSourceMaps = sourceMapsType === 'inline'; 357 | var footerContent = null; 358 | var packageText = ppackage.getSealedText(inlineSourceMaps); 359 | TimingData.data.concatEnd = Date.now(); 360 | if (useSourceMaps) { 361 | footerContent = inlineSourceMaps ? 362 | // Inline them in a comment 363 | convertSourceMap.fromObject(ppackage.getSealedSourceMaps().toJSON()).toComment() : 364 | // Link them by replacing only the extension: 365 | // path/File.tag.bundle -> path/File.tag.map 366 | '\/\/@ sourceMappingURL=' + route.indexNormalizedRequestPath.replace( 367 | consts.BUNDLE_EXT_RE, 368 | consts.MAP_EXT 369 | ); 370 | } 371 | TimingData.data.sourceMapEnd = Date.now(); 372 | return (footerContent ? packageText + '\n' + footerContent : packageText) + ' '; 373 | }; 374 | 375 | var computeJSBundleMapsFile = function(buildConfig, route, ppackage) { 376 | TimingData.data.concatEnd = Date.now(); 377 | var bundleText = ppackage.getSealedSourceMaps().toString(); 378 | TimingData.data.sourceMapEnd = Date.now(); 379 | return bundleText; 380 | }; 381 | 382 | var DefaultRouter = { 383 | decideRoute: decideRoute, 384 | routePackageHandler: routePackageHandler 385 | }; 386 | 387 | module.exports = DefaultRouter; 388 | -------------------------------------------------------------------------------- /src/Modularizer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | /** 18 | * #Super poor-man's require system. Other systems include larger feature sets 19 | * for asyncronously loading resources etc. Here, we completely resolve the 20 | * module load order statically - I'd be open to trying an existing solution if 21 | * it's lightweight. 22 | */ 23 | var DEFINE_MODULE_CODE = 24 | "__d(" + 25 | "'_moduleName_'," + 26 | "[/* deps */]," + 27 | "function(global, require, requireDynamic, requireLazy, module, exports) {" + 28 | " _code_" + 29 | "}" + 30 | ");"; 31 | 32 | var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_/g; 33 | 34 | /** 35 | * Only do the lookup if the name has a dot in it - providesModule don't need 36 | * any static rewriting of module names. Handles require('./something/path.js'); 37 | */ 38 | var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; 39 | 40 | 41 | var Modularizer = { 42 | /** 43 | * @param {pageComponent} pageComponent Either node-haste compatible module 44 | * description (either name or providesModule). 45 | * @param {string} code String of code to modularize. 46 | * @return {string} Modularized code. 47 | */ 48 | modularize: function(pageComponent, code, resolveModule) { 49 | 50 | var relativizedCode = !resolveModule ? code : 51 | code.replace(REL_REQUIRE_STMT, function(codeMatch, moduleName) { 52 | return "require('" + resolveModule(moduleName) + "')"; 53 | }); 54 | var ret = DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { 55 | return { 56 | '_moduleName_': pageComponent, 57 | '_code_': relativizedCode 58 | }[key]; 59 | }); 60 | return ret; 61 | } 62 | }; 63 | 64 | module.exports = Modularizer; 65 | -------------------------------------------------------------------------------- /src/Package.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | var SourceMapGenerator = require('source-map').SourceMapGenerator; 19 | 20 | /** 21 | * Stateful class, that has an odd "finalized" state, where no more resources 22 | * may be added to the package. The only reason for this, is that the 23 | * `ResourceMap` makes it difficult to prepend content to the package, or adjust 24 | * previously appended mappings. So this class accommodates that by forcing users 25 | * of a `Package` to organize their code in a way that multiple people may add 26 | * resources to a package, but only one should "seal" the package, and extract 27 | * the source maps/text from it. 28 | */ 29 | function Package() { 30 | this.items = []; 31 | this._isSealed = false; 32 | this._sourceMapGenerator = null; 33 | this._packageStr = null; 34 | } 35 | 36 | Package.prototype.push = function(originPath, originSource, transformedSource) { 37 | this._validateNotSealed(); 38 | return this.items.push({ 39 | originPath: originPath, 40 | originSource: originSource, 41 | transformedSource: transformedSource 42 | }); 43 | }; 44 | 45 | Package.prototype.resourceCount = function() { 46 | return this.items.length; 47 | }; 48 | 49 | Package.prototype._validateSealed = function() { 50 | if (!this._isSealed) { 51 | throw new Error('Operation may only be invoked on finalized package.'); 52 | } 53 | }; 54 | 55 | Package.prototype._validateNotSealed = function() { 56 | if (this._isSealed) { 57 | throw new Error('Cannot continue to build up a package once generated.'); 58 | } 59 | }; 60 | 61 | /** 62 | * Only after invoking this can you access computed package text, and source 63 | * maps. 64 | */ 65 | Package.prototype._seal = function() { 66 | this._isSealed = true; 67 | }; 68 | 69 | Package.prototype._updateSealedPackage = function(computeSourceMaps) { 70 | this._seal(); 71 | if (computeSourceMaps) { 72 | this._sourceMapGenerator = 73 | new SourceMapGenerator({file: 'bundle.js', version: 3}); 74 | } 75 | var lastCharNewLine = false; 76 | var packageStr = ''; 77 | var packageLineCount = 0; 78 | for (var i = 0; i < this.items.length; i++) { 79 | var transformedLineCount = 0; 80 | var item = this.items[i]; 81 | var transformedSource = item.transformedSource; 82 | if (computeSourceMaps) { 83 | for (var t = 0; t < transformedSource.length; t++) { 84 | if (t === 0 || lastCharNewLine) { 85 | this._sourceMapGenerator.addMapping({ 86 | generated: {line: packageLineCount + 1, column: 0}, 87 | original: {line: transformedLineCount + 1, column: 0}, 88 | source: item.originPath 89 | }); 90 | } 91 | lastCharNewLine = transformedSource[t] === '\n'; 92 | if (lastCharNewLine) { 93 | transformedLineCount++; 94 | packageLineCount++; 95 | } 96 | } 97 | this._sourceMapGenerator.setSourceContent( 98 | item.originPath, 99 | item.originSource 100 | ); 101 | } 102 | packageStr += transformedSource; 103 | } 104 | this._packageStr = packageStr; 105 | }; 106 | 107 | /** 108 | * @param {boolean} computeSourceMaps Whether or not to also compute source 109 | * maps. Pass true if you would like to optimize the case when you will access 110 | * source maps anyways. (It's just a perf optimization). 111 | */ 112 | Package.prototype.getSealedText = function(computeSourceMaps) { 113 | if (!this._packageStr) { 114 | this._updateSealedPackage(computeSourceMaps); 115 | } 116 | this._validateSealed(); 117 | return this._packageStr; 118 | }; 119 | 120 | Package.prototype.getSealedSourceMaps = function() { 121 | if (!this._sourceMapGenerator) { 122 | this._updateSealedPackage(true); 123 | } 124 | this._validateSealed(); 125 | return this._sourceMapGenerator; 126 | }; 127 | 128 | Package.prototype.unshift = 129 | function(originPath, originSource, transformedSource) { 130 | this._validateNotSealed(); 131 | return this.items.unshift({ 132 | originPath: originPath, 133 | originSource: originSource, 134 | transformedSource: transformedSource 135 | }); 136 | }; 137 | 138 | module.exports = Package; 139 | -------------------------------------------------------------------------------- /src/Packager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | /** 19 | * Using Node-Haste. 20 | */ 21 | var BrowserShimLoader = require('./BrowserShimLoader'); 22 | var Haste = require('node-haste/lib/Haste'); 23 | var HasteDependencyLoader = require('node-haste/lib/HasteDependencyLoader'); 24 | var Modularizer = require('./Modularizer'); 25 | var Package = require('./Package'); 26 | var ProjectConfigurationLoader = require('node-haste/lib/loader/ProjectConfigurationLoader'); 27 | var ResourceMap = require('node-haste/lib/ResourceMap'); 28 | var SymbolicLinkFinder = require('./SymbolicLinkFinder'); 29 | 30 | var async = require('async'); 31 | var fs = require('fs'); 32 | var hasteLoaders = require('node-haste/lib/loaders'); 33 | var path = require('path'); 34 | var transform = require('react-tools').transform; 35 | 36 | var originalSourceCache = {}; 37 | var transformCache = {}; 38 | var transformTimes = {}; 39 | 40 | var resourceMap = new ResourceMap(); 41 | 42 | var getResourceMapInstance = function() { 43 | return resourceMap; 44 | }; 45 | var hasteInstance; 46 | 47 | var prepareForRE = function(str) { 48 | return str.replace(/\./g, function() { 49 | return '\\.'; // escape the dots. 50 | }); 51 | }; 52 | 53 | /** 54 | * These directories show up in various places - they are never good to bundle 55 | * in a browser package. 56 | */ 57 | var BLACKLIST_EXTS = [ 58 | '.DS_Store', 59 | '.git', 60 | '.module-cache' 61 | ].map(function(ext) { 62 | return prepareForRE(ext + '$'); 63 | }); 64 | 65 | /** 66 | * These are known to simply be tools of `react-page`, not the app that you wish 67 | * to package. 68 | */ 69 | var REACT_PAGE_DEPENDENCIES = [ 70 | 'contextify', 71 | 'optimist', 72 | 'connect', 73 | 'markdown', 74 | 'react-tools/vendor', 75 | 'react-tools/node_modules' 76 | ]; 77 | 78 | // Everything from the react-page-middleware tree except browser-builtins. 79 | var BLACKLIST_MIDDLEWARE_DEPENDENCIES = [ 80 | 'src', 81 | 'polyfill', 82 | path.join('node_modules', 'node-terminal'), 83 | path.join('node_modules', 'browserify'), 84 | path.join('node_modules', 'react-tools'), 85 | path.join('node_modules', 'node-haste'), 86 | path.join('node_modules', 'convert-source-map'), 87 | path.join('node_modules', 'async'), 88 | path.join('node_modules', 'source-map'), 89 | path.join('node_modules', 'contextify'), 90 | path.join('node_modules', 'optimist') 91 | ]; 92 | 93 | var BLACKLIST_FILE_EXTS_RE = 94 | new RegExp('(' + BLACKLIST_EXTS.join('|') + ')'); 95 | 96 | var pathRegex = function(paths) { 97 | return new RegExp('^(' + paths.join('|') + ')'); 98 | }; 99 | 100 | var ensureBlacklistsComputed = function(buildConfig) { 101 | if (buildConfig._reactPageBlacklistRE && buildConfig._middlewareBlacklistRE) { 102 | return; 103 | } 104 | var reactPageDevDependencyPaths = REACT_PAGE_DEPENDENCIES.map(function(name) { 105 | return path.resolve(buildConfig.projectRoot, 'node_modules', name); 106 | }).map(prepareForRE); 107 | 108 | buildConfig._reactPageBlacklistRE = pathRegex(reactPageDevDependencyPaths); 109 | // Add browser-builtins to blacklist if we don't need them. 110 | var middlewareBlacklist = !buildConfig.useBrowserBuiltins ? 111 | BLACKLIST_MIDDLEWARE_DEPENDENCIES.concat('node_modules/browser-builtins/') : 112 | BLACKLIST_MIDDLEWARE_DEPENDENCIES; 113 | var middlewareBlacklistPaths = middlewareBlacklist.map(function(relPath) { 114 | return prepareForRE( 115 | path.resolve( 116 | buildConfig.projectRoot, 117 | 'node_modules', 118 | 'react-page-middleware', 119 | relPath 120 | ) 121 | ); 122 | }); 123 | buildConfig._middlewareBlacklistRE = pathRegex(middlewareBlacklistPaths); 124 | }; 125 | 126 | /** 127 | * The `react-page` `projectRoot` is the search root. By default, everything 128 | * under it is inherently whitelisted. The user may black list certain 129 | * directories under it. They should be careful not to blacklist the 130 | * `browser-builtins` in `react-page-middleware`. 131 | * 132 | * We ignore any directory that is, or is *inside* of a black listed path. 133 | * 134 | * @param {object} buildConfig React-page build config. 135 | * @param {string} path Absolute path in question. 136 | * @return {boolean} Whether or not path should be ignored. 137 | */ 138 | var shouldStopTraversing = function(buildConfig, path) { 139 | ensureBlacklistsComputed(buildConfig); 140 | var buildConfigBlacklistRE = buildConfig.blacklistRE; 141 | var internalDependenciesBlacklistRE = buildConfig._reactPageBlacklistRE; 142 | if (BLACKLIST_FILE_EXTS_RE.test(path) || 143 | buildConfig._middlewareBlacklistRE.test(path) || 144 | internalDependenciesBlacklistRE.test(path) || 145 | buildConfigBlacklistRE && buildConfigBlacklistRE.test(path) || 146 | buildConfig.ignorePaths && buildConfig.ignorePaths(path)) { 147 | return true; 148 | } 149 | return false; 150 | }; 151 | 152 | /** 153 | * We must make sure to add react-page-middleware to the ignored search paths, 154 | * otherwise a prebuilt version of React (which still has @providesModule React) 155 | * could end up getting bundled with the pure version. Not only would it be 156 | * wasteful, but the built version requires in the form of (./Module) which 157 | * would fail. We'll also filter build/modules/ which is a common place for 158 | * React build output. 159 | * 160 | * We pass the `projectRoot` to haste as the search directory, but we have to do 161 | * some really heavy filtering along the way. 162 | */ 163 | var getHasteInstance = function(buildConfig) { 164 | if (hasteInstance) { 165 | return hasteInstance; 166 | } 167 | var loaders = buildConfig.useBrowserBuiltins ? 168 | [new BrowserShimLoader(buildConfig)] : []; 169 | loaders = loaders.concat([ 170 | new hasteLoaders.JSLoader({ // JS files 171 | extractSpecialRequires: true // support for requireLazy, requireDynamic 172 | }), 173 | new hasteLoaders.ImageLoader({}), // Images too. 174 | new hasteLoaders.CSSLoader({}), // CSS files 175 | new ProjectConfigurationLoader() // package.json files 176 | ]); 177 | var ext = {}; 178 | loaders.forEach(function(loader) { 179 | loader.getExtensions().forEach(function(e) { 180 | ext[e] = true; 181 | }); 182 | }); 183 | var finder = new SymbolicLinkFinder({ 184 | scanDirs: [buildConfig.projectRoot], 185 | extensions: Object.keys(ext), 186 | ignore: shouldStopTraversing.bind(null, buildConfig) 187 | }); 188 | return new Haste(loaders, [buildConfig.projectRoot], {finder: finder}); 189 | }; 190 | 191 | /** 192 | * Replaces relative modules, transforms JSX, and wraps each module in a 193 | * `define()`. 194 | */ 195 | var transformModuleImpl = function(mod, modName, rawCode, done) { 196 | var resolveModule = function(requiredName) { 197 | return mod.getModuleIDByOrigin(requiredName); 198 | }; 199 | try { 200 | var transformedCode = fixReactTransform(transform(rawCode, {harmony: true})); 201 | var modularizedCode = 202 | Modularizer.modularize(modName, transformedCode, resolveModule); 203 | originalSourceCache[modName] = rawCode; 204 | transformCache[modName] = modularizedCode; 205 | transformTimes[modName] = Date.now(); 206 | done(null, transformCache[modName]); 207 | } catch (e) { 208 | // Original error only includes esprima trace! 209 | var parseError = 210 | new Error('Syntax Error: ' + mod.path + ' ' + e.toString()); 211 | done(parseError); 212 | } 213 | }; 214 | 215 | /** 216 | * @param {string} str Code resulting from running transforms. 217 | * @return {string} Fixed string - removes additional trailing newline. 218 | */ 219 | var fixReactTransform = function(str) { 220 | return str.charAt(str.length - 1) === '\n' ? 221 | str.substr(0, str.length - 1) : str; 222 | }; 223 | 224 | function warmCache(orderedModulesObj, modName, done) { 225 | var mod = orderedModulesObj[modName]; 226 | if (transformTimes[modName] && transformTimes[modName] > mod.mtime) { 227 | done(null, transformCache[modName]); 228 | } else { 229 | fs.readFile(mod.path, {encoding: 'utf8'}, function(err, contents) { 230 | var error = err || (!contents ? new Error('[ERROR] no content:' + mod.path) : null); 231 | if (error) { 232 | done(error); 233 | } else { 234 | transformModuleImpl(mod, modName, contents, done); 235 | } 236 | }); 237 | } 238 | } 239 | 240 | /** 241 | * @param {object} Options - including callback for completion. 242 | */ 243 | var computePackageForAbsolutePath = function(options, onComputePackageDone) { 244 | /** 245 | * @param {string} moduleName Resolved module name, corresponding to the 246 | * absolute path provided as `options.rootModuleAbsolutePath`. 247 | * @param {Object} orderedModulesObj Key value (topologically ordered) of 248 | * dependent resources. 249 | */ 250 | var onModuleDependenciesLoaded = 251 | function(err, resolvedRootModuleID, orderedModulesObj) { 252 | if (err) { 253 | return onComputePackageDone(err); 254 | } 255 | var moduleNames = Object.keys(orderedModulesObj); 256 | var onWarmed = function(err) { 257 | if (err) { 258 | return onComputePackageDone(err); 259 | } 260 | var ppackage = new Package(); 261 | var modCount = 0; 262 | moduleNames.forEach(function(modName) { 263 | var mod = orderedModulesObj[modName]; 264 | var originalSource = originalSourceCache[modName]; 265 | var transformedSource = transformCache[modName]; 266 | modCount = ppackage.push(mod.path, originalSource, transformedSource); 267 | }); 268 | var packageErr = !modCount && 269 | new Error('No modules for:' + options.rootModuleAbsolutePath); 270 | onComputePackageDone(packageErr, resolvedRootModuleID, ppackage); 271 | }; 272 | async.each( 273 | moduleNames, 274 | warmCache.bind(null, orderedModulesObj), 275 | onWarmed 276 | ); 277 | }; 278 | 279 | HasteDependencyLoader.loadOrderedDependencies({ 280 | rootDependencies: options.runtimeDependencies, 281 | rootJSPath: options.rootModuleAbsolutePath, 282 | haste: getHasteInstance(options.buildConfig), 283 | resourceMap: getResourceMapInstance(options.buildConfig), 284 | done: onModuleDependenciesLoaded, 285 | debug: options.buildConfig.debugPackager 286 | }); 287 | }; 288 | 289 | var Packager = { 290 | computePackageForAbsolutePath: computePackageForAbsolutePath 291 | }; 292 | 293 | module.exports = Packager; 294 | -------------------------------------------------------------------------------- /src/SymbolicLinkFinder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | var fs = require('fs'); 18 | var path = require('path'); 19 | 20 | /** 21 | * Scans directories for files with given extensions (async) Will follow 22 | * symlinks. Uses node.js native function to traverse, instead of shelling out 23 | * - for safety. Invokes callback with non-real path. 24 | * 25 | * @param {Array.} scanDirs Directories to scan, ex: ['html/'] 26 | * @param {Array.} extensions Extensions to searc for, ex: ['.js'] 27 | * @param {function|null} ignore Optional function to filter out paths 28 | * @param {Function} callback 29 | */ 30 | function find(scanDirs, extensions, ignore, callback) { 31 | var result = []; 32 | var activeCalls = 0; 33 | 34 | function readdirRecursive(curDir) { 35 | activeCalls++; 36 | fs.readdir(curDir, function(err, names) { 37 | activeCalls--; 38 | if (!names) { 39 | return; 40 | } 41 | 42 | for (var i = 0; i < names.length; i++) { 43 | names[i] = path.join(curDir, names[i]); 44 | } 45 | 46 | names.forEach(function(curFile) { 47 | if (ignore && ignore(curFile)) { 48 | return; 49 | } 50 | activeCalls++; 51 | 52 | fs.lstat(curFile, function(err, stat) { 53 | activeCalls--; 54 | 55 | if (!err && stat) { 56 | if (stat.isDirectory() || stat.isSymbolicLink()) { 57 | readdirRecursive(curFile); 58 | } else { 59 | var ext = path.extname(curFile); 60 | if (extensions.indexOf(ext) !== -1) { 61 | result.push([curFile, stat.mtime.getTime()]); 62 | } 63 | } 64 | } 65 | if (activeCalls === 0) { 66 | callback(result); 67 | } 68 | }); 69 | }); 70 | 71 | if (activeCalls === 0) { 72 | callback(result); 73 | } 74 | }); 75 | } 76 | 77 | scanDirs.forEach(readdirRecursive); 78 | } 79 | 80 | 81 | /** 82 | * Wrapper for options for a find call 83 | * @class 84 | * @param {Object} options 85 | */ 86 | function SymbolicLinkFinder(options) { 87 | this.scanDirs = options && options.scanDirs || ['.']; 88 | this.extensions = options && options.extensions || ['.js']; 89 | this.ignore = options && options.ignore || null; 90 | } 91 | 92 | /** 93 | * @param {Function} callback 94 | */ 95 | SymbolicLinkFinder.prototype.find = function(callback) { 96 | find(this.scanDirs, this.extensions, this.ignore, callback); 97 | }; 98 | 99 | 100 | module.exports = SymbolicLinkFinder; 101 | module.exports.find = find; 102 | -------------------------------------------------------------------------------- /src/TimingData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule TimingData 3 | */ 4 | 5 | /** 6 | * Simple global timing data prevents having to pass around data as arguments. 7 | * This means if multiple, parallel requests are coming in simultaneously, then 8 | * results are not accurate (TODO: Fix this). 9 | */ 10 | var TimingData = { 11 | data: {} 12 | }; 13 | 14 | module.exports = TimingData; 15 | -------------------------------------------------------------------------------- /src/consts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | module.exports = { 19 | LEADING_SLASH_RE: /^\//, 20 | INDEX_JS_SUFFIX_RE: /\/index\.js[^\/]*$/, 21 | 22 | HAS_EXT_RE: /\.[^\/]*$/, 23 | // Captures the set of tags and extension in parens () 24 | ALL_TAGS_AND_EXT_RE: /\.([^\/]+)$/, 25 | 26 | 27 | // URL bundle-routing scheme: 28 | // localhost:8080/path/to/Root.x.y.z.a.b.c.d.e.f.g.bundle 29 | // \----- tags ------/ \type/ 30 | // Route types: 31 | PAGE_EXT_RE: /\.html$/, 32 | 33 | BUNDLE_EXT: '.bundle', 34 | BUNDLE_EXT_RE: /\.bundle$/, 35 | 36 | MAP_EXT: '.map', 37 | MAP_EXT_RE: /\.map$/, 38 | 39 | // Route tags: 40 | INCLUDE_REQUIRE_TAG: 'includeRequire', 41 | RUN_MODULE_TAG: 'runModule', 42 | 43 | // Misc: 44 | JS_SRC_EXT: '.js', 45 | JS_SRC_EXT_RE: /\.js[^\/]*$/ 46 | }; 47 | -------------------------------------------------------------------------------- /src/extractSourceMappedStack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | var SourceMapConsumer = require('source-map').SourceMapConsumer; 19 | 20 | var ERR_RE = /():([^:]*):(.*)/g; 21 | var SANDBOX_ERR_MSG = 'ERROR RENDERING PAGE ON SERVER:'; 22 | 23 | 24 | /** 25 | * @param {string} stack String representing call stack of error. 26 | * @return {string} Source mapped stack. 27 | */ 28 | var extractSourceMappedStack = function(ppackage, stack) { 29 | var sourceMap = ppackage.getSealedSourceMaps(); 30 | if (!sourceMap) { 31 | return "ERROR Computing Source Maps: " + stack; 32 | } 33 | var sourceMapsObj = ppackage.getSealedSourceMaps().toJSON(); 34 | var sourceMapConsumer = new SourceMapConsumer(sourceMapsObj); 35 | var mappedError = 36 | "\n\n" + 37 | stack.replace(ERR_RE, function(match, file, line, column) { 38 | var originalPosition = 39 | sourceMapConsumer.originalPositionFor({line: line, column: column}); 40 | var origFile = originalPosition && originalPosition.source; 41 | var origLine = originalPosition && (originalPosition.line); 42 | return origFile + ':' + origLine + ':' + column; 43 | }) + 44 | '\n<\/mapped error>\n'; 45 | return SANDBOX_ERR_MSG + mappedError; 46 | }; 47 | 48 | module.exports = extractSourceMappedStack; 49 | -------------------------------------------------------------------------------- /src/guard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | /** 18 | * Creates a callback that invokes the `next` function if an error occured 19 | * (first arg). Otherwise it will call `cb` with the single arg. 20 | * @param {function} cb Function to gracefully wrap. 21 | * @param {next} next Continues. 22 | * @return {function} Error handling wrapper. 23 | */ 24 | var guard = function(next, cb) { 25 | return function(err /*rest args*/) { 26 | if (err) { 27 | next(new Error(err)); 28 | } else { 29 | cb.apply(null, Array.prototype.slice.call(arguments, 1)); 30 | } 31 | }; 32 | }; 33 | 34 | 35 | module.exports = guard; 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | var DefaultRouter = require('./DefaultRouter'); 18 | var Packager = require('./Packager'); 19 | var TimingData = require('./TimingData'); 20 | 21 | var fs = require('fs'); 22 | var guard = require('./guard'); 23 | var path = require('path'); 24 | 25 | var ES5_RUNTIME_DEPENDENCIES = [ 26 | 'es5-shim/es5-shim.js', 27 | 'es5-shim/es5-sham.js' 28 | ]; 29 | 30 | var REACT_RUNTIME_DEPENDENCIES = [ 31 | 'React', 32 | 'ReactMount' 33 | ]; 34 | 35 | var validateBuildConfig = function(buildConfig) { 36 | var middlewarePath = 37 | path.resolve(buildConfig.projectRoot, 'node_modules', 'react-page-middleware'); 38 | var browserBuiltinsPath = 39 | path.resolve(middlewarePath, 'node_modules', 'browser-builtins'); 40 | if (buildConfig.useBrowserBuiltins && buildConfig.blacklistRE && 41 | buildConfig.blacklistRE.test(browserBuiltinsPath)) { 42 | throw new Error( 43 | 'You have blacklisted the browser builtins directory (' + 44 | browserBuiltinsPath + ')' + 45 | ' but you have set `useBrowserBuiltins` to be true'); 46 | } 47 | // Substring/indexOf is actually a flawed way to infer folder hierarchy! 48 | if (buildConfig.pageRouteRoot.indexOf(buildConfig.projectRoot) !== 0) { 49 | throw new Error('pageRouteRoot must be prefix of projectRoot'); 50 | } 51 | if (!fs.existsSync(buildConfig.projectRoot)) { 52 | throw new Error('ERROR: No root:' + buildConfig.projectRoot); 53 | } 54 | }; 55 | 56 | /** 57 | * TODO: We may need to call next here, if we want to allow something like a 58 | * gzip plugin. 59 | */ 60 | function send(type, res, str, mtime) { 61 | res.setHeader('Date', new Date().toUTCString()); 62 | // Always assume we had compiled something that may have changed. 63 | res.setHeader('Last-Modified', mtime || (new Date()).toUTCString()); 64 | // Would like to set the content length but it isn't clear how to do that 65 | // efficiently with JS strings (string length is not byte length!). 66 | res.setHeader('Content-Type', type); 67 | res.end(str); 68 | } 69 | 70 | exports.provide = function provide(buildConfig) { 71 | validateBuildConfig(buildConfig); 72 | /** 73 | * TODO: We can cache sign the module cache with a particular web request ID. 74 | * We generate a page with request ID x, and include a script 75 | * src="main.js?pageRenderedWithRequestID=x" so we know that we can somehow use 76 | * that old module version, saving a module cache invalidation. 77 | */ 78 | return function provideImpl(req, res, next) { 79 | if (req.method !== 'GET') { 80 | return next(); 81 | } 82 | var router = buildConfig.router || DefaultRouter; 83 | var decideRoute = router.decideRoute; 84 | var routePackageHandler = router.routePackageHandler; 85 | 86 | decideRoute(buildConfig, req.url, function(err, route) { 87 | TimingData.data = {pageStart: Date.now()}; 88 | if (err || !route) { 89 | return next(err); 90 | } 91 | if (!route.contentType || !route.rootModulePath) { 92 | return next(new Error('Router must provide contentType and rootModulePath')); 93 | } 94 | var serveResult = guard(next, send.bind(null, route.contentType, res)); 95 | var onOutputGenerated = function(err, resultText) { 96 | serveResult(err, resultText); 97 | }; 98 | var onComputePackage = function(rootModuleID, ppackage) { 99 | TimingData.data.findEnd = Date.now(); 100 | routePackageHandler(buildConfig, route, rootModuleID, ppackage, onOutputGenerated); 101 | }; 102 | var packageOptions = { 103 | buildConfig: buildConfig, 104 | rootModuleAbsolutePath: path.join(buildConfig.pageRouteRoot || '', route.rootModulePath), 105 | runtimeDependencies: REACT_RUNTIME_DEPENDENCIES.concat( 106 | buildConfig.skipES5Shim ? [] : ES5_RUNTIME_DEPENDENCIES 107 | ) 108 | }; 109 | var onComputePackageGuarded = guard(next, onComputePackage); 110 | Packager.computePackageForAbsolutePath(packageOptions, onComputePackageGuarded); 111 | }); 112 | }; 113 | }; 114 | 115 | /** 116 | * @param {object} buildConfig The same build config object used everywhere. 117 | * @return {function} Function that when invoked with the absolute path of a web 118 | * request, will return the response that would normally be served over the 119 | * network. 120 | * 121 | * > require('react-page-middleware') 122 | * > .compute({buildConfigOptions}) 123 | * > ('path/to/x.html', console.log.bind(console)) 124 | * 125 | * < ... 126 | * 127 | * Can also be used to compute JS bundles. 128 | */ 129 | exports.compute = function(buildConfig) { 130 | return function(requestedPath, onComputed) { 131 | if (!requestedPath) { 132 | throw new Error('Must supply file to compute build from:'); 133 | } 134 | var mockRequestedURL = 'http://localhost:8080/' + requestedPath; 135 | var noop = function() {}; 136 | exports.provide(buildConfig)( 137 | {url: mockRequestedURL, method: 'GET'}, // req 138 | {end: onComputed, setHeader: noop}, // res 139 | function(err) { // next() 140 | console.log('ERROR computing build:', err); 141 | } 142 | ); 143 | }; 144 | }; 145 | -------------------------------------------------------------------------------- /src/renderReactPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Facebook, Inc. 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 | * http://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 | "use strict"; 17 | 18 | var path = require('path'); 19 | 20 | var TimingData = require('./TimingData'); 21 | var consts = require('./consts'); 22 | var extractSourceMappedStack = require('./extractSourceMappedStack'); 23 | 24 | function createClientScript(rootModuleID, props) { 25 | return ( 26 | '' 38 | ); 39 | } 40 | 41 | function createClientIncludeScript(rootModulePath) { 42 | return ( 43 | '' 52 | ); 53 | } 54 | 55 | function createServerRenderScript(rootModuleID, props) { 56 | return ( 57 | 'var React = require(\'React\');' + 58 | 'var Component = require(\'' + rootModuleID + '\');' + 59 | 'var renderResult = React.renderToString(' + 60 | 'React.createElement(Component, '+ JSON.stringify(props) + ')' + 61 | ');' 62 | ); 63 | } 64 | 65 | function createServerRenderStaticScript(rootModuleID, props) { 66 | return ( 67 | 'var React = require(\'React\');' + 68 | 'var Component = require(\'' + rootModuleID + '\');' + 69 | 'var renderResult = React.renderToStaticMarkup(' + 70 | 'React.createElement(Component, '+ JSON.stringify(props) + ')' + 71 | ');' 72 | ); 73 | } 74 | 75 | /** 76 | * @param options { 77 | * @property {Route} rout Rout that caused request to arrive here. 78 | * @property {string} rootModuleID Module name (that you would require) 79 | * @property {Object} props Props for initial construction of the instance. 80 | * @property {string} bundleText Preconcatenated bundle text. 81 | * @property {Package} ppackage Package containing all deps of component. 82 | * @property {function} done Invoke when completed. 83 | * } 84 | */ 85 | var renderReactPage = function(options) { 86 | try { 87 | var sandboxScript = options.bundleText + '\n'; 88 | 89 | if (options.static) { 90 | sandboxScript += createServerRenderStaticScript( 91 | options.rootModuleID, 92 | options.props 93 | ); 94 | } else { 95 | sandboxScript += createServerRenderScript( 96 | options.rootModuleID, 97 | options.props 98 | ); 99 | } 100 | TimingData.data.concatEnd = Date.now(); 101 | var jsSources = createClientIncludeScript(options.rootModulePath); 102 | 103 | // Todo: Don't reflow - and root must be at ! 104 | var jsScripts = createClientScript(options.rootModuleID, options.props); 105 | if (options.serverRender) { 106 | try { 107 | var vm = require('vm'); 108 | var sandbox = {renderResult: '', console: console}; 109 | vm.runInNewContext(sandboxScript, sandbox); 110 | if (sandbox.renderResult.indexOf(' tags.' + 113 | ' Please ensure that there is nothing between and ' + 114 | ' in your app.' 115 | ); 116 | } 117 | // There's no way to render a doctype in React so prepend manually. 118 | var page = '' + sandbox.renderResult; 119 | 120 | if (!options.static) { 121 | page = page.replace( 122 | ''; 134 | options.done(null, lazyPage); 135 | } 136 | } catch (e) { 137 | options.done(e, null); 138 | } 139 | }; 140 | 141 | renderReactPage.bundleTagsForFullPage = function() { 142 | return [consts.INCLUDE_REQUIRE_TAG]; 143 | }; 144 | 145 | 146 | module.exports = renderReactPage; 147 | --------------------------------------------------------------------------------