1 | # Batfish
|
2 |
|
3 | [![Build Status](https://travis-ci.com/mapbox/batfish.svg?branch=main)](https://travis-ci.com/mapbox/batfish)
|
4 |
|
5 | A 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 |
|
45 | Batfish 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 |
|
69 | You will need:
|
70 |
|
71 | - Node 10
|
72 | - npm 6
|
73 |
|
74 | If 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 |
|
76 | Besides 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 | ```
|
86 | npm install --save @mapbox/batfish
|
87 | ```
|
88 |
|
89 | **You should not install the Batfish CLI globally.**
|
90 | Install Batfish as an npm dependency for your project, then use the CLI via npm `"scripts"`, npx, or `node_modules/.bin/batfish`.
|
91 |
|
92 | The 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 |
|
102 | Then 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 |
|
146 | If you need to add configuration, create a `batfish.config.js` module in your project root.
|
147 | See ["Configuration"](#configuration).
|
148 |
|
149 | Look at [`examples/basic/`](examples/basic) for a simple example project.
|
150 | Look at [`examples/no-config/`](examples/no-config) for a project with no configuration.
|
151 | Or [`examples/miscellany/`](examples/miscellany), for a more advanced example project.
|
152 |
|
153 | ## API
|
154 |
|
155 | ### Configuration
|
156 |
|
157 | By default, all Batfish CLI commands look for `batfish.config.js` at the root of your project.
|
158 | It should export a function that returns your configuration object.
|
159 |
|
160 | For example:
|
161 |
|
162 | ```js
|
163 | module.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 |
|
172 | See [`docs/configuration.md`](docs/configuration.md) to learn about all the ways you can configure Batfish.
|
173 |
|
174 | ### CLI
|
175 |
|
176 | The 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 |
|
183 | All commands will look for your configuration module in the current working directory or where you point with the `--config` option.
|
184 |
|
185 | For more details, run `batfish --help` or see [`docs/cli.md`](docs/cli.md).
|
186 |
|
187 | **You should not install the Batfish CLI globally.**
|
188 | Install Batfish as an npm dependency and use the CLI via npm `"scripts"`, npx, or `node_modules/.bin/batfish`.
|
189 |
|
190 | ### Node API
|
191 |
|
192 | Usually you should use the Batfish CLI.
|
193 | But 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 |
|
195 | See [`docs/node-api.md`](docs/node-api.md).
|
196 |
|
197 | ## Pages
|
198 |
|
199 | **The structure of your [`pagesDirectory`] determines the URLs of your site.**
|
200 | JavaScript (`.js`) and Markdown (`.md`) files map directly to corresponding URLs.
|
201 |
|
202 | So `src/pages/industries/real-estate.js` corresponds to the URL `/industries/real-estate/`,
|
203 | and `src/pages/about/index.md` corresponds to the URL `/about/`.
|
204 |
|
205 | When 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 |
|
213 | JS pages must export a React component with either `export default` (ES2015 modules) or `module.exports` (Node.js modules).
|
214 |
|
215 | JS pages can include front matter within block comments, delimited by `/*---` and `---*/`.
|
216 |
|
217 | For example:
|
218 |
|
219 | ```js
|
220 | /*---
|
221 | title: Power tie catalog
|
222 | ---*/
|
223 | import React from 'react';
|
224 |
|
225 | export 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 |
|
239 | Markdown 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!**
|
242 | They are transformed into React components.
|
243 |
|
244 | All the props for the page (e.g. `frontMatter`, `location`) are available on `props`, e.g. `props.frontMatter.title`.
|
245 |
|
246 | For example:
|
247 |
|
248 | ```md
|
249 | ---
|
250 | title: Power tie catalog
|
251 | ---
|
252 |
|
253 | # {{ props.frontMatter.title }}
|
254 |
|
255 | Content forthcoming ...
|
256 | ```
|
257 |
|
258 | If you haven't seen [jsxtreme-markdown] before, [try it out online](https://mapbox.github.io/jsxtreme-markdown/).
|
259 |
|
260 | #### Markdown page wrapper components
|
261 |
|
262 | You need a wrapper component for each of your Markdown pages.
|
263 | You can specify a site-wide default wrapper, and also wrappers for specific Markdown pages.
|
264 | The 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}`.
|
265 | Because 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 |
|
267 | Example:
|
268 |
|
269 | ```js
|
270 | // blog-post-wrapper.js
|
271 | import React from 'react';
|
272 | import { MyPageShell } from './my-page-shell';
|
273 |
|
274 | export 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 | ---
|
295 | wrapper: '../path/to/blog-post-wrapper'
|
296 | title: Today I cleaned my refrigerator
|
297 | summary: You can't put off your responsibilities forever, and refrigerators do not clean themselves. So I cleaned my refrigerator.
|
298 | date: January 7, 2016
|
299 | ---
|
300 |
|
301 | ## Why did I do it
|
302 |
|
303 | Things had started to smell ...
|
304 |
|
305 | ## How did I do it
|
306 |
|
307 | I love shopping for cleaning supplies ...
|
308 | ```
|
309 |
|
310 | The 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.
|
311 | This 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 |
|
314 | See [`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 |
|
318 | In 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).
|
319 | List lines of `import` or `require` statements that define variables you can use in your interpolated JS and JSX.
|
320 |
|
321 | By 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 |
|
326 | This means that those functions can be used with no additional configuration.
|
327 | Import your own modules and do more things.
|
328 |
|
329 | Example:
|
330 |
|
331 | ```markdown
|
332 | ---
|
333 | prependJs:
|
334 | - "import { myDateFormatter } from './path/to/my-date-formatter';"
|
335 | ---
|
336 |
|
337 | Learn more about [security]({{prefixUrl('/about/security')}}).
|
338 |
|
339 | Today is {{myDateFormatter('2015-08-21')}}
|
340 | ```
|
341 |
|
342 | ### Non-page files within the pages directory
|
343 |
|
344 | Sometimes you need to put an asset at a specific URL.
|
345 | You may want a `favicon.ico` in the root directory, for example; or a special image for social media `<meta>` tags on a page.
|
346 | For 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.*
|
349 | That 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`.
|
350 | You may want to [prefix the URLs](#prefixing-urls), also.
|
351 |
|
352 | ### Path not found: 404
|
353 |
|
354 | Create a custom 404 page by adding `404.js` (or `404.md`) to the root of your [`pagesDirectory`].
|
355 |
|
356 | In development, you can expect to see your 404 page by entering an invalid path.
|
357 | When 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 |
|
362 | Batfish 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.**
|
367 | When the user clicks a link, Batfish checks to see if the link's `href` refers to a page it knows about.
|
368 | If so, client-side routing is used.
|
369 | If not, the link behaves normally.
|
370 |
|
371 | If 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 |
|
373 | This is all accomplished with [link-hijacker].
|
374 |
|
375 | ### Prefixing URLs
|
376 |
|
377 | To prefix URLs with your [`siteBasePath`] and [`siteOrigin`] configuration options, use the [`prefix-url`] module.
|
378 |
|
379 | ### Programmatically changing pages
|
380 |
|
381 | To change pages programmatically, with JavaScript, use the [`route-to`] module.
|
382 |
|
383 | ## CSS
|
384 |
|
385 | Add stylesheets to your site with the [`stylesheets`] configuration option.
|
386 | List all your stylesheets, URLs or filepaths, in the order you'd like, and Batfish will concatenate them together and add them to the build.
|
387 | You 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.**
|
390 | This 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 |
|
393 | Assets referenced by `url()`s in your stylesheets will be hashed and copied to Batfish's [`outputDirectory`].
|
394 |
|
395 | You 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.
|
396 | Read more about ["Page-specific CSS"].
|
397 |
|
398 | **If you want to bypass this CSS system and use your own, just do it.**
|
399 | You 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 |
|
407 | Batfish has a [peer dependency](https://nodejs.org/en/blog/npm/peer-dependencies/) on [react-helmet].
|
408 | You definitely want to use it.
|
409 | A 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 |
|
413 | The development server (for `start` and `serve-static` commands) is a [Browsersync] server, which provides a nice experience for cross-device testing.
|
414 |
|
415 | When you change a file, Webpack will recompile and the browser will automatically refresh.
|
416 |
|
417 | (Why not hot module reloading?
|
418 | Seemed like more trouble than it's worth.
|
419 | But if you want to help add the feature, please open an issue.)
|
420 |
|
421 | ## Advanced usage
|
422 |
|
423 | Additional documentation can be found in [`docs/advanced-usage.md`](docs/advanced-usage.md).
|
424 |
|
425 | ## Comparison to other React-powered static-site generators
|
426 |
|
427 | We built Batfish by systematically addressing a set of problems we've had while building websites with React components.
|
428 | We focused first on the problems themselves, trying to develop effective and focused, minimalistic solutions.
|
429 | Sometimes this meant we used a popular tool, like Webpack.
|
430 | Other times we sidestepped a popular tool, like React Router, and opted to build something more fitted to our needs.
|
431 |
|
432 | As 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/).
|
433 | It's a thinner wrapper over the underlying tools, not an ecosystem of its own — more of a gateway into existing ecosystems.
|
434 |
|
435 | Batfish 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 |
|
439 | Since 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 |
|
441 | Please 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
|