├── .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('