UNPKG

27.3 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## What it does and why is it needed?
15
16Javascript allows you to run all your `.js` code (Views, Controllers, Stores, and so on) both on the client and the server, and Webpack gives you the ability to just `require()` your javascript modules both on the client and the server so that the same code works both on the client and the server automagically (I guess that was the main purpose of Webpack).
17
18When you write your web application in React, you create the main `style.css` where you describe all your base styles (h1, h2, a, p, nav, footer, fonts, etc).
19
20Then, you use inline styles to style each React component individually (use [react-styling](https://github.com/halt-hammerzeit/react-styling) for that).
21
22What about that `style.css` file? On the server in development mode it needs to be injected automagically through javascript to support hot module reload, so you don't need to know the exact path to it on disk because it isn't even a `.css` file on your disk: it's actually a javascript file because that's how Webpack [style-loader](https://github.com/webpack/style-loader) works. So you don't need to `require()` your styles in the server code because you simply can't because there are no such files. (You only need to require `style.css` in your `client-application.js` which is gonna be a Webpack entry point)
23
24What about fonts? Fonts are parsed correctly by Webpack [css-loader](https://github.com/webpack/css-loader) when it finds `url()` sections in your main `style.css`, so no issues there.
25
26What's left are images. Images are `require()`d in React components and then used like this:
27
28```javascript
29// alternatively one can use import, but in this case hot reloading won't work
30// import image from '../image.png'
31
32// next you just `src` your image inside your `render()` method
33class Photo extends React.Component
34{
35 render()
36 {
37 // when Webpack url-loader finds this `require()` call
38 // it will copy `image.png` to your build folder
39 // and name it something like `9059f094ddb49c2b0fa6a254a6ebf2ad.png`,
40 // because we are using the `[hash]` file naming feature of Webpack url-loader
41 // which (feature) is required to make browser caching work correctly
42 const image = require('../image.png')
43
44 return <img src={image}/>
45 }
46}
47```
48
49It works on the client because Webpack intelligently replaces all the `require()` calls for you.
50But it wouldn't work on the server because Node.js only knows how to `require()` javascript modules.
51What `webpack-isomorphic-tools` does is it makes the code above work on the server too (and much more), so that you can have your isomorphic (universal) rendering (e.g. React).
52
53What about javascripts on the Html page?
54
55When you render your Html page on the server you need to include all the client scripts using `<script src={...}/>` tags. And for that purpose you need to know the real paths to your Webpack compiled javascripts. Which are gonna have names like `main-9059f094ddb49c2b0fa6a254a6ebf2ad.js` because we are using the `[hash]` file naming feature of Webpack which is required to make browser caching work correctly. And `webpack-isomorphic-tools` tells you these filenames (see the [Usage](#usage) section).
56
57It also tells you real paths to your CSS styles in case you're using [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin) which is usually the case for production build.
58
59Aside all of that, `webpack-isomorphic-tools` is highly extensible, and finding the real paths for your assets is just the simplest example of what it's capable of. Using [custom configuration](#configuration) one can make `require()` calls return virtually anything (not just String, it may be a JSON object, for example). For 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 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).
60
61## Installation
62
63`webpack-isomorphic-tools` are required both for development and production
64
65```bash
66$ npm install webpack-isomorphic-tools --save
67```
68
69## Usage
70
71First you add `webpack_isomorphic_tools` plugin to your Webpack configuration.
72
73### webpack.config.js
74
75```javascript
76var Webpack_isomorphic_tools_plugin = require('webpack-isomorphic-tools/plugin')
77
78var webpack_isomorphic_tools_plugin =
79 // webpack-isomorphic-tools settings reside in a separate .js file
80 // (because they will be used in the web server code too).
81 new Webpack_isomorphic_tools_plugin(require('./webpack-isomorphic-tools-configuration'))
82 // also enter development mode since it's a development webpack configuration
83 // (see below for explanation)
84 .development()
85
86// usual Webpack configuration
87module.exports =
88{
89 context: '(required) your project path here',
90
91 output:
92 {
93 publicPath: '(required) web path for static files here'
94 },
95
96 module:
97 {
98 loaders:
99 [
100 ...,
101 {
102 test: webpack_isomorphic_tools_plugin.regular_expression('images'),
103 loader: 'url-loader?limit=10240', // any image below or equal to 10K will be converted to inline base64 instead
104 }
105 ]
106 },
107
108 plugins:
109 [
110 ...,
111
112 webpack_isomorphic_tools_plugin
113 ]
114
115 ...
116}
117```
118
119What 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 don't call it for production webpack build).
120
121For 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:
122
123### webpack-isomorphic-tools-configuration.js
124
125```javascript
126import Webpack_isomorphic_tools_plugin from 'webpack-isomorphic-tools/plugin'
127
128export default
129{
130 assets:
131 {
132 images:
133 {
134 extensions: ['png', 'jpg', 'gif', 'ico', 'svg'],
135 parser: Webpack_isomorphic_tools_plugin.url_loader_parser // see Configuration and API sections for more info on this parameter
136 }
137 }
138}
139```
140
141That'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)
142
143### main.js
144
145```javascript
146var Webpack_isomorphic_tools = require('webpack-isomorphic-tools')
147
148// this must be equal to your Webpack configuration "context" parameter
149var project_base_path = require('path').resolve(__dirname, '..')
150
151// this global variable will be used later in express middleware
152global.webpack_isomorphic_tools = new Webpack_isomorphic_tools(require('./webpack-isomorphic-tools-configuration'))
153// enter development mode if needed
154// (for example, based on a Webpack DefinePlugin variable)
155.development(_development_)
156// initializes a server-side instance of webpack-isomorphic-tools
157// (the first parameter is the base path for your project)
158.server(project_base_path, function()
159{
160 // webpack-isomorphic-tools is all set now.
161 // here goes all your web application code:
162 require('./server')
163})
164```
165
166Then you, for example, create an express middleware to render your pages on the server
167
168```javascript
169import React from 'react'
170
171// html page markup
172import Html from './html'
173
174// will be used in express_application.use(...)
175export function page_rendering_middleware(request, response)
176{
177 // clear require() cache if in development mode
178 // (makes asset hot reloading work)
179 if (_development_)
180 {
181 webpack_isomorphic_tools.refresh()
182 }
183
184 // for react-router example of determining current page by URL take a look at this:
185 // https://github.com/halt-hammerzeit/cinema/blob/master/code/server/webpage%20rendering.js
186 const page_component = [determine your page component here using request.path]
187
188 // for a Redux Flux store implementation you can see the same example:
189 // https://github.com/halt-hammerzeit/cinema/blob/master/code/server/webpage%20rendering.js
190 const flux_store = [initialize and populate your flux store depending on the page being shown]
191
192 // render the page to string and send it to the browser as text/html
193 response.send('<!doctype html>\n' +
194 React.renderToString(<Html assets={webpack_isomorphic_tools.assets()} component={page_component} store={flux_store}/>))
195}
196```
197
198And finally you use the `assets` inside the `Html` component's `render()` method
199
200```javascript
201import React, {Component, PropTypes} from 'react'
202import serialize from 'serialize-javascript'
203
204export default class Html extends Component
205{
206 static propTypes =
207 {
208 assets : PropTypes.object,
209 component : PropTypes.object,
210 store : PropTypes.object
211 }
212
213 render()
214 {
215 const { assets, component, store } = this.props
216
217 // "import" will work here too
218 // but if you want hot reloading to work while developing your project
219 // then you need to use require()
220 // because import will only be executed a single time
221 // (when the application launches)
222 // you can refer to the "Require() vs import" section for more explanation
223 const picture = require('../assets/images/cat.jpg')
224
225 // favicon
226 const icon = require('../assets/images/icon/32x32.png')
227
228 const html =
229 (
230 <html lang="en-us">
231 <head>
232 <meta charSet="utf-8"/>
233 <title>xHamster</title>
234
235 {/* favicon */}
236 <link rel="shortcut icon" href={icon} />
237
238 {/* styles (will be present only in production with webpack extract text plugin) */}
239 {Object.keys(assets.styles).map((style, i) =>
240 <link href={assets.styles[style]} key={i} media="screen, projection"
241 rel="stylesheet" type="text/css"/>)}
242 </head>
243
244 <body>
245 {/* image requiring demonstration */}
246 <img src={picture}/>
247
248 {/* rendered React page */}
249 <div id="content" dangerouslySetInnerHTML={{__html: React.renderToString(component)}}/>
250
251 {/* Flux store data will be reloaded into the store on the client */}
252 <script dangerouslySetInnerHTML={{__html: `window._flux_store_data=${serialize(store.getState())};`}} />
253
254 {/* javascripts */}
255 {/* (usually one for each "entry" in webpack configuration) */}
256 {/* (for more informations on "entries" see https://github.com/petehunt/webpack-howto/) */}
257 {Object.keys(assets.javascript).map((script, i) =>
258 <script src={assets.javascript[script]} key={i}/>
259 )}
260 </body>
261 </html>
262 )
263
264 return html
265 }
266}
267```
268
269`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.
270
271```javascript
272{
273 javascript:
274 {
275 main: '/assets/main-d8c29e9b2a4623f696e8.js'
276 },
277
278 styles:
279 {
280 main: '/assets/main-d8c29e9b2a4623f696e8.css'
281 },
282
283 images:
284 {
285 './assets/images/cat.jpg': '/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg',
286
287 './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='
288 }
289}
290```
291
292And that's it, now you can `require()` your assets "isomorphically" (both on client and server).
293
294## A working example
295
296`webpack-isomorphic-tools` are featured in [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example).
297
298Also for a comprehensive example of isomorphic React rendering you can look at this sample project:
299
300* clone [this repo](https://github.com/halt-hammerzeit/cinema)
301* `npm install`
302* `npm run dev`
303* wait a moment for Webpack to finish the first build (green stats will appear in the terminal)
304* go to `http://localhost:3000`
305* `Ctrl + C`
306* `npm run production`
307* go to `http://localhost:3000`
308
309Some source code guidance for this particular project:
310
311* [webpack-isomorphic-tools configuration](https://github.com/halt-hammerzeit/cinema/blob/master/webpack/isomorphic.js)
312* [webpack-isomorphic-tools client initialization](https://github.com/halt-hammerzeit/cinema/blob/master/webpack/development%20server.js)
313* [webpack-isomorphic-tools server initialization](https://github.com/halt-hammerzeit/cinema/blob/master/code/server/entry.js)
314* [webpage rendering express middleware](https://github.com/halt-hammerzeit/cinema/blob/master/code/server/webpage%20rendering.js)
315* [the Html file](https://github.com/halt-hammerzeit/cinema/blob/master/code/client/html.js)
316
317## Configuration
318
319Available configuration parameters:
320
321```javascript
322{
323 // sets "development" mode flag.
324 // see the API section below for method .development()
325 // for more explanation about what "development" mode does
326 // and when is it needed.
327 development: true, // is false by default
328
329 // debug mode.
330 // when set to true, lets you see debugging messages in the console,
331 // and also outputs 'webpack-stats.debug.json' file with Webpack stats.
332 // (you'll be interested in the contents of this file
333 // in case you're writing your own filter, naming or parser
334 // for some asset type)
335 debug: true, // is false by default
336
337 // By default it creates 'webpack-assets.json' file at
338 // webpack_configuration.context (which is your project folder).
339 // You can change the assets file path as you wish
340 // (therefore changing both folder and filename).
341 //
342 // The folder derived from this parameter will also be used
343 // to store 'webpack-stats.debug.json' file in case you're in debug mode
344 //
345 // (relative to webpack_configuration.context which is your project folder)
346 // (these aren't actually 'stats', these are some values derived from Webpack 'stats')
347 webpack_assets_file_path: 'webpack-stats.json', // is 'webpack-assets.json' by default
348
349 // here you are able to add some file paths
350 // for which the require() call will bypass webpack-isomorphic-tools
351 // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
352 exceptions: [],
353
354 // here you can define all your asset types
355 assets:
356 {
357 // asset_type will appear in:
358 // * webpack-assets.json
359 // * .assets() method call result
360 // * .regular_expression(asset_type) method call
361 asset_type:
362 {
363 // which file types belong to this asset type
364 extension: 'png', // or extensions: ['png', 'jpg', ...],
365
366 // [optional]
367 //
368 // determines which webpack stats modules
369 // belong to this asset type
370 //
371 // arguments:
372 //
373 // module - a webpack stats module
374 //
375 // regular_expression - a regular expression
376 // composed of this asset type's extensions
377 // e.g. /\.scss$/, /\.(ico|gif)$/
378 //
379 // options - various options
380 // (development mode flag,
381 // debug mode flag,
382 // assets base path (on the disk or on the network),
383 // regular_expressions{} for each asset type (by name),
384 // webpack stats json object)
385 //
386 // log
387 //
388 // returns: a Boolean
389 //
390 // by default is: "return regular_expression.test(module.name)"
391 //
392 filter: function(module, regular_expression, options, log)
393 {
394 return regular_expression.test(module.name)
395 },
396
397 // [optional]
398 //
399 // transforms a webpack stats module name
400 // to an asset name
401 //
402 // arguments:
403 //
404 // module - a webpack stats module
405 //
406 // options - various options
407 // (development mode flag,
408 // debug mode flag,
409 // assets base path (on the disk or on the network),
410 // regular_expressions{} for each asset type (by name),
411 // webpack stats json object)
412 //
413 // log
414 //
415 // returns: a String
416 //
417 // by default is: "return module.name"
418 //
419 naming: function(module, options, log)
420 {
421 return module.name
422 },
423
424 // [required]
425 //
426 // parses a webpack stats module object
427 // for an asset of this asset type
428 // to whatever you need to get
429 // when you require() these assets
430 // in your code later on.
431 //
432 // in other words: require(...) = function(...) { ... return parser(...) }
433 //
434 // arguments:
435 //
436 // module - a webpack stats module
437 //
438 // options - various options
439 // (development mode flag,
440 // debug mode flag,
441 // assets base path (on the disk or on the network),
442 // regular_expressions{} for each asset type (by name),
443 // webpack stats json object)
444 //
445 // log
446 //
447 // returns: whatever (could be a filename, could be a JSON object, etc)
448 //
449 parser: function(module, options, log)
450 {
451 log.info('# module name', module.name)
452 log.info('# module source', module.source)
453 log.info('# assets base path', options.assets_base_path)
454 log.info('# regular expressions', options.regular_expressions)
455 log.info('# debug mode', options.debug)
456 log.info('# development mode', options.development)
457 log.debug('debugging')
458 log.warning('warning')
459 log.error('error')
460 }
461 },
462 ...
463 },
464 ...]
465}
466```
467
468## API
469
470#### Constructor
471
472(both Webpack plugin and server tools)
473
474Takes an object with options (see [Configuration](#configuration) section above)
475
476#### .development(true or false or undefined -> true)
477
478(both Webpack plugin and server tools)
479
480Is 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.
481
482#### .regular_expression(asset_type)
483
484(Webpack plugin)
485
486Returns the regular expression for this asset type (based on this asset type's `extension` (or `extensions`))
487
488#### Webpack_isomorphic_tools_plugin.url_loader_parser
489
490(Webpack plugin)
491
492A 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.
493
494#### .server(project_path, callback)
495
496(server tools)
497
498Initializes 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. The callback is called when `webpack-assets.json` has been found (it's needed for development because `webpack-dev-server` and your application server are usually run in parallel).
499
500#### .refresh()
501
502(server tools)
503
504Refreshes your assets info (re-reads `webpack-assets.json` from disk) and also flushes cache for all the previously `require()`d assets
505
506#### .assets()
507
508(server tools)
509
510Returns the contents of `webpack-assets.json` which is created by `webpack-isomorphic-tools` in your project base folder
511
512## Gotchas
513
514### .gitignore
515
516Make sure you add this to your `.gitignore`
517
518```
519# webpack-isomorphic-tools
520/webpack-stats.debug.json
521/webpack-assets.json
522```
523
524### Require() vs import
525
526In the image requiring examples above we could have wrote it like this:
527
528```
529import picture from './cat.jpg'
530```
531
532That 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:
533
534* 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
535* it's smart enough to resolve cyclic dependencies
536* 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
537* 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
538* it's simple, it's transparent, it's sane
539
540If 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?
541
542```javascript
543if (_development_)
544{
545 webpack_isomorhic_tools.refresh()
546}
547```
548
549It 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.
550
551I also read on the internets that ES6 supports dynamic module loading too and it looks something like this:
552
553```javascript
554System.import('some_module')
555.then(some_module =>
556{
557 // Use some_module
558})
559.catch(error =>
560{
561 ...
562})
563```
564
565I'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.
566
567Also 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 step in and port this "require hook" to ES6 to work with `import`s.
568
569## References
570
571Initially based on the code from [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) by Erik Rasmussen
572
573Also the same codebase (as in the project mentioned above) can be found in [isomorphic500](https://github.com/gpbl/isomorphic500) by Giampaolo Bellavite
574
575Also uses `require()` hooking techniques from [node-hook](https://github.com/bahmutov/node-hook) by Gleb Bahmutov
576
577## Contributing
578
579After cloning this repo, ensure dependencies are installed by running:
580
581```sh
582npm install
583```
584
585This module is written in ES6 and uses [Babel](http://babeljs.io/) for ES5
586transpilation. Widely consumable JavaScript can be produced by running:
587
588```sh
589npm run build
590```
591
592Once `npm run build` has run, you may `import` or `require()` directly from
593node.
594
595After developing, the full test suite can be evaluated by running:
596
597```sh
598npm test
599```
600
601While actively developing, one can use
602
603```sh
604npm run watch
605```
606
607in a terminal. This will watch the file system and run tests automatically
608whenever you save a js file.
609
610## License
611
612[MIT](LICENSE)
613[npm-image]: https://img.shields.io/npm/v/webpack-isomorphic-tools.svg
614[npm-url]: https://npmjs.org/package/webpack-isomorphic-tools
615[travis-image]: https://img.shields.io/travis/halt-hammerzeit/webpack-isomorphic-tools/master.svg
616[travis-url]: https://travis-ci.org/halt-hammerzeit/webpack-isomorphic-tools
617[downloads-image]: https://img.shields.io/npm/dm/webpack-isomorphic-tools.svg
618[downloads-url]: https://npmjs.org/package/webpack-isomorphic-tools
619[coveralls-image]: https://img.shields.io/coveralls/halt-hammerzeit/webpack-isomorphic-tools/master.svg
620[coveralls-url]: https://coveralls.io/r/halt-hammerzeit/webpack-isomorphic-tools?branch=master
621
622<!---
623[gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg
624[gratipay-url]: https://gratipay.com/dougwilson/
625-->
\No newline at end of file