UNPKG

52 kBMarkdownView Raw
1# webpack-isomorphic-tools
2
3[![NPM Version][npm-image]][npm-url]
4[![NPM Downloads][downloads-image]][downloads-url]
5[![Build Status][travis-image]][travis-url]
6[![Test Coverage][coveralls-image]][coveralls-url]
7
8<!---
9[![Gratipay][gratipay-image]][gratipay-url]
10-->
11
12`webpack-isomorphic-tools` is a small helper module providing basic support for isomorphic (universal) rendering when using Webpack (this is an alternative solution to using Webpack's officially recommended `target: "node"` approach).
13
14For an officially recommended Webpack's `target: "node"` approach see [`universal-webpack`](https://github.com/halt-hammerzeit/universal-webpack) library. `universal-webpack` library is the recommended way to go.
15
16If for some reason `universal-webpack` doesn't suit your needs, or is too complex to grasp, then you can try using `webpack-isomorphic-tools`. `webpack-isomorphic-tools` are not affiliated with Webpack team in any way and provide support for basic use cases (no support for Webpack plugins, for example) aiming to be easy to understand for beginners.
17
18*Small Advertisement:* 📞 if you're looking for a React phone number component check out [`react-phone-number-input`](http://halt-hammerzeit.github.io/react-phone-number-input/)
19
20## Topics
21
22- [What it does](#what-it-does)
23- [A simple example](#a-simple-example)
24- [Installation](#installation)
25- [Usage](#usage)
26- [A working example](#a-working-example)
27- [Configuration](#configuration)
28- [Configuration examples](#configuration-examples)
29- [What are webpack-assets.json?](#what-are-webpack-assetsjson)
30- [What are Webpack stats?](#what-are-webpack-stats)
31- [What's a "module"?](#whats-a-module)
32- [API](#api)
33- [Troubleshooting](#troubleshooting)
34- [Miscellaneous](#miscellaneous)
35- [References](#references)
36- [Contributing](#contributing)
37
38## What it does
39
40Suppose you have an application which is built using Webpack. It works in the web browser.
41
42Should it be "isomorphic" ("universal")? It's better if it is. One reason is that search engines will be able to index your page. The other reason is that we live in a realtime mobile age which declared war on network latency, and so it's always better to fetch an already rendered content than to first fetch the application code and only then fetch the content to render the page. Every time you release a client-side only website to the internet someone writes a [frustrated blog post](https://ponyfoo.com/articles/stop-breaking-the-web).
43
44So, it's obvious then that web applications should be "isomorphic" ("universal"), i.e. be able to render both on the client and the server, depending on circumstances. And it is perfectly possible nowadays since javascript runs everywhere: both in web browsers and on servers.
45
46Ok, then one can just go ahead and run the web application in Node.js and its done. But, there's one gotcha: a Webpack application will usually crash when tried to be run in Node.js straight ahead (you'll get a lot of `SyntaxError`s with `Unexpected token`s).
47
48The reason is that Webpack introduces its own layer above the standard javascript. This extra layer handles all `require()` calls magically resolving them to whatever it is configured to. For example, Webpack is perfectly fine with the code `require()`ing CSS styles or SVG images.
49
50Bare Node.js obviously doesn't come with such trickery up its sleeve. Maybe it can be somehow enhanced to be able to do such things? Turned out that it can, and that's what `webpack-isomorphic-tools` do: they inject that `require()` magic layer above the standard javascript in Node.js.
51
52An alternative solution exists now: to compile server-side code with Webpack the same way it already compiles the client-side code. This is the officially recommended way to go and one can use [`universal-webpack`](https://github.com/halt-hammerzeit/universal-webpack) library to achieve that. However, some people still prefer this (earlier) library, so it still exists.
53
54`webpack-isomorphic-tools` mimics (to a certain extent) Webpack's `require()` magic when running application code on a Node.js server without Webpack. It basically fixes all those `require()`s of assets and makes them work instead of throwing `SyntaxError`s. It doesn't provide all the capabilities of Webpack (for example, plugins won't work), but for the basic stuff, it works.
55
56## A simple example
57
58For example, consider images. Images are `require()`d in React components and then used like this:
59
60```javascript
61// alternatively one can use `import`,
62// but with `import`s hot reloading won't work
63// import image_path from '../image.png'
64
65// Just `src` the image inside the `render()` method
66class Photo extends React.Component
67{
68 render()
69 {
70 // When Webpack url-loader finds this `require()` call
71 // it will copy `image.png` to the build folder
72 // and name it something like `9059f094ddb49c2b0fa6a254a6ebf2ad.png`,
73 // because Webpack is set up to use the `[hash]` file naming feature
74 // which makes browser asset caching work correctly.
75 return <img src={ require('../image.png') }/>
76 }
77}
78```
79
80It works on the client-side because Webpack intelligently replaces all the `require()` calls with a bit of magic.
81But it wouldn't work on the server-side because Node.js only knows how to `require()` javascript modules. It would just throw a `SyntaxError`.
82
83To solve this issue one can use `webpack-isomorphic-tools`. With the help of `webpack-isomorphic-tools` in this particular case the `require()` call will return the real path to the image on the disk. It would be something like `../../build/9059f094ddb49c2b0fa6a254a6ebf2ad.png`. How did `webpack-isomorphic-tools` figure out this weird real file path? It's just a bit of magic.
84
85`webpack-isomorphic-tools` is extensible, and finding the real paths for assets is the simplest example of what it can do inside `require()` calls. Using [custom configuration](#configuration) one can make `require()` calls (on the server) return anything (not just a String; it may be a JSON object, for example).
86
87For example, if one is using Webpack [css-loader](https://github.com/webpack/css-loader) modules feature (also referred to as ["local styles"](https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284)) one can make `require(*.css)` calls return JSON objects with generated CSS class names maps like they do in [este](https://github.com/este/este/blob/master/webpack/assets.js) and [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example#styles).
88
89## Tutorials and blog posts
90
91Just some basic guidance from other people on the internets
92
93 * [Importing SVGs](https://github.com/peter-mouland/react-lego/wiki/Importing-SVGs) by [@peter-mouland](https://github.com/peter-mouland)
94
95## Installation
96
97`webpack-isomorphic-tools` are required both for development and production
98
99```bash
100$ npm install webpack-isomorphic-tools --save
101```
102
103## Usage
104
105First you add `webpack_isomorphic_tools` plugin to your Webpack configuration.
106
107### webpack.config.js
108
109```javascript
110var Webpack_isomorphic_tools_plugin = require('webpack-isomorphic-tools/plugin')
111
112var webpack_isomorphic_tools_plugin =
113 // webpack-isomorphic-tools settings reside in a separate .js file
114 // (because they will be used in the web server code too).
115 new Webpack_isomorphic_tools_plugin(require('./webpack-isomorphic-tools-configuration'))
116 // also enter development mode since it's a development webpack configuration
117 // (see below for explanation)
118 .development()
119
120// usual Webpack configuration
121module.exports =
122{
123 context: '(required) your project path here',
124
125 module:
126 {
127 loaders:
128 [
129 ...,
130 {
131 test: webpack_isomorphic_tools_plugin.regular_expression('images'),
132 loader: 'url-loader?limit=10240', // any image below or equal to 10K will be converted to inline base64 instead
133 }
134 ]
135 },
136
137 plugins:
138 [
139 ...,
140
141 webpack_isomorphic_tools_plugin
142 ]
143
144 ...
145}
146```
147
148What does `.development()` method do? It enables development mode. In short, when in development mode, it disables asset caching (and enables asset hot reload), and optionally runs its own "dev server" utility (see `port` configuration setting). Call it in development webpack build configuration, and, conversely, don't call it in production webpack build configuration.
149
150For each asset type managed by `webpack_isomorphic_tools` there should be a corresponding loader in your Webpack configuration. For this reason `webpack_isomorphic_tools/plugin` provides a `.regular_expression(asset_type)` method. The `asset_type` parameter is taken from your `webpack-isomorphic-tools` configuration:
151
152### webpack-isomorphic-tools-configuration.js
153
154```javascript
155import Webpack_isomorphic_tools_plugin from 'webpack-isomorphic-tools/plugin'
156
157export default
158{
159 assets:
160 {
161 images:
162 {
163 extensions: ['png', 'jpg', 'gif', 'ico', 'svg']
164 }
165 }
166}
167```
168
169That's it for the client side. Next, the server side. You create your server side instance of `webpack-isomorphic-tools` in the very main server javascript file (and your web application code will reside in some `server.js` file which is `require()`d in the bottom)
170
171### main.js
172
173```javascript
174var Webpack_isomorphic_tools = require('webpack-isomorphic-tools')
175
176// this must be equal to your Webpack configuration "context" parameter
177var project_base_path = require('path').resolve(__dirname, '..')
178
179// this global variable will be used later in express middleware
180global.webpack_isomorphic_tools = new Webpack_isomorphic_tools(require('./webpack-isomorphic-tools-configuration'))
181// initializes a server-side instance of webpack-isomorphic-tools
182// (the first parameter is the base path for your project
183// and is equal to the "context" parameter of you Webpack configuration)
184// (if you prefer Promises over callbacks
185// you can omit the callback parameter
186// and then it will return a Promise instead)
187.server(project_base_path, function()
188{
189 // webpack-isomorphic-tools is all set now.
190 // here goes all your web application code:
191 // (it must reside in a separate *.js file
192 // in order for the whole thing to work)
193 require('./server')
194})
195```
196
197Then you, for example, create an express middleware to render your pages on the server
198
199```javascript
200import React from 'react'
201
202// html page markup
203import Html from './html'
204
205// will be used in express_application.use(...)
206export function page_rendering_middleware(request, response)
207{
208 // clear require() cache if in development mode
209 // (makes asset hot reloading work)
210 if (_development_)
211 {
212 webpack_isomorphic_tools.refresh()
213 }
214
215 // for react-router example of determining current page by URL take a look at this:
216 // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
217 const page_component = [determine your page component here using request.path]
218
219 // for a Redux Flux store implementation you can see the same example:
220 // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
221 const flux_store = [initialize and populate your flux store depending on the page being shown]
222
223 // render the page to string and send it to the browser as text/html
224 response.send('<!doctype html>\n' +
225 React.renderToString(<Html assets={webpack_isomorphic_tools.assets()} component={page_component} store={flux_store}/>))
226}
227```
228
229And finally you use the `assets` inside the `Html` component's `render()` method
230
231```javascript
232import React, {Component, PropTypes} from 'react'
233import serialize from 'serialize-javascript'
234
235export default class Html extends Component
236{
237 static propTypes =
238 {
239 assets : PropTypes.object,
240 component : PropTypes.object,
241 store : PropTypes.object
242 }
243
244 // a sidenote for "advanced" users:
245 // (you may skip this)
246 //
247 // this file is usually not included in your Webpack build
248 // because this React component is only needed for server side React rendering.
249 //
250 // so, if this React component is not `require()`d from anywhere in your client code,
251 // then Webpack won't ever get here
252 // which means Webpack won't detect and parse any of the `require()` calls here,
253 // which in turn means that if you `require()` any unique assets here
254 // you should also `require()` those assets somewhere in your client code,
255 // otherwise those assets won't be present in your Webpack bundle and won't be found.
256 //
257 render()
258 {
259 const { assets, component, store } = this.props
260
261 // "import" will work here too
262 // but if you want hot reloading to work while developing your project
263 // then you need to use require()
264 // because import will only be executed a single time
265 // (when the application launches)
266 // you can refer to the "Require() vs import" section for more explanation
267 const picture = require('../assets/images/cat.jpg')
268
269 // favicon
270 const icon = require('../assets/images/icon/32x32.png')
271
272 const html =
273 (
274 <html lang="en-us">
275 <head>
276 <meta charSet="utf-8"/>
277 <title>xHamster</title>
278
279 {/* favicon */}
280 <link rel="shortcut icon" href={icon} />
281
282 {/* styles (will be present only in production with webpack extract text plugin) */}
283 {Object.keys(assets.styles).map((style, i) =>
284 <link href={assets.styles[style]} key={i} media="screen, projection"
285 rel="stylesheet" type="text/css"/>)}
286
287 {/* resolves the initial style flash (flicker) on page load in development mode */}
288 { Object.keys(assets.styles).is_empty() ? <style dangerouslySetInnerHTML={{__html: require('../assets/styles/main_style.css')}}/> : null }
289 </head>
290
291 <body>
292 {/* image requiring demonstration */}
293 <img src={picture}/>
294
295 {/* rendered React page */}
296 <div id="content" dangerouslySetInnerHTML={{__html: React.renderToString(component)}}/>
297
298 {/* Flux store data will be reloaded into the store on the client */}
299 <script dangerouslySetInnerHTML={{__html: `window._flux_store_data=${serialize(store.getState())};`}} />
300
301 {/* javascripts */}
302 {/* (usually one for each "entry" in webpack configuration) */}
303 {/* (for more informations on "entries" see https://github.com/petehunt/webpack-howto/) */}
304 {Object.keys(assets.javascript).map((script, i) =>
305 <script src={assets.javascript[script]} key={i}/>
306 )}
307 </body>
308 </html>
309 )
310
311 return html
312 }
313}
314```
315
316`assets` in the code above are simply the contents of `webpack-assets.json` which is created by `webpack-isomorphic-tools` in your project base folder. `webpack-assets.json` (in the simplest case) keeps track of the real paths to your assets, e.g.
317
318```javascript
319{
320 "javascript":
321 {
322 "main": "/assets/main-d8c29e9b2a4623f696e8.js"
323 },
324
325 "styles":
326 {
327 "main": "/assets/main-d8c29e9b2a4623f696e8.css"
328 },
329
330 "assets":
331 {
332 "./assets/images/cat.jpg": "http://localhost:3001/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
333
334 "./assets/images/icon/32x32.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffBhcWAg6gFw6bAAAB60lEQVRIx+3UTUjUQRzG8c+u/n2BDe3lIJtQSuYhsPTQG+TFYLulguStoA5dPHYogoKigoi8dIsOCd0iiC4JFYFQBAVZEUgklWVQqam4vu1uF111d1310qWe0/yemfnyzPyG4b8KllQl6jWqNuX3nFNun/0qjJpYGRB1TkyRWu0C76Q0uKhOkT1aDfqSP0uxTpetR1i9e2Iq3HVUCQKt7tuWP0GDmDOGkfJd3GEbhFwzg6T3alR5lg0Ip0fVPhhKV2+UqfNcMu28sjlXggVAXEQoXZVKmlC2aGXETH5Ary3q026zPg8dtGnOKXPIi/x3MCJwUtyUqBN2uarXTi1+Cql1yqibuTKElsCaHBFBn1v6sU67RoGkHl3GciVYDNiuWVSphDEJYaSkRBSbNqLHI7PZgML0qNIFrz3OwqZAuQ6BB8KqRL01nA3YbdCVRW3L1KxGTx1zQMI3p01nAkqN5NnOkBrXJZw1qlOlj5mAlTQuqluXcRGTSrOPsJJeajOQzphaOyDucy47vGrAMvqLgCLlS97HmgH17mgRzFWhbEAq43/M1EYF2p1XoVAgMW8vdKFfmx0+LbO9WJNut3W44Ze4r/MTC6cKHBczutDhJSrxwyWDAntt9cRANoCwqLKcgJApAyZXfV//mP4AWg969geZ6qgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMDYtMjNUMjI6MDI6MTQrMDI6MDBG88r0AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTA2LTIzVDIyOjAyOjE0KzAyOjAwN65ySAAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAASUVORK5CYII="
335 }
336}
337```
338
339And that's it, now you can `require()` your assets "isomorphically" (both on client and server).
340
341## A working example
342
343`webpack-isomorphic-tools` are featured in [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example/blob/master/webpack/webpack-isomorphic-tools.js#L64-L96). There it is used to `require()` images and CSS styles (in the form of CSS `modules`).
344
345Also you may look at [this sample project](https://github.com/halt-hammerzeit/webapp). There it is used to `require()` images and CSS styles (without using CSS `modules` feature).
346
347Some source code guidance for the aforementioned project:
348
349* [webpack-isomorphic-tools configuration](https://github.com/halt-hammerzeit/webapp/blob/master/frontend/webpack/webpack-isomorphic-tools.js)
350* [webpack-isomorphic-tools plugin](https://github.com/halt-hammerzeit/webapp/blob/master/frontend/webpack/development%20server.js#L57)
351* [webpack-isomorphic-tools server-side initialization](https://github.com/halt-hammerzeit/webapp/blob/master/frontend/page-server/entry.es6.js#L13-L18)
352
353## Configuration
354
355Available configuration parameters:
356
357```javascript
358{
359 // debug mode.
360 // when set to true, lets you see debugging messages in the console.
361 //
362 debug: true, // is false by default
363
364 // (optional)
365 // (recommended)
366 //
367 // when `port` is set, then this `port` is used
368 // to run an HTTP server serving Webpack assets.
369 // (`express` npm package must be installed in order for this to work)
370 //
371 // this way, in development mode, `webpack-assets.json` won't ever
372 // be written to disk and instead will always reside in memory
373 // and be served from memory (just as `webpack-dev-server` does).
374 //
375 // this `port` setting will take effect only in development mode.
376 //
377 // port: 8888, // is false by default
378
379 // verbosity.
380 //
381 // when set to 'no webpack stats',
382 // outputs no Webpack stats to the console in development mode.
383 // this also means no Webpack errors or warnings will be output to the console.
384 //
385 // when set to 'webpack stats for each build',
386 // outputs Webpack stats to the console
387 // in development mode on each incremental build.
388 // (i guess no one is gonna ever use this setting)
389 //
390 // when not set (default), outputs Webpack stats to the console
391 // in development mode for the first build only.
392 //
393 // verbosity: ..., // is `undefined` by default
394
395 // enables support for `require.context()` and `require.ensure()` functions.
396 // is turned off by default
397 // to skip unnecessary code instrumentation
398 // because not everyone uses it.
399 //
400 // patch_require: true, // is false by default
401
402 // By default it creates 'webpack-assets.json' file at
403 // webpack_configuration.context (which is your project folder).
404 // You can change the assets file path as you wish
405 // (therefore changing both folder and filename).
406 //
407 // (relative to webpack_configuration.context which is your project folder)
408 //
409 webpack_assets_file_path: 'webpack-assets.json',
410
411 // By default, when running in debug mode, it creates 'webpack-stats.json' file at
412 // webpack_configuration.context (which is your project folder).
413 // You can change the stats file path as you wish
414 // (therefore changing both folder and filename).
415 //
416 // (relative to webpack_configuration.context which is your project folder)
417 //
418 webpack_stats_file_path: 'webpack-stats.json',
419
420 // Makes `webpack-isomorphic-tools` aware of Webpack aliasing feature
421 // (if you use it)
422 // https://webpack.github.io/docs/resolving.html#aliasing
423 //
424 // The `alias` parameter corresponds to `resolve.alias`
425 // in your Webpack configuration.
426 //
427 alias: webpack_configuration.resolve.alias, // is {} by default
428
429 // if you're using Webpack's `resolve.modulesDirectories`
430 // then you should also put them here.
431 //
432 // modulesDirectories: webpack_configuration.resolve.modulesDirectories // is ['node_modules'] by default
433
434 // here you can define all your asset types
435 //
436 assets:
437 {
438 // keys of this object will appear in:
439 // * webpack-assets.json
440 // * .assets() method call result
441 // * .regular_expression(key) method call
442 //
443 png_images:
444 {
445 // which file types belong to this asset type
446 //
447 extension: 'png', // or extensions: ['png', 'jpg', ...],
448
449 // [optional]
450 //
451 // here you are able to add some file paths
452 // for which the require() call will bypass webpack-isomorphic-tools
453 // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
454 // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
455 // and functions(path) { return true / false })
456 //
457 // exclude: [],
458
459 // [optional]
460 //
461 // here you can specify manually the paths
462 // for which the require() call will be processed by webpack-isomorphic-tools
463 // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
464 // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
465 // and functions(path) { return true / false }).
466 // in case of `include` only included paths will be processed by webpack-isomorphic-tools.
467 //
468 // include: [],
469
470 // [optional]
471 //
472 // determines which webpack stats modules
473 // belong to this asset type
474 //
475 // arguments:
476 //
477 // module - a webpack stats module
478 //
479 // (to understand what a "module" is
480 // read the "What's a "module"?" section of this readme)
481 //
482 // regular_expression - a regular expression
483 // composed of this asset type's extensions
484 // e.g. /\.scss$/, /\.(ico|gif)$/
485 //
486 // options - various options
487 // (development mode flag,
488 // debug mode flag,
489 // assets base url,
490 // project base folder,
491 // regular_expressions{} for each asset type (by name),
492 // webpack stats json object)
493 //
494 // log
495 //
496 // returns: a Boolean
497 //
498 // by default is: "return regular_expression.test(module.name)"
499 //
500 // premade utility filters:
501 //
502 // Webpack_isomorphic_tools_plugin.style_loader_filter
503 // (for use with style-loader + css-loader)
504 //
505 filter: function(module, regular_expression, options, log)
506 {
507 return regular_expression.test(module.name)
508 },
509
510 // [optional]
511 //
512 // transforms a webpack stats module name
513 // to an asset path (usually is the same thing)
514 //
515 // arguments:
516 //
517 // module - a webpack stats module
518 //
519 // (to understand what a "module" is
520 // read the "What's a "module"?" section of this readme)
521 //
522 // options - various options
523 // (development mode flag,
524 // debug mode flag,
525 // assets base url,
526 // project base folder,
527 // regular_expressions{} for each asset type (by name),
528 // webpack stats json object)
529 //
530 // log
531 //
532 // returns: a String
533 //
534 // by default is: "return module.name"
535 //
536 // premade utility path extractors:
537 //
538 // Webpack_isomorphic_tools_plugin.style_loader_path_extractor
539 // (for use with style-loader + css-loader)
540 //
541 path: function(module, options, log)
542 {
543 return module.name
544 },
545
546 // [optional]
547 //
548 // parses a webpack stats module object
549 // for an asset of this asset type
550 // to whatever you need to get
551 // when you require() these assets
552 // in your code later on.
553 //
554 // this is what you'll see as the asset value in webpack-assets.json:
555 // { ..., path(): compile(parser()), ... }
556 //
557 // can be a CommonJS module source code:
558 // module.exports = ...what you export here is
559 // what you get when you require() this asset...
560 //
561 // if the returned value is not a CommonJS module source code
562 // (it may be a string, a JSON object, whatever)
563 // then it will be transformed into a CommonJS module source code.
564 //
565 // in other words:
566 //
567 // // making of webpack-assets.json
568 // for each type of configuration.assets
569 // modules.filter(type.filter).for_each (module)
570 // assets[type.path()] = compile(type.parser(module))
571 //
572 // // requiring assets in your code
573 // require(path) = (path) => return assets[path]
574 //
575 // arguments:
576 //
577 // module - a webpack stats module
578 //
579 // (to understand what a "module" is
580 // read the "What's a "module"?" section of this readme)
581 //
582 // options - various options
583 // (development mode flag,
584 // debug mode flag,
585 // assets base url,
586 // project base folder,
587 // regular_expressions{} for each asset type (by name),
588 // webpack stats json object)
589 //
590 // log
591 //
592 // returns: whatever (could be a filename, could be a JSON object, etc)
593 //
594 // by default is: "return module.source"
595 //
596 // premade utility parsers:
597 //
598 // Webpack_isomorphic_tools_plugin.url_loader_parser
599 // (for use with url-loader or file-loader)
600 // require() will return file URL
601 // (is equal to the default parser, i.e. no parser)
602 //
603 // Webpack_isomorphic_tools_plugin.css_loader_parser
604 // (for use with css-loader when not using "modules" feature)
605 // require() will return CSS style text
606 //
607 // Webpack_isomorphic_tools_plugin.css_modules_loader_parser
608 // (for use with css-loader when using "modules" feature)
609 // require() will return a JSON object map of style class names
610 // which will also have a `_style` key containing CSS style text
611 //
612 parser: function(module, options, log)
613 {
614 log.info('# module name', module.name)
615 log.info('# module source', module.source)
616 log.info('# project path', options.project_path)
617 log.info('# assets base url', options.assets_base_url)
618 log.info('# regular expressions', options.regular_expressions)
619 log.info('# debug mode', options.debug)
620 log.info('# development mode', options.development)
621 log.debug('debugging')
622 log.warning('warning')
623 log.error('error')
624 }
625 },
626 ...
627 },
628 ...]
629}
630```
631
632## Configuration examples
633
634#### url-loader / file-loader (images, fonts, etc)
635
636`url-loader` and `file-loader` are supported with no additional configuration
637
638```javascript
639{
640 assets:
641 {
642 images:
643 {
644 extensions: ['png', 'jpg']
645 },
646
647 fonts:
648 {
649 extensions: ['woff', 'ttf']
650 }
651 }
652}
653```
654
655#### style-loader (standard CSS stylesheets)
656
657If you aren't using "CSS modules" feature of Webpack, and if in your production Webpack config you use `ExtractTextPlugin` for CSS styles, then you can set it up like this
658
659```javascript
660{
661 assets:
662 {
663 styles:
664 {
665 extensions: ['less', 'scss'],
666
667 // which `module`s to parse CSS from:
668 filter: function(module, regular_expression, options, log)
669 {
670 if (options.development)
671 {
672 // In development mode there's Webpack "style-loader",
673 // which outputs `module`s with `module.name == asset_path`,
674 // but those `module`s do not contain CSS text.
675 //
676 // The `module`s containing CSS text are
677 // the ones loaded with Webpack "css-loader".
678 // (which have kinda weird `module.name`)
679 //
680 // Therefore using a non-default `filter` function here.
681 //
682 return webpack_isomorphic_tools_plugin.style_loader_filter(module, regular_expression, options, log)
683 }
684
685 // In production mode there will be no CSS text at all
686 // because all styles will be extracted by Webpack Extract Text Plugin
687 // into a .css file (as per Webpack configuration).
688 //
689 // Therefore in production mode `filter` function always returns non-`true`.
690 },
691
692 // How to correctly transform kinda weird `module.name`
693 // of the `module` created by Webpack "css-loader"
694 // into the correct asset path:
695 path: webpack_isomorphic_tools_plugin.style_loader_path_extractor,
696
697 // How to extract these Webpack `module`s' javascript `source` code.
698 // basically takes `module.source` and modifies `module.exports` a little.
699 parser: webpack_isomorphic_tools_plugin.css_loader_parser
700 }
701 }
702}
703```
704
705#### style-loader (CSS stylesheets with "CSS modules" feature)
706
707If you are using "CSS modules" feature of Webpack, and if in your production Webpack config you use `ExtractTextPlugin` for CSS styles, then you can set it up like this
708
709```javascript
710{
711 assets:
712 {
713 style_modules:
714 {
715 extensions: ['less', 'scss'],
716
717 // which `module`s to parse CSS style class name maps from:
718 filter: function(module, regex, options, log)
719 {
720 if (options.development)
721 {
722 // In development mode there's Webpack "style-loader",
723 // which outputs `module`s with `module.name == asset_path`,
724 // but those `module`s do not contain CSS text.
725 //
726 // The `module`s containing CSS text are
727 // the ones loaded with Webpack "css-loader".
728 // (which have kinda weird `module.name`)
729 //
730 // Therefore using a non-default `filter` function here.
731 //
732 return webpack_isomorphic_tools_plugin.style_loader_filter(module, regex, options, log)
733 }
734
735 // In production mode there's no Webpack "style-loader",
736 // so `module.name`s of the `module`s created by Webpack "css-loader"
737 // (those which contain CSS text)
738 // will be simply equal to the correct asset path
739 return regex.test(module.name)
740 },
741
742 // How to correctly transform `module.name`s
743 // into correct asset paths
744 path: function(module, options, log)
745 {
746 if (options.development)
747 {
748 // In development mode there's Webpack "style-loader",
749 // so `module.name`s of the `module`s created by Webpack "css-loader"
750 // (those picked by the `filter` function above)
751 // will be kinda weird, and this path extractor extracts
752 // the correct asset paths from these kinda weird `module.name`s
753 return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
754 }
755
756 // in production mode there's no Webpack "style-loader",
757 // so `module.name`s will be equal to correct asset paths
758 return module.name
759 },
760
761 // How to extract these Webpack `module`s' javascript `source` code.
762 // Basically takes `module.source` and modifies its `module.exports` a little.
763 parser: function(module, options, log)
764 {
765 if (options.development)
766 {
767 // In development mode it adds an extra `_style` entry
768 // to the CSS style class name map, containing the CSS text
769 return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
770 }
771
772 // In production mode there's Webpack Extract Text Plugin
773 // which extracts all CSS text away, so there's
774 // only CSS style class name map left.
775 return module.source
776 }
777 }
778 }
779}
780```
781
782## What are webpack-assets.json?
783
784This file is needed for `webpack-isomorphic-tools` operation on server. It is created by a custom Webpack plugin and is then read from the filesystem by `webpack-isomorphic-tools` server instance. When you `require(path_to_an_asset)` an asset on server then what you get is simply what's there in this file corresponding to this `path_to_an_asset` key (under the `assets` section).
785
786Pseudocode:
787
788```
789// requiring assets in your code
790require(path) = (path) => return assets[path]
791```
792
793Therefore, if you get such a message in the console:
794
795```
796[webpack-isomorphic-tools] [error] asset not found: ./~/react-toolbox/lib/font_icon/style.scss
797```
798
799Then it means that the asset you requested (`require()`d) is absent from your `webpack-assets.json` which in turn means that you haven't placed this asset to your `webpack-assets.json` in the first place. How to place an asset into `webpack-assets.json`?
800
801Pseudocode:
802
803```
804// making of webpack-assets.json inside the Webpack plugin
805for each type of configuration.assets
806 modules.filter(type.filter).for_each (module)
807 assets[type.path()] = compile(type.parser(module))
808```
809
810Therefore, if you get the "asset not found" error, first check your `webpack-assets.json` and second check your `webpack-isomorphic-tools` configuration section for this asset type: are your `filter`, `path` and `parser` functions correct?
811
812## What are Webpack stats?
813
814[Webpack stats](https://github.com/webpack/docs/wiki/node.js-api#stats) are a description of all the modules in a Webpack build. When running in debug mode Webpack stats are output to a file named `webpack-stats.json` in the same folder as your `webpack-assets.json` file. One may be interested in the contents of this file when writing custom `filter`, `path` or `parser` functions. This file is not needed for operation, it's just some debugging information.
815
816## What's a "module"?
817
818**This is an advanced topic on Webpack internals**
819
820A "module" is a Webpack entity. One of the main features of Webpack is code splitting. When Webpack builds your code it splits it into "chunks" - large portions of code which can be downloaded separately later on (if needed) therefore reducing the initial page load time for your website visitor. These big "chunks" aren't monolithic and in their turn are composed of "modules" which are: standard CommonJS javascript modules you `require()` every day, pictures, stylesheets, etc. Every time you `require()` something (it could be anything: an npm module, a javascript file, or a css style, or an image) a `module` entry is created by Webpack. And the file where this `require()` call originated is called a `reason` for this `require()`d `module`. Each `module` entry has a `name` and a `source` code, along with a list of `chunks` it's in and a bunch of other miscellaneous irrelevant properties.
821
822For example, here's a piece of an example `webpack-stats.json` file (which is generated along with `webpack-assets.json` in debug mode). Here you can see a random `module` entry created by Webpack.
823
824```javascript
825{
826 ...
827
828 "modules": [
829 {
830 "id": 0,
831 ...
832 },
833 {
834 "id": 1,
835 "name": "./~/fbjs/lib/invariant.js",
836 "source": "module.exports = global[\"undefined\"] = require(\"-!G:\\\\work\\\\isomorphic-demo\\\\node_modules\\\\fbjs\\\\lib\\\\invariant.js\");",
837
838 // the rest of the fields are irrelevant
839
840 "chunks": [
841 0
842 ],
843 "identifier": "G:\\work\\isomorphic-demo\\node_modules\\expose-loader\\index.js?undefined!G:\\work\\isomorphic-demo\\node_modules\\fbjs\\lib\\invariant.js",
844 "index": 27,
845 "index2": 7,
846 "size": 117,
847 "cacheable": true,
848 "built": true,
849 "optional": false,
850 "prefetched": false,
851 "assets": [],
852 "issuer": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
853 "failed": false,
854 "errors": 0,
855 "warnings": 0,
856
857 "reasons": [
858 {
859 "moduleId": 418,
860 "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
861 "module": "./~/react/lib/ReactInstanceHandles.js",
862 "moduleName": "./~/react/lib/ReactInstanceHandles.js",
863 "type": "cjs require",
864 "userRequest": "fbjs/lib/invariant",
865 "loc": "17:16-45"
866 },
867 ...
868 {
869 "moduleId": 483,
870 "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\traverseAllChildren.js",
871 "module": "./~/react/lib/traverseAllChildren.js",
872 "moduleName": "./~/react/lib/traverseAllChildren.js",
873 "type": "cjs require",
874 "userRequest": "fbjs/lib/invariant",
875 "loc": "19:16-45"
876 }
877 ]
878 },
879
880 ...
881 ]
882}
883```
884
885Judging by its `reasons` and their `userRequest`s one can deduce that this `module` is `require()`d by many other `module`s in this project and the code triggering this `module` entry creation could look something like this
886
887```javascript
888var invariant = require('fbjs/lib/invariant')
889```
890
891Every time you `require()` anything in your code, Webpack detects it during build process and the `require()`d `module` is "loaded" (decorated, transformed, replaced, etc) by a corresponding module "loader" (or loaders) specified in Webpack configuration file (`webpack.conf.js`) under the "module.loaders" path. For example, say, all JPG images in a project are configured to be loaded with a "url-loader":
892
893```javascript
894// Webpack configuration
895module.exports =
896{
897 ...
898
899 module:
900 {
901 loaders:
902 [
903 ...
904
905 {
906 test : /\.jpg$/,
907 loader : 'url-loader'
908 }
909 ]
910 },
911
912 ...
913}
914```
915
916This works on client: `require()` calls will return URLs for JPG images. The next step is to make `require()` calls to these JPG images behave the same way when this code is run on the server, with the help of `webpack-isomorphic-tools`. So, the fields of interest of the `module` object would be `name` and `source`: first you find the modules of interest by their `name`s (in this case, the module `name`s would end in ".jpg") and then you parse the `source`s of those modules to extract the information you need (in this case that would be the real path to an image).
917
918The `module` object for an image would look like this
919
920```javascript
921{
922 ...
923 "name": "./assets/images/husky.jpg",
924 "source": "module.exports = __webpack_public_path__ + \"9059f094ddb49c2b0fa6a254a6ebf2ad.jpg\""
925}
926```
927
928Therefore, in this simple case, in `webpack-isomorphic-tools` configuration file we create an "images" asset type with extension "jpg" and these parameters:
929
930* the `filter` function would be `module => module.name.ends_with('.jpg')` (and it's the default `filter` if no `filter` is specified)
931* the `path` parser function would be `module => module.name` (and it's the default `path` parser if no `path` parser is specified)
932* the `parser` function would be `module => module.source` (and it's the default `parser` if no `parser` is specified)
933
934When the javascript `source` code returned by this `parser` function gets compiled by `webpack-isomorphic-tools` it will yeild a valid CommonJS javascript module which will return the URL for this image, resulting in the following piece of `webpack-assets.json`:
935
936```
937{
938 ...
939 assets:
940 {
941 "./assets/images/husky.jpg": "/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
942 ...
943 }
944}
945```
946
947And so when you later `require("./assets/images/husky.jpg")` in your server code it will return `"/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg"` and that's it.
948
949## API
950
951> Note : All exported functions and public methods have camelCase aliases
952
953#### Constructor
954
955(both Webpack plugin and server tools)
956
957Takes an object with options (see [Configuration](#configuration) section above)
958
959#### `process.env.NODE_ENV`
960
961(server tools instance only)
962
963`process.env.NODE_ENV` variable is examined to determine if it's production mode or development mode. Any value for `process.env.NODE_ENV` other than `production` will indicate development mode.
964
965For example, in development mode, assets aren't cached, and therefore support hot reloading (if anyone would ever need that). Also `development` variable is passed to asset type's `filter`, `path` and `parser` functions.
966
967The prevously available `.development()` method for the server-side instance is now deprecated and has no effect.
968
969#### .development(true or false, or undefined -> true)
970
971(Webpack plugin instance only)
972
973Is it development mode or is it production mode? By default it's production mode. But if you're instantiating `webpack-isomorphic-tools/plugin` for use in Webpack development configuration, then you should call this method to enable asset hot reloading (and disable asset caching), and optinally to run its own "dev server" utility (see `port` configuration setting). It should be called right after the constructor.
974
975#### .regular_expression(asset_type)
976
977(aka `.regexp(asset_type)`)
978
979(Webpack plugin instance)
980
981Returns the regular expression for this asset type (based on this asset type's `extension` (or `extensions`))
982
983#### Webpack_isomorphic_tools_plugin.url_loader_parser
984
985(Webpack plugin)
986
987A parser (see [Configuration](#configuration) section above) for Webpack [url-loader](https://github.com/webpack/url-loader), also works for Webpack [file-loader](https://github.com/webpack/file-loader). Use it for your images, fonts, etc.
988
989#### .server(project_path, [callback])
990
991(server tools instance)
992
993Initializes a server-side instance of `webpack-isomorphic-tools` with the base path for your project and makes all the server-side `require()` calls work. The `project_path` parameter must be identical to the `context` parameter of your Webpack configuration and is needed to locate `webpack-assets.json` (contains the assets info) which is output by Webpack process.
994
995When you're running your project in development mode for the very first time the `webpack-assets.json` file doesn't exist yet because in development mode `webpack-dev-server` and your application server are run concurrently and by the time the application server starts the `webpack-assets.json` file hasn't yet been generated by Webpack and `require()` calls for your assets would return `undefined`.
996
997To fix this you can put your application server code into a `callback` and pass it as a second parameter and it will be called as soon as `webpack-assets.json` file is detected. If not given a `callback` this method will return a `Promise` which is fulfilled as soon as `webpack-assets.json` file is detected (in case you prefer `Promise`s over `callback`s). When choosing a `Promise` way you won't be able to get the `webpack-isomorphic-tools` instance variable reference out of the `.server()` method call result, so your code can be a bit more verbose in this case.
998
999#### .refresh()
1000
1001(server tools instance)
1002
1003Refreshes your assets info (re-reads `webpack-assets.json` from disk) and also flushes cache for all the previously `require()`d assets
1004
1005#### .assets()
1006
1007(server tools instance)
1008
1009Returns the contents of `webpack-assets.json` which is created by `webpack-isomorphic-tools` in your project base folder
1010
1011## Troubleshooting
1012
1013### Cannot find module
1014
1015If encountered when run on server, this error means that the `require()`d path doesn't exist in the filesystem (all the `require()`d assets [must exist in the filesystem](https://github.com/nodejs/node/blob/4d4cfb27ca7718c7df381ac3b257175927cd17d1/lib/module.js#L436-L441) when run on server). If encountered during Webpack build, this error means that the `require()`d path is absent from `webpack-stats.json`.
1016
1017As an illustration, consider an example where a developer transpiles all his ES6 code using Babel into a single compiled file `./build/server-bundle-es5.js`. Because all the assets still remain in the `./src` directory, `Cannot find module` error will be thrown when trying to run the compiled bundle. As a workaround use [`babel-register`](https://babeljs.io/docs/usage/require/) instead. Or [copy all assets](https://github.com/halt-hammerzeit/webpack-isomorphic-tools/pull/68#issuecomment-218698675) to the `./build` folder (keeping the file tree structure) and point Webpack `context` to the `./src` folder.
1018
1019### SyntaxError: Unexpected token ILLEGAL
1020
1021This probably means that in some asset module source there's a `require()` call to some file extension that isn't specified in
1022
1023### "TypeError: require.context is not a function" or "TypeError: require.ensure is not a function"
1024
1025You should enable `patch_require: true` flag in your `webpack-isomorphic-tools` configuration file. The reason is that the support for `require.context()` and `require.ensure()` [is hacky at the moment](https://github.com/halt-hammerzeit/webpack-isomorphic-tools/issues/48#issuecomment-182878437). It works and does its thing but the solution is not elegant enough if you know what I mean.
1026
1027### Infinite "(waiting for the first Webpack build to finish)"
1028
1029If you're getting this message infinitely then it means that `webpack-assets.json` is never generated by Webpack.
1030
1031It can happen, for example, in any of these cases
1032
1033 * you forgot to add `webpack-isomorphic-tools` plugin to your Webpack configuration
1034 * you aren't running your Webpack build either in parallel with your app or prior to running you app
1035 * you're using `webpack-dev-middleware` inside your main server code [which you shouldn't](https://github.com/halt-hammerzeit/webpack-isomorphic-tools/issues/47)
1036 * your Webpack configuration's `context` path doesn't point to the project base directory
1037
1038If none of those is your case, enable `debug: true` flag in `webpack-isomorphic-tools` configuration to get debugging info.
1039
1040## Miscellaneous
1041
1042### Webpack 2 `System.import`
1043
1044Instead of implementing `System.import` in this library I think that it would be more rational to use existing tools for transforming `System.import()` calls into `require()` calls. See [this stackoverflow answer](http://stackoverflow.com/questions/37121442/server-side-react-with-webpack-2-system-import/39088208#39088208) for a list of such tools.
1045
1046### .gitignore
1047
1048Make sure you add this to your `.gitignore` so that you don't commit these unnecessary files to your repo
1049
1050```
1051# webpack-isomorphic-tools
1052/webpack-stats.json
1053/webpack-assets.json
1054```
1055
1056### Require() vs import
1057
1058In the image requiring examples above we could have wrote it like this:
1059
1060```
1061import picture from './cat.jpg'
1062```
1063
1064That would surely work. Much simpler and more modern. But, the disadvantage of the new ES6 module `import`ing is that by design it's static as opposed to dynamic nature of `require()`. Such a design decision was done on purpose and it's surely the right one:
1065
1066* it's static so it can be optimized by the compiler and you don't need to know which module depends on which and manually reorder them in the right order because the compiler does it for you
1067* it's smart enough to resolve cyclic dependencies
1068* it can load modules both synchronously and asynchronously if it wants to and you'll never know because it can do it all by itself behind the scenes without your supervision
1069* the `export`s are static which means that your IDE can know exactly what each module is gonna export without compiling the code (and therefore it can autocomplete names, detect syntax errors, check types, etc); the compiler too has some benefits such as improved lookup speed and syntax and type checking
1070* it's simple, it's transparent, it's sane
1071
1072If you wrote your code with just `import`s it would work fine. But imagine you're developing your website, so you're changing files constantly, and you would like it all refresh automagically when you reload your webpage (in development mode). `webpack-isomorphic-tools` gives you that. Remember this code in the express middleware example above?
1073
1074```javascript
1075if (_development_)
1076{
1077 webpack_isomorphic_tools.refresh()
1078}
1079```
1080
1081It does exactly as it says: it refreshes everything on page reload when you're in development mode. And to leverage this feature you need to use dynamic module loading as opposed to static one through `import`s. This can be done by `require()`ing your assets, and not at the top of the file where all `require()`s usually go but, say, inside the `render()` method for React components.
1082
1083I also read on the internets that ES6 supports dynamic module loading too and it looks something like this:
1084
1085```javascript
1086System.import('some_module')
1087.then(some_module =>
1088{
1089 // Use some_module
1090})
1091.catch(error =>
1092{
1093 ...
1094})
1095```
1096
1097I'm currently unfamiliar with ES6 dynamic module loading system because I didn't research this question. Anyway it's still a draft specification so I guess good old `require()` is just fine to the time being.
1098
1099Also it's good to know that the way all this `require('./asset.whatever_extension')` magic is based on [Node.js require hooks](http://bahmutov.calepin.co/hooking-into-node-loader-for-fun-and-profit.html) and it works with `import`s only when your ES6 code is transpiled by Babel which simply replaces all the `import`s with `require()`s. For now, everyone out there uses Babel, both on client and server. But when the time comes for ES6 to be widely natively adopted, and when a good enough ES6 module loading specification is released, then I (or someone else) will port this "require hook" to ES6 to work with `import`s.
1100
1101## References
1102
1103Initially based on the code from [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) by Erik Rasmussen
1104
1105Also the same codebase (as in the project mentioned above) can be found in [isomorphic500](https://github.com/gpbl/isomorphic500) by Giampaolo Bellavite
1106
1107Also uses `require()` hooking techniques from [node-hook](https://github.com/bahmutov/node-hook) by Gleb Bahmutov
1108
1109## Contributing
1110
1111After cloning this repo, ensure dependencies are installed by running:
1112
1113```sh
1114npm install
1115```
1116
1117This module is written in ES6 and uses [Babel](http://babeljs.io/) for ES5
1118transpilation. Widely consumable JavaScript can be produced by running:
1119
1120```sh
1121npm run build
1122```
1123
1124Once `npm run build` has run, you may `import` or `require()` directly from
1125node.
1126
1127After developing, the full test suite can be evaluated by running:
1128
1129```sh
1130npm test
1131```
1132
1133When you're ready to test your new functionality on a real project, you can run
1134
1135```sh
1136npm pack
1137```
1138
1139It will `build`, `test` and then create a `.tgz` archive which you can then install in your project folder
1140
1141```sh
1142npm install [module name with version].tar.gz
1143```
1144
1145## To do
1146
1147 * Implement `require.context(folder, include_subdirectories, regular_expression)` and `require.ensure` Webpack helper functions [properly](https://github.com/halt-hammerzeit/webpack-isomorphic-tools/issues/48#issuecomment-182878437)
1148 * Proper testing for `log` (output to a variable rather than `console`)
1149 * Proper testing for `notify_stats` (output to a `log` variable)
1150 * Proper testing for parsers (using `eval()` CommonJS module compilation)
1151 * Proper testing for `require('./node_modules/whatever.jpg')` test case
1152
1153## License
1154
1155[MIT](LICENSE)
1156[npm-image]: https://img.shields.io/npm/v/webpack-isomorphic-tools.svg
1157[npm-url]: https://npmjs.org/package/webpack-isomorphic-tools
1158[travis-image]: https://img.shields.io/travis/halt-hammerzeit/webpack-isomorphic-tools/master.svg
1159[travis-url]: https://travis-ci.org/halt-hammerzeit/webpack-isomorphic-tools
1160[downloads-image]: https://img.shields.io/npm/dm/webpack-isomorphic-tools.svg
1161[downloads-url]: https://npmjs.org/package/webpack-isomorphic-tools
1162[coveralls-image]: https://img.shields.io/coveralls/halt-hammerzeit/webpack-isomorphic-tools/master.svg
1163[coveralls-url]: https://coveralls.io/r/halt-hammerzeit/webpack-isomorphic-tools?branch=master
1164
1165<!---
1166[gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg
1167[gratipay-url]: https://gratipay.com/dougwilson/
1168-->