UNPKG

18.6 kBMarkdownView Raw
1# Batfish
2
3[![Build Status](https://travis-ci.org/mapbox/batfish.svg?branch=master)](https://travis-ci.org/mapbox/batfish)
4
5A static-site generator powered by React and Webpack.
6
7![](./batfish-dark.png)
8
9## Table of contents
10
11- [Other documentation to check out](#other-documentation-to-check-out)
12- [Goals](#goals)
13- [Installation](#installation)
14- [Getting Started](#getting-started)
15- [API](#api)
16 - [Configuration](#configuration)
17 - [CLI](#cli)
18 - [Node API](#node-api)
19- [Pages](#pages)
20 - [JS pages](#js-pages)
21 - [Markdown pages](#markdown-pages)
22 - [Non-page files within the pages directory](#non-page-files-within-the-pages-directory)
23 - [Path not found: 404](#path-not-found-404)
24- [Routing](#routing)
25 - [Links](#links)
26 - [Prefixing URLs](#prefixing-urls)
27 - [Programmatically changing pages](#programmatically-changing-pages)
28- [CSS](#css)
29- [Document <head>](#document-head)
30- [Development server](#development-server)
31- [Advanced usage](#advanced-usage)
32- [Comparison to other React-powered static-site generators](#comparison-to-other-react-powered-static-site-generators)
33
34## Other documentation to check out
35
36- [`docs/q-and-a.md`](docs/q-and-a.md)
37- [`docs/configuration.md`](docs/configuration.md)
38- [`docs/advanced-usage.md`](docs/advanced-usage.md)
39- [`docs/batfish-modules.md`](docs/batfish-modules.md)
40- [`docs/cli.md`](docs/cli.md)
41- [`docs/node-api.md`](docs/node-api.md)
42
43## Goals
44
45Batfish aims to provide *the essentials* for building excellent static websites with React and Webpack.
46
47- **(Universal) React.**
48 Use React components as your building blocks.
49 Your components are rendered into HTML pages at build time and then mounted in the browser for interactivity at run time.
50- **Super-powered Markdown pages.**
51 Batfish supports [jsxtreme-markdown] pages, which allow for interpolated JS expressions and JSX elements.
52- **Client-side routing with key features and minimal overhead.**
53 There is often no need for a big router library, but there *is* a need for often-overlooked features like automatic link hijacking (via [link-hijacker]) and scroll restoration (via [scroll-restorer]).
54- **Essential optimizations.**
55 JS bundles split by page and loaded on demand.
56 Hashed asset filenames for long-term caching.
57 Essential CSS injected into static HTML (via [postcss-html-filter]).
58 And so on.
59- **Minimal configuration.**
60 Though almost every user will want to set a couple of configuration properties, you might not need more than that — and none are required.
61- **Minimal.**
62 Batfish does not aim to be an ecosystem unto itself.
63 Instead, we've kept the codebase focused on a finite set of problems, while allowing extensibility by providing clear access to the underlying tools (React, Webpack, and Babel).
64 We've also tried to abstract generalizable functionality into independent npm packages, like [jsxtreme-markdown], [link-hijacker], and [scroll-restorer].
65 You can use these packages outside of Batfish — they are not coupled to Batfish conventions or configuration.
66
67## Installation
68
69You will need:
70
71- Node v6+
72- npm, preferably v5+
73
74Besides installing this package, you'll want to do a few things:
75
76- Install the peer dependencies:
77 ```
78 npm install --save react react-dom react-helmet
79 ```
80- Add `_batfish*` to your `.gitignore`, and maybe other ignore files (e.g. `.eslintignore`).
81 Batfish generates files and puts them in `_batfish_site` and `_batfish_tmp`.
82
83
84```
85npm install --save @mapbox/batfish
86```
87
88**You should not install the Batfish CLI globally.**
89Install Batfish as an npm dependency for your project, then use the CLI via npm `"scripts"`, npx, or `node_modules/.bin/batfish`.
90
91The easiest way to do this is to set up npm scripts in `package.json`, like so:
92
93```
94"scripts": {
95 "start": "batfish start",
96 "build": "batfish build",
97 "serve-static": "batfish serve-static"
98}
99```
100
101Then run `npm run start`, `npm run build`, and `npm run serve-static`, as needed.
102
103## Getting Started
104
105**The bare minimum to get started with Batfish.**
106
107- Install Batfish and its peer dependencies.
108 ```
109 npm install --save @mapbox/batfish react react-dom react-helmet
110 ```
111- Create 3 new `script`s in your `package.json`:
112 ```
113 "start": "batfish start",
114 "build": "batfish build",
115 "serve-static": "batfish serve-static",
116 ```
117- Create your first page file at `src/pages/index.js`.
118- Export from that page file a React component that renders something. Maybe something like this:
119
120 ```jsx
121 import React from 'react';
122
123 export default class Home extends React.Component {
124 render() {
125 return (
126 <div>Hello world</div>
127 );
128 }
129 }
130 ```
131
132- Run `npm run start`.
133- Open the URL printed in your terminal.
134- Build your website.
135- When you're ready to deploy, run `npm run build` to build the site for production, then `npm run serve-static` to check out the production site, which was written to `_batfish_site/`.
136- Put your `_batfish_site/` directory on the Internet.
137
138If you need to add configuration, create a `batfish.config.js` module in your project root.
139See ["Configuration"](#configuration).
140
141Look at [`examples/basic/`](examples/basic) for a simple example project.
142Look at [`examples/no-config/`](examples/no-config) for a project with no configuration.
143Or [`examples/miscellany/`](examples/miscellany), for a more advanced example project.
144
145## API
146
147### Configuration
148
149By default, all Batfish CLI commands look for `batfish.config.js` at the root of your project.
150It should export a function that returns your configuration object.
151
152For example:
153
154```js
155module.exports = () => {
156 return {
157 siteBasePath: '/my/site/base/path',
158 siteOrigin: 'https://www.mydomain.com'
159 // Add more configuration options here ...
160 };
161}
162```
163
164See [`docs/configuration.md`](docs/configuration.md) to learn about all the ways you can configure Batfish.
165
166### CLI
167
168The CLI has the following commands:
169
170- `start`: Start a development server and watch files for changes, rebuilding and refreshing as needed.
171- `build`: Build the static site.
172- `serve-static`: Serve the static site.
173- `write-babelrc`: Write a `.babelrc` file that other processes, like your test runner, can use.
174
175All commands will look for your configuration module in the current working directory or where you point with the `--config` option.
176
177For more details, run `batfish --help` or see [`docs/cli.md`](docs/cli.md).
178
179**You should not install the Batfish CLI globally.**
180Install Batfish as an npm dependency and use the CLI via npm `"scripts"`, npx, or `node_modules/.bin/batfish`.
181
182### Node API
183
184Usually you should use the Batfish CLI.
185But for those special cases when you want absolute control within a Node process, all the CLI's functionality is available in a Node API.
186
187See [`docs/node-api.md`](docs/node-api.md).
188
189## Pages
190
191**The structure of your [`pagesDirectory`] determines the URLs of your site.**
192JavaScript (`.js`) and Markdown (`.md`) files map directly to corresponding URLs.
193
194So `src/pages/industries/real-estate.js` corresponds to the URL `/industries/real-estate/`,
195and `src/pages/about/index.md` corresponds to the URL `/about/`.
196
197When a page is rendered, its component is passed the following props:
198
199- `location`: The browser's current [Location](https://developer.mozilla.org/en-US/docs/Web/API/Location).
200 (During the static build, this will only include the `pathname` property.)
201- `frontMatter`: The page's parsed front matter (parsed by [gray-matter]).
202
203### JS pages
204
205JS pages must export a React component with either `export default` (ES2015 modules) or `module.exports` (Node.js modules).
206
207JS pages can include front matter within block comments, delimited by `/*---` and `---*/`.
208
209For example:
210
211```js
212/*---
213title: Power tie catalog
214---*/
215import React from 'react';
216
217export default class PowerTiePage extends React.PureComponent {
218 render() {
219 return (
220 <div>
221 <h1>{this.props.frontMatter.title}</h1>
222 <p>Content forthcoming ...</p>
223 </div>
224 );
225 }
226}
227```
228
229### Markdown pages
230
231Markdown pages can include front matter delimited by `---`.
232
233**These files are interpreted as [jsxtreme-markdown], so the Markdown text can include interpolated JS expressions and JSX elements!**
234They are transformed into React components.
235
236All the props for the page (e.g. `frontMatter`, `location`) are available on `props`, e.g. `props.frontMatter.title`.
237
238For example:
239
240```md
241---
242title: Power tie catalog
243---
244
245# {{ props.frontMatter.title }}
246
247Content forthcoming ...
248```
249
250If you haven't seen [jsxtreme-markdown] before, [try it out online](https://mapbox.github.io/jsxtreme-markdown/).
251
252#### Markdown page wrapper components
253
254You need a wrapper component for each of your Markdown pages.
255You can specify a site-wide default wrapper, and also wrappers for specific Markdown pages.
256The wrapper component should be a React component (the default export of its module) which accepts the page's props and renders the Markdown content as `{this.props.children}`.
257Because it will receive the page's front matter as `this.props.frontMatter`, you can use front matter to fill out different parts of the wrapper (just like a Jekyll layout).
258
259Example:
260
261```js
262// blog-post-wrapper.js
263import React from 'react';
264import { MyPageShell } from './my-page-shell';
265
266export default class BlogPostWrapper extends React.PureComponent {
267 render() {
268 const { frontMatter } = this.props;
269 return (
270 <MyPageShell>
271 <h1>{frontMatter.title}</h1>
272 <p>
273 <strong>Summary:</strong> {frontMatter.summary}
274 </p>
275 <p>
276 Posted on {frontMatter.date}
277 </p>
278 {this.props.children}
279 </MyPageShell>
280 );
281 }
282}
283```
284
285```markdown
286---
287wrapper: '../path/to/blog-post-wrapper'
288title: Today I cleaned my refrigerator
289summary: You can't put off your responsibilities forever, and refrigerators do not clean themselves. So I cleaned my refrigerator.
290date: January 7, 2016
291---
292
293## Why did I do it
294
295Things had started to smell ...
296
297## How did I do it
298
299I love shopping for cleaning supplies ...
300```
301
302The front matter passed to Markdown wrapper components is augmented with a `headings` field, which contains an array of data about the headings in the Markdown.
303This data includes `slug`s that correspond to `id` attributes automatically added to the heading elements; so you can use this to generate a table of contents.
304(Read more in ["Generating tables of contents for Markdown pages"].)
305
306See [`examples/miscellany/`](examples/miscellany) and [`examples/table-of-contents/`](examples/table-of-contents) to learn more about what's possible with Markdown wrappers.
307
308#### Import JS modules into jsxtreme-markdown
309
310In jsxtreme-markdown components, you can specify JS modules to import and use within the interpolated code using [`prependJs` front matter](https://github.com/mapbox/jsxtreme-markdown/tree/master/packages/jsxtreme-markdown#prependjs).
311List lines of `import` or `require` statements that define variables you can use in your interpolated JS and JSX.
312
313By default, the following lines are always specified:
314
315- `import prefixUrl from '@mapbox/batfish/modules/prefix-url'`: See [Prefixing URLs].
316- `import routeTo from '@mapbox/batfish/modules/route-to')`: See docs for the [`route-to`] module.
317
318This means that those functions can be used with no additional configuration.
319Import your own modules and do more things.
320
321Example:
322
323```markdown
324---
325prependJs:
326 - "import { myDateFormatter } from './path/to/my-date-formatter';"
327---
328
329Learn more about [security]({{prefixUrl('/about/security')}}).
330
331Today is {{myDateFormatter('2015-08-21')}}
332```
333
334### Non-page files within the pages directory
335
336Sometimes you need to put an asset at a specific URL.
337You may want a `favicon.ico` in the root directory, for example; or a special image for social media `<meta>` tags on a page.
338For this reason, **any non-page files within the [`pagesDirectory`] are copied directly into the same location during the static build.**
339
340*When you access these files from pages, though, you need to use root-relative or absolute URLs.*
341That is, within `src/pages/foo/bar.js` you cannot access `src/pages/foo/bar.jpg` as `bar.jpg`: you need to use `/foo/bar.jpg`.
342You may want to [prefix the URLs](#prefixing-urls), also.
343
344### Path not found: 404
345
346Create a custom 404 page by adding `404.js` (or `404.md`) to the root of your [`pagesDirectory`].
347
348In development, you can expect to see your 404 page by entering an invalid path.
349When you build for [`production`], though, your 404 page will need to be handled and rendered by the server.
350(If you run your [`production`] build locally with `serve-static`, expect to see `Cannot GET /yourInvalidPathHere`.)
351
352## Routing
353
354Batfish builds you a minimal client-side router with Webpack bundle splitting by page.
355
356### Links
357
358**You can use regular `<a>` elements throughout your site.**
359When the user clicks a link, Batfish checks to see if the link's `href` refers to a page it knows about.
360If so, client-side routing is used.
361If not, the link behaves normally.
362
363If you would like to use an `<a>` without this link-hijacking (e.g. for your own internal routing within a page), you can give it (or one of its ancestor elements) the attribute `data-batfish-no-hijack`.
364
365This is all accomplished with [link-hijacker].
366
367### Prefixing URLs
368
369To prefix URLs with your [`siteBasePath`] and [`siteOrigin`] configuration options, use the [`prefix-url`] module.
370
371### Programmatically changing pages
372
373To change pages programmatically, with JavaScript, use the [`route-to`] module.
374
375## CSS
376
377Add stylesheets to your site with the [`stylesheets`] configuration option.
378List all your stylesheets, URLs or filepaths, in the order you'd like, and Batfish will concatenate them together and add them to the build.
379You can also pass them through whatever [PostCSS] plugins you'd like, with the [`postcssPlugins`] configuration option.
380
381**During the static build, each page has its relevant CSS injected inline, and the complete stylesheet is loaded lazily, after the rest of the page is rendered.**
382This optimization ensures that the loading of an external stylesheet does not block rendering, and your page content is visible as quickly as possible.
383(This is accomplished with [postcss-html-filter].)
384
385Assets referenced by `url()`s in your stylesheets will be hashed and copied to Batfish's [`outputDirectory`].
386
387You can also add page-specific CSS (processed through the same PostCSS pipeline), if you find yourself adding lots of CSS rules that are not used on multiple pages.
388Read more about ["Page-specific CSS"].
389
390**If you want to bypass this CSS system and use your own, just do it.**
391You can use the [`webpackLoaders`] and [`webpackPlugins`] configuration options to do whatever you need.
392
393(Curious or concerned? Check out the [Q&A entries about CSS](docs/q-and-a.md).)
394
395## Document `<head>`
396
397**Use [react-helmet] to add things the document `<head>`**, (e.g. `<title>` and `<meta>` tags).
398
399Batfish has a [peer dependency](https://nodejs.org/en/blog/npm/peer-dependencies/) on [react-helmet].
400You definitely want to use it.
401A good pattern is to create a `PageShell` React component that accepts props that it uses to populate that page's `<head>`.
402
403## Development server
404
405The development server (for `start` and `serve-static` commands) is a [Browsersync] server, which provides a nice experience for cross-device testing.
406
407When you change a file, Webpack will recompile and the browser will automatically refresh.
408
409(Why not hot module reloading?
410Seemed like more trouble than it's worth.
411But if you want to help add the feature, please open an issue.)
412
413## Advanced usage
414
415Additional documentation can be found in [`docs/advanced-usage.md`](docs/advanced-usage.md).
416
417## Comparison to other React-powered static-site generators
418
419We built Batfish by systematically addressing a set of problems we've had while building websites with React components.
420We focused first on the problems themselves, trying to develop effective and focused, minimalistic solutions.
421Sometimes this meant we used a popular tool, like Webpack.
422Other times we sidestepped a popular tool, like React Router, and opted to build something more fitted to our needs.
423
424As a result, Batfish is smaller and less ambitious than projects like [Gatsby](https://www.gatsbyjs.org/) and [Next.js](https://github.com/zeit/next.js/).
425It's a thinner wrapper over the underlying tools, not an ecosystem of its own — more of a gateway into existing ecosystems.
426
427Batfish also includes some features that we considered important but are overlooked by similar projects, like powerful Markdown integration and link hijacking.
428(Though we tried to build such features in such a way that they could be re-used in other contexts.
429 Try [jsxtreme-markdown] in your Gatsby site!)
430
431Since we use Batfish for vital projects, we prioritize the needs of end-users (website visitors) and the stability, simplicity, and clarity of the system.
432
433Please let us know what you think!
434
435![The batfish](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Longnose_batfish.jpg/320px-Longnose_batfish.jpg)
436
437[configuration]: #configuration
438
439[pages]: #pages
440
441[prefixing urls]: #prefixing-urls
442
443[`pagesdirectory`]: docs/configuration.md#pagesdirectory
444
445[`outputdirectory`]: docs/configuration.md#outputdirectory
446
447[`dataselectors`]: docs/configuration.md#dataselectors
448
449[`sitebasepath`]: docs/configuration.md#sitebasepath
450
451[`siteorigin`]: docs/configuration.md#siteorigin
452
453[`production`]: docs/configuration.md#production
454
455[`stylesheets`]: docs/configuration.md#stylesheets
456
457[`includepromisepolyfill`]: docs/configuration.md#includepromisepolyfill
458
459[`webpackloaders`]: docs/configuration.md#webpackloaders
460
461[`webpackplugins`]: docs/configuration.md#webpackplugins
462
463[jsxtreme-markdown]: https://github.com/mapbox/jsxtreme-markdown
464
465[link-hijacker]: https://github.com/mapbox/link-hijacker
466
467[scroll-restorer]: https://github.com/mapbox/scroll-restorer
468
469[react-helmet]: https://github.com/nfl/react-helmet
470
471[browsersync]: https://www.browsersync.io/
472
473[postcss-html-filter]: https://github.com/mapbox/postcss-html-filter
474
475[postcss]: http://postcss.org/
476
477["injecting data"]: docs/advanced-usage.md#injecting-data
478
479[`postcssplugins`]: docs/configuration.md#postcssplugins
480
481[gray-matter]: https://github.com/jonschlinkert/gray-matter
482
483[`route-to`]: docs/batfish-modules.md#route-to
484
485[`prefix-url`]: docs/batfish-modules.md#prefix-url
486
487["page-specific css"]: docs/advanced-usage.md#page-specific-css
488
489["hello world guide"]: docs/hello-world.md
490
491["generating tables of contents for markdown pages"]: docs/advanced-usage.md#generating-tables-of-contents-for-markdown-pages