Closed HriBB closed 8 years ago
Hey, really sorry but I'll be away on vacation for the next 2 days. I'll take a look when I get back in case you haven't found any answers by then. Do you have a sample project that demonstrates this problem?
Anyways, good luck until then!
So I finally figured out the problem. As @bitinn suggested in his reply, we should not build the server https://github.com/bitinn/node-fetch/issues/41#issuecomment-137837210. It makes sense.
So I reconfigured my server to use the ./src/server instead of the built ./dist/server in render-route middleware. Then I also reconfigured webpack to only use a single client config. The error went away, because now I'm not building the server app anymore.
BTW what are the reasons for building the server app?
Interesting. So, not to be pedantic but I'm not bundling the server, I'm bundling the client application twice - once for client distribution, and once for server consumption. The reasoning for the latter bundle is that I want to be able to import styles and other non-js assets within the client application (which is a major feature of webpack), but those sorts of imports don't work in a non-webpack environment (i.e. the Koa server). Compiling this separate bundle allows the server to simply ignore those assets.
From what I've seen, a lot of universal boilerplates simply avoid this issue by restricting you to js-only imports. This means you can directly import all your client source code freely without a build step, so long as you have some sort of JSX transformer like Babel. There's no real problem with this approach, but unless I'm missing something you're giving up a lot of what Webpack has to offer. Perhaps it's just a matter of a difference in approach: I just want basic server rendering, they want fully isomorphic/universal applications.
I'm still not convinced this is unsolvable in this starter kit, but until I get a minute to toy with it myself I guess I'll have to defer to the issue you referenced: https://github.com/bitinn/node-fetch/issues/41#issuecomment-137837210. Anyways, glad you fixed the issue!
Hmm now I understand. When I started thinking about how to implement styles it became clear to me, that in a non-webpack environment style imports wont work. ATM I am using normal sass loader in development. For production I have a different config with ExtractTextPlugin and I simply disable loading of .css and .scss files on server
require('babel/register')({
stage: 0
});
require.extensions['.css'] = function() {return null}
require.extensions['.scss'] = function() {return null}
require('./index');
And then there's this https://github.com/halt-hammerzeit/webpack-isomorphic-tools ...
I'm still a bit confused as to what is the best way to setup webpack dev server. Do you run it together with koa server or standalone? Do you use server rendering in development? If not, how do you load your initial data into redux store? I want to be able to run koa server together with webpack-dev-server in the background for hot reloading. I already had a working env, but when I started implementing styles it all fell apart :P
Aaaanyway ... I will probably switch back to bundling the client app for server :) I knew that there must have been a good reason why you did this ... and now I know.
Exactly, and at this point I'm wondering if the easiest solution is to just deprecate support for non-JS assets. If you want styles, you may just have to hop on the bandwagon of inline styles (either that or just build them separately). In fact, I started a discussion for this very topic here: https://github.com/davezuko/react-redux-starter-kit/issues/61.
Anyways, as you've discovered, the current architecture (needing to precompile the app) means that it's essentially pointless to run the Koa server and webpack-dev-server in tandem, which isn't awesome. I did recently add a (very basic) ability to inject initial state in from the server (see https://github.com/davezuko/react-redux-starter-kit/issues/57), but even still you're not able to refresh changes to the React application on the fly, which leads to a less-than-stellar developer experience.
What are your thoughts on this? If you just drop css support you save yourself a lot of headache and can do exactly what you're asking: namely run the Koa and webpack-dev-server together. You do lose a few other things in the process (aliases, dynamic asset insertion with HtmlWebpackPlugin, for example), but perhaps the tradeoff is worth it.
I agree with https://byjoeybaker.com/react-inline-styles ... inline styles are "not there yet". Need to do some research, but in the end I would like to have 1 scss file per component + global styles, overrides and vendors. All hot reloadable with webpack dev server and development server rendering :)
I hit this too. Webpack is a bit mysterious to me. Perhaps there's a way to move the isomorphic-fetch method into the koa side of things so webpack doesn't touch it?
I'm also struggling with how to package a server side API that I'd like to have live in the same tree, and eventually bundle it with the server. But I'd like to use the API when developing the client side as well.
The problem with isomorphic-fecth is that it throws an error when you try to bundle it for node with webpack. But that could easily be fixed.
Main problem is the webpack setup and workflow. For my "dream setup", my main requirement is that in development I can use webpack-dev-server with hot reload in combination with an existing server (koa in my case). I want to be able to render the application on the server and use hot reload on the client. My current setup allows me to do everything but the server rendering in dev mode. So I just need to figure out how to tell koa server to use webpack-dev-server hot reload in development. Preferably without any gulp or grunt.
In this example webpack-dev-server is started from the express server, but only in development. Then all requests to bundle are proxied from the express to dev-server. Maybe we can use proxy for a perfect setup?
Also I see that there are two ways to run the server: inline or iframe, but the docs are very basic. If only there was some good example for this ...
Back to the drawing board ... :)
@HriBB if you want the iframe mode, just append /webpack-dev-server/
to the URL. not sure how this works with React Router though, as I haven't tested it yet.
It's fairly trivial to get the Koa app to serve assets from the webpack-dev-server (I should get around to this...) but the problem is that the bundle that the server consumes to render the application can't be hot reloaded (at least to my knowledge), so after the first hot reload your server + client application get out of sync. Maybe take a look at webpack-hot-middleware and see how that fits in to things.
I ran into the same issue with isomorphic-fetch. The steps below seem to fix it:
1. Install json-loader
npm install --save-dev json-loader
2. Add the following to the start of the preLoaders
array in /build/webpack/server.js
{
test: /\.json$/,
loader: 'json-loader'
}
Then npm run compile
should work. Hope this helps.
Edit: This only gets isomorphic-fetch to compile with webpack, but you'll still get the warning about an expression in a require statement.
Thanks @crazile. Should I just add this in as a default loader so nobody else runs into this?
Hi @davezuko, it looks like being able to require('file.json')
was added in node 0.5.x so it could turn up in some universal/isomorphic libraries. I don't think there's any harm in adding support for it in the default loaders.
Ok cool, I'll add it then. Curious about why you added it as a preLoader rather than a regular loader, however. Any particular reason?
Honestly, I thought it might need to run before the other loaders, so that was the first place I tried. I just tested it now in with the regular loaders and it works fine there as well.
I realise now I didn't actually solve HriBB's issue. We both had the same problem with isomorphic-fetch, fixed it the same way by adding json-loader (turns out there's no difference between loader: 'json'
and loader: 'json-loader'
one is just shorthand for the other.) Then ran into the next warning about expressions in a require statement. This was caused intentionally by a dependency of isomorphic-fetch trying to discourage browserfying the library (presumably because it contains large json files.) That warning is what this issue was originally about and which my answer doesn't fix.
That said, adding json-loader to the stater kit will still help anyone who runs into issues with require json.
I am thinking about using two separate servers, koa for api server and express for serving react app. The reason for using express for react app is to be able to use webpack-hot-middleware which lets you use hot reloading with your existing server. Then you can pre-render your app on server and use hot reloading on client in development. What do you think about this approach? It's basically more code and more setup, but for me it makes sense. I really like koa and how I can keep flat code structure with generators. Other option is to create webpack-hot-koa-middleware :)
So I did exactly as I said above. For now it works perfectly and I hope I don't find any big problems with this setup. Good by webpack dev server :) Here is my setup if you are interested. It's not yet perfect, still need to improve the fetchInitialData() logic, but other than that I'm pretty happy.
server/index.js
import 'babel-core/polyfill';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import Express from 'express';
import serve from 'serve-static';
import favicon from 'serve-favicon';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import { reduxReactRouter, match } from 'redux-router/server';
import createHistory from 'history/lib/createMemoryHistory';
import configureStore from '../client/stores';
import routes from '../client/routes';
import {
fetchTypes,
fetchLocationsByType,
fetchLocationBySlug
} from '../client/actions';
// Server config
const host = 'localhost';
const port = 3000;
// Create express app
const app = new Express();
// Attach hot middleware in development
if (__DEV__) {
console.log('==> Using webpack-hot-middleware');
// Step 1: Create & configure a webpack compiler
const webpack = require('webpack');
const webpackConfig = require('../webpack/dev.config');
const compiler = webpack(webpackConfig);
// Step 2: Attach the dev middleware to the compiler & the server
app.use(require("webpack-dev-middleware")(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath
}));
// Step 3: Attach the hot middleware to the compiler & the server
app.use(require("webpack-hot-middleware")(compiler, {
log: console.log,
path: '/__webpack_hmr',
heartbeat: 10 * 1000
}));
}
// Serve favicon
app.use(favicon(resolve(__dirname, '..', 'static', 'favicon.ico')));
// Serve app
app.use('/app.js', (req, res) => {
res.sendFile(resolve(__dirname, '..', 'dist', 'client', 'app.js'));
});
// Serve styles
app.use('/styles.css', (req, res) => {
res.sendFile(resolve(__dirname, '..', 'dist', 'client', 'styles.css'));
});
// Prepare template for router middleware
const template = readFileSync(resolve(__dirname, '..', 'dist', 'client', 'index.html'), 'utf-8')
.replace('<div id="cb"></div>', '<div id="cb">${content}</div><script>window.__INITIAL_STATE__ = ${data};</script>');
// Render react app
app.use((req, res) => {
console.info('==> Render react application:', req.url);
// Do the redux magic
const history = createHistory();
const router = reduxReactRouter({ history, routes });
const store = configureStore(router);
// Run the redux-router
store.dispatch(match(req.url, (error, redirectLocation, renderProps) => {
if (error) {
console.log(error);
return res.status(500).end('Internal server error');
}
if (redirectLocation) {
return res.status(301).redirect(redirectLocation.pathname);
}
if (!renderProps) {
return res.status(404).end('Not found'); // TODO: render 404 on client?
}
function renderReactApp() {
// Render react app to string
const content = ReactDOMServer.renderToString(
<div className="app">
<Provider store={store}>
<ReduxRouter {...renderProps}/>
</Provider>
</div>
);
// Get initial application state from redux store
const initialState = store.getState();
// Inject content and initial state into html
const html = template.replace('${content}', content).replace('${data}', JSON.stringify(initialState));
// Return final version back to middleware
return html;
}
function fetchInitialData() {
const path = renderProps.location.pathname;
if (path === '/') {
return store.dispatch(fetchTypes());
} else if (path.indexOf('/type') === 0) {
return store.dispatch(fetchLocationsByType(renderProps.params.type));
} else if (path.indexOf('/location') === 0) {
return store.dispatch(fetchLocationBySlug(renderProps.params.location));
} else {
return Promise.resolve(true);
}
}
fetchInitialData()
.then(renderReactApp)
.then(html => res.end(html))
.catch(err => res.end(err.message));
}));
});
app.listen(port, (err) => {
if (err) {
console.error(err);
}
console.log('==> Express server running on http://' + host + ': ' + port + '/');
});
server/start.js
// Babel require hook
require('babel/register')({
stage: 0
});
// Disable styles on server
// TODO: check if this is still needed with webpack-hot-middleware
require.extensions['.css'] = function() {return null}
require.extensions['.scss'] = function() {return null}
// Global application config
global.__DEV__ = process.env.NODE_ENV !== 'production';
global.__PROD__ = process.env.NODE_ENV === 'production';
global.__CLIENT__ = false;
global.__SERVER__ = true;
// Load server
require('./index');
Closing this since this repo no longer comes with a server. I should probably find a good place to stick this post though since it might be helpful for others.
Hello
To learn node, webpack and react, I created my own starter kit based on yours. I started from scratch and slowly built it up into a fully working universal react router redux app :) I still don't understand everything, but I'm getting there. I have a problem with isomorphic-fetch though. I tested with my starter kit and also this one and the problem is the same.
Install isomorphic-fetch and json loader
Add json loader to webpack config
When I run npm run compile I get this message
The funny thing is that the app works. But I want to get rid of this error or at least fully understand it. Any ideas?