Skip to content

Commit

Permalink
[patch] fix resolving user's htmlFile view. (#454)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored and didi0613 committed Jul 3, 2017
1 parent dcef48a commit 36b6305
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 14 deletions.
43 changes: 31 additions & 12 deletions packages/electrode-react-webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const reactWebapp = server.register({
pageTitle: "My Awesome React WebApp",
paths: {
"/{args*}": {
view: "index",
content: "<h1>Hello React!</h1>"
}
}
}
});
Expand All @@ -43,8 +43,8 @@ const config = {
pageTitle: "My Awesome React WebApp",
paths: {
"/{args*}": {
view: "index",
content: "<h1>Hello React!</h1>"
}
},
unbundledJS: {
enterHead: [
Expand Down Expand Up @@ -89,14 +89,9 @@ What you can do with the options:
- `webpackDev` `(Boolean)` whether to use webpack-dev-server's URLs for retrieving CSS and JS bundles.
- `serverSideRendering` `(Boolean)` Toggle server-side rendering.
- `htmlFile` `(String)` Absolute or relative path to the application root html file.
It must contains the following placeholders:
- `{{PAGE_TITLE}}` page title.
- `{{WEBAPP_BUNDLES}}` injected `<script>` and `<link>` tags to load bundled JavaScript and Css
- `{{PREFETCH_BUNDLES}}` `<script>` tag containing code that will contains prefetched JavaScript code
- `{{SSR_CONTENT}}` injected content rendered on server side
- `paths` `(Object)` An object of key/value pairs specifying paths within your application with their view and (optionally) initial content for server-side render
- _path_ `(Object)`
- `view` `(String)` Name of the view to be used for this path **required**
- `htmlFile` `(String)` Name of the view file to be used for this path **optional**
- `content` Content to be rendered by the server when server-side rendering is used _optional_ [see details](#content-details)
- `unbundledJS` (Object) Specify JavaScript files to be loaded at an available extension point in the index template
- `enterHead` (Array) Array of script objects (`{ src: "path to file" }`) to be inserted as `<script>` tags in the document `head` before anything else. To load scripts asynchronously use `{ src: "...", async: true }` or `{ src: "...", defer: true }`
Expand All @@ -106,15 +101,40 @@ What you can do with the options:
- `port` `(String)` The port that webpack-dev-server runs on
- `prodBundleBase` `(String)` Base path to locate the JavaScript, CSS and manifest bundles. Defaults to "/js/". Should end with "/".

### `htmlFile` view details

The top level options can specify an **optional** value `htmlFile`.

Also each _path_ can specify the same option to override the top level one.

This option specifies the view template for rendering your path's HTML output.

- If it's `undefined`, then the built-in `index.html` view template is used.

- If it's a string, then it must point to a valid view template file.
- If it's not an absolute path, then `process.cwd()` is prepended.

You can see this module's [build-in index.html](./lib/index.html) for details on how to create your own view template.

It should basically be a valid HTML file, with the following special token to be replaced by dynamically generated strings:

- `{{META_TAGS}}` - Helmet meta tags.
- `{{PAGE_TITLE}}` - The page's title.
- `{{CRITICAL_CSS}}` - Critical CSS bundle.
- `{{PREFETCH_BUNDLES}}` - The Redux store prefetch data bundle.
- `{{WEBAPP_HEADER_BUNDLES}}` - The CSS bundle for your React app
- `{{SSR_CONTENT}}` - The server side rendered content.
- `{{WEBAPP_BODY_BUNDLES}}` - The JS bundle for your React app.

### Content details

The content you specify for your path is the entry to your React application when doing Server Side Rendering.
The content you specify for your path is the entry to your React application when doing Server Side Rendering. Ultimately, a `string` should be acquired from the content and will replace the `{{SSR_CONTENT}}` marker in the view.

It can be a string, a function, or an object.

#### `string`

If it's a string, it's treated as a straight React JSX template to be rendered.
If it's a string, it's treated as a straight plain HTML to be inserted as the `{{SSR_CONTENT}}`.

#### `function`

Expand Down Expand Up @@ -149,8 +169,7 @@ const config = {
options: {
pageTitle: "My Awesome React WebApp",
paths: {
"/{args*}": {
view: "index"
"/{args*}": {}
},
unbundledJS: {
enterHead: [
Expand Down
4 changes: 3 additions & 1 deletion packages/electrode-react-webapp/lib/hapi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const registerRoutes = (server, options, next) => {
return "";
};

const routeHandler = ReactWebapp.makeRouteHandler(registerOptions, resolveContent());
const routeOptions = _.defaults({ htmlFile: v.htmlFile }, registerOptions);

const routeHandler = ReactWebapp.makeRouteHandler(routeOptions, resolveContent());

server.route({
method: v.method || "GET",
Expand Down
4 changes: 3 additions & 1 deletion packages/electrode-react-webapp/lib/react-webapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ const getIconStats = utils.getIconStats;
const getCriticalCSS = utils.getCriticalCSS;
const getStatsPath = utils.getStatsPath;

const resolvePath = path => (!Path.isAbsolute(path) ? Path.resolve(path) : path);

function makeRouteHandler(routeOptions, userContent) {
const WEBPACK_DEV = routeOptions.webpackDev;
const RENDER_JS = routeOptions.renderJS;
const RENDER_SS = routeOptions.serverSideRendering;
const html = fs.readFileSync(routeOptions.htmlFile).toString();
const html = fs.readFileSync(resolvePath(routeOptions.htmlFile)).toString();
const assets = routeOptions.__internals.assets;
const devBundleBase = routeOptions.__internals.devBundleBase;
const prodBundleBase = routeOptions.prodBundleBase;
Expand Down
23 changes: 23 additions & 0 deletions packages/electrode-react-webapp/test/data/index-1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> {{META_TAGS}} {{PAGE_TITLE}} {{CRITICAL_CSS}} {{PREFETCH_BUNDLES}} {{WEBAPP_HEADER_BUNDLES}}
</head>

<body>
<div>test html-1</div>
<div class="js-content">{{SSR_CONTENT}}</div>
{{WEBAPP_BODY_BUNDLES}}
<script>
if (window.webappStart) webappStart();
</script>
<noscript>
<h4>JavaScript is Disabled</h4>
<p>Sorry, this webpage requires JavaScript to function correctly.</p>
<p>Please enable JavaScript in your browser and reload the page.</p>
</noscript>
</body>

</html>
23 changes: 23 additions & 0 deletions packages/electrode-react-webapp/test/data/index-2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> {{META_TAGS}} {{PAGE_TITLE}} {{CRITICAL_CSS}} {{PREFETCH_BUNDLES}} {{WEBAPP_HEADER_BUNDLES}}
</head>

<body>
<div>test html-2</div>
<div class="js-content">{{SSR_CONTENT}}</div>
{{WEBAPP_BODY_BUNDLES}}
<script>
if (window.webappStart) webappStart();
</script>
<noscript>
<h4>JavaScript is Disabled</h4>
<p>Sorry, this webpage requires JavaScript to function correctly.</p>
<p>Please enable JavaScript in your browser and reload the page.</p>
</noscript>
</body>

</html>
48 changes: 48 additions & 0 deletions packages/electrode-react-webapp/test/spec/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
const Promise = require("bluebird");
const assign = require("object-assign");
const electrodeServer = require("electrode-server");
const Path = require("path");

describe("Test electrode-react-webapp", () => {
let config;
Expand Down Expand Up @@ -385,4 +386,51 @@ describe("Test electrode-react-webapp", () => {
});
});
});

it("should use top level htmlFile", () => {
configOptions.prodBundleBase = "http://awesome-cdn.com/myapp/";
configOptions.stats = "test/data/stats-test-one-bundle.json";
configOptions.htmlFile = "test/data/index-1.html";

return electrodeServer(config).then(server => {
return server
.inject({
method: "GET",
url: "/"
})
.then(res => {
expect(res.statusCode).to.equal(200);
expect(res.result).to.contain(`<div>test html-1</div>`);
stopServer(server);
})
.catch(err => {
stopServer(server);
throw err;
});
});
});

it("should use route level htmlFile", () => {
configOptions.prodBundleBase = "http://awesome-cdn.com/myapp/";
configOptions.stats = "test/data/stats-test-one-bundle.json";
configOptions.htmlFile = "test/data/index-1.html";
configOptions.paths["/{args*}"].htmlFile = Path.resolve("test/data/index-2.html");

return electrodeServer(config).then(server => {
return server
.inject({
method: "GET",
url: "/"
})
.then(res => {
expect(res.statusCode).to.equal(200);
expect(res.result).to.contain(`<div>test html-2</div>`);
stopServer(server);
})
.catch(err => {
stopServer(server);
throw err;
});
});
});
});

0 comments on commit 36b6305

Please sign in to comment.