UNPKG

37.1 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
12Is a small helper module providing support for isomorphic (universal) rendering when using Webpack.
13
14## Topics
15
16- [What it does and why is it needed?](#what-it-does-and-why-is-it-needed)
17- [Getting down to business](#getting-down-to-business)
18- [Installation](#installation)
19- [Usage](#usage)
20- [A working example](#a-working-example)
21- [Configuration](#configuration)
22- [What's a "module"?](#whats-a-module)
23- [API](#api)
24- [Gotchas](#gotchas)
25- [References](#references)
26- [Contributing](#contributing)
27
28## What it does and why is it needed?
29
30What is a web application? I would define it as a box with a bunch of inputs (keyboard events, mouse events) and a display as an output. A user walks into your website and your web application renders a "page" on his display.
31
32At first all the rendering used to happen on the server. But then "AJAX" came (in 2005) and it opened a possibility of moving all rendering logic to the client (user's web browser) leaving the server with just serving API calls (data fetching, data modification, etc).
33
34And so numerous javascript frameworks emerged to serve the purpose of client side rendering and routing. But then everybody realised that this new way of building web applications broke search engine indexing because the search engines didn't talk any javascript.
35
36Then the age of super-responsive websites came and also the iPhone emerged and the battle for milliseconds began. And everybody noticed that client side rendering introduced unnecessary data fetching roundtrips on the first page load: the web browser loaded markup templates and scripts first and then asked the server for the actual data to display.
37
38So it became obvious that web applications need to be "isomorphic" ("universal"), i.e. be able to render both on the client and the server, depending on circumstances. It was quite manageable: one just had to write the rendering logic in such a programming language that is able to run both on client and server. One such language is javascript.
39
40Time moved on and then another new technology emerged - bundling: web applications became so sophisticated and big that the programmers needed a software to take control of the development, testing and building process and to manage all the heterogeneous components of the system. Currently the most popular and well-thought bundler is Webpack.
41
42But Webpack is made for client side code development only: it finds all `require()` calls inside your code and replaces them with various kinds of magic to make the things work. If you try to run the same source code outside of Webpack - for example, on a Node.js server - you'll get a ton of `SyntaxError`s with `Unexpected token`s. That's because on a Node.js server there's no Webpack `require()` magic happening and it simply tries to `require()` all the "assets" (styles, images, fonts, OpenGL shaders, etc) as if they were proper javascript-coded modules hence the error message.
43
44This module - `webpack-isomorphic-tools` - aims to solve these issues and make the client-side code work on the server too therefore reclaiming isomorphic (universal) rendering capabilities. It provides the missing `require()` magic - same as Webpack does on client-side - when running your code on the server. With the help of `webpack-isomorphic-tools` one can fix all those webpack-ish `require()`s of assets and make them work on the server instead of throwing `SyntaxError`s.
45
46## Getting down to business
47
48For example, consider images. Images are `require()`d in React components and then used like this:
49
50```javascript
51// alternatively one can use import, but in this case hot reloading won't work
52// import image_path from '../image.png'
53
54// you just `src` your image inside your `render()` method
55class Photo extends React.Component
56{
57 render()
58 {
59 // when Webpack url-loader finds this `require()` call
60 // it will copy `image.png` to your build folder
61 // and name it something like `9059f094ddb49c2b0fa6a254a6ebf2ad.png`,
62 // because we are using the `[hash]` file naming feature of Webpack url-loader
63 // which (feature) is required to make browser caching work correctly
64 const image_path = require('../image.png')
65
66 return <img src={image_path}/>
67 }
68}
69```
70
71It works on the client because Webpack intelligently replaces all the `require()` calls for you.
72But it wouldn't work on the server because Node.js only knows how to `require()` javascript modules. It would just throw a `SyntaxError`.
73
74To solve this issue you use `webpack-isomorphic-tools` in your application and what it does is it makes the code above work on the server too so that you can have your isomorphic (universal) rendering working.
75
76In 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` know this weird real file path? It's just a bit of magic.
77
78So, you get the idea now?
79
80Aside all of that, `webpack-isomorphic-tools` is highly extensible, and finding the real paths for your assets is just the simplest example of what you can do. Using [custom configuration](#configuration) one can make `require()` calls (on the server) return whatever is needed (not just a String; it may be a JSON object, for example).
81
82For example, if you're 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)) you can make `require(*.css)` calls return JSON objects with generated CSS class names like they do in [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example#styles) (it's just a demonstration of what one can do with `webpack-isomorphic-tools`, and I'm not using this "modules" feature of `ccs-plugin` in my projects).
83
84## Installation
85
86`webpack-isomorphic-tools` are required both for development and production
87
88```bash
89$ npm install webpack-isomorphic-tools --save
90```
91
92## Usage
93
94First you add `webpack_isomorphic_tools` plugin to your Webpack configuration.
95
96### webpack.config.js
97
98```javascript
99var Webpack_isomorphic_tools_plugin = require('webpack-isomorphic-tools/plugin')
100
101var webpack_isomorphic_tools_plugin =
102 // webpack-isomorphic-tools settings reside in a separate .js file
103 // (because they will be used in the web server code too).
104 new Webpack_isomorphic_tools_plugin(require('./webpack-isomorphic-tools-configuration'))
105 // also enter development mode since it's a development webpack configuration
106 // (see below for explanation)
107 .development()
108
109// usual Webpack configuration
110module.exports =
111{
112 context: '(required) your project path here',
113
114 output:
115 {
116 publicPath: '(required) web path for static files here'
117 },
118
119 module:
120 {
121 loaders:
122 [
123 ...,
124 {
125 test: webpack_isomorphic_tools_plugin.regular_expression('images'),
126 loader: 'url-loader?limit=10240', // any image below or equal to 10K will be converted to inline base64 instead
127 }
128 ]
129 },
130
131 plugins:
132 [
133 ...,
134
135 webpack_isomorphic_tools_plugin
136 ]
137
138 ...
139}
140```
141
142What does `.development()` method do? It enables development mode. In short, when in development mode, it disables asset caching (and enables asset hot reload). Just call it if you're developing your project with `webpack-dev-server` using this config (and, conversely, don't call it for your production webpack build - obvious enough).
143
144For 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:
145
146### webpack-isomorphic-tools-configuration.js
147
148```javascript
149import Webpack_isomorphic_tools_plugin from 'webpack-isomorphic-tools/plugin'
150
151export default
152{
153 assets:
154 {
155 images:
156 {
157 extensions: ['png', 'jpg', 'gif', 'ico', 'svg'],
158 parser: Webpack_isomorphic_tools_plugin.url_loader_parser // see Configuration and API sections for more info on this parameter
159 }
160 }
161}
162```
163
164That'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)
165
166### main.js
167
168```javascript
169var Webpack_isomorphic_tools = require('webpack-isomorphic-tools')
170
171// this must be equal to your Webpack configuration "context" parameter
172var project_base_path = require('path').resolve(__dirname, '..')
173
174// this global variable will be used later in express middleware
175global.webpack_isomorphic_tools = new Webpack_isomorphic_tools(require('./webpack-isomorphic-tools-configuration'))
176// enter development mode if needed
177// (you may also prefer to use a Webpack DefinePlugin variable)
178.development(process.env.NODE_ENV === 'development')
179// initializes a server-side instance of webpack-isomorphic-tools
180// (the first parameter is the base path for your project
181// and is equal to the "context" parameter of you Webpack configuration)
182// (if you prefer Promises over callbacks
183// you can omit the callback parameter
184// and then it will return a Promise instead)
185.server(project_base_path, function()
186{
187 // webpack-isomorphic-tools is all set now.
188 // here goes all your web application code:
189 require('./server')
190})
191```
192
193Then you, for example, create an express middleware to render your pages on the server
194
195```javascript
196import React from 'react'
197
198// html page markup
199import Html from './html'
200
201// will be used in express_application.use(...)
202export function page_rendering_middleware(request, response)
203{
204 // clear require() cache if in development mode
205 // (makes asset hot reloading work)
206 if (_development_)
207 {
208 webpack_isomorphic_tools.refresh()
209 }
210
211 // for react-router example of determining current page by URL take a look at this:
212 // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
213 const page_component = [determine your page component here using request.path]
214
215 // for a Redux Flux store implementation you can see the same example:
216 // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
217 const flux_store = [initialize and populate your flux store depending on the page being shown]
218
219 // render the page to string and send it to the browser as text/html
220 response.send('<!doctype html>\n' +
221 React.renderToString(<Html assets={webpack_isomorphic_tools.assets()} component={page_component} store={flux_store}/>))
222}
223```
224
225And finally you use the `assets` inside the `Html` component's `render()` method
226
227```javascript
228import React, {Component, PropTypes} from 'react'
229import serialize from 'serialize-javascript'
230
231export default class Html extends Component
232{
233 static propTypes =
234 {
235 assets : PropTypes.object,
236 component : PropTypes.object,
237 store : PropTypes.object
238 }
239
240 render()
241 {
242 const { assets, component, store } = this.props
243
244 // "import" will work here too
245 // but if you want hot reloading to work while developing your project
246 // then you need to use require()
247 // because import will only be executed a single time
248 // (when the application launches)
249 // you can refer to the "Require() vs import" section for more explanation
250 const picture = require('../assets/images/cat.jpg')
251
252 // favicon
253 const icon = require('../assets/images/icon/32x32.png')
254
255 const html =
256 (
257 <html lang="en-us">
258 <head>
259 <meta charSet="utf-8"/>
260 <title>xHamster</title>
261
262 {/* favicon */}
263 <link rel="shortcut icon" href={icon} />
264
265 {/* styles (will be present only in production with webpack extract text plugin) */}
266 {Object.keys(assets.styles).map((style, i) =>
267 <link href={assets.styles[style]} key={i} media="screen, projection"
268 rel="stylesheet" type="text/css"/>)}
269
270 {/* resolves the initial style flash (flicker) on page load in development mode */}
271 { Object.keys(assets.styles).is_empty() ? <style dangerouslySetInnerHTML={{__html: require('../assets/styles/main_style.css')}}/> : null }
272 </head>
273
274 <body>
275 {/* image requiring demonstration */}
276 <img src={picture}/>
277
278 {/* rendered React page */}
279 <div id="content" dangerouslySetInnerHTML={{__html: React.renderToString(component)}}/>
280
281 {/* Flux store data will be reloaded into the store on the client */}
282 <script dangerouslySetInnerHTML={{__html: `window._flux_store_data=${serialize(store.getState())};`}} />
283
284 {/* javascripts */}
285 {/* (usually one for each "entry" in webpack configuration) */}
286 {/* (for more informations on "entries" see https://github.com/petehunt/webpack-howto/) */}
287 {Object.keys(assets.javascript).map((script, i) =>
288 <script src={assets.javascript[script]} key={i}/>
289 )}
290 </body>
291 </html>
292 )
293
294 return html
295 }
296}
297```
298
299`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.
300
301```javascript
302{
303 "javascript":
304 {
305 "main": "/assets/main-d8c29e9b2a4623f696e8.js"
306 },
307
308 "styles":
309 {
310 "main": "/assets/main-d8c29e9b2a4623f696e8.css"
311 },
312
313 "assets":
314 {
315 "./assets/images/cat.jpg": "http://localhost:3001/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
316
317 "./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="
318 }
319}
320```
321
322And that's it, now you can `require()` your assets "isomorphically" (both on client and server).
323
324## A working example
325
326`webpack-isomorphic-tools` are featured in [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example).
327
328Also for a comprehensive example of isomorphic React rendering you can look at [this sample project](https://github.com/halt-hammerzeit/webapp) (see the Quick Start section of the readme)
329
330Some source code guidance for the aforementioned project:
331
332* [webpack-isomorphic-tools configuration](https://github.com/halt-hammerzeit/webapp/blob/master/webpack/isomorphic.js)
333* [webpack-isomorphic-tools client initialization](https://github.com/halt-hammerzeit/webapp/blob/master/webpack/development%20server.js)
334* [webpack-isomorphic-tools server initialization](https://github.com/halt-hammerzeit/webapp/blob/master/code/server/entry.js)
335* [webpage rendering express middleware](https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js)
336* [the Html file](https://github.com/halt-hammerzeit/webapp/blob/master/code/client/html.js)
337
338## Configuration
339
340Available configuration parameters:
341
342```javascript
343{
344 // sets "development" mode flag.
345 // see the API section below for method .development()
346 // for more explanation about what "development" mode does
347 // and when is it needed.
348 development: true, // is false by default
349
350 // debug mode.
351 // when set to true, lets you see debugging messages in the console.
352 debug: true, // is false by default
353
354 // By default it creates 'webpack-assets.json' file at
355 // webpack_configuration.context (which is your project folder).
356 // You can change the assets file path as you wish
357 // (therefore changing both folder and filename).
358 //
359 // (relative to webpack_configuration.context which is your project folder)
360 // (these aren't actually 'stats', these are some values derived from Webpack 'stats')
361 webpack_assets_file_path: 'webpack-stats.json', // is 'webpack-assets.json' by default
362
363 // here you can define all your asset types
364 assets:
365 {
366 // asset_type will appear in:
367 // * webpack-assets.json
368 // * .assets() method call result
369 // * .regular_expression(asset_type) method call
370 asset_type:
371 {
372 // which file types belong to this asset type
373 extension: 'png', // or extensions: ['png', 'jpg', ...],
374
375 // here you are able to add some file paths
376 // for which the require() call will bypass webpack-isomorphic-tools
377 // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
378 // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
379 // and functions(path) { return true / false })
380 exclude: [],
381
382 // here you can specify manually the paths
383 // for which the require() call will be processed by webpack-isomorphic-tools
384 // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
385 // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
386 // and functions(path) { return true / false }).
387 // in case of `include` only included paths will be processed by webpack-isomorphic-tools.
388 include: [],
389
390 // [optional]
391 //
392 // determines which webpack stats modules
393 // belong to this asset type
394 //
395 // arguments:
396 //
397 // module - a webpack stats module
398 //
399 // (to understand what a "module" is
400 // read the "What's a "module"?" section of this readme)
401 //
402 // regular_expression - a regular expression
403 // composed of this asset type's extensions
404 // e.g. /\.scss$/, /\.(ico|gif)$/
405 //
406 // options - various options
407 // (development mode flag,
408 // debug mode flag,
409 // assets base url,
410 // project base folder,
411 // regular_expressions{} for each asset type (by name),
412 // webpack stats json object)
413 //
414 // log
415 //
416 // returns: a Boolean
417 //
418 // by default is: "return regular_expression.test(module.name)"
419 //
420 // premade utility filters:
421 //
422 // Webpack_isomorphic_tools_plugin.style_loader_filter
423 // (for use with style-loader + css-loader)
424 //
425 filter: function(module, regular_expression, options, log)
426 {
427 return regular_expression.test(module.name)
428 },
429
430 // [optional]
431 //
432 // transforms a webpack stats module name
433 // to an asset path (usually is the same thing)
434 //
435 // arguments:
436 //
437 // module - a webpack stats module
438 //
439 // (to understand what a "module" is
440 // read the "What's a "module"?" section of this readme)
441 //
442 // options - various options
443 // (development mode flag,
444 // debug mode flag,
445 // assets base url,
446 // project base folder,
447 // regular_expressions{} for each asset type (by name),
448 // webpack stats json object)
449 //
450 // log
451 //
452 // returns: a String
453 //
454 // by default is: "return module.name"
455 //
456 // premade utility path extractors:
457 //
458 // Webpack_isomorphic_tools_plugin.style_loader_path_extractor
459 // (for use with style-loader + css-loader)
460 //
461 path: function(module, options, log)
462 {
463 return module.name
464 },
465
466 // [required]
467 //
468 // parses a webpack stats module object
469 // for an asset of this asset type
470 // to whatever you need to get
471 // when you require() these assets
472 // in your code later on.
473 //
474 // this is what you'll see as the asset value in webpack-assets.json:
475 // { ..., path: parser(), ... }
476 //
477 // can be a CommonJS module source code:
478 // module.exports = ...what you export here is what you get when you require()...
479 //
480 // if it's not a CommonJS module (a string, a JSON object)
481 // then it will be transformed to a CommonJS module source code.
482 //
483 // in other words: require(...) = function(...) { return eval(parser(...)) }
484 //
485 // arguments:
486 //
487 // module - a webpack stats module
488 //
489 // (to understand what a "module" is
490 // read the "What's a "module"?" section of this readme)
491 //
492 // options - various options
493 // (development mode flag,
494 // debug mode flag,
495 // assets base url,
496 // project base folder,
497 // regular_expressions{} for each asset type (by name),
498 // webpack stats json object)
499 //
500 // log
501 //
502 // returns: whatever (could be a filename, could be a JSON object, etc)
503 //
504 // premade utility parsers:
505 //
506 // Webpack_isomorphic_tools_plugin.url_loader_parser
507 // (for use with url-loader or file-loader)
508 // require() will return file URL
509 //
510 // Webpack_isomorphic_tools_plugin.css_loader_parser
511 // (for use with css-loader when not using "modules" feature)
512 // require() will return CSS style text
513 //
514 // Webpack_isomorphic_tools_plugin.css_modules_loader_parser
515 // (for use with css-loader when using "modules" feature)
516 // require() will return a JSON object map of style class names
517 // which will also have a `_style` key containing CSS style text
518 //
519 parser: function(module, options, log)
520 {
521 log.info('# module name', module.name)
522 log.info('# module source', module.source)
523 log.info('# project path', options.project_path)
524 log.info('# assets base url', options.assets_base_url)
525 log.info('# regular expressions', options.regular_expressions)
526 log.info('# debug mode', options.debug)
527 log.info('# development mode', options.development)
528 log.debug('debugging')
529 log.warning('warning')
530 log.error('error')
531 }
532 },
533 ...
534 },
535 ...]
536}
537```
538
539## What are Webpack stats?
540
541[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 development 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.
542
543## What's a "module"?
544
545**This is an advanced topic on Webpack internals**
546
547A "module" is a Webpack entity. When Webpack compiles your code it splits it into "chunks" (which is irrelevant to this explanation). Every time you `require()` a file (it could be anything: a javascript file, or a css style, or an image) a `module` entry is created. And the file from where you `require()`d this file is called a "reason" for this "module". Also each `module` has a `name` and a `source`, along with a list of `chunks` it's in and a bunch of other miscellaneous irrelevant properties.
548
549For example, here's the content of the `webpack-stats.json` file (which is generated along with `webpack-assets.json`) for a random "module".
550
551```javascript
552{
553 ...
554
555 "modules": [
556 {
557 "id": 0,
558 ...
559 },
560 {
561 "id": 1,
562 "name": "./~/fbjs/lib/invariant.js",
563 "source": "module.exports = global[\"undefined\"] = require(\"-!G:\\\\work\\\\isomorphic-demo\\\\node_modules\\\\fbjs\\\\lib\\\\invariant.js\");",
564
565 // the rest of the fields are irrelevant
566
567 "chunks": [
568 0
569 ],
570 "identifier": "G:\\work\\isomorphic-demo\\node_modules\\expose-loader\\index.js?undefined!G:\\work\\isomorphic-demo\\node_modules\\fbjs\\lib\\invariant.js",
571 "index": 27,
572 "index2": 7,
573 "size": 117,
574 "cacheable": true,
575 "built": true,
576 "optional": false,
577 "prefetched": false,
578 "assets": [],
579 "issuer": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
580 "failed": false,
581 "errors": 0,
582 "warnings": 0,
583
584 "reasons": [
585 {
586 "moduleId": 418,
587 "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
588 "module": "./~/react/lib/ReactInstanceHandles.js",
589 "moduleName": "./~/react/lib/ReactInstanceHandles.js",
590 "type": "cjs require",
591 "userRequest": "fbjs/lib/invariant",
592 "loc": "17:16-45"
593 },
594 ...
595 {
596 "moduleId": 483,
597 "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\traverseAllChildren.js",
598 "module": "./~/react/lib/traverseAllChildren.js",
599 "moduleName": "./~/react/lib/traverseAllChildren.js",
600 "type": "cjs require",
601 "userRequest": "fbjs/lib/invariant",
602 "loc": "19:16-45"
603 }
604 ]
605 },
606
607 ...
608 ]
609}
610```
611
612When a `require()` call fires the corresponding module is "loaded" (decorated, transformed, replaced, etc) by a corresponding module "loader" specified in Webpack configuration file (`webpack.conf.js`) under the "module.loaders" path. For example, say, all JPG images in a project are loaded with a "url-loader":
613
614```javascript
615{
616 ...
617
618 module:
619 {
620 loaders:
621 [
622 ...
623
624 {
625 test : /\.jpg$/,
626 loader : 'url-loader'
627 }
628 ]
629 },
630
631 ...
632}
633```
634
635This 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)
636
637The `module` object would look like this
638
639```javascript
640{
641 ...
642 "name": "./assets/images/husky.jpg",
643 "source": "module.exports = __webpack_public_path__ + \"9059f094ddb49c2b0fa6a254a6ebf2ad.jpg\""
644}
645```
646
647Therefore
648
649* the `filter` function would be `module => module.name.ends_with('.jpg')`
650* the `naming` function would be `module => module.name`
651* the `parser` function would be `module => build_folder_path + module.source.substring(first_quote_index, second_quote_index)`
652
653## API
654
655#### Constructor
656
657(both Webpack plugin and server tools)
658
659Takes an object with options (see [Configuration](#configuration) section above)
660
661#### .development(true or false or undefined -> true)
662
663(both Webpack plugin and server tools)
664
665Is 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, or if you're instantiating `webpack-isomorphic-tools` on server when you're developing your project, then you should call this method to enable asset hot reloading (and disable asset caching). It should be called right after the constructor.
666
667#### .regular_expression(asset_type)
668
669(Webpack plugin)
670
671Returns the regular expression for this asset type (based on this asset type's `extension` (or `extensions`))
672
673#### Webpack_isomorphic_tools_plugin.url_loader_parser
674
675(Webpack plugin)
676
677A 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.
678
679#### .server(project_path, [callback])
680
681(server tools)
682
683Initializes 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.
684
685When 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`.
686
687To 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.
688
689#### .refresh()
690
691(server tools)
692
693Refreshes your assets info (re-reads `webpack-assets.json` from disk) and also flushes cache for all the previously `require()`d assets
694
695#### .assets()
696
697(server tools)
698
699Returns the contents of `webpack-assets.json` which is created by `webpack-isomorphic-tools` in your project base folder
700
701## Gotchas
702
703### .gitignore
704
705Make sure you add this to your `.gitignore`
706
707```
708# webpack-isomorphic-tools
709/webpack-stats.json
710/webpack-assets.json
711```
712
713### Require() vs import
714
715In the image requiring examples above we could have wrote it like this:
716
717```
718import picture from './cat.jpg'
719```
720
721That 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:
722
723* 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
724* it's smart enough to resolve cyclic dependencies
725* 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
726* 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
727* it's simple, it's transparent, it's sane
728
729If 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?
730
731```javascript
732if (_development_)
733{
734 webpack_isomorhic_tools.refresh()
735}
736```
737
738It 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 `reder()` method for React components.
739
740I also read on the internets that ES6 supports dynamic module loading too and it looks something like this:
741
742```javascript
743System.import('some_module')
744.then(some_module =>
745{
746 // Use some_module
747})
748.catch(error =>
749{
750 ...
751})
752```
753
754I'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.
755
756Also 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.
757
758### Error: Cannot find module
759
760This error means that the `require()`d path doesn't exist in the filesystem and also wasn't found in `webpack-stats.json`.
761
762### SyntaxError: Unexpected token ILLEGAL
763
764This probably means that in some asset module source there's a `require()` call to some file extension that isn't specified in
765
766## References
767
768Initially based on the code from [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) by Erik Rasmussen
769
770Also the same codebase (as in the project mentioned above) can be found in [isomorphic500](https://github.com/gpbl/isomorphic500) by Giampaolo Bellavite
771
772Also uses `require()` hooking techniques from [node-hook](https://github.com/bahmutov/node-hook) by Gleb Bahmutov
773
774## Contributing
775
776After cloning this repo, ensure dependencies are installed by running:
777
778```sh
779npm install
780```
781
782This module is written in ES6 and uses [Babel](http://babeljs.io/) for ES5
783transpilation. Widely consumable JavaScript can be produced by running:
784
785```sh
786npm run build
787```
788
789Once `npm run build` has run, you may `import` or `require()` directly from
790node.
791
792After developing, the full test suite can be evaluated by running:
793
794```sh
795npm test
796```
797
798While actively developing, one can use
799
800```sh
801npm run watch
802```
803
804in a terminal. This will watch the file system and run tests automatically
805whenever you save a js file.
806
807## License
808
809[MIT](LICENSE)
810[npm-image]: https://img.shields.io/npm/v/webpack-isomorphic-tools.svg
811[npm-url]: https://npmjs.org/package/webpack-isomorphic-tools
812[travis-image]: https://img.shields.io/travis/halt-hammerzeit/webpack-isomorphic-tools/master.svg
813[travis-url]: https://travis-ci.org/halt-hammerzeit/webpack-isomorphic-tools
814[downloads-image]: https://img.shields.io/npm/dm/webpack-isomorphic-tools.svg
815[downloads-url]: https://npmjs.org/package/webpack-isomorphic-tools
816[coveralls-image]: https://img.shields.io/coveralls/halt-hammerzeit/webpack-isomorphic-tools/master.svg
817[coveralls-url]: https://coveralls.io/r/halt-hammerzeit/webpack-isomorphic-tools?branch=master
818
819<!---
820[gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg
821[gratipay-url]: https://gratipay.com/dougwilson/
822-->
\No newline at end of file