UNPKG

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