README.md 12 KB
Newer Older
1
# @hypha/web-compiler
Yoshua Wuyts's avatar
Yoshua Wuyts committed
2

3
@hypha/web-compiler v1.0.0 was forked from Bankai v9.15.0.
Yoshua Wuyts's avatar
Yoshua Wuyts committed
4

5
[Bankai](https://github.com/choojs/bankai) is an excellent “friendly web compiler” by [Yoshua Wuyts](https://github.com/yoshuawuyts), [Renée Kooi](https://github.com/goto-bus-stop), and [other contributors](https://github.com/choojs/bankai/graphs/contributors).
Yoshua Wuyts's avatar
Yoshua Wuyts committed
6

7
8
9
10
11
12
13
14
15
If you want a general purpose web compiler, please use Bankai and [back their project](https://opencollective.com/choo).

@hypha/web-compiler is a simplified version of Bankai, tuned for the needs of the Hypha project that uses [https-server](https://source.ind.ie/hypha/tools/https-server) for seamless installation and use of locally-trusted certificates during development (and, soon, seamless Let’s Encrypt certificate provisioning and use in production).

## Installation

```sh
npm i @hypha/web-compiler
```
Yoshua Wuyts's avatar
.  
Yoshua Wuyts committed
16
17

## Usage
18

19
20
21
22
23
24
25
The primary use case of web-compiler is programmatically with Hypha.

In Hypha, web-compiler is used:

1. As a live build and reload tool on development.
2. As a build and optimisation tool on production.

26
There is a command-line binary but it is not used in Hypha except for its _inspect_ command:
27
28
29
30
31
32
33
34
35
36
37
38

```sh
web-compiler inspect
```

## Example

@hypha/web-compiler is used by hooking it up to an HTTPS server.

The following example demonstrates how you can use @hypha/web-compiler in both
development and production and alongside regular HTTPS and WebSocket routes (the
latter demonstrated using the [Express](http://expressjs.com/) framework):
39

40
41
42
43
44
45
46
```js
const httpsServer = require('@ind.ie/https-server')

const express = require('express')
const expressWebSocket = require('express-ws')

const path = require('path')
47

48
49
50
51
52
53
54
55
56
// Catch any uncaught errors.
process.on('uncaughtException', function (error) {
  console.log('Uncaught exception:', error)
})

// Create the Express app, the HTTPS server, and add WebSocket support.
const app = express()
const server = httpsServer.createServer({}, app)
expressWebSocket(app, server, { perMessageDeflate: false })
57

58
59
60
//
// Websocket routes go here.
//
61

62
63
64
65
66
67
app.ws('/echo', (webSocket, request) => {
  webSocket.on('message', message => {
    console.log('Got web socket request to echo ', message)
    webSocket.send(message)
  })
})
68

69
70
71
//
// Regular HTTPS routes go here.
//
Yoshua Wuyts's avatar
Yoshua Wuyts committed
72

73
74
75
76
app.get('/hello', (request, response) => {
  response.writeHeader(200, {'Content-Type': 'text/html'})
  response.end('<!doctype html><html lang=\'en\'><head><meta charset=\'utf-8\'/><title>Hello</title><style>body{background-color: "white";}</head><body>Hello, I am an HTTPS GET route!</body></html>')
})
Yoshua Wuyts's avatar
Yoshua Wuyts committed
77

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//
// Set up @hypha/web-compiler.
//
// In development, we use it as middleware to enable live compilation and live reload.
// In production, use build a static distribution and serve it with express.static.
//

// client/index.js is the entry-point of your client-side JavaScript (e.g., Choo app).
const entryPoint = path.join(__dirname, 'client/index.js')

if (process.env.NODE_ENV === 'production') {
  // Build the static distribution and serve it in production.
  const build = require('@hypha/web-compiler/lib/cmd-build')
  build(entryPoint, null, {base: 'https://localhost'})
  app.use(express.static('dist'))
} else {
  // Set up development mode with live compilation and reload.
  const webCompilerMiddleware = require('@hypha/web-compiler/http')(entryPoint)
  app.use(webCompilerMiddleware)
}
Yoshua Wuyts's avatar
Yoshua Wuyts committed
98

99
100
101
102
103
// Handle server errors.
app.use(function(error, request, response, next) {
  console.log('Error', error)
  res.send(500)
})
Yoshua Wuyts's avatar
Yoshua Wuyts committed
104

105
106
107
108
// Start the server.
server.listen(443, () => {
  console.log('Server running on port 443.\n')
})
109
110
```

111
112
113
114

## Optimisations

The following optimisations are applied during a build:
Yoshua Wuyts's avatar
Yoshua Wuyts committed
115
116

### JavaScript
117
- __[nanohtml][]:__ Optimize `choo` HTML code so it runs significantly faster in the
Yoshua Wuyts's avatar
Yoshua Wuyts committed
118
  browser.
119
120
- __[glslify][]:__ Adds a module system to GLSL shaders.
- __[brfs][]:__ Statically inline calls to `fs.readFile()`. Useful to ship assets
Yoshua Wuyts's avatar
Yoshua Wuyts committed
121
  in the browser.
122
- __[envify][]:__ Allow environment variables to be used in the bundle. Especially
123
  useful in combination with minification, which removes unused code paths.
124
- __[split-require][]:__ Lazy load parts of your application using the
Renée Kooi's avatar
Renée Kooi committed
125
  [`require('split-require')`][split-require] function.
126
- __[babelify][]:__ Bring the latest browser features to _all_ browsers. See
Yoshua Wuyts's avatar
Yoshua Wuyts committed
127
  [our babel section](#babel) for more details.
Yoshua Wuyts's avatar
Yoshua Wuyts committed
128

129
It also uses [tinyify][], which adds the following optimisations:
130

131
- __[browser-pack-flat][]:__ Remove function wrappers from the bundle, making
132
  the result faster to run and easier to minify.
133
- __[bundle-collapser][]:__ Remove all pathnames from inside the bundle, and
134
135
  replace them with IDs. This not only makes bundles smaller, it prevents
  details from your local dev setup leaking.
136
- __[common-shakeify][]:__ Remove unused JavaScript code from the bundle. Best
137
  known as _dead code elimination_ or _tree shaking_.
138
- __[unassertify][]:__ Remove all `require('assert')` statements from the code.
139
  Only applied for production builds.
140
- __[uglifyify][]:__ Minify the bundle.
141

Yoshua Wuyts's avatar
Yoshua Wuyts committed
142
### CSS
143
- __[sheetify][]:__ extract all inline CSS from JavaScript, and include it in
Yoshua Wuyts's avatar
Yoshua Wuyts committed
144
  `bundle.js`.
145
146
- __[purifyCSS][purify-css]:__ removes unused CSS from the project.
- __[cleanCSS][clean-css]:__ minify the bundle.
Yoshua Wuyts's avatar
Yoshua Wuyts committed
147
148

### HTML
149
- __[inline-critical-css][]:__ extract all critical CSS for a page into the
Yoshua Wuyts's avatar
Yoshua Wuyts committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
  `<head>` of the document. This means that every page will be able to render
  after the first roundtrip, which makes for super snappy pages.
- __async load scripts:__ loads scripts in the background using the
  [`defer`](https://devdocs.io/html/attributes#defer-attribute) attribute.
- __async load styles:__ loads styles in the background using the
  [`preload`](https://devdocs.io/html/attributes#preload-attribute) attribute.
- __async load styles:__ preloads fonts in the background using the
  [`preload`](https://devdocs.io/html/attributes#preload-attribute) attribute.
- __server render:__ server renders Choo applications. We're welcome to
  supporting other frameworks too. PRs welcome!
- __manifest:__ includes a link to `manifest.json` so the application can be
  installed on mobile.
- __viewport:__ defines the right viewport dimensions to make applications
  accessible for everyone.
- __theme color:__ sets the theme color defined in `manifest.json` so the
  navigator bar on mobile is styled on brand.
- __title:__ sets the right title on a page. Either extracts it from the
  application (choo only, for now) or uses whatever the title is in
  `manifest.json`.
- __live reload:__ during development, we inject a live reload script.


172
### Custom HTML
173
By default, @hypha/web-compiler starts with an empty HTML document, injecting the tags
174
mentioned [above](#html). You can also create a custom template as `index.html`,
175
and @hypha/web-compiler will inject tags into it instead.
176

177
If you export your Choo app instance after doing `.mount()`, @hypha/web-compiler respects the
178
179
mount location during server side rendering:

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
```js
// app.js
...
module.exports = app.mount('#app')
```

```html
<!-- index.html -->
...
<body>
  <div id="app"></div>
  <div id="footer">© 2018</div>
</body>
...
```

196
### Service Workers
197
198
199

@hypha/web-compiler comes with support for service workers. You can place a service worker
entry point in a file called `sw.js` or `service-worker.js`. @hypha/web-compiler will output
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
a browserify bundle by the same name.

You can easily register service workers using
[choo-service-worker](https://github.com/choojs/choo-service-worker):
```js
app.use(require('choo-service-worker')())
```

choo-service-worker defaults to `/sw.js` for the service worker file name. If
you named your service worker `service-worker.js` instead, do:
```js
app.use(require('choo-service-worker')('/service-worker.js'))
```

Service workers have access to some environment variables:
 * __process.env.STYLE_LIST:__ An array of URLs to stylesheet files.
 * __process.env.SCRIPT_LIST:__ An array of URLs to script files.
 * __process.env.ASSET_LIST:__ An array of URLs to assets.
 * __process.env.DOCUMENT_LIST:__ An array of URLs to server-rendered routes.
 * __process.env.MANIFEST_LIST:__ An array containing the URL to the manifest
   file.
 * __process.env.FILE_LIST:__ An array of URLs to assets and routes. This can
   be used to add all your app's files to a service worker cache.

Yoshua Wuyts's avatar
Yoshua Wuyts committed
224
225
226
227
## Babel

[Babel](https://babeljs.io/) is a plugin-based JavaScript compiler. It takes
JavaScript in, and outputs JavaScript based for the platforms you've decided to
228
target. In @hypha/web-compiler we target the last 2 versions of FireFox, Chrome and Edge,
Yoshua Wuyts's avatar
Yoshua Wuyts committed
229
230
and every other browser that's used by more than 1% of people on earth. This
includes IE11. And if you have different opinions on which browsers to use,
231
@hypha/web-compiler respects `.babelrc` and [`.browserslistrc`](https://github.com/ai/browserslist) files.
Yoshua Wuyts's avatar
Yoshua Wuyts committed
232
233

Some newer JavaScript features require loading an extra library; `async/await`
Arve Knudsen's avatar
Arve Knudsen committed
234
being the clearest example. To enable such features, the `babel-polyfill`
Yoshua Wuyts's avatar
Yoshua Wuyts committed
235
236
237
238
239
240
library needs to be included in your application's root (e.g. `index.js`).

```js
require('babel-polyfill')
```

241
We don't include this file by default because it has a significant
Yoshua Wuyts's avatar
Yoshua Wuyts committed
242
243
244
size overhead. Once Babel includes only the language features you're using,
we'll work to include `babel-polyfill` by default.

Yoshua Wuyts's avatar
Yoshua Wuyts committed
245
## Events
246

247
### `compiler.on('error', callback(nodeName, edgeName, error))`
248

Yoshua Wuyts's avatar
Yoshua Wuyts committed
249
250
251
Whenever an internal error occurs.

### `compiler.on('change', callback(nodeName, edgeName, state))`
252

Yoshua Wuyts's avatar
Yoshua Wuyts committed
253
254
255
Whenever a change in the internal graph occurs.

## API
256
257
258
259

### `compiler = webCompiler(entry, [opts])`

Create a new @hypha/web-compiler instance. Takes a path to a JavaScript file as the first argument. The following options are available:
260

261
262
263
264
- __opts.quiet:__ Defaults to `false`. Don't output any data to `stdout`. Useful
  if you have your own logging system.
- __opts.watch:__ Defaults to `true`. Watch for changes in the source files and
  rebuild. Set to `false` to get optimized bundles.
Arve Knudsen's avatar
Arve Knudsen committed
265
- __babelifyDeps:__ Defaults to true. Transform dependencies with babelify.
266
267

### `compiler.documents(routename, [opts], done(err, { buffer, hash }))`
268

Yoshua Wuyts's avatar
Yoshua Wuyts committed
269
270
271
272
273
274
Output an HTML bundle for a route. Routes are determined based on the project's
router. Pass `'/'` to get the default route.

- __opts.state:__ Will be passed the render function for the route, and inlined
  in the `<head>` of the body as `window.initialState`.

275
### `compiler.scripts(filename, done(err, { buffer, hash }))`
276

Yoshua Wuyts's avatar
Yoshua Wuyts committed
277
278
Pass in a filename and output a JS bundle.

279
### `compiler.assets(assetName, done(err, { buffer, hash }))`
280

Yoshua Wuyts's avatar
Yoshua Wuyts committed
281
282
Output any other file besides JS, CSS or HTML.

283
### `compiler.styles(name, done(err, { buffer, hash }))`
284

Yoshua Wuyts's avatar
Yoshua Wuyts committed
285
286
Output a CSS bundle.

287
### `compiler.manifest(done(err, { buffer, hash }))`
288

Yoshua Wuyts's avatar
Yoshua Wuyts committed
289
290
Output a `manifest.json`.

291
### `compiler.serviceWorker(done(err, { buffer, hash }))`
292

Yoshua Wuyts's avatar
Yoshua Wuyts committed
293
294
295
Output a service worker.

### `compiler.close()`
296

Yoshua Wuyts's avatar
Yoshua Wuyts committed
297
Close all file watchers.
Yoshua Wuyts's avatar
.  
Yoshua Wuyts committed
298
299

## License
300
301
302

  * Any code added starting from and including commit 7ae96cf36c3d335ef482a5e08c59d3b956d7a7b4 is released under [AGPLv3 or later](./LICENSE).
  * All code up to and including commit 41a32b3361d5ad926a74bf63fb5345606091a4fd is licensed under [Apache License 2.0](./LICENSE).
Yoshua Wuyts's avatar
Yoshua Wuyts committed
303

304
305
For license compatibility information, see [GPL-compatibility](https://www.apache.org/licenses/GPL-compatibility.html).

306
307
308
309
310
311
312
[babelify]: https://github.com/babel/babelify
[brfs]: https://github.com/browserify/brfs
[browser-pack-flat]: https://github.com/goto-bus-stop/browser-pack-flat
[browserify]: https://github.com/browserify/browserify
[bundle-collapser]: https://github.com/substack/bundle-collapser
[clean-css]: https://github.com/jakubpawlowicz/clean-css
[common-shakeify]: https://github.com/browserify/common-shakeify
Yoshua Wuyts's avatar
Yoshua Wuyts committed
313
[documentify]: https://github.com/stackhtml/documentify
314
315
316
317
318
319
[envify]: https://github.com/hughsk/envify
[glslify]: https://github.com/glslify/glslify
[inline-critical-css]: https://github.com/stackcss/inline-critical-css
[nanohtml]: https://github.com/choojs/nanohtml
[purify-css]: https://github.com/purifycss/purifycss
[sheetify]: https://github.com/stackcss/sheetify
Renée Kooi's avatar
Renée Kooi committed
320
[split-require]: https://github.com/goto-bus-stop/split-require
321
322
323
[tinyify]: https://github.com/browserify/tinyify
[uglifyify]: https://github.com/hughsk/uglifyify
[unassertify]: https://github.com/unassert-js/unassertify
Yoshua Wuyts's avatar
.  
Yoshua Wuyts committed
324