Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## HEAD

- Allow for base path injection in `htmlSource`.

## 0.4.0

- **Bug:**: Fixes the way Underreact handles root relative urls.
Expand Down
41 changes: 36 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,26 +200,55 @@ Underreact is intended for single-page apps, so you only need one HTML page. If

You have 2 choices:

1. **Preferred:** Provide the [`htmlSource`] configuration option, which is an HTML string or a Promise that resolves to an HTML string.
1. **Preferred:** Provide the [`htmlSource`] configuration option, which is an HTML string, a Promise or a Function returning HTML string or promise, that resolves to an HTML string.
2. Provide no HTML-rendering function and let Underreact use the default, development-only HTML document. *You should only do this for prototyping and early development*: for production projects, you'll definitely want to define your own HTML, if only for the `<title>`.

If you provide a Promise for [`htmlSource`], you can use any async I/O you need to put together the page. For example, you could read JS files and inject their code directly into `<script>` tags, or inject CSS into `<style>` tags. Or you could make an HTTP call to fetch dynamic data and inject it into the page with a `<script>` tag, so it's available to your React app.

If you provide a Function for [`htmlSource`], Underreact would call it with the named parameter `basePath`. This gives you the flexibility to load assets with a root relative URL. The example below shows how to load a favicon from your `public` directory:

```js
// underreact.config.js
module.exports = {
/**
* @param {Object} opts
* @param {Webpack} opts.basePath - the normalized value of your site's base path
* @returns {Promise<string> | string}
*/
htmlSource: ({ basePath }) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Words that rhyme with fish</title>
<meta name="description" content="A website about words that rhyme with fish, like plish">
<link rel="shortcut icon" href="${basePath}/img/favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="app">
<!-- React app will be rendered into this div -->
</div>
</body>
</html>
`
};
```

**Note: Underreact would automatically inject the relevant `script` and `link` tags to your HTML template.**

In the example below, we are defining our HTML in a separate file and requiring it in `underreact.config.js`:

```js
// underreact.config.js
const htmlSource = require('./html-source');
const html = require('./html');

module.exports = function underreactConfig({ webpack, command, mode }) {
return {
htmlSource: htmlSource(mode)
htmlSource: html(mode)
};
};

// html-source.js
// html.js
const fs = require('fs');
const { promisify } = require('util');
const minimizeJs = require('./minimize-js');
Expand Down Expand Up @@ -460,7 +489,7 @@ Enable hot module reloading of Underreact. Read ["How do I enable hot module rel

### htmlSource

Type: `string`\|`Promise<string>`. Default:[see the default HTML](https://github.com/mapbox/underreact/blob/next/lib/default-html.js).
Type: `string`\|`Promise<string>`\|`Function<string | Promise<string>>`. Default:[see the default HTML](https://github.com/mapbox/underreact/blob/next/lib/default-html.js).

The HTML template for your app, or a Promise that resolves to it. Read ["Defining your HTML"](#defining-your-html) for more details.

Expand Down Expand Up @@ -543,6 +572,8 @@ Path to the base directory on the domain where the site will be deployed. The de

This normalization behaviour comes in handy when writing statements like `process.env.BASE_PATH + '/my-path'`. Read ["How do I include SVGs, images, and videos?"](#how-do-i-include-svgs-images-and-videos).

Underreact also passes this as a named parameter to the [`htmlSource`] function. Read ["Defining your HTML"](#defining-your-html) for more details.

**Tip**: There's a good chance your app isn't at the root of your domain. So this option represents the path of your site *within* that domain. For example, if your app is at `https://www.special.com/ketchup/*`, you should set `siteBasePath: '/ketchup'`.

### stats
Expand Down
11 changes: 11 additions & 0 deletions examples/fancy/public/blink.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.blink{
animation:blinkText 1.5s infinite;
}

@keyframes blinkText{
0% { color: rgba(0,0,0,0); }
49% { color: rgba(0,0,0,0.33); }
50% { color: rgba(0,0,0,0.33); }
99% { color: rgba(0,0,0,0.33); }
100%{ color: rgba(0,0,0,0); }
}
3 changes: 3 additions & 0 deletions examples/fancy/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class App extends React.Component {
{typeof DEFINE_WORKED === 'undefined' ? 'No' : DEFINE_WORKED}
</p>
<p>Less-loading worked if this text is light blue.</p>
<p className="blink">
HTML base path injection works if this text blinks.
</p>
<p>
Value of the env variable CLIENT_TOKEN:{' '}
{process.env.CLIENT_TOKEN}
Expand Down
3 changes: 2 additions & 1 deletion examples/fancy/underreact.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

const htmlSource = `
const htmlSource = ({basePath}) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fancy examples</title>
<link href="https://api.mapbox.com/mapbox-assembly/v0.21.2/assembly.min.css" rel="stylesheet">
<link href="${basePath}/blink.css" rel="stylesheet">
<script async defer src="https://api.mapbox.com/mapbox-assembly/v0.21.2/assembly.js"></script>
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion lib/config/validate-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function validateConfig(rawConfig = {}) {
compileNodeModules: v.oneOfType(v.boolean, v.arrayOf(v.string)),
devServerHistoryFallback: v.boolean,
hot: v.boolean,
htmlSource: v.oneOfType(validatePromise, v.string),
htmlSource: v.oneOfType(validatePromise, v.string, v.func),
jsEntry: validateAbsolutePaths,
liveReload: v.boolean,
environmentVariables: v.plainObject,
Expand Down
8 changes: 7 additions & 1 deletion lib/webpack-config/generate-html-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ module.exports = function generateHtmlTemplate({
webpackCompilation,
publicPath
}) {
return Promise.resolve(urc.htmlSource).then(html =>
let htmlSource = urc.htmlSource;

if (typeof urc.htmlSource === 'function') {
htmlSource = urc.htmlSource({ basePath: urc.getBasePathEnv() });
}

return Promise.resolve(htmlSource).then(html =>
html
.replace(
'<head>',
Expand Down