• Stars
    star
    157
  • Rank 238,399 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 9 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Build full static sites using React, React Router and Webpack (Webpack 2 supported)

React Static Webpack Plugin

Build Status react-static-webpack-plugin on NPM

Build full static sites using React, React Router and Webpack

This module can be added to exiting projects, but if you're looking to start coding right now check out the React Static Boilerplate.

Install

$ npm install --save-dev react-static-webpack-plugin

Usage (Webpack 2.x)

This plugin should Just Workโ„ข with Webpack 2, so have a look at the examples below. However, some of them may contain other configuration which is specific to Webpack 1, so your best bet if something doesn't work is to check out the:

๐Ÿ‘‰ Webpack 2 Example

Usage (Webpack 1.x)

Simple Example

// webpack.config.js
const ReactStaticPlugin = require('react-static-webpack-plugin');

module.exports = {

  entry: {
    app: './client/index.js',
  },

  output: {
    path: path.join(__dirname, 'public'),
    filename: '[name].js',
    publicPath: '/',
  },

  plugins: [
    new ReactStaticPlugin({
      routes: './client/index.js',  // Path to routes file
      template: './template.js',    // Path to JSX template file
    }),
  ],

  // ... other config

};
// client/index.js
import React from 'react';
import { render } from 'react-dom';
import App from './components/App.js';

render(<App />, document.getElementById('root'));

// Be sure to export the React component so that it can be statically rendered
export default App;

Now when you run webpack you will see index.html in the output. Serve it statically and open it in any browser.

Multi-page sites with React Router

Creating sites with multiple static pages using React Router is very similar to the simple example, but instead of exporting any old React component export a <Route /> component:

// client/index.js
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';

// Since we're rendering static files don't forget to use browser history.
// Server's don't get the URL hash during a request.
import createBrowserHistory from 'history/lib/createBrowserHistory';

// Import your routes so that you can pass them to the <Router /> component
import routes from './routes.js';

render(
  <Router routes={routes} history={browserHistory} />,
  document.getElementById('root')
);
// client/routes.js
import React from 'react';
import { Route } from 'react-router';

import {
  App,
  About,
  Products,
  Product,
  Contact,
  Nested,
} from './components';

const NotFound = () => <h4>Not found ๐Ÿ˜ž</h4>;

export const routes = (
  <Route path='/' title='App' component={App}>
    <Route path='about' title='App - About' component={About} />
    <Route path='contact' title='App - Contact' component={Contact} />
    <Route path='products' title='App - Products' component={Products}>
      <Route path='product' title='App - Products - Product' component={Product}>
        <Route path='nested' title='App - Products - Product - Nested' component={Nested} />
      </Route>
    </Route>
    <Route path='*' title='404: Not Found' component={NotFound} />
  </Route>
);

export default routes;

NOTE: The title prop on the <Route /> components is totally optional but recommended. It will not affect your client side app, only the <title> tag of the generated static HTML.

Now you will see nested HTML files int the webpack output. Given our router example it would look something like this:

                     Asset       Size  Chunks             Chunk Names
                index.html  818 bytes          [emitted]
                    app.js     797 kB       0  [emitted]  app
                   app.css    8.28 kB       0  [emitted]  app
                about.html    1.05 kB          [emitted]
              contact.html    1.46 kB          [emitted]
             products.html    2.31 kB          [emitted]
      products/zephyr.html    2.45 kB          [emitted]
products/zephyr/nomad.html    2.53 kB          [emitted]
                  404.html  882 bytes          [emitted]

NOTE: When the plugin encounters <Route path='*' /> it will assume that this is the 404 page and will name it 404.html.

Full Example

For a full examples you can run locally, see the example/ directory or the React Static Boilerplate.

Generating index.html for every route

By default this plugin will generate a named HTML file for leaf routes, i.e. any route without child routes. Example:

<Route path='about' component={About} />
// -> 'about.html'

However you can also chose to opt in to generating an index.html file for every route by simply adding a trailing / to your path prop. Example:

// Notice the trailing slash below
//                โ†“
<Route path='about/' component={About} />
// -> 'about/index.html'

See the deep route nesting example for a complete example of generating index.html files.

Rendering State React Components (Sort of like a browser)

This plugin uses JSDOM to render your components in a pseudo browser environment. This means that everything you expect in the browser should be available to you at render time. This means that code like this won't break your build:

class Comp extends React.Component {
  constructor() {
    this.width = window.innerWidth;
    this.height = window.innerHeight;
  }

  render() {
    const { width, height } = this;
    return (
      <div style={{ width, height, }} className='Comp' />
    );
  }
}

Since JSDOM provides a window object the React component above will be able to access the global window object just fine.

Current Limitations

This plugin does not currently support all the functionality of react router. Most notably it does not support dynamic route paths. For example:

<Route path='blog' component={Blog}>
  <Route path=':id' component={Post} />
</Route>

In a standard single page app when you hit the Post component you would probably look at the ID in the URL and fetch the appropriate post. However, to build static files we need all data available to us at the time of compilation, and in this case I have yet to come up with a clever way of passing dynamic data to the plugin and correctly mapping it to HTML files.

I have some thoughts on this and am actively exploring how it might work but nothing has been implemented yet. If you have any thoughts on what this might look like please open an issue and let me know!

API

new ReactStaticPlugin({ ...options })

routes (required)

Type: string

The path to your routes component. Your routes component should be exported either as routes or the default: './client/routes.js'

template (required)

Type: string

Path to the file that exports your template React component. Example: ./template.js

With this option you can provide the path to a custom React component that will render the layout for your static pages. The function will be passed an options object that will give you access to the page title and the rendered component:

// template.js
import React from 'react';

const Html = (props) => (
  <html lang='en'>
    <head>
      <meta charSet='utf-8' />
      <meta httpEquiv='X-UA-Compatible' content='IE=edge' />
      <meta name='viewport' content='width=device-width, minimum-scale=1.0' />
      <title>{props.title}</title>
      <script dangerouslySetInnerHTML={{ __html: 'console.log("analytics")' }} />
    </head>
    <body>
      <div id='root' dangerouslySetInnerHTML={{ __html: props.body }} />
      <script src='/app.js' />
    </body>
  </html>
);

export default Html;

NOTE: Your template component will be run through Webpack using whatever transformations or loaders you already have set up for the filetype specified. For example, if you are using babel for all JS files then your template file will be run through babel using whatever settings you have set up in .babelrc.

NOTE: You can pass arbitrary data to your template component by adding to the options object passed when you initialize the plugin:

new ReactStaticPlugin({
  routes: './client/index.js',
  template: './template.js',

  // Some arbitrary data...
  someData: 'Welcome to Webpack plugins',
}),

Then access the data within your template component using props:

// template.js
import React from 'react';

const Html = (props) => (
  <html lang='en'>
    <head>
      <title>{props.title}</title>
    </head>
    <body>
      <h1>{props.someData}</h1>
      <div id='root' dangerouslySetInnerHTML={{ __html: props.body }} />
      <script src='/app.js' />
    </body>
  </html>
);

export default Html;

The props object will have everything you passed in the options object to the plugin as well as:

  • body: A string of HTML to be rendered in the document.
  • title: A string that is passed from each of your Route components
  • manifest: An object mapping asset names to their generated output filenames. This will simply map asset names to themselves unless you add the webpack-manifest-plugin. Example usage: manifest['app.js']. See the section on the manifest option below.
  • initialState: If you pass the reduxStore option you will get access to the result of calling store.getState(). NOTE: Since this plugin makes no assumptions about the shape of your app state it is up to you to stringify it and place it in the DOM if you wish to use it.

reduxStore

Type: string

Default: undefined

The path to your Redux store. This option allows you to pass a store to react-static-webpack-plugin. This allows for Redux support. The store you pass in will be used in tandem with the react-redux <Provider store={store}> component to render your Redux app to a static site.

renderToStaticMarkup

Type: boolean

Default: false

Set to true to use render output code without extra DOM attributes such as data-reactid, that React uses internally. This is useful if you want to use the React Static Webpack Plugin as a simple static page generator, as stripping away the extra attributes can save lots of bytes.

manifest

Type: string

Default: 'manifest.json'

(Optional) The output filename of a manifest file if using the webpack-manifest-plugin. This is useful if you want to have access to the manifest within your template file so that you can easily implement long-term caching.

IMPORTANT NOTE: For this to work you MUST include the webpack-manifest-plugin before this static site plugin.

Example:

// webpack.config.js
module.exports = {

  // Other config...

  plugins: [
    // Other plugins...

    new ManifestPlugin(), // Important! This must come before the ReactStaticPlugin
    new ReactStaticPlugin({
      routes: './client/routes.js',
      template: './template.js',
    }),
  ],
};
// template.js
const React = require('react');
const T = React.PropTypes;

const Html = ({ title = 'Amazing Default Title', body, manifest }) => (
  <html lang='en'>
    <head>
      <meta charSet='utf-8' />
      <meta httpEquiv='X-UA-Compatible' content='IE=edge' />
      <meta name='viewport' content='width=device-width, initial-scale=1' />
      <title>{title}</title>
      <link rel='stylesheet' href={manifest['app.css']} />
    </head>
    <body>
      <div id='root' dangerouslySetInnerHTML={{ __html: body }} />
      <script src={manifest['app.js']} />
    </body>
  </html>
);

Html.propTypes = {
  title: T.string,
  body: T.string,
  manifest: T.object.isRequired,
};

module.exports = Html;

NOTE: In the above template file the href for the stylesheet as well as the src for the script tag are specified as keys on the manifest object.

Roadmap

  • Custom HTML layout option
  • Improved testing
  • JSX templating support
  • Redux support
  • Support for dynamic routes + data (i.e. <Route path='post/:id' />)
  • Custom 404 page filename option
  • Passing all props from <Route> components and React Router to your template component as props (See #12)

Development

The source for this plugin is transpiled using Babel. Most importantly this allows us to use JSX, but it also provides access to all ES6 features. During development you probably want to watch the source files and compile them whenever they change. To do this:

To watch

npm run watch

To build

npm run build

Make sure to run the project locally to be sure everything works as expected (we don't yet have a test suite). To do this link this repo locally using NPM. From the source directory:

npm link .

Then you can link it within any local NPM project:

Now when you require or import it you will get the local version.

npm link react-static-webpack-plugin

To test

First, make sure you've installed all the test dependencies. This means installing all node_modules within the example/ directory. You can do this with the provided script.

./install_test_dependencies.sh

Now you can run the tests:

npm test

Runs ESLint, Flow type checking and the suite of Wepback tests.

Running individual tests

If there is one specific test failing and you want to run it individually you can do so. Make sure you have ava installed globally:

npm install -g ava

Then you can run invidual tests by running a command similar to this. For example, to test only the Redux tests you can run:

NODE_ENV=production DEBUG=react-static-webpack-plugin* ava --verbose  ./example/redux/test.js

The DEBUG env variable tells the plugin to be very verbose in its logging output.

License

MIT ยฉ Ian Sinnott

More Repositories

1

react-string-replace

A simple way to safely do string replacement with React components
JavaScript
605
star
2

alfred-maestro

An Alfred workflow to execute Keyboard Maestro macros.
Go
409
star
3

jstz

๐ŸŒTimezone detection for JavaScript
JavaScript
172
star
4

react-static-boilerplate

A boilerplate for building static sites with Webpack 2, React and React Router
JavaScript
108
star
5

prompta

ChatGPT UI that is keyboard-centric, mobile friendly, can syncs chat history across devices and search past conversations.
Svelte
30
star
6

asciilib

(๏พ‰โ—•ใƒฎโ—•)๏พ‰*:๏ฝฅ๏พŸโœง A library of ascii faces and kaomoji
JavaScript
18
star
7

rxjs-dash-docset

RxJS 5 documentation for Dash
JavaScript
15
star
8

browser-gopher

Search, aggregate, backup your browsing history from the command line.
Go
14
star
9

notion-utils

TypeScript
9
star
10

asciilib-workflow

Quickly search through ascii faces and kaomoji (๏พ‰โ—•ใƒฎโ—•)๏พ‰*:๏ฝฅ๏พŸโœง
JavaScript
9
star
11

zazu-emoji

โšก Fast, offline emoji search for Zazu
JavaScript
8
star
12

jekyll-post

A tool for managing Jekyll from the command line
JavaScript
8
star
13

react-static-presentation

The Slide deck and examples for my React Static talk
JavaScript
8
star
14

darkly-darker-theme

A dark Chrome theme
JavaScript
7
star
15

one-dark-tab

Like OneTab, but darker.
JavaScript
7
star
16

nightmare-ava-example

JavaScript
6
star
17

webpack-base-project

A minimal Webpack project to teach you how to set up a new project
JavaScript
6
star
18

things-2do-importer

Import 2Do tasks into Things 3
Python
6
star
19

mailstring

Generate mailto strings for fun and profit. Also a React component ๐Ÿ“ค
JavaScript
5
star
20

zazu-dark-theme

๐Ÿ•ถ A simple, dark theme for Zazu
CSS
5
star
21

shirt

๐Ÿ‘• Put a shirt on that data! Simple algebraic data types
JavaScript
4
star
22

iansinnott.github.io

The blog of Ian SInnott
HTML
4
star
23

app-time

๐ŸŒŸ Build complete, wonderful, beautiful apps with one dependency. Compile to static HTML files with a single command.
JavaScript
4
star
24

google-sheets-backend

JavaScript
3
star
25

express-middleware-lecture

Source code and writeup for my lecture on Express middleware
JavaScript
3
star
26

stylite

๐ŸŽจ A super lightweight style editor. Apply any styles you want to any site.
JavaScript
3
star
27

chinese-common-wordlist-pleco-decks

JavaScript
2
star
28

real-time-stack

JavaScript
2
star
29

imessage-backup-helpers

JavaScript
2
star
30

react-boilerplate

React + Webpack + Hot Reloading ๐ŸŽ‰
JavaScript
2
star
31

bitbucket-cli

A CLI for BitBucket
JavaScript
2
star
32

asciilib-site

The website for asciilib
TypeScript
2
star
33

slush-express-isinn

Generate Express apps with Slush.
CSS
1
star
34

markdown-to-csv

JavaScript
1
star
35

emoji-annotations

JavaScript
1
star
36

character-frequency-workflow

JavaScript
1
star
37

gatsby-notion

TypeScript
1
star
38

url-spider

TypeScript
1
star
39

jquery-ui-dropdown

A simple dropdown widget for jQuery UI
JavaScript
1
star
40

trimstring

๐Ÿ”ช Neatly trim template strings
JavaScript
1
star
41

mini-redux

A simple imitation of Redux implemented in a single React component
JavaScript
1
star
42

sqlite-syncta

Experimenting with syncing sqlite databases
Go
1
star
43

eslint-config-zen

An ESLint config for use with the latest ESNext features, React and Flow
JavaScript
1
star
44

electron-auto-update

TypeScript
1
star
45

addr.fyi

JavaScript
1
star
46

how-the-web-works

An over simplified explanation of how the web works
JavaScript
1
star
47

history-to-finda

Clojure
1
star
48

notedown

A note-taking app...
JavaScript
1
star
49

record-video

An example of recording on mobile with the front-facing camera.
TypeScript
1
star
50

baby-lisp-interpreter

Clojure
1
star
51

try-instantdb

Trying out InstantDB
TypeScript
1
star
52

next-tailwind-typescript-starter

TypeScript
1
star
53

authguardian-react-starter

JavaScript
1
star
54

lab.iansinnott.com

๐Ÿ”ฌ Where I run experiments and learn by doing
JavaScript
1
star