Code Monkey home page Code Monkey logo

static-site-generator-webpack-plugin's Introduction

Build Status Coverage Status Dependency Status npm

static site generator webpack plugin

Minimal, unopinionated static site generator powered by webpack.

Bring the world of server rendering to your static build process. Either provide an array of paths to be rendered, or crawl your site automatically, and a matching set of index.html files will be rendered in your output directory by executing your own custom, webpack-compiled render function.

This plugin works particularly well with universal libraries like React and React Router since it allows you to pre-render your routes at build time, rather than requiring a Node server in production.

Install

$ npm install --save-dev static-site-generator-webpack-plugin

Usage

Ensure you have webpack installed, e.g. npm install -g webpack

webpack.config.js

const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');

module.exports = {

  entry: './index.js',

  output: {
    filename: 'index.js',
    path: 'dist',
    /* IMPORTANT!
     * You must compile to UMD or CommonJS
     * so it can be required in a Node context: */
    libraryTarget: 'umd'
  },

  plugins: [
    new StaticSiteGeneratorPlugin({
      paths: [
        '/hello/',
        '/world/'
      ],
      locals: {
        // Properties here are merged into `locals`
        // passed to the exported render function
        greet: 'Hello'
      }
    })
  ]

};

index.js

Sync rendering:

module.exports = function render(locals) {
  return '<html>' + locals.greet + ' from ' + locals.path + '</html>';
};

Async rendering via callbacks:

module.exports = function render(locals, callback) {
  callback(null, '<html>' + locals.greet + ' from ' + locals.path + '</html>');
};

Async rendering via promises:

module.exports = function render(locals) {
  return Promise.resolve('<html>' + locals.greet + ' from ' + locals.path + '</html>');
};

Multi rendering

If you need to generate multiple files per render, or you need to alter the path, you can return an object instead of a string, where each key is the path, and the value is the file contents:

module.exports = function render() {
  return {
    '/': '<html>Home</html>',
    '/hello': '<html>Hello</html>',
    '/world': '<html>World</html>'
  };
};

Note that this will still be executed for each entry in your paths array in your plugin config.

Default locals

// The path currently being rendered:
locals.path;

// An object containing all assets:
locals.assets;

// Advanced: Webpack's stats object:
locals.webpackStats;

Any additional locals provided in your config are also available.

Crawl mode

Rather than manually providing a list of paths, you can use the crawl option to automatically crawl your site. This will follow all relative links and iframes, executing your render function for each:

module.exports = {

  ...

  plugins: [
    new StaticSiteGeneratorPlugin({
      crawl: true
    })
  ]
};

Note that this can be used in conjunction with the paths option to allow multiple crawler entry points:

module.exports = {

  ...

  plugins: [
    new StaticSiteGeneratorPlugin({
      crawl: true,
      paths: [
        '/',
        '/uncrawlable-page/'
      ]
    })
  ]
};

Custom file names

By providing paths that end in .html, you can generate custom file names other than the default index.html. Please note that this may break compatibility with your router, if you're using one.

module.exports = {

  ...

  plugins: [
    new StaticSiteGeneratorPlugin({
      paths: [
        '/index.html',
        '/news.html',
        '/about.html'
      ]
    })
  ]
};

Globals

If required, you can provide an object that will exist in the global scope when executing your render function. This is particularly useful if certain libraries or tooling you're using assumes a browser environment.

For example, when using Webpack's require.ensure, which assumes that window exists:

module.exports = {
  ...,
  plugins: [
    new StaticSiteGeneratorPlugin({
      globals: {
        window: {}
      }
    })
  ]
}

Asset support

template.ejs

<% css.forEach(function(file){ %>
<link href="<%- file %>" rel="stylesheet">
<% }); %>

<% js.forEach(function(file){ %>
<script src="<%- file %>" async></script>
<% }); %>

index.js

if (typeof global.document !== 'undefined') {
  const rootEl = global.document.getElementById('outlay');
  React.render(
    <App />,
    rootEl,
  );
}

export default (data) => {
  const assets = Object.keys(data.webpackStats.compilation.assets);
  const css = assets.filter(value => value.match(/\.css$/));
  const js = assets.filter(value => value.match(/\.js$/));
  return template({ css, js, ...data});
}

Specifying entry

This plugin defaults to the first chunk found. While this should work in most cases, you can specify the entry name if needed:

module.exports = {
  ...,
  plugins: [
    new StaticSiteGeneratorPlugin({
      entry: 'main'
    })
  ]
}

Compression support

Generated files can be compressed with compression-webpack-plugin, but first ensure that this plugin appears before compression-webpack-plugin in your plugins array:

const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  ...

  plugins: [
    new StaticSiteGeneratorPlugin(...),
    new CompressionPlugin(...)
  ]
};

Related projects

  • react-router-to-array - useful for avoiding hardcoded lists of routes to render
  • gatsby - opinionated static site generator built on top of this plugin

License

MIT License

static-site-generator-webpack-plugin's People

Contributors

airtonix avatar alonbardavid avatar ambroos avatar greenkeeperio-bot avatar jahredhope avatar jstcki avatar markdalgleish avatar mushishi78 avatar thewillhuang avatar werehamster avatar zenlambda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

static-site-generator-webpack-plugin's Issues

Error: Source file not found: "main"

I have a similar config to the one in README.md:

var path = require('path');
var webpack = require('webpack');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var data = {};

module.exports = {
  entry: {
    'main': './entry.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'index.js',
    publicPath: '/static/',
    libraryTarget: 'umd'
  },
  plugins: [
    //new webpack.optimize.UglifyJsPlugin({minimize: true}),
    new StaticSiteGeneratorPlugin('main', ['/'], data)
  ],
  resolve: {
    alias: {
      'redux': path.join(__dirname, 'node_modules/redux'),
    },
    extensions: ['', '.js'],
  },
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      exclude: /node_modules/,
      include: __dirname,
    }, {
      test: /\.js$/,
      loaders: ['babel'],
      include: path.join(__dirname, '..', '..', 'src'),
    }, {
      test: /\.css?$/,
      loaders: ['style', 'raw'],
      include: __dirname,
    }],
  },
};

But I am getting an error when building: Error: Source file not found: "main"

prettified html output

coming from "opinionated" gatsby.js i want prettified html output (b/c git diffs)
so currently i use a modified static-site-generator-webpack-plugin from v2.1.1

var prettifier = require('js-beautify').html;
compiler.assets[outputFileName] = createAssetFromContents(prettifier(output));

see https://github.com/markdalgleish/static-site-generator-webpack-plugin/blob/v2.1.1/index.js#L63
but since i don't know how and when gatsby will update this dependency, i don't even know if it makes sense at all...

Problem with code splitting

In my code I have something that looks like this:

function retriveLocaleData(language, callback) {
  if (language === 'es') {
    require('bundle?name=spanish!../locales/es.js')(callback);
  } else {
    require('bundle?name=english!../locales/en.js')(callback);
  }
}

In other projects that would successfully create a separate bundle for my I18n data for that locale, but using this library I am constantly getting this error:

ReferenceError: window is not defined
    at evalmachine.<anonymous>:3:37
    at evalmachine.<anonymous>:94:10
    at ContextifyScript.Script.runInNewContext (vm.js:38:15)
    at module.exports ([snip]/node_modules/static-site-generator-webpack-plugin/node_modules/eval/eval.js:62:12)
    at Compiler.<anonymous> ([snip]/node_modules/static-site-generator-webpack-plugin/index.js:29:20)

I think I finally understand why it's happening. Webpack is adding code to the bundle that appends the second chunk to the dom under certain conditions. The code it generates is using window, which blows up while the static-site-generator-webpack-plugin is generating the HTML.

Has anyone encountered this before / can think of any solutions? It would be a real shame to lose the ability to code split while generating a static site.

confusion about nested file hierarchy styles

So this plugin is awesome, but I've read a bunch of uses and this part still doesn't make sense in the existing documentation. The extract-text-plugin will only generate one css file. So if you have a nested directory structure in your generated output, you need to dynamically point the <link> tags to different locations. For example:

public/
    | index.html             <- This file needs <link href="styles.css"/>
    | styles.css             <- extract-text-plugin generates this file
    | about/
       | index.html          <- This file needs <link href="../styles.css"/>
    | posts/
       | some-post-title/
           | index.html      <- This file needs <link href="../../styles.css"/>

You can circumvent the problem during development using webpack-dev-server or browser-sync or something that fools around with the path while serving. But I don't see how you can deploy code this way.

For example: https://css-tricks.com/css-modules-part-3-react/ has a working example using dev-server.

http://jxnblk.com/writing/posts/static-site-generation-with-react-and-webpack/ just uses dangerouslySetInnerHtml to hardcode a <style> tag into his root component. That seems pretty suboptimal.

Is there a way using the webpackStats object in your base template to dynamically generate the needed <link> tags down the hierarchy? I haven't seen any use-cases that circumvent this yet, is it a possible feature, or just a tricky implementation detail?

Thanks!

Why locals.assets do not contains .css ?

Hello. First of all I want to say thanks for the stuff. It's really cool!

But I've got some problem..

Why locals.assets do not contains .css ?

I use extrack-text-webpack-plugin for excract css from bundle.

This is my terminal:

{ main: '/main.c7d89a.js' }
{ main: '/main.c7d89a.js' }
{ main: '/main.c7d89a.js' }
{ main: '/main.c7d89a.js' }

Hash: c7d89a5b9c64c3063774
Version: webpack 1.12.13
Time: 8569ms
Asset       Size  Chunks             Chunk Names
.c7d89a.js     258 kB       0  [emitted]  main
main.c7d89a.css   71 bytes       0  [emitted]  main
index.html  885 bytes          [emitted]  
contacts/index.html  878 bytes          [emitted]  
page/1/index.html  979 bytes          [emitted]  
page/2/index.html  978 bytes          [emitted]  
+ 269 hidden modules
Child extract-text-webpack-plugin:
+ 2 hidden modules
Production server is at http://localhost:8080

And this is the entry point

const renderHTML = (component, assets) => {
    console.log(assets);
    let script = '';
    let style = '';

    if (/\.css$/.test(assets.main)) {
        style = `<link href="${ assets.main } rel="stylesheet>`;
    }
    if (/\.js$/.test(assets.main)) {
        script = `<script src="${ assets.main }"></script>`;
    }

    return `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Document</title>
            ${ style }
        </head>
        <body>
            <div id="app">${ component }</div>
            ${ script }
        </body>
        </html>
    `;
};

module.exports = (locals, cb) => {
    const history = createMemoryHistory();
    const location = history.createLocation(locals.path);
    match({ routes, location }, (error, redirectLocation, renderProps) => {
        const component = renderToString(<RoutingContext { ...renderProps } />);
        const html = renderHTML(component, locals.assets);
        cb(null, html);
    });
}```

Support glob paths

Something like:

  plugins: [
    new StaticSiteGeneratorPlugin('main', 'src/content/**/*.md', {
      // Properties here are merged into `locals`
      // passed to the exported render function
      greet: 'Hello'
    })
  ]

or

  const patterns = [
    'src/content/**/*.md',
    'src/other/**/*.md',
  ]

  ...

  plugins: [
    new StaticSiteGeneratorPlugin('main', patterns, {
      // Properties here are merged into `locals`
      // passed to the exported render function
      greet: 'Hello'
    })
  ]

Output a different filename?

I'm already using this plugin for generating an index.html file containing my statically rendered React code. However I also have a requirement to generate an XML file that contains the same statically rendered code.

I can generate the contents of the file correctly, but it always writes as index.html. Is it possible for me to specify my own filename and extension?

Thanks!

CSS imports are undefined

I'm working with the static website generator plugin (https://github.com/markdalgleish/static-site-generator-webpack-plugin) and React. Normally I don't have any problem with this but I can't figure out what's happening this time.

I've got the following loaders :

            { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]") },
            { test: /\.scss$/, loader: ExtractTextPlugin.extract("style-loader", "css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]!sass") },

and of course the plugin:

    plugins: [
        new ExtractTextPlugin("css/[name].css"),

in my webpack config. This generates the files correctly. However, when the static website plugin does its thing to generate the HTML:

import styles from "./styles.scss";

console.log(styles);

styles ends up being undefined, despite having generated the correct files.

Am I missing something here? Can provide more info if needed.

Thanks!

Why are urls hardcoded?

Is there a reason I have to hardcode every route path to get this plugin to work? Wouldn't it be more ideal to simply interpret those route paths from React Router? I'm confused as to how this is scalable if I have to write:

var routes = [
  '/path-1',
  '/path-2'
]

Cannot read property 'listenBeforeLeavingRoute' of undefined

Hi,

thank you for the plugin. Although, I can not set it up, as

a) I am getting error in console
ERROR in TypeError: Cannot read property 'listenBeforeLeavingRoute' of undefined

b) Secondly, what should I write to template.ejs file?

Please see my webpack.config.js content:

const
    path = require('path'),
    StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');

const scope = { window: {} };

const paths = [
    '/hello/',
    '/world/'
];

var config = {
    context: path.join(__dirname, 'src'),
    entry: [
        './main.js'
    ],
    output: {
        path: path.join(__dirname, 'www'),
        filename: 'bundle.js',
        /* IMPORTANT!
         * You must compile to UMD or CommonJS
         * so it can be required in a Node context: */
        libraryTarget: 'umd'
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolveLoader: {
        root: [
            path.join(__dirname, 'node_modules')
        ]
    },
    resolve: {
        root: [
            path.join(__dirname, 'node_modules')
        ]
    },
    plugins: [
        new StaticSiteGeneratorPlugin('main', paths, {
            // Properties here are merged into `locals`
            // passed to the exported render function
            greet: 'Hello'
        }, scope)
    ]
};

module.exports = config;

and my main.js file

'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import { Router, RouterContext, match, browserHistory, createMemoryHistory } from 'react-router';

import routes from './config/routes';
import template from './config/template.ejs';

// Client render (optional):
if (typeof document !== 'undefined') {
    const mount = document.getElementById('mount');

    document.addEventListener('DOMContentLoaded', function() {
        ReactDOM.render(<Router history={browserHistory} routes={routes} />, mount)
    });
}

// Exported static site renderer:
export default (locals, callback) => {
    const history = createMemoryHistory();
    const location = history.createLocation(locals.path);

    match({ routes, location }, (error, redirectLocation, renderProps) => {
        callback(null, template({
            html: ReactDOMServer.renderToString(<RouterContext {...renderProps} />),
            assets: locals.assets
        }));
    });
};

and finally my package.json content

"dependencies": {
    "babel-core": "^6.9.1",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "express": "^4.13.4",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "react-router": "^2.8.0",
    "webpack": "^1.13.1",
    "webpack-dev-middleware": "^1.6.1"
},
"devDependencies": {
    "static-site-generator-webpack-plugin": "^3.0.0"
}

What am I doing wrong? I can provide more files, if necessary.

Small Errors Will Abort Webpack Dev Server

When I'm editing a javascript file that is compiled with babel, and there's a minor syntax error when I save it the first time, an error is thrown and webpack-dev-server is taken down. Is there a way to report these errors but not take the entire server down?

API Documentation

"Error: Soure file not found". What is the first argument in StaticSiteGeneratorPlugin? I'm trying to pass the path to the file declaring the routes, but I don't know if it expects an absolute path, a relative path, or a filename, or if it expects the file to be in the same path as the webpack config.

Also, there's a typo. It should be "source" and not "soure"

custom folder for statically built html files

I have a setup where I want all of the html files in an html folder, and all of the assets (also built with webpack) in an assets folder. Is there any way to specify a custom output folder? This is not required, but it would clean up subsequent steps of the build process.

Basically, html files are copied into a current s3 folder, while all of the assets are accessed via $DIFFERENT_S3_URL/$HASH/assetName. aws s3 sync allows me to sync only html files, so what I'm hoping for isn't required, but it would clean things up. Thanks!

How to use together with split-by-name-webpack-plugin?

This plugin allows the build output to be split into multiple bundles: split-by-name-webpack-plugin

... however, when used together with static-site-generator-webpack-plugin, I get the following error:

ERROR in ReferenceError: webpackJsonp is not defined
    at app:11:8
    at webpackUniversalModuleDefinition (app:3:20)
    at app:10:3
    at ContextifyScript.Script.runInNewContext (vm.js:18:15)
    at module.exports (/code/my-project/node_modules/static-site-generator-webpack-plugin/node_modules/eval/eval.js:62:12)
    at Compiler.<anonymous> (/code/my-project/node_modules/static-site-generator-webpack-plugin/index.js:30:20)

...

Googling around, it looks it is similar to
this error encountered with CommonChunksPlugin
and the solution could be related to not all chunks being included when attempting to generate.

Digging into the source, in index.js:

      var source = asset.source();
      var render = evaluate(source, /* filename: */ self.renderSrc, /* scope: */ undefined, /* includeGlobals: */ true);

It looks like assuming just 1 source here could be related to that.

Is there a way to specify that this plugin should use multiple sources or multiple chunks?

HTML minification

Hi. First of all thanks for a great plugin.

Is there a way to minify the prerendered html using html-minifier?

P.S. html-minifier depends on other modules that don't play well with webpack compilation.

Minification attempt:

minify(ReactDOMServer.renderToString(<RoutingContext {...renderProps} />), locals.minify)

...results in:

ERROR in ../~/uglify-js/tools/node.js
Module not found: Error: Cannot resolve module 'fs' in $PROJECT_DIR/node_modules/uglify-js/tools
 @ ../~/uglify-js/tools/node.js 2:9-22

ERROR in ../~/clean-css/lib/clean.js
Module not found: Error: Cannot resolve module 'fs' in $PROJECT_DIR/node_modules/clean-css/lib
 @ ../~/clean-css/lib/clean.js 29:9-22

ERROR in ../~/clean-css/lib/imports/inliner.js
Module not found: Error: Cannot resolve module 'fs' in $PROJECT_DIR/node_modules/clean-css/lib/imports
 @ ../~/clean-css/lib/imports/inliner.js 1:9-22

ERROR in ../~/clean-css/lib/utils/input-source-map-tracker.js
Module not found: Error: Cannot resolve module 'fs' in $PROJECT_DIR/node_modules/clean-css/lib/utils
 @ ../~/clean-css/lib/utils/input-source-map-tracker.js 3:9-22

How to debug evaluated code?

The errors produced by the eval statement are incomprehensible to me.

I have a dozen or so safe document references in my code. However, one causes an error, and I can't pin point which one it is. The error I'm getting is:

ERROR in ReferenceError: document is not defined
    at Function.t.e (evalmachine.<anonymous>:1:812)
    at Object.getComponent (evalmachine.<anonymous>:1:25003)
    at n (evalmachine.<anonymous>:25:40385)
    at evalmachine.<anonymous>:25:40509
    at evalmachine.<anonymous>:25:19343
    at Array.forEach (native)
    at r (evalmachine.<anonymous>:25:19321)
    at r (evalmachine.<anonymous>:25:40483)
    at n (evalmachine.<anonymous>:25:38302)
    at u (evalmachine.<anonymous>:25:35271)

Unfortunately, those line and character numbers don't even line up with the compiled main.js file.

I had no luck with node --inspect and vm.runInNewContext/vm.runInThisContext.

What is the intended way of debugging this?

Usage with single page app

Hi, I know this plugin aims to generate the static pages but I wonder if there is any documentation around generating the static pages that includes the index.js that will perform as a Single Page App for future requests.

When I add libraryTarget: 'umd' to my output webpack config, I'm not able to use the index.js in my html template.

The work around I'm going for now is having two webpack config file that runs in sequence, one to generate the static page with a index-static.js output that I'm not using at all (not even sure if I will ever use it). The other config is the regular webpack dist with the index.js that I use as the script for single page app.

Plugin ignores webpack loaders (such as babel)

If a project is using some webpack javascript loaders, such as babel for ES6 features, the output of these loaders is ignored by this plugin - instead original source files are executed (evaled) for the static page generation.

This results in strange non-isomorphic behavior when the same code runs fine on the client (because it is already processed by loaders such as babel transpiler) and fails during static site generation by this plugin (because the runtime engine does not support that ES6 feature).

In my example I was using babel to transpile from ES6 to standard JavaScript. I used this code in one react component:

//find object on array of objects if it's property name is equal to local variable name
//this can be rewritten in hundred other ways without ES6, but that is not the point
var product = data[data.findIndex(p => p.name == name)];

The findIndex function is ES6 new feature not interpreted by standard node 0.12.x. and most browsers. When the code was processed by webpack with the babel loader, the resulting transpiled code did run just fine in a browser. But when the same code is run by this plugin, it fails with an error and does not generate a static html page. The exact error message is ERROR in undefined is not a function.

In my case the workaround is using node --harmony to run webpack, because node 0.12.x in harmony mode just happens to supports this feature. But there are other ES6 features supported by babel, that might not get solved by --harmony flag. Also switching from node 0.12.x to 4.x might help with some features, but your mileage may vary (I have not tried this one, but 4.x supports greater ES6 feature set). Using polyfills is another option, but this kind of duplicates the usage of transpiler...

TL;DR; Please change this plugin implementation to use javascript as outputted by webpack, not as the original source to webpack.

CSS preprocessor implementation.

Goal: hot reload, compile .scss file and then include it in root.jsx inline.

I've been trying to use loaders and then var css = require('./base.css'); but I get errors. Module not found even if in the same directory. Anyone know how to go about doing this?

404 Not Found Page

Is there a way to have the plugin generate a 404.html page?

// routes.js
import React from 'react';
import {IndexRoute, Route} from 'react-router';
import App from './components/App';
import Home from './components/Home';
import About from './components/About';
import NotFound from './components/NotFound';

const routes = (
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="about" component={About}/>
    <Route path="*" component={NotFound}/>
  </Route>
);

export default routes;
// NotFound.js
import React from 'react';

class NotFound extends React.Component {
  render() {
    return (
      <h1>404 - Not Found</h1>
    );
  }
}

export default NotFound;

Weird error message on some platforms

This plugin works fine on my macbook pro (OS X 10.10) but doesn't seem to work on Debian (not sure if it's really related to the platform but that's the only difference I could think about).

The error message isn't really helpful:

ERROR in Symbol is not defined

Best way to not include server rendering in client build

Is there a good way to not include the default export in the optimized client build? The way I'm doing this now is defining two entry points main and static where this plugin uses static to generate the pages. However, I'm unsure how negatively this effects webpack's performance (now it has to build a lot of the same things twice). Is there a good idiomatic solution to avoiding this? If there is could it be included in the README?

How to select asset with extracted CSS file?

Hi! ๐Ÿ˜ƒ

I've some trouble with using your plugin with extract-text-webpack-plugin.

Here is a part of my Webpack v.2.0 config:

import webpack from 'webpack'
import path from 'path'

import ExtractTextPlugin from 'extract-text-webpack-plugin'
import StaticSiteGeneratorPlugin from 'static-site-generator-webpack-plugin'

export default {
    entry: {
        main: './components/Root/index.jsx',
    },
    output: {
        path: path.join(process.cwd(), './public'),
        filename: 'js/[name].[hash].js',
        libraryTarget: 'umd',
    },

    plugins: [
        new ExtractTextPlugin({
            id: 'style',
            name: 'style',
            filename: 'css/[name].[hash].css',
            disable: false,
            allChanks: true,
        }),
        new StaticSiteGeneratorPlugin('main', '/', {}),
    ],

    module: {
        loaders: [{
            test: /\.jsx?$/,
            exclude: [
                '/node_modules/',
                '/public/',
            ],
            loader: 'babel-loader',
        }, {
            test: /\.css$/,
            loader: ExtractTextPlugin.extract({
                fallbackLoader: 'style-loader',
                loader: 'css-loader?modules&localIdentName=[local]--[hash:base64:5]' +
                        '!postcss-loader',
            }),
        }],
    },
}

This is entry ./components/Root/index.jsx file:

import React from 'react'
import ReactDOMServer from 'react-dom/server'
import HTMLDocument from 'react-html-document'

import App from '../App/index.jsx'

export default function (locals, callback) {
    const MyHtml = (
        <HTMLDocument
            title='My Page'
            metatags={[
              { name: 'description', content: 'My description' },
            ]}
            scripts={[ `${locals.assets.main}` ]}
            stylesheets={[ `${locals.assets.main}` ]}>
            <App />
        </HTMLDocument>
    )

    const html = ReactDOMServer.renderToStaticMarkup(MyHtml, locals.assets)
    callback(null, '<!doctype html>' + html)
}

This is an output:

Hash: af561446bacb80803dea
Version: webpack 2.1.0-beta.27
Time: 22030ms
                               Asset     Size  Chunks             Chunk Names
0a7d43a8297f9411d0f57fc63bc3d4e0.jpg  21.2 kB          [emitted]  
504a3a6c4941f1d80e195f24622e8be1.png  34.3 kB          [emitted]  
     js/main.af561446bacb80803dea.js   175 kB       0  [emitted]  main
   css/main.af561446bacb80803dea.css   269 kB       0  [emitted]  main
  js/main.af561446bacb80803dea.js.gz  51.6 kB          [emitted]  
                          index.html  5.68 kB          [emitted]  
         assets/apple-touch-icon.png  4.23 kB          [emitted]  
                  assets/favicon.ico  1.15 kB          [emitted]  

So. How can I select .css file with the same classname? I'm confused, could you help me please

Run client side

Hello,

I've been playing around with this Static Site Generator for a while, thanks for the work you've put in! It's a great idea. ๐Ÿ‘

I do have a few questions though, and I hope someone can lead me towards the right direction.

Me and @henriquea have spent a few hours trying to get client side code to run.

By client side code, I mean basic React method, such as:

componentDidMount()
setState()

I guess, that I was expecting this to work on the client side as well as just having the initial load being static.

Maybe I am approaching this the wrong way, but can anyone help?

I couldn't find anything on the documentation about not being able to run it client side :/

PS: Also, if using with any external libraries, such as fastClick that checks for navigator or window it breaks, for obvious reasons. ๐Ÿ˜’

/cc @KyleAMathews I saw your feedback on #2 and though they were very relevant. I was wondering if maybe you'd be able to help me? Or tell me if Gatsby is an alternative? Thanks!

Serve static pages by webpack-dev-server

This is probably not an issue, but feature request. Is there a way to serve html files generated by this plugin by webpack-dev-server dynamically? I can get the dev server to serve already generated files from the output directory, but I would like the webpack-dev-server to rerun generation by the plugin whenever sources change and serve this up-to-date html files. Any ideas how to do this?

GatsbyJs + react-translate = context is lost during build

I did a lot of digging and I think the issue is related to this plugin.

In gatsby-browser.js

import React from 'react';
import { TranslatorProvider } from "react-translate";
const translations = require('./i18n');

exports.wrapRootComponent = (Root) => {
  const Wrapper = () => (
    <TranslatorProvider translations={translations}>
      <Root />
    </TranslatorProvider>
  )
  return Wrapper;
}

In my Home.js component

import React from 'react';
import { translate } from "react-translate";
const Home = ({ t }) => {
  return (
    <div>
      {t("tagline1")}
    </div>
  );
}
export default translate("Home")(Home);

It works great during development but then when the project has to be built with gatsby build using this webpack plugin, it's failing because seems like the context is lost

Error: TypeError: translator is not a function

I think this might be related to the same issue people had with the new react-router context, do you have any advice how to fix this?

Is static-site-generator-webpack-plugin supposed to work with a <Provider> component in React that is adding a context to all the children elements?

Thank you ๐Ÿ˜„

Not sure why static files not rendering

in react-dev/ I have my react configuration/files/components, webpack is in the root of the project /webpack.config.js and same with package.json

I have tried to model the project in accordance with https://github.com/Bernie-2016/how-to-vote that I saw in issue #34 , although it is still not working. I'm unsure why, I was wondering if maybe you all could help?

My repo is :
https://github.com/InsidiousMind/material-bliss-jekyll-theme/tree/cec1552d5e18f348101f9f7127b6579eea360b04

Running "webpack-dev-server" with the "--inline" option causes a "document not defined" error

Repo that reproduces the issue. See the README there for more deets.

Typically, when using webpack-dev-server, the --inline option is preferred for development since it will do fast page reloads, and is required if you want to do hot module reloading. It would be extremely helpful if this plugin could accommodate webpack-dev-server's reliance on document to serve the site.

If instead this issue should be opened on webpack-dev-server, just let me know. I'm not exactly sure what the best course of action is. If it's any help, webpack-dev-server refers to document here.

Bluebird makeNodePromisified is not a function

When I run my site without target: 'node' in the webpack.config I get the the following error.

ERROR in TypeError: makeNodePromisified is not a function
    at promisifyAll (eval at <anonymous> (app:2866:2), <anonymous>:3422:19)
    at Function.Promise.promisifyAll (eval at <anonymous> (app:2866:2), <anonymous>:3468:13)
    at eval (eval at <anonymous> (app:3028:2), <anonymous>:9:25)
    at Object.<anonymous> (app:3028:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:3022:2), <anonymous>:1:15)
    at Object.<anonymous> (app:3022:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:1900:2), <anonymous>:14:29)
    at Object.<anonymous> (app:1900:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:1894:2), <anonymous>:15:18)
    at Object.<anonymous> (app:1894:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:1888:2), <anonymous>:13:15)
    at Object.<anonymous> (app:1888:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:1882:2), <anonymous>:13:13)
    at Object.<anonymous> (app:1882:2)
    at __webpack_require__ (app:30:30)
    at eval (eval at <anonymous> (app:154:2), <anonymous>:54:15)
    at Object.<anonymous> (app:154:2)

I'm pretty sure this is an issue with Bluebird being optimized for the browser. Running the configuration with target: 'node' doesn't quite feel right as I think it adds in node code that I don't want.

Has anyone ran into this issue or have any suggestions?

Build .html file *without* a correlating .js file?

Hey there.

Are we not able to generate the html files without a js file for each one?

For example, could this:

        entry: {
            'splash-page': './src/static/splash_page.ts',
            'app-index': './src/static/app_index.ts',
        }

just output:

/splash_page.html
/index.html

not

/splash-page.html
/splash-page.js
/app-index.html
/app-index.js

Thanks!

Adding webpack.optimize.UglifyJsPlugin freezes the generation

First thanks for working on this project.

Whenever I'm trying to add webpack.optimize.UglifyJsPlugin to my list of webpack plugins, the generation freezes, no console errors. This is my webpack configuration:

require('babel-register');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var path = require('path');
var webpack = require('webpack');

const routes = require('./src/routes')('/');
const paths = [
  '/', '/develop/learn', '/develop/hello-world'
];

module.exports = {

  entry: {
    'main': './src/index.js'
  },

  output: {
    filename: 'index.js',
    path: './dist',
    libraryTarget: 'umd'
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /(node_modules\/)/
      },
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.svg$/,
        loader: 'file-loader?mimetype=image/svg'
      },
      {
        test: /\.jpg$/,
        loader: 'file-loader?mimetype=image/jpg'
      },
      {
        test: /\.png$/,
        loader: 'file-loader?mimetype=image/png&name=assets/[name].png'
      },
      {
        test: /\.ico$/,
        loader: 'file-loader?mimetype=image/x-icon&name=assets/[name].ico'
      },
      {
        test: /\.woff$/,
        loader: 'file-loader?mimetype=application/font-woff'
      },
      {
        test: /\.otf$/,
        loader: 'file-loader?mimetype=application/font/opentype'
      },
      {
        test: /\.txt$/,
        loader: 'file-loader?mimetype=text/plain&name=[path]/[name].[ext]'
      },
      {
        test: /\.scss$/,
        loader: "file?name=assets/css/[name].css!sass?" +
          'includePaths[]=' +
          (encodeURIComponent(
            path.resolve(process.cwd(), './node_modules')
          )) +
          '&includePaths[]=' +
          (encodeURIComponent(
            path.resolve( process.cwd(),
            './node_modules/grommet/node_modules'))
          )
      }
    ]
  },

  plugins: [
    new StaticSiteGeneratorPlugin(
      'main', paths , { routes }
    ),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: true
      }
    })
  ]
};

If I remove UglifyJsPlugin everything works fine again.

Error: Export from "app" must be a function that returns an HTML string

I am trying to use this library in combination with react router.

my webpack plugins has:

  entry: {
    app: ['src/app.js']
  },
  plugins: [
    new StaticSiteGeneratorPlugin('app', ['/']),
  ]

and then

// src/app.js

if (typeof document !== 'undefined') {
  // do client side rendering
}

const template = (htmlRenderedByReact, assets) => `
   <!doctype html>
   <html lang='en'>
    <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
      <div class='js-app-shell'>${htmlRenderedByReact}</div>
      <script src='${assets.index}'></script>
    </body>
  </html>
`;

// Exported static site renderer:
export default (locals, callback) => {
  const history = createMemoryHistory();
  const location = history.createLocation(locals.path);
  match({ routes, location }, (error, redirectLocation, renderProps) => {
    callback(null, template(
      ReactDOMServer.renderToString(<RoutingContext {...renderProps} />),
      locals.assets,
    ));
  });
};

however, I get the error Error: Export from "app" must be a function that returns an HTML string.

Issue When Using React Router With Different Components

Description

With the router defined in the code below, I'm attempting to have each route render the html from its specified component. My current understanding is that the "/" path should render the Base component and the IndexRoute should cause the About component to also load at the "/' path.

NOTE: I'm using react-router 1.0.2

// Routes.jsx
import React from 'react';
import {Router, Route, IndexRoute} from "react-router";

import {Portfolio} from './components/portfolio.js';
import {About} from './components/about.js';
import {Base} from './components/base.js';
import {Blog} from './components/blog.js';
// let About = require('./components/index.js')

let Routes = (
    <Route path="/" component={Base}>
      <IndexRoute component={About} />
      <Route path="/portfolio" component={Portfolio}/>
      <Route path="/blog" component={Blog}/>
    </Route>
)

export {Routes};

Original issue (Solved):

After running webpack the website generator plugin creates static pages in the correct location but they all end up with the same html. I defined a different component to each path just as a test yet they all render the component or html from the top level route (in this case "Base").

Below is my Entry file

entry.js

import path  from 'path';
import React from "react";
import ReactDOMServer from 'react-dom/server';
import {EventEmitter} from "events";
import { createHistory, createMemoryHistory } from 'history';
import { Router, RoutingContext, match } from 'react-router';

import {Portfolio} from './components/portfolio.js';
import {About} from './components/about.js';
import {Base} from './components/base.js';
import {Routes} from './routes.js';

module.exports = function render(locals, callback) {
  let history = createMemoryHistory(locals.path);

  let html = ReactDOMServer.renderToStaticMarkup(
    React.createElement(Router, {'history': history}, Routes )
  );

  callback(null, html);
};

Finally here is my webpack.config.js file

var path = require('path');
var webpack = require('webpack');
var data = require('./data.js');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');


module.exports = {
  entry: './dev/app/entry.js',

  output: {
    filename: 'bundle.js',
    path: __dirname,
    libraryTarget: 'umd'
  },

  module: {
      loaders:[
        {
          test: /\.js$/,
          exclude: /(node_modules)/,
          loaders:['babel?presets[]=es2015,presets[]=react'],
        }
      ]
    },

  plugins: [
    new StaticSiteGeneratorPlugin('bundle.js', data.routes, data)
  ]
}

I'm not sure what I'm doing wrong. I've tried to follow the react router example on the README but I'm having trouble understanding exactly what the default render function is doing. How is match used ??? And the template function ??

Also, I'm unsure of how the required files are setup.

I am really looking forward to finally figuring this out. I've spent a long time on it. I appreciate if you are able to bring some clarification in setting things up correctly with different html components.

Please let me know if anything is unclear or additional information is needed.


Update

After some more grinding through the code I figured out my issue. I can't say I completely understand it as some of it has to do with how React Router is written but It works. I can dive more into React Router code later if need be.

First my router top level component needed to use {{this.props.children}} in the rendered Base Component that I have referenced. This way it will also render the IndexRoute component inside of the top level or "Base" Component.

Next I needed to change my render function to look like this. I wasn't using

<RoutingContext {...renderProps} />

Also the es6 destructuring syntax was not as it should be considering how I had my routes component being imported. I simply changed {Routes, location} to {routes: Routes, location}.

New Entry.js file

import path  from 'path';
import React from "react";
import ReactDOMServer from 'react-dom/server';
import {EventEmitter} from "events";
import { createHistory, createMemoryHistory } from 'history';
import { Router, RoutingContext, match } from 'react-router';
import Routes from './routes.js';


// version with match...
module.exports = function render(locals, callback) {
  let history = createMemoryHistory(locals.path);
  let location = history.createLocation(locals.path);

  match( { routes:Routes, location:location }, (error, redirectLocation, renderProps) => {

    function generateHTML() {
      return ReactDOMServer.renderToStaticMarkup(
        <RoutingContext {...renderProps} />
      )
    }

    callback(null, generateHTML());
  })

};

New Problem

How do I setup "live reloading" with scss that is being applied to the statically generated components? Currently I have my styles being extracted into a separate css file. Unfortunately I've read that there is no support for hot reloading the styles this way.... How do I setup hot reloading with scss that needs to be applied to components that will be statically rendered ? Keep in mind I tried using the traditional method of style!css!sass loader but I run into the issue of the Window not being defined because it executes before its in the browser environment. Suggestions / solutions welcomed. Right now I have to manually refresh my page to style the react components being statically rendered.

How to with HtmlWebpackPlugin?

my config is the following

const { join } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  context: __dirname,
  entry  : {
    main: './src/app.js'
  },
  output : {
    path         : join(__dirname, 'dist'),
    filename     : '[name].bundle.js',
    chunkFilename: '[id]_chunk.js'
  },
  module : {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.(scss|sass)$/, loaders: ['style-loader', 'css-loader', 'sass-loader'] },
      { test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader?limit=8192' },
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({m: 'mithril'}),
    new HtmlWebpackPlugin({
      title: 'MyTitle'
    })
  ],
  devServer: {
    historyApiFallback: true
  }
};

my entry point is just rendering a simple component

import m from 'mithril';
import Landing from './landing';

import './styles/theme.scss';

m.render(document.body, m(Landing));

now when Add the static site generator at the end of plugin list always got an "Error: window is not defined" maybe I'm doing something wrong with my config

Add events so other plugins can alter the rendered pages, like html plugin does

I'm using this plugin to create a static toolkit site, but I have the need to add stuff like favicon etc...., There are wonderful plugins for this, that generate the favicon files for you (https://www.npmjs.com/package/favicons-webpack-plugin), and even add it the html that comes out from the html plugin (https://github.com/jantimon/html-webpack-plugin). I would lik to see events so those plugins can also do the same here.

Or, if it can be done right now, probably in another way, that I would like to hear it.

UTF-8 support?

There seems to be an issue with UTF-8 encoded data.

When starting with webpack-dev-server, it works fine. When running it through this plugin to generate a static HTML page, it seems to break the encoding.

The contents of the file seem to be fine, but when I open the HTML in a browser it's broken. I'm processing the static HTML to a PDF later, which means it's also broken there.

Webpack code splitting with require.ensure()

Is it currently possible to build an application that makes use of webpack code splitting with require.ensure() to render routes and be able to do static rendering of these routes as well? If not, what is needed to make this happen and is it planned? Is there a workaround?

I saw #31 but this seems to only make sure require.ensure() can occur in compiled code at all - but using it inside the render method used by the static rendering plugin is not possible?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.