erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
11.99k stars 2.5k forks source link

Provide a standalone frontend branch? #1183

Open eromoe opened 8 years ago

eromoe commented 8 years ago

I am a python developer, I'd like to use react-redux as frontend, django-rest-framework as backend. But the code embed too much things, hide a lot details, such as ajax, I can't see how client communicate with server. A standalone frontend branch would make it more clear to understand how the frontend moudles cooperate.

jmarceli commented 8 years ago

I think that there is no need for standalone frontend branch. The only "switch" you need to turn off is __SSR__ inside src/server.js file. Then if you execute npm run build you will get standalone app without server rendering which might be hosted on any server with any backend (I use it with Rails). For more info (about API communication) see the docs https://github.com/erikras/react-redux-universal-hot-example/blob/master/docs/ApiConfig.md

ballwood commented 8 years ago

Hi,

Your request coincides with something I'm trying to get working actually - here's what I've done so far, its quick and dirty and helmet is broken at the moment for it but I don't have the luxury of time.

Added /src/static.js

import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import createStore from './redux/create';
import ApiClient from './helpers/ApiClient';
import {Provider} from 'react-redux';
import { Router, hashHistory } from 'react-router';
import { ReduxAsyncConnect } from 'redux-connect';
import { syncHistoryWithStore } from 'react-router-redux';
import useScroll from 'scroll-behavior/lib/useStandardScroll';

import getRoutes from './routes';

const client = new ApiClient();
const history = useScroll(() => hashHistory)();
const dest = document.getElementById('content');
const store = createStore(history, client);
const syncedHistory = syncHistoryWithStore(history, store);

const component = (
  <Router render={(props) =>
        <ReduxAsyncConnect {...props} helpers={{client}} filter={item => !item.deferred} />
      } history={syncedHistory}>
    {getRoutes(store)}
  </Router>
);

ReactDOM.render(
  <Provider store={store} key="provider">
    {component}
  </Provider>,
  dest
);

Added /src/webpack/static.config.js

require('babel-polyfill');

// Webpack config for creating the production bundle.
var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var strip = require('strip-loader');

var projectRootPath = path.resolve(__dirname, '../');
var assetsPath = path.resolve(projectRootPath, './static/build');

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));

module.exports = {
  context: path.resolve(__dirname, '..'),
  entry: {
    'main': [
      'bootstrap-sass!./src/theme/bootstrap.config.prod.js',
      'font-awesome-webpack!./src/theme/font-awesome.config.prod.js',
      './src/static.js'
    ]
  },
  output: {
    path: assetsPath,
    filename: '[name].js',
    chunkFilename: '[name].js',
    publicPath: '/'
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel']},
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  progress: true,
  resolve: {
    modulesDirectories: [
      'src',
      'node_modules'
    ],
    extensions: ['', '.json', '.js', '.jsx']
  },
  plugins: [
    new CleanPlugin([assetsPath], { root: projectRootPath }),

    // css files from the extract-text-plugin loader
    new ExtractTextPlugin('[name].css', {allChunks: true}),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      },

      __CLIENT__: true,
      __SERVER__: false,
      __DEVELOPMENT__: false,
      __DEVTOOLS__: false
    }),

    // ignore dev config
    new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),

    // optimizations
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),

    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    webpackIsomorphicToolsPlugin
  ]
};

Added /index.html

<!doctype html>
<html lang="en-us">
<head>
  </title>
  <link rel="shortcut icon" href="/favicon.ico"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <link href="main.css" media="screen, projection" rel="stylesheet" type="text/css"
        charset="UTF-8"/>
</head>
<body>
<div id="content" style="height:100%;min-height:100%;"></div>
<script src="main.js" charset="UTF-8"></script>
</body>
</html>

Edited package.json

  "scripts": {
    ...
    "build": "better-npm-run build",
    "build-static": "better-npm-run build-static",
    ...
  },
  "betterScripts": {
    ...
    "build": {
      "command": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js",
      "env": {
        "NODE_ENV": "production"
      }
    },
    "build-static": {
      "command": "webpack --verbose --colors --display-error-details --config webpack/static.config.js",
      "env": {
        "NODE_ENV": "production"
      }
    }
   ...
  },
...
}
Alexoner commented 8 years ago

@eromoe I would like the same feature so I have forked a branch to meet this requirement.Maybe you can check it out: https://github.com/Alexoner/react-redux-universal-hot-example/tree/static This branch will generate a html file dynamically using webpack isomorphic config when npm run build .

pixelomo commented 7 years ago

@jmarceli how do you disable SSR? like this?

__DISABLE_SSR__ = true;
jmarceli commented 7 years ago

It looks like this option gets renamed since I was using it, but yes, I think that __DISABLE_SSR__ = true will disable SSR.