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 | Is 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 | - [Configuration examples](#configuration-examples)
|
23 | - [What are webpack-assets.json?](#what-are-webpack-assetsjson)
|
24 | - [What are Webpack stats?](#what-are-webpack-stats)
|
25 | - [What's a "module"?](#whats-a-module)
|
26 | - [API](#api)
|
27 | - [Gotchas](#gotchas)
|
28 | - [References](#references)
|
29 | - [Contributing](#contributing)
|
30 |
|
31 | ## What it does and why is it needed?
|
32 |
|
33 | What 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.
|
34 |
|
35 | At 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).
|
36 |
|
37 | And 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.
|
38 |
|
39 | Then 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.
|
40 |
|
41 | So 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.
|
42 |
|
43 | Time 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.
|
44 |
|
45 | But 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.
|
46 |
|
47 | This 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.
|
48 |
|
49 | ## Getting down to business
|
50 |
|
51 | For example, consider images. Images are `require()`d in React components and then used like this:
|
52 |
|
53 | ```javascript
|
54 | // alternatively one can use import, but in this case hot reloading won't work
|
55 | // import image_path from '../image.png'
|
56 |
|
57 | // you just `src` your image inside your `render()` method
|
58 | class Photo extends React.Component
|
59 | {
|
60 | render()
|
61 | {
|
62 | // when Webpack url-loader finds this `require()` call
|
63 | // it will copy `image.png` to your build folder
|
64 | // and name it something like `9059f094ddb49c2b0fa6a254a6ebf2ad.png`,
|
65 | // because we are using the `[hash]` file naming feature of Webpack url-loader
|
66 | // which (feature) is required to make browser caching work correctly
|
67 | const image_path = require('../image.png')
|
68 |
|
69 | return <img src={image_path}/>
|
70 | }
|
71 | }
|
72 | ```
|
73 |
|
74 | It works on the client because Webpack intelligently replaces all the `require()` calls for you.
|
75 | But it wouldn't work on the server because Node.js only knows how to `require()` javascript modules. It would just throw a `SyntaxError`.
|
76 |
|
77 | To 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.
|
78 |
|
79 | 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` know this weird real file path? It's just a bit of magic.
|
80 |
|
81 | So, you get the idea now?
|
82 |
|
83 | Aside 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).
|
84 |
|
85 | 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 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).
|
86 |
|
87 | ## Installation
|
88 |
|
89 | `webpack-isomorphic-tools` are required both for development and production
|
90 |
|
91 | ```bash
|
92 | $ npm install webpack-isomorphic-tools --save
|
93 | ```
|
94 |
|
95 | ## Usage
|
96 |
|
97 | First you add `webpack_isomorphic_tools` plugin to your Webpack configuration.
|
98 |
|
99 | ### webpack.config.js
|
100 |
|
101 | ```javascript
|
102 | var Webpack_isomorphic_tools_plugin = require('webpack-isomorphic-tools/plugin')
|
103 |
|
104 | var webpack_isomorphic_tools_plugin =
|
105 | // webpack-isomorphic-tools settings reside in a separate .js file
|
106 | // (because they will be used in the web server code too).
|
107 | new Webpack_isomorphic_tools_plugin(require('./webpack-isomorphic-tools-configuration'))
|
108 | // also enter development mode since it's a development webpack configuration
|
109 | // (see below for explanation)
|
110 | .development()
|
111 |
|
112 | // usual Webpack configuration
|
113 | module.exports =
|
114 | {
|
115 | context: '(required) your project path here',
|
116 |
|
117 | output:
|
118 | {
|
119 | publicPath: '(required) web path for static files here'
|
120 | },
|
121 |
|
122 | module:
|
123 | {
|
124 | loaders:
|
125 | [
|
126 | ...,
|
127 | {
|
128 | test: webpack_isomorphic_tools_plugin.regular_expression('images'),
|
129 | loader: 'url-loader?limit=10240', // any image below or equal to 10K will be converted to inline base64 instead
|
130 | }
|
131 | ]
|
132 | },
|
133 |
|
134 | plugins:
|
135 | [
|
136 | ...,
|
137 |
|
138 | webpack_isomorphic_tools_plugin
|
139 | ]
|
140 |
|
141 | ...
|
142 | }
|
143 | ```
|
144 |
|
145 | What 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).
|
146 |
|
147 | For 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:
|
148 |
|
149 | ### webpack-isomorphic-tools-configuration.js
|
150 |
|
151 | ```javascript
|
152 | import Webpack_isomorphic_tools_plugin from 'webpack-isomorphic-tools/plugin'
|
153 |
|
154 | export default
|
155 | {
|
156 | assets:
|
157 | {
|
158 | images:
|
159 | {
|
160 | extensions: ['png', 'jpg', 'gif', 'ico', 'svg']
|
161 | }
|
162 | }
|
163 | }
|
164 | ```
|
165 |
|
166 | That'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)
|
167 |
|
168 | ### main.js
|
169 |
|
170 | ```javascript
|
171 | var Webpack_isomorphic_tools = require('webpack-isomorphic-tools')
|
172 |
|
173 | // this must be equal to your Webpack configuration "context" parameter
|
174 | var project_base_path = require('path').resolve(__dirname, '..')
|
175 |
|
176 | // this global variable will be used later in express middleware
|
177 | global.webpack_isomorphic_tools = new Webpack_isomorphic_tools(require('./webpack-isomorphic-tools-configuration'))
|
178 | // enter development mode if needed
|
179 | // (you may also prefer to use a Webpack DefinePlugin variable)
|
180 | .development(process.env.NODE_ENV === 'development')
|
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 | require('./server')
|
192 | })
|
193 | ```
|
194 |
|
195 | Then you, for example, create an express middleware to render your pages on the server
|
196 |
|
197 | ```javascript
|
198 | import React from 'react'
|
199 |
|
200 | // html page markup
|
201 | import Html from './html'
|
202 |
|
203 | // will be used in express_application.use(...)
|
204 | export function page_rendering_middleware(request, response)
|
205 | {
|
206 | // clear require() cache if in development mode
|
207 | // (makes asset hot reloading work)
|
208 | if (_development_)
|
209 | {
|
210 | webpack_isomorphic_tools.refresh()
|
211 | }
|
212 |
|
213 | // for react-router example of determining current page by URL take a look at this:
|
214 | // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
|
215 | const page_component = [determine your page component here using request.path]
|
216 |
|
217 | // for a Redux Flux store implementation you can see the same example:
|
218 | // https://github.com/halt-hammerzeit/webapp/blob/master/code/server/webpage%20rendering.js
|
219 | const flux_store = [initialize and populate your flux store depending on the page being shown]
|
220 |
|
221 | // render the page to string and send it to the browser as text/html
|
222 | response.send('<!doctype html>\n' +
|
223 | React.renderToString(<Html assets={webpack_isomorphic_tools.assets()} component={page_component} store={flux_store}/>))
|
224 | }
|
225 | ```
|
226 |
|
227 | And finally you use the `assets` inside the `Html` component's `render()` method
|
228 |
|
229 | ```javascript
|
230 | import React, {Component, PropTypes} from 'react'
|
231 | import serialize from 'serialize-javascript'
|
232 |
|
233 | export default class Html extends Component
|
234 | {
|
235 | static propTypes =
|
236 | {
|
237 | assets : PropTypes.object,
|
238 | component : PropTypes.object,
|
239 | store : PropTypes.object
|
240 | }
|
241 |
|
242 | // a sidenote for "advanced" users:
|
243 | // (you may skip this)
|
244 | //
|
245 | // this file is usually not included in your Webpack build
|
246 | // because this React component is only needed for server side React rendering.
|
247 | //
|
248 | // so, if this React component is not `require()`d from anywhere in your client code,
|
249 | // then Webpack won't ever get here
|
250 | // which means Webpack won't detect and parse any of the `require()` calls here,
|
251 | // which in turn means that if you `require()` any unique assets here
|
252 | // you should also `require()` those assets somewhere in your client code,
|
253 | // otherwise those assets won't be present in your Webpack bundle and won't be found.
|
254 | //
|
255 | render()
|
256 | {
|
257 | const { assets, component, store } = this.props
|
258 |
|
259 | // "import" will work here too
|
260 | // but if you want hot reloading to work while developing your project
|
261 | // then you need to use require()
|
262 | // because import will only be executed a single time
|
263 | // (when the application launches)
|
264 | // you can refer to the "Require() vs import" section for more explanation
|
265 | const picture = require('../assets/images/cat.jpg')
|
266 |
|
267 | // favicon
|
268 | const icon = require('../assets/images/icon/32x32.png')
|
269 |
|
270 | const html =
|
271 | (
|
272 | <html lang="en-us">
|
273 | <head>
|
274 | <meta charSet="utf-8"/>
|
275 | <title>xHamster</title>
|
276 |
|
277 | {/* favicon */}
|
278 | <link rel="shortcut icon" href={icon} />
|
279 |
|
280 | {/* styles (will be present only in production with webpack extract text plugin) */}
|
281 | {Object.keys(assets.styles).map((style, i) =>
|
282 | <link href={assets.styles[style]} key={i} media="screen, projection"
|
283 | rel="stylesheet" type="text/css"/>)}
|
284 |
|
285 | {/* resolves the initial style flash (flicker) on page load in development mode */}
|
286 | { Object.keys(assets.styles).is_empty() ? <style dangerouslySetInnerHTML={{__html: require('../assets/styles/main_style.css')}}/> : null }
|
287 | </head>
|
288 |
|
289 | <body>
|
290 | {/* image requiring demonstration */}
|
291 | <img src={picture}/>
|
292 |
|
293 | {/* rendered React page */}
|
294 | <div id="content" dangerouslySetInnerHTML={{__html: React.renderToString(component)}}/>
|
295 |
|
296 | {/* Flux store data will be reloaded into the store on the client */}
|
297 | <script dangerouslySetInnerHTML={{__html: `window._flux_store_data=${serialize(store.getState())};`}} />
|
298 |
|
299 | {/* javascripts */}
|
300 | {/* (usually one for each "entry" in webpack configuration) */}
|
301 | {/* (for more informations on "entries" see https://github.com/petehunt/webpack-howto/) */}
|
302 | {Object.keys(assets.javascript).map((script, i) =>
|
303 | <script src={assets.javascript[script]} key={i}/>
|
304 | )}
|
305 | </body>
|
306 | </html>
|
307 | )
|
308 |
|
309 | return html
|
310 | }
|
311 | }
|
312 | ```
|
313 |
|
314 | `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.
|
315 |
|
316 | ```javascript
|
317 | {
|
318 | "javascript":
|
319 | {
|
320 | "main": "/assets/main-d8c29e9b2a4623f696e8.js"
|
321 | },
|
322 |
|
323 | "styles":
|
324 | {
|
325 | "main": "/assets/main-d8c29e9b2a4623f696e8.css"
|
326 | },
|
327 |
|
328 | "assets":
|
329 | {
|
330 | "./assets/images/cat.jpg": "http://localhost:3001/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
|
331 |
|
332 | "./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="
|
333 | }
|
334 | }
|
335 | ```
|
336 |
|
337 | And that's it, now you can `require()` your assets "isomorphically" (both on client and server).
|
338 |
|
339 | ## A working example
|
340 |
|
341 | `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`).
|
342 |
|
343 | Also 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). There it is used to `require()` images and CSS stylesheet contents.
|
344 |
|
345 | Some source code guidance for the aforementioned project:
|
346 |
|
347 | * [webpack-isomorphic-tools configuration](https://github.com/halt-hammerzeit/webapp/blob/master/webpack/isomorphic.js)
|
348 | * [webpack-isomorphic-tools client initialization](https://github.com/halt-hammerzeit/webapp/blob/master/webpack/development%20server.js#L48)
|
349 | * [webpack-isomorphic-tools server initialization](https://github.com/halt-hammerzeit/webapp/blob/master/code/page-server/entry.js#L9-L17)
|
350 | * [webpage rendering express middleware](https://github.com/halt-hammerzeit/webapp/blob/master/code/page-server/webpage%20rendering.js)
|
351 | * [the Html file](https://github.com/halt-hammerzeit/webapp/blob/master/code/page-server/html.js)
|
352 |
|
353 | ## Configuration
|
354 |
|
355 | Available configuration parameters:
|
356 |
|
357 | ```javascript
|
358 | {
|
359 | // sets "development" mode flag.
|
360 | // see the API section below for method .development()
|
361 | // for more explanation about what "development" mode does
|
362 | // and when is it needed.
|
363 | //
|
364 | development: true, // is false by default
|
365 |
|
366 | // debug mode.
|
367 | // when set to true, lets you see debugging messages in the console.
|
368 | //
|
369 | debug: true, // is false by default
|
370 |
|
371 | // By default it creates 'webpack-assets.json' file at
|
372 | // webpack_configuration.context (which is your project folder).
|
373 | // You can change the assets file path as you wish
|
374 | // (therefore changing both folder and filename).
|
375 | //
|
376 | // (relative to webpack_configuration.context which is your project folder)
|
377 | //
|
378 | webpack_assets_file_path: 'webpack-assets.json',
|
379 |
|
380 | // By default, when running in debug mode, it creates 'webpack-stats.json' file at
|
381 | // webpack_configuration.context (which is your project folder).
|
382 | // You can change the stats file path as you wish
|
383 | // (therefore changing both folder and filename).
|
384 | //
|
385 | // (relative to webpack_configuration.context which is your project folder)
|
386 | //
|
387 | webpack_stats_file_path: 'webpack-stats.json',
|
388 |
|
389 | // Makes `webpack-isomorphic-tools` aware of Webpack aliasing feature
|
390 | // (if you use it)
|
391 | // https://webpack.github.io/docs/resolving.html#aliasing
|
392 | //
|
393 | // The `alias` parameter corresponds to `resolve.alias`
|
394 | // in your Webpack configuration.
|
395 | //
|
396 | alias: webpack_configuration.resolve.alias, // is {} by default
|
397 |
|
398 | // here you can define all your asset types
|
399 | //
|
400 | assets:
|
401 | {
|
402 | // asset_type will appear in:
|
403 | // * webpack-assets.json
|
404 | // * .assets() method call result
|
405 | // * .regular_expression(asset_type) method call
|
406 | //
|
407 | asset_type:
|
408 | {
|
409 | // which file types belong to this asset type
|
410 | //
|
411 | extension: 'png', // or extensions: ['png', 'jpg', ...],
|
412 |
|
413 | // here you are able to add some file paths
|
414 | // for which the require() call will bypass webpack-isomorphic-tools
|
415 | // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
|
416 | // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
|
417 | // and functions(path) { return true / false })
|
418 | //
|
419 | exclude: [],
|
420 |
|
421 | // here you can specify manually the paths
|
422 | // for which the require() call will be processed by webpack-isomorphic-tools
|
423 | // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
|
424 | // (also supports regular expressions, e.g. /^\.\/node_modules\/*/,
|
425 | // and functions(path) { return true / false }).
|
426 | // in case of `include` only included paths will be processed by webpack-isomorphic-tools.
|
427 | //
|
428 | include: [],
|
429 |
|
430 | // [optional]
|
431 | //
|
432 | // determines which webpack stats modules
|
433 | // belong to this asset type
|
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 | // regular_expression - a regular expression
|
443 | // composed of this asset type's extensions
|
444 | // e.g. /\.scss$/, /\.(ico|gif)$/
|
445 | //
|
446 | // options - various options
|
447 | // (development mode flag,
|
448 | // debug mode flag,
|
449 | // assets base url,
|
450 | // project base folder,
|
451 | // regular_expressions{} for each asset type (by name),
|
452 | // webpack stats json object)
|
453 | //
|
454 | // log
|
455 | //
|
456 | // returns: a Boolean
|
457 | //
|
458 | // by default is: "return regular_expression.test(module.name)"
|
459 | //
|
460 | // premade utility filters:
|
461 | //
|
462 | // Webpack_isomorphic_tools_plugin.style_loader_filter
|
463 | // (for use with style-loader + css-loader)
|
464 | //
|
465 | filter: function(module, regular_expression, options, log)
|
466 | {
|
467 | return regular_expression.test(module.name)
|
468 | },
|
469 |
|
470 | // [optional]
|
471 | //
|
472 | // transforms a webpack stats module name
|
473 | // to an asset path (usually is the same thing)
|
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 | // options - various options
|
483 | // (development mode flag,
|
484 | // debug mode flag,
|
485 | // assets base url,
|
486 | // project base folder,
|
487 | // regular_expressions{} for each asset type (by name),
|
488 | // webpack stats json object)
|
489 | //
|
490 | // log
|
491 | //
|
492 | // returns: a String
|
493 | //
|
494 | // by default is: "return module.name"
|
495 | //
|
496 | // premade utility path extractors:
|
497 | //
|
498 | // Webpack_isomorphic_tools_plugin.style_loader_path_extractor
|
499 | // (for use with style-loader + css-loader)
|
500 | //
|
501 | path: function(module, options, log)
|
502 | {
|
503 | return module.name
|
504 | },
|
505 |
|
506 | // [optional]
|
507 | //
|
508 | // parses a webpack stats module object
|
509 | // for an asset of this asset type
|
510 | // to whatever you need to get
|
511 | // when you require() these assets
|
512 | // in your code later on.
|
513 | //
|
514 | // this is what you'll see as the asset value in webpack-assets.json:
|
515 | // { ..., path(): compile(parser()), ... }
|
516 | //
|
517 | // can be a CommonJS module source code:
|
518 | // module.exports = ...what you export here is
|
519 | // what you get when you require() this asset...
|
520 | //
|
521 | // if the returned value is not a CommonJS module source code
|
522 | // (it may be a string, a JSON object, whatever)
|
523 | // then it will be transformed into a CommonJS module source code.
|
524 | //
|
525 | // in other words:
|
526 | //
|
527 | // // making of webpack-assets.json
|
528 | // for each type of configuration.assets
|
529 | // modules.filter(type.filter).for_each (module)
|
530 | // assets[type.path()] = compile(type.parser(module))
|
531 | //
|
532 | // // requiring assets in your code
|
533 | // require(path) = (path) => return assets[path]
|
534 | //
|
535 | // arguments:
|
536 | //
|
537 | // module - a webpack stats module
|
538 | //
|
539 | // (to understand what a "module" is
|
540 | // read the "What's a "module"?" section of this readme)
|
541 | //
|
542 | // options - various options
|
543 | // (development mode flag,
|
544 | // debug mode flag,
|
545 | // assets base url,
|
546 | // project base folder,
|
547 | // regular_expressions{} for each asset type (by name),
|
548 | // webpack stats json object)
|
549 | //
|
550 | // log
|
551 | //
|
552 | // returns: whatever (could be a filename, could be a JSON object, etc)
|
553 | //
|
554 | // by default is: "return module.source"
|
555 | //
|
556 | // premade utility parsers:
|
557 | //
|
558 | // Webpack_isomorphic_tools_plugin.url_loader_parser
|
559 | // (for use with url-loader or file-loader)
|
560 | // require() will return file URL
|
561 | // (is equal to the default parser, i.e. no parser)
|
562 | //
|
563 | // Webpack_isomorphic_tools_plugin.css_loader_parser
|
564 | // (for use with css-loader when not using "modules" feature)
|
565 | // require() will return CSS style text
|
566 | //
|
567 | // Webpack_isomorphic_tools_plugin.css_modules_loader_parser
|
568 | // (for use with css-loader when using "modules" feature)
|
569 | // require() will return a JSON object map of style class names
|
570 | // which will also have a `_style` key containing CSS style text
|
571 | //
|
572 | parser: function(module, options, log)
|
573 | {
|
574 | log.info('# module name', module.name)
|
575 | log.info('# module source', module.source)
|
576 | log.info('# project path', options.project_path)
|
577 | log.info('# assets base url', options.assets_base_url)
|
578 | log.info('# regular expressions', options.regular_expressions)
|
579 | log.info('# debug mode', options.debug)
|
580 | log.info('# development mode', options.development)
|
581 | log.debug('debugging')
|
582 | log.warning('warning')
|
583 | log.error('error')
|
584 | }
|
585 | },
|
586 | ...
|
587 | },
|
588 | ...]
|
589 | }
|
590 | ```
|
591 |
|
592 | ## Configuration examples
|
593 |
|
594 | #### url-loader / file-loader (images, fonts, etc)
|
595 |
|
596 | ```javascript
|
597 | {
|
598 | assets:
|
599 | {
|
600 | images:
|
601 | {
|
602 | extensions: ['png', 'jpg']
|
603 | },
|
604 |
|
605 | fonts:
|
606 | {
|
607 | extensions: ['woff', 'ttf']
|
608 | }
|
609 | }
|
610 | }
|
611 | ```
|
612 |
|
613 | #### style-loader (standard CSS stylesheets)
|
614 |
|
615 | ```javascript
|
616 | {
|
617 | assets:
|
618 | {
|
619 | styles:
|
620 | {
|
621 | extensions: ['less', 'scss'],
|
622 |
|
623 | // which `module`s to parse CSS from:
|
624 | filter: function(module, regular_expression, options, log)
|
625 | {
|
626 | if (options.development)
|
627 | {
|
628 | // In development mode there's Webpack "style-loader",
|
629 | // which outputs `module`s with `module.name == asset_path`,
|
630 | // but those `module`s do not contain CSS text.
|
631 | //
|
632 | // The `module`s containing CSS text are
|
633 | // the ones loaded with Webpack "css-loader".
|
634 | // (which have kinda weird `module.name`)
|
635 | //
|
636 | // Therefore using a non-default `filter` function here.
|
637 | //
|
638 | return webpack_isomorphic_tools_plugin.style_loader_filter(module, regular_expression, options, log)
|
639 | }
|
640 |
|
641 | // In production mode there will be no CSS text at all
|
642 | // because all styles will be extracted by Webpack Extract Text Plugin
|
643 | // into a .css file (as per Webpack configuration).
|
644 | //
|
645 | // Therefore in production mode `filter` function always returns non-`true`.
|
646 | },
|
647 |
|
648 | // How to correctly transform kinda weird `module.name`
|
649 | // of the `module` created by Webpack "css-loader"
|
650 | // into the correct asset path:
|
651 | path: webpack_isomorphic_tools_plugin.style_loader_path_extractor,
|
652 |
|
653 | // How to extract these Webpack `module`s' javascript `source` code.
|
654 | // basically takes `module.source` and modifies `module.exports` a little.
|
655 | parser: webpack_isomorphic_tools_plugin.css_loader_parser
|
656 | }
|
657 | }
|
658 | }
|
659 | ```
|
660 |
|
661 | #### style-loader (CSS stylesheets with "CSS modules" feature)
|
662 |
|
663 | ```javascript
|
664 | {
|
665 | assets:
|
666 | {
|
667 | style_modules:
|
668 | {
|
669 | extensions: ['less', 'scss'],
|
670 |
|
671 | // which `module`s to parse CSS style class name maps from:
|
672 | filter: function(module, regex, options, log)
|
673 | {
|
674 | if (options.development)
|
675 | {
|
676 | // In development mode there's Webpack "style-loader",
|
677 | // which outputs `module`s with `module.name == asset_path`,
|
678 | // but those `module`s do not contain CSS text.
|
679 | //
|
680 | // The `module`s containing CSS text are
|
681 | // the ones loaded with Webpack "css-loader".
|
682 | // (which have kinda weird `module.name`)
|
683 | //
|
684 | // Therefore using a non-default `filter` function here.
|
685 | //
|
686 | return webpack_isomorphic_tools_plugin.style_loader_filter(module, regex, options, log)
|
687 | }
|
688 |
|
689 | // In production mode there's no Webpack "style-loader",
|
690 | // so `module.name`s of the `module`s created by Webpack "css-loader"
|
691 | // (those which contain CSS text)
|
692 | // will be simply equal to the correct asset path
|
693 | return regex.test(module.name)
|
694 | },
|
695 |
|
696 | // How to correctly transform `module.name`s
|
697 | // into correct asset paths
|
698 | path: function(module, options, log)
|
699 | {
|
700 | if (options.development)
|
701 | {
|
702 | // In development mode there's Webpack "style-loader",
|
703 | // so `module.name`s of the `module`s created by Webpack "css-loader"
|
704 | // (those picked by the `filter` function above)
|
705 | // will be kinda weird, and this path extractor extracts
|
706 | // the correct asset paths from these kinda weird `module.name`s
|
707 | return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
|
708 | }
|
709 |
|
710 | // in production mode there's no Webpack "style-loader",
|
711 | // so `module.name`s will be equal to correct asset paths
|
712 | return module.name
|
713 | },
|
714 |
|
715 | // How to extract these Webpack `module`s' javascript `source` code.
|
716 | // Basically takes `module.source` and modifies its `module.exports` a little.
|
717 | parser: function(module, options, log)
|
718 | {
|
719 | if (options.development)
|
720 | {
|
721 | // In development mode it adds an extra `_style` entry
|
722 | // to the CSS style class name map, containing the CSS text
|
723 | return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
|
724 | }
|
725 |
|
726 | // In production mode there's Webpack Extract Text Plugin
|
727 | // which extracts all CSS text away, so there's
|
728 | // only CSS style class name map left.
|
729 | return module.source
|
730 | }
|
731 | }
|
732 | }
|
733 | }
|
734 | ```
|
735 |
|
736 | ## What are webpack-assets.json?
|
737 |
|
738 | This 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).
|
739 |
|
740 | Pseudocode:
|
741 |
|
742 | ```
|
743 | // requiring assets in your code
|
744 | require(path) = (path) => return assets[path]
|
745 | ```
|
746 |
|
747 | Therefore, if you get such a message in the console:
|
748 |
|
749 | ```
|
750 | [webpack-isomorphic-tools] [error] asset not found: ./~/react-toolbox/lib/font_icon/style.scss
|
751 | ```
|
752 |
|
753 | Then 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`?
|
754 |
|
755 | Pseudocode:
|
756 |
|
757 | ```
|
758 | // making of webpack-assets.json inside the Webpack plugin
|
759 | for each type of configuration.assets
|
760 | modules.filter(type.filter).for_each (module)
|
761 | assets[type.path()] = compile(type.parser(module))
|
762 | ```
|
763 |
|
764 | Therefore, 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?
|
765 |
|
766 | ## What are Webpack stats?
|
767 |
|
768 | [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.
|
769 |
|
770 | ## What's a "module"?
|
771 |
|
772 | **This is an advanced topic on Webpack internals**
|
773 |
|
774 | A "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.
|
775 |
|
776 | For 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.
|
777 |
|
778 | ```javascript
|
779 | {
|
780 | ...
|
781 |
|
782 | "modules": [
|
783 | {
|
784 | "id": 0,
|
785 | ...
|
786 | },
|
787 | {
|
788 | "id": 1,
|
789 | "name": "./~/fbjs/lib/invariant.js",
|
790 | "source": "module.exports = global[\"undefined\"] = require(\"-!G:\\\\work\\\\isomorphic-demo\\\\node_modules\\\\fbjs\\\\lib\\\\invariant.js\");",
|
791 |
|
792 | // the rest of the fields are irrelevant
|
793 |
|
794 | "chunks": [
|
795 | 0
|
796 | ],
|
797 | "identifier": "G:\\work\\isomorphic-demo\\node_modules\\expose-loader\\index.js?undefined!G:\\work\\isomorphic-demo\\node_modules\\fbjs\\lib\\invariant.js",
|
798 | "index": 27,
|
799 | "index2": 7,
|
800 | "size": 117,
|
801 | "cacheable": true,
|
802 | "built": true,
|
803 | "optional": false,
|
804 | "prefetched": false,
|
805 | "assets": [],
|
806 | "issuer": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
|
807 | "failed": false,
|
808 | "errors": 0,
|
809 | "warnings": 0,
|
810 |
|
811 | "reasons": [
|
812 | {
|
813 | "moduleId": 418,
|
814 | "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
|
815 | "module": "./~/react/lib/ReactInstanceHandles.js",
|
816 | "moduleName": "./~/react/lib/ReactInstanceHandles.js",
|
817 | "type": "cjs require",
|
818 | "userRequest": "fbjs/lib/invariant",
|
819 | "loc": "17:16-45"
|
820 | },
|
821 | ...
|
822 | {
|
823 | "moduleId": 483,
|
824 | "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\traverseAllChildren.js",
|
825 | "module": "./~/react/lib/traverseAllChildren.js",
|
826 | "moduleName": "./~/react/lib/traverseAllChildren.js",
|
827 | "type": "cjs require",
|
828 | "userRequest": "fbjs/lib/invariant",
|
829 | "loc": "19:16-45"
|
830 | }
|
831 | ]
|
832 | },
|
833 |
|
834 | ...
|
835 | ]
|
836 | }
|
837 | ```
|
838 |
|
839 | Judging 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
|
840 |
|
841 | ```javascript
|
842 | var invariant = require('fbjs/lib/invariant')
|
843 | ```
|
844 |
|
845 | Every 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":
|
846 |
|
847 | ```javascript
|
848 | // Webpack configuration
|
849 | module.exports =
|
850 | {
|
851 | ...
|
852 |
|
853 | module:
|
854 | {
|
855 | loaders:
|
856 | [
|
857 | ...
|
858 |
|
859 | {
|
860 | test : /\.jpg$/,
|
861 | loader : 'url-loader'
|
862 | }
|
863 | ]
|
864 | },
|
865 |
|
866 | ...
|
867 | }
|
868 | ```
|
869 |
|
870 | This 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).
|
871 |
|
872 | The `module` object for an image would look like this
|
873 |
|
874 | ```javascript
|
875 | {
|
876 | ...
|
877 | "name": "./assets/images/husky.jpg",
|
878 | "source": "module.exports = __webpack_public_path__ + \"9059f094ddb49c2b0fa6a254a6ebf2ad.jpg\""
|
879 | }
|
880 | ```
|
881 |
|
882 | Therefore, in this simple case, in `webpack-isomorphic-tools` configuration file we create an "images" asset type with extension "jpg" and these parameters:
|
883 |
|
884 | * the `filter` function would be `module => module.name.ends_with('.jpg')` (and it's the default `filter` if no `filter` is specified)
|
885 | * the `path` parser function would be `module => module.name` (and it's the default `path` parser if no `path` parser is specified)
|
886 | * the `parser` function would be `module => module.source` (and it's the default `parser` if no `parser` is specified)
|
887 |
|
888 | When 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`:
|
889 |
|
890 | ```
|
891 | {
|
892 | ...
|
893 | assets:
|
894 | {
|
895 | "./assets/images/husky.jpg": "/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
|
896 | ...
|
897 | }
|
898 | }
|
899 | ```
|
900 |
|
901 | And so when you later `require("./assets/images/husky.jpg")` in your server code it will return `"/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg"` and that's it.
|
902 |
|
903 | ## API
|
904 |
|
905 | #### Constructor
|
906 |
|
907 | (both Webpack plugin and server tools)
|
908 |
|
909 | Takes an object with options (see [Configuration](#configuration) section above)
|
910 |
|
911 | #### .development(true or false or undefined -> true)
|
912 |
|
913 | (both Webpack plugin instance and server tools instance)
|
914 |
|
915 | Is 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.
|
916 |
|
917 | #### .regular_expression(asset_type)
|
918 |
|
919 | (Webpack plugin instance)
|
920 |
|
921 | Returns the regular expression for this asset type (based on this asset type's `extension` (or `extensions`))
|
922 |
|
923 | #### Webpack_isomorphic_tools_plugin.url_loader_parser
|
924 |
|
925 | (Webpack plugin)
|
926 |
|
927 | A 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.
|
928 |
|
929 | #### .server(project_path, [callback])
|
930 |
|
931 | (server tools instance)
|
932 |
|
933 | Initializes 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.
|
934 |
|
935 | When 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`.
|
936 |
|
937 | To 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.
|
938 |
|
939 | #### .refresh()
|
940 |
|
941 | (server tools instance)
|
942 |
|
943 | Refreshes your assets info (re-reads `webpack-assets.json` from disk) and also flushes cache for all the previously `require()`d assets
|
944 |
|
945 | #### .assets()
|
946 |
|
947 | (server tools instance)
|
948 |
|
949 | Returns the contents of `webpack-assets.json` which is created by `webpack-isomorphic-tools` in your project base folder
|
950 |
|
951 | ## Configuration example
|
952 |
|
953 | ### .gitignore
|
954 |
|
955 | Make sure you add this to your `.gitignore`
|
956 |
|
957 | ```
|
958 | # webpack-isomorphic-tools
|
959 | /webpack-stats.json
|
960 | /webpack-assets.json
|
961 | ```
|
962 |
|
963 | ### Require() vs import
|
964 |
|
965 | In the image requiring examples above we could have wrote it like this:
|
966 |
|
967 | ```
|
968 | import picture from './cat.jpg'
|
969 | ```
|
970 |
|
971 | That 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:
|
972 |
|
973 | * 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
|
974 | * it's smart enough to resolve cyclic dependencies
|
975 | * 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
|
976 | * 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
|
977 | * it's simple, it's transparent, it's sane
|
978 |
|
979 | If 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?
|
980 |
|
981 | ```javascript
|
982 | if (_development_)
|
983 | {
|
984 | webpack_isomorhic_tools.refresh()
|
985 | }
|
986 | ```
|
987 |
|
988 | It 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.
|
989 |
|
990 | I also read on the internets that ES6 supports dynamic module loading too and it looks something like this:
|
991 |
|
992 | ```javascript
|
993 | System.import('some_module')
|
994 | .then(some_module =>
|
995 | {
|
996 | // Use some_module
|
997 | })
|
998 | .catch(error =>
|
999 | {
|
1000 | ...
|
1001 | })
|
1002 | ```
|
1003 |
|
1004 | I'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.
|
1005 |
|
1006 | Also 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.
|
1007 |
|
1008 | ### Cannot find module
|
1009 |
|
1010 | If 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 when run on server). If encountered during Webpack build, this error means that the `require()`d path is absent from `webpack-stats.json`.
|
1011 |
|
1012 | ### SyntaxError: Unexpected token ILLEGAL
|
1013 |
|
1014 | This probably means that in some asset module source there's a `require()` call to some file extension that isn't specified in
|
1015 |
|
1016 | ## References
|
1017 |
|
1018 | Initially based on the code from [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example) by Erik Rasmussen
|
1019 |
|
1020 | Also the same codebase (as in the project mentioned above) can be found in [isomorphic500](https://github.com/gpbl/isomorphic500) by Giampaolo Bellavite
|
1021 |
|
1022 | Also uses `require()` hooking techniques from [node-hook](https://github.com/bahmutov/node-hook) by Gleb Bahmutov
|
1023 |
|
1024 | ## Contributing
|
1025 |
|
1026 | After cloning this repo, ensure dependencies are installed by running:
|
1027 |
|
1028 | ```sh
|
1029 | npm install
|
1030 | ```
|
1031 |
|
1032 | This module is written in ES6 and uses [Babel](http://babeljs.io/) for ES5
|
1033 | transpilation. Widely consumable JavaScript can be produced by running:
|
1034 |
|
1035 | ```sh
|
1036 | npm run build
|
1037 | ```
|
1038 |
|
1039 | Once `npm run build` has run, you may `import` or `require()` directly from
|
1040 | node.
|
1041 |
|
1042 | After developing, the full test suite can be evaluated by running:
|
1043 |
|
1044 | ```sh
|
1045 | npm test
|
1046 | ```
|
1047 |
|
1048 | While actively developing, one can use (personally I don't use it)
|
1049 |
|
1050 | ```sh
|
1051 | npm run watch
|
1052 | ```
|
1053 |
|
1054 | in a terminal. This will watch the file system and run tests automatically
|
1055 | whenever you save a js file.
|
1056 |
|
1057 | When you're ready to test your new functionality on a real project, you can run
|
1058 |
|
1059 | ```sh
|
1060 | npm pack
|
1061 | ```
|
1062 |
|
1063 | It will `build`, `test` and then create a `.tgz` archive which you can then install in your project folder
|
1064 |
|
1065 | ```sh
|
1066 | npm install [module name with version].tar.gz
|
1067 | ```
|
1068 |
|
1069 | ## To do
|
1070 |
|
1071 | * Proper testing for `log` (output to a variable rather than `console`)
|
1072 | * Proper testing for `notify_stats` (output to a `log` variable)
|
1073 | * Proper testing for parsers (using `eval()` CommonJS module compilation)
|
1074 | * Proper testing for `require('./node_modules/whatever.jpg')` test case
|
1075 |
|
1076 | ## License
|
1077 |
|
1078 | [MIT](LICENSE)
|
1079 | [npm-image]: https://img.shields.io/npm/v/webpack-isomorphic-tools.svg
|
1080 | [npm-url]: https://npmjs.org/package/webpack-isomorphic-tools
|
1081 | [travis-image]: https://img.shields.io/travis/halt-hammerzeit/webpack-isomorphic-tools/master.svg
|
1082 | [travis-url]: https://travis-ci.org/halt-hammerzeit/webpack-isomorphic-tools
|
1083 | [downloads-image]: https://img.shields.io/npm/dm/webpack-isomorphic-tools.svg
|
1084 | [downloads-url]: https://npmjs.org/package/webpack-isomorphic-tools
|
1085 | [coveralls-image]: https://img.shields.io/coveralls/halt-hammerzeit/webpack-isomorphic-tools/master.svg
|
1086 | [coveralls-url]: https://coveralls.io/r/halt-hammerzeit/webpack-isomorphic-tools?branch=master
|
1087 |
|
1088 |
|
1089 | [gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg
|
1090 | [gratipay-url]: https://gratipay.com/dougwilson/
|
1091 | --> |
\ | No newline at end of file |