diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc6caf..15b170e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 50ee594..3f97b34 100644 --- a/README.md +++ b/README.md @@ -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 ``. 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 + + + + +
+ +
+ + + ` +}; +``` + **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'); @@ -460,7 +489,7 @@ Enable hot module reloading of Underreact. Read ["How do I enable hot module rel ### htmlSource -Type: `string`\|`Promise`. Default:[see the default HTML](https://github.com/mapbox/underreact/blob/next/lib/default-html.js). +Type: `string`\|`Promise`\|`Function>`. 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. @@ -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 diff --git a/examples/fancy/public/blink.css b/examples/fancy/public/blink.css new file mode 100644 index 0000000..5ce32a4 --- /dev/null +++ b/examples/fancy/public/blink.css @@ -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); } +} \ No newline at end of file diff --git a/examples/fancy/src/app.js b/examples/fancy/src/app.js index 9912fbc..9e12e5a 100644 --- a/examples/fancy/src/app.js +++ b/examples/fancy/src/app.js @@ -34,6 +34,9 @@ class App extends React.Component { {typeof DEFINE_WORKED === 'undefined' ? 'No' : DEFINE_WORKED}

Less-loading worked if this text is light blue.

+

+ HTML base path injection works if this text blinks. +

Value of the env variable CLIENT_TOKEN:{' '} {process.env.CLIENT_TOKEN} diff --git a/examples/fancy/underreact.config.js b/examples/fancy/underreact.config.js index cd19edd..00f39da 100644 --- a/examples/fancy/underreact.config.js +++ b/examples/fancy/underreact.config.js @@ -3,7 +3,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const path = require('path'); -const htmlSource = ` +const htmlSource = ({basePath}) => ` @@ -11,6 +11,7 @@ const htmlSource = ` Fancy examples + diff --git a/lib/config/validate-config.js b/lib/config/validate-config.js index cb9b4cc..c963c88 100644 --- a/lib/config/validate-config.js +++ b/lib/config/validate-config.js @@ -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, diff --git a/lib/webpack-config/generate-html-template.js b/lib/webpack-config/generate-html-template.js index 59d7566..851a7bf 100644 --- a/lib/webpack-config/generate-html-template.js +++ b/lib/webpack-config/generate-html-template.js @@ -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( '',