diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index e6c2203b7bb13..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -interfaces -**/__tests__/fixtures/ diff --git a/.eslintrc b/.eslintrc index 960579931c9e8..769d650f77d16 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,10 +9,7 @@ "prettier/flowtype", "prettier/react" ], - "plugins": [ - "flowtype", - "react" - ], + "plugins": ["flowtype", "react"], "parserOptions": { "ecmaVersion": 2016, "sourceType": "module", @@ -29,30 +26,16 @@ "no-console": "off", "valid-jsdoc": "off", "require-jsdoc": "off", - "quotes": [ - "error", - "backtick" - ], - "consistent-return": [ - "error", - ], + "quotes": ["error", "backtick"], + "consistent-return": ["error"], "arrow-body-style": [ "error", "as-needed", { "requireReturnForObjectLiteral": true } ], - "jsx-quotes": [ - "error", - "prefer-double" - ], - "semi": [ - "error", - "never" - ], - "object-curly-spacing": [ - "error", - "always" - ], + "jsx-quotes": ["error", "prefer-double"], + "semi": ["error", "never"], + "object-curly-spacing": ["error", "always"], "comma-dangle": [ "error", { diff --git a/.flowconfig b/.flowconfig index 1fdf79f22aa58..75e66a13c27be 100644 --- a/.flowconfig +++ b/.flowconfig @@ -3,8 +3,12 @@ [ignore] .*/test/.* +.*/node_modules/documentation/.* .*/node_modules/typography/.* .*/node_modules/fbjs/.* .*/node_modules/react-side-effect/.* +.*/node_modules/styled-components/.* +.*/node_modules/preact/.* +.*/node_modules/jss/.* .*/www/.* .*/examples/.* diff --git a/.gitignore b/.gitignore index acf48a1842836..46c283f992d49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ + +packages/*/yarn.lock +packages/*/package-lock.json + # Logs logs *.log @@ -44,5 +48,4 @@ node_modules/ # IDE specific .idea/ .vscode/ -yarn.lock -package-lock.json + diff --git a/.pre-commit.sh b/.pre-commit.sh deleted file mode 100755 index 9a0a5667abde4..0000000000000 --- a/.pre-commit.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -jsfiles=$(git diff --cached --name-only --diff-filter=ACM | grep '\.jsx\?$' | tr '\n' ' ') -[ -z "$jsfiles" ] && exit 0 - -diffs=$(node_modules/.bin/prettier -l $jsfiles) -[ -z "$diffs" ] && exit 0 - -echo "here" -echo >&2 "Javascript files must be formatted with prettier. Please run:" -echo >&2 "node_modules/.bin/prettier --write "$diffs"" - -exit 1 diff --git a/.publishrc b/.publishrc deleted file mode 100644 index a377f6245a420..0000000000000 --- a/.publishrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "prepublishScript": "./scripts/prepublish.sh" -} diff --git a/decls/globals.js b/flow-typed/globals.js similarity index 100% rename from decls/globals.js rename to flow-typed/globals.js diff --git a/interfaces/invariant.js b/interfaces/invariant.js deleted file mode 100644 index 12febf96fa665..0000000000000 --- a/interfaces/invariant.js +++ /dev/null @@ -1,3 +0,0 @@ -declare module `invariant` { - declare var exports: (condition: any, message: string) => ?Error -} diff --git a/interfaces/object-assign.js b/interfaces/object-assign.js deleted file mode 100644 index f1d4d95705188..0000000000000 --- a/interfaces/object-assign.js +++ /dev/null @@ -1,3 +0,0 @@ -declare module `object-assign` { - declare var exports: (target: Object, ...sources?: Array) => {}; -} diff --git a/interfaces/parse-filepath.js b/interfaces/parse-filepath.js deleted file mode 100644 index f1d55517e03ab..0000000000000 --- a/interfaces/parse-filepath.js +++ /dev/null @@ -1,24 +0,0 @@ -declare module `parse-filepath` { - declare type FileData = { - root: string, - dir: string, - base: string, - ext: string, - name: string, - - // aliases - extname: string, - basename: string, - dirname: string, - stem: string, - - // original path - path: string, - - // getters - absolute: () => string, - isAbsolute: () => Boolean - } - - declare var exports: (input: string) => FileData -} diff --git a/interfaces/slash.js b/interfaces/slash.js deleted file mode 100644 index 13207b88aba0d..0000000000000 --- a/interfaces/slash.js +++ /dev/null @@ -1,3 +0,0 @@ -declare module `slash` { - declare var exports: (input: string) => string; -} diff --git a/package.json b/package.json index 815f0c39264eb..4aa15b60dcc57 100644 --- a/package.json +++ b/package.json @@ -37,18 +37,19 @@ "rimraf": "^2.6.1" }, "engines": { - "yarn": "^1.0.2" + "yarn": "^1.2.1" }, "private": true, - "engines": { - "yarn": "^1.0.2" - }, + "scripts": { "bootstrap": "yarn && npm run check-versions && lerna run prepublish", "check-versions": "babel-node scripts/check-versions.js", - "format": "npm run format-packages && npm run format-cache-dir && npm run format-www && npm run format-examples && npm run format-scripts", - "format-cache-dir": "prettier-eslint --write \"packages/gatsby/cache-dir/*.js\"", - "format-examples": "prettier-eslint --write \"examples/**/gatsby-node.js\" \"examples/**/gatsby-config.js\" \"examples/**/src/**/*.js\"", + "format": + "npm run format-packages && npm run format-cache-dir && npm run format-www && npm run format-examples && npm run format-scripts", + "format-cache-dir": + "prettier-eslint --write \"packages/gatsby/cache-dir/*.js\"", + "format-examples": + "prettier-eslint --write \"examples/**/gatsby-node.js\" \"examples/**/gatsby-config.js\" \"examples/**/src/**/*.js\"", "format-packages": "prettier-eslint --write \"packages/*/src/**/*.js\"", "format-scripts": "prettier-eslint --write \"scripts/**/*.js\"", "format-www": "prettier-eslint --write \"www/*.js\" \"www/src/**/*.js\"", @@ -63,10 +64,10 @@ "test": "yarn run lint && jest", "test:update": "jest --updateSnapshot", "test:watch": "jest --watch", - "test_bkup": "npm run lint && npm run test-node && npm run test-integration", + "test_bkup": + "npm run lint && npm run test-node && npm run test-integration", "watch": "lerna run watch --no-sort --stream --concurrency 999" }, - "workspaces": [ - "packages/*" - ] + "workspaces": ["packages/*"], + "eslintIgnore": ["interfaces", "**/__tests__/fixtures/"] } diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/jobs.js.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/jobs.js.snap index 891c22bf829d4..8c8336f98fcec 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/jobs.js.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/jobs.js.snap @@ -8,7 +8,7 @@ Object { "completedAt": 1482363367071, "createdAt": 1482363367071, "id": "test job", - "plugin": Object {}, + "plugin": null, "runTime": 0, }, ], @@ -20,7 +20,7 @@ Object { "payload": Object { "id": "test job", }, - "plugin": Object {}, + "plugin": null, "type": "CREATE_JOB", } `; diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/pages.js.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/pages.js.snap index 6a3f22341a24a..fab299e14054b 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/pages.js.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/pages.js.snap @@ -8,6 +8,7 @@ Array [ "context": Object {}, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -19,6 +20,7 @@ Array [ "context": Object {}, "internalComponentName": "ComponentHiPizza", "jsonName": "hi-pizza.json", + "layout": null, "path": "/hi/pizza/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -35,6 +37,7 @@ Object { "context": Object {}, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -57,6 +60,7 @@ Array [ "context": Object {}, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -75,6 +79,7 @@ Object { }, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -99,6 +104,7 @@ Array [ }, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", @@ -117,6 +123,7 @@ Array [ "context": Object {}, "internalComponentName": "ComponentHi", "jsonName": "hi.json", + "layout": null, "path": "/hi/", "pluginCreatorId": "test", "pluginCreator___NODE": "test", diff --git a/packages/gatsby/src/redux/actions.js b/packages/gatsby/src/redux/actions.js index df16d4a3f4645..22e19e4f3bb50 100644 --- a/packages/gatsby/src/redux/actions.js +++ b/packages/gatsby/src/redux/actions.js @@ -1,6 +1,6 @@ // @flow -import Joi from "joi" -import chalk from "chalk" +import Joi from 'joi' +import chalk from 'chalk' const _ = require(`lodash`) const { bindActionCreators } = require(`redux`) const { stripIndent } = require(`common-tags`) @@ -10,18 +10,60 @@ const path = require(`path`) const { joinPath } = require(`../utils/path`) const { getNode, hasNodeChanged } = require(`./index`) const { store } = require(`./index`) -import * as joiSchemas from "../joi-schemas/joi" -import { generateComponentChunkName } from "../utils/js-chunk-names" +import * as joiSchemas from '../joi-schemas/joi' +import { generateComponentChunkName } from '../utils/js-chunk-names' const actions = {} +type Job = { + id: string, +} +type PageInput = { + path: string, + component: string, + layout?: string, + context?: Object, +} +type LayoutInput = { + id?: string, + component: string, + layout?: string, + context?: Object, +} + +type Page = { + path: string, + component: string, + context: Object, + internalComponentName: string, + jsonName: string, + componentChunkName: string, + layout: ?string, + updatedAt: number, +} + +type Layout = { + id: any, + context: Object, + component: string, + componentWrapperPath: string, + componentChunkName: string, + internalComponentName: string, + jsonName: string, + isLayout: true, +} + +type Plugin = { + name: string, +} + /** * Delete a page * @param {string} page a page object with at least the path set * @example * deletePage(page) */ -actions.deletePage = (page, plugin = ``) => { +actions.deletePage = (page: PageInput) => { return { type: `DELETE_PAGE`, payload: page, @@ -51,53 +93,55 @@ const pascalCase = _.flow(_.camelCase, _.upperFirst) * }, * }) */ -actions.createPage = (page, plugin = ``, traceId) => { - page.componentChunkName = generateComponentChunkName(page.component) - +actions.createPage = (page: PageInput, plugin?: Plugin, traceId?: string) => { let jsonName = `${_.kebabCase(page.path)}.json` let internalComponentName = `Component${pascalCase(page.path)}` + if (jsonName === `.json`) { jsonName = `index.json` internalComponentName = `ComponentIndex` } - + let layout = page.layout || null // If no layout is set we try fallback to `/src/layouts/index`. if ( - !page.layout && - !glob.sync( + !layout && + glob.sync( joinPath(store.getState().program.directory, `src/layouts/index.*`) - ).length == 0 + ).length ) { - page.layout = `index` + layout = `index` } - page.jsonName = jsonName - page.internalComponentName = internalComponentName - page.updatedAt = Date.now() - - // Ensure the page has a context object - if (!page.context) { - page.context = {} + let internalPage: Page = { + layout, + jsonName, + internalComponentName, + path: page.path, + component: page.component, + componentChunkName: generateComponentChunkName(page.component), + // Ensure the page has a context object + context: page.context || {}, + updatedAt: Date.now(), } - const result = Joi.validate(page, joiSchemas.pageSchema) + const result = Joi.validate(internalPage, joiSchemas.pageSchema) if (result.error) { console.log(chalk.blue.bgYellow(`The upserted page didn't pass validation`)) console.log(chalk.bold.red(result.error)) - console.log(page) + console.log(internalPage) return null } // If the path doesn't have an initial forward slash, add it. - if (page.path[0] !== `/`) { - page.path = `/` + page.path + if (internalPage.path[0] !== `/`) { + internalPage.path = `/${internalPage.path}` } return { type: `CREATE_PAGE`, plugin, traceId, - payload: page, + payload: internalPage, } } @@ -107,7 +151,7 @@ actions.createPage = (page, plugin = ``, traceId) => { * @example * deleteLayout(layout) */ -actions.deleteLayout = (layout, plugin = ``) => { +actions.deleteLayout = (layout: Layout, plugin?: Plugin) => { return { type: `DELETE_LAYOUT`, payload: layout, @@ -129,32 +173,39 @@ actions.deleteLayout = (layout, plugin = ``) => { * } * }) */ -actions.createLayout = (layout, plugin = ``, traceId) => { - layout.id = layout.id || path.parse(layout.component).name - layout.componentWrapperPath = joinPath( +actions.createLayout = ( + layout: LayoutInput, + plugin?: Plugin, + traceId?: string +) => { + let id = layout.id || path.parse(layout.component).name + let componentWrapperPath = joinPath( store.getState().program.directory, `.cache`, `layouts`, - layout.id + `.js` + `${id}.js` ) - layout.componentChunkName = generateComponentChunkName(layout.component) - layout.jsonName = `layout-${_.kebabCase(layout.id)}.json` - layout.internalComponentName = `Component-layout-${pascalCase(layout.id)}` - layout.isLayout = true - // Ensure the layout has a context object - if (!layout.context) { - layout.context = {} + let internalLayout: Layout = { + id, + componentWrapperPath, + isLayout: true, + jsonName: `layout-${_.kebabCase(id)}.json`, + internalComponentName: `Component-layout-${pascalCase(id)}`, + component: layout.component, + componentChunkName: generateComponentChunkName(layout.component), + // Ensure the page has a context object + context: layout.context || {}, } - const result = Joi.validate(layout, joiSchemas.layoutSchema) + const result = Joi.validate(internalLayout, joiSchemas.layoutSchema) if (result.error) { console.log( chalk.blue.bgYellow(`The upserted layout didn't pass validation`) ) console.log(chalk.bold.red(result.error)) - console.log(layout) + console.log(internalLayout) return null } @@ -162,7 +213,7 @@ actions.createLayout = (layout, plugin = ``, traceId) => { type: `CREATE_LAYOUT`, plugin, traceId, - payload: layout, + payload: internalLayout, } } @@ -173,7 +224,7 @@ actions.createLayout = (layout, plugin = ``, traceId) => { * @example * deleteNode(node.id, node) */ -actions.deleteNode = (nodeId, node, plugin = ``) => { +actions.deleteNode = (nodeId: string, node: any, plugin: Plugin) => { return { type: `DELETE_NODE`, plugin, @@ -188,7 +239,7 @@ actions.deleteNode = (nodeId, node, plugin = ``) => { * @example * deleteNodes([`node1`, `node2`]) */ -actions.deleteNodes = (nodes, plugin = ``) => { +actions.deleteNodes = (nodes: any[], plugin: Plugin) => { return { type: `DELETE_NODES`, plugin, @@ -254,7 +305,7 @@ const typeOwners = {} * } * }) */ -actions.createNode = (node, plugin, traceId) => { +actions.createNode = (node: any, plugin?: Plugin, traceId?: string) => { if (!_.isObject(node)) { return console.log( chalk.bold.red( @@ -340,19 +391,22 @@ actions.createNode = (node, plugin, traceId) => { } }) + const oldNode = getNode(node.id) + // Ensure the plugin isn't creating a node type owned by another // plugin. Type "ownership" is first come first served. - if (!typeOwners[node.internal.type] && plugin) { - typeOwners[node.internal.type] = plugin.name - } else { - if (typeOwners[node.internal.type] !== plugin.name) { - throw new Error( - stripIndent` - The plugin "${plugin.name}" created a node of a type owned by another plugin. + if (plugin) { + let pluginName = plugin.name + + if (!typeOwners[node.internal.type]) + typeOwners[node.internal.type] = pluginName + else if (typeOwners[node.internal.type] !== pluginName) + throw new Error(stripIndent` + The plugin "${pluginName}" created a node of a type owned by another plugin. The node type "${node.internal.type}" is owned by "${typeOwners[ - node.internal.type - ]}". + node.internal.type + ]}". If you copy and pasted code from elsewhere, you'll need to pick a new type name for your new node(s). @@ -364,24 +418,20 @@ actions.createNode = (node, plugin, traceId) => { The plugin creating the node: ${JSON.stringify(plugin, null, 4)} - ` - ) - } - } + `) - const oldNode = getNode(node.id) - - // If the node has been created in the past, check that - // the current plugin is the same as the previous. - if (oldNode && oldNode.internal.owner !== plugin.name) { - throw new Error( - stripIndent` - Nodes can only be updated by their owner. Node "${node.id}" is - owned by "${oldNode.internal.owner}" and another plugin "${plugin.name}" - tried to update it. + // If the node has been created in the past, check that + // the current plugin is the same as the previous. + if (oldNode && oldNode.internal.owner !== pluginName) { + throw new Error( + stripIndent` + Nodes can only be updated by their owner. Node "${node.id}" is + owned by "${oldNode.internal.owner}" and another plugin "${pluginName}" + tried to update it. - ` - ) + ` + ) + } } // Check if the node has already been processed. @@ -412,7 +462,7 @@ actions.createNode = (node, plugin, traceId) => { * @example * touchNode(`a-node-id`) */ -actions.touchNode = (nodeId, plugin = ``) => { +actions.touchNode = (nodeId: string, plugin?: Plugin) => { return { type: `TOUCH_NODE`, plugin, @@ -420,6 +470,13 @@ actions.touchNode = (nodeId, plugin = ``) => { } } +type CreateNodeInput = { + node: Object, + fieldName?: string, + fieldValue?: string, + name?: string, + value: any, +} /** * Extend another node. The new node field is placed under the `fields` * key on the extended node object. @@ -443,9 +500,9 @@ actions.touchNode = (nodeId, plugin = ``) => { * // The field value is now accessible at node.fields.happiness */ actions.createNodeField = ( - { node, name, value, fieldName, fieldValue }, - plugin, - traceId + { node, name, value, fieldName, fieldValue }: CreateNodeInput, + plugin: Plugin, + traceId?: string ) => { if (fieldName) { console.warn( @@ -506,7 +563,10 @@ actions.createNodeField = ( * @example * createParentChildLink({ parent: parentNode, child: childNode }) */ -actions.createParentChildLink = ({ parent, child }, plugin) => { +actions.createParentChildLink = ( + { parent, child }: { parent: any, child: any }, + plugin?: Plugin +) => { // Update parent parent.children.push(child.id) parent.children = _.uniq(parent.children) @@ -527,7 +587,14 @@ actions.createParentChildLink = ({ parent, child }, plugin) => { * @param {string} $0.connection A connection type * @private */ -actions.createPageDependency = ({ path, nodeId, connection }, plugin = ``) => { +actions.createPageDependency = ( + { + path, + nodeId, + connection, + }: { path: string, nodeId: string, connection: string }, + plugin: string = `` +) => { return { type: `CREATE_COMPONENT_DEPENDENCY`, plugin, @@ -545,7 +612,7 @@ actions.createPageDependency = ({ path, nodeId, connection }, plugin = ``) => { * @param {Array} paths the paths to delete. * @private */ -actions.deleteComponentsDependencies = paths => { +actions.deleteComponentsDependencies = (paths: string[]) => { return { type: `DELETE_COMPONENTS_DEPENDENCIES`, payload: { @@ -559,7 +626,13 @@ actions.deleteComponentsDependencies = paths => { * this to store the query with its component. * @private */ -actions.replaceComponentQuery = ({ query, componentPath }) => { +actions.replaceComponentQuery = ({ + query, + componentPath, +}: { + query: string, + componentPath: string, +}) => { return { type: `REPLACE_COMPONENT_QUERY`, payload: { @@ -581,7 +654,7 @@ actions.replaceComponentQuery = ({ query, componentPath }) => { * @example * createJob({ id: `write file id: 123`, fileName: `something.jpeg` }) */ -actions.createJob = (job, plugin = {}) => { +actions.createJob = (job: Job, plugin?: ?Plugin = null) => { return { type: `CREATE_JOB`, plugin, @@ -598,7 +671,7 @@ actions.createJob = (job, plugin = {}) => { * @example * setJob({ id: `write file id: 123`, progress: 50 }) */ -actions.setJob = (job, plugin = {}) => { +actions.setJob = (job: Job, plugin?: ?Plugin = null) => { return { type: `SET_JOB`, plugin, @@ -615,7 +688,7 @@ actions.setJob = (job, plugin = {}) => { * @example * endJob({ id: `write file id: 123` }) */ -actions.endJob = (job, plugin = {}) => { +actions.endJob = (job: Job, plugin?: ?Plugin = null) => { return { type: `END_JOB`, plugin, @@ -631,7 +704,10 @@ actions.endJob = (job, plugin = {}) => { * @example * setPluginStatus({ lastFetched: Date.now() }) */ -actions.setPluginStatus = (status, plugin) => { +actions.setPluginStatus = ( + status: { [key: string]: mixed }, + plugin: Plugin +) => { return { type: `SET_PLUGIN_STATUS`, plugin, diff --git a/packages/gatsby/src/redux/reducers/jobs.js b/packages/gatsby/src/redux/reducers/jobs.js index 7968411b082ce..0066c79a40524 100644 --- a/packages/gatsby/src/redux/reducers/jobs.js +++ b/packages/gatsby/src/redux/reducers/jobs.js @@ -1,4 +1,5 @@ const _ = require(`lodash`) +const { oneLine } = require(`common-tags`) const moment = require(`moment`) module.exports = (state = { active: [], done: [] }, action) => { @@ -44,11 +45,10 @@ module.exports = (state = { active: [], done: [] }, action) => { const completedAt = Date.now() const job = state.active.find(j => j.id === action.payload.id) if (!job) { - throw new Error( - `The plugin "${action.plugin - .name}" tried to end a job with the id "${action.payload - .id}" that either hasn't yet been created or has already been ended` - ) + throw new Error(oneLine` + The plugin "${_.get(action, `plugin.name`, `anonymous`)}" + tried to end a job with the id "${action.payload.id}" + that either hasn't yet been created or has already been ended`) } return { diff --git a/v0-README.md b/v0-README.md deleted file mode 100644 index a0abf40c8656f..0000000000000 --- a/v0-README.md +++ /dev/null @@ -1,725 +0,0 @@ -[![Travis CI Build Status](https://img.shields.io/travis/gatsbyjs/gatsby/master.svg?style=flat-square)](https://travis-ci.org/gatsbyjs/gatsby) -[![npm package](https://img.shields.io/npm/v/gatsby.svg?style=flat-square)](https://www.npmjs.org/package/gatsby) -[![gatsby channel on discord](https://img.shields.io/badge/discord-gatsby%40reactiflux-738bd7.svg?style=flat-square)](https://discord.gg/0ZcbPKXt5bVoxkfV) -[![OpenCollective](https://opencollective.com/gatsby/backers/badge.svg)](#backers) -[![Twitter Follow](https://img.shields.io/twitter/follow/gatsbyjs.svg?style=social)](https://twitter.com/gatsbyjs) - -# Gatsby - -βš›οΈπŸ“„πŸš€ Blazing fast React.js static site generator - -Transform plain text and data into dynamic blogs and websites using the latest -web technologies. - -Supports pages written in Markdown, HTML, and React.js out of the box. Easy to -add support for additional file types. - -Leverages React's [component model](https://facebook.github.io/react/blog/2013/06/05/why-react.html) - and [React Router's "nested component hierarchy"](https://github.com/ReactTraining/react-router/blob/v3.0.3/docs/Introduction.md) -to make templating incredibly powerful and intuitive. - -All templates, css, and content are *hot reloadable* β€” powered by -[webpack](https://github.com/webpack/webpack). Makes for a brilliant -development experience - -Build sites like it's 1995. Files are translated into HTML pages at the -same position within the file system. Add a markdown file at `/docs/index.md` and -it'll be converted to `/docs/index.html`. - -*[We're working on the 1.0 release of Gatsby!](https://github.com/gatsbyjs/gatsby/issues/419)* Find the code in the `1.0` branch. - -Come help us finish up the release! See the list of [remaining tasks on the 1.0 umbrella issue](https://github.com/gatsbyjs/gatsby/issues/796). - -Checkout the first 1.0 example site GatsbyGram -https://gatsbygram.gatsbyjs.org and the case study on how its built https://www.gatsbyjs.org/blog/gatsbygram-case-study/ - -![live-reloading example](http://zippy.gfycat.com/UltimateWeeklyBarebirdbat.gif) - -## Goals -* No-reload page transitions -* Hot reload editing. Tweak your pages, templates, and styles and see changes in - real time. -* Make React.js component model and ecosystem available for building static sites -* Intuitive directory-based URLs. The URL of a page is derived from its - spot on the file system. -* Support "Starters" β€” install starter sites directly from Github. Use open sourced - starters or build your own. - -## Why use Gatsby instead of other Static Site Generators -* No-refresh page transitions -* The awesome React.js component model -* Live editing on every part of your site. Dramatically speed development. - -## Sites built with Gatsby -* [bricolage.io](https://bricolage.io/?utm_source=github.com) ([source](https://github.com/KyleAMathews/blog)) -* [reindex.io](https://www.reindex.io) -* [syncano.io](https://www.syncano.io) ([source](https://github.com/Syncano/syncano.com)) -* [graphene-python.org](http://graphene-python.org/) ([source](https://github.com/graphql-python/graphene/tree/master/docs)) -* [back-to-the-basics.io](http://back-to-the-basics.io/) -* [nordnet.se/brand](https://www.nordnet.se/brand/) -* [likescoffee.com](https://likescoffee.com/) ([source](https://github.com/pamo/pamo.github.io/tree/development)) -* [vii.campjs.com](http://vii.campjs.com/) ([source](https://github.com/campjs/campjs-vii)) -* [michaeljdeeb.com](http://michaeljdeeb.com) ([source](https://github.com/michaeljdeeb/michaeljdeeb-gatsby-blog)) -* [brittcrawford.com](http://brittcrawford.com) ([source](https://github.com/britt/britt.github.com/tree/gatsby)) -* [React-MDL](https://react-mdl.github.io/react-mdl/) ([source](https://github.com/tleunen/react-mdl/tree/master/docs)) -* [Snapgit](https://snapgit.com) ([source](https://github.com/glassfalcon/snapgit.com)) -* [iamdustan.com](http://iamdustan.com/) ([source](https://github.com/iamdustan/iamdustan.github.io)) -* [React Hardware](http://iamdustan.com/react-hardware/) ([source](https://github.com/iamdustan/react-hardware/tree/master/docs)) -* [peterp.me](https://www.peterp.me) ([source](https://github.com/peterpme/peterpme.github.io)) -* [k-create.com](https://k-create.com) ([source](https://github.com/kristofferh/kristoffer)) -* [kylegach.com](https://kylegach.com) ([source](https://github.com/kylegach/kylegach_com)) -* [jazlal.li](http://jazlal.li) ([source](https://github.com/jazlalli/jazlal.li)) -* [React Headroom](https://kyleamathews.github.io/react-headroom/) ([source](https://github.com/KyleAMathews/react-headroom/tree/master/website)) -* [VΓΆllig Ohne](http://volligohne.com/) ([source](https://github.com/voellig-ohne/voellig-ohne-website)) -* [michaelcereda.com](https://michaelcereda.com/) ([source](https://github.com/MichaelCereda/michaelcereda.com)) -* [openFDA](https://open.fda.gov/) ([source](https://github.com/FDA/open.fda.gov)) -* [emilyaviva.com](http://emilyaviva.com) ([source](https://github.com/emilyaviva/emilyaviva.com)) -* [dynamicext.com](http://www.dynamicext.com/) -* [React Gravatar](http://kyleamathews.github.io/react-gravatar/) ([source](https://github.com/KyleAMathews/react-gravatar/tree/master/www)) -* [johnmorris.io](http://johnmorris.io) ([source](https://github.com/johnpmorris/johnpmorris.github.io/tree/react-rebuild)) -* [twnsnd.co](http://twnsnd.co/) -* [Beau Han Xu](http://www.beauhanxu.com/) -* [HalfAtheist](https://halfatheist.com/) ([source](https://github.com/halfatheist/halfatheist.github.io)) -* [joshuahorwitz.net](http://joshuahorwitz.net) -* [DOSH](http://www.dosh.cash/) -* [Outcomes.com](https://www.outcomes.com/) -* [Codefellows.org](https://www.codefellows.org) -* [ollieglass.com](http://ollieglass.com/) -* [waigojs.com](https://waigojs.com/) ([source](https://github.com/waigo/waigo.github.io)) -* [The State Of JavaScript](http://stateofjs.com/) -* [ZBT MIT Website](http://zbt.mit.edu) ([source](https://github.com/Slava/zbt-website)) -* [ethereumclassic.org](http://ethereumclassic.org/) ([source](https://github.com/ethereumclassic/ethereumclassic.github.io/tree/source)) -* [Husam Machlovi, Portfolio & Blog](http://husammachlovi.com) -* [numenta.com](http://numenta.com) ([source](https://github.com/numenta/numenta-web/tree/master/numenta.com)) -* [chiedolabs.com](https://labs.chie.do) -* [Yuppies](https://yuppi.es) ([source](https://github.com/f0rr0/f0rr0.github.io)) -* [Go7hic](http://blog.yongyuan.us) -* [Video streaming devops blog](https://devops.spuul.com/) ([source](https://github.com/Spuul/devops-blog/)) -* [blog.rphl.io](https://blog.rphl.io) -* [oliverbenns.com](http://oliverbenns.com?utm_source=github.com) ([source](https://github.com/oliverbenns/oliverbenns.com)) -* [Kapadiya.net](https://www.kapadiya.net/?utm_source=github.com) ([source](https://github.com/vikas5914/vikas5914.github.io)) -* [anvilabs.co](https://anvilabs.co/?utm_source=github.com) ([source](https://github.com/anvilabs/anvilabs.co)) -* [Cardiogram](https://cardiogr.am) -* [Geographer](https://geographer.su) ([source](https://github.com/MenaraSolutions/geographer-docs)) -* [fuchs+wald](https://fuchsundwald.de) ([source](https://github.com/voellig-ohne/cf-website)) -* [AngularToReact.com](https://angulartoreact.com) -* [damianmullins.com](http://www.damianmullins.com) ([source](https://github.com/DamianMullins/damianmullins.github.io)) -* [Spencer Dixon's Blog](https://www.spencerdixon.com/) ([source](https://github.com/SpencerCDixon/blog)) -* [LandlordAccountz.com](http://www.landlordaccountz.com) -* [Timo Becker](https://timobecker.com) ([source](https://github.com/voellig-ohne/timobecker)) -* [Sacha Greif](http://sachagreif.com/) ([source](https://github.com/SachaG/sg2017)) -* [Crypto Christmas](https://crypto.christmas/) ([source](https://github.com/rileyjshaw/crypto.christmas)) -* [Perspexi Labs](https://www.perspexilabs.com/) -* [Scaphold.io Community](https://scaphold.io/community/) ([source](https://github.com/scaphold-io/scaphold-community)) -* [Green Navigation wiki](https://greennav.github.io) ([source](https://github.com/Greennav/greennav.github.io)) -* [Fabric](https://www.meetfabric.com) -* [eugenyzeiri.xyz](http://eugenyzeiri.xyz) -* [Reactiflux](https://www.reactiflux.com/) ([source](https://github.com/reactiflux/reactiflux.com)) -* [2016 JavaScript Rising Stars](https://risingstars2016.js.org/) ([source](https://github.com/michaelrambeau/risingstars2016)) -* [Daniel Reszka blog, code & gallery](http://blog.pixarea.com) ([source](https://github.com/danielres/blog)) -* [meadowlab.io](https://meadowlab.io/) -* [xpchbill.github.io](https://xpchbill.github.io/) ([source](https://github.com/xpchbill/xpchbill.github.io)) -* [jaredhanstra.com](http://www.jaredhanstra.com/) ([source](https://github.com/jhanstra/jh-gatsby)) -* [BBQ Agency](https://bbq.agency/) ([source](https://github.com/bbq-agency/bbq-agency.github.io)) -* [Coriolan UI - sass mixins pack](https://coriolan-ui.github.io/) ([source](https://github.com/coriolan-ui/coriolan-ui.github.io)) -* [5 minutes of React podcast - in Russian](http://5minreact.ru/) ([source](https://github.com/5minreact/5minreact)) -* [5 minutes of React podcast - in English](https://5minreact.audio/) ([source](https://github.com/5minreact/5minreact_audio)) -* [@hsribei's log](https://hsribei.github.io/log) ([source](https://github.com/hsribei/log)) -* [Megan Keesee](https://megankeesee.com) ([source](https://github.com/MeganKeesee/personal-site)) -* [devtheweb.io](https://devtheweb.io) -* [SameMoment Blog](http://blog.samemoment.com/) -* [Kombi Labs](http://kombi.io/) -* [harshiniraji.in](http://harshiniraji.in) -* [Psalm.Bible](http://psalm.bible/psalm-139-16/) -* [Dimitrios Lytras](https://dimitrioslytras.com) -* [storybooks.js.org](https://storybooks.js.org) ([source](https://github.com/storybooks/storybooks.github.io/)) -* [Nicholas Young](https://nicholaswyoung.com) -* [Segment Blog](https://segment.com/blog/) -* [Adarsh Pyarelal](https://adarsh.netlify.com) -* [Yisela Alvarez Trentini](https://www.yisela.com) -* [Thijs Koerselman](https://www.vauxlab.com) -* [Programming Is Easy](http://programming-is-easy.com) -* [Arun Venkatesan](http://arun.is) -* [effulgence.io](http://effulgence.io)([source](https://github.com/prayasht/prayasht.github.io/tree/develop/v3)) -* [Edit this file to add yours!](https://github.com/gatsbyjs/gatsby/blob/master/README.md) - -*Note, for the sites that have made their source available, you can -install them locally by running `gatsby new SITE_NAME SOURCE_URL`.* - -## Sponsors -* [RelateRocket](https://relaterocket.co) -* [Thinkmill](http://thinkmill.com.au/) -* [Loanpal](https://loanpal.com/) - -## Videos and blogs -* [Scott Nonnenberg](https://twitter.com/scottnonnenberg) presents on Gatsby to the Seattle React.js Meetup https://blog.scottnonnenberg.com/static-site-generation-with-gatsby-js/ -* [Kyle Mathews](https://twitter.com/kylemathews) gave a lightning talk @ the 2016 React.js Conference on Gatsby https://www.youtube.com/watch?v=RFkNRKL6ZoE -* [Kyle Mathews](https://twitter.com/kylemathews) spoke on Gatsby to the San Francisco Static Web Tech meetup http://www.staticwebtech.com/presentations/developing-with-react-gatsbyjs/ -* [ReactJS E-Commerce With No Backend Using Snipcart & Gatsby](https://snipcart.com/blog/snipcart-reactjs-static-ecommerce-gatsby) -* [A step-by-step guide: Gatsby on Netlify](https://www.netlify.com/blog/2016/02/24/a-step-by-step-guide-gatsby-on-netlify) -* [Hosting GatsbyJS – A React Static Site Generator](https://www.aerobatic.com/blog/gatsbyjs) -* [Michael Cereda's multi-part series of blog posts on building his site with Gatsby](https://medium.com/@michaelcereda/creating-an-isomorphic-universal-website-with-react-part-1-a905350acba8#.akoo25l6j) -* [Talk on GatsbyJS @ ViennaJS](https://www.youtube.com/watch?v=f0O1nCgqA3E&feature=youtu.be&a) - -## I'm already building a server-rendered React.js site, is Gatsby a good fit? - -If your site falls closer to the site end of the app<---->site spectrum -then yes. - -Gatsby is an excellent fit for blogs, marketing sites, docs sites, etc. Proper web -apps should probably remain as normal web apps (though I'd love to be -proved wrong!). - -## Warning! - -Gatsby is not yet stable. APIs will break. Functionality is missing. It's -usable but if you plan on building with it, expect a rocky road for some time. - -Contributions welcome! - -## Getting started - -### Install -`npm install -g gatsby` - -### Usage -1. Create new Gatsby site `gatsby new my-test-gatsby-site` This creates the - directory for your Gatsby project and adds the minimal files - needed. -2. `cd my-test-gatsby-site` -3. `gatsby develop` β€” Gatsby will start a hot-reloading development - server accessible at [localhost:8000](http://localhost:8000) -4. See the tutorial below for more. - -### Gatsby Starters -The Gatsby CLI tool lets you install "starters". These are -partially built sites preconfigured to help you get moving faster on -creating a certain type of site. - -When creating a new site, you can optionally specify a starter to -base your new site on e.g. `gatsby new [SITE_DIRECTORY] [URL_OF_STARTER]` - -For example, to quickly create a blog using Gatsby, you could install -the Gatsby Starter Blog by running: - -`gatsby new blog https://github.com/gatsbyjs/gatsby-starter-blog` - -This downloads the files and initializes the site by running `npm -install` - -If you don't specify a custom starter, your site will be created -from the [default -starter](https://github.com/gatsbyjs/gatsby-starter-default). - -There are several starters that have been created. Create a PR to -include yours! - -* [Default minimal starter](https://github.com/gatsbyjs/gatsby-starter-default) ([Demo](http://gatsbyjs.github.io/gatsby-starter-default/)) -* [Kitchen sink starter](https://github.com/gatsbyjs/gatsby-starter-kitchen-sink) ([Demo](http://gatsbyjs.github.io/gatsby-starter-kitchen-sink/)) -* [Simple blog](https://github.com/gatsbyjs/gatsby-starter-blog) ([Demo](http://gatsbyjs.github.io/gatsby-starter-blog/)) -* [Simple documentation site](https://github.com/gatsbyjs/gatsby-starter-documentation) ([Demo](http://gatsbyjs.github.io/gatsby-starter-documentation/)) -* [Lumen](https://github.com/wpioneer/gatsby-starter-lumen) ([Demo](https://alxshelepenok.github.io/gatsby-starter-lumen/)) -* [DrunkenBlog](https://github.com/konsumer/gatsby-starter-drunkenblog) ([Demo](http://konsumer.js.org/gatsby-starter-drunkenblog/)) -* [Clean start](https://github.com/brianstone/gatsby-starter-clean) ([Demo](http://gatsby-starter-clean.netlify.com/)) -* [Project AMP blog](https://github.com/chiedo/gatsby-amp-starter-blog) -* [Gatstrap](https://github.com/jaxx2104/gatsby-starter-bootstrap) ([Demo](https://jaxx2104.github.io/gatsby-starter-bootstrap/)) -* [Alchemy](https://github.com/bntzio/gatsby-starter-alchemy) ([Demo](https://alchemy.netlify.com/)) -* [Superstylin'](https://github.com/bntzio/gatsby-starter-superstylin) ([Demo](https://superstylin.netlify.com/)) -* [Multi-Author Blog](https://github.com/emilyaviva/gatsby-starter-blog-multiple-authors -) ([Demo](https://emilyaviva.github.io/gatsby-starter-blog-multiple-authors/)) -* [Barebones](https://github.com/Rusta/gatsby-starter-barebones) ([Demo](https://rusta.github.io/gatsby-starter-barebones/)) -* [Barebones with Preact](https://github.com/Rusta/gatsby-starter-barebones-preact) ([Demo](https://rusta.github.io/gatsby-starter-barebones-preact/)) -* [Undefined](https://github.com/kentaro-m/gatsby-starter-undefined) ([Demo](https://undefined.netlify.com/)) -* [Guoliim blog starter](https://github.com/guoliim/guoliim-blog) ([Demo](https://blog.guoli.im/)) -* [Material Blog Starter](https://github.com/Vagr9K/gatsby-material-starter) ([Demo](https://vagr9k.github.io/gatsby-material-starter/)) - -### Tutorial: Building a documentation site from the Gatsby Documentation Starter -1. Install gatsby `npm install -g gatsby` -1. Install documentation site starter `gatsby new docs-site - gh:gatsbyjs/gatsby-starter-documentation` -2. type `cd docs-site` -2. type `gatsby develop` -3. Open site in browser at [localhost:8000](http://localhost:8000). Verify clicking on links works. -4. Try editing the site's config file `config.toml`. - Change the `siteTitle` key. The site's title should change shortly - after saving. -5. Next try editing a doc page. Open - `/pages/docs/getting-started/index.md` and edit it. Again any saved - changes should load without refreshing in the browser. -6. Add a new markdown page to the documentation. Copy the `getting-started` - directory to `some-additional-steps`. Then edit the markdown file - within the new directory. If you're familiar with other static site - generation software, you'll be familiar with the "frontmatter" at the - top of the file. Edit the title there + change the order to "5". Save - this. Ideally this new file would be hot reloaded like other changes - but I haven't figured out how to make this happen yet ([help - appreciated here](https://github.com/webpack/webpack/issues/1162)). - So to see your new page, restart `gatsby develop` and then refresh your - browser. -7. Build your site `gatsby build`. The site is built to the `/public` - directory. Test that the build worked by running `gatsby serve-build` - which serves the contents of the `/public` directory. - -## How Gatsby works - -### How files become pages -The process is file --> Webpack loader --> React.js wrapper component ---> static HTML page. - -Gatsby leverages [Webpack](http://webpack.github.io/) extensively. - Webpack is a sophisticated module bundler that can turn any sort of -file into a commonjs module. Webpack uses "Loaders" to convert a file -into a module. These loaded modules are then wrapped inside a React.js -component that's specific to a given file type. Gatsby then generates a -static HTML page from this component. - -Gatsby ships with default loaders and wrappers for HTML, Markdown, and -JSX/CJSX but for most projects you'll want to write your own loaders and -wrappers (very easy to do). - -As an example of how this process works, let's walk quickly through -converting a markdown file into an HTML page. - -The [default Gatsby markdown -loader](https://github.com/gatsbyjs/gatsby/blob/master/lib/loaders/markdown-loader/index.js) - parses the markdown into HTML and uses [Highlight.js](https://highlightjs.org/) - to syntax highlight code blocks. - -Our markdown file. - -```markdown ---- -title: This is a title ---- - -# Hi friends. -This is a markdown file. -``` - -When loaded and required, the resulting javascript object looks like the following: - -```javascript -{ - file: { - // Information about file on disk e.g. extension, directory path, etc. - }, - data: { - title: "This is a title", - body: "

Hi friends.

This is a markdown file

" - } -} -``` -Now Gatsby wraps the markdown file in this very simple React.js component. - -```javascript -export default MarkdownWrapper extends React.Component { - render() { - const post = this.props.route.page.data - - return ( -
-

{post.title}

-
-
- ); - } -} -``` - -#### frontmatter and metadata -Gatsby uses [front-matter](https://github.com/jxson/front-matter) and [html-frontmatter](https://github.com/zeke/html-frontmatter) to pull metadata out of files. This data is typically used in links leading to each page. The most relevant example is a list of blog posts in which you display the title, description, tags, etc. in the form of `{post.title}` in the React.js component. - -As seen in our previous markdown file, the title is part of the frontmatter -``` ---- -title: This is a title ---- - -# Hi friends. -This is a markdown file. -``` - -An html example as follows. -``` - - -

Hello World

-``` -In a .js|.jsx file, export a data object to set your metadata variables, like so: -``` -import React from 'react' - -exports.data = { - title: 'This is a title', -} - -export default MyComponent ... -``` - -You can also use a named export for the data object: - -```javascript -export const data = { - title: 'This is a title', -} -``` - -### Structure of a Gatsby site -* `config.toml` - Core application configuration is stored here. Available via a `require` -or `import` of 'config'. Values: - * `noProductionJavascript` - set to a truthy value to prevent generation of bundle.js - (containing your client-side Single Page App) during a `gatsby build`. You'll need - to update your top-level `html.js` file so that it doesn't pull in `bundle.js` in - production, but you'll want to keep it for `gatsby develop` mode. -* `html.js` - A React.js component that provides the overall HTML structure for the site. -* `/pages` - All pages go here. Everything is turned into a page except -files which start with an underscore: - * `_template` files under `/pages` are treated as parent templates for other pages in - the same directory tree. - * (optional) `pages/404.js` or `pages/404.html` - automatically picked up as your 'not - found' page. If you `` to an unknown URL, this page will be shown. Note: in - production, you'll need to [set up your server host to show this page when it can't find - the requested file](https://github.com/gatsbyjs/gatsby/pull/121#issuecomment-194715068). -* (optional) `gatsby-browser.js` - a way to hook into key application events. - * Export `onRouteUpdate` of type `function()` to be notified whenever React-Router navigates. - * Export `modifyRoutes` of type `function(routes: Object) => Object` to modify the react-router routes. - * Export `shouldUpdateScroll` of type `function(prevRouterProps: Object, nextRouterProps: Object) => boolean` - to determine if a given route change should scroll. - * Export `wrapRootComponent` of type `function(Root: React.Component) => React.Component` to allow you to wrap your `` component before mounting it with `ReactDOM.render()`. -* (optional) `gatsby-node.js` - a way to hook into events during build -and development. - * Export `rewritePath` of type `function(parsedFilePath: Object, metadata: Object)` to programmatically rewrite paths. This function will be called for every page and when you return a string, it is used as the new path. -* (optional) `gatsby-ssr.js` - a way to hook into events during server-side rendering - * Export `wrapRootComponent` of type `function(Root: React.Component) => React.Component` to allow you to wrap your `` component before `ReactDOMServer.renderToString()`. - -### How to use your own webpack loaders - -Gatsby uses [webpack-configurator](https://github.com/lewie9021/webpack-configurator) -to make changing the webpack loaders easy. The default set of loaders is organized by [key](lib/utils/webpack.config.js#L125). - -To modify the Webpack configuration, create a `gatsby-node.js` in the root of your project -and export there a `modifyWebpackConfig` function. - -```javascript -exports.modifyWebpackConfig = function(config, stage) { - // edit loaders here - return config -} -``` - -Gatsby calls this function with the webpack-configurator object and -"stage" string when it creates a Webpack config. It first -loads the defaults and then allows you to modify it. - -The `stage` can be: - -1. develop: for `gatsby develop` command, hot reload and CSS injection into page -2. develop-html: same as develop without react-hmre in the babel config for html renderer -3. build-css: build styles.css file -4. build-html: build all HTML files -5. build-javascript: Build bundle.js for Single Page App in production - -Consider the following example which removes the default css loader -and replaces it with a loader that uses css-modules. - -```javascript -exports.modifyWebpackConfig = function(config, stage) { - config.removeLoader('css') - config.loader('css', function(cfg) { - cfg.test = /\.css$/ - cfg.loader = 'style!css?modules' - return cfg - }) - return config -} -``` - -Each loader (`cfg` in the above example) can be a valid -[webpack loader](https://webpack.github.io/docs/configuration.html#module-loaders) -and there are a host of -[preexisting loaders](https://webpack.github.io/docs/list-of-loaders.html) -which you can use to enhance Gatsby. - -It is also possible to [write your own loaders](https://webpack.github.io/docs/how-to-write-a-loader.html). - -Gatsby includes [some default loaders](https://github.com/gatsbyjs/gatsby/tree/master/lib/loaders) that you can also override. - -To write your own loader or override a Gatsby loader, make a `loaders` directory at the root of your site that contains directories for custom loaders. - -e.g. `loaders/markdown-loader/index.js` [will take precedence](https://github.com/gatsbyjs/gatsby/blob/master/lib/utils/webpack.config.js#L325) -over the markdown-loader that Gatsby includes. - -[See an example of a custom loader in the default starter](https://github.com/gatsbyjs/gatsby-starter-default/blob/master/loaders/markdown-loader/index.js). - -### How to use your own webpack plugins - -Similar to the loaders, plugins are handled via -[webpack-configurator](https://github.com/lewie9021/webpack-configurator) -and `gatsby-node.js`. - -_Note: the following example is now redundant since -ExtractTextWebpackPlugin is now setup by default but you can still use -it as an example of how to modify the Webpack plugins._ - -If we wanted to extract all of the css in our project into a since -`styles.css` file for production, we could add the -`ExtractTextWebpackPlugin`. To do this, we need to modify the loader -and add the plugin when generating the static HTML for our site. - -```javascript -var ExtractTextPlugin = require("extract-text-webpack-plugin") - -exports.modifyWebpackConfig = function(config, stage) { - if(stage === 'build-html') { - config.removeLoader('css') - config.loader('css', function(cfg) { - cfg.test = /\.css$/ - cfg.loader = ExtractTextPlugin.extract('css?minimize') - return cfg - }) - config.plugin('extract-css', - ExtractTextPlugin, - ["styles.css", { allChunks: true }]) - } - return config -} -``` - -Each plugin (`extract-css` in the above example) can be a valid -[webpack plugin](https://webpack.github.io/docs/using-plugins.html) -and there are a host of -[preexisting plugins](https://webpack.github.io/docs/list-of-plugins.html) -which you can use to enhance Gatsby. - -It is also possible to -[write your own plugins](https://webpack.github.io/docs/how-to-write-a-plugin.html). - -### Perform additional post build step - -Gatsby also uses `gatsby-node.js` to pass control of the final build step over -to the user when running `gatsby build`. The post build function takes two arguments, the pages and the callback for completing the build: - -```javascript -exports.postBuild = function(pages, callback) { - // perform actions on pages here - - callback() -} -``` - -### How to write your own wrappers -* Coming... - -## FAQ - -### Pre-Requisites - -[Python v2](https://www.python.org/) is required to install Gatsby. Please ensure python is in your path before running `npm install -g gatsby`. - -### I added a new page and it's not showing up! - -[Webpack doesn't currently support hot-reloading new files added to a context](https://github.com/webpack/webpack/issues/1162). When you add a new file, restart the `gatsby develop` process and your new page will show up. - -Make sure you also including the **trailing slash** in your URLs: - -- Bad: `http://localhost:8000/foo` -- Good: `http://localhost:8000/foo/` - -### Inline CSS - -A neat performance feature supported by Gatsby is inlining your CSS in -the `` of each HTML page. Not referencing external style sheets -significantly speeds up the initial render of your site by avoiding -another round trip to your server as the initial render of a page is -blocked by external CSS files. This is a best practice suggested by -many groups including [Google's AMP -project](https://www.ampproject.org/docs/guides/responsive/style_pages.html). - -Each of [the official starters supports this pattern](https://github.com/gatsbyjs/gatsby-starter-default/blob/master/html.js). - The code to make it happen is in brief: - -```javascript -// In your html.js -let css -// In development, css is injected by Javascript by the Webpack style-loader. -if (process.env.NODE_ENV === 'production') { - css =