isomorphic-webpack
is a program that runs server-side and enables rendering of the same code base client- and server-side.
Put it another way, it is a service for rendering webpack applications server-side. All that can be rendered client-side (e.g. React, Angular, etc. applications) will be processed server-side and served as static HTML.
Try it!
git clone git@github.com:gajus/isomorphic-webpack-demo.git
cd ./isomorphic-webpack-demo
npm install
export DEBUG=express:application,isomorphic-webpack
npm start
This will start the server on http://127.0.0.1:8000/.
$ curl http://127.0.0.1:8000/
<!doctype html>
<html>
<head></head>
<body>
<div id='app'>
<div class="app-___style___greetings" data-reactroot="" data-reactid="1" data-react-checksum="72097819">Hello, World!</div>
</div>
<script src='/static/app.js'></script>
</body>
</html>
isomorphic-webpack
work?*-loader
loader?isomorphic-webpack
different from webpack-isomorphic-tools
, universal-webpack
, ...?import {
createIsomorphicWebpack
} from 'isomorphic-webpack';
import webpackConfiguration from './webpack.configuration';
createIsomorphicWebpack(webpackConfiguration);
/**
* @see https://webpack.js.org/configuration/
*/
type WebpackConfigurationType = Object;
/**
* @see https://github.com/gajus/gitdown#isomorphic-webpack-setup-high-level-abstraction-isomorphic-webpack-configuration
*/
type UserIsomorphicWebpackConfigurationType = {
useCompilationPromise?: boolean
};
type IsomorphicWebpackType = {|
/**
* @see https://webpack.github.io/docs/node.js-api.html#compiler
*/
+compiler: Compiler,
+createCompilationPromise: Function,
+evalBundleCode: Function,
+formatErrorStack: Function
|};
createIsomorphicWebpack(webpackConfiguration: WebpackConfigurationType, isomorphicWebpackConfiguration: UserIsomorphicWebpackConfigurationType): IsomorphicWebpackType;
{
"additionalProperties": false,
"properties": {
"nodeExternalsWhitelist": {
"description": "An array of paths to whitelist in the webpack `external` configuration. The default behaviour is to externalise all modules present in the `node_modules/` directory.",
"items": {
"oneOf": [
{
"type": "string"
},
{
"instanceof": "RegExp"
}
]
},
"type": "array"
},
"useCompilationPromise": {
"description": "Toggles compilation observer. Enable this feature to use `createCompilationPromise`.",
"type": "boolean"
}
},
"type": "object"
}
If you have a requirement for a configuration, raise an issue describing your use case.
When a runtime error originates in a bundle, the stack trace refers to the code executed in the bundle (#4).
Use formatErrorStack
to replace references to the VM code with the references resolved using the sourcemap, e.g.
const {
formatErrorStack
} = createIsomorphicWebpack(webpackConfiguration);
app.get('*', isomorphicMiddleware);
app.use((err, req, res, next) => {
console.error(formatErrorStack(err.stack));
});
ReferenceError: props is not defined
- at TopicIndexContainer (evalmachine.<anonymous>:485:15)
+ at TopicIndexContainer (/src/client/containers/TopicIndexContainer/index.js:14:14)
at WrappedComponent (/node_modules/react-css-modules/dist/wrapStatelessFunction.js:55:38)
at /node_modules/react-dom/lib/ReactCompositeComponent.js:306:16
at measureLifeCyclePerf (/node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner (/node_modules/react-dom/lib/ReactCompositeComponent.js:305:14)
at ReactCompositeComponentWrapper._constructComponent (/node_modules/react-dom/lib/ReactCompositeComponent.js:280:21)
at ReactCompositeComponentWrapper.mountComponent (/node_modules/react-dom/lib/ReactCompositeComponent.js:188:21)
at Object.mountComponent (/node_modules/react-dom/lib/ReactReconciler.js:46:35)
at /node_modules/react-dom/lib/ReactServerRendering.js:45:36
at ReactServerRenderingTransaction.perform (/node_modules/react-dom/lib/Transaction.js:140:20)
Note: References to a generated code that cannot be resolved in a source map are ignored (#5).
isomorphic-webpack
, how to use webpack loaders and dependencies that depend on the browser environment.The easiest way to start is to analyse the demo application.
To start the server:
git clone git@github.com:gajus/isomorphic-webpack-demo.git
cd ./isomorphic-webpack-demo
npm install
export DEBUG=express:application,isomorphic-webpack
npm start
This will start the server on http://127.0.0.1:8000/.
open http://127.0.0.1:8000/
isomorphic-webpack
work?Refer to the Low-level abstraction documentation.
*-loader
loader?Loaders allow you to preprocess files as you require() or "load" them. [..] Loaders can transform files from a different language like, CoffeeScript to JavaScript, or inline images as data URLs.
– https://webpack.github.io/docs/loaders.html
isomorphic-webpack
is simulating the browser environment to evaluate loaders that are designed to run in a browser, e.g. style-loader
. Therefore, all webpack loaders work out of the box with isomorphic-webpack
.
If you have found a loader that does not work, report an issue.
I have been asked a question:
I have setup https://github.com/gajus/isomorphic-webpack-demo and navigated to http://127.0.0.1:8000/. It printed 'Hello, World!'.
Then I have changed
./src/app/index.js
to sayHello, HRM!
. I was expecting the message 'Hello, World!' to change to 'Hello, HMR!' in the already open browser window. However, it didn't.The message changed to 'Hello, HRM!' only after I have refreshed the browser window.
How is this hot-reloading?
I have used the term "hot-reloading" to describe a process where the webpack bundle is rebuilt every time a file in the project changes. The change will be visible on the next HTTP request.
It is "hot-reloading" in a sense that you do not need to restart the HTTP server every time you make a change to the application.
There is no logic that would force-refresh the page on completion of the compilation.
There are several ways to achieve this, e.g. using a custom script that queries the backend.
However, this does logic does not belong in isomorphic-webpack
.
The purpose of the server-side rendering is to generate HTML response to a HTTP request.
isomorphic-webpack
does perform hot-reloading that satisfies this use case.
The primary purpose of hot module reloading (HRM) is to enable better developer experience. Given that it is a development feature, it is safe to assume that the developer is in control over the development environment. Therefore, to achieve HMR you need to implement the logic in your frontend application and configure webpack as described in the Hot module replacement with webpack guide.
Check for presence of ISOMORPHIC_WEBPACK
variable.
Presence of ISOMORPHIC_WEBPACK
indicates that code is executed using Node.js.
if (typeof ISOMORPHIC_WEBPACK === 'undefined') {
// Browser
} else {
// Node.js
}
isomorphic-webpack
is using debug
to log messages.
To enable logging, export DEBUG
environment variable:
export DEBUG=isomorphic-webpack:*
Using createIsomorphicWebpack
result has a compiler
property. compiler
is an instance of a webpack Compiler
. Use it to subscribe to all compiler events.
See also:
Attempting to render a route server-side before the compiler has completed at least one compilation will produce an error, e.g.
+SyntaxError: /src/app/style.css: Unexpected token (1:0)
+> 1 | .greetings {
+ | ^
+ 2 | font-weight: bold;
+ 3 | }
+ 4 |
at Parser.pp$5.raise (/node_modules/babylon/lib/index.js:4246:13)
at Parser.pp.unexpected (/node_modules/babylon/lib/index.js:1627:8)
at Parser.pp$3.parseExprAtom (/node_modules/babylon/lib/index.js:3586:12)
at Parser.parseExprAtom (/node_modules/babylon/lib/index.js:6402:22)
at Parser.pp$3.parseExprSubscripts (/node_modules/babylon/lib/index.js:3331:19)
at Parser.pp$3.parseMaybeUnary (/node_modules/babylon/lib/index.js:3311:19)
at Parser.pp$3.parseExprOps (/node_modules/babylon/lib/index.js:3241:19)
at Parser.pp$3.parseMaybeConditional (/node_modules/babylon/lib/index.js:3218:19)
at Parser.pp$3.parseMaybeAssign (/node_modules/babylon/lib/index.js:3181:19)
at Parser.parseMaybeAssign (/node_modules/babylon/lib/index.js:5694:20)
The error will vary depending on what loaders your application code depends on.
Therefore, it is desirable to delay the first server-side render until the compiler has completed at least one compilation.
const {
compiler
} = createIsomorphicWebpack(webpackConfiguration);
let routesAreInitialized;
compiler.plugin('done', () => {
if (routesAreInitialized) {
return;
}
routesAreInitialized = true;
app.get('/', isomorphicMiddleware);
});
This pattern is demonstrated in the isomorphic-webpack-demo.
See also:
WARNING!
Do not use this in production. This implementation has a large overhead.
It might be desirable to stall HTTP request handling until whatever in-progress compilation has completed. This ensures that during the development you do not receive a stale response.
To achieve this:
useCompilationPromise
configuration.createCompilationPromise
to create a promise that resolves when a current compilation completes.Note:
You must enable this feature using
useCompilationPromise
configuration.If you use
createCompilationPromise
without configuringuseCompilationPromise
, you will get an error:"createCompilationPromise" feature has not been enabled.
Example usage:
const {
createCompilationPromise
} = createIsomorphicWebpack(webpackConfiguration, {
useCompilationPromise: true
});
app.use(async (req, res, next) => {
await createCompilationPromise();
next();
});
app.get('/', isomorphicMiddleware);
isomorphic-webpack
different from webpack-isomorphic-tools
, universal-webpack
, ...?Feature | isomorphic-webpack |
webpack-isomorphic-tools |
universal-webpack |
---|---|---|---|
Only one running node process. | ✅ | ❌ | ❌ |
Does not require a separate webpack configuration. | ✅ | ❌ | ❌ |
Enables use of all webpack loaders. | ✅ | ❌ | ❌ |
Server-side hot reloading of modules. | ✅ | ✅ | ✅ |
Supports stack trace. | ✅ | ❌ | ❌ |
Prevents serving stale data. | ✅ | ❌ | ❌ |
Does not override Node.js require() . |
✅ | ❌ | ✅ |
Uses webpack target: "node" . |
✅ | ❌ | ✅ |
Provides low-level API. | ✅ | ❌ | ❌ |
From a subjective perspective, isomorphic-webpack
is a lot easier to setup than any of the existing alternatives.
I apologise in advance if I have misrepresented either of the frameworks.
Contact me to correct an error in the above comparison table, if you'd like to add another comparison criteria, or to add another framework.
TL;DR: Isomorphism is the functional aspect of seamlessly switching between client- and server-side rendering without losing state. Universal is a term used to emphasize the fact that a particular piece of JavaScript code is able to run in multiple environments.
– https://medium.com/@ghengeveld/isomorphism-vs-universal-javascript-4b47fb481beb#.h7fikpuyk
isomorphic-webpack
is a program that runs server-side and enables rendering of the same code base client- and server-side.