From a0393d3a3d5f4100b67a3db9bc921d7d3bf68e9e Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Wed, 15 Aug 2018 20:24:58 +1000 Subject: [PATCH 01/39] add gatsby-remark-graphviz package --- packages/gatsby-remark-graphviz/.babelrc | 5 ++ packages/gatsby-remark-graphviz/.gitignore | 1 + packages/gatsby-remark-graphviz/.npmignore | 34 ++++++++ packages/gatsby-remark-graphviz/README.md | 88 ++++++++++++++++++++ packages/gatsby-remark-graphviz/package.json | 43 ++++++++++ packages/gatsby-remark-graphviz/src/index.js | 34 ++++++++ 6 files changed, 205 insertions(+) create mode 100644 packages/gatsby-remark-graphviz/.babelrc create mode 100644 packages/gatsby-remark-graphviz/.gitignore create mode 100644 packages/gatsby-remark-graphviz/.npmignore create mode 100644 packages/gatsby-remark-graphviz/README.md create mode 100644 packages/gatsby-remark-graphviz/package.json create mode 100644 packages/gatsby-remark-graphviz/src/index.js diff --git a/packages/gatsby-remark-graphviz/.babelrc b/packages/gatsby-remark-graphviz/.babelrc new file mode 100644 index 0000000000000..b5d6b28d4b5ea --- /dev/null +++ b/packages/gatsby-remark-graphviz/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + ["../../.babel-preset.js"] + ] +} diff --git a/packages/gatsby-remark-graphviz/.gitignore b/packages/gatsby-remark-graphviz/.gitignore new file mode 100644 index 0000000000000..f720180601510 --- /dev/null +++ b/packages/gatsby-remark-graphviz/.gitignore @@ -0,0 +1 @@ +/index.js \ No newline at end of file diff --git a/packages/gatsby-remark-graphviz/.npmignore b/packages/gatsby-remark-graphviz/.npmignore new file mode 100644 index 0000000000000..e771d2c9fa299 --- /dev/null +++ b/packages/gatsby-remark-graphviz/.npmignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules +*.un~ +yarn.lock +src +flow-typed +coverage +decls +examples diff --git a/packages/gatsby-remark-graphviz/README.md b/packages/gatsby-remark-graphviz/README.md new file mode 100644 index 0000000000000..55df972d7c504 --- /dev/null +++ b/packages/gatsby-remark-graphviz/README.md @@ -0,0 +1,88 @@ +# gatsby-remark-graphviz + +Processes [graphviz](https://www.graphviz.org/) (`dot` and `circo`) code blocks in your markdown files and replaces them with the rendered SVG using [viz.js](https://github.com/mdaines/viz.js/) + +## Install + +`npm install --save gatsby-remark-graphviz` + +Note that you do **not** need graphviz installed on your machine as this project depends on viz.js which is a pure javascript port of graphviz. + +## How to use + +```javascript +// In your gatsby-config.js +plugins: [ + { + resolve: 'gatsby-transformer-remark', + options: { + plugins: [ + 'gatsby-remark-graphviz' + ] + } + } +], +``` + +Then, add `dot` code blocks to your markdown. E.g + + ```dot + digraph graphname { + a -> b; + b -> c; + a -> c; + } + ``` + +Which will be rendered using viz.js and the output html will replace the code block with the actual SVG. + + + +graphname + + + +a + +a + + + +b + +b + + + +a->b + + + + + +c + +c + + + +a->c + + + + + +b->c + + + + + + +## Alternatives + +If you want a broader range of drawing options, checkout [gatsby-remark-draw](https://www.npmjs.com/package/gatsby-remark-draw). It provides SvgBobRus, Graphviz, and Mermaid, but note that you must have these already installed on your system + +If you're simply looking for a normal (not Gatsby) remark plugin for graphviz, see [remark-graphviz](https://www.npmjs.com/package/remark-graphviz) which inspired this plugin. + + diff --git a/packages/gatsby-remark-graphviz/package.json b/packages/gatsby-remark-graphviz/package.json new file mode 100644 index 0000000000000..2c05f61a108cb --- /dev/null +++ b/packages/gatsby-remark-graphviz/package.json @@ -0,0 +1,43 @@ +{ + "name": "gatsby-remark-graphviz", + "description": "Processes graphviz code blocks and renders to SVG using viz.js", + "version": "1.0.0-alpha.1", + "author": "Anthony Marcar ", + "bugs": { + "url": "https://github.com/gatsbyjs/gatsby/issues" + }, + "dependencies": { + "@babel/runtime": "7.0.0-beta.52", + "unist-util-visit": "^1.4.0", + "viz.js": "^2.0.0" + }, + "devDependencies": { + "@babel/cli": "7.0.0-beta.52", + "@babel/core": "7.0.0-beta.52", + "cross-env": "^5.1.4", + "rimraf": "^2.6.2", + "unist-util-find": "^1.0.1" + }, + "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-graphviz#readme", + "keywords": [ + "gatsby", + "gatsby-plugin", + "markdown", + "remark", + "graphviz", + "dot", + "circo", + "graphs" + ], + "license": "MIT", + "main": "index.js", + "peerDependencies": { + "gatsby": ">2.0.0-alpha" + }, + "repository": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-graphviz", + "scripts": { + "build": "babel --out-dir . src", + "prepare": "cross-env NODE_ENV=production npm run build", + "watch": "babel -w src --out-dir . --ignore **/__tests__" + } +} diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js new file mode 100644 index 0000000000000..53f67b54d59ba --- /dev/null +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -0,0 +1,34 @@ +const visit = require(`unist-util-visit`) +const Viz = require('viz.js'); +const { Module, render } = require(`viz.js/full.render.js`) + +const validLanguages = ['dot', 'circo']; + +module.exports = async ({ markdownAST }, pluginOptions = {}) => { + return new Promise((resolve, reject) => { + + visit(markdownAST, `code`, node => { + const { lang, value } = node + + // If this codeblock is not a known graphviz format, bail. + if (!validLanguages.includes(lang)) { + return node; + } + + const viz = new Viz({ Module, render }) + + viz.renderString(value, { lang }) + .then(svgString => { + node.type = `html` + node.value = svgString + resolve(markdownAST) + }) + .catch(error => { + console.log(error) + reject() + }) + + }) + + }) +} From 30dc68b7b8625c949eb09e18432470458add4722 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Wed, 15 Aug 2018 20:46:25 +1000 Subject: [PATCH 02/39] replace README SVG with image to guarantee it will display --- packages/gatsby-remark-graphviz/README.md | 43 +----------------- .../gatsby-remark-graphviz/example-graph.png | Bin 0 -> 27422 bytes 2 files changed, 1 insertion(+), 42 deletions(-) create mode 100644 packages/gatsby-remark-graphviz/example-graph.png diff --git a/packages/gatsby-remark-graphviz/README.md b/packages/gatsby-remark-graphviz/README.md index 55df972d7c504..11a08770ecb5d 100644 --- a/packages/gatsby-remark-graphviz/README.md +++ b/packages/gatsby-remark-graphviz/README.md @@ -36,48 +36,7 @@ Then, add `dot` code blocks to your markdown. E.g Which will be rendered using viz.js and the output html will replace the code block with the actual SVG. - - -graphname - - - -a - -a - - - -b - -b - - - -a->b - - - - - -c - -c - - - -a->c - - - - - -b->c - - - - - +![](./example-graph.png) ## Alternatives diff --git a/packages/gatsby-remark-graphviz/example-graph.png b/packages/gatsby-remark-graphviz/example-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..efc9281df16190ef44db33ed8067c70db65f31d5 GIT binary patch literal 27422 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 literal 0 HcmV?d00001 From d9b9caa142744de54818fa5545c8255a49b1e3ba Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 07:35:34 +1000 Subject: [PATCH 03/39] Replace README rendered image with rendered svg --- packages/gatsby-remark-graphviz/README.md | 2 +- .../gatsby-remark-graphviz/example-graph.png | Bin 27422 -> 0 bytes .../gatsby-remark-graphviz/rendered-graph.svg | 46 ++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) delete mode 100644 packages/gatsby-remark-graphviz/example-graph.png create mode 100644 packages/gatsby-remark-graphviz/rendered-graph.svg diff --git a/packages/gatsby-remark-graphviz/README.md b/packages/gatsby-remark-graphviz/README.md index 11a08770ecb5d..7c1b5606377f5 100644 --- a/packages/gatsby-remark-graphviz/README.md +++ b/packages/gatsby-remark-graphviz/README.md @@ -36,7 +36,7 @@ Then, add `dot` code blocks to your markdown. E.g Which will be rendered using viz.js and the output html will replace the code block with the actual SVG. -![](./example-graph.png) +![rendered-graph](./rendered-graph.svg) ## Alternatives diff --git a/packages/gatsby-remark-graphviz/example-graph.png b/packages/gatsby-remark-graphviz/example-graph.png deleted file mode 100644 index efc9281df16190ef44db33ed8067c70db65f31d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27422 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 diff --git a/packages/gatsby-remark-graphviz/rendered-graph.svg b/packages/gatsby-remark-graphviz/rendered-graph.svg new file mode 100644 index 0000000000000..83d822177e7cb --- /dev/null +++ b/packages/gatsby-remark-graphviz/rendered-graph.svg @@ -0,0 +1,46 @@ + + + + + +graphname + + + +a + +a + + + +b + +b + + + +a->b + + + + + +c + +c + + + +a->c + + + + + +b->c + + + + + From 13b8d03a6ca018022a340ddca2ffbcb72581576f Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 07:47:25 +1000 Subject: [PATCH 04/39] formatting/linting --- packages/gatsby-remark-graphviz/README.md | 2 -- packages/gatsby-remark-graphviz/src/index.js | 19 +++++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/gatsby-remark-graphviz/README.md b/packages/gatsby-remark-graphviz/README.md index 7c1b5606377f5..66d7f268bc792 100644 --- a/packages/gatsby-remark-graphviz/README.md +++ b/packages/gatsby-remark-graphviz/README.md @@ -43,5 +43,3 @@ Which will be rendered using viz.js and the output html will replace the code bl If you want a broader range of drawing options, checkout [gatsby-remark-draw](https://www.npmjs.com/package/gatsby-remark-draw). It provides SvgBobRus, Graphviz, and Mermaid, but note that you must have these already installed on your system If you're simply looking for a normal (not Gatsby) remark plugin for graphviz, see [remark-graphviz](https://www.npmjs.com/package/remark-graphviz) which inspired this plugin. - - diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js index 53f67b54d59ba..b28b3f99f0f84 100644 --- a/packages/gatsby-remark-graphviz/src/index.js +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -1,23 +1,23 @@ const visit = require(`unist-util-visit`) -const Viz = require('viz.js'); +const Viz = require(`viz.js`) const { Module, render } = require(`viz.js/full.render.js`) -const validLanguages = ['dot', 'circo']; - -module.exports = async ({ markdownAST }, pluginOptions = {}) => { - return new Promise((resolve, reject) => { +const validLanguages = [`dot`, `circo`] +module.exports = async ({ markdownAST }, pluginOptions = {}) => + new Promise((resolve, reject) => { visit(markdownAST, `code`, node => { const { lang, value } = node - + // If this codeblock is not a known graphviz format, bail. if (!validLanguages.includes(lang)) { - return node; + return node } const viz = new Viz({ Module, render }) - viz.renderString(value, { lang }) + viz + .renderString(value, { lang }) .then(svgString => { node.type = `html` node.value = svgString @@ -28,7 +28,6 @@ module.exports = async ({ markdownAST }, pluginOptions = {}) => { reject() }) + return null }) - }) -} From 0c2f3c4327a008c1c535066b71af70cd27fda3c7 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 08:04:44 +1000 Subject: [PATCH 05/39] use engine render option instead of lang --- packages/gatsby-remark-graphviz/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js index b28b3f99f0f84..ada6d9740cce7 100644 --- a/packages/gatsby-remark-graphviz/src/index.js +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -17,7 +17,7 @@ module.exports = async ({ markdownAST }, pluginOptions = {}) => const viz = new Viz({ Module, render }) viz - .renderString(value, { lang }) + .renderString(value, { engine: lang }) .then(svgString => { node.type = `html` node.value = svgString From d0fbe30b4aa16014917d6aaa114fed6b9b1a7a58 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 15:28:00 +1000 Subject: [PATCH 06/39] fixed bug where top level promise callback was never resolved Was causing gatsby build to freeze on createPages --- packages/gatsby-remark-graphviz/src/index.js | 54 ++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js index ada6d9740cce7..4b33bcbf80852 100644 --- a/packages/gatsby-remark-graphviz/src/index.js +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -2,32 +2,34 @@ const visit = require(`unist-util-visit`) const Viz = require(`viz.js`) const { Module, render } = require(`viz.js/full.render.js`) +const viz = new Viz({ Module, render }) + const validLanguages = [`dot`, `circo`] -module.exports = async ({ markdownAST }, pluginOptions = {}) => - new Promise((resolve, reject) => { - visit(markdownAST, `code`, node => { - const { lang, value } = node - - // If this codeblock is not a known graphviz format, bail. - if (!validLanguages.includes(lang)) { - return node - } - - const viz = new Viz({ Module, render }) - - viz - .renderString(value, { engine: lang }) - .then(svgString => { - node.type = `html` - node.value = svgString - resolve(markdownAST) - }) - .catch(error => { - console.log(error) - reject() - }) - - return null - }) +module.exports = async ({ markdownAST }, pluginOptions = {}) => { + let codeNodes = [] + + visit(markdownAST, `code`, node => { + // Only act on languages supported by graphviz + if (validLanguages.includes(node.lang)) { + codeNodes.push(node) + } + return node }) + + await Promise.all( + codeNodes.map(async node => { + const { value, lang } = node + + // Perform actual render + const svgString = await viz.renderString(value, { engine: lang }) + + // Mutate the current node. Converting from a code block to + // HTML (with svg content) + node.type = `html` + node.value = svgString + + return node + }) + ) +} From 998ff5ccf7c0a192412a590f32e81598d55033bf Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 15:34:11 +1000 Subject: [PATCH 07/39] fixed bug where top level promise callback was never resolved Was causing gatsby build to freeze on createPages --- packages/gatsby-remark-graphviz/src/index.js | 52 ++++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js index bada225b17006..4b33bcbf80852 100644 --- a/packages/gatsby-remark-graphviz/src/index.js +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -6,30 +6,30 @@ const viz = new Viz({ Module, render }) const validLanguages = [`dot`, `circo`] -module.exports = async ({ markdownAST }, pluginOptions = {}) => - new Promise((resolve, reject) => { - visit(markdownAST, `code`, node => { - const { lang, value } = node - - // If this codeblock is not a known graphviz format, bail. - if (!validLanguages.includes(lang)) { - return node - } - - const viz = new Viz({ Module, render }) - - viz - .renderString(value, { engine: lang }) - .then(svgString => { - node.type = `html` - node.value = svgString - resolve(markdownAST) - }) - .catch(error => { - console.log(error) - reject() - }) - - return null - }) +module.exports = async ({ markdownAST }, pluginOptions = {}) => { + let codeNodes = [] + + visit(markdownAST, `code`, node => { + // Only act on languages supported by graphviz + if (validLanguages.includes(node.lang)) { + codeNodes.push(node) + } + return node }) + + await Promise.all( + codeNodes.map(async node => { + const { value, lang } = node + + // Perform actual render + const svgString = await viz.renderString(value, { engine: lang }) + + // Mutate the current node. Converting from a code block to + // HTML (with svg content) + node.type = `html` + node.value = svgString + + return node + }) + ) +} From 6754bbe00f3a1d0a139832560a33cd43d739f496 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 15:48:49 +1000 Subject: [PATCH 08/39] add detail about prismjs to remark-graphviz --- packages/gatsby-remark-graphviz/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/gatsby-remark-graphviz/README.md b/packages/gatsby-remark-graphviz/README.md index 66d7f268bc792..238f787a6bbf0 100644 --- a/packages/gatsby-remark-graphviz/README.md +++ b/packages/gatsby-remark-graphviz/README.md @@ -38,6 +38,10 @@ Which will be rendered using viz.js and the output html will replace the code bl ![rendered-graph](./rendered-graph.svg) +## Caveats + +In your gatsby-config.js, make sure you place this plugin before other remark plugins that modify code blocks (like prism). + ## Alternatives If you want a broader range of drawing options, checkout [gatsby-remark-draw](https://www.npmjs.com/package/gatsby-remark-draw). It provides SvgBobRus, Graphviz, and Mermaid, but note that you must have these already installed on your system From 37e77d908fb41adb13d6f3dc9b8fe894e4af6e92 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 19:28:13 +1000 Subject: [PATCH 09/39] add schema behind the scenes --- docs/docs/schema-behind-the-scenes.md | 71 +++++++++++++++++++++++++++ www/gatsby-config.js | 1 + 2 files changed, 72 insertions(+) create mode 100644 docs/docs/schema-behind-the-scenes.md diff --git a/docs/docs/schema-behind-the-scenes.md b/docs/docs/schema-behind-the-scenes.md new file mode 100644 index 0000000000000..144412432635c --- /dev/null +++ b/docs/docs/schema-behind-the-scenes.md @@ -0,0 +1,71 @@ +--- +title: Schema +--- + +## Schema + +Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. We must infer a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how that occurs. + +At a high level, here are the steps performed during this stage: + +```dot +"Node" -> ExampleValue; +``` + +1. Group all nodes by type (`node.internal.type`). E.g `File`, `ImageSharp`. Then, for each type, perform the following steps: +1. Each plugin has the chance to provide custom field Types for their node type via the [setFieldsOnGraphQLNodeType]() API. These APIS are run now +1. Convert each of these custom field types into an input filter type. +1. Build an example object from all the existing nodes. +1. For each example value, generate a GraphQL Type (or use a scalar), and an input filter type. So we can query this node by any of its fields +1. Create a `GraphQLObjectType` (the actual GraphQL Type object) +1. Create GraphQL types for connections between fields. This includes pagination, sorting, and limits for collections of nodes. +1. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` + +See below for details on each of the above steps. + +### 1. Group all nodes by type + +Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g `PostsJson` for a `posts.json` file. + +During the schema generation phase, we must generate a [GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype) for each of these `node.internal.type`s. The below steps are run for each unique type. + +### 2. Plugins create custom fields + +Gatsby infers GraphQL Types from the fields on the sourced and transformed nodes. But before that, we allow plugins to create their own custom fields. For example, `source-filesystem` creates a [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L11) field that when resolved, will copy the file into the `public/static` directory and return the new path. + +To declare custom fields, plugins implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API and apply the change only to types that they care about (e.g source-filesystem [only proceeds if type.name = `File`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L6). During schema generation, Gatsby will call this API, allowing the plugin to declare these custom fields, [which are returned](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L151) to the main schema process. + +### 3. Convert custom plugin fields into Input Filter Types + +For each of the custom fields declared by the plugins in the previous step, we create a [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). Depending on the type of the field, we also generate a variety of input filters to assist with querying. For example, for `source-filesystem`, [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L12) is a string. Therefore we might want to query the publicURL by a string that is equal to, not equal to, matches a regex etc. These are defined in the [scalarFilterMap](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js#L84). + +### 4. Build an example object + +The next step is to start inferring the GraphQL equivelent of the node's fields. To do this, we [build a super object](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L305) by merging all instances of the type's nodes from redux. During this step, we can detect if nodes have inconsistent types for their fields. E.g node1.foo = "string". node2.foo = 5. And raise an error if so. + +### 5. Generate GraphQL Type from Example + +Now that we've built an object with examples of all the type's possible fields, we can [infer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L71) the actual GraphQL equivelent types. This simply maps javascript types to GraphQL types. E.g `typeof(value) = int` would result in `GraphQLInt`. If the type is an object, we recurse down into its structure, inferring types from each of its sub fields. + +### 6. Create a GraphqlObjectType + +We're now ready to create our final Graphql Type, known as a [ProcessedType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). This includes all inferred fields from the example object, merged with all the fields created from the plugins. It also includes the `Node Interface`, which provides reuseuable fields such as id, parent and children. We also create the same Input Types like in the plugin so that we can query by a node's field by regex, equality etc. + +### 7. Create Connection Fields + +We've built a GraphQL type where we can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? + +This occurs in [build-connection-fields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L17). We create useful fields such as `totalCount`, `distinct`, `group`, `next`, `prev`, `edges`, `sort`, etc. We end up with a GraphQL Type that can be queried for all of its nodes. For example : + +```javascript +allMarkdownRemark( + sort: {}, + limit: 100, + filter: { fileAbsolutePath: { ne: null} } +) +``` + +### 8. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` + +We're done! We've created GraphQL Types by inferring from node's examples and combining them with custom fields declared by plugins. We've also created Types that can be queried for all instances of that node's types. We now merge these types into a single collection which is used to create a new [GraphQLSchema](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/index.js#L23), which is then saved to `redux.schema`. + diff --git a/www/gatsby-config.js b/www/gatsby-config.js index f8234814ed70a..07a481973030c 100644 --- a/www/gatsby-config.js +++ b/www/gatsby-config.js @@ -59,6 +59,7 @@ module.exports = { resolve: `gatsby-transformer-remark`, options: { plugins: [ + `gatsby-remark-graphviz`, { resolve: `gatsby-remark-images`, options: { From dfad95776a9d24b897571c81df398a81eacb7dac Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 19:34:53 +1000 Subject: [PATCH 10/39] add error handling to viz.js execution --- packages/gatsby-remark-graphviz/src/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/gatsby-remark-graphviz/src/index.js b/packages/gatsby-remark-graphviz/src/index.js index 4b33bcbf80852..02d47505c4bc1 100644 --- a/packages/gatsby-remark-graphviz/src/index.js +++ b/packages/gatsby-remark-graphviz/src/index.js @@ -21,13 +21,18 @@ module.exports = async ({ markdownAST }, pluginOptions = {}) => { codeNodes.map(async node => { const { value, lang } = node - // Perform actual render - const svgString = await viz.renderString(value, { engine: lang }) - - // Mutate the current node. Converting from a code block to - // HTML (with svg content) - node.type = `html` - node.value = svgString + try { + // Perform actual render + const svgString = await viz.renderString(value, { engine: lang }) + + // Mutate the current node. Converting from a code block to + // HTML (with svg content) + node.type = `html` + node.value = svgString + } catch (error) { + console.log(`Error during viz.js execution. Leaving code block unchanged`) + console.log(error) + } return node }) From 0b714c29e61b5f7a4bc9e0685f7f377aa8bdbb69 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 16 Aug 2018 20:08:58 +1000 Subject: [PATCH 11/39] add first graph --- docs/docs/schema-behind-the-scenes.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/docs/schema-behind-the-scenes.md b/docs/docs/schema-behind-the-scenes.md index 144412432635c..dc201090c719b 100644 --- a/docs/docs/schema-behind-the-scenes.md +++ b/docs/docs/schema-behind-the-scenes.md @@ -9,7 +9,26 @@ Once the nodes have been sourced and transformed, the next step is to generate t At a high level, here are the steps performed during this stage: ```dot -"Node" -> ExampleValue; +digraph graphname { + graph [labelloc="t" label=""]; + "cfd" [ style = "filled" label = "Custom Field Declarations + { type, args, resolve }" ]; + "cfiot" [ label = "Custom Fields InputObjectType + { args: lt, gt, eq, re }" ]; + "cf" [ label = "ConnectionFields + e.g allMarkdownRemark()" ]; + "Plugin" -> "Node" [ label = "Creates node with type" ]; + "Node" -> "exampleValue" [ label = "all nodes of type" ]; + "exampleValue" -> "Node Fields InputObjectType"; + "Plugin" -> cfd [ label = "setFieldsOnGraphQLNodeType" ]; + cfd -> cfiot; + "Node Fields InputObjectType" -> "ProcessedType"; + cfiot -> "ProcessedType"; + "Node Interface" -> "ProcessedType"; + "ProcessedType" -> cf; + "ProcessedType" -> "GraphQLSchema" [ label = "all types" ]; + cf -> "GraphQLSchema"; +} ``` 1. Group all nodes by type (`node.internal.type`). E.g `File`, `ImageSharp`. Then, for each type, perform the following steps: From 884a132d5665e19bdce04bc39e07146c938cd716 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 17 Aug 2018 09:32:04 +1000 Subject: [PATCH 12/39] moar behind the scenes docs --- docs/docs/learning-gatsbys-internals.md | 7 ++++ docs/docs/node-creation-behind-the-scenes.md | 3 ++ ...=> schema-generation-behind-the-scenes.md} | 39 +++++++++++-------- www/src/data/sidebars/doc-links.yaml | 8 ++++ 4 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 docs/docs/learning-gatsbys-internals.md create mode 100644 docs/docs/node-creation-behind-the-scenes.md rename docs/docs/{schema-behind-the-scenes.md => schema-generation-behind-the-scenes.md} (84%) diff --git a/docs/docs/learning-gatsbys-internals.md b/docs/docs/learning-gatsbys-internals.md new file mode 100644 index 0000000000000..9449e084f8492 --- /dev/null +++ b/docs/docs/learning-gatsbys-internals.md @@ -0,0 +1,7 @@ +--- +title: Learning Gatsby's Internals +--- + +This section describes how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby. If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. + + diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md new file mode 100644 index 0000000000000..d541c48b02378 --- /dev/null +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -0,0 +1,3 @@ +--- +title: Node Creation +--- diff --git a/docs/docs/schema-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md similarity index 84% rename from docs/docs/schema-behind-the-scenes.md rename to docs/docs/schema-generation-behind-the-scenes.md index dc201090c719b..57b374dbe183c 100644 --- a/docs/docs/schema-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -1,5 +1,5 @@ --- -title: Schema +title: Schema Generation --- ## Schema @@ -10,24 +10,29 @@ At a high level, here are the steps performed during this stage: ```dot digraph graphname { - graph [labelloc="t" label=""]; - "cfd" [ style = "filled" label = "Custom Field Declarations - { type, args, resolve }" ]; - "cfiot" [ label = "Custom Fields InputObjectType + "Node" [ label = "Node + e.g type = File" ]; + "rnodes" [ label = "redux.nodes" ]; + "cfd" [ label = "Custom Field Declarations + e.g source-filesystem: publicURL()" ]; + "cfiot" [ label = "Custom Fields InputObjectType { args: lt, gt, eq, re }" ]; - "cf" [ label = "ConnectionFields + "cf" [ label = "ConnectionFields e.g allMarkdownRemark()" ]; + "nfiot" [ label = "Node Fields InputObjectType" ]; + "pt" [ label = "ProcessedNodeType" ]; "Plugin" -> "Node" [ label = "Creates node with type" ]; - "Node" -> "exampleValue" [ label = "all nodes of type" ]; - "exampleValue" -> "Node Fields InputObjectType"; - "Plugin" -> cfd [ label = "setFieldsOnGraphQLNodeType" ]; - cfd -> cfiot; - "Node Fields InputObjectType" -> "ProcessedType"; - cfiot -> "ProcessedType"; - "Node Interface" -> "ProcessedType"; - "ProcessedType" -> cf; - "ProcessedType" -> "GraphQLSchema" [ label = "all types" ]; - cf -> "GraphQLSchema"; + "Node" -> "rnodes"; + "rnodes" -> "exampleValue" [ label = "filtered by Node Type" ]; + "exampleValue" -> "nfiot"; + "Plugin" -> "cfd" [ label = "setFieldsOnGraphQLNodeType" ]; + "cfd" -> "cfiot"; + "nfiot" -> "pt"; + "cfiot" -> "pt"; + "Node Interface" -> "pt"; + "pt" -> "cf"; + "pt" -> "GraphQLSchema" [ label = "all types" ]; + "cf" -> "GraphQLSchema"; } ``` @@ -68,7 +73,7 @@ Now that we've built an object with examples of all the type's possible fields, ### 6. Create a GraphqlObjectType -We're now ready to create our final Graphql Type, known as a [ProcessedType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). This includes all inferred fields from the example object, merged with all the fields created from the plugins. It also includes the `Node Interface`, which provides reuseuable fields such as id, parent and children. We also create the same Input Types like in the plugin so that we can query by a node's field by regex, equality etc. +We're now ready to create our final Graphql Type, known as a [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). This includes all inferred fields from the example object, merged with all the fields created from the plugins. It also includes the `Node Interface`, which provides reuseuable fields such as id, parent and children. We also create the same Input Types like in the plugin so that we can query by a node's field by regex, equality etc. ### 7. Create Connection Fields diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 5387a66f63f33..9663e0ca9e59f 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -251,6 +251,14 @@ link: /docs/prpl-pattern/ - title: Querying data with GraphQL link: /docs/querying-with-graphql/ +- title: Behind The Scenes + items: + - title: Learning Gatsbys Internals + link: /docs/learning-gatsbys-internals + - title: Node Creation + link: /docs/node-creation-behind-the-scenes/ + - title: Schema Generation + link: /docs/schema-generation-behind-the-scenes/ - title: Advanced Tutorials items: - title: Making a site with user authentication* From 1b7f3294fcd12ebc6b5e4e6900f2cc4ddb53f663 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 17 Aug 2018 09:44:09 +1000 Subject: [PATCH 13/39] add more behind the scenes sections --- docs/docs/build-caching.md | 4 + docs/docs/data-storage-redux.md | 4 + docs/docs/node-creation-behind-the-scenes.md | 104 +++++++++++++++++++ www/src/data/sidebars/doc-links.yaml | 4 + 4 files changed, 116 insertions(+) create mode 100644 docs/docs/build-caching.md create mode 100644 docs/docs/data-storage-redux.md diff --git a/docs/docs/build-caching.md b/docs/docs/build-caching.md new file mode 100644 index 0000000000000..04c73e615a3b6 --- /dev/null +++ b/docs/docs/build-caching.md @@ -0,0 +1,4 @@ +--- +title: Build Caching +--- + diff --git a/docs/docs/data-storage-redux.md b/docs/docs/data-storage-redux.md new file mode 100644 index 0000000000000..16f5128de2503 --- /dev/null +++ b/docs/docs/data-storage-redux.md @@ -0,0 +1,4 @@ +--- +title: Data Storage (Redux) +--- + diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index d541c48b02378..231fdc2455ffd 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -1,3 +1,107 @@ --- title: Node Creation --- + +#### early in the build + +Early in the bootstrap phase, we load all the configured plugins (and internal plugins) for the site. These are saved into redux under: + +- **flattenedPlugins**: All plugins in the system, each contains the following information: + - **resolve**: path to the plugin's directory + - **id**: String concatenation of 'Plugin ' and the name of the plugin. E.g 'Plugin query-runner' + - **name**: The name of the plugin. E.g 'query-runner' + - **version**: The version as per the package.json. Or one is generated from the file's hash + - **pluginOptions**: Plugin options as specified in `gatsby-config.js` + - **nodeAPIs**: A list of node APIs that this plugin implements. E.g [ 'sourceNodes', ...] + - **browserAPIs**: List of browser APIs that this plugin implements + - **ssrAPIs**: List of SSR APIs that this plugin implements +- **api-to-plugins**: A map of apis to to all the plugins that implement it + +#### apiRunInstance + +Some API calls can take a while to finish. So every time an API is run, we create an object called `apiRunInstance` to track it. It contains the following fields: + +- **api**: The API we're running. E.g `onCreateNode` +- **args**: Any arguments passed to `api-runner-node`. E.g a node object +- **pluginSource**: optional name of the plugin that initiated the original call. +- **resolve**: promise resolve callback to be called when the API has finished running +- **startTime**: time that the API run was started +- **traceId**: optional args.traceId provided if API will result in further API calls (see below) + +We immediately place this object into an `apisRunningById` Map. + +#### waiting for all plugins to run + +Next, we run all the plugins that implement the API. We run them concurrently by using the [map-series]() library. We then wait for all the `apiRunInstance.resolve` functions to be called, resulting an an array of results. We then call the original `api-runner-node` promise's resolve function with the results of the call, and we're done. + +#### running each plugin + +Each plugin implements an API by implementing the APIs function as defined in [api-node-docs.js](). So to run the plugin, we first `require` it via its `plugin.resolve` field. And then we call its `plugin[api]` function with the originally provided args. If the plugin provides a callback, we return a promise that resolves when that callback is resolved. Otherwise, we run the plugin synchronously and return it in a new promise. + +#### closing over plugin/traceId for actions + +In addition to the primary arguments, most Gatsby actions allow for an additional `plugin` and `traceId` field. E.g in [createPage](). Ordinarily, a plugin that calls these actions would have to add extra boilerplate to pass this plugina and traceId to every single function. To make the developer's life easier, Gatsby redefines the actions that are passed to the implementing plugin so that the `plugin` and `traceId` fields are included automatically (if not explicitly provided by the implementing plugin). + +#### using traceID to await downstream API calls + +The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes]()) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all children to complete. The mechanism for this is the `traceId`. + +The traceID is passed as an argument to the original API runner. Using the action rebinding mentioned above, the traceId is passed through to all action calls, which are then automatically passed to downstream API calls. You can see the traceId being passed through to the api runner call via the [plugin-runner.js](). + +### Node Creation/Transformation/Storage + +Nodes are created by calling the [createNode]() action. Nodes can be any object, as long as it doesn't contain certain [reserved fields](). + +A node is stored in redux under `state.nodes` as a map of the node ID to the actual node object. + +TODO: What's the deal with ___NODE. Including in infer-graphql-input-fields. + +#### Sourcing Nodes + +Nodes are created in Gatsby by calling [createNode](). This happens primarily in the [sourceNodes]() bootstrap phase, though can happen in other places to (TODO: Find places). Nodes created during this phase are top level nodes. I.e, they have no parent. + +#### Parent/child relationship + +TODO: Must explicitly set parent: field when creating node? + +TODO: used in data-tree-utils. Might make more sense when discussing schema? Used in build-node-types when resolving parent. Also used when finding rootNodeAncestor. and in source filesystem, create-file-path + +A node can be linked to another node as a parent or child. To create this link, you must explicitly call the `createParentChildLink`. All this does is add the child node's ID to the parent's `children` array. And it then persist the parent back to `redux.nodes`. + +TODO: [internal-data-bridge]() plugin? + +Note, a `node.owner` refers to the plugin that created it, not the parent node. + +#### Fresh/stale nodes + +Every time a build is re-run, there is a chance that a node that exists in the redux store no longer exists in the original data source. E.g a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby. + +To track this, there is a `redux.nodesTouched` store that tracks whether a particular node ID has been touched. This occurs whenever a node is created (by handled [CREATE_NODE]()), or an explicit call to [touchNode](). + +When a source-nodes plugin runs, it generally recreates nodes (which touches them too). But in some cases, such as [transformer-screenshot](), a node might not change, but we still want to keep it around for the build. In these cases, we could have to explicitly call `touchNode`. + +Any nodes that aren't touched by the end of source-nodes, are deleted. This is performed by a diff on the `redux.nodesTouched` and `redux.nodes` collections. + +#### Changing a node's fields + +From a site developer's point of view, nodes are immutable. In the sense that if you simply change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to redux via an action. + +So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. + +#### Internal Data Bridge + +On sourceNodes, this plugin creates: + +- a default SitePage node for `/dev-404-page/`. +- a Node for each of the plugins previously loaded earlier in bootstrap. This node includes the contents of they plugin's package.json. +- a `Site` node that is the contents of `gatsby-config.js`, which was previously loaded into `redux.config`. +- Sets up a watcher to re-require `gatsby-config.js` and re-create the node if it ever changes + +on createPage: + +- Create a new Node that is the same as the original page, but with type = `SitePage` and id = `SitePage ${path}` + +And when a `DELETE_PAGE` event is emitted, it deletes the corresponding `SitePage` using [deleteNode](). + +TODO: Where is all this used? Only reference to SitePage is in [gatsby-plugin-sitemap](). Talk about how this overall fits into the Gatsby build process + diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 9663e0ca9e59f..acd47ca83a279 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -257,8 +257,12 @@ link: /docs/learning-gatsbys-internals - title: Node Creation link: /docs/node-creation-behind-the-scenes/ + - title: Data Storage (Redux) + link: /docs/data-storage-redux/ - title: Schema Generation link: /docs/schema-generation-behind-the-scenes/ + - title: Build Caching + link: /docs/build-caching/ - title: Advanced Tutorials items: - title: Making a site with user authentication* From 87567fea80073e910c78a26282e22299770c908e Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 17 Aug 2018 12:31:17 +1000 Subject: [PATCH 14/39] separated plugins/apis docs --- docs/docs/how-plugins-apis-are-run.md | 81 ++++++++++++++++++++ docs/docs/learning-gatsbys-internals.md | 4 +- docs/docs/node-creation-behind-the-scenes.md | 58 ++------------ www/src/data/sidebars/doc-links.yaml | 2 + 4 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 docs/docs/how-plugins-apis-are-run.md diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md new file mode 100644 index 0000000000000..24b66bbe8de84 --- /dev/null +++ b/docs/docs/how-plugins-apis-are-run.md @@ -0,0 +1,81 @@ +--- +title: How APIs/plugins are run +--- + +For most sites, plugins take up the majority of the build time. So what's really happening when APIs are called? + +Note, this section only explains how `gatsby-node` plugins are run. Not browser or ssr plugins. + +## Early in the build + +Early in the bootstrap phase, we [load all the configured plugins](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/bootstrap/load-plugins/index.js#L40) (and internal plugins) for the site. These are saved into redux under `flattenedPlugins`. Each plugin in redux contains the following fields: + +- **resolve**: absolute path to the plugin's directory +- **id**: String concatenation of 'Plugin ' and the name of the plugin. E.g `Plugin query-runner` +- **name**: The name of the plugin. E.g `query-runner` +- **version**: The version as per the package.json. Or if it is a site plugin, one is generated from the file's hash +- **pluginOptions**: Plugin options as specified in [gatsby-config.js](/docs/gatsby-config/) +- **nodeAPIs**: A list of node APIs that this plugin implements. E.g `[ 'sourceNodes', ...]` +- **browserAPIs**: List of browser APIs that this plugin implements +- **ssrAPIs**: List of SSR APIs that this plugin implements + +In addition, we also create a lookup from api to the plugins that implement it and save this to redux as `api-to-plugins`. This is implemented in [load-plugins/validate.js](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/bootstrap/load-plugins/validate.js#L106) + +## General Flow + +## apiRunInstance + +Some API calls can take a while to finish. So every time an API is run, we create an object called [apiRunInstance](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L179) to track it. It contains the following notable fields: + +- **id**: Unique identifier generated based on type of API +- **api**: The API we're running. E.g `onCreateNode` +- **args**: Any arguments passed to `api-runner-node`. E.g a node object +- **pluginSource**: optional name of the plugin that initiated the original call +- **resolve**: promise resolve callback to be called when the API has finished running +- **startTime**: time that the API run was started +- **span**: opentracing span for tracing builds +- **traceId**: optional args.traceId provided if API will result in further API calls (see below) + +We immediately place this object into an `apisRunningById` Map. + +## Running each plugin + +Next, we filter all `flattenedPlugins` down to those that implement the API we're trying to run. For each plugin, we require its `gatsby-node.js` and call its exported API function. E.g if API was `sourceNodes`, it would result in a call to `gatsbyNode['sourceNodes'](...apiCallargs)`. + +## Injected arguments + +API implementations are passed a variety of useful [actions](/docs/actions/) and other interesting functions/objects. These arguments are [created](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L94) each time a plugin is run for an API, which allows us to rebind actions with default information. + +All actions take 3 arguments: + +1. The core information required by the action. E.g for [createNode](/docs/actions/#createNode), we must pass a node +2. The plugin that is calling this action. E.g `createNode` uses this to assign the owner of the new node +3. An object with misc action options: + - **traceId**: See below + - **parentSpan**: opentracing span + +Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind inject actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. + +## Waiting for all plugins to run + +Each plugin is run inside a [map-series](https://www.npmjs.com/package/map-series) promise, which allows them to be executed concurrently. Once all plugins have finished running, we remove them from [apisRunningById](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L246) and fire a `API_RUNNING_QUEUE_EMPTY` event. This in turn, results in any dirty pages being recreated, as well as their queries. Finally, the results are returned. + +## Using traceID to await downstream API calls + +The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes]()) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. + +1. The traceID is passed as an argument to the original API runner. E.g + + ```javascript + apiRunner(`sourceNodes`, { + traceId: `initial-sourceNodes`, + waitForCascadingActions: true, + parentSpan: parentSpan, + }) + ``` +1. We keep track of the number of API calls with this traceId in the [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L139) Map. On this first invocation, it will be set to `1`. +1. Using the action rebinding mentioned [above](#injected-arguments), the traceId is passed through to all action calls via the `actionOptions` object. +1. After reducing the Action, a global event is [emitted](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/index.js#L93) which includes the action information +1. For the `CREATE_NODE` and `CREATE_PAGE` events, we need to call the `onCreateNode` and `onCreatePage` APIs respectively. The [plugin-runner](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/plugin-runner.js) takes care of this. It also passes on the traceId from the Action back into the API call. +1. We're back in `api-runner-node.js` and can tie this new API call back to its original. So we increment the value of [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L218) for this traceId. +1. Now, whenever an API finishes running (when all its implementing plugins have finished), we decrement `apisRunningByTraceId[traceId]`. If the original API call included the `waitForCascadingActions` option, then we wait until `apisRunningByTraceId[traceId]` == 0 before resolving. diff --git a/docs/docs/learning-gatsbys-internals.md b/docs/docs/learning-gatsbys-internals.md index 9449e084f8492..66cc061d4c693 100644 --- a/docs/docs/learning-gatsbys-internals.md +++ b/docs/docs/learning-gatsbys-internals.md @@ -2,6 +2,8 @@ title: Learning Gatsby's Internals --- -This section describes how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby. If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. +Curious how Gatsby works under the hood? This section describes how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby. + +If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 231fdc2455ffd..4de60fa97a31d 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -2,53 +2,7 @@ title: Node Creation --- -#### early in the build - -Early in the bootstrap phase, we load all the configured plugins (and internal plugins) for the site. These are saved into redux under: - -- **flattenedPlugins**: All plugins in the system, each contains the following information: - - **resolve**: path to the plugin's directory - - **id**: String concatenation of 'Plugin ' and the name of the plugin. E.g 'Plugin query-runner' - - **name**: The name of the plugin. E.g 'query-runner' - - **version**: The version as per the package.json. Or one is generated from the file's hash - - **pluginOptions**: Plugin options as specified in `gatsby-config.js` - - **nodeAPIs**: A list of node APIs that this plugin implements. E.g [ 'sourceNodes', ...] - - **browserAPIs**: List of browser APIs that this plugin implements - - **ssrAPIs**: List of SSR APIs that this plugin implements -- **api-to-plugins**: A map of apis to to all the plugins that implement it - -#### apiRunInstance - -Some API calls can take a while to finish. So every time an API is run, we create an object called `apiRunInstance` to track it. It contains the following fields: - -- **api**: The API we're running. E.g `onCreateNode` -- **args**: Any arguments passed to `api-runner-node`. E.g a node object -- **pluginSource**: optional name of the plugin that initiated the original call. -- **resolve**: promise resolve callback to be called when the API has finished running -- **startTime**: time that the API run was started -- **traceId**: optional args.traceId provided if API will result in further API calls (see below) - -We immediately place this object into an `apisRunningById` Map. - -#### waiting for all plugins to run - -Next, we run all the plugins that implement the API. We run them concurrently by using the [map-series]() library. We then wait for all the `apiRunInstance.resolve` functions to be called, resulting an an array of results. We then call the original `api-runner-node` promise's resolve function with the results of the call, and we're done. - -#### running each plugin - -Each plugin implements an API by implementing the APIs function as defined in [api-node-docs.js](). So to run the plugin, we first `require` it via its `plugin.resolve` field. And then we call its `plugin[api]` function with the originally provided args. If the plugin provides a callback, we return a promise that resolves when that callback is resolved. Otherwise, we run the plugin synchronously and return it in a new promise. - -#### closing over plugin/traceId for actions - -In addition to the primary arguments, most Gatsby actions allow for an additional `plugin` and `traceId` field. E.g in [createPage](). Ordinarily, a plugin that calls these actions would have to add extra boilerplate to pass this plugina and traceId to every single function. To make the developer's life easier, Gatsby redefines the actions that are passed to the implementing plugin so that the `plugin` and `traceId` fields are included automatically (if not explicitly provided by the implementing plugin). - -#### using traceID to await downstream API calls - -The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes]()) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all children to complete. The mechanism for this is the `traceId`. - -The traceID is passed as an argument to the original API runner. Using the action rebinding mentioned above, the traceId is passed through to all action calls, which are then automatically passed to downstream API calls. You can see the traceId being passed through to the api runner call via the [plugin-runner.js](). - -### Node Creation/Transformation/Storage +# Node Creation/Transformation/Storage Nodes are created by calling the [createNode]() action. Nodes can be any object, as long as it doesn't contain certain [reserved fields](). @@ -56,11 +10,11 @@ A node is stored in redux under `state.nodes` as a map of the node ID to the act TODO: What's the deal with ___NODE. Including in infer-graphql-input-fields. -#### Sourcing Nodes +## Sourcing Nodes Nodes are created in Gatsby by calling [createNode](). This happens primarily in the [sourceNodes]() bootstrap phase, though can happen in other places to (TODO: Find places). Nodes created during this phase are top level nodes. I.e, they have no parent. -#### Parent/child relationship +## Parent/child relationship TODO: Must explicitly set parent: field when creating node? @@ -72,7 +26,7 @@ TODO: [internal-data-bridge]() plugin? Note, a `node.owner` refers to the plugin that created it, not the parent node. -#### Fresh/stale nodes +## Fresh/stale nodes Every time a build is re-run, there is a chance that a node that exists in the redux store no longer exists in the original data source. E.g a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby. @@ -82,13 +36,13 @@ When a source-nodes plugin runs, it generally recreates nodes (which touches the Any nodes that aren't touched by the end of source-nodes, are deleted. This is performed by a diff on the `redux.nodesTouched` and `redux.nodes` collections. -#### Changing a node's fields +## Changing a node's fields From a site developer's point of view, nodes are immutable. In the sense that if you simply change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to redux via an action. So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. -#### Internal Data Bridge +## Internal Data Bridge On sourceNodes, this plugin creates: diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index acd47ca83a279..b441546b7eec9 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -255,6 +255,8 @@ items: - title: Learning Gatsbys Internals link: /docs/learning-gatsbys-internals + - title: How APIS/Plugins Are Run + link: /docs/how-plugins-apis-are-run/ - title: Node Creation link: /docs/node-creation-behind-the-scenes/ - title: Data Storage (Redux) From c7fea22b4093de1dbd3b77af1a023665586e8c15 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 17 Aug 2018 12:56:26 +1000 Subject: [PATCH 15/39] APIs running diagram --- docs/docs/how-plugins-apis-are-run.md | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index 24b66bbe8de84..c22bebcac9865 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -62,6 +62,36 @@ Each plugin is run inside a [map-series](https://www.npmjs.com/package/map-serie ## Using traceID to await downstream API calls +```dot +digraph { + node [ shape="box" ]; + + "initialCall" [ label="apiRunner(`sourceNodes`, {\l traceId: `initial-sourceNodes`,\l waitForCascadingActions: true,\l parentSpan: parentSpan\l})\l " ]; + "apiRunner1" [ label="api-runner-node.js" ]; + "sourceNodes" [ label="plugin.SourceNodes()" ]; + "createNode" [ label="createNode(node)" ]; + "apisRunning" [ label="apisRunningByTraceId[traceId]" ]; + "createNodeReducer" [ label="CREATE_NODE reducer" ]; + "CREATE_NODE" [ label="CREATE_NODE event" ]; + "pluginRunner" [ label="plugin-runner.js" ]; + "onCreateNode" [ label="plugin.onCreateNode()" ]; + "apiRunnerOnCreateNode" [ label="apiRunner(`onCreateNode`, {\l node,\l traceId: action.traceId\l})\l "; ]; + "apiRunner2" [ label="api-runner-node.js" ]; + + "initialCall" -> "apiRunner1"; + "apiRunner1" -> "apisRunning" [ label="set to 1" ]; + "apiRunner1" -> "sourceNodes" [ label="call" ]; + "sourceNodes" -> "createNode" [ label="call (traceID implicitly passed)" ]; + "createNode" -> "createNodeReducer" [ label="triggers" ]; + "createNodeReducer" -> "CREATE_NODE" [ label="emits" ]; + "CREATE_NODE" -> "pluginRunner" [ label="handled by" ]; + "pluginRunner" -> "apiRunnerOnCreateNode"; + "apiRunnerOnCreateNode" -> "apiRunner2"; + "apiRunner2" -> "onCreateNode" [ label="call" ]; + "apiRunner2" -> "apisRunning" [ label="increment" ]; +} +``` + The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes]()) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. 1. The traceID is passed as an argument to the original API runner. E.g From 5e6d9d1095e04e3ee5ffd081d7bcaf0a870566fd Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 17 Aug 2018 13:01:31 +1000 Subject: [PATCH 16/39] disable unfinished docs --- docs/docs/build-caching.md | 4 ++++ docs/docs/data-storage-redux.md | 4 ++++ docs/docs/how-plugins-apis-are-run.md | 2 -- www/src/data/sidebars/doc-links.yaml | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/docs/build-caching.md b/docs/docs/build-caching.md index 04c73e615a3b6..73d2bdf319941 100644 --- a/docs/docs/build-caching.md +++ b/docs/docs/build-caching.md @@ -2,3 +2,7 @@ title: Build Caching --- +This is a stub. Help our community expand it. + +Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your +pull request gets accepted. diff --git a/docs/docs/data-storage-redux.md b/docs/docs/data-storage-redux.md index 16f5128de2503..e5eb099dc2a0b 100644 --- a/docs/docs/data-storage-redux.md +++ b/docs/docs/data-storage-redux.md @@ -2,3 +2,7 @@ title: Data Storage (Redux) --- +This is a stub. Help our community expand it. + +Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your +pull request gets accepted. diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index c22bebcac9865..a16782f62ed76 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -21,8 +21,6 @@ Early in the bootstrap phase, we [load all the configured plugins](https://githu In addition, we also create a lookup from api to the plugins that implement it and save this to redux as `api-to-plugins`. This is implemented in [load-plugins/validate.js](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/bootstrap/load-plugins/validate.js#L106) -## General Flow - ## apiRunInstance Some API calls can take a while to finish. So every time an API is run, we create an object called [apiRunInstance](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L179) to track it. It contains the following notable fields: diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index b441546b7eec9..a0c83b79f859a 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -259,11 +259,11 @@ link: /docs/how-plugins-apis-are-run/ - title: Node Creation link: /docs/node-creation-behind-the-scenes/ - - title: Data Storage (Redux) + - title: Data Storage (Redux)* link: /docs/data-storage-redux/ - title: Schema Generation link: /docs/schema-generation-behind-the-scenes/ - - title: Build Caching + - title: Build Caching* link: /docs/build-caching/ - title: Advanced Tutorials items: From 879fe11616c9e853c4efb4252b6733e9ede67814 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 21 Aug 2018 13:44:09 +1000 Subject: [PATCH 17/39] more internal docs --- docs/docs/how-plugins-apis-are-run.md | 8 ++-- docs/docs/node-creation-behind-the-scenes.md | 48 +++++++------------ .../schema-generation-behind-the-scenes.md | 25 +++++++++- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index a16782f62ed76..beb17a5db76ba 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -79,10 +79,10 @@ digraph { "initialCall" -> "apiRunner1"; "apiRunner1" -> "apisRunning" [ label="set to 1" ]; "apiRunner1" -> "sourceNodes" [ label="call" ]; - "sourceNodes" -> "createNode" [ label="call (traceID implicitly passed)" ]; - "createNode" -> "createNodeReducer" [ label="triggers" ]; - "createNodeReducer" -> "CREATE_NODE" [ label="emits" ]; - "CREATE_NODE" -> "pluginRunner" [ label="handled by" ]; + "sourceNodes" -> "createNode" [ label="call (traceID passed via doubleBind)" ]; + "createNode" -> "createNodeReducer" [ label="triggers (action has traceId)" ]; + "createNodeReducer" -> "CREATE_NODE" [ label="emits (event has traceId)" ]; + "CREATE_NODE" -> "pluginRunner" [ label="handled by (event has traceId)" ]; "pluginRunner" -> "apiRunnerOnCreateNode"; "apiRunnerOnCreateNode" -> "apiRunner2"; "apiRunner2" -> "onCreateNode" [ label="call" ]; diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 4de60fa97a31d..3c8728a0ab32e 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -2,39 +2,23 @@ title: Node Creation --- -# Node Creation/Transformation/Storage +Nodes are created by calling the [createNode](/docs/actions/#createNode) action. Nodes can be any object. -Nodes are created by calling the [createNode]() action. Nodes can be any object, as long as it doesn't contain certain [reserved fields](). - -A node is stored in redux under `state.nodes` as a map of the node ID to the actual node object. - -TODO: What's the deal with ___NODE. Including in infer-graphql-input-fields. +A node is stored in redux under the `nodes` namespace, whose state is a map of the node ID to the actual node object. ## Sourcing Nodes -Nodes are created in Gatsby by calling [createNode](). This happens primarily in the [sourceNodes]() bootstrap phase, though can happen in other places to (TODO: Find places). Nodes created during this phase are top level nodes. I.e, they have no parent. - -## Parent/child relationship - -TODO: Must explicitly set parent: field when creating node? - -TODO: used in data-tree-utils. Might make more sense when discussing schema? Used in build-node-types when resolving parent. Also used when finding rootNodeAncestor. and in source filesystem, create-file-path - -A node can be linked to another node as a parent or child. To create this link, you must explicitly call the `createParentChildLink`. All this does is add the child node's ID to the parent's `children` array. And it then persist the parent back to `redux.nodes`. - -TODO: [internal-data-bridge]() plugin? - -Note, a `node.owner` refers to the plugin that created it, not the parent node. +Nodes are created in Gatsby by calling [createNode](/docs/actions/#createNode). This happens primarily in the [sourceNodes](/docs/node-apis/#sourceNodes) bootstrap phase, though can happen in other places too (TODO: Find places). Nodes created during this phase are top level nodes. I.e, they have no parent. This is represented by source plugins setting the node's `parent` field to `___SOURCE___`. ## Fresh/stale nodes Every time a build is re-run, there is a chance that a node that exists in the redux store no longer exists in the original data source. E.g a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby. -To track this, there is a `redux.nodesTouched` store that tracks whether a particular node ID has been touched. This occurs whenever a node is created (by handled [CREATE_NODE]()), or an explicit call to [touchNode](). +To track this, there is a redux `nodesTouched` namespace that tracks whether a particular node ID has been touched. This occurs whenever a node is created (handled by [CREATE_NODE](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/nodes-touched.js)), or an explicit call to [touchNode](/docs/bound-action-creators/#touchNode). -When a source-nodes plugin runs, it generally recreates nodes (which touches them too). But in some cases, such as [transformer-screenshot](), a node might not change, but we still want to keep it around for the build. In these cases, we could have to explicitly call `touchNode`. +When a `source-nodes` plugin runs again, it generally recreates nodes (which touches them too). But in some cases, such as [transformer-screenshot](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-screenshot/src/gatsby-node.js#L56), a node might not change, but we still want to keep it around for the build. In these cases, we could have to explicitly call `touchNode`. -Any nodes that aren't touched by the end of source-nodes, are deleted. This is performed by a diff on the `redux.nodesTouched` and `redux.nodes` collections. +Any nodes that aren't touched by the end of the `source-nodes` phase, are deleted. This is performed by a diff on the `nodesTouched` and `nodes` redux namespaces, in [source-nodes.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/utils/source-nodes.js) ## Changing a node's fields @@ -42,20 +26,20 @@ From a site developer's point of view, nodes are immutable. In the sense that if So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. -## Internal Data Bridge +## Parent/child relationship + +TODO: Must explicitly set parent: field when creating node? -On sourceNodes, this plugin creates: +TODO: used in data-tree-utils. Might make more sense when discussing schema? Used in build-node-types when resolving parent. Also used when finding rootNodeAncestor. and in source filesystem, create-file-path -- a default SitePage node for `/dev-404-page/`. -- a Node for each of the plugins previously loaded earlier in bootstrap. This node includes the contents of they plugin's package.json. -- a `Site` node that is the contents of `gatsby-config.js`, which was previously loaded into `redux.config`. -- Sets up a watcher to re-require `gatsby-config.js` and re-create the node if it ever changes +A node can be linked to another node as a parent or child. To create this link, you must explicitly call the [createParentChildLink](/docs/bound-action-creators/#createParentChildLink). All this does is add the child node's ID to the parent's `children` array. And it then persist the parent back to the redux `nodes` namespace. -on createPage: +TODO: [internal-data-bridge]() plugin? + +Note, a `node.owner` refers to the plugin that created it, not the parent node. -- Create a new Node that is the same as the original page, but with type = `SitePage` and id = `SitePage ${path}` +## `${field}___NODE` fields -And when a `DELETE_PAGE` event is emitted, it deletes the corresponding `SitePage` using [deleteNode](). +You may notice references to node fields ending in `___NODE`. These signify foreign key relationships to some other node. For more info on how to use this, see the [create-source-plugin](/docs/create-source-plugin/) tutorial. -TODO: Where is all this used? Only reference to SitePage is in [gatsby-plugin-sitemap](). Talk about how this overall fits into the Gatsby build process diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 57b374dbe183c..99754363aadc0 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -2,7 +2,7 @@ title: Schema Generation --- -## Schema +## GrapQL Schema Generation Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. We must infer a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how that occurs. @@ -93,3 +93,26 @@ allMarkdownRemark( We're done! We've created GraphQL Types by inferring from node's examples and combining them with custom fields declared by plugins. We've also created Types that can be queried for all instances of that node's types. We now merge these types into a single collection which is used to create a new [GraphQLSchema](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/index.js#L23), which is then saved to `redux.schema`. +## More schema information + +### `${type}___${field}` strings e.g `frontmatter___date` + +You might see sorting queries such as: + +```graphql +{ + allMarkdownRemark(sort: { fields: [frontmatter___date]}) { + edges { + node { + ... + } + } + } +} +``` + +The `frontmatter___date` is a notation the `date` field on the `frontmatter` type. In sift, this would be represented as `frontmatter.date`, but graphql doesn't allow this syntax, so we're forced to use `___` instead of a period. The translation occurs in [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L255) + +### Sift querying + +TODO From 27c212e8abd94bdb2c328343bee89ddc84ae126a Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 21 Aug 2018 14:33:27 +1000 Subject: [PATCH 18/39] more docs updates --- docs/docs/behind-the-scenes.md | 8 ++++++++ docs/docs/how-plugins-apis-are-run.md | 8 ++++---- docs/docs/learning-gatsbys-internals.md | 9 --------- docs/docs/node-creation-behind-the-scenes.md | 20 ++++--------------- .../schema-generation-behind-the-scenes.md | 2 +- www/src/data/sidebars/doc-links.yaml | 4 ++-- 6 files changed, 19 insertions(+), 32 deletions(-) create mode 100644 docs/docs/behind-the-scenes.md delete mode 100644 docs/docs/learning-gatsbys-internals.md diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md new file mode 100644 index 0000000000000..0572dd22584e8 --- /dev/null +++ b/docs/docs/behind-the-scenes.md @@ -0,0 +1,8 @@ +--- +title: Behind the Scenes +overview: true +--- + +Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works. + +If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docso diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index beb17a5db76ba..c4ba058fde04c 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -8,7 +8,7 @@ Note, this section only explains how `gatsby-node` plugins are run. Not browser ## Early in the build -Early in the bootstrap phase, we [load all the configured plugins](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/bootstrap/load-plugins/index.js#L40) (and internal plugins) for the site. These are saved into redux under `flattenedPlugins`. Each plugin in redux contains the following fields: +Early in the bootstrap phase, we [load all the configured plugins](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/bootstrap/load-plugins/index.js#L40) (and internal plugins) for the site. These are saved into redux under the `flattenedPlugins` namespace. Each plugin in redux contains the following fields: - **resolve**: absolute path to the plugin's directory - **id**: String concatenation of 'Plugin ' and the name of the plugin. E.g `Plugin query-runner` @@ -34,7 +34,7 @@ Some API calls can take a while to finish. So every time an API is run, we creat - **span**: opentracing span for tracing builds - **traceId**: optional args.traceId provided if API will result in further API calls (see below) -We immediately place this object into an `apisRunningById` Map. +We immediately place this object into an `apisRunningById` Map, where we track its execution. ## Running each plugin @@ -50,7 +50,7 @@ All actions take 3 arguments: 2. The plugin that is calling this action. E.g `createNode` uses this to assign the owner of the new node 3. An object with misc action options: - **traceId**: See below - - **parentSpan**: opentracing span + - **parentSpan**: opentracing span (see [tracing docs](/docs/performance-tracing/)) Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind inject actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. @@ -90,7 +90,7 @@ digraph { } ``` -The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes]()) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. +The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes](/docs/node-apis/#sourceNodes)) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. 1. The traceID is passed as an argument to the original API runner. E.g diff --git a/docs/docs/learning-gatsbys-internals.md b/docs/docs/learning-gatsbys-internals.md deleted file mode 100644 index 66cc061d4c693..0000000000000 --- a/docs/docs/learning-gatsbys-internals.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Learning Gatsby's Internals ---- - -Curious how Gatsby works under the hood? This section describes how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby. - -If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. - - diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 3c8728a0ab32e..260b280808b39 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -8,17 +8,17 @@ A node is stored in redux under the `nodes` namespace, whose state is a map of t ## Sourcing Nodes -Nodes are created in Gatsby by calling [createNode](/docs/actions/#createNode). This happens primarily in the [sourceNodes](/docs/node-apis/#sourceNodes) bootstrap phase, though can happen in other places too (TODO: Find places). Nodes created during this phase are top level nodes. I.e, they have no parent. This is represented by source plugins setting the node's `parent` field to `___SOURCE___`. +Nodes are created in Gatsby by calling [createNode](/docs/actions/#createNode). This happens primarily in the [sourceNodes](/docs/node-apis/#sourceNodes) bootstrap phase. Nodes created during this phase are top level nodes. I.e, they have no parent. This is represented by source plugins setting the node's `parent` field to `___SOURCE___`. Nodes created via transform plugins (who implement [onCreateNode](/docs/node-apis/#onCreateNode)) will have source nodes as their parents, or other transformed nodes. For a rough overview of what happens when source nodes run, see the [traceID illustration](/docs/how-plugins-apis-are-run/#using-traceid-to-await-downstream-api-calls). ## Fresh/stale nodes Every time a build is re-run, there is a chance that a node that exists in the redux store no longer exists in the original data source. E.g a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby. -To track this, there is a redux `nodesTouched` namespace that tracks whether a particular node ID has been touched. This occurs whenever a node is created (handled by [CREATE_NODE](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/nodes-touched.js)), or an explicit call to [touchNode](/docs/bound-action-creators/#touchNode). +To track this, there is a redux `nodesTouched` namespace that tracks whether a particular node ID has been touched. This occurs whenever a node is created (handled by [CREATE_NODE](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/nodes-touched.js)), or an explicit call to [touchNode](/docs/actions/#touchNode). -When a `source-nodes` plugin runs again, it generally recreates nodes (which touches them too). But in some cases, such as [transformer-screenshot](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-screenshot/src/gatsby-node.js#L56), a node might not change, but we still want to keep it around for the build. In these cases, we could have to explicitly call `touchNode`. +When a `source-nodes` plugin runs again, it generally recreates nodes (which automatically touches them too). But in some cases, such as [transformer-screenshot](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-screenshot/src/gatsby-node.js#L56), a node might not change, but we still want to keep it around for the build. In these cases, we must explicitly call `touchNode`. -Any nodes that aren't touched by the end of the `source-nodes` phase, are deleted. This is performed by a diff on the `nodesTouched` and `nodes` redux namespaces, in [source-nodes.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/utils/source-nodes.js) +Any nodes that aren't touched by the end of the `source-nodes` phase, are deleted. This is performed via a diff between the `nodesTouched` and `nodes` redux namespaces, in [source-nodes.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/utils/source-nodes.js) ## Changing a node's fields @@ -26,18 +26,6 @@ From a site developer's point of view, nodes are immutable. In the sense that if So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. -## Parent/child relationship - -TODO: Must explicitly set parent: field when creating node? - -TODO: used in data-tree-utils. Might make more sense when discussing schema? Used in build-node-types when resolving parent. Also used when finding rootNodeAncestor. and in source filesystem, create-file-path - -A node can be linked to another node as a parent or child. To create this link, you must explicitly call the [createParentChildLink](/docs/bound-action-creators/#createParentChildLink). All this does is add the child node's ID to the parent's `children` array. And it then persist the parent back to the redux `nodes` namespace. - -TODO: [internal-data-bridge]() plugin? - -Note, a `node.owner` refers to the plugin that created it, not the parent node. - ## `${field}___NODE` fields You may notice references to node fields ending in `___NODE`. These signify foreign key relationships to some other node. For more info on how to use this, see the [create-source-plugin](/docs/create-source-plugin/) tutorial. diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 99754363aadc0..faed917fde833 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -77,7 +77,7 @@ We're now ready to create our final Graphql Type, known as a [ProcessedNodeType] ### 7. Create Connection Fields -We've built a GraphQL type where we can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? +We've built a GraphQL type that can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? This occurs in [build-connection-fields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L17). We create useful fields such as `totalCount`, `distinct`, `group`, `next`, `prev`, `edges`, `sort`, etc. We end up with a GraphQL Type that can be queried for all of its nodes. For example : diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 8355000d68ea8..71da93cca239d 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -251,8 +251,8 @@ link: /docs/querying-with-graphql/ - title: Behind The Scenes items: - - title: Learning Gatsbys Internals - link: /docs/learning-gatsbys-internals + - title: Behind the Scenes + link: /docs/behind-the-scenes/ - title: How APIS/Plugins Are Run link: /docs/how-plugins-apis-are-run/ - title: Node Creation From 916d64b56926d2d7e82239b6e7348493ee003e4a Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 21 Aug 2018 14:43:17 +1000 Subject: [PATCH 19/39] add graphviz dependency --- www/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/www/package.json b/www/package.json index 3a712fca7c7f8..9c7f019412879 100644 --- a/www/package.json +++ b/www/package.json @@ -32,6 +32,7 @@ "gatsby-remark-autolink-headers": "next", "gatsby-remark-copy-linked-files": "next", "gatsby-remark-images": "next", + "gatsby-remark-graphviz": "next", "gatsby-remark-prismjs": "next", "gatsby-remark-responsive-iframe": "next", "gatsby-remark-smartypants": "next", From dd58c754dc87aa8f1e520cbefd9aa65d5e07457a Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 21 Aug 2018 18:07:14 +1000 Subject: [PATCH 20/39] fixed overview: true --- docs/docs/behind-the-scenes.md | 1 - www/src/data/sidebars/doc-links.yaml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md index 0572dd22584e8..b044dc20d5e56 100644 --- a/docs/docs/behind-the-scenes.md +++ b/docs/docs/behind-the-scenes.md @@ -1,6 +1,5 @@ --- title: Behind the Scenes -overview: true --- Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works. diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 71da93cca239d..5c8d6ae17e43f 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -250,9 +250,8 @@ - title: Querying data with GraphQL link: /docs/querying-with-graphql/ - title: Behind The Scenes + link: /docs/behind-the-scenes/ items: - - title: Behind the Scenes - link: /docs/behind-the-scenes/ - title: How APIS/Plugins Are Run link: /docs/how-plugins-apis-are-run/ - title: Node Creation From b84db49c24de6693b8fbf3e16e253a06233c85ac Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 21 Aug 2018 18:22:27 +1000 Subject: [PATCH 21/39] formatting --- docs/docs/behind-the-scenes.md | 2 +- docs/docs/how-plugins-apis-are-run.md | 31 +++++++++-------- docs/docs/node-creation-behind-the-scenes.md | 6 ++-- .../schema-generation-behind-the-scenes.md | 34 +++++++++---------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md index b044dc20d5e56..a3ba4be6ccd2c 100644 --- a/docs/docs/behind-the-scenes.md +++ b/docs/docs/behind-the-scenes.md @@ -4,4 +4,4 @@ title: Behind the Scenes Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works. -If you're looking for information on how to *use* Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docso +If you're looking for information on how to _use_ Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index c4ba058fde04c..fc3a3d7435d0b 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -38,7 +38,7 @@ We immediately place this object into an `apisRunningById` Map, where we track i ## Running each plugin -Next, we filter all `flattenedPlugins` down to those that implement the API we're trying to run. For each plugin, we require its `gatsby-node.js` and call its exported API function. E.g if API was `sourceNodes`, it would result in a call to `gatsbyNode['sourceNodes'](...apiCallargs)`. +Next, we filter all `flattenedPlugins` down to those that implement the API we're trying to run. For each plugin, we require its `gatsby-node.js` and call its exported API function. E.g if API was `sourceNodes`, it would result in a call to `gatsbyNode['sourceNodes'](...apiCallargs)`. ## Injected arguments @@ -46,13 +46,13 @@ API implementations are passed a variety of useful [actions](/docs/actions/) and All actions take 3 arguments: -1. The core information required by the action. E.g for [createNode](/docs/actions/#createNode), we must pass a node -2. The plugin that is calling this action. E.g `createNode` uses this to assign the owner of the new node -3. An object with misc action options: +1. The core information required by the action. E.g for [createNode](/docs/actions/#createNode), we must pass a node +2. The plugin that is calling this action. E.g `createNode` uses this to assign the owner of the new node +3. An object with misc action options: - **traceId**: See below - **parentSpan**: opentracing span (see [tracing docs](/docs/performance-tracing/)) -Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind inject actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. +Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind inject actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. ## Waiting for all plugins to run @@ -63,7 +63,7 @@ Each plugin is run inside a [map-series](https://www.npmjs.com/package/map-serie ```dot digraph { node [ shape="box" ]; - + "initialCall" [ label="apiRunner(`sourceNodes`, {\l traceId: `initial-sourceNodes`,\l waitForCascadingActions: true,\l parentSpan: parentSpan\l})\l " ]; "apiRunner1" [ label="api-runner-node.js" ]; "sourceNodes" [ label="plugin.SourceNodes()" ]; @@ -75,7 +75,7 @@ digraph { "onCreateNode" [ label="plugin.onCreateNode()" ]; "apiRunnerOnCreateNode" [ label="apiRunner(`onCreateNode`, {\l node,\l traceId: action.traceId\l})\l "; ]; "apiRunner2" [ label="api-runner-node.js" ]; - + "initialCall" -> "apiRunner1"; "apiRunner1" -> "apisRunning" [ label="set to 1" ]; "apiRunner1" -> "sourceNodes" [ label="call" ]; @@ -90,9 +90,9 @@ digraph { } ``` -The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes](/docs/node-apis/#sourceNodes)) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. +The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes](/docs/node-apis/#sourceNodes)) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. -1. The traceID is passed as an argument to the original API runner. E.g +1. The traceID is passed as an argument to the original API runner. E.g ```javascript apiRunner(`sourceNodes`, { @@ -101,9 +101,10 @@ The majority of API calls result in one or more implementing plugins being calle parentSpan: parentSpan, }) ``` -1. We keep track of the number of API calls with this traceId in the [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L139) Map. On this first invocation, it will be set to `1`. -1. Using the action rebinding mentioned [above](#injected-arguments), the traceId is passed through to all action calls via the `actionOptions` object. -1. After reducing the Action, a global event is [emitted](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/index.js#L93) which includes the action information -1. For the `CREATE_NODE` and `CREATE_PAGE` events, we need to call the `onCreateNode` and `onCreatePage` APIs respectively. The [plugin-runner](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/plugin-runner.js) takes care of this. It also passes on the traceId from the Action back into the API call. -1. We're back in `api-runner-node.js` and can tie this new API call back to its original. So we increment the value of [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L218) for this traceId. -1. Now, whenever an API finishes running (when all its implementing plugins have finished), we decrement `apisRunningByTraceId[traceId]`. If the original API call included the `waitForCascadingActions` option, then we wait until `apisRunningByTraceId[traceId]` == 0 before resolving. + +1. We keep track of the number of API calls with this traceId in the [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L139) Map. On this first invocation, it will be set to `1`. +1. Using the action rebinding mentioned [above](#injected-arguments), the traceId is passed through to all action calls via the `actionOptions` object. +1. After reducing the Action, a global event is [emitted](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/index.js#L93) which includes the action information +1. For the `CREATE_NODE` and `CREATE_PAGE` events, we need to call the `onCreateNode` and `onCreatePage` APIs respectively. The [plugin-runner](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/redux/plugin-runner.js) takes care of this. It also passes on the traceId from the Action back into the API call. +1. We're back in `api-runner-node.js` and can tie this new API call back to its original. So we increment the value of [apisRunningByTraceId](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L218) for this traceId. +1. Now, whenever an API finishes running (when all its implementing plugins have finished), we decrement `apisRunningByTraceId[traceId]`. If the original API call included the `waitForCascadingActions` option, then we wait until `apisRunningByTraceId[traceId]` == 0 before resolving. diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 260b280808b39..2a83609ce07e9 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -16,7 +16,7 @@ Every time a build is re-run, there is a chance that a node that exists in the r To track this, there is a redux `nodesTouched` namespace that tracks whether a particular node ID has been touched. This occurs whenever a node is created (handled by [CREATE_NODE](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/nodes-touched.js)), or an explicit call to [touchNode](/docs/actions/#touchNode). -When a `source-nodes` plugin runs again, it generally recreates nodes (which automatically touches them too). But in some cases, such as [transformer-screenshot](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-screenshot/src/gatsby-node.js#L56), a node might not change, but we still want to keep it around for the build. In these cases, we must explicitly call `touchNode`. +When a `source-nodes` plugin runs again, it generally recreates nodes (which automatically touches them too). But in some cases, such as [transformer-screenshot](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-screenshot/src/gatsby-node.js#L56), a node might not change, but we still want to keep it around for the build. In these cases, we must explicitly call `touchNode`. Any nodes that aren't touched by the end of the `source-nodes` phase, are deleted. This is performed via a diff between the `nodesTouched` and `nodes` redux namespaces, in [source-nodes.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/utils/source-nodes.js) @@ -24,10 +24,8 @@ Any nodes that aren't touched by the end of the `source-nodes` phase, are delete From a site developer's point of view, nodes are immutable. In the sense that if you simply change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to redux via an action. -So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. +So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. ## `${field}___NODE` fields You may notice references to node fields ending in `___NODE`. These signify foreign key relationships to some other node. For more info on how to use this, see the [create-source-plugin](/docs/create-source-plugin/) tutorial. - - diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index faed917fde833..61453367a7236 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -13,11 +13,11 @@ digraph graphname { "Node" [ label = "Node e.g type = File" ]; "rnodes" [ label = "redux.nodes" ]; - "cfd" [ label = "Custom Field Declarations + "cfd" [ label = "Custom Field Declarations e.g source-filesystem: publicURL()" ]; - "cfiot" [ label = "Custom Fields InputObjectType + "cfiot" [ label = "Custom Fields InputObjectType { args: lt, gt, eq, re }" ]; - "cf" [ label = "ConnectionFields + "cf" [ label = "ConnectionFields e.g allMarkdownRemark()" ]; "nfiot" [ label = "Node Fields InputObjectType" ]; "pt" [ label = "ProcessedNodeType" ]; @@ -36,14 +36,14 @@ digraph graphname { } ``` -1. Group all nodes by type (`node.internal.type`). E.g `File`, `ImageSharp`. Then, for each type, perform the following steps: -1. Each plugin has the chance to provide custom field Types for their node type via the [setFieldsOnGraphQLNodeType]() API. These APIS are run now -1. Convert each of these custom field types into an input filter type. -1. Build an example object from all the existing nodes. -1. For each example value, generate a GraphQL Type (or use a scalar), and an input filter type. So we can query this node by any of its fields -1. Create a `GraphQLObjectType` (the actual GraphQL Type object) -1. Create GraphQL types for connections between fields. This includes pagination, sorting, and limits for collections of nodes. -1. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` +1. Group all nodes by type (`node.internal.type`). E.g `File`, `ImageSharp`. Then, for each type, perform the following steps: +1. Each plugin has the chance to provide custom field Types for their node type via the [setFieldsOnGraphQLNodeType]() API. These APIS are run now +1. Convert each of these custom field types into an input filter type. +1. Build an example object from all the existing nodes. +1. For each example value, generate a GraphQL Type (or use a scalar), and an input filter type. So we can query this node by any of its fields +1. Create a `GraphQLObjectType` (the actual GraphQL Type object) +1. Create GraphQL types for connections between fields. This includes pagination, sorting, and limits for collections of nodes. +1. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` See below for details on each of the above steps. @@ -69,7 +69,7 @@ The next step is to start inferring the GraphQL equivelent of the node's fields. ### 5. Generate GraphQL Type from Example -Now that we've built an object with examples of all the type's possible fields, we can [infer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L71) the actual GraphQL equivelent types. This simply maps javascript types to GraphQL types. E.g `typeof(value) = int` would result in `GraphQLInt`. If the type is an object, we recurse down into its structure, inferring types from each of its sub fields. +Now that we've built an object with examples of all the type's possible fields, we can [infer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L71) the actual GraphQL equivelent types. This simply maps javascript types to GraphQL types. E.g `typeof(value) = int` would result in `GraphQLInt`. If the type is an object, we recurse down into its structure, inferring types from each of its sub fields. ### 6. Create a GraphqlObjectType @@ -77,15 +77,15 @@ We're now ready to create our final Graphql Type, known as a [ProcessedNodeType] ### 7. Create Connection Fields -We've built a GraphQL type that can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? +We've built a GraphQL type that can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? This occurs in [build-connection-fields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L17). We create useful fields such as `totalCount`, `distinct`, `group`, `next`, `prev`, `edges`, `sort`, etc. We end up with a GraphQL Type that can be queried for all of its nodes. For example : ```javascript allMarkdownRemark( - sort: {}, - limit: 100, - filter: { fileAbsolutePath: { ne: null} } + sort: {}, + limit: 100, + filter: { fileAbsolutePath: { ne: null } } ) ``` @@ -95,7 +95,7 @@ We're done! We've created GraphQL Types by inferring from node's examples and co ## More schema information -### `${type}___${field}` strings e.g `frontmatter___date` +### `${type}___${field}` strings e.g `frontmatter___date` You might see sorting queries such as: From 4572b9892cde6588dc5ffd561e54b62b1f7f31ab Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Wed, 22 Aug 2018 07:34:09 +1000 Subject: [PATCH 22/39] All caps heading --- www/src/data/sidebars/doc-links.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 5c8d6ae17e43f..73de06cc0b73f 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -249,7 +249,7 @@ link: /docs/prpl-pattern/ - title: Querying data with GraphQL link: /docs/querying-with-graphql/ -- title: Behind The Scenes +- title: BEHIND THE SCENES link: /docs/behind-the-scenes/ items: - title: How APIS/Plugins Are Run From 5053654a4dece93b9ee2381b7e34bda38ba4f1fd Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 28 Aug 2018 13:04:12 +1000 Subject: [PATCH 23/39] in depth schema docs --- .../schema-generation-behind-the-scenes.md | 127 ++++--------- docs/docs/schema-gql-type.md | 179 ++++++++++++++++++ docs/docs/schema-input-gql.md | 137 ++++++++++++++ docs/docs/schema-sift.md | 120 ++++++++++++ www/src/data/sidebars/doc-links.yaml | 9 + 5 files changed, 483 insertions(+), 89 deletions(-) create mode 100644 docs/docs/schema-gql-type.md create mode 100644 docs/docs/schema-input-gql.md create mode 100644 docs/docs/schema-sift.md diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 61453367a7236..775542a1975d2 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -2,117 +2,66 @@ title: Schema Generation --- -## GrapQL Schema Generation +## Build-node-types Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. We must infer a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how that occurs. -At a high level, here are the steps performed during this stage: +### 1. Group all nodes by type + +Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g `PostsJson` for a `posts.json` file. + +During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. + +The flow is summarized by the below graph. It shows the intermediate transformations that are performed by various files in the [schema folder](TODO), finally resulting in the `ProcessedNoteType`. The below transformations occur for each of the uniq `internal.type` values. ```dot digraph graphname { - "Node" [ label = "Node - e.g type = File" ]; - "rnodes" [ label = "redux.nodes" ]; - "cfd" [ label = "Custom Field Declarations - e.g source-filesystem: publicURL()" ]; - "cfiot" [ label = "Custom Fields InputObjectType - { args: lt, gt, eq, re }" ]; - "cf" [ label = "ConnectionFields - e.g allMarkdownRemark()" ]; - "nfiot" [ label = "Node Fields InputObjectType" ]; - "pt" [ label = "ProcessedNodeType" ]; - "Plugin" -> "Node" [ label = "Creates node with type" ]; - "Node" -> "rnodes"; - "rnodes" -> "exampleValue" [ label = "filtered by Node Type" ]; - "exampleValue" -> "nfiot"; - "Plugin" -> "cfd" [ label = "setFieldsOnGraphQLNodeType" ]; - "cfd" -> "cfiot"; - "nfiot" -> "pt"; - "cfiot" -> "pt"; - "Node Interface" -> "pt"; - "pt" -> "cf"; - "pt" -> "GraphQLSchema" [ label = "all types" ]; - "cf" -> "GraphQLSchema"; + "pluginFields" [ label = "custom plugin fields\l{\l publicURL: {\l type: GraphQLString,\l resolve(file, a, c) { ... }\l }\l}\l ", shape = box ]; + "typeNodes" [ label = "all redux nodes of type\le.g internal.type === `File`", shape = "box" ]; + "exampleValue" [ label = "exampleValue\l{\l relativePath: `bogs/my-blog.md`,\l accessTime: 8292387234\l}\l ", shape = "box" ]; + "resolve" [ label = "ProcessedNodeType.resolve()", shape = box ]; + "gqlType" [ label = "gqlType (GraphQLObjectType)\l{\l fields,\l name: `File`\l}\l ", shape = box ]; + "parentChild" [ label = "Parent/Children fields\lnode {\l childMarkdownRemark { html }\l parent { id }\l}\l ", shape = "box" ]; + "objectFields" [ label = "Object node fields\l node {\l relativePath,\l accessTime }\l }\l ", shape = "box" ]; + "inputFilters" [ label = "InputFilters\lfile({\l relativePath: {\l eq: `blogs/my-blog.md`\l }\l})\l ", shape = box ] + + "pluginFields" -> "inputFilters"; + "pluginFields" -> "gqlType"; + "objectFields" -> "gqlType"; + "parentChild" -> "gqlType" + "gqlType" -> "inputFilters"; + "typeNodes" -> "exampleValue"; + "typeNodes" -> "parentChild"; + "typeNodes" -> "resolve"; + "exampleValue" -> "objectFields"; + "inputFilters" -> "resolve"; + "gqlType" -> "resolve"; } ``` -1. Group all nodes by type (`node.internal.type`). E.g `File`, `ImageSharp`. Then, for each type, perform the following steps: -1. Each plugin has the chance to provide custom field Types for their node type via the [setFieldsOnGraphQLNodeType]() API. These APIS are run now -1. Convert each of these custom field types into an input filter type. -1. Build an example object from all the existing nodes. -1. For each example value, generate a GraphQL Type (or use a scalar), and an input filter type. So we can query this node by any of its fields -1. Create a `GraphQLObjectType` (the actual GraphQL Type object) -1. Create GraphQL types for connections between fields. This includes pagination, sorting, and limits for collections of nodes. -1. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` - -See below for details on each of the above steps. - -### 1. Group all nodes by type - -Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g `PostsJson` for a `posts.json` file. - -During the schema generation phase, we must generate a [GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype) for each of these `node.internal.type`s. The below steps are run for each unique type. - ### 2. Plugins create custom fields Gatsby infers GraphQL Types from the fields on the sourced and transformed nodes. But before that, we allow plugins to create their own custom fields. For example, `source-filesystem` creates a [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L11) field that when resolved, will copy the file into the `public/static` directory and return the new path. To declare custom fields, plugins implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API and apply the change only to types that they care about (e.g source-filesystem [only proceeds if type.name = `File`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L6). During schema generation, Gatsby will call this API, allowing the plugin to declare these custom fields, [which are returned](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L151) to the main schema process. -### 3. Convert custom plugin fields into Input Filter Types - -For each of the custom fields declared by the plugins in the previous step, we create a [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). Depending on the type of the field, we also generate a variety of input filters to assist with querying. For example, for `source-filesystem`, [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L12) is a string. Therefore we might want to query the publicURL by a string that is equal to, not equal to, matches a regex etc. These are defined in the [scalarFilterMap](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js#L84). - -### 4. Build an example object +### Create a "GQLType" -The next step is to start inferring the GraphQL equivelent of the node's fields. To do this, we [build a super object](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L305) by merging all instances of the type's nodes from redux. During this step, we can detect if nodes have inconsistent types for their fields. E.g node1.foo = "string". node2.foo = 5. And raise an error if so. +This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 2). This step is explained in detail in [GraphQL Node Types Creation](TODO). -### 5. Generate GraphQL Type from Example +### Create Input filters -Now that we've built an object with examples of all the type's possible fields, we can [infer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L71) the actual GraphQL equivelent types. This simply maps javascript types to GraphQL types. E.g `typeof(value) = int` would result in `GraphQLInt`. If the type is an object, we recurse down into its structure, inferring types from each of its sub fields. +This step creates GraphQL input filters for each field so the objects can be queried by them. More details in [schema-input-gql](TODO). -### 6. Create a GraphqlObjectType +### ProcessedTypeNode creation with resolve implementation -We're now ready to create our final Graphql Type, known as a [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). This includes all inferred fields from the example object, merged with all the fields created from the plugins. It also includes the `Node Interface`, which provides reuseuable fields such as id, parent and children. We also create the same Input Types like in the plugin so that we can query by a node's field by regex, equality etc. +Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the args and gqlType created above, and implements a resolve function for it using sift. More detail in the [GraphQL Queries over redux](TODO) section. -### 7. Create Connection Fields +TODO: Mention how the terminology can get confusing here. Fields, objects, inputs, etc. -We've built a GraphQL type that can query by any of the node's fields, including custom plugin fields. But what about querying for connections between nodes? E.g query the first 10 fields of this type, sorted by this field? And use skip/limit pagination style? +### ThirdParty Schema -This occurs in [build-connection-fields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L17). We create useful fields such as `totalCount`, `distinct`, `group`, `next`, `prev`, `edges`, `sort`, etc. We end up with a GraphQL Type that can be queried for all of its nodes. For example : - -```javascript -allMarkdownRemark( - sort: {}, - limit: 100, - filter: { fileAbsolutePath: { ne: null } } -) -``` - -### 8. Create a new `GraphQLSchema` using the inferred Types and connection types and save this to `redux.schema` - -We're done! We've created GraphQL Types by inferring from node's examples and combining them with custom fields declared by plugins. We've also created Types that can be queried for all instances of that node's types. We now merge these types into a single collection which is used to create a new [GraphQLSchema](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/index.js#L23), which is then saved to `redux.schema`. - -## More schema information - -### `${type}___${field}` strings e.g `frontmatter___date` - -You might see sorting queries such as: - -```graphql -{ - allMarkdownRemark(sort: { fields: [frontmatter___date]}) { - edges { - node { - ... - } - } - } -} -``` - -The `frontmatter___date` is a notation the `date` field on the `frontmatter` type. In sift, this would be represented as `frontmatter.date`, but graphql doesn't allow this syntax, so we're forced to use `___` instead of a period. The translation occurs in [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L255) +TODO -### Sift querying +TODO: What is elemMatch? -TODO diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md new file mode 100644 index 0000000000000..1997f24872feb --- /dev/null +++ b/docs/docs/schema-gql-type.md @@ -0,0 +1,179 @@ +--- +title: GraphQL Node Types Creation +--- + +Gatsby creates a [GraphQLObjectType](TODO) for each distinct `node.internal.type` that is created during the source-nodes phase. Find out below how this is done. + +## GraphQL Types for each type of node + +When running a GraphQL query, there are a variety of fields that you will want to query. Let's take an example, say we have the below query: + +```graphql + query { + file( relativePath: { eq: `blogs/2018-08-23/announcing-v2.md` } ) { + frontmatter: { + title + } + } + } +``` + +When GraphQL runs, it will query all `markdownRemark` nodes by the wordcount expression and return a node that satisfied the query. Then, it will filter down the fields to return by the inner expression. I.e `{ frontmatter: { title } }`. It is the schema that allows the filtering that this section deals with. The creation of the query arguments is dealt with by [schema-gql-input](TODO). + +During the [source-nodes](TODO) phase, let's say that [gatsby-source-filesystem](TODO) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](TODO), resulting in children of different `node.internal.type`s being created. + +When querying the File nodes, there are 3 categories of fields that we'll be querying: + +- Fields on the node object. E.g + +```graphql +node { + relativePath, + extension, + size, + accessTime +} +``` + +- Child/Parent. E.g: + +```graphql +node { + childMarkdownRemark, + childrenPostsJson, + children, + parent +} +``` + +- fields created by plugins + +```graphql +node { + publicURL +} +``` + +## gqlType Creation + +The Gatsby term for the GraphQLObjectType for a unique node type, is `gqlType`. GraphQLObjectTypes are simply objects that define the type name and fields. The field definitions are created by the [createNodeFields](TODO) function in `build-node-types.js`. + +An important thing to note is that all gqlTypes are created before their fields are inferred. This allows fields to be of types that haven't yet been created due to their order of compilation. This is accomplished by the use of `fields` [being a function](TODO). + +The first step in inferring GraphQL Fields is to generate an `exampleValue`. It is the result of merging all fields of all nodes of the type in question. This `exampleValue` will therefore contain all potential field names and values, which allows us to infer each field's types. The logic to create this exampleValue is in [getExampleValues](TODO). + +With the exampleValue in hand, we can use each of its key/values to infer the Type's fields (broken down by the 3 categories above). + +### Object Node Fields + +Fields on the node that were created directly by the source and transform plugins. E.g for `File` type, these would be `relativePath`, `size`, `accessTime` etc. + +The creation of these fields is handled by the [inferObjectStructureFromNodes](TODO) function in `infer-graphql-type.js`. Given an object, a field could be in one of 3 sub-categories: + +1. It involves a mapping in [gatsby-config.js](TODO) +2. It's value is a foreign key refernce to some other node (ends in `___NODE`) +3. It's a plain object or value (e.g String, number, etc) + +#### Mapping field + +Mappings are explained in the [gatsby-config.js docs](TODO). If the object field we're generating a GraphQL type for is configured in the gatsby-config mapping, then we handle it specially. + +Imagine our top level Type we're currently generating fields for is `MarkdownRemark.frontmatter`. And the field we are creating a GraphQL field for is called `author`. And, that we have a mapping setup of: + +```javascript +mapping: { + "MarkdownRemark.frontmatter.author": `AuthorYaml.name`, +}, +``` + +The field generaton in this case is handled by [inferFromMapping](TODO). The first step is to find the type that is mapped to. In this case, `AuthorYaml`. This is known as the `linkedType`. That type will have a field to link by. In this case `name`. If one is not supplied, it defaults to `id`. This field is known as `linkedField` + +Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which we look up in list of other `gqlTypes`). The field resolver will get the value for the node (in this case, the author string), and then search through the react nodes till it finds one whose type is `AuthorYaml` and whose `name` field matches the author string. + +#### Foreign Key reference (`___NODE`) + +If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is actually a foreign key reference to another node in redux. Check out the [source-plugin docs](TODO) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](TODO). + +This is actually quite similar to the mapping case above. We remove the `___NODE` part of the field name. E.g `author___NODE` would become `author`. Then, we find our `linkedNode`. I.e given the example value for `author` (which would be an ID), we find its actual node in redux. Then, we find its type in processed types by its `internal.type`. Note, that also like in mapping fields, we can define the `linkedField` too. This can be specified via `nodeFieldname___NODE___linkedFieldName`. E.g for `author___NODE___name`, the linkedField would be `name` instead of `id`. + +Now we can return a new GraphQL Field object, whose type is the one found above. Its resolver searches through all redux nodes until it finds one with the matching ID. As usual, it also creates a [page dependency](TODO), from the query context's path to the node ID. + +If the foreign key value is an array of IDs, then instead of returning a Field declaration for a single field, we return a `GraphQLUnionType`, which is a union of all the distinct linked types in the array. + + + +#### Plain object or value field + +If the field was not handled as a mapping or foreign key reference, then it must be a normal every day field. E.g a scalar, string, or plain object. These cases are handled by [inferGraphQLType](TODO). + +The core of this step creates a GraphQL Field object, where the type is inferred directly via the result of `typeof`. E.g `typeof(value) === 'boolean'` would result in type `GraphQLBoolean`. Since these are simple values, resolvers are not defined (graphql-js takes care of that for us). + +If however, the value is an object or array, we recurse, using [inferObjectStructureFromNodes](TODO) to create the GraphQL fields. + +in addition, if a value is a string that actually points to a file, then we hand off field inference to [types/file-type](TODO) (TODO: Dive into why). The same goes for Strings that look like dates. These are handled by [types/date-type](TODO) + +### Child/Parent fields + +#### Child fields creation + +Let's say we're creating a type for `File`. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. + +Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](TODO)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: + +```javascript +{ + `id1`: { type: `File`, children: [`id2`, `id3`], ...other_fields }, + `id2`: { type: `markdownRemark`, ...other_fields }, + `id3`: { type: `postsJson`, ...other_fields } +} +``` + +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. The benefit of this is that we can easily create a `File.children` field that returns all children, regardless of type. The downside is that the creation of fields such as `File.childrenMarkdownRemark` and `File.childrenPostsJson` is more complicated. This is what [build-node-types](TODO) does. + +Another convenience Gatsby provides is the ability to query a node's `child` or `children`, depending on whether a parent node has 1 or more children of that type. This inference is also made by `build-node-types`. + +#### child resolvers + +When defining our parent `File` gqlType, [build-node-types](TODO) will iterate over the distinct types of its children, and create their fields. Let's say one of these child types is `markdownRemark`. Let's assume there is only one `markdownRemark` child per `File`. Therefore, its field name is `childMarkdownRemark`. Now, we must create its graphql Resolver. + +``` +resolve(node, args, context, info) +``` + +The resolve function will be called when we eventually are running queries for our pages. A query might look like: + +```graphql + query { + allFile { + edges { + node { + childMarkdownRemark { title } + } + } + } + } +``` + +To resolve `node.childMarkdownRemark`, we filter over all the node's (that we're resolving) `children`, until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. + +But before we return from the resolve function, remember that we might be running this query within the context of a page. If that's the case, then whenever the node changes, the page will need to be rerendered. To record that fact, we call call [createPageDependency](TODO) with the node ID and the page, which is a field in the `context` object in the resolve function signature. + +#### parent field + +When a node is created as a child of some node, that fact is stored as a `parent` field on the redux node whose value is the ID of the parent. The parent GraphQL field resolver simply looks up the resolving node's `parent` field, and then looks up the parent in the redux `nodes` namespace map. It also creates a [page dependency](TODO have this in own section), as is done by child resolvers. + +### Plugin fields + +These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](TODO) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. + +## TODO GQLType related to Schema/connections + +#### NOTES + +TODO: If haven't already. Talk about how all the field generation occurs after we've defined all the gqlTypes themselves first. + +TODO wat is linkedField?? +-------- + +TODO: Add to section where links between node and path are created. build-node-types.js:56. + diff --git a/docs/docs/schema-input-gql.md b/docs/docs/schema-input-gql.md new file mode 100644 index 0000000000000..0579d9f7912b8 --- /dev/null +++ b/docs/docs/schema-input-gql.md @@ -0,0 +1,137 @@ +--- +title: Inferring Input Filters +--- + +## Input Filters vs gqlType + +Up until now, we've inferred a Gatsby Node's main fields ([gqlType](TODO)). These allow us to query a node's children, parent, object fields. But these are only useful inside a GraphQL Query. Before we get there, we have to be able to query the types we have already created by their fields. E.g, querying for all markdownRemark nodes that have 4 paragraphs. + +```graphql +{ + markdownRemark(wordCount: {paragraphs: {eq: 4}}) { + html + } +} +``` + +The arguments (`wordcount: {paragraphs: {eq: 4}}`) to the query are known as Input filters. In GraphQL Land, they are the [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). This section covers how these Input filters are inferred. + +### Inferring input filters from example node values + +The first step is to generate an input field for each type of field on the redux nodes. For example, we might want to query markdown nodes by their front matter author: + +```graphql +{ + markdownRemark(frontmatter: {author: {eq: "F. Scott Fitzgerald"}}) { id } +} +``` + +This step is handled by [inferInputObjectStrctureFromNodes](TODO). First, we generate an example Value (see [gqlTypes](TODO)). For each field on the example value (e.g `author`), we create a [GraphQLInputObjectType](TODO) with an appropriate name. The fields for Input Objects are predicates that depend on the value's `typeof` result. E.g for a String, we need to be able to query by `eq`, `regex` etc. If the value is an object itself, then we recurse, building its fields as above. + +If the key is a foreign key reference (ends in `___NODE`), then we find the field's linked Type first, and progress as above (for more on how foreign keys are implemented, see [gqlType](TODO)). After this step, we will end up with an Input Object type such as . + +```javascript +{ + `MarkdownRemarkFrontmatterAuthor`: { + name: `MarkdownRemarkFrontmatterAuthorInputObject`, + fields: { + `MarkdownRemarkFrontmatterAuthorName` : { + name: `MarkdownRemarkFrontmatterAuthorNameQueryString`, + fields: { + eq: { type: GraphQLString }, + ne: { type: GraphQLString }, + regex: { type: GraphQLString }, + glob: { type: GraphQLString }, + in: { type: new GraphQLList(GraphQLString) }, + } + } + } + } +} + +``` + +### Inferring input filters from plugin fields + +Plugins themselves have the opportunity to create custom fields that apply to ALL nodes of a particular type, as opposed to having to expicitly add the field on every node creation. An example would be `markdownRemark` which adds a `wordcount` field to each node automatically. This section deals with the generation of input filters so that we can query by these fields as well. E.g: + +```graphql +{ + markdownRemark(wordCount: {paragraphs: {eq: 4}}) { + html + } +} +``` + +Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](TODO) API. They must return a full GraphQLObjectType, complete with `resolve` function. Once this API has been run, the fields are passed to [inferInputObjectStructureFromFields](TODO), which will generate input filters for thew new fields. The result would look something like: + +```javascript +{ //GraphQLInputObjectType + name: `WordCountwordcountInputObject`, + fields: { + `paragraphs`: { + type: { // GraphQLInputObjectType + name: `WordCountParagraphsQueryInt`, + fields: { + eq: { type: GraphQLInt }, + ne: { type: GraphQLInt }, + gt: { type: GraphQLInt }, + gte: { type: GraphQLInt }, + lt: { type: GraphQLInt }, + lte: { type: GraphQLInt }, + in: { type: new GraphQLList(GraphQLInt) }, + } + } + } + } +} +``` + +As usual, the input filter fields are based on the type of the field, which is defined by the plugin. + +### Merged result + +Now that we've generated input fields from the redux nodes and from custom plugin fields, we merge them together. E.g + +```javascript +{ + + // from infer input fields from object + `MarkdownRemarkAuthor`: { + name: `MarkdownRemarkAuthorInputObject`, + fields: { + `MarkdownRemarkAuthorName` : { + name: `MarkdownRemarkAuthorNameQueryString`, + fields: { + eq: { type: GraphQLString }, + ne: { type: GraphQLString }, + regex: { type: GraphQLString }, + glob: { type: GraphQLString }, + in: { type: new GraphQLList(GraphQLString) }, + } + } + } + }, + + // From infer input fields from fields + `wordCount`: { //GraphQLInputObjectType + name: `WordCountwordcountInputObject`, + fields: { + `paragraphs`: { + type: { // GraphQLInputObjectType + name: `WordCountParagraphsQueryInt`, + fields: { + eq: { type: GraphQLInt }, + ne: { type: GraphQLInt }, + gt: { type: GraphQLInt }, + gte: { type: GraphQLInt }, + lt: { type: GraphQLInt }, + lte: { type: GraphQLInt }, + in: { type: new GraphQLList(GraphQLInt) }, + } + } + } + } + } +} +``` diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md new file mode 100644 index 0000000000000..967dbe24f0495 --- /dev/null +++ b/docs/docs/schema-sift.md @@ -0,0 +1,120 @@ +--- +title: GraphQL Queries over redux +--- + +## Summary + +Gatsby stores all data loaded during the source-nodes phase in redux. And it allows you to write GraphQL queries to query that data. But Redux is a plain javascript object store. So how does Gatsby query over those nodes using the GraphQL Query language? + +The answer is that it uses the [sift.js](TODO) library. It is a port of the MongoDB query language that works over plain javascript objects. The logic for this is in the [run-sift.js](TODO) file, which is called from the [ProcessedNodeType] `resolve()` function. + +TODO: Mention this is different from `allTypeFields`. This just finds one result. + +## ProcessedNodeType Resolve Function + +Remember, at the point this resolve function is created, we have been iterating over all the distinct `internal.node.type`s in the redux `nodes` namespace. So for instance we might be on the `markdownRemark` type. Therefore the `resolve()` function closes over this type name. + +The `resolve()` function calls `run-sift`, and provides it with the following arguments: + +- GraphQLArgs (as js object). Within a filter. E.g `wordcount: { paragraphs: { eg: 4 } }` +- All nodes in redux of this type. E.g where `internal.type == 'markdownRemark'` +- Context `path`, if present +- typeName. E.g `markdownRemark` +- gqlType. See [more on gqlType](TODO) + +For example: + +```javascript +{ + args: { + filter: { // Exact args from GraphQL Query + wordcount: { + paragraphs: { + eg: 4 + } + } + } + }, + nodes: ${latestNodes}, + path: context.path, // E.g /blog/2018-08-23/introducing-v2 + typeName: `markdownRemark`, + type: ${gqlType} +} +``` + +## Run-sift.js + +This file converts GraphQL Arguments into sift queries and applies them to the collection of all nodes of this type. The rough steps are: + +1. Convert query args to sift args +1. Drop leaves from args +1. Resolve inner query fields on all nodes +1. Track newly realized fields +1. Run sift query on all nodes +1. Create Page dependency if required + + +### 1. Convert query args to sift args + +Sift expects all field names to be prepended by a `$`. The [siftify-args](TODO) function takes care of this. It descends the args tree, performing the following transformations for each field key/value scenario. + +- field key is`elemMatch`? Change to `$elemMatch`. Recurse on value object +- field value is regex? Apply regex cleaning +- field value is globl, use [minimatch](TODO) library to convert to Regex +- normal value, prepend `$` to field name. + +So, the above query would become: + +```javascript +{ + `$wordcount`: { + `$paragraphs`: { + `$eg`: 4 + } + } +} +``` + +### 2. Drop leaves (e.g `{eq: 4}`) from args + +To assist in step 3, we create a version of the siftified args called `fieldsToSift` that has all leaves of the args tree replaced with boolean `true`. This is handled by the [extractFieldsToSift](TODO) function. `fieldsToSift` would look like this after the function is applied: + +```javascript +{ + `wordcount`: { + `paragraphs`: true + } +} +``` + +### 3. Resolve inner query fields on all nodes + +Step 4 will perform the actual sift query over all the nodes, returning the first one that matches the query. But we must remember that the nodes that are in redux only include data that was explicitly created by their source or transform plugins. If instead of creating a data field, a plugin used `setFieldsOnGraphQLNodeType` to define a custom field (see [gql-type](TODO)), then we have to manually call that field's resolver on each node. The example in step 2 is a great example. The `wordcount` field is defined by the [gatsby-transformer-remark](TODO) plugin, rather than created during the transformation of the remark node. + +The [nodesPromise](TODO) function iterates over all nodes of this type. Then, for each node, [resolveRecursive](TODO) descends the `siftToFields` tree, getting the field name, and then finding its gqlType, and then calling that type's `resolve` function manually. E.g, for the above example, we would find the gqlField for `wordcount` and call its resolve field: + +```javascript +markdownRemarkGqlType.resolve(node, {}, {}, { fieldName: `wordcount` }) +``` + +Note that the [graphql-js](TODO) library has NOT been invoked yet. We're instead calling the appropriate gqlType resolve function manually. + +The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). + +After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. + +### 4. Track newly inlined fields + +TODO: I think what's going on here is that nodes when created, have explicit objects on them already. And there must be a step that links all these sub objects to the root node. But, the fields that are realized in the above step haven't been created before. So we need to track them explicitly. Feels a bit cray cray, but legit I guess. + +TODO: Create proper node tracking doc in other .md + +### 5. Run sift query on all nodes + +Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the query to all those nodes. This step is handled by [tempPromise](TODO). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. + +### 6. Create Page dependency if required + +Assuming we find a node, we finish off by recording the page that initiated the query (in the `path` field depends on the found node. More on this in [create page dependency](TODO) + +TODO: Due to how it operates over all fields, gets the value, and then applies the query to those results, if plugins perform side effects, won't the all be applied, even if only some nodes actually match query? Shouldn't plugin authors be aware of this? diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 73de06cc0b73f..f70406199cf64 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -260,6 +260,15 @@ link: /docs/data-storage-redux/ - title: Schema Generation link: /docs/schema-generation-behind-the-scenes/ + items: + - title: Building the gqlType + link: /docs/schema-gql-type + - title: Building the input filters + link: /docs/schema-input-gql + - title: How sift resolvers work + link: /docs/schema-sift + - title: Page Creation + link: /docs/page-creation/ - title: Build Caching* link: /docs/build-caching/ - title: Advanced Tutorials From ee5c233c4157549d125425b0b44786a802739265 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 28 Aug 2018 14:50:30 +1000 Subject: [PATCH 24/39] doc updates --- .../schema-generation-behind-the-scenes.md | 28 +++---- docs/docs/schema-gql-type.md | 83 ++++++++----------- docs/docs/schema-input-gql.md | 12 +-- docs/docs/schema-sift.md | 36 ++++---- www/src/data/sidebars/doc-links.yaml | 6 +- 5 files changed, 78 insertions(+), 87 deletions(-) diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 775542a1975d2..a002d687f1b27 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -2,17 +2,15 @@ title: Schema Generation --- -## Build-node-types - Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. We must infer a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how that occurs. -### 1. Group all nodes by type +### Group all nodes by type Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g `PostsJson` for a `posts.json` file. During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. -The flow is summarized by the below graph. It shows the intermediate transformations that are performed by various files in the [schema folder](TODO), finally resulting in the `ProcessedNoteType`. The below transformations occur for each of the uniq `internal.type` values. +The flow is summarized by the below graph. It shows the intermediate transformations that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNoteType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. ```dot digraph graphname { @@ -39,27 +37,29 @@ digraph graphname { } ``` -### 2. Plugins create custom fields +### For each unique Type + +The majority of schema generation code occurs in [build-node-types.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js). The below steps will be executed for each unique type. + +#### 1. Plugins create custom fields Gatsby infers GraphQL Types from the fields on the sourced and transformed nodes. But before that, we allow plugins to create their own custom fields. For example, `source-filesystem` creates a [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L11) field that when resolved, will copy the file into the `public/static` directory and return the new path. To declare custom fields, plugins implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API and apply the change only to types that they care about (e.g source-filesystem [only proceeds if type.name = `File`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L6). During schema generation, Gatsby will call this API, allowing the plugin to declare these custom fields, [which are returned](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L151) to the main schema process. -### Create a "GQLType" - -This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 2). This step is explained in detail in [GraphQL Node Types Creation](TODO). +#### 2. Create a "GQLType" -### Create Input filters +This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 2). This step is explained in detail in [GraphQL Node Types Creation](/docs/schema-gql-type). -This step creates GraphQL input filters for each field so the objects can be queried by them. More details in [schema-input-gql](TODO). +#### 3. Create Input filters -### ProcessedTypeNode creation with resolve implementation +This step creates GraphQL input filters for each field so the objects can be queried by them. More details in [Building the Input Filters](/docs/schema-input-gql). -Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the args and gqlType created above, and implements a resolve function for it using sift. More detail in the [GraphQL Queries over redux](TODO) section. +#### 4. ProcessedTypeNode creation with resolve implementation -TODO: Mention how the terminology can get confusing here. Fields, objects, inputs, etc. +Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the input filters and gqlType created above, and implements a resolve function for it using sift. More detail in the [Querying with Sift](/docs/schema-sift) section. -### ThirdParty Schema +#### ThirdParty Schema TODO diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index 1997f24872feb..fd6e3418eb740 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -2,7 +2,7 @@ title: GraphQL Node Types Creation --- -Gatsby creates a [GraphQLObjectType](TODO) for each distinct `node.internal.type` that is created during the source-nodes phase. Find out below how this is done. +Gatsby creates a [GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype) for each distinct `node.internal.type` that is created during the source-nodes phase. Find out below how this is done. ## GraphQL Types for each type of node @@ -11,20 +11,22 @@ When running a GraphQL query, there are a variety of fields that you will want t ```graphql query { file( relativePath: { eq: `blogs/2018-08-23/announcing-v2.md` } ) { - frontmatter: { - title + childMarkdownRemark { + frontmatter: { + title + } } } } ``` -When GraphQL runs, it will query all `markdownRemark` nodes by the wordcount expression and return a node that satisfied the query. Then, it will filter down the fields to return by the inner expression. I.e `{ frontmatter: { title } }`. It is the schema that allows the filtering that this section deals with. The creation of the query arguments is dealt with by [schema-gql-input](TODO). +When GraphQL runs, it will query all `file` nodes by their relativePath and return the first node that satisfies that query. Then, it will filter down the fields to return by the inner expression. I.e `{ childMarkdownRemark ... }`. The building of the query arguments is covered by the [Inferring Input Filters](/docs/schema-input-gql) doc. This section instead explains how the inner filter schema is generated. -During the [source-nodes](TODO) phase, let's say that [gatsby-source-filesystem](TODO) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](TODO), resulting in children of different `node.internal.type`s being created. +During the [sourceNodes](/docs/node-apis/#sourceNodes) phase, let's say that [gatsby-source-filesystem](/packages/gatsby-source-filesystem) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](/docs/node-apis/#onCreateNode), resulting in children of different `node.internal.type`s being created. -When querying the File nodes, there are 3 categories of fields that we'll be querying: +There are 3 categories of node fields that we can query. -- Fields on the node object. E.g +#### Fields on the node object. E.g ```graphql node { @@ -35,7 +37,7 @@ node { } ``` -- Child/Parent. E.g: +#### Child/Parent. E.g: ```graphql node { @@ -46,21 +48,23 @@ node { } ``` -- fields created by plugins +#### fields created by plugins ```graphql node { publicURL } ``` + +Each of these categories of fields is created in a different way, explained below. ## gqlType Creation -The Gatsby term for the GraphQLObjectType for a unique node type, is `gqlType`. GraphQLObjectTypes are simply objects that define the type name and fields. The field definitions are created by the [createNodeFields](TODO) function in `build-node-types.js`. +The Gatsby term for the GraphQLObjectType for a unique node type, is `gqlType`. GraphQLObjectTypes are simply objects that define the type name and fields. The field definitions are created by the [createNodeFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L48) function in `build-node-types.js`. -An important thing to note is that all gqlTypes are created before their fields are inferred. This allows fields to be of types that haven't yet been created due to their order of compilation. This is accomplished by the use of `fields` [being a function](TODO). +An important thing to note is that all gqlTypes are created before their fields are inferred. This allows fields to be of types that haven't yet been created due to their order of compilation. This is accomplished by the use of `fields` [being a function](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L167). -The first step in inferring GraphQL Fields is to generate an `exampleValue`. It is the result of merging all fields of all nodes of the type in question. This `exampleValue` will therefore contain all potential field names and values, which allows us to infer each field's types. The logic to create this exampleValue is in [getExampleValues](TODO). +The first step in inferring GraphQL Fields is to generate an `exampleValue`. It is the result of merging all fields of all nodes of the type in question. This `exampleValue` will therefore contain all potential field names and values, which allows us to infer each field's types. The logic to create this exampleValue is in [getExampleValues](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L305). With the exampleValue in hand, we can use each of its key/values to infer the Type's fields (broken down by the 3 categories above). @@ -68,15 +72,15 @@ With the exampleValue in hand, we can use each of its key/values to infer the Ty Fields on the node that were created directly by the source and transform plugins. E.g for `File` type, these would be `relativePath`, `size`, `accessTime` etc. -The creation of these fields is handled by the [inferObjectStructureFromNodes](TODO) function in `infer-graphql-type.js`. Given an object, a field could be in one of 3 sub-categories: +The creation of these fields is handled by the [inferObjectStructureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L317) function in [infer-graphql-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js). Given an object, a field could be in one of 3 sub-categories: -1. It involves a mapping in [gatsby-config.js](TODO) -2. It's value is a foreign key refernce to some other node (ends in `___NODE`) +1. It involves a mapping in [gatsby-config.js](/docs/gatsby-config/#mapping-node-types) +2. It's value is a foreign key reference to some other node (ends in `___NODE`) 3. It's a plain object or value (e.g String, number, etc) #### Mapping field -Mappings are explained in the [gatsby-config.js docs](TODO). If the object field we're generating a GraphQL type for is configured in the gatsby-config mapping, then we handle it specially. +Mappings are explained in the [gatsby-config.js docs](/docs/gatsby-config/#mapping-node-types). If the object field we're generating a GraphQL type for is configured in the gatsby-config mapping, then we handle it specially. Imagine our top level Type we're currently generating fields for is `MarkdownRemark.frontmatter`. And the field we are creating a GraphQL field for is called `author`. And, that we have a mapping setup of: @@ -86,13 +90,13 @@ mapping: { }, ``` -The field generaton in this case is handled by [inferFromMapping](TODO). The first step is to find the type that is mapped to. In this case, `AuthorYaml`. This is known as the `linkedType`. That type will have a field to link by. In this case `name`. If one is not supplied, it defaults to `id`. This field is known as `linkedField` +The field generaton in this case is handled by [inferFromMapping](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L129). The first step is to find the type that is mapped to. In this case, `AuthorYaml`. This is known as the `linkedType`. That type will have a field to link by. In this case `name`. If one is not supplied, it defaults to `id`. This field is known as `linkedField` Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which we look up in list of other `gqlTypes`). The field resolver will get the value for the node (in this case, the author string), and then search through the react nodes till it finds one whose type is `AuthorYaml` and whose `name` field matches the author string. #### Foreign Key reference (`___NODE`) -If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is actually a foreign key reference to another node in redux. Check out the [source-plugin docs](TODO) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](TODO). +If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is a foreign key reference to another node in redux. Check out the [Create a Source Plugin](/docs/create-source-plugin/#create-source-plugin) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L204). This is actually quite similar to the mapping case above. We remove the `___NODE` part of the field name. E.g `author___NODE` would become `author`. Then, we find our `linkedNode`. I.e given the example value for `author` (which would be an ID), we find its actual node in redux. Then, we find its type in processed types by its `internal.type`. Note, that also like in mapping fields, we can define the `linkedField` too. This can be specified via `nodeFieldname___NODE___linkedFieldName`. E.g for `author___NODE___name`, the linkedField would be `name` instead of `id`. @@ -104,19 +108,19 @@ If the foreign key value is an array of IDs, then instead of returning a Field d #### Plain object or value field -If the field was not handled as a mapping or foreign key reference, then it must be a normal every day field. E.g a scalar, string, or plain object. These cases are handled by [inferGraphQLType](TODO). +If the field was not handled as a mapping or foreign key reference, then it must be a normal every day field. E.g a scalar, string, or plain object. These cases are handled by [inferGraphQLType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L38). The core of this step creates a GraphQL Field object, where the type is inferred directly via the result of `typeof`. E.g `typeof(value) === 'boolean'` would result in type `GraphQLBoolean`. Since these are simple values, resolvers are not defined (graphql-js takes care of that for us). -If however, the value is an object or array, we recurse, using [inferObjectStructureFromNodes](TODO) to create the GraphQL fields. +If however, the value is an object or array, we recurse, using [inferObjectStructureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L317) to create the GraphQL fields. -in addition, if a value is a string that actually points to a file, then we hand off field inference to [types/file-type](TODO) (TODO: Dive into why). The same goes for Strings that look like dates. These are handled by [types/date-type](TODO) +in addition, if a value is a string that actually points to a file, then we hand off field inference to [types/file-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js) which creates a new type that resolves File-like strings to File types. The same goes for Strings that look like dates. These are handled by [types/date-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-date.js) which creates a `GraphQLDate` custom type. ### Child/Parent fields #### Child fields creation -Let's say we're creating a type for `File`. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. +Let's continue with the `File` type example. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](TODO)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: @@ -128,33 +132,29 @@ Gatsby stores these children in redux as IDs in the parent's `children` field. A } ``` -An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. The benefit of this is that we can easily create a `File.children` field that returns all children, regardless of type. The downside is that the creation of fields such as `File.childrenMarkdownRemark` and `File.childrenPostsJson` is more complicated. This is what [build-node-types](TODO) does. +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. The benefit of this is that we can easily create a `File.children` field that returns all children, regardless of type. The downside is that the creation of fields such as `File.childMarkdownRemark` and `File.childrenPostsJson` is more complicated. This is what [createNodeFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L48) does. -Another convenience Gatsby provides is the ability to query a node's `child` or `children`, depending on whether a parent node has 1 or more children of that type. This inference is also made by `build-node-types`. +Another convenience Gatsby provides is the ability to query a node's `child` or `children`, depending on whether a parent node has 1 or more children of that type. #### child resolvers -When defining our parent `File` gqlType, [build-node-types](TODO) will iterate over the distinct types of its children, and create their fields. Let's say one of these child types is `markdownRemark`. Let's assume there is only one `markdownRemark` child per `File`. Therefore, its field name is `childMarkdownRemark`. Now, we must create its graphql Resolver. +When defining our parent `File` gqlType, [createNodeFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L48) will iterate over the distinct types of its children, and create their fields. Let's say one of these child types is `markdownRemark`. Let's assume there is only one `markdownRemark` child per `File`. Therefore, its field name is `childMarkdownRemark`. Now, we must create its graphql Resolver. ``` resolve(node, args, context, info) ``` -The resolve function will be called when we eventually are running queries for our pages. A query might look like: +The resolve function will be called when we are running queries for our pages. A query might look like: ```graphql - query { - allFile { - edges { - node { - childMarkdownRemark { title } - } - } - } +query { + file( relativePath { eq: "blog/my-blog.md" } ) { + childMarkdownRemark { html } } +} ``` -To resolve `node.childMarkdownRemark`, we filter over all the node's (that we're resolving) `children`, until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. +To resolve `file.childMarkdownRemark`, we filter over all the node's (that we're resolving) `children`, until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. But before we return from the resolve function, remember that we might be running this query within the context of a page. If that's the case, then whenever the node changes, the page will need to be rerendered. To record that fact, we call call [createPageDependency](TODO) with the node ID and the page, which is a field in the `context` object in the resolve function signature. @@ -164,16 +164,5 @@ When a node is created as a child of some node, that fact is stored as a `parent ### Plugin fields -These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](TODO) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. - -## TODO GQLType related to Schema/connections - -#### NOTES - -TODO: If haven't already. Talk about how all the field generation occurs after we've defined all the gqlTypes themselves first. - -TODO wat is linkedField?? --------- - -TODO: Add to section where links between node and path are created. build-node-types.js:56. +These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. diff --git a/docs/docs/schema-input-gql.md b/docs/docs/schema-input-gql.md index 0579d9f7912b8..7a2df45ea911e 100644 --- a/docs/docs/schema-input-gql.md +++ b/docs/docs/schema-input-gql.md @@ -4,7 +4,7 @@ title: Inferring Input Filters ## Input Filters vs gqlType -Up until now, we've inferred a Gatsby Node's main fields ([gqlType](TODO)). These allow us to query a node's children, parent, object fields. But these are only useful inside a GraphQL Query. Before we get there, we have to be able to query the types we have already created by their fields. E.g, querying for all markdownRemark nodes that have 4 paragraphs. +In [gqlTypes](/docs/schema-gql-type), we inferred a Gatsby Node's main fields. These allow us to query a node's children, parent and object fields. But these are only useful once a top level GraphQL Query has returned results. In order to query by those fields, we must create GraphQL objects for input filters. E.g, querying for all markdownRemark nodes that have 4 paragraphs. ```graphql { @@ -14,7 +14,7 @@ Up until now, we've inferred a Gatsby Node's main fields ([gqlType](TODO)). Thes } ``` -The arguments (`wordcount: {paragraphs: {eq: 4}}`) to the query are known as Input filters. In GraphQL Land, they are the [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). This section covers how these Input filters are inferred. +The arguments (`wordcount: {paragraphs: {eq: 4}}`) to the query are known as Input filters. In graphql-js, they are the [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). This section covers how these Input filters are inferred. ### Inferring input filters from example node values @@ -26,9 +26,9 @@ The first step is to generate an input field for each type of field on the redux } ``` -This step is handled by [inferInputObjectStrctureFromNodes](TODO). First, we generate an example Value (see [gqlTypes](TODO)). For each field on the example value (e.g `author`), we create a [GraphQLInputObjectType](TODO) with an appropriate name. The fields for Input Objects are predicates that depend on the value's `typeof` result. E.g for a String, we need to be able to query by `eq`, `regex` etc. If the value is an object itself, then we recurse, building its fields as above. +This step is handled by [inferInputObjectStrctureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L235). First, we generate an example Value (see [gqlTypes](/docs/schema-gql-type#gqltype-creation)). For each field on the example value (e.g `author`), we create a [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype) with an appropriate name. The fields for Input Objects are predicates that depend on the value's `typeof` result. E.g for a String, we need to be able to query by `eq`, `regex` etc. If the value is an object itself, then we recurse, building its fields as above. -If the key is a foreign key reference (ends in `___NODE`), then we find the field's linked Type first, and progress as above (for more on how foreign keys are implemented, see [gqlType](TODO)). After this step, we will end up with an Input Object type such as . +If the key is a foreign key reference (ends in `___NODE`), then we find the field's linked Type first, and progress as above (for more on how foreign keys are implemented, see [gqlType](/docs/schema-gql-type#foreign-key-reference-___node)). After this step, we will end up with an Input Object type such as . ```javascript { @@ -63,7 +63,7 @@ Plugins themselves have the opportunity to create custom fields that apply to AL } ``` -Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](TODO) API. They must return a full GraphQLObjectType, complete with `resolve` function. Once this API has been run, the fields are passed to [inferInputObjectStructureFromFields](TODO), which will generate input filters for thew new fields. The result would look something like: +Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. They must return a full GraphQLObjectType, complete with `resolve` function. Once this API has been run, the fields are passed to [inferInputObjectStructureFromFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js#L195), which will generate input filters for thew new fields. The result would look something like: ```javascript { //GraphQLInputObjectType @@ -87,7 +87,7 @@ Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](TODO) } ``` -As usual, the input filter fields are based on the type of the field, which is defined by the plugin. +As usual, the input filter fields (`eq`, `lt`, `gt`, etc) are based on the type of the field (`Int` in this case), which is defined by the plugin. ### Merged result diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 967dbe24f0495..7f9f0bbe81d81 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -1,31 +1,31 @@ --- -title: GraphQL Queries over redux +title: Querying with Sift --- ## Summary Gatsby stores all data loaded during the source-nodes phase in redux. And it allows you to write GraphQL queries to query that data. But Redux is a plain javascript object store. So how does Gatsby query over those nodes using the GraphQL Query language? -The answer is that it uses the [sift.js](TODO) library. It is a port of the MongoDB query language that works over plain javascript objects. The logic for this is in the [run-sift.js](TODO) file, which is called from the [ProcessedNodeType] `resolve()` function. +The answer is that it uses the [sift.js](https://github.com/crcn/sift.js/tree/master) library. It is a port of the MongoDB query language that works over plain javascript objects. It turns out that mongo's query language is very compatible with GraphQL. -TODO: Mention this is different from `allTypeFields`. This just finds one result. +Most of the logic below is in the the [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js) file, which is called from the [ProcessedNodeType `resolve()`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L191) function. ## ProcessedNodeType Resolve Function -Remember, at the point this resolve function is created, we have been iterating over all the distinct `internal.node.type`s in the redux `nodes` namespace. So for instance we might be on the `markdownRemark` type. Therefore the `resolve()` function closes over this type name. +Remember, at the point this resolve function is created, we have been iterating over all the distinct `node.internal.type`s in the redux `nodes` namespace. So for instance we might be on the `MarkdownRemark` type. Therefore the `resolve()` function closes over this type name and has access to all the nodes of that type. -The `resolve()` function calls `run-sift`, and provides it with the following arguments: +The `resolve()` function calls `run-sift.js`, and provides it with the following arguments: - GraphQLArgs (as js object). Within a filter. E.g `wordcount: { paragraphs: { eg: 4 } }` -- All nodes in redux of this type. E.g where `internal.type == 'markdownRemark'` +- All nodes in redux of this type. E.g where `internal.type == MmarkdownRemark'` - Context `path`, if present - typeName. E.g `markdownRemark` -- gqlType. See [more on gqlType](TODO) +- gqlType. See [more on gqlType](/docs/schema-gql-type) For example: ```javascript -{ +runSift({ args: { filter: { // Exact args from GraphQL Query wordcount: { @@ -39,7 +39,7 @@ For example: path: context.path, // E.g /blog/2018-08-23/introducing-v2 typeName: `markdownRemark`, type: ${gqlType} -} +}) ``` ## Run-sift.js @@ -56,11 +56,11 @@ This file converts GraphQL Arguments into sift queries and applies them to the c ### 1. Convert query args to sift args -Sift expects all field names to be prepended by a `$`. The [siftify-args](TODO) function takes care of this. It descends the args tree, performing the following transformations for each field key/value scenario. +Sift expects all field names to be prepended by a `$`. The [siftify-args](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L58) function takes care of this. It descends the args tree, performing the following transformations for each field key/value scenario. - field key is`elemMatch`? Change to `$elemMatch`. Recurse on value object - field value is regex? Apply regex cleaning -- field value is globl, use [minimatch](TODO) library to convert to Regex +- field value is globl, use [minimatch](https://www.npmjs.com/package/minimatch) library to convert to Regex - normal value, prepend `$` to field name. So, the above query would become: @@ -77,7 +77,7 @@ So, the above query would become: ### 2. Drop leaves (e.g `{eq: 4}`) from args -To assist in step 3, we create a version of the siftified args called `fieldsToSift` that has all leaves of the args tree replaced with boolean `true`. This is handled by the [extractFieldsToSift](TODO) function. `fieldsToSift` would look like this after the function is applied: +To assist in step 3, we create a version of the siftified args called `fieldsToSift` that has all leaves of the args tree replaced with boolean `true`. This is handled by the [extractFieldsToSift](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L84) function. `fieldsToSift` would look like this after the function is applied: ```javascript { @@ -89,15 +89,15 @@ To assist in step 3, we create a version of the siftified args called `fieldsToS ### 3. Resolve inner query fields on all nodes -Step 4 will perform the actual sift query over all the nodes, returning the first one that matches the query. But we must remember that the nodes that are in redux only include data that was explicitly created by their source or transform plugins. If instead of creating a data field, a plugin used `setFieldsOnGraphQLNodeType` to define a custom field (see [gql-type](TODO)), then we have to manually call that field's resolver on each node. The example in step 2 is a great example. The `wordcount` field is defined by the [gatsby-transformer-remark](TODO) plugin, rather than created during the transformation of the remark node. +Step 4 will perform the actual sift query over all the nodes, returning the first one that matches the query. But we must remember that the nodes that are in redux only include data that was explicitly created by their source or transform plugins. If instead of creating a data field, a plugin used `setFieldsOnGraphQLNodeType` to define a custom field, then we have to manually call that field's resolver on each node. The args in step 2 is a great example. The `wordcount` field is defined by the [gatsby-transformer-remark](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-remark/src/extend-node-type.js#L416) plugin, rather than created during the creation of the remark node. -The [nodesPromise](TODO) function iterates over all nodes of this type. Then, for each node, [resolveRecursive](TODO) descends the `siftToFields` tree, getting the field name, and then finding its gqlType, and then calling that type's `resolve` function manually. E.g, for the above example, we would find the gqlField for `wordcount` and call its resolve field: +The [nodesPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L168) function iterates over all nodes of this type. Then, for each node, [resolveRecursive](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L112) descends the `siftToFields` tree, getting the field name, and then finding its gqlType, and then calling that type's `resolve` function manually. E.g, for the above example, we would find the gqlField for `wordcount` and call its resolve field: ```javascript markdownRemarkGqlType.resolve(node, {}, {}, { fieldName: `wordcount` }) ``` -Note that the [graphql-js](TODO) library has NOT been invoked yet. We're instead calling the appropriate gqlType resolve function manually. +Note that the graphql-js library has NOT been invoked yet. We're instead calling the appropriate gqlType resolve function manually. The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). @@ -111,10 +111,12 @@ TODO: Create proper node tracking doc in other .md ### 5. Run sift query on all nodes -Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the query to all those nodes. This step is handled by [tempPromise](TODO). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. +Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the sift query to all those nodes. This step is handled by [tempPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L214). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. ### 6. Create Page dependency if required Assuming we find a node, we finish off by recording the page that initiated the query (in the `path` field depends on the found node. More on this in [create page dependency](TODO) -TODO: Due to how it operates over all fields, gets the value, and then applies the query to those results, if plugins perform side effects, won't the all be applied, even if only some nodes actually match query? Shouldn't plugin authors be aware of this? +## Note about plugin resolve side effects + +As [mentioned above](#3-resolve-inner-query-fields-on-all-nodes), `run-sift` must "realize" all query fields before querying over them. This involves calling the resolvers of custom plugins on **each node of that type**. Therefore, if a resolver performs side effects, then these will be triggered, regardless of whether the field result actually matches the query. diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index f70406199cf64..cc379b3c446d9 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -261,11 +261,11 @@ - title: Schema Generation link: /docs/schema-generation-behind-the-scenes/ items: - - title: Building the gqlType + - title: Building the GqlType link: /docs/schema-gql-type - - title: Building the input filters + - title: Building the Input Filters link: /docs/schema-input-gql - - title: How sift resolvers work + - title: Querying with Sift link: /docs/schema-sift - title: Page Creation link: /docs/page-creation/ From 5a90ca48c5affa7a85d8f76c47b56e6bfeb8222c Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 28 Aug 2018 14:52:18 +1000 Subject: [PATCH 25/39] doc update --- docs/docs/behind-the-scenes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md index a3ba4be6ccd2c..188a3e00c24d6 100644 --- a/docs/docs/behind-the-scenes.md +++ b/docs/docs/behind-the-scenes.md @@ -5,3 +5,5 @@ title: Behind the Scenes Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works. If you're looking for information on how to _use_ Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. + +These docs aren't supposed to be definitive, or tell you everything there is to know. But as you're exploring the Gatsby codebase, you might find yourself wondering what a concept means, or which part of the codebase implements a particular idea. These docs aim to answer those kinds of questions. From 79ae696d0bf4d9cd8601c99f50bb2189d69f446b Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 28 Aug 2018 18:56:21 +1000 Subject: [PATCH 26/39] schema docs update --- docs/docs/how-plugins-apis-are-run.md | 4 +- docs/docs/node-creation-behind-the-scenes.md | 47 +++++++++++++++++-- docs/docs/schema-connections.md | 8 ++++ .../schema-generation-behind-the-scenes.md | 7 --- docs/docs/schema-gql-type.md | 2 +- www/src/data/sidebars/doc-links.yaml | 2 + 6 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 docs/docs/schema-connections.md diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index fc3a3d7435d0b..72d5f4ea8cda3 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -4,7 +4,7 @@ title: How APIs/plugins are run For most sites, plugins take up the majority of the build time. So what's really happening when APIs are called? -Note, this section only explains how `gatsby-node` plugins are run. Not browser or ssr plugins. +_Note: this section only explains how `gatsby-node` plugins are run. Not browser or ssr plugins_ ## Early in the build @@ -52,7 +52,7 @@ All actions take 3 arguments: - **traceId**: See below - **parentSpan**: opentracing span (see [tracing docs](/docs/performance-tracing/)) -Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind inject actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. +Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind injected actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. ## Waiting for all plugins to run diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 2a83609ce07e9..4325c207f0d86 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -10,6 +10,49 @@ A node is stored in redux under the `nodes` namespace, whose state is a map of t Nodes are created in Gatsby by calling [createNode](/docs/actions/#createNode). This happens primarily in the [sourceNodes](/docs/node-apis/#sourceNodes) bootstrap phase. Nodes created during this phase are top level nodes. I.e, they have no parent. This is represented by source plugins setting the node's `parent` field to `___SOURCE___`. Nodes created via transform plugins (who implement [onCreateNode](/docs/node-apis/#onCreateNode)) will have source nodes as their parents, or other transformed nodes. For a rough overview of what happens when source nodes run, see the [traceID illustration](/docs/how-plugins-apis-are-run/#using-traceid-to-await-downstream-api-calls). +## Parent/Child/Refs + +There are a few different scenarios for creating parent/child relationships. + +### Node relationship storage model + +Gatsby stores child nodes in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves. E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: + +```javascript +{ + `id1`: { type: `File`, children: [`id2`, `id3`], ...other_fields }, + `id2`: { type: `markdownRemark`, ...other_fields }, + `id3`: { type: `postsJson`, ...other_fields } +} +``` + +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. + +### Explicitly recording a parent/child relationship + +This occurs when a transformer plugin implements [onCreateNode](/docs/node-apis/#onCreateNode) in order to create some child of the originally created node. In this case, the transformer plugin will call [createParentChildLink](/docs/actions/#createParentChildLink), with the original node, and the newly created node. All this does is push the child's node ID onto the parent's `children` collection and resave the parent to redux. + +This does **not** automatically create a `parent` field on the child node. If a plugin author wishes to allow child nodes to navigate to their parents in GraphQL queries, they must explicitly set `childNode.parent: 'parent.id'` when creating the child node. + +### Foreign Key reference (`___NODE`) + +We've established that child nodes are stored at the top level in redux, and are referenced via ids in their parent's `children` collection. The same mechanism drives foreign key relationships. Foreign key fields have a `___NODE` suffix on the field name. At query time, Gatsby will take the field's value as an ID, and search redux for a matching node. This is explained in more detail in [schema gqlTypes](/docs/schema-gql-type#foreign-key-reference-___node). + +### Plain objects at creation time + +Let's say you create the following node by passing it to `createNode` + +```javascript +{ + foo: 'bar', + baz: { + car: 10 + } +} +``` + +The value for `baz` is itself an object. That value's parent is the top level object. In this case, Gatsby simply saves the top level node as is to redux. It doesn't attempt to extract `baz` into its own node. During schema compilation, Gatsby will infer the sub object's type while [creating the gqlType](/docs/schema-gql-type#plain-object-or-value-field). + ## Fresh/stale nodes Every time a build is re-run, there is a chance that a node that exists in the redux store no longer exists in the original data source. E.g a file might be deleted from disk between runs. We need a way to indicate that fact to Gatsby. @@ -25,7 +68,3 @@ Any nodes that aren't touched by the end of the `source-nodes` phase, are delete From a site developer's point of view, nodes are immutable. In the sense that if you simply change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to redux via an action. So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. - -## `${field}___NODE` fields - -You may notice references to node fields ending in `___NODE`. These signify foreign key relationships to some other node. For more info on how to use this, see the [create-source-plugin](/docs/create-source-plugin/) tutorial. diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md new file mode 100644 index 0000000000000..d1556a2a39a75 --- /dev/null +++ b/docs/docs/schema-connections.md @@ -0,0 +1,8 @@ +--- +title: Schema connections +--- + +This is a stub. Help our community expand it. + +Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your +pull request gets accepted. diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index a002d687f1b27..6adc047676c22 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -58,10 +58,3 @@ This step creates GraphQL input filters for each field so the objects can be que #### 4. ProcessedTypeNode creation with resolve implementation Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the input filters and gqlType created above, and implements a resolve function for it using sift. More detail in the [Querying with Sift](/docs/schema-sift) section. - -#### ThirdParty Schema - -TODO - -TODO: What is elemMatch? - diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index fd6e3418eb740..7737ec85e6279 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -122,7 +122,7 @@ in addition, if a value is a string that actually points to a file, then we hand Let's continue with the `File` type example. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. -Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](TODO)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: +Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](/docs/node-creation-behind-the-scenes/#node-relationship-storage-model)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: ```javascript { diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index cc379b3c446d9..7a3635fc840b4 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -267,6 +267,8 @@ link: /docs/schema-input-gql - title: Querying with Sift link: /docs/schema-sift + - title: Connections* + link: /docs/schema-connections - title: Page Creation link: /docs/page-creation/ - title: Build Caching* From 1c4fb58cfb3c601c10ee405e0697c6bad50a3fe1 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 28 Aug 2018 18:58:47 +1000 Subject: [PATCH 27/39] formatting --- docs/docs/node-creation-behind-the-scenes.md | 2 +- docs/docs/recipes.md | 68 ++++++++++--------- .../schema-generation-behind-the-scenes.md | 2 +- docs/docs/schema-gql-type.md | 23 +++---- docs/docs/schema-input-gql.md | 13 ++-- docs/docs/schema-sift.md | 25 ++++--- 6 files changed, 66 insertions(+), 67 deletions(-) diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index 4325c207f0d86..e41aceb04635e 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -26,7 +26,7 @@ Gatsby stores child nodes in redux as IDs in the parent's `children` field. And } ``` -An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. ### Explicitly recording a parent/child relationship diff --git a/docs/docs/recipes.md b/docs/docs/recipes.md index f1d1cd57ddf2c..7ed446fb491f8 100644 --- a/docs/docs/recipes.md +++ b/docs/docs/recipes.md @@ -14,9 +14,10 @@ Links: And yeah — those three things are exactly what we're thinking. A first step would be to just go through the tutorial and pull out all the basic things we teach there in a condensed form e.g. creating a site, creating a page, linking between pages, etc. --> -Craving a happy medium between doing the [full tutorial](/tutorial/) and crawling the [full docs]((/tutorial/))? Here's a quick guiding reference for how to build things, Gatsby style. +Craving a happy medium between doing the [full tutorial](/tutorial/) and crawling the [full docs](<(/tutorial/)>)? Here's a quick guiding reference for how to build things, Gatsby style. ## Table of Contents + - [Using a starter](#using-a-starter) - [Creating pages](#creating-pages) - [Linking between pages](#linking-between-pages) @@ -31,73 +32,74 @@ Craving a happy medium between doing the [full tutorial](/tutorial/) and crawlin Starters are boilerplate Gatsby sites maintained officially, or by the community. -* Learn how to use the Gatsby CLI tool to use starters in [tutorial part one](/tutorial/part-one/#using-gatsby-starters) -* See a list of [official and community starters](/docs/gatsby-starters/) -* Check out Gatsby's [official default starter](https://github.com/gatsbyjs/gatsby-starter-default) +- Learn how to use the Gatsby CLI tool to use starters in [tutorial part one](/tutorial/part-one/#using-gatsby-starters) +- See a list of [official and community starters](/docs/gatsby-starters/) +- Check out Gatsby's [official default starter](https://github.com/gatsbyjs/gatsby-starter-default) ## Creating pages -You can create pages in Gatsby explicitly by definining React components in `src/pages/`, or programmatically by using the `createPages` API. +You can create pages in Gatsby explicitly by definining React components in `src/pages/`, or programmatically by using the `createPages` API. -* Walk through creating a page by defining a React component in `src/pages` in [tutorial part one](/tutorial/part-one/#familiarizing-with-gatsby-pages) -* Walk through programmatically creating pages in [tutorial part seven](/tutorial/part-seven/) -* Check out the docs overview on [creating and modifying pages](/docs/creating-and-modifying-pages/) +- Walk through creating a page by defining a React component in `src/pages` in [tutorial part one](/tutorial/part-one/#familiarizing-with-gatsby-pages) +- Walk through programmatically creating pages in [tutorial part seven](/tutorial/part-seven/) +- Check out the docs overview on [creating and modifying pages](/docs/creating-and-modifying-pages/) ## Linking between pages Routing in Gatsby relies on the `` component, a wrapper around [@reach/router's Link component](https://reach.tech/router/api/Link). -* Walk through using Gatsby's `` component in [tutorial part one](/tutorial/part-one/#linking-between-pages) -* Learn more about how `` works [in the docs](/docs/gatsby-link/) +- Walk through using Gatsby's `` component in [tutorial part one](/tutorial/part-one/#linking-between-pages) +- Learn more about how `` works [in the docs](/docs/gatsby-link/) ## Styling + There are so many ways to add styles to your website; Gatsby supports almost every possible option, through official and community plugins. -* Walk through adding global styles to an example site in [tutorial part two](/tutorial/part-two/#creating-global-styles) - * More on global styles [with standard CSS files](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-with-standard-css-files) - * More on global styles with [CSS-in-JS](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-using-css-in-js) - * More on global styles [with CSS files and no layout component](/docs/creating-global-styles/#add-global-styles-with-css-files-and-no-layout-component) -* Use the CSS-in-JS library [Glamor](/docs/glamor/) -* Use the CSS-in-JS library [Styled Components](/docs/styled-components/) -* Use [CSS Modules](/tutorial/part-two/#css-modules) +- Walk through adding global styles to an example site in [tutorial part two](/tutorial/part-two/#creating-global-styles) + - More on global styles [with standard CSS files](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-with-standard-css-files) + - More on global styles with [CSS-in-JS](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-using-css-in-js) + - More on global styles [with CSS files and no layout component](/docs/creating-global-styles/#add-global-styles-with-css-files-and-no-layout-component) +- Use the CSS-in-JS library [Glamor](/docs/glamor/) +- Use the CSS-in-JS library [Styled Components](/docs/styled-components/) +- Use [CSS Modules](/tutorial/part-two/#css-modules) ## Creating layouts To wrap pages with layouts, use normal React components. -* Walk through creating a layout component in [tutorial part three](/tutorial/part-three/#your-first-layout-component) -* Gatsby v1 approached layouts differently. If the context is helpful, learn about the [differences in v2](/blog/2018-06-08-life-after-layouts/) +- Walk through creating a layout component in [tutorial part three](/tutorial/part-three/#your-first-layout-component) +- Gatsby v1 approached layouts differently. If the context is helpful, learn about the [differences in v2](/blog/2018-06-08-life-after-layouts/) ## Deploying Showtime. -* Walk through building and deploying an example site in [tutorial part one](/tutorial/part-one/#deploying-a-gatsby-site) -* Learn how to make sure your site is configured properly to be [searchable, sharable, and properly navigable](/docs/preparing-for-site-launch/) -* Learn about [performance optimization](/docs/performance/) -* Read about [other deployment related topics](/docs/deploying-and-hosting/) +- Walk through building and deploying an example site in [tutorial part one](/tutorial/part-one/#deploying-a-gatsby-site) +- Learn how to make sure your site is configured properly to be [searchable, sharable, and properly navigable](/docs/preparing-for-site-launch/) +- Learn about [performance optimization](/docs/performance/) +- Read about [other deployment related topics](/docs/deploying-and-hosting/) ## Querying data In Gatsby, you access data through a query language called [GraphQL](https://graphql.org/). -* Walk through an example of how Gatsby's data layer [pulls data into components using GraphQL](/tutorial/part-four/#how-gatsbys-data-layer-uses-graphql-to-pull-data-into-components) -* Walk through [using Gatsby's `graphql` tag for page queries](/tutorial/part-five/#build-a-page-with-a-graphql-query) -* Read through a conceptual guide on [querying data with GraphQL in Gatsby](/docs/querying-with-graphql/) -* Learn more about the `graphql` tag -- [querying data in a Gatsby page](/docs/page-query/) -* Learn more about `` -- [querying data in (non-page) components](/docs/static-query/) +- Walk through an example of how Gatsby's data layer [pulls data into components using GraphQL](/tutorial/part-four/#how-gatsbys-data-layer-uses-graphql-to-pull-data-into-components) +- Walk through [using Gatsby's `graphql` tag for page queries](/tutorial/part-five/#build-a-page-with-a-graphql-query) +- Read through a conceptual guide on [querying data with GraphQL in Gatsby](/docs/querying-with-graphql/) +- Learn more about the `graphql` tag -- [querying data in a Gatsby page](/docs/page-query/) +- Learn more about `` -- [querying data in (non-page) components](/docs/static-query/) ## Sourcing data Data sourcing in Gatsby is plugin-driven; Source plugins fetch data from their source (e.g. the `gatsby-source-filesystem` plugin fetches data from the file system, the `gatsby-source-wordpress` plugin fetches data from the WordPress API, etc). -* Walk through an example using the `gatsby-source-filesystem` plugin in [tutorial part five](/tutorial/part-five/#source-plugins) -* Search available source plugins in the [Gatsby library](/plugins/?=source) -* Understand source plugins by building one in the [source plugin tutorial](/docs/source-plugin-tutorial/) +- Walk through an example using the `gatsby-source-filesystem` plugin in [tutorial part five](/tutorial/part-five/#source-plugins) +- Search available source plugins in the [Gatsby library](/plugins/?=source) +- Understand source plugins by building one in the [source plugin tutorial](/docs/source-plugin-tutorial/) ## Transforming data Transforming data in Gatsby is also plugin-driven; Transformer plugins take data fetched using source plugins, and process it into something more usable (e.g. JSON into JavaScript objects, markdown to HTML, and more). -* Walk through an example using the `gatsby-transformer-remark` plugin to transform markdown files [tutorial part six](/tutorial/part-six/#transformer-plugins) -* Search available transformer plugins in the [Gatsby library](/plugins/?=transformer) \ No newline at end of file +- Walk through an example using the `gatsby-transformer-remark` plugin to transform markdown files [tutorial part six](/tutorial/part-six/#transformer-plugins) +- Search available transformer plugins in the [Gatsby library](/plugins/?=transformer) diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 6adc047676c22..6705ebe26bf02 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -8,7 +8,7 @@ Once the nodes have been sourced and transformed, the next step is to generate t Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g `PostsJson` for a `posts.json` file. -During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. +During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. The flow is summarized by the below graph. It shows the intermediate transformations that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNoteType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index 7737ec85e6279..9ef19415aa933 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -22,7 +22,7 @@ When running a GraphQL query, there are a variety of fields that you will want t When GraphQL runs, it will query all `file` nodes by their relativePath and return the first node that satisfies that query. Then, it will filter down the fields to return by the inner expression. I.e `{ childMarkdownRemark ... }`. The building of the query arguments is covered by the [Inferring Input Filters](/docs/schema-input-gql) doc. This section instead explains how the inner filter schema is generated. -During the [sourceNodes](/docs/node-apis/#sourceNodes) phase, let's say that [gatsby-source-filesystem](/packages/gatsby-source-filesystem) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](/docs/node-apis/#onCreateNode), resulting in children of different `node.internal.type`s being created. +During the [sourceNodes](/docs/node-apis/#sourceNodes) phase, let's say that [gatsby-source-filesystem](/packages/gatsby-source-filesystem) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](/docs/node-apis/#onCreateNode), resulting in children of different `node.internal.type`s being created. There are 3 categories of node fields that we can query. @@ -57,7 +57,7 @@ node { ``` Each of these categories of fields is created in a different way, explained below. - + ## gqlType Creation The Gatsby term for the GraphQLObjectType for a unique node type, is `gqlType`. GraphQLObjectTypes are simply objects that define the type name and fields. The field definitions are created by the [createNodeFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L48) function in `build-node-types.js`. @@ -74,9 +74,9 @@ Fields on the node that were created directly by the source and transform plugin The creation of these fields is handled by the [inferObjectStructureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L317) function in [infer-graphql-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js). Given an object, a field could be in one of 3 sub-categories: -1. It involves a mapping in [gatsby-config.js](/docs/gatsby-config/#mapping-node-types) -2. It's value is a foreign key reference to some other node (ends in `___NODE`) -3. It's a plain object or value (e.g String, number, etc) +1. It involves a mapping in [gatsby-config.js](/docs/gatsby-config/#mapping-node-types) +2. It's value is a foreign key reference to some other node (ends in `___NODE`) +3. It's a plain object or value (e.g String, number, etc) #### Mapping field @@ -96,15 +96,13 @@ Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which #### Foreign Key reference (`___NODE`) -If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is a foreign key reference to another node in redux. Check out the [Create a Source Plugin](/docs/create-source-plugin/#create-source-plugin) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L204). +If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is a foreign key reference to another node in redux. Check out the [Create a Source Plugin](/docs/create-source-plugin/#create-source-plugin) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L204). This is actually quite similar to the mapping case above. We remove the `___NODE` part of the field name. E.g `author___NODE` would become `author`. Then, we find our `linkedNode`. I.e given the example value for `author` (which would be an ID), we find its actual node in redux. Then, we find its type in processed types by its `internal.type`. Note, that also like in mapping fields, we can define the `linkedField` too. This can be specified via `nodeFieldname___NODE___linkedFieldName`. E.g for `author___NODE___name`, the linkedField would be `name` instead of `id`. Now we can return a new GraphQL Field object, whose type is the one found above. Its resolver searches through all redux nodes until it finds one with the matching ID. As usual, it also creates a [page dependency](TODO), from the query context's path to the node ID. -If the foreign key value is an array of IDs, then instead of returning a Field declaration for a single field, we return a `GraphQLUnionType`, which is a union of all the distinct linked types in the array. - - +If the foreign key value is an array of IDs, then instead of returning a Field declaration for a single field, we return a `GraphQLUnionType`, which is a union of all the distinct linked types in the array. #### Plain object or value field @@ -120,12 +118,12 @@ in addition, if a value is a string that actually points to a file, then we hand #### Child fields creation -Let's continue with the `File` type example. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. +Let's continue with the `File` type example. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](/docs/node-creation-behind-the-scenes/#node-relationship-storage-model)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: ```javascript -{ +{ `id1`: { type: `File`, children: [`id2`, `id3`], ...other_fields }, `id2`: { type: `markdownRemark`, ...other_fields }, `id3`: { type: `postsJson`, ...other_fields } @@ -164,5 +162,4 @@ When a node is created as a child of some node, that fact is stored as a `parent ### Plugin fields -These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. - +These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. diff --git a/docs/docs/schema-input-gql.md b/docs/docs/schema-input-gql.md index 7a2df45ea911e..7b6780f4d5cab 100644 --- a/docs/docs/schema-input-gql.md +++ b/docs/docs/schema-input-gql.md @@ -8,7 +8,7 @@ In [gqlTypes](/docs/schema-gql-type), we inferred a Gatsby Node's main fields. T ```graphql { - markdownRemark(wordCount: {paragraphs: {eq: 4}}) { + markdownRemark(wordCount: { paragraphs: { eq: 4 } }) { html } } @@ -22,7 +22,9 @@ The first step is to generate an input field for each type of field on the redux ```graphql { - markdownRemark(frontmatter: {author: {eq: "F. Scott Fitzgerald"}}) { id } + markdownRemark(frontmatter: { author: { eq: "F. Scott Fitzgerald" } }) { + id + } } ``` @@ -48,7 +50,6 @@ If the key is a foreign key reference (ends in `___NODE`), then we find the fiel } } } - ``` ### Inferring input filters from plugin fields @@ -57,7 +58,7 @@ Plugins themselves have the opportunity to create custom fields that apply to AL ```graphql { - markdownRemark(wordCount: {paragraphs: {eq: 4}}) { + markdownRemark(wordCount: { paragraphs: { eq: 4 } }) { html } } @@ -69,7 +70,7 @@ Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](/docs { //GraphQLInputObjectType name: `WordCountwordcountInputObject`, fields: { - `paragraphs`: { + `paragraphs`: { type: { // GraphQLInputObjectType name: `WordCountParagraphsQueryInt`, fields: { @@ -117,7 +118,7 @@ Now that we've generated input fields from the redux nodes and from custom plugi `wordCount`: { //GraphQLInputObjectType name: `WordCountwordcountInputObject`, fields: { - `paragraphs`: { + `paragraphs`: { type: { // GraphQLInputObjectType name: `WordCountParagraphsQueryInt`, fields: { diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 7f9f0bbe81d81..e66447de99710 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -6,13 +6,13 @@ title: Querying with Sift Gatsby stores all data loaded during the source-nodes phase in redux. And it allows you to write GraphQL queries to query that data. But Redux is a plain javascript object store. So how does Gatsby query over those nodes using the GraphQL Query language? -The answer is that it uses the [sift.js](https://github.com/crcn/sift.js/tree/master) library. It is a port of the MongoDB query language that works over plain javascript objects. It turns out that mongo's query language is very compatible with GraphQL. +The answer is that it uses the [sift.js](https://github.com/crcn/sift.js/tree/master) library. It is a port of the MongoDB query language that works over plain javascript objects. It turns out that mongo's query language is very compatible with GraphQL. -Most of the logic below is in the the [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js) file, which is called from the [ProcessedNodeType `resolve()`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L191) function. +Most of the logic below is in the the [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js) file, which is called from the [ProcessedNodeType `resolve()`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L191) function. ## ProcessedNodeType Resolve Function -Remember, at the point this resolve function is created, we have been iterating over all the distinct `node.internal.type`s in the redux `nodes` namespace. So for instance we might be on the `MarkdownRemark` type. Therefore the `resolve()` function closes over this type name and has access to all the nodes of that type. +Remember, at the point this resolve function is created, we have been iterating over all the distinct `node.internal.type`s in the redux `nodes` namespace. So for instance we might be on the `MarkdownRemark` type. Therefore the `resolve()` function closes over this type name and has access to all the nodes of that type. The `resolve()` function calls `run-sift.js`, and provides it with the following arguments: @@ -46,13 +46,12 @@ runSift({ This file converts GraphQL Arguments into sift queries and applies them to the collection of all nodes of this type. The rough steps are: -1. Convert query args to sift args -1. Drop leaves from args -1. Resolve inner query fields on all nodes -1. Track newly realized fields -1. Run sift query on all nodes -1. Create Page dependency if required - +1. Convert query args to sift args +1. Drop leaves from args +1. Resolve inner query fields on all nodes +1. Track newly realized fields +1. Run sift query on all nodes +1. Create Page dependency if required ### 1. Convert query args to sift args @@ -97,13 +96,13 @@ The [nodesPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsb markdownRemarkGqlType.resolve(node, {}, {}, { fieldName: `wordcount` }) ``` -Note that the graphql-js library has NOT been invoked yet. We're instead calling the appropriate gqlType resolve function manually. +Note that the graphql-js library has NOT been invoked yet. We're instead calling the appropriate gqlType resolve function manually. -The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). +The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. -### 4. Track newly inlined fields +### 4. Track newly inlined fields TODO: I think what's going on here is that nodes when created, have explicit objects on them already. And there must be a step that links all these sub objects to the root node. But, the fields that are realized in the above step haven't been created before. So we need to track them explicitly. Feels a bit cray cray, but legit I guess. From 436166d635be61973c3cbeac779d1de3d39b1268 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 30 Aug 2018 12:21:47 +1000 Subject: [PATCH 28/39] finished schema connections doc --- docs/docs/schema-connections.md | 96 +- docs/docs/schema-sift.md | 4 +- docs/sites.yml | 4299 +++++++++++++------------- www/src/data/sidebars/doc-links.yaml | 2 +- 4 files changed, 2194 insertions(+), 2207 deletions(-) diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md index d1556a2a39a75..433386ca22e1c 100644 --- a/docs/docs/schema-connections.md +++ b/docs/docs/schema-connections.md @@ -2,7 +2,97 @@ title: Schema connections --- -This is a stub. Help our community expand it. +## What are schema connections? -Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your -pull request gets accepted. +So far in schema generation, we have covered how [GraphQL types are inferred](/docs/schema-gql-type), how [query arguments for types](/docs/schema-input-gql) are created, and how [sift resolvers](/docs/schema-sift) work. But all of these only allow querying down to a single node of a type. Schema connections is the ability to query over collections of nodes of a type. For example, if we want to query all markdown nodes by some criteria, it will allow us to write queries such as: + +```graphql +{ + allMarkdownRemark(filter: {frontmatter: {tags: {in: "wordpress"}}}) { + edges { + node { + ... + } + } + } +} +``` + +Other features covered by schema connections are aggregators and reducers such as `distinct`, `group` and `totalCount`, `edges`, `skip`, `limit`, and more. + +### Connection/Edge + +A connection is an abstraction that describes a collection of nodes of a type, and how to query and navigate through them. In the above example query, `allMarkdownRemark` is a Connection Type. Its field `edges` is analagous to `results`. Each Edge points at a `node` (in the collection of all markdownRemark nodes), but it also points to the logical `next` and `previous` nodes, relative to the `node` in the collection (meaningful if you provided a `sort` arg). + +_Fun Fact: This stuff is all based on [relay connections](https://facebook.github.io/relay/graphql/connections.htm) concepts_ + +The ConnectionType also defines input args to perform paging using the `skip/limit` pattern. The actual logic for paging is defined in the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library in [arrayconnection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/arrayconnection.js). It is invoked as the last part of the [run-sift](/docs/schema-sift#5-run-sift-query-on-all-nodes) function. To aid in paging, the ConnectionType also defines a `pageInfo` field with a `hasNextPage` field. + +The ConnectionType is defined in the [graphql-skip-limit connection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/connection.js) file. Its construction function takes a Type, and it creates a connectionType for that type. E.g passing in `MarkdownRemark` Type would result in a `MarkdownRemarkConnection` type whose `edges` field would be of type `MarkdownRemarkEdge`. + +### GroupConnection + +A GroupConnection is a Connection with extended functionality. Instead of simply providing the means to access nodes in a collection, it allows you to group those nodes by one of its fields. It _is_ a `Connection` Type itself, but with 3 new fields: `field`, `fieldValue`, and `totalCount`. It adds a new input argument to `ConnectionType` whose value can be any (possibly nested) field on the original type. + +The creation of the GroupConnection is handled in [build-connection-fields.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L57). It's added as the `group` field to the top level type connection. This is most easily shown in the below diagram. + +```dot +digraph structs { + node [shape=Mrecord]; + mdConn [ label = "{ MarkdownRemarkConnection\l (allMarkdownRemark) | pageInfo | edges | group | distinct | totalCount }" ]; + mdEdge [ label = "{ MarkdownRemarkEdge | node | next | previous }" ]; + mdGroupConn [ label = "{ MarkdownRemarkGroupConnectionConnection | pageInfo | edges | field | fieldValue | totalCount }" ]; + mdGroupConnEdge [ label = "{ MarkdownRemarkGroupConnectionEdge | node | next | previous }" ]; + mdConn:group -> mdGroupConn; + mdConn:edges -> mdEdge; + mdGroupConn:edges -> mdGroupConnEdge; +} +``` + +Let's see this in practice. Say we were trying to group all markdown nodes by their author. We would query the top level `MarkdownRemarkConnection` (`allMarkdownRemark`) which would return a `MarkdownRemarkConnection` with this new group input argument, which would return a `MarkdownRemarkGroupConnectionConnection` field. E.g: + +```graphql +{ + allMarkdownRemark { + group(field: frontmatter___author) { + fieldValue + edges { + node { + frontmatter { + title + } + }, + }, + } + } +} +``` + +#### Field enum value + +The `frontmatter___author` value is interesting. It describes a nested field. I.e, we want to group all markdown nodes by their `frontmatter.author` field. The author field in each frontmatter subobject. So why not use a period? The problem is that GraphQL doesn't allow periods in fields names, so we instead use `___`, and then in the [resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L69), we convert it back to a period. + +The second interesting thing is that `frontmatter___author` is not a string, but rather a GraphQL enum. You can verify this by using intellisense in GraphiQL to see all possible values. This implies that Gatsby has generated all possible field names. Which is true! To do this, we create an [exampleValue](/docs/schema-gql-type#gqltype-creation) and then use the [flat](https://www.npmjs.com/package/flat) library to flatten the nested object into string keys, using `___` delimeters. This is handled by the [data-tree-utils.js/buildFieldEnumValues](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L277) function. + +Note, the same enum mechanism is used for creation of `distinct` fields + +#### Group Resolver + +The resolver for the Group type is created in [build-connection-fields.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L57). It operates on the result of the core connection query (e.g `allMarkdownRemark`), which is a `Connection` object with edges. From these edges, we retrieve all the nodes (each edge has a `node` field). And now we can use lodash to group those nodes by the fieldname argument (e.g `field: frontmatter___author`). + +If sorting was specified ([see below](#sorting)), we sort the groups by fieldname, and then apply any `skip/limit` arguments using the [graph-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library. Finally we are ready to fill in our `field`, `fieldValue`, and `totalCount` fields on each group, and we can return our resolved node. + +### Input filter creation + +Just like in [gql type input filters](/docs/schema-input-gql), we must generate standard input filters on our connectiontype arguments. As a reminder, these allow us to query any fields by predicates such as `{ eq: "value" }`, or `{ glob: "foo*" }`. This is covered by the same functions (in [infer-graphql-object-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js)), except that we're passing in Connection types instead of basic types. The only difference is that we use the `sort` field ([see below](#sorting)) + +### Sorting + +A `sort` argument can be added to the `Connection` type (not the `GroupConnection` type). You can sort by any (possibly nested( field in the connection results. These are enums that are created via the same mechanism described in [enum fields](#field-enum-value). Except that the inference of these enums occurs in [infer-graphql-input-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L302). + +The Sort Input Type itself is created in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L49) and implemented by [create-sort-field.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/create-sort-field.js). The actual sorting occurs in run-sift (below). + +### Connection Resolver (sift) + +Finally, we're ready to define the resolver for our Connection type (in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L65)). This is where we come up with the name `all${type}` (e.g `allMarkdownRemark`) that is so common in Gatsby queries. The resolver is fairly simple. It uses the [sift.js](https://www.npmjs.com/package/sift) library to query across all nodes of the same type in redux. The big difference is that we supply the `connection: true` parameter to `run-sift.js` which is where sorting, and pagination is actually executed. See [Querying with Sift](/docs/schema-sift) for how this actually works. +runs diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index e66447de99710..56d2b224c32f2 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -112,9 +112,11 @@ TODO: Create proper node tracking doc in other .md Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the sift query to all those nodes. This step is handled by [tempPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L214). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. +In the case that `connection === true` (argument passed to run-sift), then instead of just choosing the first argument, we will select ALL nodes that match the sift query. If the GraphQL query specified `sort`, `skip`, or `limit` fields, then we use the [graphql-skip-limit](TODO) library to filter down to the appropriate results. See [schema-connections](TODO) for more info. + ### 6. Create Page dependency if required -Assuming we find a node, we finish off by recording the page that initiated the query (in the `path` field depends on the found node. More on this in [create page dependency](TODO) +Assuming we find a node (or multiple if `connection` === true), we finish off by recording the page that initiated the query (in the `path` field depends on the found node. More on this in [create page dependency](TODO). ## Note about plugin resolve side effects diff --git a/docs/sites.yml b/docs/sites.yml index 3b3b6f848887b..89286aefe7b40 100644 --- a/docs/sites.yml +++ b/docs/sites.yml @@ -1,19 +1,19 @@ -- title: ReactJS - main_url: 'https://reactjs.org/' - url: 'https://reactjs.org/' - source_url: 'https://github.com/reactjs/reactjs.org' - featured: true - categories: - - Web Dev - - Featured -- title: NEON - main_url: 'http://neonrated.com/' - url: 'http://neonrated.com/' - featured: true - categories: - - Gallery - - Cinema - - Featured +# - title: ReactJS +# main_url: 'https://reactjs.org/' +# url: 'https://reactjs.org/' +# source_url: 'https://github.com/reactjs/reactjs.org' +# featured: true +# categories: +# - Web Dev +# - Featured +# - title: NEON +# main_url: 'http://neonrated.com/' +# url: 'http://neonrated.com/' +# featured: true +# categories: +# - Gallery +# - Cinema +# - Featured - title: The State of European Tech main_url: 'https://2017.stateofeuropeantech.com/' url: 'https://2017.stateofeuropeantech.com/' @@ -24,41 +24,41 @@ plugins: 'gatsby-plugin-react-helmet, gatsby-plugin-react-next' built_by: Studio Lovelock built_by_url: 'http://www.studiolovelock.com/' -- title: Slite - main_url: 'https://slite.com/' - url: 'https://slite.com/' - featured: true - categories: - - Landing - - Marketing - - Technology - - Featured -- title: GraphCMS - main_url: 'https://graphcms.com/' - url: 'https://graphcms.com/' - featured: true - categories: - - Landing - - Marketing - - Technology -- title: Bottender Docs - main_url: 'https://bottender.js.org/' - url: 'https://bottender.js.org/' - source_url: 'https://github.com/bottenderjs/bottenderjs.github.io' - featured: true - categories: - - Documentation - - Web Dev - - Open Source - - Featured -- title: Cardiogram - main_url: 'https://cardiogr.am/' - url: 'https://cardiogr.am/' - featured: true - categories: - - Marketing - - Technology - - Featured +# - title: Slite +# main_url: 'https://slite.com/' +# url: 'https://slite.com/' +# featured: true +# categories: +# - Landing +# - Marketing +# - Technology +# - Featured +# - title: GraphCMS +# main_url: 'https://graphcms.com/' +# url: 'https://graphcms.com/' +# featured: true +# categories: +# - Landing +# - Marketing +# - Technology +# - title: Bottender Docs +# main_url: 'https://bottender.js.org/' +# url: 'https://bottender.js.org/' +# source_url: 'https://github.com/bottenderjs/bottenderjs.github.io' +# featured: true +# categories: +# - Documentation +# - Web Dev +# - Open Source +# - Featured +# - title: Cardiogram +# main_url: 'https://cardiogr.am/' +# url: 'https://cardiogr.am/' +# featured: true +# categories: +# - Marketing +# - Technology +# - Featured - title: Etcetera Design main_url: 'https://etcetera.design/' url: 'https://etcetera.design/' @@ -68,167 +68,167 @@ - Portfolio - Creative - Featured -- title: Hack Club - main_url: 'https://hackclub.com/' - url: 'https://hackclub.com/' - source_url: 'https://github.com/hackclub/site' - featured: true - categories: - - Education - - Web Dev - - Featured -- title: Matthias Jordan Portfolio - main_url: 'https://iammatthias.com/' - url: 'https://iammatthias.com/' - source_url: 'https://github.com/iammatthias/net' - featured: true - categories: - - Photography - - Portfolio - - Featured -- title: Investment Calculator - main_url: 'https://investmentcalculator.io/' - url: 'https://investmentcalculator.io/' - featured: true - categories: - - Education - - Featured - - Finance -- title: CSS Grid Playground by MozillaDev - main_url: 'https://mozilladevelopers.github.io/playground/' - url: 'https://mozilladevelopers.github.io/playground/' - source_url: 'https://github.com/MozillaDevelopers/playground' - featured: true - categories: - - Education - - Web Dev - - Featured -- title: Piotr Fedorczyk Portfolio - main_url: 'https://piotrf.pl/' - url: 'https://piotrf.pl/' - featured: true - categories: - - Portfolio - - Creative - - Web Dev - - Featured -- title: unrealcpp - main_url: 'https://unrealcpp.com/' - url: 'https://unrealcpp.com/' - source_url: 'https://github.com/Harrison1/unrealcpp-com' - featured: true - categories: - - Blog - - Web Dev - - Featured -- title: Andy Slezak - main_url: 'https://www.aslezak.com/' - url: 'https://www.aslezak.com/' - source_url: 'https://github.com/amslezak' - featured: true - categories: - - Web Dev - - Portfolio - - Featured -- title: Deliveroo.Design - main_url: 'https://www.deliveroo.design/' - url: 'https://www.deliveroo.design/' - featured: true - categories: - - Food - - Marketing - - Featured -- title: Dona Rita - main_url: 'https://www.donarita.co.uk/' - url: 'https://www.donarita.co.uk/' - source_url: 'https://github.com/peduarte/dona-rita-website' - featured: true - categories: - - Food - - Marketing - - Featured -- title: Fröhlich ∧ Frei - main_url: 'https://www.froehlichundfrei.de/' - url: 'https://www.froehlichundfrei.de/' - featured: true - categories: - - Web Dev - - Blog - - Open Source -- title: How to GraphQL - main_url: 'https://www.howtographql.com/' - url: 'https://www.howtographql.com/' - source_url: 'https://github.com/howtographql/howtographql' - featured: true - categories: - - Documentation - - Web Dev - - Open Source - - Featured -- title: OnCallogy - main_url: 'https://www.oncallogy.com/' - url: 'https://www.oncallogy.com/' - featured: true - categories: - - Landing - - Marketing - - Featured - - Healthcare -- title: Ryan Wiemer's Portfolio - main_url: 'https://www.ryanwiemer.com/' - url: 'https://www.ryanwiemer.com/knw-photography/' - source_url: 'https://github.com/ryanwiemer/rw' - featured: true - categories: - - Portfolio - - Web Dev - - Featured -- title: Ventura Digitalagentur Köln - main_url: 'https://www.ventura-digital.de/' - url: 'https://www.ventura-digital.de/' - featured: true - categories: - - Agency - - Marketing - - Featured -- title: Azer Koçulu - main_url: 'http://azer.bike/' - url: 'http://azer.bike/photography' - featured: false - categories: - - Portfolio - - Photography - - Web Dev -- title: Damir.io - main_url: 'http://damir.io/' - url: 'http://damir.io/' - source_url: 'https://github.com/dvzrd/gatsby-sfiction' - featured: false - categories: - - Creative -- title: Digital Psychology - main_url: 'http://digitalpsychology.io/' - url: 'http://digitalpsychology.io/' - source_url: 'https://github.com/danistefanovic/digitalpsychology.io' - featured: false - categories: - - Education - - Library -- title: GRANDstack - main_url: 'http://grandstack.io/' - url: 'http://grandstack.io/' - featured: false - categories: - - Open Source - - Web Dev -- title: Théâtres Parisiens - main_url: 'http://theatres-parisiens.fr/' - url: 'http://theatres-parisiens.fr/' - source_url: 'https://github.com/phacks/theatres-parisiens' - featured: false - categories: - - Education - - Entertainment +# - title: Hack Club +# main_url: 'https://hackclub.com/' +# url: 'https://hackclub.com/' +# source_url: 'https://github.com/hackclub/site' +# featured: true +# categories: +# - Education +# - Web Dev +# - Featured +# - title: Matthias Jordan Portfolio +# main_url: 'https://iammatthias.com/' +# url: 'https://iammatthias.com/' +# source_url: 'https://github.com/iammatthias/net' +# featured: true +# categories: +# - Photography +# - Portfolio +# - Featured +# - title: Investment Calculator +# main_url: 'https://investmentcalculator.io/' +# url: 'https://investmentcalculator.io/' +# featured: true +# categories: +# - Education +# - Featured +# - Finance +# - title: CSS Grid Playground by MozillaDev +# main_url: 'https://mozilladevelopers.github.io/playground/' +# url: 'https://mozilladevelopers.github.io/playground/' +# source_url: 'https://github.com/MozillaDevelopers/playground' +# featured: true +# categories: +# - Education +# - Web Dev +# - Featured +# - title: Piotr Fedorczyk Portfolio +# main_url: 'https://piotrf.pl/' +# url: 'https://piotrf.pl/' +# featured: true +# categories: +# - Portfolio +# - Creative +# - Web Dev +# - Featured +# - title: unrealcpp +# main_url: 'https://unrealcpp.com/' +# url: 'https://unrealcpp.com/' +# source_url: 'https://github.com/Harrison1/unrealcpp-com' +# featured: true +# categories: +# - Blog +# - Web Dev +# - Featured +# - title: Andy Slezak +# main_url: 'https://www.aslezak.com/' +# url: 'https://www.aslezak.com/' +# source_url: 'https://github.com/amslezak' +# featured: true +# categories: +# - Web Dev +# - Portfolio +# - Featured +# - title: Deliveroo.Design +# main_url: 'https://www.deliveroo.design/' +# url: 'https://www.deliveroo.design/' +# featured: true +# categories: +# - Food +# - Marketing +# - Featured +# - title: Dona Rita +# main_url: 'https://www.donarita.co.uk/' +# url: 'https://www.donarita.co.uk/' +# source_url: 'https://github.com/peduarte/dona-rita-website' +# featured: true +# categories: +# - Food +# - Marketing +# - Featured +# - title: Fröhlich ∧ Frei +# main_url: 'https://www.froehlichundfrei.de/' +# url: 'https://www.froehlichundfrei.de/' +# featured: true +# categories: +# - Web Dev +# - Blog +# - Open Source +# - title: How to GraphQL +# main_url: 'https://www.howtographql.com/' +# url: 'https://www.howtographql.com/' +# source_url: 'https://github.com/howtographql/howtographql' +# featured: true +# categories: +# - Documentation +# - Web Dev +# - Open Source +# - Featured +# - title: OnCallogy +# main_url: 'https://www.oncallogy.com/' +# url: 'https://www.oncallogy.com/' +# featured: true +# categories: +# - Landing +# - Marketing +# - Featured +# - Healthcare +# - title: Ryan Wiemer's Portfolio +# main_url: 'https://www.ryanwiemer.com/' +# url: 'https://www.ryanwiemer.com/knw-photography/' +# source_url: 'https://github.com/ryanwiemer/rw' +# featured: true +# categories: +# - Portfolio +# - Web Dev +# - Featured +# - title: Ventura Digitalagentur Köln +# main_url: 'https://www.ventura-digital.de/' +# url: 'https://www.ventura-digital.de/' +# featured: true +# categories: +# - Agency +# - Marketing +# - Featured +# - title: Azer Koçulu +# main_url: 'http://azer.bike/' +# url: 'http://azer.bike/photography' +# featured: false +# categories: +# - Portfolio +# - Photography +# - Web Dev +# - title: Damir.io +# main_url: 'http://damir.io/' +# url: 'http://damir.io/' +# source_url: 'https://github.com/dvzrd/gatsby-sfiction' +# featured: false +# categories: +# - Creative +# - title: Digital Psychology +# main_url: 'http://digitalpsychology.io/' +# url: 'http://digitalpsychology.io/' +# source_url: 'https://github.com/danistefanovic/digitalpsychology.io' +# featured: false +# categories: +# - Education +# - Library +# - title: GRANDstack +# main_url: 'http://grandstack.io/' +# url: 'http://grandstack.io/' +# featured: false +# categories: +# - Open Source +# - Web Dev +# - title: Théâtres Parisiens +# main_url: 'http://theatres-parisiens.fr/' +# url: 'http://theatres-parisiens.fr/' +# source_url: 'https://github.com/phacks/theatres-parisiens' +# featured: false +# categories: +# - Education +# - Entertainment - title: William Owen UK Portfolio / Blog main_url: 'http://william-owen.co.uk/' url: 'http://william-owen.co.uk/' @@ -242,1993 +242,1888 @@ - Blog built_by: William Owen built_by_url: 'https://twitter.com/twilowen' -- title: A4 纸网 - main_url: 'http://www.a4z.cn/' - url: 'http://www.a4z.cn/price' - source_url: 'https://github.com/hiooyUI/hiooyui.github.io' - featured: false - categories: - - Retail -- title: Steve Meredith's Portfolio - main_url: 'http://www.stevemeredith.com/' - url: 'http://www.stevemeredith.com/' - featured: false - categories: - - Portfolio -- title: Sourcegraph - main_url: 'https://about.sourcegraph.com/' - url: 'https://about.sourcegraph.com/' - featured: false - categories: - - Web Dev -- title: API Platform - main_url: 'https://api-platform.com/' - url: 'https://api-platform.com/' - source_url: 'https://github.com/api-platform/website' - featured: false - categories: - - Documentation - - Web Dev - - Open Source - - Library -- title: Artivest - main_url: 'https://artivest.co/' - url: 'https://artivest.co/what-we-do/for-advisors-and-investors/' - featured: false - categories: - - Marketing - - Blog - - Documentation - - Finance -- title: The Audacious Project - main_url: 'https://audaciousproject.org/' - url: 'https://audaciousproject.org/' - featured: false - categories: - - Nonprofit -- title: Dustin Schau's Blog - main_url: 'https://blog.dustinschau.com/' - url: 'https://blog.dustinschau.com/' - source_url: 'https://github.com/dschau/blog' - featured: false - categories: - - Blog - - Web Dev -- title: FloydHub's Blog - main_url: 'https://blog.floydhub.com/' - url: 'https://blog.floydhub.com/' - featured: false - categories: - - Technology - - Blog -- title: iContract Blog - main_url: 'https://blog.icontract.co.uk/' - url: 'http://blog.icontract.co.uk/' - featured: false - categories: - - Blog -- title: BRIIM - main_url: 'https://bri.im/' - url: 'https://bri.im/' - featured: false - description: >- - BRIIM is a movement to enable JavaScript enthusiasts and web developers in - machine learning. Learn about artificial intelligence and data science, two - fields which are governed by machine learning, in JavaScript. Take it right - to your browser with WebGL. - categories: - - Education - - Web Dev - - Technology -- title: Caddy Smells Like Trees - main_url: 'https://caddysmellsliketrees.ru/' - url: 'https://caddysmellsliketrees.ru/' - source_url: 'https://github.com/podabed/caddysmellsliketrees.github.io' - featured: false - description: >- - We play soul-searching songs for every day. They are merging in our forests - in such a way that it is difficult to separate them from each other, and - between them bellow bold deer poems. - categories: - - Music -- title: Calpa's Blog - main_url: 'https://calpa.me/' - url: 'https://calpa.me/' - source_url: 'https://github.com/calpa/blog' - featured: false - categories: - - Blog - - Web Dev -- title: Chocolate Free - main_url: 'https://chocolate-free.com/' - url: 'https://chocolate-free.com/' - source_url: 'https://github.com/Khaledgarbaya/chocolate-free-website' - featured: false - description: "A full time foodie \U0001F60D a forever Parisian \"patisserie\" lover and \U0001F382 \U0001F369 \U0001F370 \U0001F36A explorer and finally an under construction #foodblogger #foodblog" - categories: - - Blog - - Food -- title: Code Bushi - main_url: 'https://codebushi.com/' - url: 'https://codebushi.com/' - featured: false - description: >- - Web development resources, trends, & techniques to elevate your coding journey. - categories: - - Web Dev - - Open Source - - Blog - built_by: Hunter Chang - built_by_url: 'https://hunterchang.com/' -- title: Daniel Hollcraft - main_url: 'https://danielhollcraft.com/' - url: 'https://danielhollcraft.com/' - source_url: 'https://github.com/danielbh/danielhollcraft.com' - featured: false - categories: - - Web Dev - - Blog - - Portfolio -- title: Darren Britton's Portfolio - main_url: 'https://darrenbritton.com/' - url: 'https://darrenbritton.com/' - source_url: 'https://github.com/darrenbritton/darrenbritton.github.io' - featured: false - categories: - - Web Dev - - Portfolio -- title: Dave Lindberg Marketing & Design - url: 'https://davelindberg.com/' - main_url: 'https://davelindberg.com/' - source_url: 'https://github.com/Dave-Lindberg/dl-gatsby' - featured: false - description: >- - My work revolves around solving problems for people in business, using integrated design and marketing strategies to improve sales, increase brand engagement, generate leads and achieve goals. - categories: - - Creative - - Design - - Featured - - Marketing - - SEO - - Portfolio -- title: Design Systems Weekly - main_url: 'https://designsystems.email/' - url: 'https://designsystems.email/' - featured: false - categories: - - Education - - Web Dev -- title: Dalbinaco's Website - main_url: 'https://dlbn.co/en/' - url: 'https://dlbn.co/en/' - source_url: 'https://github.com/dalbinaco/dlbn.co' - featured: false - categories: - - Portfolio - - Web Dev -- title: mParticle's Documentation - main_url: 'https://docs.mparticle.com/' - url: 'https://docs.mparticle.com/' - featured: false - categories: - - Web Dev - - Documentation -- title: Doopoll - main_url: 'https://doopoll.co/' - url: 'https://doopoll.co/' - featured: false - categories: - - Landing - - Marketing - - Technology -- title: ERC dEX - main_url: 'https://ercdex.com/' - url: 'https://ercdex.com/aqueduct' - featured: false - categories: - - Marketing -- title: Fabian Schultz' Portfolio - main_url: 'https://fabianschultz.com/' - url: 'https://fabianschultz.com/' - source_url: 'https://github.com/fabe/site' - featured: false - description: >- - Hello, I’m Fabian — a product designer and developer based in Potsdam, - Germany. I’ve been working both as a product designer and frontend developer - for over 5 years now. I particularly enjoy working with companies that try - to meet broad and unique user needs. - categories: - - Portfolio - - Web Dev -- title: Formidable - main_url: 'https://formidable.com/' - url: 'https://formidable.com/' - featured: true - categories: - - Web Dev - - Agency - - Open Source - - Featured -- title: Gatsby Manor - main_url: 'https://gatsbymanor.com/' - url: 'https://gatsbymanor.com/themes' - featured: false - categories: - - Web Dev -- title: The freeCodeCamp Guide - main_url: 'https://guide.freecodecamp.org/' - url: 'https://guide.freecodecamp.org/' - source_url: 'https://github.com/freeCodeCamp/guide' - featured: false - categories: - - Web Dev - - Documentation -- title: High School Hackathons - main_url: 'https://hackathons.hackclub.com/' - url: 'https://hackathons.hackclub.com/' - source_url: 'https://github.com/hackclub/hackathons' - featured: false - categories: - - Education - - Web Dev -- title: Hapticmedia - main_url: 'https://hapticmedia.fr/en/' - url: 'https://hapticmedia.fr/en/' - featured: false - categories: - - Agency - - Creative -- title: heml.io - main_url: 'https://heml.io/' - url: 'https://heml.io/' - source_url: 'https://github.com/SparkPost/heml.io' - featured: false - categories: - - Documentation - - Web Dev - - Open Source -- title: Juliette Pretot's Portfolio - main_url: 'https://juliette.sh/' - url: 'https://juliette.sh/' - featured: false - categories: - - Web Dev - - Portfolio - - Blog -- title: Kris Hedstrom's Portfolio - main_url: 'https://k-create.com/' - url: 'https://k-create.com/portfolio/' - source_url: 'https://github.com/kristofferh/kristoffer' - featured: false - description: >- - Hey. I’m Kris. I’m an interactive designer / developer. I grew up in Umeå, - in northern Sweden, but I now live in Brooklyn, NY. I am currently enjoying - a hybrid Art Director + Lead Product Engineer role at a small startup called - Nomad Health. Before that, I was a Product (Engineering) Manager at Tumblr. - Before that, I worked at agencies. Before that, I was a baby. I like to - design things, and then I like to build those things. I occasionally take on - freelance projects. Feel free to get in touch if you have an interesting - project that you want to collaborate on. Or if you just want to say hello, - that’s cool too. - categories: - - Creative - - Portfolio -- title: knpw.rs - main_url: 'https://knpw.rs/' - url: 'https://knpw.rs/' - source_url: 'https://github.com/knpwrs/knpw.rs' - featured: false - categories: - - Blog - - Web Dev -- title: Kostas Bariotis' Blog - main_url: 'https://kostasbariotis.com/' - url: 'https://kostasbariotis.com/' - source_url: 'https://github.com/kbariotis/kostasbariotis.com' - featured: false - categories: - - Blog - - Portfolio - - Web Dev -- title: LaserTime Clinic - main_url: 'https://lasertime.ru/' - url: 'https://lasertime.ru/' - source_url: 'https://github.com/oleglegun/lasertime' - featured: false - categories: - - Marketing -- title: Jason Lengstorf - main_url: 'https://lengstorf.com' - url: 'https://lengstorf.com' - source_url: 'https://github.com/jlengstorf/lengstorf.com' - featured: false - date_added: Apr 10 - gatsby_version: V1 - categories: - - Blog - - Personal - built_by: Jason Lengstorf - built_by_url: 'https://github.com/jlengstorf' -- title: Mannequin.io - main_url: 'https://mannequin.io/' - url: 'https://mannequin.io/' - source_url: 'https://github.com/LastCallMedia/Mannequin/tree/master/site' - featured: false - categories: - - Open Source - - Web Dev - - Documentation -- title: manu.ninja - main_url: 'https://manu.ninja/' - url: 'https://manu.ninja/' - source_url: 'https://github.com/Lorti/manu.ninja' - featured: false - description: >- - manu.ninja is the personal blog of Manuel Wieser, where he talks about - front-end development, games and digital art - categories: - - Blog - - Technology - - Web Dev -- title: Fabric - main_url: 'https://meetfabric.com/' - url: 'https://meetfabric.com/' - featured: true - categories: - - Corporate - - Marketing - - Featured - - Insurance -- title: Nexit - main_url: 'https://nexit.sk/' - url: 'https://nexit.sk/references' - featured: false - categories: - - Web Dev -- title: Nortcast - main_url: 'https://nortcast.com/' - url: 'https://nortcast.com/' - featured: false - categories: - - Technology - - Entertainment - - Podcast -- title: openFDA - main_url: 'https://open.fda.gov/' - url: 'https://open.fda.gov/' - source_url: 'https://github.com/FDA/open.fda.gov' - featured: false - categories: - - Government - - Open Source - - Web Dev -- title: NYC Planning Labs (New York City Department of City Planning) - main_url: 'https://planninglabs.nyc/' - url: 'https://planninglabs.nyc/about/' - source_url: 'https://github.com/NYCPlanning/' - featured: false - description: >- - We work with New York City's Urban Planners to deliver impactful, modern - technology tools. - categories: - - Open Source - - Government -- title: Pravdomil - main_url: 'https://pravdomil.com/' - url: 'https://pravdomil.com/' - source_url: 'https://github.com/pravdomil/pravdomil.com' - featured: false - description: >- - I’ve been working both as a product designer and frontend developer for over - 5 years now. I particularly enjoy working with companies that try to meet - broad and unique user needs. - categories: - - Portfolio - - Personal -- title: Preston Richey Portfolio / Blog - main_url: 'https://prestonrichey.com/' - url: 'https://prestonrichey.com/' - source_url: 'https://github.com/prichey/prestonrichey.com' - featured: false - categories: - - Web Dev - - Portfolio - - Blog -- title: Landing page of Put.io - main_url: 'https://put.io/' - url: 'https://put.io/' - featured: false - categories: - - eCommerce - - Technology -- title: The Rick and Morty API - main_url: 'https://rickandmortyapi.com/' - url: 'https://rickandmortyapi.com/' - built_by: Axel Fuhrmann - built_by_url: https://axelfuhrmann.com/ - featured: false - categories: - - Web Dev - - Entertainment - - Documentation - - Open Source - - API -- title: Santa Compañía Creativa - main_url: 'https://santacc.es/' - url: 'https://santacc.es/' - source_url: 'https://github.com/DesarrolloWebSantaCC/santacc-web' - featured: false - categories: - - Agency - - Creative -- title: Sean Coker's Blog - main_url: 'https://sean.is/' - url: 'https://sean.is/' - featured: false - categories: - - Blog - - Portfolio - - Web Dev -- title: Segment's Blog - main_url: 'https://segment.com/blog/' - url: 'https://segment.com/blog/' - featured: false - categories: - - Web Dev - - Blog -- title: Several Levels - main_url: 'https://severallevels.io/' - url: 'https://severallevels.io/' - source_url: 'https://github.com/Harrison1/several-levels' - featured: false - categories: - - Agency - - Web Dev -- title: Simply - main_url: 'https://simply.co.za/' - url: 'https://simply.co.za/' - featured: false - categories: - - Corporate - - Marketing - - Insurance -- title: Storybook - main_url: 'https://storybook.js.org/' - url: 'https://storybook.js.org/' - source_url: 'https://github.com/storybooks/storybook' - featured: false - categories: - - Web Dev - - Open Source -- title: Vibert Thio's Portfolio - main_url: 'https://vibertthio.com/portfolio/' - url: 'https://vibertthio.com/portfolio/projects/' - source_url: 'https://github.com/vibertthio/portfolio' - featured: false - categories: - - Portfolio - - Web Dev - - Creative -- title: VisitGemer - main_url: 'https://visitgemer.sk/' - url: 'https://visitgemer.sk/' - featured: false - categories: - - Marketing -- title: Beach Hut Poole - main_url: 'https://www.beachhutpoole.co.uk/' - url: 'https://www.beachhutpoole.co.uk/' - featured: false - categories: - - Travel - - Marketing -- title: Bricolage.io - main_url: 'https://www.bricolage.io/' - url: 'https://www.bricolage.io/' - source_url: 'https://github.com/KyleAMathews/blog' - featured: false - categories: - - Blog -- title: Charles Pinnix Website - main_url: 'https://www.charlespinnix.com/' - url: 'https://www.charlespinnix.com/' - featured: false - description: >- - I’m a senior front end engineer with 8 years of experience building websites - and web applications. I’m interested in leading creative, multidisciplinary - engineering teams. I’m a creative technologist, merging photography, art, - and design into engineering and visa versa. I take a pragmatic, - product-oriented approach to development, allowing me to see the big picture - and ensuring quality products are completed on time. I have a passion for - modern front end JavaScript frameworks such as React and Vue, and I have - substantial experience on the back end with an interest in Node and - container based deployment with Docker and AWS. - categories: - - Portfolio - - Web Dev -- title: Charlie Harrington's Blog - main_url: 'https://www.charlieharrington.com/' - url: 'https://www.charlieharrington.com/' - source_url: 'https://github.com/whatrocks/blog' - featured: false - categories: - - Blog - - Web Dev - - Music -- title: Developer Ecosystem - main_url: 'https://www.developerecosystem.com/' - url: 'https://www.developerecosystem.com/' - featured: false - categories: - - Blog - - Web Dev -- title: Gabriel Adorf's Portfolio - main_url: 'https://www.gabrieladorf.com/' - url: 'https://www.gabrieladorf.com/' - source_url: 'https://github.com/gabdorf/gabriel-adorf-portfolio' - featured: false - categories: - - Portfolio - - Web Dev -- title: greglobinski.com - main_url: 'https://www.greglobinski.com/' - url: 'https://www.greglobinski.com/' - source_url: 'https://github.com/greglobinski/www.greglobinski.com' - featured: false - categories: - - Portfolio - - Web Dev -- title: I am Putra - main_url: 'https://www.iamputra.com/' - url: 'https://www.iamputra.com/' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: In Sowerby Bridge - main_url: 'https://www.insowerbybridge.co.uk/' - url: 'https://www.insowerbybridge.co.uk/' - featured: false - categories: - - Marketing - - Government -- title: JavaScript Stuff - main_url: 'https://www.javascriptstuff.com/' - url: 'https://www.javascriptstuff.com/' - featured: false - categories: - - Education - - Web Dev - - Library -- title: KNW Photography - main_url: 'https://www.knw.io/' - url: 'https://www.knw.io/galleries/' - source_url: 'https://github.com/ryanwiemer/knw' - featured: false - description: >- - Hi there! I’m Kirsten and I’m currently living in Oakland with my husband - and fluffy pup Birch. I’ve always been an excessive photo taker and decided - to turn my love for taking photos into a career. - categories: - - Photography - - Portfolio -- title: Ledgy - main_url: 'https://www.ledgy.com/' - url: 'https://github.com/morloy/ledgy.com' - featured: false - categories: - - Marketing - - Finance -- title: Alec Lomas's Portfolio / Blog - main_url: 'https://www.lowmess.com/' - url: 'https://www.lowmess.com/' - source_url: 'https://github.com/lowmess/lowmess' - featured: false - categories: - - Web Dev - - Blog - - Portfolio -- title: Michele Mazzucco's Portfolio - main_url: 'https://www.michelemazzucco.it/' - url: 'https://www.michelemazzucco.it/' - source_url: 'https://github.com/michelemazzucco/michelemazzucco.it' - featured: false - categories: - - Creative - - Portfolio -- title: Orbit FM Podcasts - main_url: 'https://www.orbit.fm/' - url: 'https://www.orbit.fm/' - source_url: 'https://github.com/agarrharr/orbit.fm' - featured: false - categories: - - Podcast -- title: Prosecco Springs - main_url: 'https://www.proseccosprings.com/' - url: 'https://www.proseccosprings.com/' - featured: false - categories: - - Food - - Blog - - Marketing -- title: Verious - main_url: 'https://www.verious.io/' - url: 'https://www.verious.io/' - source_url: 'https://github.com/cpinnix/verious' - featured: false - categories: - - Web Dev -- title: Whittle School - main_url: 'https://www.whittleschool.org/en/' - url: 'https://www.whittleschool.org/en/' - featured: false - categories: - - Education -- title: Yisela - main_url: 'https://www.yisela.com/' - url: 'https://www.yisela.com/tetris-against-trauma-gaming-as-therapy/' - featured: false - categories: - - Blog -- title: YouFoundRon.com - main_url: 'https://www.youfoundron.com/' - url: 'https://www.youfoundron.com/' - source_url: 'https://github.com/rongierlach/yfr-dot-com' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: yerevancoder - main_url: 'https://yerevancoder.com/' - url: 'https://forum.yerevancoder.com/categories' - source_url: 'https://github.com/yerevancoder/yerevancoder.github.io' - featured: false - categories: - - Blog - - Web Dev -- title: EaseCentral - main_url: 'https://www.easecentral.com/' - url: 'https://www.easecentral.com/' - featured: false - categories: - - Marketing - - Healthcare -- title: Policygenius - main_url: 'https://www.policygenius.com/' - url: 'https://www.policygenius.com/' - featured: false - categories: - - Marketing - - Healthcare -- title: Moteefe - main_url: 'http://www.moteefe.com/' - url: 'http://www.moteefe.com/' - featured: false - categories: - - Marketing - - Agency - - Technology -- title: Athelas - main_url: 'http://www.athelas.com/' - url: 'http://www.athelas.com/' - featured: true - categories: - - Marketing - - Healthcare - - Featured -- title: Pathwright - main_url: 'http://www.pathwright.com/' - url: 'http://www.pathwright.com/' - featured: false - categories: - - Marketing - - Education -- title: pi-top - main_url: 'http://www.pi-top.com/' - url: 'http://www.pi-top.com/' - featured: false - categories: - - Marketing - - Web Dev - - Technology - - eCommerce -- title: Troops - main_url: 'http://www.troops.ai/' - url: 'http://www.troops.ai/' - featured: false - categories: - - Marketing - - Technology -- title: ClearBrain - main_url: 'https://clearbrain.com/' - url: 'https://clearbrain.com/' - featured: false - categories: - - Marketing - - Technology -- title: Lucid - main_url: 'https://www.golucid.co/' - url: 'https://www.golucid.co/' - featured: true - categories: - - Marketing - - Technology - - Featured -- title: Bench - main_url: 'http://www.bench.co/' - url: 'http://www.bench.co/' - featured: false - categories: - - Marketing -- title: Union Plus Credit Card - main_url: 'http://www.unionpluscard.com' - url: 'https://unionplus.capitalone.com/' - featured: false - categories: - - Marketing - - Finance -- title: Gin Lane - main_url: 'http://www.ginlane.com/' - url: 'https://www.ginlane.com/' - featured: false - categories: - - Web Dev - - Creative - - Agency -- title: Marmelab - main_url: 'https://marmelab.com/en/' - url: 'https://marmelab.com/en/' - featured: false - categories: - - Web Dev - - Agency -- title: Fusion Media Group - main_url: 'http://thefmg.com/' - url: 'http://thefmg.com/' - featured: true - categories: - - Creative - - Entertainment - - News - - Featured -- title: Cool Hunting - main_url: 'http://www.coolhunting.com/' - url: 'http://www.coolhunting.com/' - featured: false - categories: - - Magazine -- title: Dovetail - main_url: 'https://dovetailapp.com/' - url: 'https://dovetailapp.com/' - featured: false - categories: - - Marketing - - Technology -- title: GraphQL College - main_url: 'https://www.graphql.college/' - url: 'https://www.graphql.college/' - source_url: 'https://github.com/GraphQLCollege/graphql-college' - featured: false - categories: - - Web Dev - - Education -- title: F1 Vision - main_url: 'https://www.f1vision.com/' - url: 'https://www.f1vision.com/' - featured: false - categories: - - Marketing - - Entertainment - - Technology - - eCommerce -- title: Yuuniworks Portfolio / Blog - main_url: 'https://www.yuuniworks.com/' - url: 'https://www.yuuniworks.com/' - source_url: 'https://github.com/junkboy0315/yuuni-web' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: The Bastion Bot - main_url: 'https://bastionbot.org/' - url: 'https://bastionbot.org/' - source_url: 'https://github.com/TheBastionBot/Bastion-Website' - description: 'Give awesome perks to your Discord server!' - featured: false - categories: - - Open Source - - Technology - - Documentation - - Bot - - Community - - Services - - Software - - Tool - built_by: Sankarsan Kampa - built_by_url: https://sankarsankampa.com -- title: upGizmo - main_url: 'https://www.upgizmo.com/' - url: 'https://www.upgizmo.com/' - featured: false - categories: - - News - - Technology -- title: Smakosh - main_url: 'https://smakosh.com/' - url: 'https://smakosh.com/' - source_url: 'https://github.com/smakosh/smakosh.com' - featured: false - categories: - - Portfolio - - Web Dev - - Creative -- title: Philipp Czernitzki - Blog/Website - main_url: 'http://philippczernitzki.me/' - url: 'http://philippczernitzki.me/' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: WebGazer - main_url: 'https://www.webgazer.io/' - url: 'https://www.webgazer.io/' - featured: false - categories: - - Marketing - - Web Dev - - Technology -- title: Joe Seifi's Blog - main_url: 'http://seifi.org/' - url: 'http://seifi.org/' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: Bartosz Dominiak Blog/Portfolio - main_url: 'http://bartoszdominiak.com/' - url: 'http://bartoszdominiak.com/' - source_url: 'https://github.com/bartdominiak/blog' - featured: false - categories: - - Portfolio - - Web Dev - - Blog -- title: HBTU MUN 2018 (source) - featured: false -- title: Jamie Henson's Blog (source) - featured: false -- title: Ruben's Blog (source) - featured: false -- title: Thao Am Private Enterprise - featured: false -- title: Bakadono - featured: false -- title: Travellers.cafe - featured: false -- title: Oliver Benns' Portfolio (source) - featured: false -- title: angeloocana.com (source) - featured: false -- title: Overlap.show (source) - featured: false -- title: smartive Company Website - featured: false -- title: Haboba Find Jobs at Phu Quoc Island - featured: false -- title: Song Wang’s website (source) - featured: false -- title: Magicly's blog (source) - featured: false -- title: Phu Quoc Works - featured: false -- title: Kabir Goel's website (source) - featured: false -- title: David James' Portfolio (source) - featured: false -- title: Tic Tac Toe AI (source) - featured: false -- title: Random Screencast - featured: false -- title: Phu Quoc Tea & Coffee Store - featured: false -- title: Steven Natera's blog - featured: false -- title: LekoArts - main_url: 'https://www.lekoarts.de' - url: 'https://www.lekoarts.de' - source_url: 'https://github.com/LeKoArts/portfolio' - featured: false - built_by: LekoArts - built_by_url: 'https://github.com/LeKoArts' - description: >- - Hi, I'm Lennart — a self-taught and passionate graphic/web designer & frontend developer based in Darmstadt, Germany. I love it to realize complex projects in a creative manner and face new challenges. Since 6 years I do graphic design, my love for frontend development came up 3 years ago. I enjoy acquiring new skills and cementing this knowledge by writing blogposts and creating tutorials. - categories: - - Portfolio - - Blog -- title: Georgi Yanev (source) - featured: false -- title: Hallingdata - featured: false -- title: '@swyx (source)' - featured: false -- title: 伊撒尔の窝 - featured: false -- title: 杨二小的博客 - main_url: 'https://blog.yangerxiao.com/' - url: 'https://blog.yangerxiao.com/' - source_url: 'https://github.com/zerosoul/blog.yangerxiao.com' - featured: false - categories: - - Blog - - Portfolio -- title: mottox2 blog - main_url: 'https://mottox2.com' - url: 'https://mottox2.com' - featured: false - categories: - - Blog - - Portfolio -- title: Pride of the Meadows - main_url: 'https://www.prideofthemeadows.com/' - url: 'https://www.prideofthemeadows.com/' - featured: false - categories: - - Retail - - Food - - Blog -- title: Michael Uloth - main_url: 'https://www.michaeluloth.com' - url: 'https://www.michaeluloth.com' - featured: false - description: >- - Michael Uloth is an opera singer and web developer based in Toronto. - categories: - - Portfolio - - Music - - Web Dev - built_by: Michael Uloth - built_by_url: 'https://www.michaeluloth.com' -- title: NYC Pride 2019 | WorldPride NYC | Stonewall50 - main_url: 'https://2019-worldpride-stonewall50.nycpride.org/' - url: 'https://2019-worldpride-stonewall50.nycpride.org/' - featured: false - description: >- - Join us in 2019 for NYC Pride, as we welcome WorldPride and mark the 50th Anniversary of the Stonewall Uprising and a half-century of LGBTQ+ liberation. - categories: - - Education - - Marketing - - Nonprofit - built_by: Canvas United - built_by_url: 'https://www.canvasunited.com/' -- title: Spacetime - main_url: 'https://www.heyspacetime.com/' - url: 'https://www.heyspacetime.com/' - featured: false - description: >- - Spacetime is a Dallas-based digital experience agency specializing in web, app, startup, and digital experience creation. - categories: - - Marketing - - Portfolio - - Agency - - Creative - - Featured - built_by: Spacetime - built_by_url: 'https://www.heyspacetime.com/' -- title: Eric Jinks - main_url: 'https://ericjinks.com/' - url: 'https://ericjinks.com/' - featured: false - description: >- - Software engineer / web developer from the Gold Coast, Australia. - categories: - - Portfolio - - Blog - - Web Dev - - Technology - built_by: Eric Jinks - built_by_url: 'https://ericjinks.com/' -- title: GaiAma - We are wildlife - main_url: 'https://www.gaiama.org/' - url: 'https://www.gaiama.org/' - featured: false - description: >- - We founded the GaiAma conservation organization to protect wildlife in Perú and to create an example of a permaculture neighborhood, living symbiotically with the forest - because reforestation is just the beginning - categories: - - Nonprofit - - Marketing - - Blog - source_url: https://github.com/GaiAma/gaiama.org - built_by: GaiAma - built_by_url: 'https://www.gaiama.org/' -- title: Healthcare Logic - main_url: 'https://www.healthcarelogic.com/' - url: 'https://www.healthcarelogic.com/' - featured: false - description: >- - Revolutionary technology that empowers clinical and managerial leaders to collaborate with clarity. - categories: - - Marketing - - Healthcare - - Technology - built_by: Thrive - built_by_url: 'https://thriveweb.com.au/' -- title: Localgov.fyi - main_url: 'https://localgov.fyi/' - url: 'https://localgov.fyi/' - featured: false - description: >- - Finding local government services made easier. - categories: - - Directory - - Government - - Technology - source_url: https://github.com/WeOpenly/localgov.fyi - built_by: Openly - built_by_url: 'https://weopenly.com/' -- title: Kata.ai Documentation - main_url: 'https://docs.kata.ai/' - url: 'https://docs.kata.ai/' - source_url: https://github.com/kata-ai/kata-platform-docs - featured: false - description: >- - Documentation website for the Kata Platform, an all-in-one platform for - building chatbots using AI technologies. - categories: - - Documentation - - Technology -- title: goalgetters - main_url: 'https://goalgetters.space/' - url: 'https://goalgetters.space/' - featured: false - description: >- - goalgetters is a source of inspiration for people who want to change their career. - We offer articles, success stories and expert interviews on how to find a new passion and how to implement change. - categories: - - Blog - - Education - - Careers - - Personal Development - built_by: Stephanie Langers (content), Adrian Wenke (development) - built_by_url: 'https://twitter.com/AdrianWenke' -- title: Life Without Barriers | Foster Care - main_url: 'https://www.lwb.org.au/foster-care' - url: 'https://www.lwb.org.au/foster-care' - featured: false - description: >- - We are urgently seeking foster carers all across Australia. Can you open your heart and your home to a child in need? - There are different types of foster care that can suit you. We offer training and 24/7 support. - categories: - - Charity - - Nonprofit - - Education - - Documentation - - Marketing - built_by: LWB Digital Team - built_by_url: 'https://twitter.com/LWBAustralia' -- title: Bejamas - JAM Experts for hire - main_url: 'https://bejamas.io/' - url: 'https://bejamas.io/' - featured: false - description: >- - We help agencies and companies with JAMStack tools. This includes web development using Static Site Generators, Headless CMS, CI / CD and CDN setup. - categories: - - Technology - - Web Dev - - Agency - - Creative - - Marketing - built_by: Bejamas.io - built_by_url: 'https://bejamas.io/' -- title: Zensum - main_url: 'https://zensum.se/' - url: 'https://zensum.se/' - featured: false - description: >- - Borrow money quickly and safely through Zensum. We compare Sweden's leading banks and credit institutions. Choose from multiple offers and lower your monthly cost. [Translated from Swedish] - categories: - - Technology - - Finance - - Corporate - - Marketing - built_by: Bejamas.io - built_by_url: 'https://bejamas.io/' -- title: StatusHub - Easy to use Hosted Status Page Service - main_url: 'https://statushub.com/' - url: 'https://statushub.com/' - featured: false - description: >- - Set up your very own service status page in minutes with StatusHub. Allow customers to subscribe to be updated automatically. - categories: - - Technology - - Marketing - built_by: Bejamas.io - built_by_url: 'https://bejamas.io/' -- title: Matthias Kretschmann Portfolio - main_url: 'https://matthiaskretschmann.com/' - url: 'https://matthiaskretschmann.com/' - source_url: 'https://github.com/kremalicious/portfolio' - featured: false - description: >- - Portfolio of designer & developer Matthias Kretschmann. - categories: - - Portfolio - - Web Dev - - Creative - built_by: Matthias Kretschmann - built_by_url: 'https://matthiaskretschmann.com/' -- title: Cajun Bowfishing - main_url: 'https://cajunbowfishing.com/' - url: 'https://cajunbowfishing.com/' - featured: false - categories: - - eCommerce - - Sports - built_by: Escalade Sports - built_by_url: 'http://escaladesports.com/' -- title: Iron Cove Solutions - main_url: 'https://ironcovesolutions.com/' - url: 'https://ironcovesolutions.com/' - description: >- - Iron Cove Solutions is a cloud based consulting firm. We help companies deliver a return on cloud usage by applying best practices - categories: - - Technology - - Web Dev - built_by: Iron Cove Solutions - built_by_url: 'https://ironcovesolutions.com/' - featured: false -- title: Eventos orellana - description: >- - Somos una empresa dedicada a brindar asesoría personalizada - y profesional para la elaboración y coordinación de eventos - sociales y empresariales. - main_url: 'https://eventosorellana.com/' - url: 'https://eventosorellana.com/' - featured: false - categories: - - Gallery - built_by: Codedebug - built_by_url: 'https://codedebug.co/' -- title: Moetez Chaabene Portfolio / Blog - main_url: 'https://moetez.me/' - url: 'https://moetez.me/' - source_url: 'https://github.com/moetezch/moetez.me' - featured: false - description: >- - Portfolio of Moetez Chaabene - categories: - - Portfolio - - Web Dev - - Blog - built_by: Moetez Chaabene - built_by_url: 'https://twitter.com/moetezch' -- title: Nikita - description: >- - Automation of system deployments in Node.js for applications and infrastructures. - main_url: https://nikita.js.org/ - url: https://nikita.js.org/ - source_url: 'https://github.com/adaltas/node-nikita' - categories: - - Documentation - - Open Source - - Technology - built_by: David Worms - built_by_url: http://www.adaltas.com - featured: false -- title: Gourav Sood Blog & Portfolio - main_url: 'https://www.gouravsood.com/' - url: 'https://www.gouravsood.com/' - featured: false - categories: - - Blog - - Portfolio - - Salesforce - built_by: Gourav Sood - built_by_url: 'https://www.gouravsood.com/' -- title: Figma - main_url: 'https://www.figma.com/' - url: 'https://www.figma.com/' - featured: false - categories: - - Marketing - - Design - built_by: Corey Ward - built_by_url: http://www.coreyward.me/ -- title: Jonas Tebbe Portfolio - description: > - Hey, I’m Jonas and I create digital products. - main_url: https://jonastebbe.com - url: https://jonastebbe.com - categories: - - Portfolio - built_by: Jonas Tebbe - built_by_url: http://twitter.com/jonastebbe - featured: false -- title: Parker Sarsfield Portfolio - description: > - I'm Parker, a software engineer and sneakerhead. - main_url: https://parkersarsfield.com - url: https://parkersarsfield.com - categories: - - Blog - - Portfolio - built_by: Parker Sarsfield - built_by_url: https://parkersarsfield.com -- title: Front-end web development with Greg - description: > - JavaScript, GatsbyJS, ReactJS, CSS in JS... Let's learn some stuff together. - main_url: https://dev.greglobinski.com - url: https://dev.greglobinski.com - categories: - - Blog - - Web Dev - built_by: Greg Lobinski - built_by_url: https://github.com/greglobinski -- title: Insomnia - description: > - Desktop HTTP and GraphQL client for developers - main_url: https://insomnia.rest/ - url: https://insomnia.rest/ - categories: - - Landing - - Blog - built_by: Gregory Schier - built_by_url: https://schier.co - featured: false -- title: Timeline Theme Portfolio - description: > - I'm Aman Mittal, a software developer. - main_url: http://www.amanhimself.me/ - url: http://www.amanhimself.me/ - categories: - - Landing - - Web Dev - - Portfolio - built_by: Aman Mittal - built_by_url: http://www.amanhimself.me/ -- title: Ocean artUp - description: > - Science outreach site built using styled-components and Contentful. It presents the research project "Ocean artUp" funded by an Advanced Grant of the European Research Council to explore the possible benefits of artificial uplift of nutrient-rich deep water to the ocean’s sunlit surface layer. - main_url: https://ocean-artup.eu - url: https://ocean-artup.eu - source_url: https://github.com/JanoshRiebesell/ocean-artup - categories: - - Education - - Blog - - Science - built_by: Janosh Riebesell - built_by_url: https://janosh.io - featured: false -- title: Ryan Fitzgerald - description: > - Personal portfolio and blog for Ryan Fitzgerald - main_url: https://ryanfitzgerald.ca/ - url: https://ryanfitzgerald.ca/ - categories: - - Web Dev - - Portfolio - built_by: Ryan Fitzgerald - built_by_url: https://github.com/RyanFitzgerald - featured: false -- title: Kaizen - description: > - Content Marketing, PR & SEO Agency in London - main_url: https://www.kaizen.co.uk/ - url: https://www.kaizen.co.uk/ - categories: - - Agency - - Blog - - Creative - - Design - - Web Dev - - SEO - built_by: Bogdan Stanciu - built_by_url: https://github.com/b0gd4n - featured: false -- title: HackerOne Platform Documentation - description: > - HackerOne's Product Documentation Center! - url: https://docs.hackerone.com/ - main_url: https://docs.hackerone.com/ - categories: - - Documentation - - Security - featured: false -- title: Patreon Partners - description: > - Resources and products to help you do more with Patreon. - url: https://partners.patreon.com/ - main_url: https://partners.patreon.com/ - categories: - - Resources - - Directory - featured: false -- title: Bureau Of Meteorology (beta) - description: > - Help shape the future of Bureau services - url: https://beta.bom.gov.au/ - main_url: https://beta.bom.gov.au/ - categories: - - Meteorology - - Research - - Services - featured: false -- title: Curbside - description: > - Connecting Stores with Mobile Customers - main_url: https://curbside.com/ - url: https://curbside.com/ - categories: - - Mobile Commerce - - Software - featured: false -- title: Mux Video - description: > - API to video hosting and streaming - main_url: https://mux.com/ - url: https://mux.com/ - categories: - - Video - - Hosting - - Streaming - - API - featured: false -- title: Swapcard - description: > - The easiest way for event organizers to instantly connect people, build a community of attendees and exhibitors, and increase revenue over time - main_url: https://www.swapcard.com/ - url: https://www.swapcard.com/ - categories: - - Event - - Community - - Services - featured: false -- title: Kalix - description: > - Kalix is perfect for healthcare professionals starting out in private practice, to those with an established clinic. - main_url: https://www.kalixhealth.com/ - url: https://www.kalixhealth.com/ - categories: - - Scheduling - - Documentation - - Messaging - - Billing - - Services - featured: false -- title: Hubba - description: > - Buy wholesale products from thousands of independent, verified Brands. - main_url: https://join.hubba.com/ - url: https://join.hubba.com/ - categories: - - eCommerce - - Retail - featured: false -- title: Airbnb Engineering & Data Science - description: > - Creative engineers and data scientists building a world where you can belong anywhere - main_url: https://airbnb.io/ - url: https://airbnb.io/ - categories: - - Blog - - Gallery - - Projects - featured: false -- title: HyperPlay - description: > - In Asean's 1st Ever LOL Esports X Music Festival - main_url: https://hyperplay.leagueoflegends.com/ - url: https://hyperplay.leagueoflegends.com/ - categories: - - Landing - featured: false -- title: Bad Credit Loans - description: > - Get the funds you need, from $250-$5,000 - main_url: https://www.creditloan.com/ - url: https://www.creditloan.com/ - categories: - - Loans - - Credits - featured: false -- title: Financial Center - description: > - Member-owned, not-for-profit, co-operative whose members receive financial benefits in the form of lower loan rates, higher savings rates, and lower fees than banks. - main_url: https://fcfcu.com/ - url: https://fcfcu.com/ - categories: - - Loans - - Credits - - Online Banking - - Nonprofit - featured: false -- title: Open FDA - description: > - Provides APIs and raw download access to a number of high-value, high priority and scalable structured datasets, including adverse events, drug product labeling, and recall enforcement reports. - main_url: https://open.fda.gov/ - url: https://open.fda.gov/ - categories: - - APIs - - Datasets - - Reports - featured: false -- title: Office of Institutional Research and Assessment - description: > - Good Data, Good Decisions - main_url: http://oira.ua.edu/ - url: http://oira.ua.edu/ - categories: - - Research - - Data - featured: false -- title: GM Capital One - description: > - Introducing the new online experience for your GM Rewards Credit Card - main_url: https://gm.capitalone.com/ - url: https://gm.capitalone.com/ - categories: - - Rewards - - Credit Card - featured: false -- title: House Manager - description: > - Home service membership that offers proactive and on-demand maintenance for homeowners - main_url: https://housemanager.calstate.aaa.com/ - url: https://housemanager.calstate.aaa.com/ - categories: - - Home - - Services - featured: false -- title: Trintellix - description: > - It may help make a difference for your depression (MDD). - main_url: https://us.trintellix.com/ - url: https://us.trintellix.com/ - categories: - - Health - - Help - featured: false -- title: The Telegraph Premium - description: > - Exclusive stories from award-winning journalists - main_url: https://premium.telegraph.co.uk/ - url: https://premium.telegraph.co.uk/ - categories: - - Landing - - Newspaper - - Subscription - featured: false -- title: html2canvas - description: > - Screenshots with JavaScript - main_url: http://html2canvas.hertzen.com/ - url: http://html2canvas.hertzen.com/ - source_url: https://github.com/niklasvh/html2canvas/tree/master/www - categories: - - Tool - - Screenshots - - JavaScript - - Documentation - built_by: Niklas von Hertzen - built_by_url: http://hertzen.com/ - featured: false -- title: The State of JavaScript 2017 - description: > - Data from over 20,000 developers, asking them questions on topics ranging from front-end frameworks and state management, to build tools and testing libraries. - main_url: https://stateofjs.com/ - url: https://stateofjs.com/ - source_url: https://github.com/StateOfJS/StateOfJS - categories: - - Data - - JavaScript - - Statistics - built_by: StateOfJS - built_by_url: https://github.com/StateOfJS/StateOfJS/graphs/contributors - featured: false -- title: Dato CMS - description: > - The API-based CMS your editors will love - main_url: https://www.datocms.com/ - url: https://www.datocms.com/ - categories: - - CMS - - API - - Tool - featured: false -- title: Half Electronics - description: > - Personal website - main_url: https://www.halfelectronic.com/ - url: https://www.halfelectronic.com/ - categories: - - Blog - - Electronics - built_by: Fernando Poumian - built_by_url: https://github.com/fpoumian/halfelectronic.com - featured: false -- title: Frithir Software Development - main_url: 'https://frithir.com/' - url: 'https://frithir.com/' - featured: false - description: >- - I DRINK COFFEE, WRITE CODE AND IMPROVE MY DEVELOPMENT SKILLS EVERY DAY. - categories: - - Creative - - Design - - Web Dev - built_by: Frithir - built_by_url: 'https://Frithir.com/' -- title: Unow - main_url: https://www.unow.fr/ - url: https://www.unow.fr/ - categories: - - Education - - Corporate - - Marketing - featured: false -- title: Peter Hironaka - description: > - Freelance Web Developer based in Los Angeles. - main_url: https://peterhironaka.com/ - url: https://peterhironaka.com/ - categories: - - Portfolio - - Web Dev - built_by: Peter Hironaka - built_by_url: https://github.com/PHironaka - featured: false -- title: Michael McQuade - description: > - Personal website and blog for Michael McQuade - main_url: https://giraffesyo.io - url: https://giraffesyo.io - categories: - - Blog - built_by: Michael McQuade - built_by_url: https://github.com/giraffesyo - featured: false -- title: Haacht Brewery - description: > - Corporate wesbite for Haacht Brewery. Designed and Developed by Gafas. - main_url: https://haacht.com/en/ - url: https://haacht.com - categories: - - Corporate - built_by: Gafas - built_by_url: https://gafas.be - featured: false -- title: StoutLabs - description: > - Portfolio of Daniel Stout, freelance web dev in East Tennessee. - main_url: https://www.stoutlabs.com/ - url: https://www.stoutlabs.com/ - categories: - - Web Dev - - Portfolio - built_by: Daniel Stout - built_by_url: https://github.com/stoutlabs - featured: false -- title: Chicago Ticket Outcomes By Neighborhood - description: > - ProPublica data visualization of traffic ticket court outcomes - categories: - - News - - Nonprofit - - Visualization - url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-status/?initialWidth=782 - built_by: David Eads - built_by_url: https://github.com/eads - featured: false -- title: Chicago South Side Traffic Ticketing rates - description: > - ProPublica data visualization of traffic ticket rates by community - url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-rate/?initialWidth=782 - categories: - - News - - Nonprofit - - Visualization - built_by: David Eads - built_by_url: https://github.com/eads - featured: false -- title: Otsimo - description: > - Otsimo is a special education application for children with autism, down syndrome and other developmental disabilities. - main_url: https://otsimo.com/en/ - url: https://otsimo.com/en/ - categories: - - Landing - - Blog - - Education - featured: false -- title: Matt Bagni Portfolio 2018 - description: > - Mostly the result of playing with Gatsby and learning about react and graphql. Using the screenshot plugin to showcase the work done for my company in the last 2 years, and a good amount of other experiments. - main_url: https://mattbag.github.io - url: https://mattbag.github.io - categories: - - Landing - - Portfolio - featured: false -- title: Lifestone Church - main_url: 'https://www.lifestonechurch.net/' - url: 'https://www.lifestonechurch.net/' - source_url: 'https://github.com/lifestonechurch/lifestonechurch.net' - featured: false - categories: - - Marketing - - Nonprofit -- title: Artem Sapegin - description: > - Little homepage of Artem Sapegin, a frontend developer, passionate photographer, coffee drinker and crazy dogs’ owner. - main_url: https://sapegin.me/ - url: https://sapegin.me/ - categories: - - Landing - - Portfolio - - Open Source - - Personal - - Web Dev - built_by: Artem Sapegin - built_by_url: https://github.com/sapegin - featured: false -- title: SparkPost Developers - main_url: 'https://developers.sparkpost.com/' - url: 'https://developers.sparkpost.com/' - source_url: 'https://github.com/SparkPost/developers.sparkpost.com' - categories: - - Documentation - - API - featured: false -- title: Malik Browne Portfolio 2018 - description: > - The portfolio blog of Malik Browne, a full stack engineer, foodie, and avid blogger/YouTuber. - main_url: https://www.malikbrowne.com/about - url: https://www.malikbrowne.com - categories: - - Blog - - Portfolio - built_by: Malik Browne - built_by_url: https://twitter.com/milkstarz - featured: false -- title: Novatics - description: > - Digital products that inspire and make a difference - main_url: https://www.novatics.com.br - url: https://www.novatics.com.br - categories: - - Landing - - Portfolio - - Technology - - Digital Products - - Web Dev - built_by: Novatics - built_by_url: https://github.com/Novatics - featured: false -- title: I migliori by Vivigratis - description: > - Product review website - main_url: https://imigliori.vivigratis.com/ - url: https://imigliori.vivigratis.com/ - categories: - - Blog - - Travel - - Technology - - Wellness - - Fashion - built_by: Kframe Interactive SA - featured: false -- title: Max McKinney - description: > - I’m a developer and designer with a focus in web technologies. I build cars on the side. - main_url: https://maxmckinney.com/ - url: https://maxmckinney.com/ - categories: - - Portfolio - - Web Dev - - Design - - Landing - - Personal - built_by: Max McKinney - featured: false -- title: Stickyard - description: > - Make your React component sticky the easy way - main_url: https://nihgwu.github.io/stickyard/ - url: https://nihgwu.github.io/stickyard/ - source_url: https://github.com/nihgwu/stickyard/tree/master/website - categories: - - Web Dev - built_by: Neo Nie - featured: false -- title: Agata Milik - description: > - Website of a Polish psychologist/psychotherapist based in Gdańsk, Poland. - main_url: https://agatamilik.pl - url: https://agatamilik.pl - categories: - - Landing - - Marketing - - Healthcare - built_by: Piotr Fedorczyk - built_by_url: https://piotrf.pl - featured: false -- title: WebPurple - main_url: 'https://www.webpurple.net/' - url: 'https://www.webpurple.net/' - source_url: 'https://github.com/WebPurple/site' - description: >- - Site of local (Russia, Ryazan) frontend community. Main purpose is to show info about meetups and keep blog. - categories: - - Nonprofit - - Web Dev - - Community - - Blog - - Open Source - built_by: Nikita Kirsanov - built_by_url: 'https://twitter.com/kitos_kirsanov' - featured: false -- title: Papertrail.io - description: > - Inspection Management for the 21st Century - main_url: https://www.papertrail.io/ - url: https://www.papertrail.io/ - categories: - - Marketing - - Technology - - Landing - built_by: Papertrail.io - built_by_url: https://www.papertrail.io - featured: false -- title: Matt Ferderer - main_url: https://mattferderer.com - url: https://mattferderer.com - source_url: https://github.com/mattferderer/gatsbyblog - description: > - {titleofthesite} is a blog built with Gatsby that discusses web related tech such as JavaScript, .NET, Blazor & security. - categories: - - Blog - - Web Dev - - Personal - built_by: Matt Ferderer - built_by_url: https://twitter.com/mattferderer - featured: false -- title: Sahyadri Open Source Community - main_url: https://sosc.org.in - url: https://sosc.org.in - source_url: https://github.com/haxzie/sosc-website - description: > - Official website of Sahyadri Open Source Community for community blog, event details and members info. - categories: - - Blog - - Community - - Open Source - built_by: Musthaq Ahamad - built_by_url: https://github.com/haxzie - featured: false -- title: Tech Confessions - main_url: https://confessions.tech - url: https://confessions.tech - source_url: https://github.com/JonathanSpeek/tech-confessions - description: > - A guilt-free place for us to confess our tech sins 🙏 - categories: - - Community - - Open Source - built_by: Jonathan Speek - built_by_url: https://speek.design - featured: false -- title: Thibault Maekelbergh - main_url: https://thibmaek.com - url: https://thibmaek.com - source_url: https://github.com/thibmaek/thibmaek.github.io - description: > - A nice blog about development, Raspberry Pi, plants and probably records. - categories: - - Blog - - Open Source - built_by: Thibault Maekelbergh - built_by_url: https://twitter.com/thibmaek - featured: false -- title: LearnReact.design - main_url: https://learnreact.design - url: https://learnreact.design - description: > - React Essentials For Designers: A React course tailored for product designers, ux designers, ui designers. - categories: - - Blog - - Landing - - Creative - built_by: Linton Ye - built_by_url: https://twitter.com/lintonye -- title: DesignSystems.com - main_url: https://www.designsystems.com/ - url: https://www.designsystems.com/ - description: > - A resource for learning, creating and evangelizing design systems. - categories: - - Design - - Blog - - Technology - built_by: Corey Ward - built_by_url: http://www.coreyward.me/ - featured: false -- title: Devol’s Dance - main_url: https://www.devolsdance.com/ - url: https://www.devolsdance.com/ - description: > - Devol’s Dance is an invite-only, one-day event celebrating industrial robotics, AI, and automation. - categories: - - Marketing - - Landing - - Technology - built_by: Corey Ward - built_by_url: http://www.coreyward.me/ - featured: false -- title: Mega House Creative - main_url: https://www.megahousecreative.com/ - url: https://www.megahousecreative.com/ - description: > - Mega House Creative is a digital agency that provides unique goal-oriented web marketing solutions. - categories: - - Marketing - - Agency - built_by: Daniel Robinson - featured: false -- title: Tobie Marier Robitaille - csc - main_url: https://tobiemarierrobitaille.com/ - url: https://tobiemarierrobitaille.com/en/ - description: > - Portfolio site for director of photography Tobie Marier Robitaille - categories: - - Portfolio - - Gallery - built_by: Mill3 Studio - built_by_url: https://mill3.studio/en/ - featured: false -- title: Bestvideogame.deals - main_url: https://bestvideogame.deals/ - url: https://bestvideogame.deals/ - description: > - Video game comparison website for the UK, build with GatsbyJS. - categories: - - eCommerce - - Video Games - built_by: Koen Kamphuis - built_by_url: https://koenkamphuis.com/ - featured: false -- title: Mahipat's Portfolio - main_url: https://mojaave.com/ - url: https://mojaave.com - source_url: https://github.com/mhjadav/mojaave - description: > - mojaave.com is Mahipat's portfolio, I have developed it using Gatsby v2 and Bootstrap, To get in touch with people looking for full stack developer. - categories: - - Portfolio - - Software - - Web Dev - - Personal - built_by: Mahipat Jadav - built_by_url: https://mojaave.com/ - featured: false -- title: Cmsbased - main_url: https://www.cmsbased.net - url: https://www.cmsbased.net - description: > - Cmsbased is providing automation tools and design resources for Web Hosting and IT services industry. - categories: - - Technology - - Design - - Digital Products - featured: false -- title: Traffic Design Biennale 2018 - main_url: https://trafficdesign.pl - url: http://trafficdesign.pl/pl/ficzery/2018-biennale/ - description: > - Mini site for 2018 edition of Traffic Design’s Biennale - categories: - - Landing - - Creative - - Nonprofit - - Gallery - built_by: Piotr Fedorczyk - built_by_url: https://piotrf.pl - featured: false -- title: Timely - main_url: https://timelyapp.com/ - url: https://timelyapp.com/ - description: > - Fully automatic time tracking. For those who trade in time. - categories: - - Software - - Time Tracking - - Tool - built_by: Timm Stokke - built_by_url: https://timm.stokke.me - featured: false -- title: Stitch Fix - main_url: https://www.stitchfix.com/ - url: https://www.stitchfix.com/ - description: > - Stitch Fix is an online styling service that delivers a truly personalized shopping experience. - categories: - - eCommerce - - Clothing - - Fashion - featured: false -- title: Snap Kit - main_url: https://kit.snapchat.com/ - url: https://kit.snapchat.com/ - description: > - Snap Kit lets developers integrate some of Snapchat’s best features across platforms. - categories: - - Technology - - Tool - - Software - - Documentation - featured: false -- title: Insights - main_url: https://justaskusers.com/ - url: https://justaskusers.com/ - description: > - Insights helps user experience (UX) researchers conduct their research and make sense of the findings. - categories: - - User Experience - - Research - - Design - built_by: Just Ask Users - built_by_url: https://justaskusers.com/ - featured: false -- title: Tensiq - main_url: https://tensiq.com - url: https://tensiq.com - source_url: https://github.com/Tensiq/tensiq-site - description: > - Tensiq is an e-Residency startup, that provides development in cutting-edge technology while delivering secure, resilient, performant solutions. - categories: - - Game Dev - - Web Dev - - Mobile Dev - - Agency - - Open Source - built_by: Jens - built_by_url: https://github.com/arrkiin - featured: false -- title: SendGrid - main_url: https://sendgrid.com/docs/ - url: https://sendgrid.com/docs/ - description: > - SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform. - categories: - - Technology - - Tool - - Software - - Documentation - featured: false -- title: Mintfort - main_url: https://mintfort.com/ - url: https://mintfort.com/ - source_url: https://github.com/MintFort/mintfort.com - description: > - Mintfort, the first crypto-friendly bank account. Store and manage assets on the blockchain. - categories: - - Technology - - Tool - - Bank - - Software - built_by: Axel Fuhrmann - built_by_url: https://axelfuhrmann.com/ - featured: false -- title: React Native Explorer - main_url: https://react-native-explorer.firebaseapp.com - url: https://react-native-explorer.firebaseapp.com - description: > - Explorer React Native packages and examples effortlessly. - categories: - - React Native - - Software - - Tool - featured: false +# - title: A4 纸网 +# main_url: 'http://www.a4z.cn/' +# url: 'http://www.a4z.cn/price' +# source_url: 'https://github.com/hiooyUI/hiooyui.github.io' +# featured: false +# categories: +# - Retail +# - title: Steve Meredith's Portfolio +# main_url: 'http://www.stevemeredith.com/' +# url: 'http://www.stevemeredith.com/' +# featured: false +# categories: +# - Portfolio +# - title: Sourcegraph +# main_url: 'https://about.sourcegraph.com/' +# url: 'https://about.sourcegraph.com/' +# featured: false +# categories: +# - Web Dev +# - title: API Platform +# main_url: 'https://api-platform.com/' +# url: 'https://api-platform.com/' +# source_url: 'https://github.com/api-platform/website' +# featured: false +# categories: +# - Documentation +# - Web Dev +# - Open Source +# - Library +# - title: Artivest +# main_url: 'https://artivest.co/' +# url: 'https://artivest.co/what-we-do/for-advisors-and-investors/' +# featured: false +# categories: +# - Marketing +# - Blog +# - Documentation +# - Finance +# - title: The Audacious Project +# main_url: 'https://audaciousproject.org/' +# url: 'https://audaciousproject.org/' +# featured: false +# categories: +# - Nonprofit +# - title: Dustin Schau's Blog +# main_url: 'https://blog.dustinschau.com/' +# url: 'https://blog.dustinschau.com/' +# source_url: 'https://github.com/dschau/blog' +# featured: false +# categories: +# - Blog +# - Web Dev +# - title: FloydHub's Blog +# main_url: 'https://blog.floydhub.com/' +# url: 'https://blog.floydhub.com/' +# featured: false +# categories: +# - Technology +# - Blog +# - title: iContract Blog +# main_url: 'https://blog.icontract.co.uk/' +# url: 'http://blog.icontract.co.uk/' +# featured: false +# categories: +# - Blog +# - title: BRIIM +# main_url: 'https://bri.im/' +# url: 'https://bri.im/' +# featured: false +# description: >- +# BRIIM is a movement to enable JavaScript enthusiasts and web developers in +# machine learning. Learn about artificial intelligence and data science, two +# fields which are governed by machine learning, in JavaScript. Take it right +# to your browser with WebGL. +# categories: +# - Education +# - Web Dev +# - Technology +# - title: Caddy Smells Like Trees +# main_url: 'https://caddysmellsliketrees.ru/' +# url: 'https://caddysmellsliketrees.ru/' +# source_url: 'https://github.com/podabed/caddysmellsliketrees.github.io' +# featured: false +# description: >- +# We play soul-searching songs for every day. They are merging in our forests +# in such a way that it is difficult to separate them from each other, and +# between them bellow bold deer poems. +# categories: +# - Music +# - title: Calpa's Blog +# main_url: 'https://calpa.me/' +# url: 'https://calpa.me/' +# source_url: 'https://github.com/calpa/blog' +# featured: false +# categories: +# - Blog +# - Web Dev +# - title: Chocolate Free +# main_url: 'https://chocolate-free.com/' +# url: 'https://chocolate-free.com/' +# source_url: 'https://github.com/Khaledgarbaya/chocolate-free-website' +# featured: false +# description: "A full time foodie \U0001F60D a forever Parisian \"patisserie\" lover and \U0001F382 \U0001F369 \U0001F370 \U0001F36A explorer and finally an under construction #foodblogger #foodblog" +# categories: +# - Blog +# - Food +# - title: Code Bushi +# main_url: 'https://codebushi.com/' +# url: 'https://codebushi.com/' +# featured: false +# description: >- +# Web development resources, trends, & techniques to elevate your coding journey. +# categories: +# - Web Dev +# - Open Source +# - Blog +# built_by: Hunter Chang +# built_by_url: 'https://hunterchang.com/' +# - title: Daniel Hollcraft +# main_url: 'https://danielhollcraft.com/' +# url: 'https://danielhollcraft.com/' +# source_url: 'https://github.com/danielbh/danielhollcraft.com' +# featured: false +# categories: +# - Web Dev +# - Blog +# - Portfolio +# - title: Darren Britton's Portfolio +# main_url: 'https://darrenbritton.com/' +# url: 'https://darrenbritton.com/' +# source_url: 'https://github.com/darrenbritton/darrenbritton.github.io' +# featured: false +# categories: +# - Web Dev +# - Portfolio +# - title: Dave Lindberg Marketing & Design +# url: 'https://davelindberg.com/' +# main_url: 'https://davelindberg.com/' +# source_url: 'https://github.com/Dave-Lindberg/dl-gatsby' +# featured: false +# description: >- +# My work revolves around solving problems for people in business, using integrated design and marketing strategies to improve sales, increase brand engagement, generate leads and achieve goals. +# categories: +# - Creative +# - Design +# - Featured +# - Marketing +# - SEO +# - Portfolio +# - title: Design Systems Weekly +# main_url: 'https://designsystems.email/' +# url: 'https://designsystems.email/' +# featured: false +# categories: +# - Education +# - Web Dev +# - title: Dalbinaco's Website +# main_url: 'https://dlbn.co/en/' +# url: 'https://dlbn.co/en/' +# source_url: 'https://github.com/dalbinaco/dlbn.co' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - title: mParticle's Documentation +# main_url: 'https://docs.mparticle.com/' +# url: 'https://docs.mparticle.com/' +# featured: false +# categories: +# - Web Dev +# - Documentation +# - title: Doopoll +# main_url: 'https://doopoll.co/' +# url: 'https://doopoll.co/' +# featured: false +# categories: +# - Landing +# - Marketing +# - Technology +# - title: ERC dEX +# main_url: 'https://ercdex.com/' +# url: 'https://ercdex.com/aqueduct' +# featured: false +# categories: +# - Marketing +# - title: Fabian Schultz' Portfolio +# main_url: 'https://fabianschultz.com/' +# url: 'https://fabianschultz.com/' +# source_url: 'https://github.com/fabe/site' +# featured: false +# description: >- +# Hello, I’m Fabian — a product designer and developer based in Potsdam, +# Germany. I’ve been working both as a product designer and frontend developer +# for over 5 years now. I particularly enjoy working with companies that try +# to meet broad and unique user needs. +# categories: +# - Portfolio +# - Web Dev +# - title: Formidable +# main_url: 'https://formidable.com/' +# url: 'https://formidable.com/' +# featured: true +# categories: +# - Web Dev +# - Agency +# - Open Source +# - Featured +# - title: Gatsby Manor +# main_url: 'https://gatsbymanor.com/' +# url: 'https://gatsbymanor.com/themes' +# featured: false +# categories: +# - Web Dev +# - title: The freeCodeCamp Guide +# main_url: 'https://guide.freecodecamp.org/' +# url: 'https://guide.freecodecamp.org/' +# source_url: 'https://github.com/freeCodeCamp/guide' +# featured: false +# categories: +# - Web Dev +# - Documentation +# - title: High School Hackathons +# main_url: 'https://hackathons.hackclub.com/' +# url: 'https://hackathons.hackclub.com/' +# source_url: 'https://github.com/hackclub/hackathons' +# featured: false +# categories: +# - Education +# - Web Dev +# - title: Hapticmedia +# main_url: 'https://hapticmedia.fr/en/' +# url: 'https://hapticmedia.fr/en/' +# featured: false +# categories: +# - Agency +# - Creative +# - title: heml.io +# main_url: 'https://heml.io/' +# url: 'https://heml.io/' +# source_url: 'https://github.com/SparkPost/heml.io' +# featured: false +# categories: +# - Documentation +# - Web Dev +# - Open Source +# - title: Juliette Pretot's Portfolio +# main_url: 'https://juliette.sh/' +# url: 'https://juliette.sh/' +# featured: false +# categories: +# - Web Dev +# - Portfolio +# - Blog +# - title: Kris Hedstrom's Portfolio +# main_url: 'https://k-create.com/' +# url: 'https://k-create.com/portfolio/' +# source_url: 'https://github.com/kristofferh/kristoffer' +# featured: false +# description: >- +# Hey. I’m Kris. I’m an interactive designer / developer. I grew up in Umeå, +# in northern Sweden, but I now live in Brooklyn, NY. I am currently enjoying +# a hybrid Art Director + Lead Product Engineer role at a small startup called +# Nomad Health. Before that, I was a Product (Engineering) Manager at Tumblr. +# Before that, I worked at agencies. Before that, I was a baby. I like to +# design things, and then I like to build those things. I occasionally take on +# freelance projects. Feel free to get in touch if you have an interesting +# project that you want to collaborate on. Or if you just want to say hello, +# that’s cool too. +# categories: +# - Creative +# - Portfolio +# - title: knpw.rs +# main_url: 'https://knpw.rs/' +# url: 'https://knpw.rs/' +# source_url: 'https://github.com/knpwrs/knpw.rs' +# featured: false +# categories: +# - Blog +# - Web Dev +# - title: Kostas Bariotis' Blog +# main_url: 'https://kostasbariotis.com/' +# url: 'https://kostasbariotis.com/' +# source_url: 'https://github.com/kbariotis/kostasbariotis.com' +# featured: false +# categories: +# - Blog +# - Portfolio +# - Web Dev +# - title: LaserTime Clinic +# main_url: 'https://lasertime.ru/' +# url: 'https://lasertime.ru/' +# source_url: 'https://github.com/oleglegun/lasertime' +# featured: false +# categories: +# - Marketing +# - title: Jason Lengstorf +# main_url: 'https://lengstorf.com' +# url: 'https://lengstorf.com' +# source_url: 'https://github.com/jlengstorf/lengstorf.com' +# featured: false +# date_added: Apr 10 +# gatsby_version: V1 +# categories: +# - Blog +# - Personal +# built_by: Jason Lengstorf +# built_by_url: 'https://github.com/jlengstorf' +# - title: Mannequin.io +# main_url: 'https://mannequin.io/' +# url: 'https://mannequin.io/' +# source_url: 'https://github.com/LastCallMedia/Mannequin/tree/master/site' +# featured: false +# categories: +# - Open Source +# - Web Dev +# - Documentation +# - title: manu.ninja +# main_url: 'https://manu.ninja/' +# url: 'https://manu.ninja/' +# source_url: 'https://github.com/Lorti/manu.ninja' +# featured: false +# description: >- +# manu.ninja is the personal blog of Manuel Wieser, where he talks about +# front-end development, games and digital art +# categories: +# - Blog +# - Technology +# - Web Dev +# - title: Fabric +# main_url: 'https://meetfabric.com/' +# url: 'https://meetfabric.com/' +# featured: true +# categories: +# - Corporate +# - Marketing +# - Featured +# - Insurance +# - title: Nexit +# main_url: 'https://nexit.sk/' +# url: 'https://nexit.sk/references' +# featured: false +# categories: +# - Web Dev +# - title: Nortcast +# main_url: 'https://nortcast.com/' +# url: 'https://nortcast.com/' +# featured: false +# categories: +# - Technology +# - Entertainment +# - Podcast +# - title: openFDA +# main_url: 'https://open.fda.gov/' +# url: 'https://open.fda.gov/' +# source_url: 'https://github.com/FDA/open.fda.gov' +# featured: false +# categories: +# - Government +# - Open Source +# - Web Dev +# - title: NYC Planning Labs (New York City Department of City Planning) +# main_url: 'https://planninglabs.nyc/' +# url: 'https://planninglabs.nyc/about/' +# source_url: 'https://github.com/NYCPlanning/' +# featured: false +# description: >- +# We work with New York City's Urban Planners to deliver impactful, modern +# technology tools. +# categories: +# - Open Source +# - Government +# - title: Pravdomil +# main_url: 'https://pravdomil.com/' +# url: 'https://pravdomil.com/' +# source_url: 'https://github.com/pravdomil/pravdomil.com' +# featured: false +# description: >- +# I’ve been working both as a product designer and frontend developer for over +# 5 years now. I particularly enjoy working with companies that try to meet +# broad and unique user needs. +# categories: +# - Portfolio +# - Personal +# - title: Preston Richey Portfolio / Blog +# main_url: 'https://prestonrichey.com/' +# url: 'https://prestonrichey.com/' +# source_url: 'https://github.com/prichey/prestonrichey.com' +# featured: false +# categories: +# - Web Dev +# - Portfolio +# - Blog +# - title: Landing page of Put.io +# main_url: 'https://put.io/' +# url: 'https://put.io/' +# featured: false +# categories: +# - eCommerce +# - Technology +# - title: The Rick and Morty API +# main_url: 'https://rickandmortyapi.com/' +# url: 'https://rickandmortyapi.com/' +# featured: false +# categories: +# - Web Dev +# - Entertainment +# - Documentation +# - Open Source +# - title: Santa Compañía Creativa +# main_url: 'https://santacc.es/' +# url: 'https://santacc.es/' +# source_url: 'https://github.com/DesarrolloWebSantaCC/santacc-web' +# featured: false +# categories: +# - Agency +# - Creative +# - title: Sean Coker's Blog +# main_url: 'https://sean.is/' +# url: 'https://sean.is/' +# featured: false +# categories: +# - Blog +# - Portfolio +# - Web Dev +# - title: Segment's Blog +# main_url: 'https://segment.com/blog/' +# url: 'https://segment.com/blog/' +# featured: false +# categories: +# - Web Dev +# - Blog +# - title: Several Levels +# main_url: 'https://severallevels.io/' +# url: 'https://severallevels.io/' +# source_url: 'https://github.com/Harrison1/several-levels' +# featured: false +# categories: +# - Agency +# - Web Dev +# - title: Simply +# main_url: 'https://simply.co.za/' +# url: 'https://simply.co.za/' +# featured: false +# categories: +# - Corporate +# - Marketing +# - Insurance +# - title: Storybook +# main_url: 'https://storybook.js.org/' +# url: 'https://storybook.js.org/' +# source_url: 'https://github.com/storybooks/storybook' +# featured: false +# categories: +# - Web Dev +# - Open Source +# - title: Vibert Thio's Portfolio +# main_url: 'https://vibertthio.com/portfolio/' +# url: 'https://vibertthio.com/portfolio/projects/' +# source_url: 'https://github.com/vibertthio/portfolio' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Creative +# - title: VisitGemer +# main_url: 'https://visitgemer.sk/' +# url: 'https://visitgemer.sk/' +# featured: false +# categories: +# - Marketing +# - title: Beach Hut Poole +# main_url: 'https://www.beachhutpoole.co.uk/' +# url: 'https://www.beachhutpoole.co.uk/' +# featured: false +# categories: +# - Travel +# - Marketing +# - title: Bricolage.io +# main_url: 'https://www.bricolage.io/' +# url: 'https://www.bricolage.io/' +# source_url: 'https://github.com/KyleAMathews/blog' +# featured: false +# categories: +# - Blog +# - title: Charles Pinnix Website +# main_url: 'https://www.charlespinnix.com/' +# url: 'https://www.charlespinnix.com/' +# featured: false +# description: >- +# I’m a senior front end engineer with 8 years of experience building websites +# and web applications. I’m interested in leading creative, multidisciplinary +# engineering teams. I’m a creative technologist, merging photography, art, +# and design into engineering and visa versa. I take a pragmatic, +# product-oriented approach to development, allowing me to see the big picture +# and ensuring quality products are completed on time. I have a passion for +# modern front end JavaScript frameworks such as React and Vue, and I have +# substantial experience on the back end with an interest in Node and +# container based deployment with Docker and AWS. +# categories: +# - Portfolio +# - Web Dev +# - title: Charlie Harrington's Blog +# main_url: 'https://www.charlieharrington.com/' +# url: 'https://www.charlieharrington.com/' +# source_url: 'https://github.com/whatrocks/blog' +# featured: false +# categories: +# - Blog +# - Web Dev +# - Music +# - title: Developer Ecosystem +# main_url: 'https://www.developerecosystem.com/' +# url: 'https://www.developerecosystem.com/' +# featured: false +# categories: +# - Blog +# - Web Dev +# - title: Gabriel Adorf's Portfolio +# main_url: 'https://www.gabrieladorf.com/' +# url: 'https://www.gabrieladorf.com/' +# source_url: 'https://github.com/gabdorf/gabriel-adorf-portfolio' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - title: greglobinski.com +# main_url: 'https://www.greglobinski.com/' +# url: 'https://www.greglobinski.com/' +# source_url: 'https://github.com/greglobinski/www.greglobinski.com' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - title: I am Putra +# main_url: 'https://www.iamputra.com/' +# url: 'https://www.iamputra.com/' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: In Sowerby Bridge +# main_url: 'https://www.insowerbybridge.co.uk/' +# url: 'https://www.insowerbybridge.co.uk/' +# featured: false +# categories: +# - Marketing +# - Government +# - title: JavaScript Stuff +# main_url: 'https://www.javascriptstuff.com/' +# url: 'https://www.javascriptstuff.com/' +# featured: false +# categories: +# - Education +# - Web Dev +# - Library +# - title: KNW Photography +# main_url: 'https://www.knw.io/' +# url: 'https://www.knw.io/galleries/' +# source_url: 'https://github.com/ryanwiemer/knw' +# featured: false +# description: >- +# Hi there! I’m Kirsten and I’m currently living in Oakland with my husband +# and fluffy pup Birch. I’ve always been an excessive photo taker and decided +# to turn my love for taking photos into a career. +# categories: +# - Photography +# - Portfolio +# - title: Ledgy +# main_url: 'https://www.ledgy.com/' +# url: 'https://github.com/morloy/ledgy.com' +# featured: false +# categories: +# - Marketing +# - Finance +# - title: Alec Lomas's Portfolio / Blog +# main_url: 'https://www.lowmess.com/' +# url: 'https://www.lowmess.com/' +# source_url: 'https://github.com/lowmess/lowmess' +# featured: false +# categories: +# - Web Dev +# - Blog +# - Portfolio +# - title: Michele Mazzucco's Portfolio +# main_url: 'https://www.michelemazzucco.it/' +# url: 'https://www.michelemazzucco.it/' +# source_url: 'https://github.com/michelemazzucco/michelemazzucco.it' +# featured: false +# categories: +# - Creative +# - Portfolio +# - title: Orbit FM Podcasts +# main_url: 'https://www.orbit.fm/' +# url: 'https://www.orbit.fm/' +# source_url: 'https://github.com/agarrharr/orbit.fm' +# featured: false +# categories: +# - Podcast +# - title: Prosecco Springs +# main_url: 'https://www.proseccosprings.com/' +# url: 'https://www.proseccosprings.com/' +# featured: false +# categories: +# - Food +# - Blog +# - Marketing +# - title: Verious +# main_url: 'https://www.verious.io/' +# url: 'https://www.verious.io/' +# source_url: 'https://github.com/cpinnix/verious' +# featured: false +# categories: +# - Web Dev +# - title: Whittle School +# main_url: 'https://www.whittleschool.org/en/' +# url: 'https://www.whittleschool.org/en/' +# featured: false +# categories: +# - Education +# - title: Yisela +# main_url: 'https://www.yisela.com/' +# url: 'https://www.yisela.com/tetris-against-trauma-gaming-as-therapy/' +# featured: false +# categories: +# - Blog +# - title: YouFoundRon.com +# main_url: 'https://www.youfoundron.com/' +# url: 'https://www.youfoundron.com/' +# source_url: 'https://github.com/rongierlach/yfr-dot-com' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: yerevancoder +# main_url: 'https://yerevancoder.com/' +# url: 'https://forum.yerevancoder.com/categories' +# source_url: 'https://github.com/yerevancoder/yerevancoder.github.io' +# featured: false +# categories: +# - Blog +# - Web Dev +# - title: EaseCentral +# main_url: 'https://www.easecentral.com/' +# url: 'https://www.easecentral.com/' +# featured: false +# categories: +# - Marketing +# - Healthcare +# - title: Policygenius +# main_url: 'https://www.policygenius.com/' +# url: 'https://www.policygenius.com/' +# featured: false +# categories: +# - Marketing +# - Healthcare +# - title: Moteefe +# main_url: 'http://www.moteefe.com/' +# url: 'http://www.moteefe.com/' +# featured: false +# categories: +# - Marketing +# - Agency +# - Technology +# - title: Athelas +# main_url: 'http://www.athelas.com/' +# url: 'http://www.athelas.com/' +# featured: true +# categories: +# - Marketing +# - Healthcare +# - Featured +# - title: Pathwright +# main_url: 'http://www.pathwright.com/' +# url: 'http://www.pathwright.com/' +# featured: false +# categories: +# - Marketing +# - Education +# - title: pi-top +# main_url: 'http://www.pi-top.com/' +# url: 'http://www.pi-top.com/' +# featured: false +# categories: +# - Marketing +# - Web Dev +# - Technology +# - eCommerce +# - title: Troops +# main_url: 'http://www.troops.ai/' +# url: 'http://www.troops.ai/' +# featured: false +# categories: +# - Marketing +# - Technology +# - title: ClearBrain +# main_url: 'https://clearbrain.com/' +# url: 'https://clearbrain.com/' +# featured: false +# categories: +# - Marketing +# - Technology +# - title: Lucid +# main_url: 'https://www.golucid.co/' +# url: 'https://www.golucid.co/' +# featured: true +# categories: +# - Marketing +# - Technology +# - Featured +# - title: Bench +# main_url: 'http://www.bench.co/' +# url: 'http://www.bench.co/' +# featured: false +# categories: +# - Marketing +# - title: Union Plus Credit Card +# main_url: 'http://www.unionpluscard.com' +# url: 'https://unionplus.capitalone.com/' +# featured: false +# categories: +# - Marketing +# - Finance +# - title: Gin Lane +# main_url: 'http://www.ginlane.com/' +# url: 'https://www.ginlane.com/' +# featured: false +# categories: +# - Web Dev +# - Creative +# - Agency +# - title: Marmelab +# main_url: 'https://marmelab.com/en/' +# url: 'https://marmelab.com/en/' +# featured: false +# categories: +# - Web Dev +# - Agency +# - title: Fusion Media Group +# main_url: 'http://thefmg.com/' +# url: 'http://thefmg.com/' +# featured: true +# categories: +# - Creative +# - Entertainment +# - News +# - Featured +# - title: Cool Hunting +# main_url: 'http://www.coolhunting.com/' +# url: 'http://www.coolhunting.com/' +# featured: false +# categories: +# - Magazine +# - title: Dovetail +# main_url: 'https://dovetailapp.com/' +# url: 'https://dovetailapp.com/' +# featured: false +# categories: +# - Marketing +# - Technology +# - title: GraphQL College +# main_url: 'https://www.graphql.college/' +# url: 'https://www.graphql.college/' +# source_url: 'https://github.com/GraphQLCollege/graphql-college' +# featured: false +# categories: +# - Web Dev +# - Education +# - title: F1 Vision +# main_url: 'https://www.f1vision.com/' +# url: 'https://www.f1vision.com/' +# featured: false +# categories: +# - Marketing +# - Entertainment +# - Technology +# - eCommerce +# - title: Yuuniworks Portfolio / Blog +# main_url: 'https://www.yuuniworks.com/' +# url: 'https://www.yuuniworks.com/' +# source_url: 'https://github.com/junkboy0315/yuuni-web' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: Bastion Bot +# main_url: 'https://bastionbot.org/' +# url: 'https://bastionbot.org/' +# featured: false +# categories: +# - Marketing +# - Technology +# - Documentation +# - Web Dev +# - title: upGizmo +# main_url: 'https://www.upgizmo.com/' +# url: 'https://www.upgizmo.com/' +# featured: false +# categories: +# - News +# - Technology +# - title: Smakosh +# main_url: 'https://smakosh.com/' +# url: 'https://smakosh.com/' +# source_url: 'https://github.com/smakosh/smakosh.com' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Creative +# - title: Philipp Czernitzki - Blog/Website +# main_url: 'http://philippczernitzki.me/' +# url: 'http://philippczernitzki.me/' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: WebGazer +# main_url: 'https://www.webgazer.io/' +# url: 'https://www.webgazer.io/' +# featured: false +# categories: +# - Marketing +# - Web Dev +# - Technology +# - title: Joe Seifi's Blog +# main_url: 'http://seifi.org/' +# url: 'http://seifi.org/' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: Bartosz Dominiak Blog/Portfolio +# main_url: 'http://bartoszdominiak.com/' +# url: 'http://bartoszdominiak.com/' +# source_url: 'https://github.com/bartdominiak/blog' +# featured: false +# categories: +# - Portfolio +# - Web Dev +# - Blog +# - title: HBTU MUN 2018 (source) +# featured: false +# - title: Jamie Henson's Blog (source) +# featured: false +# - title: Ruben's Blog (source) +# featured: false +# - title: Thao Am Private Enterprise +# featured: false +# - title: Bakadono +# featured: false +# - title: Travellers.cafe +# featured: false +# - title: Oliver Benns' Portfolio (source) +# featured: false +# - title: angeloocana.com (source) +# featured: false +# - title: Overlap.show (source) +# featured: false +# - title: smartive Company Website +# featured: false +# - title: Haboba Find Jobs at Phu Quoc Island +# featured: false +# - title: Song Wang’s website (source) +# featured: false +# - title: Magicly's blog (source) +# featured: false +# - title: Phu Quoc Works +# featured: false +# - title: Kabir Goel's website (source) +# featured: false +# - title: David James' Portfolio (source) +# featured: false +# - title: Tic Tac Toe AI (source) +# featured: false +# - title: Random Screencast +# featured: false +# - title: Phu Quoc Tea & Coffee Store +# featured: false +# - title: Steven Natera's blog +# featured: false +# - title: LekoArts +# main_url: 'https://www.lekoarts.de' +# url: 'https://www.lekoarts.de' +# source_url: 'https://github.com/LeKoArts/portfolio' +# featured: false +# built_by: LekoArts +# built_by_url: 'https://github.com/LeKoArts' +# description: >- +# Hi, I'm Lennart — a self-taught and passionate graphic/web designer & frontend developer based in Darmstadt, Germany. I love it to realize complex projects in a creative manner and face new challenges. Since 6 years I do graphic design, my love for frontend development came up 3 years ago. I enjoy acquiring new skills and cementing this knowledge by writing blogposts and creating tutorials. +# categories: +# - Portfolio +# - Blog +# - title: Georgi Yanev (source) +# featured: false +# - title: Hallingdata +# featured: false +# - title: '@swyx (source)' +# featured: false +# - title: 伊撒尔の窝 +# featured: false +# - title: 杨二小的博客 +# main_url: 'https://blog.yangerxiao.com/' +# url: 'https://blog.yangerxiao.com/' +# source_url: 'https://github.com/zerosoul/blog.yangerxiao.com' +# featured: false +# categories: +# - Blog +# - Portfolio +# - title: mottox2 blog +# main_url: 'https://mottox2.com' +# url: 'https://mottox2.com' +# featured: false +# categories: +# - Blog +# - Portfolio +# - title: Pride of the Meadows +# main_url: 'https://www.prideofthemeadows.com/' +# url: 'https://www.prideofthemeadows.com/' +# featured: false +# categories: +# - Retail +# - Food +# - Blog +# - title: Michael Uloth +# main_url: 'https://www.michaeluloth.com' +# url: 'https://www.michaeluloth.com' +# featured: false +# description: >- +# Michael Uloth is an opera singer and web developer based in Toronto. +# categories: +# - Portfolio +# - Music +# - Web Dev +# built_by: Michael Uloth +# built_by_url: 'https://www.michaeluloth.com' +# - title: NYC Pride 2019 | WorldPride NYC | Stonewall50 +# main_url: 'https://2019-worldpride-stonewall50.nycpride.org/' +# url: 'https://2019-worldpride-stonewall50.nycpride.org/' +# featured: false +# description: >- +# Join us in 2019 for NYC Pride, as we welcome WorldPride and mark the 50th Anniversary of the Stonewall Uprising and a half-century of LGBTQ+ liberation. +# categories: +# - Education +# - Marketing +# - Nonprofit +# built_by: Canvas United +# built_by_url: 'https://www.canvasunited.com/' +# - title: Spacetime +# main_url: 'https://www.heyspacetime.com/' +# url: 'https://www.heyspacetime.com/' +# featured: false +# description: >- +# Spacetime is a Dallas-based digital experience agency specializing in web, app, startup, and digital experience creation. +# categories: +# - Marketing +# - Portfolio +# - Agency +# - Creative +# - Featured +# built_by: Spacetime +# built_by_url: 'https://www.heyspacetime.com/' +# - title: Eric Jinks +# main_url: 'https://ericjinks.com/' +# url: 'https://ericjinks.com/' +# featured: false +# description: >- +# Software engineer / web developer from the Gold Coast, Australia. +# categories: +# - Portfolio +# - Blog +# - Web Dev +# - Technology +# built_by: Eric Jinks +# built_by_url: 'https://ericjinks.com/' +# - title: GaiAma - We are wildlife +# main_url: 'https://www.gaiama.org/' +# url: 'https://www.gaiama.org/' +# featured: false +# description: >- +# We founded the GaiAma conservation organization to protect wildlife in Perú and to create an example of a permaculture neighborhood, living symbiotically with the forest - because reforestation is just the beginning +# categories: +# - Nonprofit +# - Marketing +# - Blog +# source_url: https://github.com/GaiAma/gaiama.org +# built_by: GaiAma +# built_by_url: 'https://www.gaiama.org/' +# - title: Healthcare Logic +# main_url: 'https://www.healthcarelogic.com/' +# url: 'https://www.healthcarelogic.com/' +# featured: false +# description: >- +# Revolutionary technology that empowers clinical and managerial leaders to collaborate with clarity. +# categories: +# - Marketing +# - Healthcare +# - Technology +# built_by: Thrive +# built_by_url: 'https://thriveweb.com.au/' +# - title: Localgov.fyi +# main_url: 'https://localgov.fyi/' +# url: 'https://localgov.fyi/' +# featured: false +# description: >- +# Finding local government services made easier. +# categories: +# - Directory +# - Government +# - Technology +# source_url: https://github.com/WeOpenly/localgov.fyi +# built_by: Openly +# built_by_url: 'https://weopenly.com/' +# - title: Kata.ai Documentation +# main_url: 'https://docs.kata.ai/' +# url: 'https://docs.kata.ai/' +# source_url: https://github.com/kata-ai/kata-platform-docs +# featured: false +# description: >- +# Documentation website for the Kata Platform, an all-in-one platform for +# building chatbots using AI technologies. +# categories: +# - Documentation +# - Technology +# - title: goalgetters +# main_url: 'https://goalgetters.space/' +# url: 'https://goalgetters.space/' +# featured: false +# description: >- +# goalgetters is a source of inspiration for people who want to change their career. +# We offer articles, success stories and expert interviews on how to find a new passion and how to implement change. +# categories: +# - Blog +# - Education +# - Careers +# - Personal Development +# built_by: Stephanie Langers (content), Adrian Wenke (development) +# built_by_url: 'https://twitter.com/AdrianWenke' +# - title: Life Without Barriers | Foster Care +# main_url: 'https://www.lwb.org.au/foster-care' +# url: 'https://www.lwb.org.au/foster-care' +# featured: false +# description: >- +# We are urgently seeking foster carers all across Australia. Can you open your heart and your home to a child in need? +# There are different types of foster care that can suit you. We offer training and 24/7 support. +# categories: +# - Charity +# - Nonprofit +# - Education +# - Documentation +# - Marketing +# built_by: LWB Digital Team +# built_by_url: 'https://twitter.com/LWBAustralia' +# - title: Bejamas - JAM Experts for hire +# main_url: 'https://bejamas.io/' +# url: 'https://bejamas.io/' +# featured: false +# description: >- +# We help agencies and companies with JAMStack tools. This includes web development using Static Site Generators, Headless CMS, CI / CD and CDN setup. +# categories: +# - Technology +# - Web Dev +# - Agency +# - Creative +# - Marketing +# built_by: Bejamas.io +# built_by_url: 'https://bejamas.io/' +# - title: Zensum +# main_url: 'https://zensum.se/' +# url: 'https://zensum.se/' +# featured: false +# description: >- +# Borrow money quickly and safely through Zensum. We compare Sweden's leading banks and credit institutions. Choose from multiple offers and lower your monthly cost. [Translated from Swedish] +# categories: +# - Technology +# - Finance +# - Corporate +# - Marketing +# built_by: Bejamas.io +# built_by_url: 'https://bejamas.io/' +# - title: StatusHub - Easy to use Hosted Status Page Service +# main_url: 'https://statushub.com/' +# url: 'https://statushub.com/' +# featured: false +# description: >- +# Set up your very own service status page in minutes with StatusHub. Allow customers to subscribe to be updated automatically. +# categories: +# - Technology +# - Marketing +# built_by: Bejamas.io +# built_by_url: 'https://bejamas.io/' +# - title: Matthias Kretschmann Portfolio +# main_url: 'https://matthiaskretschmann.com/' +# url: 'https://matthiaskretschmann.com/' +# source_url: 'https://github.com/kremalicious/portfolio' +# featured: false +# description: >- +# Portfolio of designer & developer Matthias Kretschmann. +# categories: +# - Portfolio +# - Web Dev +# - Creative +# built_by: Matthias Kretschmann +# built_by_url: 'https://matthiaskretschmann.com/' +# - title: Cajun Bowfishing +# main_url: 'https://cajunbowfishing.com/' +# url: 'https://cajunbowfishing.com/' +# featured: false +# categories: +# - eCommerce +# - Sports +# built_by: Escalade Sports +# built_by_url: 'http://escaladesports.com/' +# - title: Iron Cove Solutions +# main_url: 'https://ironcovesolutions.com/' +# url: 'https://ironcovesolutions.com/' +# description: >- +# Iron Cove Solutions is a cloud based consulting firm. We help companies deliver a return on cloud usage by applying best practices +# categories: +# - Technology +# - Web Dev +# built_by: Iron Cove Solutions +# built_by_url: 'https://ironcovesolutions.com/' +# featured: false +# - title: Eventos orellana +# description: >- +# Somos una empresa dedicada a brindar asesoría personalizada +# y profesional para la elaboración y coordinación de eventos +# sociales y empresariales. +# main_url: 'https://eventosorellana.com/' +# url: 'https://eventosorellana.com/' +# featured: false +# categories: +# - Gallery +# built_by: Codedebug +# built_by_url: 'https://codedebug.co/' +# - title: Moetez Chaabene Portfolio / Blog +# main_url: 'https://moetez.me/' +# url: 'https://moetez.me/' +# source_url: 'https://github.com/moetezch/moetez.me' +# featured: false +# description: >- +# Portfolio of Moetez Chaabene +# categories: +# - Portfolio +# - Web Dev +# - Blog +# built_by: Moetez Chaabene +# built_by_url: 'https://twitter.com/moetezch' +# - title: Nikita +# description: >- +# Automation of system deployments in Node.js for applications and infrastructures. +# main_url: https://nikita.js.org/ +# url: https://nikita.js.org/ +# source_url: 'https://github.com/adaltas/node-nikita' +# categories: +# - Documentation +# - Open Source +# - Technology +# built_by: David Worms +# built_by_url: http://www.adaltas.com +# featured: false +# - title: Gourav Sood Blog & Portfolio +# main_url: 'https://www.gouravsood.com/' +# url: 'https://www.gouravsood.com/' +# featured: false +# categories: +# - Blog +# - Portfolio +# - Salesforce +# built_by: Gourav Sood +# built_by_url: 'https://www.gouravsood.com/' +# - title: Figma +# main_url: 'https://www.figma.com/' +# url: 'https://www.figma.com/' +# featured: false +# categories: +# - Marketing +# - Design +# built_by: Corey Ward +# built_by_url: http://www.coreyward.me/ +# - title: Jonas Tebbe Portfolio +# description: > +# Hey, I’m Jonas and I create digital products. +# main_url: https://jonastebbe.com +# url: https://jonastebbe.com +# categories: +# - Portfolio +# built_by: Jonas Tebbe +# built_by_url: http://twitter.com/jonastebbe +# featured: false +# - title: Parker Sarsfield Portfolio +# description: > +# I'm Parker, a software engineer and sneakerhead. +# main_url: https://parkersarsfield.com +# url: https://parkersarsfield.com +# categories: +# - Blog +# - Portfolio +# built_by: Parker Sarsfield +# built_by_url: https://parkersarsfield.com +# - title: Front-end web development with Greg +# description: > +# JavaScript, GatsbyJS, ReactJS, CSS in JS... Let's learn some stuff together. +# main_url: https://dev.greglobinski.com +# url: https://dev.greglobinski.com +# categories: +# - Blog +# - Web Dev +# built_by: Greg Lobinski +# built_by_url: https://github.com/greglobinski +# - title: Insomnia +# description: > +# Desktop HTTP and GraphQL client for developers +# main_url: https://insomnia.rest/ +# url: https://insomnia.rest/ +# categories: +# - Landing +# - Blog +# built_by: Gregory Schier +# built_by_url: https://schier.co +# featured: false +# - title: Timeline Theme Portfolio +# description: > +# I'm Aman Mittal, a software developer. +# main_url: http://www.amanhimself.me/ +# url: http://www.amanhimself.me/ +# categories: +# - Landing +# - Web Dev +# - Portfolio +# built_by: Aman Mittal +# built_by_url: http://www.amanhimself.me/ +# - title: Ocean artUp +# description: > +# Science outreach site built using styled-components and Contentful. It presents the research project "Ocean artUp" funded by an Advanced Grant of the European Research Council to explore the possible benefits of artificial uplift of nutrient-rich deep water to the ocean’s sunlit surface layer. +# main_url: https://ocean-artup.eu +# url: https://ocean-artup.eu +# source_url: https://github.com/JanoshRiebesell/ocean-artup +# categories: +# - Education +# - Blog +# - Science +# built_by: Janosh Riebesell +# built_by_url: https://janosh.io +# featured: false +# - title: Ryan Fitzgerald +# description: > +# Personal portfolio and blog for Ryan Fitzgerald +# main_url: https://ryanfitzgerald.ca/ +# url: https://ryanfitzgerald.ca/ +# categories: +# - Web Dev +# - Portfolio +# built_by: Ryan Fitzgerald +# built_by_url: https://github.com/RyanFitzgerald +# featured: false +# - title: Kaizen +# description: > +# Content Marketing, PR & SEO Agency in London +# main_url: https://www.kaizen.co.uk/ +# url: https://www.kaizen.co.uk/ +# categories: +# - Agency +# - Blog +# - Creative +# - Design +# - Web Dev +# - SEO +# built_by: Bogdan Stanciu +# built_by_url: https://github.com/b0gd4n +# featured: false +# - title: HackerOne Platform Documentation +# description: > +# HackerOne's Product Documentation Center! +# url: https://docs.hackerone.com/ +# main_url: https://docs.hackerone.com/ +# categories: +# - Documentation +# - Security +# featured: false +# - title: Patreon Partners +# description: > +# Resources and products to help you do more with Patreon. +# url: https://partners.patreon.com/ +# main_url: https://partners.patreon.com/ +# categories: +# - Resources +# - Directory +# featured: false +# - title: Bureau Of Meteorology (beta) +# description: > +# Help shape the future of Bureau services +# url: https://beta.bom.gov.au/ +# main_url: https://beta.bom.gov.au/ +# categories: +# - Meteorology +# - Research +# - Services +# featured: false +# - title: Curbside +# description: > +# Connecting Stores with Mobile Customers +# main_url: https://curbside.com/ +# url: https://curbside.com/ +# categories: +# - Mobile Commerce +# - Software +# featured: false +# - title: Mux Video +# description: > +# API to video hosting and streaming +# main_url: https://mux.com/ +# url: https://mux.com/ +# categories: +# - Video +# - Hosting +# - Streaming +# - API +# featured: false +# - title: Swapcard +# description: > +# The easiest way for event organizers to instantly connect people, build a community of attendees and exhibitors, and increase revenue over time +# main_url: https://www.swapcard.com/ +# url: https://www.swapcard.com/ +# categories: +# - Event +# - Community +# - Services +# featured: false +# - title: Kalix +# description: > +# Kalix is perfect for healthcare professionals starting out in private practice, to those with an established clinic. +# main_url: https://www.kalixhealth.com/ +# url: https://www.kalixhealth.com/ +# categories: +# - Scheduling +# - Documentation +# - Messaging +# - Billing +# - Services +# featured: false +# - title: Hubba +# description: > +# Buy wholesale products from thousands of independent, verified Brands. +# main_url: https://join.hubba.com/ +# url: https://join.hubba.com/ +# categories: +# - eCommerce +# - Retail +# featured: false +# - title: Airbnb Engineering & Data Science +# description: > +# Creative engineers and data scientists building a world where you can belong anywhere +# main_url: https://airbnb.io/ +# url: https://airbnb.io/ +# categories: +# - Blog +# - Gallery +# - Projects +# featured: false +# - title: HyperPlay +# description: > +# In Asean's 1st Ever LOL Esports X Music Festival +# main_url: https://hyperplay.leagueoflegends.com/ +# url: https://hyperplay.leagueoflegends.com/ +# categories: +# - Landing +# featured: false +# - title: Bad Credit Loans +# description: > +# Get the funds you need, from $250-$5,000 +# main_url: https://www.creditloan.com/ +# url: https://www.creditloan.com/ +# categories: +# - Loans +# - Credits +# featured: false +# - title: Financial Center +# description: > +# Member-owned, not-for-profit, co-operative whose members receive financial benefits in the form of lower loan rates, higher savings rates, and lower fees than banks. +# main_url: https://fcfcu.com/ +# url: https://fcfcu.com/ +# categories: +# - Loans +# - Credits +# - Online Banking +# - Nonprofit +# featured: false +# - title: Open FDA +# description: > +# Provides APIs and raw download access to a number of high-value, high priority and scalable structured datasets, including adverse events, drug product labeling, and recall enforcement reports. +# main_url: https://open.fda.gov/ +# url: https://open.fda.gov/ +# categories: +# - APIs +# - Datasets +# - Reports +# featured: false +# - title: Office of Institutional Research and Assessment +# description: > +# Good Data, Good Decisions +# main_url: http://oira.ua.edu/ +# url: http://oira.ua.edu/ +# categories: +# - Research +# - Data +# featured: false +# - title: GM Capital One +# description: > +# Introducing the new online experience for your GM Rewards Credit Card +# main_url: https://gm.capitalone.com/ +# url: https://gm.capitalone.com/ +# categories: +# - Rewards +# - Credit Card +# featured: false +# - title: House Manager +# description: > +# Home service membership that offers proactive and on-demand maintenance for homeowners +# main_url: https://housemanager.calstate.aaa.com/ +# url: https://housemanager.calstate.aaa.com/ +# categories: +# - Home +# - Services +# featured: false +# - title: Trintellix +# description: > +# It may help make a difference for your depression (MDD). +# main_url: https://us.trintellix.com/ +# url: https://us.trintellix.com/ +# categories: +# - Health +# - Help +# featured: false +# - title: The Telegraph Premium +# description: > +# Exclusive stories from award-winning journalists +# main_url: https://premium.telegraph.co.uk/ +# url: https://premium.telegraph.co.uk/ +# categories: +# - Landing +# - Newspaper +# - Subscription +# featured: false +# - title: html2canvas +# description: > +# Screenshots with JavaScript +# main_url: http://html2canvas.hertzen.com/ +# url: http://html2canvas.hertzen.com/ +# source_url: https://github.com/niklasvh/html2canvas/tree/master/www +# categories: +# - Tool +# - Screenshots +# - JavaScript +# - Documentation +# built_by: Niklas von Hertzen +# built_by_url: http://hertzen.com/ +# featured: false +# - title: The State of JavaScript 2017 +# description: > +# Data from over 20,000 developers, asking them questions on topics ranging from front-end frameworks and state management, to build tools and testing libraries. +# main_url: https://stateofjs.com/ +# url: https://stateofjs.com/ +# source_url: https://github.com/StateOfJS/StateOfJS +# categories: +# - Data +# - JavaScript +# - Statistics +# built_by: StateOfJS +# built_by_url: https://github.com/StateOfJS/StateOfJS/graphs/contributors +# featured: false +# - title: Dato CMS +# description: > +# The API-based CMS your editors will love +# main_url: https://www.datocms.com/ +# url: https://www.datocms.com/ +# categories: +# - CMS +# - API +# - Tool +# featured: false +# - title: Half Electronics +# description: > +# Personal website +# main_url: https://www.halfelectronic.com/ +# url: https://www.halfelectronic.com/ +# categories: +# - Blog +# - Electronics +# built_by: Fernando Poumian +# built_by_url: https://github.com/fpoumian/halfelectronic.com +# featured: false +# - title: Frithir Software Development +# main_url: 'https://frithir.com/' +# url: 'https://frithir.com/' +# featured: false +# description: >- +# I DRINK COFFEE, WRITE CODE AND IMPROVE MY DEVELOPMENT SKILLS EVERY DAY. +# categories: +# - Creative +# - Design +# - Web Dev +# built_by: Frithir +# built_by_url: 'https://Frithir.com/' +# - title: Unow +# main_url: https://www.unow.fr/ +# url: https://www.unow.fr/ +# categories: +# - Education +# - Corporate +# - Marketing +# featured: false +# - title: Peter Hironaka +# description: > +# Freelance Web Developer based in Los Angeles. +# main_url: https://peterhironaka.com/ +# url: https://peterhironaka.com/ +# categories: +# - Portfolio +# - Web Dev +# built_by: Peter Hironaka +# built_by_url: https://github.com/PHironaka +# featured: false +# - title: Michael McQuade +# description: > +# Personal website and blog for Michael McQuade +# main_url: https://giraffesyo.io +# url: https://giraffesyo.io +# categories: +# - Blog +# built_by: Michael McQuade +# built_by_url: https://github.com/giraffesyo +# featured: false +# - title: Haacht Brewery +# description: > +# Corporate wesbite for Haacht Brewery. Designed and Developed by Gafas. +# main_url: https://haacht.com/en/ +# url: https://haacht.com +# categories: +# - Corporate +# built_by: Gafas +# built_by_url: https://gafas.be +# featured: false +# - title: StoutLabs +# description: > +# Portfolio of Daniel Stout, freelance web dev in East Tennessee. +# main_url: https://www.stoutlabs.com/ +# url: https://www.stoutlabs.com/ +# categories: +# - Web Dev +# - Portfolio +# built_by: Daniel Stout +# built_by_url: https://github.com/stoutlabs +# featured: false +# - title: Chicago Ticket Outcomes By Neighborhood +# description: > +# ProPublica data visualization of traffic ticket court outcomes +# categories: +# - News +# - Nonprofit +# - Visualization +# url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-status/?initialWidth=782 +# built_by: David Eads +# built_by_url: https://github.com/eads +# featured: false +# - title: Chicago South Side Traffic Ticketing rates +# description: > +# ProPublica data visualization of traffic ticket rates by community +# url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-rate/?initialWidth=782 +# categories: +# - News +# - Nonprofit +# - Visualization +# built_by: David Eads +# built_by_url: https://github.com/eads +# featured: false +# - title: Otsimo +# description: > +# Otsimo is a special education application for children with autism, down syndrome and other developmental disabilities. +# main_url: https://otsimo.com/en/ +# url: https://otsimo.com/en/ +# categories: +# - Landing +# - Blog +# - Education +# featured: false +# - title: Matt Bagni Portfolio 2018 +# description: > +# Mostly the result of playing with Gatsby and learning about react and graphql. Using the screenshot plugin to showcase the work done for my company in the last 2 years, and a good amount of other experiments. +# main_url: https://mattbag.github.io +# url: https://mattbag.github.io +# categories: +# - Landing +# - Portfolio +# featured: false +# - title: Lifestone Church +# main_url: 'https://www.lifestonechurch.net/' +# url: 'https://www.lifestonechurch.net/' +# source_url: 'https://github.com/lifestonechurch/lifestonechurch.net' +# featured: false +# categories: +# - Marketing +# - Nonprofit +# - title: Artem Sapegin +# description: > +# Little homepage of Artem Sapegin, a frontend developer, passionate photographer, coffee drinker and crazy dogs’ owner. +# main_url: https://sapegin.me/ +# url: https://sapegin.me/ +# categories: +# - Landing +# - Portfolio +# - Open Source +# - Personal +# - Web Dev +# built_by: Artem Sapegin +# built_by_url: https://github.com/sapegin +# featured: false +# - title: SparkPost Developers +# main_url: 'https://developers.sparkpost.com/' +# url: 'https://developers.sparkpost.com/' +# source_url: 'https://github.com/SparkPost/developers.sparkpost.com' +# categories: +# - Documentation +# - API +# featured: false +# - title: Malik Browne Portfolio 2018 +# description: > +# The portfolio blog of Malik Browne, a full stack engineer, foodie, and avid blogger/YouTuber. +# main_url: https://www.malikbrowne.com/about +# url: https://www.malikbrowne.com +# categories: +# - Blog +# - Portfolio +# built_by: Malik Browne +# built_by_url: https://twitter.com/milkstarz +# featured: false +# - title: Novatics +# description: > +# Digital products that inspire and make a difference +# main_url: https://www.novatics.com.br +# url: https://www.novatics.com.br +# categories: +# - Landing +# - Portfolio +# - Technology +# - Digital Products +# - Web Dev +# built_by: Novatics +# built_by_url: https://github.com/Novatics +# featured: false +# - title: I migliori by Vivigratis +# description: > +# Product review website +# main_url: https://imigliori.vivigratis.com/ +# url: https://imigliori.vivigratis.com/ +# categories: +# - Blog +# - Travel +# - Technology +# - Wellness +# - Fashion +# built_by: Kframe Interactive SA +# featured: false +# - title: Max McKinney +# description: > +# I’m a developer and designer with a focus in web technologies. I build cars on the side. +# main_url: https://maxmckinney.com/ +# url: https://maxmckinney.com/ +# categories: +# - Portfolio +# - Web Dev +# - Design +# - Landing +# - Personal +# built_by: Max McKinney +# featured: false +# - title: Stickyard +# description: > +# Make your React component sticky the easy way +# main_url: https://nihgwu.github.io/stickyard/ +# url: https://nihgwu.github.io/stickyard/ +# source_url: https://github.com/nihgwu/stickyard/tree/master/website +# categories: +# - Web Dev +# built_by: Neo Nie +# featured: false +# - title: Agata Milik +# description: > +# Website of a Polish psychologist/psychotherapist based in Gdańsk, Poland. +# main_url: https://agatamilik.pl +# url: https://agatamilik.pl +# categories: +# - Landing +# - Marketing +# - Healthcare +# built_by: Piotr Fedorczyk +# built_by_url: https://piotrf.pl +# featured: false +# - title: WebPurple +# main_url: 'https://www.webpurple.net/' +# url: 'https://www.webpurple.net/' +# source_url: 'https://github.com/WebPurple/site' +# description: >- +# Site of local (Russia, Ryazan) frontend community. Main purpose is to show info about meetups and keep blog. +# categories: +# - Nonprofit +# - Web Dev +# - Community +# - Blog +# - Open Source +# built_by: Nikita Kirsanov +# built_by_url: 'https://twitter.com/kitos_kirsanov' +# featured: false +# - title: Papertrail.io +# description: > +# Inspection Management for the 21st Century +# main_url: https://www.papertrail.io/ +# url: https://www.papertrail.io/ +# categories: +# - Marketing +# - Technology +# - Landing +# built_by: Papertrail.io +# built_by_url: https://www.papertrail.io +# featured: false +# - title: Matt Ferderer +# main_url: https://mattferderer.com +# url: https://mattferderer.com +# source_url: https://github.com/mattferderer/gatsbyblog +# description: > +# {titleofthesite} is a blog built with Gatsby that discusses web related tech such as JavaScript, .NET, Blazor & security. +# categories: +# - Blog +# - Web Dev +# - Personal +# built_by: Matt Ferderer +# built_by_url: https://twitter.com/mattferderer +# featured: false +# - title: Sahyadri Open Source Community +# main_url: https://sosc.org.in +# url: https://sosc.org.in +# source_url: https://github.com/haxzie/sosc-website +# description: > +# Official website of Sahyadri Open Source Community for community blog, event details and members info. +# categories: +# - Blog +# - Community +# - Open Source +# built_by: Musthaq Ahamad +# built_by_url: https://github.com/haxzie +# featured: false +# - title: Tech Confessions +# main_url: https://confessions.tech +# url: https://confessions.tech +# source_url: https://github.com/JonathanSpeek/tech-confessions +# description: > +# A guilt-free place for us to confess our tech sins 🙏 +# categories: +# - Community +# - Open Source +# built_by: Jonathan Speek +# built_by_url: https://speek.design +# featured: false +# - title: Thibault Maekelbergh +# main_url: https://thibmaek.com +# url: https://thibmaek.com +# source_url: https://github.com/thibmaek/thibmaek.github.io +# description: > +# A nice blog about development, Raspberry Pi, plants and probably records. +# categories: +# - Blog +# - Open Source +# built_by: Thibault Maekelbergh +# built_by_url: https://twitter.com/thibmaek +# featured: false +# - title: LearnReact.design +# main_url: https://learnreact.design +# url: https://learnreact.design +# description: > +# React Essentials For Designers: A React course tailored for product designers, ux designers, ui designers. +# categories: +# - Blog +# - Landing +# - Creative +# built_by: Linton Ye +# built_by_url: https://twitter.com/lintonye +# - title: DesignSystems.com +# main_url: https://www.designsystems.com/ +# url: https://www.designsystems.com/ +# description: > +# A resource for learning, creating and evangelizing design systems. +# categories: +# - Design +# - Blog +# - Technology +# built_by: Corey Ward +# built_by_url: http://www.coreyward.me/ +# featured: false +# - title: Devol’s Dance +# main_url: https://www.devolsdance.com/ +# url: https://www.devolsdance.com/ +# description: > +# Devol’s Dance is an invite-only, one-day event celebrating industrial robotics, AI, and automation. +# categories: +# - Marketing +# - Landing +# - Technology +# built_by: Corey Ward +# built_by_url: http://www.coreyward.me/ +# featured: false +# - title: Mega House Creative +# main_url: https://www.megahousecreative.com/ +# url: https://www.megahousecreative.com/ +# description: > +# Mega House Creative is a digital agency that provides unique goal-oriented web marketing solutions. +# categories: +# - Marketing +# - Agency +# built_by: Daniel Robinson +# featured: false +# - title: Tobie Marier Robitaille - csc +# main_url: https://tobiemarierrobitaille.com/ +# url: https://tobiemarierrobitaille.com/en/ +# description: > +# Portfolio site for director of photography Tobie Marier Robitaille +# categories: +# - Portfolio +# - Gallery +# built_by: Mill3 Studio +# built_by_url: https://mill3.studio/en/ +# featured: false +# - title: Bestvideogame.deals +# main_url: https://bestvideogame.deals/ +# url: https://bestvideogame.deals/ +# description: > +# Video game comparison website for the UK, build with GatsbyJS. +# categories: +# - eCommerce +# - Video Games +# built_by: Koen Kamphuis +# built_by_url: https://koenkamphuis.com/ +# featured: false +# - title: Mahipat's Portfolio +# main_url: https://mojaave.com/ +# url: https://mojaave.com +# source_url: https://github.com/mhjadav/mojaave +# description: > +# mojaave.com is Mahipat's portfolio, I have developed it using Gatsby v2 and Bootstrap, To get in touch with people looking for full stack developer. +# categories: +# - Portfolio +# - Software +# - Web Dev +# - Personal +# built_by: Mahipat Jadav +# built_by_url: https://mojaave.com/ +# featured: false +# - title: Cmsbased +# main_url: https://www.cmsbased.net +# url: https://www.cmsbased.net +# description: > +# Cmsbased is providing automation tools and design resources for Web Hosting and IT services industry. +# categories: +# - Technology +# - Design +# - Digital Products +# featured: false +# - title: Traffic Design Biennale 2018 +# main_url: https://trafficdesign.pl +# url: http://trafficdesign.pl/pl/ficzery/2018-biennale/ +# description: > +# Mini site for 2018 edition of Traffic Design’s Biennale +# categories: +# - Landing +# - Creative +# - Nonprofit +# - Gallery +# built_by: Piotr Fedorczyk +# built_by_url: https://piotrf.pl +# featured: false + diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 7a3635fc840b4..2e462dd37f93d 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -267,7 +267,7 @@ link: /docs/schema-input-gql - title: Querying with Sift link: /docs/schema-sift - - title: Connections* + - title: Connections link: /docs/schema-connections - title: Page Creation link: /docs/page-creation/ From 0e0cf5172bb69a624256a9ec0eac2c1f27534843 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 30 Aug 2018 12:29:43 +1000 Subject: [PATCH 29/39] more linking --- docs/docs/schema-connections.md | 2 +- docs/docs/schema-sift.md | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md index 433386ca22e1c..2d68c07a007bd 100644 --- a/docs/docs/schema-connections.md +++ b/docs/docs/schema-connections.md @@ -80,7 +80,7 @@ Note, the same enum mechanism is used for creation of `distinct` fields The resolver for the Group type is created in [build-connection-fields.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L57). It operates on the result of the core connection query (e.g `allMarkdownRemark`), which is a `Connection` object with edges. From these edges, we retrieve all the nodes (each edge has a `node` field). And now we can use lodash to group those nodes by the fieldname argument (e.g `field: frontmatter___author`). -If sorting was specified ([see below](#sorting)), we sort the groups by fieldname, and then apply any `skip/limit` arguments using the [graph-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library. Finally we are ready to fill in our `field`, `fieldValue`, and `totalCount` fields on each group, and we can return our resolved node. +If sorting was specified ([see below](#sorting)), we sort the groups by fieldname, and then apply any `skip/limit` arguments using the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library. Finally we are ready to fill in our `field`, `fieldValue`, and `totalCount` fields on each group, and we can return our resolved node. ### Input filter creation diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 56d2b224c32f2..3d613a30d812e 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -102,17 +102,11 @@ The resolve method in this case would return a paragraph node, which also needs After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. -### 4. Track newly inlined fields - -TODO: I think what's going on here is that nodes when created, have explicit objects on them already. And there must be a step that links all these sub objects to the root node. But, the fields that are realized in the above step haven't been created before. So we need to track them explicitly. Feels a bit cray cray, but legit I guess. - -TODO: Create proper node tracking doc in other .md - ### 5. Run sift query on all nodes Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the sift query to all those nodes. This step is handled by [tempPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L214). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. -In the case that `connection === true` (argument passed to run-sift), then instead of just choosing the first argument, we will select ALL nodes that match the sift query. If the GraphQL query specified `sort`, `skip`, or `limit` fields, then we use the [graphql-skip-limit](TODO) library to filter down to the appropriate results. See [schema-connections](TODO) for more info. +In the case that `connection === true` (argument passed to run-sift), then instead of just choosing the first argument, we will select ALL nodes that match the sift query. If the GraphQL query specified `sort`, `skip`, or `limit` fields, then we use the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library to filter down to the appropriate results. See [schema-connections](/docs/schema-connections) for more info. ### 6. Create Page dependency if required From 8b13cd9014f94e5abe7a112d79639b3b42a8dcd8 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 30 Aug 2018 12:31:09 +1000 Subject: [PATCH 30/39] typo --- docs/docs/schema-connections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md index 2d68c07a007bd..d5dde2fa40cd2 100644 --- a/docs/docs/schema-connections.md +++ b/docs/docs/schema-connections.md @@ -95,4 +95,4 @@ The Sort Input Type itself is created in [build-node-connections.js](https://git ### Connection Resolver (sift) Finally, we're ready to define the resolver for our Connection type (in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L65)). This is where we come up with the name `all${type}` (e.g `allMarkdownRemark`) that is so common in Gatsby queries. The resolver is fairly simple. It uses the [sift.js](https://www.npmjs.com/package/sift) library to query across all nodes of the same type in redux. The big difference is that we supply the `connection: true` parameter to `run-sift.js` which is where sorting, and pagination is actually executed. See [Querying with Sift](/docs/schema-sift) for how this actually works. -runs + From ba666a2e10e776f172ef4e4d285943b2007f708e Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 31 Aug 2018 15:05:57 +1000 Subject: [PATCH 31/39] Docs for query behind the scenes --- .../docs/behind-the-scenes-query-execution.md | 76 +++++++++++++++++ .../behind-the-scenes-query-extraction.md | 57 +++++++++++++ ...ind-the-scenes-static-vs-normal-queries.md | 41 +++++++++ docs/docs/behind-the-scenes-terminology.md | 83 +++++++++++++++++++ docs/docs/behind-the-scenes.md | 8 +- docs/docs/query-behind-the-scenes.md | 17 ++++ .../schema-generation-behind-the-scenes.md | 2 +- www/src/data/sidebars/doc-links.yaml | 23 +++-- 8 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 docs/docs/behind-the-scenes-query-execution.md create mode 100644 docs/docs/behind-the-scenes-query-extraction.md create mode 100644 docs/docs/behind-the-scenes-static-vs-normal-queries.md create mode 100644 docs/docs/behind-the-scenes-terminology.md create mode 100644 docs/docs/query-behind-the-scenes.md diff --git a/docs/docs/behind-the-scenes-query-execution.md b/docs/docs/behind-the-scenes-query-execution.md new file mode 100644 index 0000000000000..ed1bf296e0557 --- /dev/null +++ b/docs/docs/behind-the-scenes-query-execution.md @@ -0,0 +1,76 @@ +--- +title: Query Execution +--- + +### Query Execution + +Query Execution is kicked off by bootstrap by calling [page-query-runner.js runInitialQuerys()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L29). The main files involved in this step are: + +- [page-query-runner.js](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) +- [query-queue.js](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) +- [query-runner.js](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js) + +#### Figuring out which queries need to be executed + +The first thing this query does is figure out what queries even need to be run. You would think this would simply be a matter of running the Queries that were enqueued in [Extract Queries](/docs/behind-the-scenes-query-extraction/), but matters are complicated by support for `gatsby develop`. Below is the logic for figuring out which queries need to be executed (code is in [runQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L36)). + +##### Already queued queries + +All queries queued after being extracted. + +##### Queries without node dependencies + +All queries whose component path isn't listed in `componentDataDependencies`. As a recap, in [Schema Generation](/docs/schema-generation-behind-the-scenes/), we showed that all Type resolvers record a dependency between the page whose query we're running and any nodes that were successfully resolved. So, If a component is declared in the `components` redux namespace, but is *not* contained in `componentDataDependencies`, then by definition, the query has not been run. Therefore we need to run it. Checkout [Node/Page Dependencies](http://localhost:8000/docs/behind-the-scenes-dependencies/) for more info. The code for this step is in [findIdsWithoutDataDependencies](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L89). + +##### Pages that depend on dirty nodes + +In `gatsby develop` mode, every time a node is created, or is updated (e.g via editing a markdown file), we add that node to the [enqueuedDirtyActions](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L61) collection. When we execute our queries, we can lookup all nodes in this collection and map them to pages that depend on them (as described above). These pages' queries must also be executed. In addition, this step also handles dirty `connections` (see [Schema Connections](/docs/schema-connections/)). Connections depend on a node's type. So if a node is dirty, we mark all connection nodes of that type dirty as well. The code for this step is in [findDirtyIds](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L171). _Note: dirty ids is really talking about dirty paths_. + +#### Queue Queries for Execution + +We now have the list of all pages that need to be executed (linked to their Query information). Let's queue them for execution (for realz this time). A call to [runQueriesForPathnames](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L21) kicks off this step. For each page or static query, we create a Query Job that looks something like: + +```javascript +{ + id: // page path, or static query hash + hash: // only for static queries + jsonName: // jsonName of static query or page + query: // raw query text + componentPath: // path to file where query is declared + isPage: // true if not static query + context: { + path: // if staticQuery, is jsonName of component + ...page // page object. Not for static queries + ...page.context // not for static queries + } +} +``` + +This Query Job contains everything we need to execute the query (and do things like recording dependencies between pages and nodes). So, we push it onto the queue in [query-queue.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) and then wait for the queue to empty. Let's see how `query-queue` works. + +#### Query Queue Execution + +[query-queue.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) creates a [better-queue](https://www.npmjs.com/package/better-queue) queue that offers advanced features parallel execution, which is handy since querys do not depend on each other so we can take advantage of this. Every time an item is consumed from the queue, we call [query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js) where we finally actually execute the query! + +Query execution involves calling the [graphql-js](https://graphql.org/graphql-js/) library with 3 pieces of information: + +1. The Gatsby schema that was inferred during [Schema Generation](/docs/schema-generation-behind-the-scenes/). +1. The raw query text. Obtained from the Query Job +1. The Context, also from the Query Job. Has the page's `path` amongst other things. + +Graphql-js will parse the query, and execute the top level query. E.g `allMarkdownRemark( limit: 10 )` or `file( relativePath: { eq: "blog/my-blog.md" } )`. These will invoke the resolvers defined in [Schema Connections](/docs/schema-connections/) or [GQL Type](/docs/schema-gql-type/), which both use sift to query over all nodes of the type in redux. The result will be passed through the inner part of the graphql query where each type's resolver will be invoked. The vast majority of these will be `identity` functions that just return the field value. Some however could call a [custom plugin field](/docs/schema-gql-type/#plugin-fields) resolver. These in turn might perform side effects such as generating images. This is why the query execution phase of bootstrap often takes the longest. + +Finally, a result is returned. + +#### Save Query results to redux and disk + +As queries are consumed from the queue and executed, their results are saved to redux and disk for consumption later on. This involves converting the result to pure JSON, and then saving it to its [dataPath](/docs/behind-the-scenes-terminology/#datapath). Which is relative to `public/static/d`. The data path includes the jsonName and hash. E.g: for the page `/blog/2018-07-17-announcing-gatsby-preview/`, the queries results would be saved to disk as something like: + +``` +/public/static/d/621/path---blog-2018-07-17-announcing-gatsby-preview-995-a74-dwfQIanOJGe2gi27a9CLKHjamc.json +``` + +For static queries, instead of using the page's jsonName, we just use a hash of the query. + +Now we need to store the association of the page -> the query result in redux so we can recall it later. This is accomplished via the [json-data-paths](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/json-data-paths.js) reducer which we invoke by creating a `SET_JSON_DATA_PATH` action with the page's jsonName and the saved dataPath. + diff --git a/docs/docs/behind-the-scenes-query-extraction.md b/docs/docs/behind-the-scenes-query-extraction.md new file mode 100644 index 0000000000000..60c997298cbba --- /dev/null +++ b/docs/docs/behind-the-scenes-query-extraction.md @@ -0,0 +1,57 @@ +--- +title: Query Extraction +--- + +### Extracting Queries from Files + +Up until now, we have [sourced all nodes](/docs/node-creation-behind-the-scenes/) into redux, [inferred a schema](/docs/schema-generation-behind-the-scenes/) from them, and [created all pages](/docs/page-creation/). The next step is to extract and compile all graphql queries from our source files. The entrypoint to this phase is [query-watcher extractQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js), which immediately compiles all graphql queries by calling into [query-compiler.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js). + +#### Query Compilation + +The first thing it does is use [babylon-traverse](https://babeljs.io/docs/en/next/babel-traverse.html) to load all javascript files in the site that have graphql queries in them. This produces AST results that are passed to the [relay-compiler](https://facebook.github.io/relay/docs/en/compiler-architecture.html). This accomplishes a couple of things: + +1. It informs us of any malformed queries, which are promptly reported back to the user. +1. It builds a tree of queries and fragments they depend on. And outputs a single optimized query string with the fragments. + +After this step, we will have a map of file paths (of site files with queries in them) to Query Objects, which contain the raw optimized query text, as well as other metadata such as the component path and page `jsonName`. The following diagram shows the flow involved during query compilation + +```dot +digraph { + fragments [ label = "fragments. e.g\l.cache/fragments/fragment1.js", shape = cylinder ]; + srcFiles [ label = "source files. e.g\lsrc/pages/my-page.js", shape = cylinder ]; + components [ label = "redux.state.components\l(via createPage)", shape = cylinder ]; + fileQueries [ label = "files with queries", shape = box ]; + babylon [ label = "parse files with babylon\lfilter those with queries" ]; + queryAst [ label = "QueryASTs", shape = box ]; + schema [ label = "Gatsby schema", shape = cylinder ]; + relayCompiler [ label = "Relay Compiler" ]; + queries [ label = "{ Queries | { filePath | query } }", shape = record ]; + query [ label = "{\l name: filePath,\l text: rawQueryText,\l originalText: original text from file,\l path: filePath,\l isStaticQuery: if it is,\l hash: hash of query\l}\l ", shape = box ]; + + + fragments -> fileQueries; + srcFiles -> fileQueries; + components -> fileQueries; + fileQueries -> babylon; + babylon -> queryAst; + queryAst -> relayCompiler; + schema -> relayCompiler; + relayCompiler -> queries; + queries:query -> query; +} +``` + +#### Store Queries in Redux + +We're now in the [handleQuery](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js#L68) function. + +If the query is a `StaticQuery`, we call the `replaceStaticQuery` action to save it to to the `staticQueryComponents` namespace which is a mapping from a component's path to an object that contains the raw GraphQL Query amonst other things. More details in [Static Queries](/docs/behind-the-scenes-static-vs-normal-queries/). We also remove component's `jsonName` from the `components` redux namespace. See [Component/Page dependencies](/docs/behind-the-scenes-dependencies/). + +If the query is just a normal every day query (not StaticQuery), then we update its component's `query` in the redux `components` namespace via the `replaceComponentQuery` action. + +#### Queue for execution + +Now that we've saved our query, we're ready to queue it for execution. Query execution is mainly handled by [page-query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js), so we accomplish this by passing the component's path to `queueQueryForPathname` function. + +Now let's learn about [Query Execution](/docs/behind-the-scenes-query-execution/). + diff --git a/docs/docs/behind-the-scenes-static-vs-normal-queries.md b/docs/docs/behind-the-scenes-static-vs-normal-queries.md new file mode 100644 index 0000000000000..bfeedce8c6f7f --- /dev/null +++ b/docs/docs/behind-the-scenes-static-vs-normal-queries.md @@ -0,0 +1,41 @@ +--- +title: Static vs Normal Queries +--- + +## TODO Difference between normal and Static Queries + +Static Queries don't need to get run for each page. Just once + +### staticQueryComponents + +Started here because they're referenced in page-query-runner:findIdsWithDataDependencies. + +The redux `staticQueryComponents` is a map fronm component jsonName to StaticQueryObject. E.g + +```javascript +{ + `blog-2018-07-17-announcing-gatsby-preview-995` : { + name: `/path/to/component/file`, + componentPath: `/path/to/component/file`, + id: `blog-2018-07-17-announcing-gatsby-preview-995`, + jsonName: `blog-2018-07-17-announcing-gatsby-preview-995`, + query: `raw GraphQL Query text including fragments`, + hash: `hash of graphql text` + } +} +``` + +The `staticQueryComponents` redux namespace is owned by the `static-query-components.js` reducer with reacts to `REPLACE_STATIC_QUERY` actinos. + +It is created in query-watcher. TODO: Check other usages + +TODO: in query-watcher.js/handleQuery, we remove jsonName from dataDependencies. How did it get there? Why is jsonName used here, but for other dependencies, it's a path? + +### Usages + +- [websocket-manager](TODO). TODO +- [query-watcher](TODO). + - `getQueriesSnapshot` returns map with snapshot of `state.staticQueryComponents` + - handleComponentsWithRemovedQueries. For each staticQueryComponent, if passed in queries doesn't include `staticQueryComponent.componentPath`. TODO: Where is StaticQueryComponent created? TODO: Where is queries passed into `handleComponentsWithRemovedQueries`? + + TODO: Finish above diff --git a/docs/docs/behind-the-scenes-terminology.md b/docs/docs/behind-the-scenes-terminology.md new file mode 100644 index 0000000000000..7802cb4fa4050 --- /dev/null +++ b/docs/docs/behind-the-scenes-terminology.md @@ -0,0 +1,83 @@ +## Terminology + +Read up on [page creation](/docs/page-creation/) first. + +### dataPath + +Path to the page's query result. Relative to `/public/static/d/{modInt}`. Name is kebab hash on `path--${jsonName}`-`result->sha1->base64`. E.g + +`621/path---blog-2018-07-17-announcing-gatsby-preview-995-a74-dwfQIanOJGe2gi27a9CLKHjamc` + +### Redux `jsonDataPaths` namespace (dataPaths) + +Map of page `jsonName` to `dataPath`. Updated whenever a new query is run (in [query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js)). e.g + +``` +{ + // jsonName -> dataPath + "blog-2018-07-17-announcing-gatsby-preview-995": "621/path---blog-2018-07-17-announcing-gatsby-preview-995-a74-dwfQIanOJGe2gi27a9CLKHjamc" +} +``` + +This is also known via the `dataPaths` variable. + +### Query result file + +`/public/static/d/621/${dataPath}` + +E.g + +`/public/static/d/621/path---blog-2018-07-17-announcing-gatsby-preview-995-a74-dwfQIanOJGe2gi27a9CLKHjamc.json` + +This is the actual result of the GraphQL query that was run for the page `/blog/2018-07-17-announcing-gatsby-preview/`. The contents would look something like: + +```javascript +{ + "data": { + "markdownRemark": { + "html": "

Today we....", + "timeToRead": 2, + "fields": { + "slug": "/blog/2018-07-17-announcing-gatsby-preview/" + }, + "frontmatter": { + "title": "Announcing Gatsby Preview", + "date": "July 17th 2018", + ... + }, + ... + } + }, + "pageContext": { + "slug": "/blog/2018-07-17-announcing-gatsby-preview/", + "prev": { + ... + }, + "next": null + } +} +``` + +For a query such as: + +```javascript +export const pageQuery = graphql` + query($slug: String!) { + markdownRemark(fields: { slug: { eq: $slug } }) { + html + timeToRead + fields { + slug + } + frontmatter { + title + date(formatString: "MMMM Do YYYY") + ... + } + ... + } + } +` +``` + +TODO: Consider Creating a standalone terminology page diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md index 188a3e00c24d6..594e4ec167710 100644 --- a/docs/docs/behind-the-scenes.md +++ b/docs/docs/behind-the-scenes.md @@ -2,8 +2,12 @@ title: Behind the Scenes --- -Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works. +Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works, or perhaps you're a plugin author and need to understand how core works to track down a bug? Come one, come all! -If you're looking for information on how to _use_ Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. +If you're looking for information on how to _use_ Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. This section is quite low level. These docs aren't supposed to be definitive, or tell you everything there is to know. But as you're exploring the Gatsby codebase, you might find yourself wondering what a concept means, or which part of the codebase implements a particular idea. These docs aim to answer those kinds of questions. + +A few more things. These docs are mostly focused on `gatsby build`. Operations specific to `gatsby develop` are mostly ignored. Though this may change in the future. Also, they mostly focus on the happy path, rather than getting bogged down in details of error handling. + +Ready? Dive in by exploring how [APIs/Plugins](/docs/how-plugins-apis-are-run/) work. diff --git a/docs/docs/query-behind-the-scenes.md b/docs/docs/query-behind-the-scenes.md new file mode 100644 index 0000000000000..f375024b16d4d --- /dev/null +++ b/docs/docs/query-behind-the-scenes.md @@ -0,0 +1,17 @@ +--- +title: How Queries Work +--- + +## How Queries work in Gatsby + +We're talking about GraphQL queries here. These can be tagged graphql expressions at the bottom of your component source file (e.g [query for Gatsby frontpage](https://github.com/gatsbyjs/gatsby/blob/master/www/src/pages/index.js#L225)), StaticQueries within your components (e.g [showcase site details](https://github.com/gatsbyjs/gatsby/blob/master/www/src/components/showcase-details.js#L103)), or fragments created by plugins (e.g [gatsby-source-contentful](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-contentful/src/fragments.js)). + +Note that we are NOT talking about queries involved in the creation of your pages, which is usually performed in your site's gatsby-node.js (e.g [Gatby's website](https://github.com/gatsbyjs/gatsby/blob/master/www/gatsby-node.js#L85)). We're only talking about queries that are tied to particular pages or templates. + +Almost all logic to do with queries is in the internal-plugin [query-runner](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner). There are two steps involved in a Query's life time. The first is extracting it, and the second is running it. These are separated into two bootstrap phases. + +1. [Query Extraction](/docs/behind-the-scenes-query-extraction/) +2. [Query Execution](/docs/behind-the-scenes-query-execution/) + +TODO: Diagram of query queue etc. + diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 6705ebe26bf02..184b87c1488d4 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -2,7 +2,7 @@ title: Schema Generation --- -Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. We must infer a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how that occurs. +Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. In fact, as of writing, it accounts for a third of the lines of code in core Gatsby. It involves inferring a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how it's done. ### Group all nodes by type diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 37110ceb01a73..859ce38645d44 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -262,23 +262,34 @@ link: /docs/how-plugins-apis-are-run/ - title: Node Creation link: /docs/node-creation-behind-the-scenes/ - - title: Data Storage (Redux)* - link: /docs/data-storage-redux/ - title: Schema Generation link: /docs/schema-generation-behind-the-scenes/ items: - title: Building the GqlType - link: /docs/schema-gql-type + link: /docs/schema-gql-type/ - title: Building the Input Filters - link: /docs/schema-input-gql + link: /docs/schema-input-gql/ - title: Querying with Sift - link: /docs/schema-sift + link: /docs/schema-sift/ - title: Connections - link: /docs/schema-connections + link: /docs/schema-connections/ - title: Page Creation link: /docs/page-creation/ + - title: Node/Page Dependencies + link: /docs/behind-the-scenes-dependencies/ + - title: Queries + link: /docs/query-behind-the-scenes/ + items: + - title: Query Extraction + link: /docs/behind-the-scenes-query-extraction/ + - title: Query Execution + link: /docs/behind-the-scenes-query-execution/ + - title: Data Storage (Redux)* + link: /docs/data-storage-redux/ - title: Build Caching* link: /docs/build-caching/ + - title: Terminology + link: /docs/behind-the-scenes-terminology/ - title: Advanced Tutorials items: - title: Making a site with user authentication* From f09800a4b3fb3c8fe4b062c3e9075d3f8694255e Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Fri, 31 Aug 2018 15:11:22 +1000 Subject: [PATCH 32/39] revert accidental sites changes --- docs/docs/recipes.md | 68 +- docs/sites.yml | 4325 ++++++++++++++++++++++-------------------- 2 files changed, 2261 insertions(+), 2132 deletions(-) diff --git a/docs/docs/recipes.md b/docs/docs/recipes.md index 7ed446fb491f8..d114ff2630a49 100644 --- a/docs/docs/recipes.md +++ b/docs/docs/recipes.md @@ -14,10 +14,9 @@ Links: And yeah — those three things are exactly what we're thinking. A first step would be to just go through the tutorial and pull out all the basic things we teach there in a condensed form e.g. creating a site, creating a page, linking between pages, etc. --> -Craving a happy medium between doing the [full tutorial](/tutorial/) and crawling the [full docs](<(/tutorial/)>)? Here's a quick guiding reference for how to build things, Gatsby style. +Craving a happy medium between doing the [full tutorial](/tutorial/) and crawling the [full docs]((/tutorial/))? Here's a quick guiding reference for how to build things, Gatsby style. ## Table of Contents - - [Using a starter](#using-a-starter) - [Creating pages](#creating-pages) - [Linking between pages](#linking-between-pages) @@ -32,74 +31,73 @@ Craving a happy medium between doing the [full tutorial](/tutorial/) and crawlin Starters are boilerplate Gatsby sites maintained officially, or by the community. -- Learn how to use the Gatsby CLI tool to use starters in [tutorial part one](/tutorial/part-one/#using-gatsby-starters) -- See a list of [official and community starters](/docs/gatsby-starters/) -- Check out Gatsby's [official default starter](https://github.com/gatsbyjs/gatsby-starter-default) +* Learn how to use the Gatsby CLI tool to use starters in [tutorial part one](/tutorial/part-one/#using-gatsby-starters) +* See a list of [official and community starters](/docs/gatsby-starters/) +* Check out Gatsby's [official default starter](https://github.com/gatsbyjs/gatsby-starter-default) ## Creating pages -You can create pages in Gatsby explicitly by definining React components in `src/pages/`, or programmatically by using the `createPages` API. +You can create pages in Gatsby explicitly by definining React components in `src/pages/`, or programmatically by using the `createPages` API. -- Walk through creating a page by defining a React component in `src/pages` in [tutorial part one](/tutorial/part-one/#familiarizing-with-gatsby-pages) -- Walk through programmatically creating pages in [tutorial part seven](/tutorial/part-seven/) -- Check out the docs overview on [creating and modifying pages](/docs/creating-and-modifying-pages/) +* Walk through creating a page by defining a React component in `src/pages` in [tutorial part one](/tutorial/part-one/#familiarizing-with-gatsby-pages) +* Walk through programmatically creating pages in [tutorial part seven](/tutorial/part-seven/) +* Check out the docs overview on [creating and modifying pages](/docs/creating-and-modifying-pages/) ## Linking between pages Routing in Gatsby relies on the `` component, a wrapper around [@reach/router's Link component](https://reach.tech/router/api/Link). -- Walk through using Gatsby's `` component in [tutorial part one](/tutorial/part-one/#linking-between-pages) -- Learn more about how `` works [in the docs](/docs/gatsby-link/) +* Walk through using Gatsby's `` component in [tutorial part one](/tutorial/part-one/#linking-between-pages) +* Learn more about how `` works [in the docs](/docs/gatsby-link/) ## Styling - There are so many ways to add styles to your website; Gatsby supports almost every possible option, through official and community plugins. -- Walk through adding global styles to an example site in [tutorial part two](/tutorial/part-two/#creating-global-styles) - - More on global styles [with standard CSS files](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-with-standard-css-files) - - More on global styles with [CSS-in-JS](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-using-css-in-js) - - More on global styles [with CSS files and no layout component](/docs/creating-global-styles/#add-global-styles-with-css-files-and-no-layout-component) -- Use the CSS-in-JS library [Glamor](/docs/glamor/) -- Use the CSS-in-JS library [Styled Components](/docs/styled-components/) -- Use [CSS Modules](/tutorial/part-two/#css-modules) +* Walk through adding global styles to an example site in [tutorial part two](/tutorial/part-two/#creating-global-styles) + * More on global styles [with standard CSS files](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-with-standard-css-files) + * More on global styles with [CSS-in-JS](/docs/creating-global-styles/#how-to-add-global-styles-in-gatsby-using-css-in-js) + * More on global styles [with CSS files and no layout component](/docs/creating-global-styles/#add-global-styles-with-css-files-and-no-layout-component) +* Use the CSS-in-JS library [Glamor](/docs/glamor/) +* Use the CSS-in-JS library [Styled Components](/docs/styled-components/) +* Use [CSS Modules](/tutorial/part-two/#css-modules) ## Creating layouts To wrap pages with layouts, use normal React components. -- Walk through creating a layout component in [tutorial part three](/tutorial/part-three/#your-first-layout-component) -- Gatsby v1 approached layouts differently. If the context is helpful, learn about the [differences in v2](/blog/2018-06-08-life-after-layouts/) +* Walk through creating a layout component in [tutorial part three](/tutorial/part-three/#your-first-layout-component) +* Gatsby v1 approached layouts differently. If the context is helpful, learn about the [differences in v2](/blog/2018-06-08-life-after-layouts/) ## Deploying Showtime. -- Walk through building and deploying an example site in [tutorial part one](/tutorial/part-one/#deploying-a-gatsby-site) -- Learn how to make sure your site is configured properly to be [searchable, sharable, and properly navigable](/docs/preparing-for-site-launch/) -- Learn about [performance optimization](/docs/performance/) -- Read about [other deployment related topics](/docs/deploying-and-hosting/) +* Walk through building and deploying an example site in [tutorial part one](/tutorial/part-one/#deploying-a-gatsby-site) +* Learn how to make sure your site is configured properly to be [searchable, sharable, and properly navigable](/docs/preparing-for-site-launch/) +* Learn about [performance optimization](/docs/performance/) +* Read about [other deployment related topics](/docs/deploying-and-hosting/) ## Querying data In Gatsby, you access data through a query language called [GraphQL](https://graphql.org/). -- Walk through an example of how Gatsby's data layer [pulls data into components using GraphQL](/tutorial/part-four/#how-gatsbys-data-layer-uses-graphql-to-pull-data-into-components) -- Walk through [using Gatsby's `graphql` tag for page queries](/tutorial/part-five/#build-a-page-with-a-graphql-query) -- Read through a conceptual guide on [querying data with GraphQL in Gatsby](/docs/querying-with-graphql/) -- Learn more about the `graphql` tag -- [querying data in a Gatsby page](/docs/page-query/) -- Learn more about `` -- [querying data in (non-page) components](/docs/static-query/) +* Walk through an example of how Gatsby's data layer [pulls data into components using GraphQL](/tutorial/part-four/#how-gatsbys-data-layer-uses-graphql-to-pull-data-into-components) +* Walk through [using Gatsby's `graphql` tag for page queries](/tutorial/part-five/#build-a-page-with-a-graphql-query) +* Read through a conceptual guide on [querying data with GraphQL in Gatsby](/docs/querying-with-graphql/) +* Learn more about the `graphql` tag -- [querying data in a Gatsby page](/docs/page-query/) +* Learn more about `` -- [querying data in (non-page) components](/docs/static-query/) ## Sourcing data Data sourcing in Gatsby is plugin-driven; Source plugins fetch data from their source (e.g. the `gatsby-source-filesystem` plugin fetches data from the file system, the `gatsby-source-wordpress` plugin fetches data from the WordPress API, etc). -- Walk through an example using the `gatsby-source-filesystem` plugin in [tutorial part five](/tutorial/part-five/#source-plugins) -- Search available source plugins in the [Gatsby library](/plugins/?=source) -- Understand source plugins by building one in the [source plugin tutorial](/docs/source-plugin-tutorial/) +* Walk through an example using the `gatsby-source-filesystem` plugin in [tutorial part five](/tutorial/part-five/#source-plugins) +* Search available source plugins in the [Gatsby library](/plugins/?=source) +* Understand source plugins by building one in the [source plugin tutorial](/docs/source-plugin-tutorial/) ## Transforming data Transforming data in Gatsby is also plugin-driven; Transformer plugins take data fetched using source plugins, and process it into something more usable (e.g. JSON into JavaScript objects, markdown to HTML, and more). -- Walk through an example using the `gatsby-transformer-remark` plugin to transform markdown files [tutorial part six](/tutorial/part-six/#transformer-plugins) -- Search available transformer plugins in the [Gatsby library](/plugins/?=transformer) +* Walk through an example using the `gatsby-transformer-remark` plugin to transform markdown files [tutorial part six](/tutorial/part-six/#transformer-plugins) +* Search available transformer plugins in the [Gatsby library](/plugins/?=transformer) diff --git a/docs/sites.yml b/docs/sites.yml index 89286aefe7b40..9244efe324296 100644 --- a/docs/sites.yml +++ b/docs/sites.yml @@ -1,19 +1,19 @@ -# - title: ReactJS -# main_url: 'https://reactjs.org/' -# url: 'https://reactjs.org/' -# source_url: 'https://github.com/reactjs/reactjs.org' -# featured: true -# categories: -# - Web Dev -# - Featured -# - title: NEON -# main_url: 'http://neonrated.com/' -# url: 'http://neonrated.com/' -# featured: true -# categories: -# - Gallery -# - Cinema -# - Featured +- title: ReactJS + main_url: 'https://reactjs.org/' + url: 'https://reactjs.org/' + source_url: 'https://github.com/reactjs/reactjs.org' + featured: true + categories: + - Web Dev + - Featured +- title: NEON + main_url: 'http://neonrated.com/' + url: 'http://neonrated.com/' + featured: true + categories: + - Gallery + - Cinema + - Featured - title: The State of European Tech main_url: 'https://2017.stateofeuropeantech.com/' url: 'https://2017.stateofeuropeantech.com/' @@ -24,41 +24,41 @@ plugins: 'gatsby-plugin-react-helmet, gatsby-plugin-react-next' built_by: Studio Lovelock built_by_url: 'http://www.studiolovelock.com/' -# - title: Slite -# main_url: 'https://slite.com/' -# url: 'https://slite.com/' -# featured: true -# categories: -# - Landing -# - Marketing -# - Technology -# - Featured -# - title: GraphCMS -# main_url: 'https://graphcms.com/' -# url: 'https://graphcms.com/' -# featured: true -# categories: -# - Landing -# - Marketing -# - Technology -# - title: Bottender Docs -# main_url: 'https://bottender.js.org/' -# url: 'https://bottender.js.org/' -# source_url: 'https://github.com/bottenderjs/bottenderjs.github.io' -# featured: true -# categories: -# - Documentation -# - Web Dev -# - Open Source -# - Featured -# - title: Cardiogram -# main_url: 'https://cardiogr.am/' -# url: 'https://cardiogr.am/' -# featured: true -# categories: -# - Marketing -# - Technology -# - Featured +- title: Slite + main_url: 'https://slite.com/' + url: 'https://slite.com/' + featured: true + categories: + - Landing + - Marketing + - Technology + - Featured +- title: GraphCMS + main_url: 'https://graphcms.com/' + url: 'https://graphcms.com/' + featured: true + categories: + - Landing + - Marketing + - Technology +- title: Bottender Docs + main_url: 'https://bottender.js.org/' + url: 'https://bottender.js.org/' + source_url: 'https://github.com/bottenderjs/bottenderjs.github.io' + featured: true + categories: + - Documentation + - Web Dev + - Open Source + - Featured +- title: Cardiogram + main_url: 'https://cardiogr.am/' + url: 'https://cardiogr.am/' + featured: true + categories: + - Marketing + - Technology + - Featured - title: Etcetera Design main_url: 'https://etcetera.design/' url: 'https://etcetera.design/' @@ -68,167 +68,167 @@ - Portfolio - Creative - Featured -# - title: Hack Club -# main_url: 'https://hackclub.com/' -# url: 'https://hackclub.com/' -# source_url: 'https://github.com/hackclub/site' -# featured: true -# categories: -# - Education -# - Web Dev -# - Featured -# - title: Matthias Jordan Portfolio -# main_url: 'https://iammatthias.com/' -# url: 'https://iammatthias.com/' -# source_url: 'https://github.com/iammatthias/net' -# featured: true -# categories: -# - Photography -# - Portfolio -# - Featured -# - title: Investment Calculator -# main_url: 'https://investmentcalculator.io/' -# url: 'https://investmentcalculator.io/' -# featured: true -# categories: -# - Education -# - Featured -# - Finance -# - title: CSS Grid Playground by MozillaDev -# main_url: 'https://mozilladevelopers.github.io/playground/' -# url: 'https://mozilladevelopers.github.io/playground/' -# source_url: 'https://github.com/MozillaDevelopers/playground' -# featured: true -# categories: -# - Education -# - Web Dev -# - Featured -# - title: Piotr Fedorczyk Portfolio -# main_url: 'https://piotrf.pl/' -# url: 'https://piotrf.pl/' -# featured: true -# categories: -# - Portfolio -# - Creative -# - Web Dev -# - Featured -# - title: unrealcpp -# main_url: 'https://unrealcpp.com/' -# url: 'https://unrealcpp.com/' -# source_url: 'https://github.com/Harrison1/unrealcpp-com' -# featured: true -# categories: -# - Blog -# - Web Dev -# - Featured -# - title: Andy Slezak -# main_url: 'https://www.aslezak.com/' -# url: 'https://www.aslezak.com/' -# source_url: 'https://github.com/amslezak' -# featured: true -# categories: -# - Web Dev -# - Portfolio -# - Featured -# - title: Deliveroo.Design -# main_url: 'https://www.deliveroo.design/' -# url: 'https://www.deliveroo.design/' -# featured: true -# categories: -# - Food -# - Marketing -# - Featured -# - title: Dona Rita -# main_url: 'https://www.donarita.co.uk/' -# url: 'https://www.donarita.co.uk/' -# source_url: 'https://github.com/peduarte/dona-rita-website' -# featured: true -# categories: -# - Food -# - Marketing -# - Featured -# - title: Fröhlich ∧ Frei -# main_url: 'https://www.froehlichundfrei.de/' -# url: 'https://www.froehlichundfrei.de/' -# featured: true -# categories: -# - Web Dev -# - Blog -# - Open Source -# - title: How to GraphQL -# main_url: 'https://www.howtographql.com/' -# url: 'https://www.howtographql.com/' -# source_url: 'https://github.com/howtographql/howtographql' -# featured: true -# categories: -# - Documentation -# - Web Dev -# - Open Source -# - Featured -# - title: OnCallogy -# main_url: 'https://www.oncallogy.com/' -# url: 'https://www.oncallogy.com/' -# featured: true -# categories: -# - Landing -# - Marketing -# - Featured -# - Healthcare -# - title: Ryan Wiemer's Portfolio -# main_url: 'https://www.ryanwiemer.com/' -# url: 'https://www.ryanwiemer.com/knw-photography/' -# source_url: 'https://github.com/ryanwiemer/rw' -# featured: true -# categories: -# - Portfolio -# - Web Dev -# - Featured -# - title: Ventura Digitalagentur Köln -# main_url: 'https://www.ventura-digital.de/' -# url: 'https://www.ventura-digital.de/' -# featured: true -# categories: -# - Agency -# - Marketing -# - Featured -# - title: Azer Koçulu -# main_url: 'http://azer.bike/' -# url: 'http://azer.bike/photography' -# featured: false -# categories: -# - Portfolio -# - Photography -# - Web Dev -# - title: Damir.io -# main_url: 'http://damir.io/' -# url: 'http://damir.io/' -# source_url: 'https://github.com/dvzrd/gatsby-sfiction' -# featured: false -# categories: -# - Creative -# - title: Digital Psychology -# main_url: 'http://digitalpsychology.io/' -# url: 'http://digitalpsychology.io/' -# source_url: 'https://github.com/danistefanovic/digitalpsychology.io' -# featured: false -# categories: -# - Education -# - Library -# - title: GRANDstack -# main_url: 'http://grandstack.io/' -# url: 'http://grandstack.io/' -# featured: false -# categories: -# - Open Source -# - Web Dev -# - title: Théâtres Parisiens -# main_url: 'http://theatres-parisiens.fr/' -# url: 'http://theatres-parisiens.fr/' -# source_url: 'https://github.com/phacks/theatres-parisiens' -# featured: false -# categories: -# - Education -# - Entertainment +- title: Hack Club + main_url: 'https://hackclub.com/' + url: 'https://hackclub.com/' + source_url: 'https://github.com/hackclub/site' + featured: true + categories: + - Education + - Web Dev + - Featured +- title: Matthias Jordan Portfolio + main_url: 'https://iammatthias.com/' + url: 'https://iammatthias.com/' + source_url: 'https://github.com/iammatthias/net' + featured: true + categories: + - Photography + - Portfolio + - Featured +- title: Investment Calculator + main_url: 'https://investmentcalculator.io/' + url: 'https://investmentcalculator.io/' + featured: true + categories: + - Education + - Featured + - Finance +- title: CSS Grid Playground by MozillaDev + main_url: 'https://mozilladevelopers.github.io/playground/' + url: 'https://mozilladevelopers.github.io/playground/' + source_url: 'https://github.com/MozillaDevelopers/playground' + featured: true + categories: + - Education + - Web Dev + - Featured +- title: Piotr Fedorczyk Portfolio + main_url: 'https://piotrf.pl/' + url: 'https://piotrf.pl/' + featured: true + categories: + - Portfolio + - Creative + - Web Dev + - Featured +- title: unrealcpp + main_url: 'https://unrealcpp.com/' + url: 'https://unrealcpp.com/' + source_url: 'https://github.com/Harrison1/unrealcpp-com' + featured: true + categories: + - Blog + - Web Dev + - Featured +- title: Andy Slezak + main_url: 'https://www.aslezak.com/' + url: 'https://www.aslezak.com/' + source_url: 'https://github.com/amslezak' + featured: true + categories: + - Web Dev + - Portfolio + - Featured +- title: Deliveroo.Design + main_url: 'https://www.deliveroo.design/' + url: 'https://www.deliveroo.design/' + featured: true + categories: + - Food + - Marketing + - Featured +- title: Dona Rita + main_url: 'https://www.donarita.co.uk/' + url: 'https://www.donarita.co.uk/' + source_url: 'https://github.com/peduarte/dona-rita-website' + featured: true + categories: + - Food + - Marketing + - Featured +- title: Fröhlich ∧ Frei + main_url: 'https://www.froehlichundfrei.de/' + url: 'https://www.froehlichundfrei.de/' + featured: true + categories: + - Web Dev + - Blog + - Open Source +- title: How to GraphQL + main_url: 'https://www.howtographql.com/' + url: 'https://www.howtographql.com/' + source_url: 'https://github.com/howtographql/howtographql' + featured: true + categories: + - Documentation + - Web Dev + - Open Source + - Featured +- title: OnCallogy + main_url: 'https://www.oncallogy.com/' + url: 'https://www.oncallogy.com/' + featured: true + categories: + - Landing + - Marketing + - Featured + - Healthcare +- title: Ryan Wiemer's Portfolio + main_url: 'https://www.ryanwiemer.com/' + url: 'https://www.ryanwiemer.com/knw-photography/' + source_url: 'https://github.com/ryanwiemer/rw' + featured: true + categories: + - Portfolio + - Web Dev + - Featured +- title: Ventura Digitalagentur Köln + main_url: 'https://www.ventura-digital.de/' + url: 'https://www.ventura-digital.de/' + featured: true + categories: + - Agency + - Marketing + - Featured +- title: Azer Koçulu + main_url: 'http://azer.bike/' + url: 'http://azer.bike/photography' + featured: false + categories: + - Portfolio + - Photography + - Web Dev +- title: Damir.io + main_url: 'http://damir.io/' + url: 'http://damir.io/' + source_url: 'https://github.com/dvzrd/gatsby-sfiction' + featured: false + categories: + - Creative +- title: Digital Psychology + main_url: 'http://digitalpsychology.io/' + url: 'http://digitalpsychology.io/' + source_url: 'https://github.com/danistefanovic/digitalpsychology.io' + featured: false + categories: + - Education + - Library +- title: GRANDstack + main_url: 'http://grandstack.io/' + url: 'http://grandstack.io/' + featured: false + categories: + - Open Source + - Web Dev +- title: Théâtres Parisiens + main_url: 'http://theatres-parisiens.fr/' + url: 'http://theatres-parisiens.fr/' + source_url: 'https://github.com/phacks/theatres-parisiens' + featured: false + categories: + - Education + - Entertainment - title: William Owen UK Portfolio / Blog main_url: 'http://william-owen.co.uk/' url: 'http://william-owen.co.uk/' @@ -242,1888 +242,2019 @@ - Blog built_by: William Owen built_by_url: 'https://twitter.com/twilowen' -# - title: A4 纸网 -# main_url: 'http://www.a4z.cn/' -# url: 'http://www.a4z.cn/price' -# source_url: 'https://github.com/hiooyUI/hiooyui.github.io' -# featured: false -# categories: -# - Retail -# - title: Steve Meredith's Portfolio -# main_url: 'http://www.stevemeredith.com/' -# url: 'http://www.stevemeredith.com/' -# featured: false -# categories: -# - Portfolio -# - title: Sourcegraph -# main_url: 'https://about.sourcegraph.com/' -# url: 'https://about.sourcegraph.com/' -# featured: false -# categories: -# - Web Dev -# - title: API Platform -# main_url: 'https://api-platform.com/' -# url: 'https://api-platform.com/' -# source_url: 'https://github.com/api-platform/website' -# featured: false -# categories: -# - Documentation -# - Web Dev -# - Open Source -# - Library -# - title: Artivest -# main_url: 'https://artivest.co/' -# url: 'https://artivest.co/what-we-do/for-advisors-and-investors/' -# featured: false -# categories: -# - Marketing -# - Blog -# - Documentation -# - Finance -# - title: The Audacious Project -# main_url: 'https://audaciousproject.org/' -# url: 'https://audaciousproject.org/' -# featured: false -# categories: -# - Nonprofit -# - title: Dustin Schau's Blog -# main_url: 'https://blog.dustinschau.com/' -# url: 'https://blog.dustinschau.com/' -# source_url: 'https://github.com/dschau/blog' -# featured: false -# categories: -# - Blog -# - Web Dev -# - title: FloydHub's Blog -# main_url: 'https://blog.floydhub.com/' -# url: 'https://blog.floydhub.com/' -# featured: false -# categories: -# - Technology -# - Blog -# - title: iContract Blog -# main_url: 'https://blog.icontract.co.uk/' -# url: 'http://blog.icontract.co.uk/' -# featured: false -# categories: -# - Blog -# - title: BRIIM -# main_url: 'https://bri.im/' -# url: 'https://bri.im/' -# featured: false -# description: >- -# BRIIM is a movement to enable JavaScript enthusiasts and web developers in -# machine learning. Learn about artificial intelligence and data science, two -# fields which are governed by machine learning, in JavaScript. Take it right -# to your browser with WebGL. -# categories: -# - Education -# - Web Dev -# - Technology -# - title: Caddy Smells Like Trees -# main_url: 'https://caddysmellsliketrees.ru/' -# url: 'https://caddysmellsliketrees.ru/' -# source_url: 'https://github.com/podabed/caddysmellsliketrees.github.io' -# featured: false -# description: >- -# We play soul-searching songs for every day. They are merging in our forests -# in such a way that it is difficult to separate them from each other, and -# between them bellow bold deer poems. -# categories: -# - Music -# - title: Calpa's Blog -# main_url: 'https://calpa.me/' -# url: 'https://calpa.me/' -# source_url: 'https://github.com/calpa/blog' -# featured: false -# categories: -# - Blog -# - Web Dev -# - title: Chocolate Free -# main_url: 'https://chocolate-free.com/' -# url: 'https://chocolate-free.com/' -# source_url: 'https://github.com/Khaledgarbaya/chocolate-free-website' -# featured: false -# description: "A full time foodie \U0001F60D a forever Parisian \"patisserie\" lover and \U0001F382 \U0001F369 \U0001F370 \U0001F36A explorer and finally an under construction #foodblogger #foodblog" -# categories: -# - Blog -# - Food -# - title: Code Bushi -# main_url: 'https://codebushi.com/' -# url: 'https://codebushi.com/' -# featured: false -# description: >- -# Web development resources, trends, & techniques to elevate your coding journey. -# categories: -# - Web Dev -# - Open Source -# - Blog -# built_by: Hunter Chang -# built_by_url: 'https://hunterchang.com/' -# - title: Daniel Hollcraft -# main_url: 'https://danielhollcraft.com/' -# url: 'https://danielhollcraft.com/' -# source_url: 'https://github.com/danielbh/danielhollcraft.com' -# featured: false -# categories: -# - Web Dev -# - Blog -# - Portfolio -# - title: Darren Britton's Portfolio -# main_url: 'https://darrenbritton.com/' -# url: 'https://darrenbritton.com/' -# source_url: 'https://github.com/darrenbritton/darrenbritton.github.io' -# featured: false -# categories: -# - Web Dev -# - Portfolio -# - title: Dave Lindberg Marketing & Design -# url: 'https://davelindberg.com/' -# main_url: 'https://davelindberg.com/' -# source_url: 'https://github.com/Dave-Lindberg/dl-gatsby' -# featured: false -# description: >- -# My work revolves around solving problems for people in business, using integrated design and marketing strategies to improve sales, increase brand engagement, generate leads and achieve goals. -# categories: -# - Creative -# - Design -# - Featured -# - Marketing -# - SEO -# - Portfolio -# - title: Design Systems Weekly -# main_url: 'https://designsystems.email/' -# url: 'https://designsystems.email/' -# featured: false -# categories: -# - Education -# - Web Dev -# - title: Dalbinaco's Website -# main_url: 'https://dlbn.co/en/' -# url: 'https://dlbn.co/en/' -# source_url: 'https://github.com/dalbinaco/dlbn.co' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - title: mParticle's Documentation -# main_url: 'https://docs.mparticle.com/' -# url: 'https://docs.mparticle.com/' -# featured: false -# categories: -# - Web Dev -# - Documentation -# - title: Doopoll -# main_url: 'https://doopoll.co/' -# url: 'https://doopoll.co/' -# featured: false -# categories: -# - Landing -# - Marketing -# - Technology -# - title: ERC dEX -# main_url: 'https://ercdex.com/' -# url: 'https://ercdex.com/aqueduct' -# featured: false -# categories: -# - Marketing -# - title: Fabian Schultz' Portfolio -# main_url: 'https://fabianschultz.com/' -# url: 'https://fabianschultz.com/' -# source_url: 'https://github.com/fabe/site' -# featured: false -# description: >- -# Hello, I’m Fabian — a product designer and developer based in Potsdam, -# Germany. I’ve been working both as a product designer and frontend developer -# for over 5 years now. I particularly enjoy working with companies that try -# to meet broad and unique user needs. -# categories: -# - Portfolio -# - Web Dev -# - title: Formidable -# main_url: 'https://formidable.com/' -# url: 'https://formidable.com/' -# featured: true -# categories: -# - Web Dev -# - Agency -# - Open Source -# - Featured -# - title: Gatsby Manor -# main_url: 'https://gatsbymanor.com/' -# url: 'https://gatsbymanor.com/themes' -# featured: false -# categories: -# - Web Dev -# - title: The freeCodeCamp Guide -# main_url: 'https://guide.freecodecamp.org/' -# url: 'https://guide.freecodecamp.org/' -# source_url: 'https://github.com/freeCodeCamp/guide' -# featured: false -# categories: -# - Web Dev -# - Documentation -# - title: High School Hackathons -# main_url: 'https://hackathons.hackclub.com/' -# url: 'https://hackathons.hackclub.com/' -# source_url: 'https://github.com/hackclub/hackathons' -# featured: false -# categories: -# - Education -# - Web Dev -# - title: Hapticmedia -# main_url: 'https://hapticmedia.fr/en/' -# url: 'https://hapticmedia.fr/en/' -# featured: false -# categories: -# - Agency -# - Creative -# - title: heml.io -# main_url: 'https://heml.io/' -# url: 'https://heml.io/' -# source_url: 'https://github.com/SparkPost/heml.io' -# featured: false -# categories: -# - Documentation -# - Web Dev -# - Open Source -# - title: Juliette Pretot's Portfolio -# main_url: 'https://juliette.sh/' -# url: 'https://juliette.sh/' -# featured: false -# categories: -# - Web Dev -# - Portfolio -# - Blog -# - title: Kris Hedstrom's Portfolio -# main_url: 'https://k-create.com/' -# url: 'https://k-create.com/portfolio/' -# source_url: 'https://github.com/kristofferh/kristoffer' -# featured: false -# description: >- -# Hey. I’m Kris. I’m an interactive designer / developer. I grew up in Umeå, -# in northern Sweden, but I now live in Brooklyn, NY. I am currently enjoying -# a hybrid Art Director + Lead Product Engineer role at a small startup called -# Nomad Health. Before that, I was a Product (Engineering) Manager at Tumblr. -# Before that, I worked at agencies. Before that, I was a baby. I like to -# design things, and then I like to build those things. I occasionally take on -# freelance projects. Feel free to get in touch if you have an interesting -# project that you want to collaborate on. Or if you just want to say hello, -# that’s cool too. -# categories: -# - Creative -# - Portfolio -# - title: knpw.rs -# main_url: 'https://knpw.rs/' -# url: 'https://knpw.rs/' -# source_url: 'https://github.com/knpwrs/knpw.rs' -# featured: false -# categories: -# - Blog -# - Web Dev -# - title: Kostas Bariotis' Blog -# main_url: 'https://kostasbariotis.com/' -# url: 'https://kostasbariotis.com/' -# source_url: 'https://github.com/kbariotis/kostasbariotis.com' -# featured: false -# categories: -# - Blog -# - Portfolio -# - Web Dev -# - title: LaserTime Clinic -# main_url: 'https://lasertime.ru/' -# url: 'https://lasertime.ru/' -# source_url: 'https://github.com/oleglegun/lasertime' -# featured: false -# categories: -# - Marketing -# - title: Jason Lengstorf -# main_url: 'https://lengstorf.com' -# url: 'https://lengstorf.com' -# source_url: 'https://github.com/jlengstorf/lengstorf.com' -# featured: false -# date_added: Apr 10 -# gatsby_version: V1 -# categories: -# - Blog -# - Personal -# built_by: Jason Lengstorf -# built_by_url: 'https://github.com/jlengstorf' -# - title: Mannequin.io -# main_url: 'https://mannequin.io/' -# url: 'https://mannequin.io/' -# source_url: 'https://github.com/LastCallMedia/Mannequin/tree/master/site' -# featured: false -# categories: -# - Open Source -# - Web Dev -# - Documentation -# - title: manu.ninja -# main_url: 'https://manu.ninja/' -# url: 'https://manu.ninja/' -# source_url: 'https://github.com/Lorti/manu.ninja' -# featured: false -# description: >- -# manu.ninja is the personal blog of Manuel Wieser, where he talks about -# front-end development, games and digital art -# categories: -# - Blog -# - Technology -# - Web Dev -# - title: Fabric -# main_url: 'https://meetfabric.com/' -# url: 'https://meetfabric.com/' -# featured: true -# categories: -# - Corporate -# - Marketing -# - Featured -# - Insurance -# - title: Nexit -# main_url: 'https://nexit.sk/' -# url: 'https://nexit.sk/references' -# featured: false -# categories: -# - Web Dev -# - title: Nortcast -# main_url: 'https://nortcast.com/' -# url: 'https://nortcast.com/' -# featured: false -# categories: -# - Technology -# - Entertainment -# - Podcast -# - title: openFDA -# main_url: 'https://open.fda.gov/' -# url: 'https://open.fda.gov/' -# source_url: 'https://github.com/FDA/open.fda.gov' -# featured: false -# categories: -# - Government -# - Open Source -# - Web Dev -# - title: NYC Planning Labs (New York City Department of City Planning) -# main_url: 'https://planninglabs.nyc/' -# url: 'https://planninglabs.nyc/about/' -# source_url: 'https://github.com/NYCPlanning/' -# featured: false -# description: >- -# We work with New York City's Urban Planners to deliver impactful, modern -# technology tools. -# categories: -# - Open Source -# - Government -# - title: Pravdomil -# main_url: 'https://pravdomil.com/' -# url: 'https://pravdomil.com/' -# source_url: 'https://github.com/pravdomil/pravdomil.com' -# featured: false -# description: >- -# I’ve been working both as a product designer and frontend developer for over -# 5 years now. I particularly enjoy working with companies that try to meet -# broad and unique user needs. -# categories: -# - Portfolio -# - Personal -# - title: Preston Richey Portfolio / Blog -# main_url: 'https://prestonrichey.com/' -# url: 'https://prestonrichey.com/' -# source_url: 'https://github.com/prichey/prestonrichey.com' -# featured: false -# categories: -# - Web Dev -# - Portfolio -# - Blog -# - title: Landing page of Put.io -# main_url: 'https://put.io/' -# url: 'https://put.io/' -# featured: false -# categories: -# - eCommerce -# - Technology -# - title: The Rick and Morty API -# main_url: 'https://rickandmortyapi.com/' -# url: 'https://rickandmortyapi.com/' -# featured: false -# categories: -# - Web Dev -# - Entertainment -# - Documentation -# - Open Source -# - title: Santa Compañía Creativa -# main_url: 'https://santacc.es/' -# url: 'https://santacc.es/' -# source_url: 'https://github.com/DesarrolloWebSantaCC/santacc-web' -# featured: false -# categories: -# - Agency -# - Creative -# - title: Sean Coker's Blog -# main_url: 'https://sean.is/' -# url: 'https://sean.is/' -# featured: false -# categories: -# - Blog -# - Portfolio -# - Web Dev -# - title: Segment's Blog -# main_url: 'https://segment.com/blog/' -# url: 'https://segment.com/blog/' -# featured: false -# categories: -# - Web Dev -# - Blog -# - title: Several Levels -# main_url: 'https://severallevels.io/' -# url: 'https://severallevels.io/' -# source_url: 'https://github.com/Harrison1/several-levels' -# featured: false -# categories: -# - Agency -# - Web Dev -# - title: Simply -# main_url: 'https://simply.co.za/' -# url: 'https://simply.co.za/' -# featured: false -# categories: -# - Corporate -# - Marketing -# - Insurance -# - title: Storybook -# main_url: 'https://storybook.js.org/' -# url: 'https://storybook.js.org/' -# source_url: 'https://github.com/storybooks/storybook' -# featured: false -# categories: -# - Web Dev -# - Open Source -# - title: Vibert Thio's Portfolio -# main_url: 'https://vibertthio.com/portfolio/' -# url: 'https://vibertthio.com/portfolio/projects/' -# source_url: 'https://github.com/vibertthio/portfolio' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Creative -# - title: VisitGemer -# main_url: 'https://visitgemer.sk/' -# url: 'https://visitgemer.sk/' -# featured: false -# categories: -# - Marketing -# - title: Beach Hut Poole -# main_url: 'https://www.beachhutpoole.co.uk/' -# url: 'https://www.beachhutpoole.co.uk/' -# featured: false -# categories: -# - Travel -# - Marketing -# - title: Bricolage.io -# main_url: 'https://www.bricolage.io/' -# url: 'https://www.bricolage.io/' -# source_url: 'https://github.com/KyleAMathews/blog' -# featured: false -# categories: -# - Blog -# - title: Charles Pinnix Website -# main_url: 'https://www.charlespinnix.com/' -# url: 'https://www.charlespinnix.com/' -# featured: false -# description: >- -# I’m a senior front end engineer with 8 years of experience building websites -# and web applications. I’m interested in leading creative, multidisciplinary -# engineering teams. I’m a creative technologist, merging photography, art, -# and design into engineering and visa versa. I take a pragmatic, -# product-oriented approach to development, allowing me to see the big picture -# and ensuring quality products are completed on time. I have a passion for -# modern front end JavaScript frameworks such as React and Vue, and I have -# substantial experience on the back end with an interest in Node and -# container based deployment with Docker and AWS. -# categories: -# - Portfolio -# - Web Dev -# - title: Charlie Harrington's Blog -# main_url: 'https://www.charlieharrington.com/' -# url: 'https://www.charlieharrington.com/' -# source_url: 'https://github.com/whatrocks/blog' -# featured: false -# categories: -# - Blog -# - Web Dev -# - Music -# - title: Developer Ecosystem -# main_url: 'https://www.developerecosystem.com/' -# url: 'https://www.developerecosystem.com/' -# featured: false -# categories: -# - Blog -# - Web Dev -# - title: Gabriel Adorf's Portfolio -# main_url: 'https://www.gabrieladorf.com/' -# url: 'https://www.gabrieladorf.com/' -# source_url: 'https://github.com/gabdorf/gabriel-adorf-portfolio' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - title: greglobinski.com -# main_url: 'https://www.greglobinski.com/' -# url: 'https://www.greglobinski.com/' -# source_url: 'https://github.com/greglobinski/www.greglobinski.com' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - title: I am Putra -# main_url: 'https://www.iamputra.com/' -# url: 'https://www.iamputra.com/' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: In Sowerby Bridge -# main_url: 'https://www.insowerbybridge.co.uk/' -# url: 'https://www.insowerbybridge.co.uk/' -# featured: false -# categories: -# - Marketing -# - Government -# - title: JavaScript Stuff -# main_url: 'https://www.javascriptstuff.com/' -# url: 'https://www.javascriptstuff.com/' -# featured: false -# categories: -# - Education -# - Web Dev -# - Library -# - title: KNW Photography -# main_url: 'https://www.knw.io/' -# url: 'https://www.knw.io/galleries/' -# source_url: 'https://github.com/ryanwiemer/knw' -# featured: false -# description: >- -# Hi there! I’m Kirsten and I’m currently living in Oakland with my husband -# and fluffy pup Birch. I’ve always been an excessive photo taker and decided -# to turn my love for taking photos into a career. -# categories: -# - Photography -# - Portfolio -# - title: Ledgy -# main_url: 'https://www.ledgy.com/' -# url: 'https://github.com/morloy/ledgy.com' -# featured: false -# categories: -# - Marketing -# - Finance -# - title: Alec Lomas's Portfolio / Blog -# main_url: 'https://www.lowmess.com/' -# url: 'https://www.lowmess.com/' -# source_url: 'https://github.com/lowmess/lowmess' -# featured: false -# categories: -# - Web Dev -# - Blog -# - Portfolio -# - title: Michele Mazzucco's Portfolio -# main_url: 'https://www.michelemazzucco.it/' -# url: 'https://www.michelemazzucco.it/' -# source_url: 'https://github.com/michelemazzucco/michelemazzucco.it' -# featured: false -# categories: -# - Creative -# - Portfolio -# - title: Orbit FM Podcasts -# main_url: 'https://www.orbit.fm/' -# url: 'https://www.orbit.fm/' -# source_url: 'https://github.com/agarrharr/orbit.fm' -# featured: false -# categories: -# - Podcast -# - title: Prosecco Springs -# main_url: 'https://www.proseccosprings.com/' -# url: 'https://www.proseccosprings.com/' -# featured: false -# categories: -# - Food -# - Blog -# - Marketing -# - title: Verious -# main_url: 'https://www.verious.io/' -# url: 'https://www.verious.io/' -# source_url: 'https://github.com/cpinnix/verious' -# featured: false -# categories: -# - Web Dev -# - title: Whittle School -# main_url: 'https://www.whittleschool.org/en/' -# url: 'https://www.whittleschool.org/en/' -# featured: false -# categories: -# - Education -# - title: Yisela -# main_url: 'https://www.yisela.com/' -# url: 'https://www.yisela.com/tetris-against-trauma-gaming-as-therapy/' -# featured: false -# categories: -# - Blog -# - title: YouFoundRon.com -# main_url: 'https://www.youfoundron.com/' -# url: 'https://www.youfoundron.com/' -# source_url: 'https://github.com/rongierlach/yfr-dot-com' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: yerevancoder -# main_url: 'https://yerevancoder.com/' -# url: 'https://forum.yerevancoder.com/categories' -# source_url: 'https://github.com/yerevancoder/yerevancoder.github.io' -# featured: false -# categories: -# - Blog -# - Web Dev -# - title: EaseCentral -# main_url: 'https://www.easecentral.com/' -# url: 'https://www.easecentral.com/' -# featured: false -# categories: -# - Marketing -# - Healthcare -# - title: Policygenius -# main_url: 'https://www.policygenius.com/' -# url: 'https://www.policygenius.com/' -# featured: false -# categories: -# - Marketing -# - Healthcare -# - title: Moteefe -# main_url: 'http://www.moteefe.com/' -# url: 'http://www.moteefe.com/' -# featured: false -# categories: -# - Marketing -# - Agency -# - Technology -# - title: Athelas -# main_url: 'http://www.athelas.com/' -# url: 'http://www.athelas.com/' -# featured: true -# categories: -# - Marketing -# - Healthcare -# - Featured -# - title: Pathwright -# main_url: 'http://www.pathwright.com/' -# url: 'http://www.pathwright.com/' -# featured: false -# categories: -# - Marketing -# - Education -# - title: pi-top -# main_url: 'http://www.pi-top.com/' -# url: 'http://www.pi-top.com/' -# featured: false -# categories: -# - Marketing -# - Web Dev -# - Technology -# - eCommerce -# - title: Troops -# main_url: 'http://www.troops.ai/' -# url: 'http://www.troops.ai/' -# featured: false -# categories: -# - Marketing -# - Technology -# - title: ClearBrain -# main_url: 'https://clearbrain.com/' -# url: 'https://clearbrain.com/' -# featured: false -# categories: -# - Marketing -# - Technology -# - title: Lucid -# main_url: 'https://www.golucid.co/' -# url: 'https://www.golucid.co/' -# featured: true -# categories: -# - Marketing -# - Technology -# - Featured -# - title: Bench -# main_url: 'http://www.bench.co/' -# url: 'http://www.bench.co/' -# featured: false -# categories: -# - Marketing -# - title: Union Plus Credit Card -# main_url: 'http://www.unionpluscard.com' -# url: 'https://unionplus.capitalone.com/' -# featured: false -# categories: -# - Marketing -# - Finance -# - title: Gin Lane -# main_url: 'http://www.ginlane.com/' -# url: 'https://www.ginlane.com/' -# featured: false -# categories: -# - Web Dev -# - Creative -# - Agency -# - title: Marmelab -# main_url: 'https://marmelab.com/en/' -# url: 'https://marmelab.com/en/' -# featured: false -# categories: -# - Web Dev -# - Agency -# - title: Fusion Media Group -# main_url: 'http://thefmg.com/' -# url: 'http://thefmg.com/' -# featured: true -# categories: -# - Creative -# - Entertainment -# - News -# - Featured -# - title: Cool Hunting -# main_url: 'http://www.coolhunting.com/' -# url: 'http://www.coolhunting.com/' -# featured: false -# categories: -# - Magazine -# - title: Dovetail -# main_url: 'https://dovetailapp.com/' -# url: 'https://dovetailapp.com/' -# featured: false -# categories: -# - Marketing -# - Technology -# - title: GraphQL College -# main_url: 'https://www.graphql.college/' -# url: 'https://www.graphql.college/' -# source_url: 'https://github.com/GraphQLCollege/graphql-college' -# featured: false -# categories: -# - Web Dev -# - Education -# - title: F1 Vision -# main_url: 'https://www.f1vision.com/' -# url: 'https://www.f1vision.com/' -# featured: false -# categories: -# - Marketing -# - Entertainment -# - Technology -# - eCommerce -# - title: Yuuniworks Portfolio / Blog -# main_url: 'https://www.yuuniworks.com/' -# url: 'https://www.yuuniworks.com/' -# source_url: 'https://github.com/junkboy0315/yuuni-web' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: Bastion Bot -# main_url: 'https://bastionbot.org/' -# url: 'https://bastionbot.org/' -# featured: false -# categories: -# - Marketing -# - Technology -# - Documentation -# - Web Dev -# - title: upGizmo -# main_url: 'https://www.upgizmo.com/' -# url: 'https://www.upgizmo.com/' -# featured: false -# categories: -# - News -# - Technology -# - title: Smakosh -# main_url: 'https://smakosh.com/' -# url: 'https://smakosh.com/' -# source_url: 'https://github.com/smakosh/smakosh.com' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Creative -# - title: Philipp Czernitzki - Blog/Website -# main_url: 'http://philippczernitzki.me/' -# url: 'http://philippczernitzki.me/' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: WebGazer -# main_url: 'https://www.webgazer.io/' -# url: 'https://www.webgazer.io/' -# featured: false -# categories: -# - Marketing -# - Web Dev -# - Technology -# - title: Joe Seifi's Blog -# main_url: 'http://seifi.org/' -# url: 'http://seifi.org/' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: Bartosz Dominiak Blog/Portfolio -# main_url: 'http://bartoszdominiak.com/' -# url: 'http://bartoszdominiak.com/' -# source_url: 'https://github.com/bartdominiak/blog' -# featured: false -# categories: -# - Portfolio -# - Web Dev -# - Blog -# - title: HBTU MUN 2018 (source) -# featured: false -# - title: Jamie Henson's Blog (source) -# featured: false -# - title: Ruben's Blog (source) -# featured: false -# - title: Thao Am Private Enterprise -# featured: false -# - title: Bakadono -# featured: false -# - title: Travellers.cafe -# featured: false -# - title: Oliver Benns' Portfolio (source) -# featured: false -# - title: angeloocana.com (source) -# featured: false -# - title: Overlap.show (source) -# featured: false -# - title: smartive Company Website -# featured: false -# - title: Haboba Find Jobs at Phu Quoc Island -# featured: false -# - title: Song Wang’s website (source) -# featured: false -# - title: Magicly's blog (source) -# featured: false -# - title: Phu Quoc Works -# featured: false -# - title: Kabir Goel's website (source) -# featured: false -# - title: David James' Portfolio (source) -# featured: false -# - title: Tic Tac Toe AI (source) -# featured: false -# - title: Random Screencast -# featured: false -# - title: Phu Quoc Tea & Coffee Store -# featured: false -# - title: Steven Natera's blog -# featured: false -# - title: LekoArts -# main_url: 'https://www.lekoarts.de' -# url: 'https://www.lekoarts.de' -# source_url: 'https://github.com/LeKoArts/portfolio' -# featured: false -# built_by: LekoArts -# built_by_url: 'https://github.com/LeKoArts' -# description: >- -# Hi, I'm Lennart — a self-taught and passionate graphic/web designer & frontend developer based in Darmstadt, Germany. I love it to realize complex projects in a creative manner and face new challenges. Since 6 years I do graphic design, my love for frontend development came up 3 years ago. I enjoy acquiring new skills and cementing this knowledge by writing blogposts and creating tutorials. -# categories: -# - Portfolio -# - Blog -# - title: Georgi Yanev (source) -# featured: false -# - title: Hallingdata -# featured: false -# - title: '@swyx (source)' -# featured: false -# - title: 伊撒尔の窝 -# featured: false -# - title: 杨二小的博客 -# main_url: 'https://blog.yangerxiao.com/' -# url: 'https://blog.yangerxiao.com/' -# source_url: 'https://github.com/zerosoul/blog.yangerxiao.com' -# featured: false -# categories: -# - Blog -# - Portfolio -# - title: mottox2 blog -# main_url: 'https://mottox2.com' -# url: 'https://mottox2.com' -# featured: false -# categories: -# - Blog -# - Portfolio -# - title: Pride of the Meadows -# main_url: 'https://www.prideofthemeadows.com/' -# url: 'https://www.prideofthemeadows.com/' -# featured: false -# categories: -# - Retail -# - Food -# - Blog -# - title: Michael Uloth -# main_url: 'https://www.michaeluloth.com' -# url: 'https://www.michaeluloth.com' -# featured: false -# description: >- -# Michael Uloth is an opera singer and web developer based in Toronto. -# categories: -# - Portfolio -# - Music -# - Web Dev -# built_by: Michael Uloth -# built_by_url: 'https://www.michaeluloth.com' -# - title: NYC Pride 2019 | WorldPride NYC | Stonewall50 -# main_url: 'https://2019-worldpride-stonewall50.nycpride.org/' -# url: 'https://2019-worldpride-stonewall50.nycpride.org/' -# featured: false -# description: >- -# Join us in 2019 for NYC Pride, as we welcome WorldPride and mark the 50th Anniversary of the Stonewall Uprising and a half-century of LGBTQ+ liberation. -# categories: -# - Education -# - Marketing -# - Nonprofit -# built_by: Canvas United -# built_by_url: 'https://www.canvasunited.com/' -# - title: Spacetime -# main_url: 'https://www.heyspacetime.com/' -# url: 'https://www.heyspacetime.com/' -# featured: false -# description: >- -# Spacetime is a Dallas-based digital experience agency specializing in web, app, startup, and digital experience creation. -# categories: -# - Marketing -# - Portfolio -# - Agency -# - Creative -# - Featured -# built_by: Spacetime -# built_by_url: 'https://www.heyspacetime.com/' -# - title: Eric Jinks -# main_url: 'https://ericjinks.com/' -# url: 'https://ericjinks.com/' -# featured: false -# description: >- -# Software engineer / web developer from the Gold Coast, Australia. -# categories: -# - Portfolio -# - Blog -# - Web Dev -# - Technology -# built_by: Eric Jinks -# built_by_url: 'https://ericjinks.com/' -# - title: GaiAma - We are wildlife -# main_url: 'https://www.gaiama.org/' -# url: 'https://www.gaiama.org/' -# featured: false -# description: >- -# We founded the GaiAma conservation organization to protect wildlife in Perú and to create an example of a permaculture neighborhood, living symbiotically with the forest - because reforestation is just the beginning -# categories: -# - Nonprofit -# - Marketing -# - Blog -# source_url: https://github.com/GaiAma/gaiama.org -# built_by: GaiAma -# built_by_url: 'https://www.gaiama.org/' -# - title: Healthcare Logic -# main_url: 'https://www.healthcarelogic.com/' -# url: 'https://www.healthcarelogic.com/' -# featured: false -# description: >- -# Revolutionary technology that empowers clinical and managerial leaders to collaborate with clarity. -# categories: -# - Marketing -# - Healthcare -# - Technology -# built_by: Thrive -# built_by_url: 'https://thriveweb.com.au/' -# - title: Localgov.fyi -# main_url: 'https://localgov.fyi/' -# url: 'https://localgov.fyi/' -# featured: false -# description: >- -# Finding local government services made easier. -# categories: -# - Directory -# - Government -# - Technology -# source_url: https://github.com/WeOpenly/localgov.fyi -# built_by: Openly -# built_by_url: 'https://weopenly.com/' -# - title: Kata.ai Documentation -# main_url: 'https://docs.kata.ai/' -# url: 'https://docs.kata.ai/' -# source_url: https://github.com/kata-ai/kata-platform-docs -# featured: false -# description: >- -# Documentation website for the Kata Platform, an all-in-one platform for -# building chatbots using AI technologies. -# categories: -# - Documentation -# - Technology -# - title: goalgetters -# main_url: 'https://goalgetters.space/' -# url: 'https://goalgetters.space/' -# featured: false -# description: >- -# goalgetters is a source of inspiration for people who want to change their career. -# We offer articles, success stories and expert interviews on how to find a new passion and how to implement change. -# categories: -# - Blog -# - Education -# - Careers -# - Personal Development -# built_by: Stephanie Langers (content), Adrian Wenke (development) -# built_by_url: 'https://twitter.com/AdrianWenke' -# - title: Life Without Barriers | Foster Care -# main_url: 'https://www.lwb.org.au/foster-care' -# url: 'https://www.lwb.org.au/foster-care' -# featured: false -# description: >- -# We are urgently seeking foster carers all across Australia. Can you open your heart and your home to a child in need? -# There are different types of foster care that can suit you. We offer training and 24/7 support. -# categories: -# - Charity -# - Nonprofit -# - Education -# - Documentation -# - Marketing -# built_by: LWB Digital Team -# built_by_url: 'https://twitter.com/LWBAustralia' -# - title: Bejamas - JAM Experts for hire -# main_url: 'https://bejamas.io/' -# url: 'https://bejamas.io/' -# featured: false -# description: >- -# We help agencies and companies with JAMStack tools. This includes web development using Static Site Generators, Headless CMS, CI / CD and CDN setup. -# categories: -# - Technology -# - Web Dev -# - Agency -# - Creative -# - Marketing -# built_by: Bejamas.io -# built_by_url: 'https://bejamas.io/' -# - title: Zensum -# main_url: 'https://zensum.se/' -# url: 'https://zensum.se/' -# featured: false -# description: >- -# Borrow money quickly and safely through Zensum. We compare Sweden's leading banks and credit institutions. Choose from multiple offers and lower your monthly cost. [Translated from Swedish] -# categories: -# - Technology -# - Finance -# - Corporate -# - Marketing -# built_by: Bejamas.io -# built_by_url: 'https://bejamas.io/' -# - title: StatusHub - Easy to use Hosted Status Page Service -# main_url: 'https://statushub.com/' -# url: 'https://statushub.com/' -# featured: false -# description: >- -# Set up your very own service status page in minutes with StatusHub. Allow customers to subscribe to be updated automatically. -# categories: -# - Technology -# - Marketing -# built_by: Bejamas.io -# built_by_url: 'https://bejamas.io/' -# - title: Matthias Kretschmann Portfolio -# main_url: 'https://matthiaskretschmann.com/' -# url: 'https://matthiaskretschmann.com/' -# source_url: 'https://github.com/kremalicious/portfolio' -# featured: false -# description: >- -# Portfolio of designer & developer Matthias Kretschmann. -# categories: -# - Portfolio -# - Web Dev -# - Creative -# built_by: Matthias Kretschmann -# built_by_url: 'https://matthiaskretschmann.com/' -# - title: Cajun Bowfishing -# main_url: 'https://cajunbowfishing.com/' -# url: 'https://cajunbowfishing.com/' -# featured: false -# categories: -# - eCommerce -# - Sports -# built_by: Escalade Sports -# built_by_url: 'http://escaladesports.com/' -# - title: Iron Cove Solutions -# main_url: 'https://ironcovesolutions.com/' -# url: 'https://ironcovesolutions.com/' -# description: >- -# Iron Cove Solutions is a cloud based consulting firm. We help companies deliver a return on cloud usage by applying best practices -# categories: -# - Technology -# - Web Dev -# built_by: Iron Cove Solutions -# built_by_url: 'https://ironcovesolutions.com/' -# featured: false -# - title: Eventos orellana -# description: >- -# Somos una empresa dedicada a brindar asesoría personalizada -# y profesional para la elaboración y coordinación de eventos -# sociales y empresariales. -# main_url: 'https://eventosorellana.com/' -# url: 'https://eventosorellana.com/' -# featured: false -# categories: -# - Gallery -# built_by: Codedebug -# built_by_url: 'https://codedebug.co/' -# - title: Moetez Chaabene Portfolio / Blog -# main_url: 'https://moetez.me/' -# url: 'https://moetez.me/' -# source_url: 'https://github.com/moetezch/moetez.me' -# featured: false -# description: >- -# Portfolio of Moetez Chaabene -# categories: -# - Portfolio -# - Web Dev -# - Blog -# built_by: Moetez Chaabene -# built_by_url: 'https://twitter.com/moetezch' -# - title: Nikita -# description: >- -# Automation of system deployments in Node.js for applications and infrastructures. -# main_url: https://nikita.js.org/ -# url: https://nikita.js.org/ -# source_url: 'https://github.com/adaltas/node-nikita' -# categories: -# - Documentation -# - Open Source -# - Technology -# built_by: David Worms -# built_by_url: http://www.adaltas.com -# featured: false -# - title: Gourav Sood Blog & Portfolio -# main_url: 'https://www.gouravsood.com/' -# url: 'https://www.gouravsood.com/' -# featured: false -# categories: -# - Blog -# - Portfolio -# - Salesforce -# built_by: Gourav Sood -# built_by_url: 'https://www.gouravsood.com/' -# - title: Figma -# main_url: 'https://www.figma.com/' -# url: 'https://www.figma.com/' -# featured: false -# categories: -# - Marketing -# - Design -# built_by: Corey Ward -# built_by_url: http://www.coreyward.me/ -# - title: Jonas Tebbe Portfolio -# description: > -# Hey, I’m Jonas and I create digital products. -# main_url: https://jonastebbe.com -# url: https://jonastebbe.com -# categories: -# - Portfolio -# built_by: Jonas Tebbe -# built_by_url: http://twitter.com/jonastebbe -# featured: false -# - title: Parker Sarsfield Portfolio -# description: > -# I'm Parker, a software engineer and sneakerhead. -# main_url: https://parkersarsfield.com -# url: https://parkersarsfield.com -# categories: -# - Blog -# - Portfolio -# built_by: Parker Sarsfield -# built_by_url: https://parkersarsfield.com -# - title: Front-end web development with Greg -# description: > -# JavaScript, GatsbyJS, ReactJS, CSS in JS... Let's learn some stuff together. -# main_url: https://dev.greglobinski.com -# url: https://dev.greglobinski.com -# categories: -# - Blog -# - Web Dev -# built_by: Greg Lobinski -# built_by_url: https://github.com/greglobinski -# - title: Insomnia -# description: > -# Desktop HTTP and GraphQL client for developers -# main_url: https://insomnia.rest/ -# url: https://insomnia.rest/ -# categories: -# - Landing -# - Blog -# built_by: Gregory Schier -# built_by_url: https://schier.co -# featured: false -# - title: Timeline Theme Portfolio -# description: > -# I'm Aman Mittal, a software developer. -# main_url: http://www.amanhimself.me/ -# url: http://www.amanhimself.me/ -# categories: -# - Landing -# - Web Dev -# - Portfolio -# built_by: Aman Mittal -# built_by_url: http://www.amanhimself.me/ -# - title: Ocean artUp -# description: > -# Science outreach site built using styled-components and Contentful. It presents the research project "Ocean artUp" funded by an Advanced Grant of the European Research Council to explore the possible benefits of artificial uplift of nutrient-rich deep water to the ocean’s sunlit surface layer. -# main_url: https://ocean-artup.eu -# url: https://ocean-artup.eu -# source_url: https://github.com/JanoshRiebesell/ocean-artup -# categories: -# - Education -# - Blog -# - Science -# built_by: Janosh Riebesell -# built_by_url: https://janosh.io -# featured: false -# - title: Ryan Fitzgerald -# description: > -# Personal portfolio and blog for Ryan Fitzgerald -# main_url: https://ryanfitzgerald.ca/ -# url: https://ryanfitzgerald.ca/ -# categories: -# - Web Dev -# - Portfolio -# built_by: Ryan Fitzgerald -# built_by_url: https://github.com/RyanFitzgerald -# featured: false -# - title: Kaizen -# description: > -# Content Marketing, PR & SEO Agency in London -# main_url: https://www.kaizen.co.uk/ -# url: https://www.kaizen.co.uk/ -# categories: -# - Agency -# - Blog -# - Creative -# - Design -# - Web Dev -# - SEO -# built_by: Bogdan Stanciu -# built_by_url: https://github.com/b0gd4n -# featured: false -# - title: HackerOne Platform Documentation -# description: > -# HackerOne's Product Documentation Center! -# url: https://docs.hackerone.com/ -# main_url: https://docs.hackerone.com/ -# categories: -# - Documentation -# - Security -# featured: false -# - title: Patreon Partners -# description: > -# Resources and products to help you do more with Patreon. -# url: https://partners.patreon.com/ -# main_url: https://partners.patreon.com/ -# categories: -# - Resources -# - Directory -# featured: false -# - title: Bureau Of Meteorology (beta) -# description: > -# Help shape the future of Bureau services -# url: https://beta.bom.gov.au/ -# main_url: https://beta.bom.gov.au/ -# categories: -# - Meteorology -# - Research -# - Services -# featured: false -# - title: Curbside -# description: > -# Connecting Stores with Mobile Customers -# main_url: https://curbside.com/ -# url: https://curbside.com/ -# categories: -# - Mobile Commerce -# - Software -# featured: false -# - title: Mux Video -# description: > -# API to video hosting and streaming -# main_url: https://mux.com/ -# url: https://mux.com/ -# categories: -# - Video -# - Hosting -# - Streaming -# - API -# featured: false -# - title: Swapcard -# description: > -# The easiest way for event organizers to instantly connect people, build a community of attendees and exhibitors, and increase revenue over time -# main_url: https://www.swapcard.com/ -# url: https://www.swapcard.com/ -# categories: -# - Event -# - Community -# - Services -# featured: false -# - title: Kalix -# description: > -# Kalix is perfect for healthcare professionals starting out in private practice, to those with an established clinic. -# main_url: https://www.kalixhealth.com/ -# url: https://www.kalixhealth.com/ -# categories: -# - Scheduling -# - Documentation -# - Messaging -# - Billing -# - Services -# featured: false -# - title: Hubba -# description: > -# Buy wholesale products from thousands of independent, verified Brands. -# main_url: https://join.hubba.com/ -# url: https://join.hubba.com/ -# categories: -# - eCommerce -# - Retail -# featured: false -# - title: Airbnb Engineering & Data Science -# description: > -# Creative engineers and data scientists building a world where you can belong anywhere -# main_url: https://airbnb.io/ -# url: https://airbnb.io/ -# categories: -# - Blog -# - Gallery -# - Projects -# featured: false -# - title: HyperPlay -# description: > -# In Asean's 1st Ever LOL Esports X Music Festival -# main_url: https://hyperplay.leagueoflegends.com/ -# url: https://hyperplay.leagueoflegends.com/ -# categories: -# - Landing -# featured: false -# - title: Bad Credit Loans -# description: > -# Get the funds you need, from $250-$5,000 -# main_url: https://www.creditloan.com/ -# url: https://www.creditloan.com/ -# categories: -# - Loans -# - Credits -# featured: false -# - title: Financial Center -# description: > -# Member-owned, not-for-profit, co-operative whose members receive financial benefits in the form of lower loan rates, higher savings rates, and lower fees than banks. -# main_url: https://fcfcu.com/ -# url: https://fcfcu.com/ -# categories: -# - Loans -# - Credits -# - Online Banking -# - Nonprofit -# featured: false -# - title: Open FDA -# description: > -# Provides APIs and raw download access to a number of high-value, high priority and scalable structured datasets, including adverse events, drug product labeling, and recall enforcement reports. -# main_url: https://open.fda.gov/ -# url: https://open.fda.gov/ -# categories: -# - APIs -# - Datasets -# - Reports -# featured: false -# - title: Office of Institutional Research and Assessment -# description: > -# Good Data, Good Decisions -# main_url: http://oira.ua.edu/ -# url: http://oira.ua.edu/ -# categories: -# - Research -# - Data -# featured: false -# - title: GM Capital One -# description: > -# Introducing the new online experience for your GM Rewards Credit Card -# main_url: https://gm.capitalone.com/ -# url: https://gm.capitalone.com/ -# categories: -# - Rewards -# - Credit Card -# featured: false -# - title: House Manager -# description: > -# Home service membership that offers proactive and on-demand maintenance for homeowners -# main_url: https://housemanager.calstate.aaa.com/ -# url: https://housemanager.calstate.aaa.com/ -# categories: -# - Home -# - Services -# featured: false -# - title: Trintellix -# description: > -# It may help make a difference for your depression (MDD). -# main_url: https://us.trintellix.com/ -# url: https://us.trintellix.com/ -# categories: -# - Health -# - Help -# featured: false -# - title: The Telegraph Premium -# description: > -# Exclusive stories from award-winning journalists -# main_url: https://premium.telegraph.co.uk/ -# url: https://premium.telegraph.co.uk/ -# categories: -# - Landing -# - Newspaper -# - Subscription -# featured: false -# - title: html2canvas -# description: > -# Screenshots with JavaScript -# main_url: http://html2canvas.hertzen.com/ -# url: http://html2canvas.hertzen.com/ -# source_url: https://github.com/niklasvh/html2canvas/tree/master/www -# categories: -# - Tool -# - Screenshots -# - JavaScript -# - Documentation -# built_by: Niklas von Hertzen -# built_by_url: http://hertzen.com/ -# featured: false -# - title: The State of JavaScript 2017 -# description: > -# Data from over 20,000 developers, asking them questions on topics ranging from front-end frameworks and state management, to build tools and testing libraries. -# main_url: https://stateofjs.com/ -# url: https://stateofjs.com/ -# source_url: https://github.com/StateOfJS/StateOfJS -# categories: -# - Data -# - JavaScript -# - Statistics -# built_by: StateOfJS -# built_by_url: https://github.com/StateOfJS/StateOfJS/graphs/contributors -# featured: false -# - title: Dato CMS -# description: > -# The API-based CMS your editors will love -# main_url: https://www.datocms.com/ -# url: https://www.datocms.com/ -# categories: -# - CMS -# - API -# - Tool -# featured: false -# - title: Half Electronics -# description: > -# Personal website -# main_url: https://www.halfelectronic.com/ -# url: https://www.halfelectronic.com/ -# categories: -# - Blog -# - Electronics -# built_by: Fernando Poumian -# built_by_url: https://github.com/fpoumian/halfelectronic.com -# featured: false -# - title: Frithir Software Development -# main_url: 'https://frithir.com/' -# url: 'https://frithir.com/' -# featured: false -# description: >- -# I DRINK COFFEE, WRITE CODE AND IMPROVE MY DEVELOPMENT SKILLS EVERY DAY. -# categories: -# - Creative -# - Design -# - Web Dev -# built_by: Frithir -# built_by_url: 'https://Frithir.com/' -# - title: Unow -# main_url: https://www.unow.fr/ -# url: https://www.unow.fr/ -# categories: -# - Education -# - Corporate -# - Marketing -# featured: false -# - title: Peter Hironaka -# description: > -# Freelance Web Developer based in Los Angeles. -# main_url: https://peterhironaka.com/ -# url: https://peterhironaka.com/ -# categories: -# - Portfolio -# - Web Dev -# built_by: Peter Hironaka -# built_by_url: https://github.com/PHironaka -# featured: false -# - title: Michael McQuade -# description: > -# Personal website and blog for Michael McQuade -# main_url: https://giraffesyo.io -# url: https://giraffesyo.io -# categories: -# - Blog -# built_by: Michael McQuade -# built_by_url: https://github.com/giraffesyo -# featured: false -# - title: Haacht Brewery -# description: > -# Corporate wesbite for Haacht Brewery. Designed and Developed by Gafas. -# main_url: https://haacht.com/en/ -# url: https://haacht.com -# categories: -# - Corporate -# built_by: Gafas -# built_by_url: https://gafas.be -# featured: false -# - title: StoutLabs -# description: > -# Portfolio of Daniel Stout, freelance web dev in East Tennessee. -# main_url: https://www.stoutlabs.com/ -# url: https://www.stoutlabs.com/ -# categories: -# - Web Dev -# - Portfolio -# built_by: Daniel Stout -# built_by_url: https://github.com/stoutlabs -# featured: false -# - title: Chicago Ticket Outcomes By Neighborhood -# description: > -# ProPublica data visualization of traffic ticket court outcomes -# categories: -# - News -# - Nonprofit -# - Visualization -# url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-status/?initialWidth=782 -# built_by: David Eads -# built_by_url: https://github.com/eads -# featured: false -# - title: Chicago South Side Traffic Ticketing rates -# description: > -# ProPublica data visualization of traffic ticket rates by community -# url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-rate/?initialWidth=782 -# categories: -# - News -# - Nonprofit -# - Visualization -# built_by: David Eads -# built_by_url: https://github.com/eads -# featured: false -# - title: Otsimo -# description: > -# Otsimo is a special education application for children with autism, down syndrome and other developmental disabilities. -# main_url: https://otsimo.com/en/ -# url: https://otsimo.com/en/ -# categories: -# - Landing -# - Blog -# - Education -# featured: false -# - title: Matt Bagni Portfolio 2018 -# description: > -# Mostly the result of playing with Gatsby and learning about react and graphql. Using the screenshot plugin to showcase the work done for my company in the last 2 years, and a good amount of other experiments. -# main_url: https://mattbag.github.io -# url: https://mattbag.github.io -# categories: -# - Landing -# - Portfolio -# featured: false -# - title: Lifestone Church -# main_url: 'https://www.lifestonechurch.net/' -# url: 'https://www.lifestonechurch.net/' -# source_url: 'https://github.com/lifestonechurch/lifestonechurch.net' -# featured: false -# categories: -# - Marketing -# - Nonprofit -# - title: Artem Sapegin -# description: > -# Little homepage of Artem Sapegin, a frontend developer, passionate photographer, coffee drinker and crazy dogs’ owner. -# main_url: https://sapegin.me/ -# url: https://sapegin.me/ -# categories: -# - Landing -# - Portfolio -# - Open Source -# - Personal -# - Web Dev -# built_by: Artem Sapegin -# built_by_url: https://github.com/sapegin -# featured: false -# - title: SparkPost Developers -# main_url: 'https://developers.sparkpost.com/' -# url: 'https://developers.sparkpost.com/' -# source_url: 'https://github.com/SparkPost/developers.sparkpost.com' -# categories: -# - Documentation -# - API -# featured: false -# - title: Malik Browne Portfolio 2018 -# description: > -# The portfolio blog of Malik Browne, a full stack engineer, foodie, and avid blogger/YouTuber. -# main_url: https://www.malikbrowne.com/about -# url: https://www.malikbrowne.com -# categories: -# - Blog -# - Portfolio -# built_by: Malik Browne -# built_by_url: https://twitter.com/milkstarz -# featured: false -# - title: Novatics -# description: > -# Digital products that inspire and make a difference -# main_url: https://www.novatics.com.br -# url: https://www.novatics.com.br -# categories: -# - Landing -# - Portfolio -# - Technology -# - Digital Products -# - Web Dev -# built_by: Novatics -# built_by_url: https://github.com/Novatics -# featured: false -# - title: I migliori by Vivigratis -# description: > -# Product review website -# main_url: https://imigliori.vivigratis.com/ -# url: https://imigliori.vivigratis.com/ -# categories: -# - Blog -# - Travel -# - Technology -# - Wellness -# - Fashion -# built_by: Kframe Interactive SA -# featured: false -# - title: Max McKinney -# description: > -# I’m a developer and designer with a focus in web technologies. I build cars on the side. -# main_url: https://maxmckinney.com/ -# url: https://maxmckinney.com/ -# categories: -# - Portfolio -# - Web Dev -# - Design -# - Landing -# - Personal -# built_by: Max McKinney -# featured: false -# - title: Stickyard -# description: > -# Make your React component sticky the easy way -# main_url: https://nihgwu.github.io/stickyard/ -# url: https://nihgwu.github.io/stickyard/ -# source_url: https://github.com/nihgwu/stickyard/tree/master/website -# categories: -# - Web Dev -# built_by: Neo Nie -# featured: false -# - title: Agata Milik -# description: > -# Website of a Polish psychologist/psychotherapist based in Gdańsk, Poland. -# main_url: https://agatamilik.pl -# url: https://agatamilik.pl -# categories: -# - Landing -# - Marketing -# - Healthcare -# built_by: Piotr Fedorczyk -# built_by_url: https://piotrf.pl -# featured: false -# - title: WebPurple -# main_url: 'https://www.webpurple.net/' -# url: 'https://www.webpurple.net/' -# source_url: 'https://github.com/WebPurple/site' -# description: >- -# Site of local (Russia, Ryazan) frontend community. Main purpose is to show info about meetups and keep blog. -# categories: -# - Nonprofit -# - Web Dev -# - Community -# - Blog -# - Open Source -# built_by: Nikita Kirsanov -# built_by_url: 'https://twitter.com/kitos_kirsanov' -# featured: false -# - title: Papertrail.io -# description: > -# Inspection Management for the 21st Century -# main_url: https://www.papertrail.io/ -# url: https://www.papertrail.io/ -# categories: -# - Marketing -# - Technology -# - Landing -# built_by: Papertrail.io -# built_by_url: https://www.papertrail.io -# featured: false -# - title: Matt Ferderer -# main_url: https://mattferderer.com -# url: https://mattferderer.com -# source_url: https://github.com/mattferderer/gatsbyblog -# description: > -# {titleofthesite} is a blog built with Gatsby that discusses web related tech such as JavaScript, .NET, Blazor & security. -# categories: -# - Blog -# - Web Dev -# - Personal -# built_by: Matt Ferderer -# built_by_url: https://twitter.com/mattferderer -# featured: false -# - title: Sahyadri Open Source Community -# main_url: https://sosc.org.in -# url: https://sosc.org.in -# source_url: https://github.com/haxzie/sosc-website -# description: > -# Official website of Sahyadri Open Source Community for community blog, event details and members info. -# categories: -# - Blog -# - Community -# - Open Source -# built_by: Musthaq Ahamad -# built_by_url: https://github.com/haxzie -# featured: false -# - title: Tech Confessions -# main_url: https://confessions.tech -# url: https://confessions.tech -# source_url: https://github.com/JonathanSpeek/tech-confessions -# description: > -# A guilt-free place for us to confess our tech sins 🙏 -# categories: -# - Community -# - Open Source -# built_by: Jonathan Speek -# built_by_url: https://speek.design -# featured: false -# - title: Thibault Maekelbergh -# main_url: https://thibmaek.com -# url: https://thibmaek.com -# source_url: https://github.com/thibmaek/thibmaek.github.io -# description: > -# A nice blog about development, Raspberry Pi, plants and probably records. -# categories: -# - Blog -# - Open Source -# built_by: Thibault Maekelbergh -# built_by_url: https://twitter.com/thibmaek -# featured: false -# - title: LearnReact.design -# main_url: https://learnreact.design -# url: https://learnreact.design -# description: > -# React Essentials For Designers: A React course tailored for product designers, ux designers, ui designers. -# categories: -# - Blog -# - Landing -# - Creative -# built_by: Linton Ye -# built_by_url: https://twitter.com/lintonye -# - title: DesignSystems.com -# main_url: https://www.designsystems.com/ -# url: https://www.designsystems.com/ -# description: > -# A resource for learning, creating and evangelizing design systems. -# categories: -# - Design -# - Blog -# - Technology -# built_by: Corey Ward -# built_by_url: http://www.coreyward.me/ -# featured: false -# - title: Devol’s Dance -# main_url: https://www.devolsdance.com/ -# url: https://www.devolsdance.com/ -# description: > -# Devol’s Dance is an invite-only, one-day event celebrating industrial robotics, AI, and automation. -# categories: -# - Marketing -# - Landing -# - Technology -# built_by: Corey Ward -# built_by_url: http://www.coreyward.me/ -# featured: false -# - title: Mega House Creative -# main_url: https://www.megahousecreative.com/ -# url: https://www.megahousecreative.com/ -# description: > -# Mega House Creative is a digital agency that provides unique goal-oriented web marketing solutions. -# categories: -# - Marketing -# - Agency -# built_by: Daniel Robinson -# featured: false -# - title: Tobie Marier Robitaille - csc -# main_url: https://tobiemarierrobitaille.com/ -# url: https://tobiemarierrobitaille.com/en/ -# description: > -# Portfolio site for director of photography Tobie Marier Robitaille -# categories: -# - Portfolio -# - Gallery -# built_by: Mill3 Studio -# built_by_url: https://mill3.studio/en/ -# featured: false -# - title: Bestvideogame.deals -# main_url: https://bestvideogame.deals/ -# url: https://bestvideogame.deals/ -# description: > -# Video game comparison website for the UK, build with GatsbyJS. -# categories: -# - eCommerce -# - Video Games -# built_by: Koen Kamphuis -# built_by_url: https://koenkamphuis.com/ -# featured: false -# - title: Mahipat's Portfolio -# main_url: https://mojaave.com/ -# url: https://mojaave.com -# source_url: https://github.com/mhjadav/mojaave -# description: > -# mojaave.com is Mahipat's portfolio, I have developed it using Gatsby v2 and Bootstrap, To get in touch with people looking for full stack developer. -# categories: -# - Portfolio -# - Software -# - Web Dev -# - Personal -# built_by: Mahipat Jadav -# built_by_url: https://mojaave.com/ -# featured: false -# - title: Cmsbased -# main_url: https://www.cmsbased.net -# url: https://www.cmsbased.net -# description: > -# Cmsbased is providing automation tools and design resources for Web Hosting and IT services industry. -# categories: -# - Technology -# - Design -# - Digital Products -# featured: false -# - title: Traffic Design Biennale 2018 -# main_url: https://trafficdesign.pl -# url: http://trafficdesign.pl/pl/ficzery/2018-biennale/ -# description: > -# Mini site for 2018 edition of Traffic Design’s Biennale -# categories: -# - Landing -# - Creative -# - Nonprofit -# - Gallery -# built_by: Piotr Fedorczyk -# built_by_url: https://piotrf.pl -# featured: false - +- title: A4 纸网 + main_url: 'http://www.a4z.cn/' + url: 'http://www.a4z.cn/price' + source_url: 'https://github.com/hiooyUI/hiooyui.github.io' + featured: false + categories: + - Retail +- title: Steve Meredith's Portfolio + main_url: 'http://www.stevemeredith.com/' + url: 'http://www.stevemeredith.com/' + featured: false + categories: + - Portfolio +- title: Sourcegraph + main_url: 'https://about.sourcegraph.com/' + url: 'https://about.sourcegraph.com/' + featured: false + categories: + - Web Dev +- title: API Platform + main_url: 'https://api-platform.com/' + url: 'https://api-platform.com/' + source_url: 'https://github.com/api-platform/website' + featured: false + categories: + - Documentation + - Web Dev + - Open Source + - Library +- title: Artivest + main_url: 'https://artivest.co/' + url: 'https://artivest.co/what-we-do/for-advisors-and-investors/' + featured: false + categories: + - Marketing + - Blog + - Documentation + - Finance +- title: The Audacious Project + main_url: 'https://audaciousproject.org/' + url: 'https://audaciousproject.org/' + featured: false + categories: + - Nonprofit +- title: Dustin Schau's Blog + main_url: 'https://blog.dustinschau.com/' + url: 'https://blog.dustinschau.com/' + source_url: 'https://github.com/dschau/blog' + featured: false + categories: + - Blog + - Web Dev +- title: FloydHub's Blog + main_url: 'https://blog.floydhub.com/' + url: 'https://blog.floydhub.com/' + featured: false + categories: + - Technology + - Blog +- title: iContract Blog + main_url: 'https://blog.icontract.co.uk/' + url: 'http://blog.icontract.co.uk/' + featured: false + categories: + - Blog +- title: BRIIM + main_url: 'https://bri.im/' + url: 'https://bri.im/' + featured: false + description: >- + BRIIM is a movement to enable JavaScript enthusiasts and web developers in + machine learning. Learn about artificial intelligence and data science, two + fields which are governed by machine learning, in JavaScript. Take it right + to your browser with WebGL. + categories: + - Education + - Web Dev + - Technology +- title: Caddy Smells Like Trees + main_url: 'https://caddysmellsliketrees.ru/' + url: 'https://caddysmellsliketrees.ru/' + source_url: 'https://github.com/podabed/caddysmellsliketrees.github.io' + featured: false + description: >- + We play soul-searching songs for every day. They are merging in our forests + in such a way that it is difficult to separate them from each other, and + between them bellow bold deer poems. + categories: + - Music +- title: Calpa's Blog + main_url: 'https://calpa.me/' + url: 'https://calpa.me/' + source_url: 'https://github.com/calpa/blog' + featured: false + categories: + - Blog + - Web Dev +- title: Chocolate Free + main_url: 'https://chocolate-free.com/' + url: 'https://chocolate-free.com/' + source_url: 'https://github.com/Khaledgarbaya/chocolate-free-website' + featured: false + description: "A full time foodie \U0001F60D a forever Parisian \"patisserie\" lover and \U0001F382 \U0001F369 \U0001F370 \U0001F36A explorer and finally an under construction #foodblogger #foodblog" + categories: + - Blog + - Food +- title: Code Bushi + main_url: 'https://codebushi.com/' + url: 'https://codebushi.com/' + featured: false + description: >- + Web development resources, trends, & techniques to elevate your coding journey. + categories: + - Web Dev + - Open Source + - Blog + built_by: Hunter Chang + built_by_url: 'https://hunterchang.com/' +- title: Daniel Hollcraft + main_url: 'https://danielhollcraft.com/' + url: 'https://danielhollcraft.com/' + source_url: 'https://github.com/danielbh/danielhollcraft.com' + featured: false + categories: + - Web Dev + - Blog + - Portfolio +- title: Darren Britton's Portfolio + main_url: 'https://darrenbritton.com/' + url: 'https://darrenbritton.com/' + source_url: 'https://github.com/darrenbritton/darrenbritton.github.io' + featured: false + categories: + - Web Dev + - Portfolio +- title: Dave Lindberg Marketing & Design + url: 'https://davelindberg.com/' + main_url: 'https://davelindberg.com/' + source_url: 'https://github.com/Dave-Lindberg/dl-gatsby' + featured: false + description: >- + My work revolves around solving problems for people in business, using integrated design and marketing strategies to improve sales, increase brand engagement, generate leads and achieve goals. + categories: + - Creative + - Design + - Featured + - Marketing + - SEO + - Portfolio +- title: Design Systems Weekly + main_url: 'https://designsystems.email/' + url: 'https://designsystems.email/' + featured: false + categories: + - Education + - Web Dev +- title: Dalbinaco's Website + main_url: 'https://dlbn.co/en/' + url: 'https://dlbn.co/en/' + source_url: 'https://github.com/dalbinaco/dlbn.co' + featured: false + categories: + - Portfolio + - Web Dev +- title: mParticle's Documentation + main_url: 'https://docs.mparticle.com/' + url: 'https://docs.mparticle.com/' + featured: false + categories: + - Web Dev + - Documentation +- title: Doopoll + main_url: 'https://doopoll.co/' + url: 'https://doopoll.co/' + featured: false + categories: + - Landing + - Marketing + - Technology +- title: ERC dEX + main_url: 'https://ercdex.com/' + url: 'https://ercdex.com/aqueduct' + featured: false + categories: + - Marketing +- title: Fabian Schultz' Portfolio + main_url: 'https://fabianschultz.com/' + url: 'https://fabianschultz.com/' + source_url: 'https://github.com/fabe/site' + featured: false + description: >- + Hello, I’m Fabian — a product designer and developer based in Potsdam, + Germany. I’ve been working both as a product designer and frontend developer + for over 5 years now. I particularly enjoy working with companies that try + to meet broad and unique user needs. + categories: + - Portfolio + - Web Dev +- title: Formidable + main_url: 'https://formidable.com/' + url: 'https://formidable.com/' + featured: true + categories: + - Web Dev + - Agency + - Open Source + - Featured +- title: Gatsby Manor + main_url: 'https://gatsbymanor.com/' + url: 'https://gatsbymanor.com/themes' + featured: false + categories: + - Web Dev +- title: The freeCodeCamp Guide + main_url: 'https://guide.freecodecamp.org/' + url: 'https://guide.freecodecamp.org/' + source_url: 'https://github.com/freeCodeCamp/guide' + featured: false + categories: + - Web Dev + - Documentation +- title: High School Hackathons + main_url: 'https://hackathons.hackclub.com/' + url: 'https://hackathons.hackclub.com/' + source_url: 'https://github.com/hackclub/hackathons' + featured: false + categories: + - Education + - Web Dev +- title: Hapticmedia + main_url: 'https://hapticmedia.fr/en/' + url: 'https://hapticmedia.fr/en/' + featured: false + categories: + - Agency + - Creative +- title: heml.io + main_url: 'https://heml.io/' + url: 'https://heml.io/' + source_url: 'https://github.com/SparkPost/heml.io' + featured: false + categories: + - Documentation + - Web Dev + - Open Source +- title: Juliette Pretot's Portfolio + main_url: 'https://juliette.sh/' + url: 'https://juliette.sh/' + featured: false + categories: + - Web Dev + - Portfolio + - Blog +- title: Kris Hedstrom's Portfolio + main_url: 'https://k-create.com/' + url: 'https://k-create.com/portfolio/' + source_url: 'https://github.com/kristofferh/kristoffer' + featured: false + description: >- + Hey. I’m Kris. I’m an interactive designer / developer. I grew up in Umeå, + in northern Sweden, but I now live in Brooklyn, NY. I am currently enjoying + a hybrid Art Director + Lead Product Engineer role at a small startup called + Nomad Health. Before that, I was a Product (Engineering) Manager at Tumblr. + Before that, I worked at agencies. Before that, I was a baby. I like to + design things, and then I like to build those things. I occasionally take on + freelance projects. Feel free to get in touch if you have an interesting + project that you want to collaborate on. Or if you just want to say hello, + that’s cool too. + categories: + - Creative + - Portfolio +- title: knpw.rs + main_url: 'https://knpw.rs/' + url: 'https://knpw.rs/' + source_url: 'https://github.com/knpwrs/knpw.rs' + featured: false + categories: + - Blog + - Web Dev +- title: Kostas Bariotis' Blog + main_url: 'https://kostasbariotis.com/' + url: 'https://kostasbariotis.com/' + source_url: 'https://github.com/kbariotis/kostasbariotis.com' + featured: false + categories: + - Blog + - Portfolio + - Web Dev +- title: LaserTime Clinic + main_url: 'https://lasertime.ru/' + url: 'https://lasertime.ru/' + source_url: 'https://github.com/oleglegun/lasertime' + featured: false + categories: + - Marketing +- title: Jason Lengstorf + main_url: 'https://lengstorf.com' + url: 'https://lengstorf.com' + source_url: 'https://github.com/jlengstorf/lengstorf.com' + featured: false + date_added: Apr 10 + gatsby_version: V1 + categories: + - Blog + - Personal + built_by: Jason Lengstorf + built_by_url: 'https://github.com/jlengstorf' +- title: Mannequin.io + main_url: 'https://mannequin.io/' + url: 'https://mannequin.io/' + source_url: 'https://github.com/LastCallMedia/Mannequin/tree/master/site' + featured: false + categories: + - Open Source + - Web Dev + - Documentation +- title: manu.ninja + main_url: 'https://manu.ninja/' + url: 'https://manu.ninja/' + source_url: 'https://github.com/Lorti/manu.ninja' + featured: false + description: >- + manu.ninja is the personal blog of Manuel Wieser, where he talks about + front-end development, games and digital art + categories: + - Blog + - Technology + - Web Dev +- title: Fabric + main_url: 'https://meetfabric.com/' + url: 'https://meetfabric.com/' + featured: true + categories: + - Corporate + - Marketing + - Featured + - Insurance +- title: Nexit + main_url: 'https://nexit.sk/' + url: 'https://nexit.sk/references' + featured: false + categories: + - Web Dev +- title: Nortcast + main_url: 'https://nortcast.com/' + url: 'https://nortcast.com/' + featured: false + categories: + - Technology + - Entertainment + - Podcast +- title: openFDA + main_url: 'https://open.fda.gov/' + url: 'https://open.fda.gov/' + source_url: 'https://github.com/FDA/open.fda.gov' + featured: false + categories: + - Government + - Open Source + - Web Dev +- title: NYC Planning Labs (New York City Department of City Planning) + main_url: 'https://planninglabs.nyc/' + url: 'https://planninglabs.nyc/about/' + source_url: 'https://github.com/NYCPlanning/' + featured: false + description: >- + We work with New York City's Urban Planners to deliver impactful, modern + technology tools. + categories: + - Open Source + - Government +- title: Pravdomil + main_url: 'https://pravdomil.com/' + url: 'https://pravdomil.com/' + source_url: 'https://github.com/pravdomil/pravdomil.com' + featured: false + description: >- + I’ve been working both as a product designer and frontend developer for over + 5 years now. I particularly enjoy working with companies that try to meet + broad and unique user needs. + categories: + - Portfolio + - Personal +- title: Preston Richey Portfolio / Blog + main_url: 'https://prestonrichey.com/' + url: 'https://prestonrichey.com/' + source_url: 'https://github.com/prichey/prestonrichey.com' + featured: false + categories: + - Web Dev + - Portfolio + - Blog +- title: Landing page of Put.io + main_url: 'https://put.io/' + url: 'https://put.io/' + featured: false + categories: + - eCommerce + - Technology +- title: The Rick and Morty API + main_url: 'https://rickandmortyapi.com/' + url: 'https://rickandmortyapi.com/' + built_by: Axel Fuhrmann + built_by_url: https://axelfuhrmann.com/ + featured: false + categories: + - Web Dev + - Entertainment + - Documentation + - Open Source + - API +- title: Santa Compañía Creativa + main_url: 'https://santacc.es/' + url: 'https://santacc.es/' + source_url: 'https://github.com/DesarrolloWebSantaCC/santacc-web' + featured: false + categories: + - Agency + - Creative +- title: Sean Coker's Blog + main_url: 'https://sean.is/' + url: 'https://sean.is/' + featured: false + categories: + - Blog + - Portfolio + - Web Dev +- title: Segment's Blog + main_url: 'https://segment.com/blog/' + url: 'https://segment.com/blog/' + featured: false + categories: + - Web Dev + - Blog +- title: Several Levels + main_url: 'https://severallevels.io/' + url: 'https://severallevels.io/' + source_url: 'https://github.com/Harrison1/several-levels' + featured: false + categories: + - Agency + - Web Dev +- title: Simply + main_url: 'https://simply.co.za/' + url: 'https://simply.co.za/' + featured: false + categories: + - Corporate + - Marketing + - Insurance +- title: Storybook + main_url: 'https://storybook.js.org/' + url: 'https://storybook.js.org/' + source_url: 'https://github.com/storybooks/storybook' + featured: false + categories: + - Web Dev + - Open Source +- title: Vibert Thio's Portfolio + main_url: 'https://vibertthio.com/portfolio/' + url: 'https://vibertthio.com/portfolio/projects/' + source_url: 'https://github.com/vibertthio/portfolio' + featured: false + categories: + - Portfolio + - Web Dev + - Creative +- title: VisitGemer + main_url: 'https://visitgemer.sk/' + url: 'https://visitgemer.sk/' + featured: false + categories: + - Marketing +- title: Beach Hut Poole + main_url: 'https://www.beachhutpoole.co.uk/' + url: 'https://www.beachhutpoole.co.uk/' + featured: false + categories: + - Travel + - Marketing +- title: Bricolage.io + main_url: 'https://www.bricolage.io/' + url: 'https://www.bricolage.io/' + source_url: 'https://github.com/KyleAMathews/blog' + featured: false + categories: + - Blog +- title: Charles Pinnix Website + main_url: 'https://www.charlespinnix.com/' + url: 'https://www.charlespinnix.com/' + featured: false + description: >- + I’m a senior front end engineer with 8 years of experience building websites + and web applications. I’m interested in leading creative, multidisciplinary + engineering teams. I’m a creative technologist, merging photography, art, + and design into engineering and visa versa. I take a pragmatic, + product-oriented approach to development, allowing me to see the big picture + and ensuring quality products are completed on time. I have a passion for + modern front end JavaScript frameworks such as React and Vue, and I have + substantial experience on the back end with an interest in Node and + container based deployment with Docker and AWS. + categories: + - Portfolio + - Web Dev +- title: Charlie Harrington's Blog + main_url: 'https://www.charlieharrington.com/' + url: 'https://www.charlieharrington.com/' + source_url: 'https://github.com/whatrocks/blog' + featured: false + categories: + - Blog + - Web Dev + - Music +- title: Developer Ecosystem + main_url: 'https://www.developerecosystem.com/' + url: 'https://www.developerecosystem.com/' + featured: false + categories: + - Blog + - Web Dev +- title: Gabriel Adorf's Portfolio + main_url: 'https://www.gabrieladorf.com/' + url: 'https://www.gabrieladorf.com/' + source_url: 'https://github.com/gabdorf/gabriel-adorf-portfolio' + featured: false + categories: + - Portfolio + - Web Dev +- title: greglobinski.com + main_url: 'https://www.greglobinski.com/' + url: 'https://www.greglobinski.com/' + source_url: 'https://github.com/greglobinski/www.greglobinski.com' + featured: false + categories: + - Portfolio + - Web Dev +- title: I am Putra + main_url: 'https://www.iamputra.com/' + url: 'https://www.iamputra.com/' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: In Sowerby Bridge + main_url: 'https://www.insowerbybridge.co.uk/' + url: 'https://www.insowerbybridge.co.uk/' + featured: false + categories: + - Marketing + - Government +- title: JavaScript Stuff + main_url: 'https://www.javascriptstuff.com/' + url: 'https://www.javascriptstuff.com/' + featured: false + categories: + - Education + - Web Dev + - Library +- title: KNW Photography + main_url: 'https://www.knw.io/' + url: 'https://www.knw.io/galleries/' + source_url: 'https://github.com/ryanwiemer/knw' + featured: false + description: >- + Hi there! I’m Kirsten and I’m currently living in Oakland with my husband + and fluffy pup Birch. I’ve always been an excessive photo taker and decided + to turn my love for taking photos into a career. + categories: + - Photography + - Portfolio +- title: Ledgy + main_url: 'https://www.ledgy.com/' + url: 'https://github.com/morloy/ledgy.com' + featured: false + categories: + - Marketing + - Finance +- title: Alec Lomas's Portfolio / Blog + main_url: 'https://www.lowmess.com/' + url: 'https://www.lowmess.com/' + source_url: 'https://github.com/lowmess/lowmess' + featured: false + categories: + - Web Dev + - Blog + - Portfolio +- title: Michele Mazzucco's Portfolio + main_url: 'https://www.michelemazzucco.it/' + url: 'https://www.michelemazzucco.it/' + source_url: 'https://github.com/michelemazzucco/michelemazzucco.it' + featured: false + categories: + - Creative + - Portfolio +- title: Orbit FM Podcasts + main_url: 'https://www.orbit.fm/' + url: 'https://www.orbit.fm/' + source_url: 'https://github.com/agarrharr/orbit.fm' + featured: false + categories: + - Podcast +- title: Prosecco Springs + main_url: 'https://www.proseccosprings.com/' + url: 'https://www.proseccosprings.com/' + featured: false + categories: + - Food + - Blog + - Marketing +- title: Verious + main_url: 'https://www.verious.io/' + url: 'https://www.verious.io/' + source_url: 'https://github.com/cpinnix/verious' + featured: false + categories: + - Web Dev +- title: Whittle School + main_url: 'https://www.whittleschool.org/en/' + url: 'https://www.whittleschool.org/en/' + featured: false + categories: + - Education +- title: Yisela + main_url: 'https://www.yisela.com/' + url: 'https://www.yisela.com/tetris-against-trauma-gaming-as-therapy/' + featured: false + categories: + - Blog +- title: YouFoundRon.com + main_url: 'https://www.youfoundron.com/' + url: 'https://www.youfoundron.com/' + source_url: 'https://github.com/rongierlach/yfr-dot-com' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: yerevancoder + main_url: 'https://yerevancoder.com/' + url: 'https://forum.yerevancoder.com/categories' + source_url: 'https://github.com/yerevancoder/yerevancoder.github.io' + featured: false + categories: + - Blog + - Web Dev +- title: EaseCentral + main_url: 'https://www.easecentral.com/' + url: 'https://www.easecentral.com/' + featured: false + categories: + - Marketing + - Healthcare +- title: Policygenius + main_url: 'https://www.policygenius.com/' + url: 'https://www.policygenius.com/' + featured: false + categories: + - Marketing + - Healthcare +- title: Moteefe + main_url: 'http://www.moteefe.com/' + url: 'http://www.moteefe.com/' + featured: false + categories: + - Marketing + - Agency + - Technology +- title: Athelas + main_url: 'http://www.athelas.com/' + url: 'http://www.athelas.com/' + featured: true + categories: + - Marketing + - Healthcare + - Featured +- title: Pathwright + main_url: 'http://www.pathwright.com/' + url: 'http://www.pathwright.com/' + featured: false + categories: + - Marketing + - Education +- title: pi-top + main_url: 'http://www.pi-top.com/' + url: 'http://www.pi-top.com/' + featured: false + categories: + - Marketing + - Web Dev + - Technology + - eCommerce +- title: Troops + main_url: 'http://www.troops.ai/' + url: 'http://www.troops.ai/' + featured: false + categories: + - Marketing + - Technology +- title: ClearBrain + main_url: 'https://clearbrain.com/' + url: 'https://clearbrain.com/' + featured: false + categories: + - Marketing + - Technology +- title: Lucid + main_url: 'https://www.golucid.co/' + url: 'https://www.golucid.co/' + featured: true + categories: + - Marketing + - Technology + - Featured +- title: Bench + main_url: 'http://www.bench.co/' + url: 'http://www.bench.co/' + featured: false + categories: + - Marketing +- title: Union Plus Credit Card + main_url: 'http://www.unionpluscard.com' + url: 'https://unionplus.capitalone.com/' + featured: false + categories: + - Marketing + - Finance +- title: Gin Lane + main_url: 'http://www.ginlane.com/' + url: 'https://www.ginlane.com/' + featured: false + categories: + - Web Dev + - Creative + - Agency +- title: Marmelab + main_url: 'https://marmelab.com/en/' + url: 'https://marmelab.com/en/' + featured: false + categories: + - Web Dev + - Agency +- title: Fusion Media Group + main_url: 'http://thefmg.com/' + url: 'http://thefmg.com/' + featured: true + categories: + - Creative + - Entertainment + - News + - Featured +- title: Cool Hunting + main_url: 'http://www.coolhunting.com/' + url: 'http://www.coolhunting.com/' + featured: false + categories: + - Magazine +- title: Dovetail + main_url: 'https://dovetailapp.com/' + url: 'https://dovetailapp.com/' + featured: false + categories: + - Marketing + - Technology +- title: GraphQL College + main_url: 'https://www.graphql.college/' + url: 'https://www.graphql.college/' + source_url: 'https://github.com/GraphQLCollege/graphql-college' + featured: false + categories: + - Web Dev + - Education +- title: F1 Vision + main_url: 'https://www.f1vision.com/' + url: 'https://www.f1vision.com/' + featured: false + categories: + - Marketing + - Entertainment + - Technology + - eCommerce +- title: Yuuniworks Portfolio / Blog + main_url: 'https://www.yuuniworks.com/' + url: 'https://www.yuuniworks.com/' + source_url: 'https://github.com/junkboy0315/yuuni-web' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: The Bastion Bot + main_url: 'https://bastionbot.org/' + url: 'https://bastionbot.org/' + source_url: 'https://github.com/TheBastionBot/Bastion-Website' + description: 'Give awesome perks to your Discord server!' + featured: false + categories: + - Open Source + - Technology + - Documentation + - Bot + - Community + - Services + - Software + - Tool + built_by: Sankarsan Kampa + built_by_url: https://sankarsankampa.com +- title: upGizmo + main_url: 'https://www.upgizmo.com/' + url: 'https://www.upgizmo.com/' + featured: false + categories: + - News + - Technology +- title: Smakosh + main_url: 'https://smakosh.com/' + url: 'https://smakosh.com/' + source_url: 'https://github.com/smakosh/smakosh.com' + featured: false + categories: + - Portfolio + - Web Dev + - Creative +- title: Philipp Czernitzki - Blog/Website + main_url: 'http://philippczernitzki.me/' + url: 'http://philippczernitzki.me/' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: WebGazer + main_url: 'https://www.webgazer.io/' + url: 'https://www.webgazer.io/' + featured: false + categories: + - Marketing + - Web Dev + - Technology +- title: Joe Seifi's Blog + main_url: 'http://seifi.org/' + url: 'http://seifi.org/' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: Bartosz Dominiak Blog/Portfolio + main_url: 'http://bartoszdominiak.com/' + url: 'http://bartoszdominiak.com/' + source_url: 'https://github.com/bartdominiak/blog' + featured: false + categories: + - Portfolio + - Web Dev + - Blog +- title: HBTU MUN 2018 (source) + featured: false +- title: Jamie Henson's Blog (source) + featured: false +- title: Ruben's Blog (source) + featured: false +- title: Thao Am Private Enterprise + featured: false +- title: Bakadono + featured: false +- title: Travellers.cafe + featured: false +- title: Oliver Benns' Portfolio (source) + featured: false +- title: angeloocana.com (source) + featured: false +- title: Overlap.show (source) + featured: false +- title: smartive Company Website + featured: false +- title: Haboba Find Jobs at Phu Quoc Island + featured: false +- title: Song Wang’s website (source) + featured: false +- title: Magicly's blog (source) + featured: false +- title: Phu Quoc Works + featured: false +- title: Kabir Goel's website (source) + featured: false +- title: David James' Portfolio (source) + featured: false +- title: Tic Tac Toe AI (source) + featured: false +- title: Random Screencast + featured: false +- title: Phu Quoc Tea & Coffee Store + featured: false +- title: Steven Natera's blog + featured: false +- title: LekoArts + main_url: 'https://www.lekoarts.de' + url: 'https://www.lekoarts.de' + source_url: 'https://github.com/LeKoArts/portfolio' + featured: false + built_by: LekoArts + built_by_url: 'https://github.com/LeKoArts' + description: >- + Hi, I'm Lennart — a self-taught and passionate graphic/web designer & frontend developer based in Darmstadt, Germany. I love it to realize complex projects in a creative manner and face new challenges. Since 6 years I do graphic design, my love for frontend development came up 3 years ago. I enjoy acquiring new skills and cementing this knowledge by writing blogposts and creating tutorials. + categories: + - Portfolio + - Blog +- title: Georgi Yanev (source) + featured: false +- title: Hallingdata + featured: false +- title: '@swyx (source)' + featured: false +- title: 伊撒尔の窝 + featured: false +- title: 杨二小的博客 + main_url: 'https://blog.yangerxiao.com/' + url: 'https://blog.yangerxiao.com/' + source_url: 'https://github.com/zerosoul/blog.yangerxiao.com' + featured: false + categories: + - Blog + - Portfolio +- title: mottox2 blog + main_url: 'https://mottox2.com' + url: 'https://mottox2.com' + featured: false + categories: + - Blog + - Portfolio +- title: Pride of the Meadows + main_url: 'https://www.prideofthemeadows.com/' + url: 'https://www.prideofthemeadows.com/' + featured: false + categories: + - Retail + - Food + - Blog +- title: Michael Uloth + main_url: 'https://www.michaeluloth.com' + url: 'https://www.michaeluloth.com' + featured: false + description: >- + Michael Uloth is an opera singer and web developer based in Toronto. + categories: + - Portfolio + - Music + - Web Dev + built_by: Michael Uloth + built_by_url: 'https://www.michaeluloth.com' +- title: NYC Pride 2019 | WorldPride NYC | Stonewall50 + main_url: 'https://2019-worldpride-stonewall50.nycpride.org/' + url: 'https://2019-worldpride-stonewall50.nycpride.org/' + featured: false + description: >- + Join us in 2019 for NYC Pride, as we welcome WorldPride and mark the 50th Anniversary of the Stonewall Uprising and a half-century of LGBTQ+ liberation. + categories: + - Education + - Marketing + - Nonprofit + built_by: Canvas United + built_by_url: 'https://www.canvasunited.com/' +- title: Spacetime + main_url: 'https://www.heyspacetime.com/' + url: 'https://www.heyspacetime.com/' + featured: false + description: >- + Spacetime is a Dallas-based digital experience agency specializing in web, app, startup, and digital experience creation. + categories: + - Marketing + - Portfolio + - Agency + - Creative + - Featured + built_by: Spacetime + built_by_url: 'https://www.heyspacetime.com/' +- title: Eric Jinks + main_url: 'https://ericjinks.com/' + url: 'https://ericjinks.com/' + featured: false + description: >- + Software engineer / web developer from the Gold Coast, Australia. + categories: + - Portfolio + - Blog + - Web Dev + - Technology + built_by: Eric Jinks + built_by_url: 'https://ericjinks.com/' +- title: GaiAma - We are wildlife + main_url: 'https://www.gaiama.org/' + url: 'https://www.gaiama.org/' + featured: false + description: >- + We founded the GaiAma conservation organization to protect wildlife in Perú and to create an example of a permaculture neighborhood, living symbiotically with the forest - because reforestation is just the beginning + categories: + - Nonprofit + - Marketing + - Blog + source_url: https://github.com/GaiAma/gaiama.org + built_by: GaiAma + built_by_url: 'https://www.gaiama.org/' +- title: Healthcare Logic + main_url: 'https://www.healthcarelogic.com/' + url: 'https://www.healthcarelogic.com/' + featured: false + description: >- + Revolutionary technology that empowers clinical and managerial leaders to collaborate with clarity. + categories: + - Marketing + - Healthcare + - Technology + built_by: Thrive + built_by_url: 'https://thriveweb.com.au/' +- title: Localgov.fyi + main_url: 'https://localgov.fyi/' + url: 'https://localgov.fyi/' + featured: false + description: >- + Finding local government services made easier. + categories: + - Directory + - Government + - Technology + source_url: https://github.com/WeOpenly/localgov.fyi + built_by: Openly + built_by_url: 'https://weopenly.com/' +- title: Kata.ai Documentation + main_url: 'https://docs.kata.ai/' + url: 'https://docs.kata.ai/' + source_url: https://github.com/kata-ai/kata-platform-docs + featured: false + description: >- + Documentation website for the Kata Platform, an all-in-one platform for + building chatbots using AI technologies. + categories: + - Documentation + - Technology +- title: goalgetters + main_url: 'https://goalgetters.space/' + url: 'https://goalgetters.space/' + featured: false + description: >- + goalgetters is a source of inspiration for people who want to change their career. + We offer articles, success stories and expert interviews on how to find a new passion and how to implement change. + categories: + - Blog + - Education + - Careers + - Personal Development + built_by: Stephanie Langers (content), Adrian Wenke (development) + built_by_url: 'https://twitter.com/AdrianWenke' +- title: Life Without Barriers | Foster Care + main_url: 'https://www.lwb.org.au/foster-care' + url: 'https://www.lwb.org.au/foster-care' + featured: false + description: >- + We are urgently seeking foster carers all across Australia. Can you open your heart and your home to a child in need? + There are different types of foster care that can suit you. We offer training and 24/7 support. + categories: + - Charity + - Nonprofit + - Education + - Documentation + - Marketing + built_by: LWB Digital Team + built_by_url: 'https://twitter.com/LWBAustralia' +- title: Bejamas - JAM Experts for hire + main_url: 'https://bejamas.io/' + url: 'https://bejamas.io/' + featured: false + description: >- + We help agencies and companies with JAMStack tools. This includes web development using Static Site Generators, Headless CMS, CI / CD and CDN setup. + categories: + - Technology + - Web Dev + - Agency + - Creative + - Marketing + built_by: Bejamas.io + built_by_url: 'https://bejamas.io/' +- title: Zensum + main_url: 'https://zensum.se/' + url: 'https://zensum.se/' + featured: false + description: >- + Borrow money quickly and safely through Zensum. We compare Sweden's leading banks and credit institutions. Choose from multiple offers and lower your monthly cost. [Translated from Swedish] + categories: + - Technology + - Finance + - Corporate + - Marketing + built_by: Bejamas.io + built_by_url: 'https://bejamas.io/' +- title: StatusHub - Easy to use Hosted Status Page Service + main_url: 'https://statushub.com/' + url: 'https://statushub.com/' + featured: false + description: >- + Set up your very own service status page in minutes with StatusHub. Allow customers to subscribe to be updated automatically. + categories: + - Technology + - Marketing + built_by: Bejamas.io + built_by_url: 'https://bejamas.io/' +- title: Matthias Kretschmann Portfolio + main_url: 'https://matthiaskretschmann.com/' + url: 'https://matthiaskretschmann.com/' + source_url: 'https://github.com/kremalicious/portfolio' + featured: false + description: >- + Portfolio of designer & developer Matthias Kretschmann. + categories: + - Portfolio + - Web Dev + - Creative + built_by: Matthias Kretschmann + built_by_url: 'https://matthiaskretschmann.com/' +- title: Cajun Bowfishing + main_url: 'https://cajunbowfishing.com/' + url: 'https://cajunbowfishing.com/' + featured: false + categories: + - eCommerce + - Sports + built_by: Escalade Sports + built_by_url: 'http://escaladesports.com/' +- title: Iron Cove Solutions + main_url: 'https://ironcovesolutions.com/' + url: 'https://ironcovesolutions.com/' + description: >- + Iron Cove Solutions is a cloud based consulting firm. We help companies deliver a return on cloud usage by applying best practices + categories: + - Technology + - Web Dev + built_by: Iron Cove Solutions + built_by_url: 'https://ironcovesolutions.com/' + featured: false +- title: Eventos orellana + description: >- + Somos una empresa dedicada a brindar asesoría personalizada + y profesional para la elaboración y coordinación de eventos + sociales y empresariales. + main_url: 'https://eventosorellana.com/' + url: 'https://eventosorellana.com/' + featured: false + categories: + - Gallery + built_by: Codedebug + built_by_url: 'https://codedebug.co/' +- title: Moetez Chaabene Portfolio / Blog + main_url: 'https://moetez.me/' + url: 'https://moetez.me/' + source_url: 'https://github.com/moetezch/moetez.me' + featured: false + description: >- + Portfolio of Moetez Chaabene + categories: + - Portfolio + - Web Dev + - Blog + built_by: Moetez Chaabene + built_by_url: 'https://twitter.com/moetezch' +- title: Nikita + description: >- + Automation of system deployments in Node.js for applications and infrastructures. + main_url: https://nikita.js.org/ + url: https://nikita.js.org/ + source_url: 'https://github.com/adaltas/node-nikita' + categories: + - Documentation + - Open Source + - Technology + built_by: David Worms + built_by_url: http://www.adaltas.com + featured: false +- title: Gourav Sood Blog & Portfolio + main_url: 'https://www.gouravsood.com/' + url: 'https://www.gouravsood.com/' + featured: false + categories: + - Blog + - Portfolio + - Salesforce + built_by: Gourav Sood + built_by_url: 'https://www.gouravsood.com/' +- title: Figma + main_url: 'https://www.figma.com/' + url: 'https://www.figma.com/' + featured: false + categories: + - Marketing + - Design + built_by: Corey Ward + built_by_url: http://www.coreyward.me/ +- title: Jonas Tebbe Portfolio + description: > + Hey, I’m Jonas and I create digital products. + main_url: https://jonastebbe.com + url: https://jonastebbe.com + categories: + - Portfolio + built_by: Jonas Tebbe + built_by_url: http://twitter.com/jonastebbe + featured: false +- title: Parker Sarsfield Portfolio + description: > + I'm Parker, a software engineer and sneakerhead. + main_url: https://parkersarsfield.com + url: https://parkersarsfield.com + categories: + - Blog + - Portfolio + built_by: Parker Sarsfield + built_by_url: https://parkersarsfield.com +- title: Front-end web development with Greg + description: > + JavaScript, GatsbyJS, ReactJS, CSS in JS... Let's learn some stuff together. + main_url: https://dev.greglobinski.com + url: https://dev.greglobinski.com + categories: + - Blog + - Web Dev + built_by: Greg Lobinski + built_by_url: https://github.com/greglobinski +- title: Insomnia + description: > + Desktop HTTP and GraphQL client for developers + main_url: https://insomnia.rest/ + url: https://insomnia.rest/ + categories: + - Landing + - Blog + built_by: Gregory Schier + built_by_url: https://schier.co + featured: false +- title: Timeline Theme Portfolio + description: > + I'm Aman Mittal, a software developer. + main_url: http://www.amanhimself.me/ + url: http://www.amanhimself.me/ + categories: + - Landing + - Web Dev + - Portfolio + built_by: Aman Mittal + built_by_url: http://www.amanhimself.me/ +- title: Ocean artUp + description: > + Science outreach site built using styled-components and Contentful. + It presents the research project "Ocean artUp" funded by an Advanced + Grant of the European Research Council to explore the possible + benefits of artificial uplift of nutrient-rich deep water to the + ocean’s sunlit surface layer. + main_url: https://ocean-artup.eu + url: https://ocean-artup.eu + source_url: https://github.com/janosh/ocean-artup + categories: + - Science + - Research + - Education + - Blog + built_by: Janosh Riebesell + built_by_url: https://janosh.io + featured: false +- title: Ryan Fitzgerald + description: > + Personal portfolio and blog for Ryan Fitzgerald + main_url: https://ryanfitzgerald.ca/ + url: https://ryanfitzgerald.ca/ + categories: + - Web Dev + - Portfolio + built_by: Ryan Fitzgerald + built_by_url: https://github.com/RyanFitzgerald + featured: false +- title: Kaizen + description: > + Content Marketing, PR & SEO Agency in London + main_url: https://www.kaizen.co.uk/ + url: https://www.kaizen.co.uk/ + categories: + - Agency + - Blog + - Creative + - Design + - Web Dev + - SEO + built_by: Bogdan Stanciu + built_by_url: https://github.com/b0gd4n + featured: false +- title: HackerOne Platform Documentation + description: > + HackerOne's Product Documentation Center! + url: https://docs.hackerone.com/ + main_url: https://docs.hackerone.com/ + categories: + - Documentation + - Security + featured: false +- title: Patreon Partners + description: > + Resources and products to help you do more with Patreon. + url: https://partners.patreon.com/ + main_url: https://partners.patreon.com/ + categories: + - Resources + - Directory + featured: false +- title: Bureau Of Meteorology (beta) + description: > + Help shape the future of Bureau services + url: https://beta.bom.gov.au/ + main_url: https://beta.bom.gov.au/ + categories: + - Meteorology + - Research + - Services + featured: false +- title: Curbside + description: > + Connecting Stores with Mobile Customers + main_url: https://curbside.com/ + url: https://curbside.com/ + categories: + - Mobile Commerce + - Software + featured: false +- title: Mux Video + description: > + API to video hosting and streaming + main_url: https://mux.com/ + url: https://mux.com/ + categories: + - Video + - Hosting + - Streaming + - API + featured: false +- title: Swapcard + description: > + The easiest way for event organizers to instantly connect people, build a community of attendees and exhibitors, and increase revenue over time + main_url: https://www.swapcard.com/ + url: https://www.swapcard.com/ + categories: + - Event + - Community + - Services + featured: false +- title: Kalix + description: > + Kalix is perfect for healthcare professionals starting out in private practice, to those with an established clinic. + main_url: https://www.kalixhealth.com/ + url: https://www.kalixhealth.com/ + categories: + - Scheduling + - Documentation + - Messaging + - Billing + - Services + featured: false +- title: Hubba + description: > + Buy wholesale products from thousands of independent, verified Brands. + main_url: https://join.hubba.com/ + url: https://join.hubba.com/ + categories: + - eCommerce + - Retail + featured: false +- title: Airbnb Engineering & Data Science + description: > + Creative engineers and data scientists building a world where you can belong anywhere + main_url: https://airbnb.io/ + url: https://airbnb.io/ + categories: + - Blog + - Gallery + - Projects + featured: false +- title: HyperPlay + description: > + In Asean's 1st Ever LOL Esports X Music Festival + main_url: https://hyperplay.leagueoflegends.com/ + url: https://hyperplay.leagueoflegends.com/ + categories: + - Landing + featured: false +- title: Bad Credit Loans + description: > + Get the funds you need, from $250-$5,000 + main_url: https://www.creditloan.com/ + url: https://www.creditloan.com/ + categories: + - Loans + - Credits + featured: false +- title: Financial Center + description: > + Member-owned, not-for-profit, co-operative whose members receive financial benefits in the form of lower loan rates, higher savings rates, and lower fees than banks. + main_url: https://fcfcu.com/ + url: https://fcfcu.com/ + categories: + - Loans + - Credits + - Online Banking + - Nonprofit + featured: false +- title: Open FDA + description: > + Provides APIs and raw download access to a number of high-value, high priority and scalable structured datasets, including adverse events, drug product labeling, and recall enforcement reports. + main_url: https://open.fda.gov/ + url: https://open.fda.gov/ + categories: + - API + - Datasets + - Reports + featured: false +- title: Office of Institutional Research and Assessment + description: > + Good Data, Good Decisions + main_url: http://oira.ua.edu/ + url: http://oira.ua.edu/ + categories: + - Research + - Data + featured: false +- title: GM Capital One + description: > + Introducing the new online experience for your GM Rewards Credit Card + main_url: https://gm.capitalone.com/ + url: https://gm.capitalone.com/ + categories: + - Rewards + - Credit Card + featured: false +- title: House Manager + description: > + Home service membership that offers proactive and on-demand maintenance for homeowners + main_url: https://housemanager.calstate.aaa.com/ + url: https://housemanager.calstate.aaa.com/ + categories: + - Home + - Services + featured: false +- title: Trintellix + description: > + It may help make a difference for your depression (MDD). + main_url: https://us.trintellix.com/ + url: https://us.trintellix.com/ + categories: + - Health + - Help + featured: false +- title: The Telegraph Premium + description: > + Exclusive stories from award-winning journalists + main_url: https://premium.telegraph.co.uk/ + url: https://premium.telegraph.co.uk/ + categories: + - Landing + - Newspaper + - Subscription + featured: false +- title: html2canvas + description: > + Screenshots with JavaScript + main_url: http://html2canvas.hertzen.com/ + url: http://html2canvas.hertzen.com/ + source_url: https://github.com/niklasvh/html2canvas/tree/master/www + categories: + - Tool + - Screenshots + - JavaScript + - Documentation + built_by: Niklas von Hertzen + built_by_url: http://hertzen.com/ + featured: false +- title: The State of JavaScript 2017 + description: > + Data from over 20,000 developers, asking them questions on topics ranging from front-end frameworks and state management, to build tools and testing libraries. + main_url: https://stateofjs.com/ + url: https://stateofjs.com/ + source_url: https://github.com/StateOfJS/StateOfJS + categories: + - Data + - JavaScript + - Statistics + built_by: StateOfJS + built_by_url: https://github.com/StateOfJS/StateOfJS/graphs/contributors + featured: false +- title: Dato CMS + description: > + The API-based CMS your editors will love + main_url: https://www.datocms.com/ + url: https://www.datocms.com/ + categories: + - CMS + - API + - Tool + featured: false +- title: Half Electronics + description: > + Personal website + main_url: https://www.halfelectronic.com/ + url: https://www.halfelectronic.com/ + categories: + - Blog + - Electronics + built_by: Fernando Poumian + built_by_url: https://github.com/fpoumian/halfelectronic.com + featured: false +- title: Frithir Software Development + main_url: 'https://frithir.com/' + url: 'https://frithir.com/' + featured: false + description: >- + I DRINK COFFEE, WRITE CODE AND IMPROVE MY DEVELOPMENT SKILLS EVERY DAY. + categories: + - Creative + - Design + - Web Dev + built_by: Frithir + built_by_url: 'https://Frithir.com/' +- title: Unow + main_url: https://www.unow.fr/ + url: https://www.unow.fr/ + categories: + - Education + - Corporate + - Marketing + featured: false +- title: Peter Hironaka + description: > + Freelance Web Developer based in Los Angeles. + main_url: https://peterhironaka.com/ + url: https://peterhironaka.com/ + categories: + - Portfolio + - Web Dev + built_by: Peter Hironaka + built_by_url: https://github.com/PHironaka + featured: false +- title: Michael McQuade + description: > + Personal website and blog for Michael McQuade + main_url: https://giraffesyo.io + url: https://giraffesyo.io + categories: + - Blog + built_by: Michael McQuade + built_by_url: https://github.com/giraffesyo + featured: false +- title: Haacht Brewery + description: > + Corporate wesbite for Haacht Brewery. Designed and Developed by Gafas. + main_url: https://haacht.com/en/ + url: https://haacht.com + categories: + - Corporate + built_by: Gafas + built_by_url: https://gafas.be + featured: false +- title: StoutLabs + description: > + Portfolio of Daniel Stout, freelance web dev in East Tennessee. + main_url: https://www.stoutlabs.com/ + url: https://www.stoutlabs.com/ + categories: + - Web Dev + - Portfolio + built_by: Daniel Stout + built_by_url: https://github.com/stoutlabs + featured: false +- title: Chicago Ticket Outcomes By Neighborhood + description: > + ProPublica data visualization of traffic ticket court outcomes + categories: + - News + - Nonprofit + - Visualization + url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-status/?initialWidth=782 + built_by: David Eads + built_by_url: https://github.com/eads + featured: false +- title: Chicago South Side Traffic Ticketing rates + description: > + ProPublica data visualization of traffic ticket rates by community + url: https://projects.propublica.org/graphics/il/il-city-sticker-tickets-maps/ticket-rate/?initialWidth=782 + categories: + - News + - Nonprofit + - Visualization + built_by: David Eads + built_by_url: https://github.com/eads + featured: false +- title: Otsimo + description: > + Otsimo is a special education application for children with autism, down syndrome and other developmental disabilities. + main_url: https://otsimo.com/en/ + url: https://otsimo.com/en/ + categories: + - Landing + - Blog + - Education + featured: false +- title: Matt Bagni Portfolio 2018 + description: > + Mostly the result of playing with Gatsby and learning about react and graphql. Using the screenshot plugin to showcase the work done for my company in the last 2 years, and a good amount of other experiments. + main_url: https://mattbag.github.io + url: https://mattbag.github.io + categories: + - Landing + - Portfolio + featured: false +- title: Lisa Ye's Blog + description: > + Simple blog/portofolio for a fashion designer. Gatsby_v2 + Netlify cms + main_url: https://lisaye.netlify.com/ + url: https://lisaye.netlify.com/ + categories: + - Blog + - Portfolio + - Personal + - Fashion + featured: false +- title: Lifestone Church + main_url: 'https://www.lifestonechurch.net/' + url: 'https://www.lifestonechurch.net/' + source_url: 'https://github.com/lifestonechurch/lifestonechurch.net' + featured: false + categories: + - Marketing + - Nonprofit +- title: Artem Sapegin + description: > + Little homepage of Artem Sapegin, a frontend developer, passionate photographer, coffee drinker and crazy dogs’ owner. + main_url: https://sapegin.me/ + url: https://sapegin.me/ + categories: + - Landing + - Portfolio + - Open Source + - Personal + - Web Dev + built_by: Artem Sapegin + built_by_url: https://github.com/sapegin + featured: false +- title: SparkPost Developers + main_url: 'https://developers.sparkpost.com/' + url: 'https://developers.sparkpost.com/' + source_url: 'https://github.com/SparkPost/developers.sparkpost.com' + categories: + - Documentation + - API + featured: false +- title: Malik Browne Portfolio 2018 + description: > + The portfolio blog of Malik Browne, a full stack engineer, foodie, and avid blogger/YouTuber. + main_url: https://www.malikbrowne.com/about + url: https://www.malikbrowne.com + categories: + - Blog + - Portfolio + built_by: Malik Browne + built_by_url: https://twitter.com/milkstarz + featured: false +- title: Novatics + description: > + Digital products that inspire and make a difference + main_url: https://www.novatics.com.br + url: https://www.novatics.com.br + categories: + - Landing + - Portfolio + - Technology + - Digital Products + - Web Dev + built_by: Novatics + built_by_url: https://github.com/Novatics + featured: false +- title: I migliori by Vivigratis + description: > + Product review website + main_url: https://imigliori.vivigratis.com/ + url: https://imigliori.vivigratis.com/ + categories: + - Blog + - Travel + - Technology + - Wellness + - Fashion + built_by: Kframe Interactive SA + featured: false +- title: Max McKinney + description: > + I’m a developer and designer with a focus in web technologies. I build cars on the side. + main_url: https://maxmckinney.com/ + url: https://maxmckinney.com/ + categories: + - Portfolio + - Web Dev + - Design + - Landing + - Personal + built_by: Max McKinney + featured: false +- title: Stickyard + description: > + Make your React component sticky the easy way + main_url: https://nihgwu.github.io/stickyard/ + url: https://nihgwu.github.io/stickyard/ + source_url: https://github.com/nihgwu/stickyard/tree/master/website + categories: + - Web Dev + built_by: Neo Nie + featured: false +- title: Agata Milik + description: > + Website of a Polish psychologist/psychotherapist based in Gdańsk, Poland. + main_url: https://agatamilik.pl + url: https://agatamilik.pl + categories: + - Landing + - Marketing + - Healthcare + built_by: Piotr Fedorczyk + built_by_url: https://piotrf.pl + featured: false +- title: WebPurple + main_url: 'https://www.webpurple.net/' + url: 'https://www.webpurple.net/' + source_url: 'https://github.com/WebPurple/site' + description: >- + Site of local (Russia, Ryazan) frontend community. Main purpose is to show info about meetups and keep blog. + categories: + - Nonprofit + - Web Dev + - Community + - Blog + - Open Source + built_by: Nikita Kirsanov + built_by_url: 'https://twitter.com/kitos_kirsanov' + featured: false +- title: Papertrail.io + description: > + Inspection Management for the 21st Century + main_url: https://www.papertrail.io/ + url: https://www.papertrail.io/ + categories: + - Marketing + - Technology + - Landing + built_by: Papertrail.io + built_by_url: https://www.papertrail.io + featured: false +- title: Matt Ferderer + main_url: https://mattferderer.com + url: https://mattferderer.com + source_url: https://github.com/mattferderer/gatsbyblog + description: > + {titleofthesite} is a blog built with Gatsby that discusses web related tech such as JavaScript, .NET, Blazor & security. + categories: + - Blog + - Web Dev + - Personal + built_by: Matt Ferderer + built_by_url: https://twitter.com/mattferderer + featured: false +- title: Sahyadri Open Source Community + main_url: https://sosc.org.in + url: https://sosc.org.in + source_url: https://github.com/haxzie/sosc-website + description: > + Official website of Sahyadri Open Source Community for community blog, event details and members info. + categories: + - Blog + - Community + - Open Source + built_by: Musthaq Ahamad + built_by_url: https://github.com/haxzie + featured: false +- title: Tech Confessions + main_url: https://confessions.tech + url: https://confessions.tech + source_url: https://github.com/JonathanSpeek/tech-confessions + description: > + A guilt-free place for us to confess our tech sins 🙏 + categories: + - Community + - Open Source + built_by: Jonathan Speek + built_by_url: https://speek.design + featured: false +- title: Thibault Maekelbergh + main_url: https://thibmaek.com + url: https://thibmaek.com + source_url: https://github.com/thibmaek/thibmaek.github.io + description: > + A nice blog about development, Raspberry Pi, plants and probably records. + categories: + - Blog + - Open Source + built_by: Thibault Maekelbergh + built_by_url: https://twitter.com/thibmaek + featured: false +- title: LearnReact.design + main_url: https://learnreact.design + url: https://learnreact.design + description: > + React Essentials For Designers: A React course tailored for product designers, ux designers, ui designers. + categories: + - Blog + - Landing + - Creative + built_by: Linton Ye + built_by_url: https://twitter.com/lintonye +- title: DesignSystems.com + main_url: https://www.designsystems.com/ + url: https://www.designsystems.com/ + description: > + A resource for learning, creating and evangelizing design systems. + categories: + - Design + - Blog + - Technology + built_by: Corey Ward + built_by_url: http://www.coreyward.me/ + featured: false +- title: Devol’s Dance + main_url: https://www.devolsdance.com/ + url: https://www.devolsdance.com/ + description: > + Devol’s Dance is an invite-only, one-day event celebrating industrial robotics, AI, and automation. + categories: + - Marketing + - Landing + - Technology + built_by: Corey Ward + built_by_url: http://www.coreyward.me/ + featured: false +- title: Mega House Creative + main_url: https://www.megahousecreative.com/ + url: https://www.megahousecreative.com/ + description: > + Mega House Creative is a digital agency that provides unique goal-oriented web marketing solutions. + categories: + - Marketing + - Agency + built_by: Daniel Robinson + featured: false +- title: Tobie Marier Robitaille - csc + main_url: https://tobiemarierrobitaille.com/ + url: https://tobiemarierrobitaille.com/en/ + description: > + Portfolio site for director of photography Tobie Marier Robitaille + categories: + - Portfolio + - Gallery + built_by: Mill3 Studio + built_by_url: https://mill3.studio/en/ + featured: false +- title: Bestvideogame.deals + main_url: https://bestvideogame.deals/ + url: https://bestvideogame.deals/ + description: > + Video game comparison website for the UK, build with GatsbyJS. + categories: + - eCommerce + - Video Games + built_by: Koen Kamphuis + built_by_url: https://koenkamphuis.com/ + featured: false +- title: Mahipat's Portfolio + main_url: https://mojaave.com/ + url: https://mojaave.com + source_url: https://github.com/mhjadav/mojaave + description: > + mojaave.com is Mahipat's portfolio, I have developed it using Gatsby v2 and Bootstrap, To get in touch with people looking for full stack developer. + categories: + - Portfolio + - Software + - Web Dev + - Personal + built_by: Mahipat Jadav + built_by_url: https://mojaave.com/ + featured: false +- title: Cmsbased + main_url: https://www.cmsbased.net + url: https://www.cmsbased.net + description: > + Cmsbased is providing automation tools and design resources for Web Hosting and IT services industry. + categories: + - Technology + - Design + - Digital Products + featured: false +- title: Traffic Design Biennale 2018 + main_url: https://trafficdesign.pl + url: http://trafficdesign.pl/pl/ficzery/2018-biennale/ + description: > + Mini site for 2018 edition of Traffic Design’s Biennale + categories: + - Landing + - Creative + - Nonprofit + - Gallery + built_by: Piotr Fedorczyk + built_by_url: https://piotrf.pl + featured: false +- title: Timely + main_url: https://timelyapp.com/ + url: https://timelyapp.com/ + description: > + Fully automatic time tracking. For those who trade in time. + categories: + - Software + - Time Tracking + - Tool + built_by: Timm Stokke + built_by_url: https://timm.stokke.me + featured: false +- title: Stitch Fix + main_url: https://www.stitchfix.com/ + url: https://www.stitchfix.com/ + description: > + Stitch Fix is an online styling service that delivers a truly personalized shopping experience. + categories: + - eCommerce + - Clothing + - Fashion + featured: false +- title: Snap Kit + main_url: https://kit.snapchat.com/ + url: https://kit.snapchat.com/ + description: > + Snap Kit lets developers integrate some of Snapchat’s best features across platforms. + categories: + - Technology + - Tool + - Software + - Documentation + featured: false +- title: Insights + main_url: https://justaskusers.com/ + url: https://justaskusers.com/ + description: > + Insights helps user experience (UX) researchers conduct their research and make sense of the findings. + categories: + - User Experience + - Research + - Design + built_by: Just Ask Users + built_by_url: https://justaskusers.com/ + featured: false +- title: Tensiq + main_url: https://tensiq.com + url: https://tensiq.com + source_url: https://github.com/Tensiq/tensiq-site + description: > + Tensiq is an e-Residency startup, that provides development in cutting-edge technology while delivering secure, resilient, performant solutions. + categories: + - Game Dev + - Web Dev + - Mobile Dev + - Agency + - Open Source + built_by: Jens + built_by_url: https://github.com/arrkiin + featured: false +- title: SendGrid + main_url: https://sendgrid.com/docs/ + url: https://sendgrid.com/docs/ + description: > + SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform. + categories: + - API + - Technology + - Tool + - Software + - Documentation + featured: false +- title: Mintfort + main_url: https://mintfort.com/ + url: https://mintfort.com/ + source_url: https://github.com/MintFort/mintfort.com + description: > + Mintfort, the first crypto-friendly bank account. Store and manage assets on the blockchain. + categories: + - Technology + - Tool + - Bank + - Software + built_by: Axel Fuhrmann + built_by_url: https://axelfuhrmann.com/ + featured: false +- title: React Native Explorer + main_url: https://react-native-explorer.firebaseapp.com + url: https://react-native-explorer.firebaseapp.com + description: > + Explorer React Native packages and examples effortlessly. + categories: + - React Native + - Software + - Tool + featured: false +- title: 500Tech + main_url: 'https://500tech.com/' + url: 'https://500tech.com/' + featured: false + categories: + - Web Dev + - Agency + - Open Source + - Featured From f2497f999223be8a44727987dc59e3dd35e3a13e Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Mon, 3 Sep 2018 17:24:11 +1000 Subject: [PATCH 33/39] add graph for how query execution works --- .../docs/behind-the-scenes-query-execution.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/docs/behind-the-scenes-query-execution.md b/docs/docs/behind-the-scenes-query-execution.md index ed1bf296e0557..4d2d3a73e1197 100644 --- a/docs/docs/behind-the-scenes-query-execution.md +++ b/docs/docs/behind-the-scenes-query-execution.md @@ -10,6 +10,65 @@ Query Execution is kicked off by bootstrap by calling [page-query-runner.js runI - [query-queue.js](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) - [query-runner.js](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js) +Here's an overview of how it all relates: + +```dot +digraph { + compound = true; + + subgraph cluster_other { + style = invis; + extractQueries [ label = "query-watcher.js", shape = box ]; + componentsDD [ label = "componentDataDependencies\l(redux)", shape = cylinder ]; + components [ label = "components\l (redux)", shape = cylinder ]; + createNode [ label = "CREATE_NODE action", shape = box ]; + } + + subgraph cluster_pageQueryRunner { + label = "page-query-runner.js" + + dirtyActions [ label = "dirtyActions", shape = cylinder ]; + extractedQueryQ [ label = "queueQueryForPathname()", shape = box ]; + findIdsWithoutDD [ label = "findIdsWithoutDataDependencies()", shape = box ]; + findDirtyActions [ label = "findDirtyActions()", shape = box ]; + queryJobs [ label = "runQueriesForPathnames()", shape = box ]; + + extractedQueryQ -> queryJobs; + findIdsWithoutDD -> queryJobs; + dirtyActions -> findDirtyActions [ weight = 100 ]; + findDirtyActions -> queryJobs; + } + + subgraph cluster_queryQueue { + label = "query-queue.js"; + queryQ [ label = "better-queue", shape = box ]; + } + + subgraph cluster_queryRunner { + label = "query-runner.js" + queryRunner [ label = "default" ]; + graphqlJs [ label = "graphqlJs(schema, query, context, ...)" ]; + result [ label = "Query Result" ]; + diskResult [ label = "/public/static/d/${dataPath}", shape = cylinder ]; + jsonDataPaths [ label = "jsonDataPaths\l(redux)", shape = cylinder ]; + + queryRunner -> graphqlJs; + graphqlJs -> result; + result -> diskResult; + result -> jsonDataPaths; + } + + extractQueries -> extractedQueryQ; + componentsDD -> findIdsWithoutDD; + components -> findIdsWithoutDD; + createNode -> dirtyActions; + + queryJobs -> queryQ [ lhead = cluster_queryQueue ]; + + queryQ -> queryRunner [ lhead = cluster_queryRunner ]; +} +``` + #### Figuring out which queries need to be executed The first thing this query does is figure out what queries even need to be run. You would think this would simply be a matter of running the Queries that were enqueued in [Extract Queries](/docs/behind-the-scenes-query-extraction/), but matters are complicated by support for `gatsby develop`. Below is the logic for figuring out which queries need to be executed (code is in [runQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L36)). From 27d9b65bbdafa5a97023e697441a2b0d90e9995d Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 4 Sep 2018 13:34:35 +1000 Subject: [PATCH 34/39] added query extraction graphs --- .../behind-the-scenes-query-extraction.md | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/docs/docs/behind-the-scenes-query-extraction.md b/docs/docs/behind-the-scenes-query-extraction.md index 60c997298cbba..c74df8041e08e 100644 --- a/docs/docs/behind-the-scenes-query-extraction.md +++ b/docs/docs/behind-the-scenes-query-extraction.md @@ -20,24 +20,31 @@ digraph { fragments [ label = "fragments. e.g\l.cache/fragments/fragment1.js", shape = cylinder ]; srcFiles [ label = "source files. e.g\lsrc/pages/my-page.js", shape = cylinder ]; components [ label = "redux.state.components\l(via createPage)", shape = cylinder ]; - fileQueries [ label = "files with queries", shape = box ]; - babylon [ label = "parse files with babylon\lfilter those with queries" ]; - queryAst [ label = "QueryASTs", shape = box ]; schema [ label = "Gatsby schema", shape = cylinder ]; - relayCompiler [ label = "Relay Compiler" ]; - queries [ label = "{ Queries | { filePath | query } }", shape = record ]; - query [ label = "{\l name: filePath,\l text: rawQueryText,\l originalText: original text from file,\l path: filePath,\l isStaticQuery: if it is,\l hash: hash of query\l}\l ", shape = box ]; + subgraph cluster_compiler { + label = "query-compiler.js"; + fileQueries [ label = "files containing queries", shape = box ]; + babylon [ label = "parse files with babylon\lfilter those with queries" ]; + queryAst [ label = "QueryASTs", shape = box ]; + relayCompiler [ label = "Relay Compiler" ]; + queries [ label = "{ Queries | { filePath | query } }", shape = record ]; + query [ label = "{\l name: filePath,\l text: rawQueryText,\l originalText: original text from file,\l path: filePath,\l isStaticQuery: if it is,\l hash: hash of query\l}\l ", shape = box ]; + + } - fragments -> fileQueries; - srcFiles -> fileQueries; - components -> fileQueries; fileQueries -> babylon; babylon -> queryAst; queryAst -> relayCompiler; - schema -> relayCompiler; relayCompiler -> queries; queries:query -> query; + fragments -> fileQueries; + srcFiles -> fileQueries; + components -> fileQueries; + schema -> relayCompiler; + + fragments -> srcFiles [ style = invis ]; + fragments -> components [ style = invis ]; } ``` @@ -49,9 +56,55 @@ If the query is a `StaticQuery`, we call the `replaceStaticQuery` action to save If the query is just a normal every day query (not StaticQuery), then we update its component's `query` in the redux `components` namespace via the `replaceComponentQuery` action. +```dot +digraph { + compound = true; + + compiler [ label = "query-compiler.js" ]; + + subgraph cluster_watcher { + label = "query-watcher.js:handleQuery()" + query [ label = "{\l name: filePath,\l text: rawQueryText,\l originalText: original text from file,\l path: filePath,\l isStaticQuery: if it is,\l hash: hash of query\l}\l ", shape = box ]; + replaceStaticQuery [ label = "replaceStaticQuery()" ]; + staticQueryComponents [ label = "staticQueryComponents\l (redux)", shape = cylinder ]; + replaceComponentQuery [ label = "replaceComponentQuery()" ]; + components [ label = "components\l (redux)", shape = cylinder ]; + + query -> replaceStaticQuery [ label = "if static query" ]; + query -> replaceComponentQuery [ label = "if not static" ]; + replaceStaticQuery -> staticQueryComponents; + replaceComponentQuery -> components [ label = "set `query` attribute" ]; + } + + compiler -> query [ label = "for each compiled query", lhead = cluster_watcher ]; +} +``` + + #### Queue for execution Now that we've saved our query, we're ready to queue it for execution. Query execution is mainly handled by [page-query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js), so we accomplish this by passing the component's path to `queueQueryForPathname` function. -Now let's learn about [Query Execution](/docs/behind-the-scenes-query-execution/). +```dot +digraph { + compound = true; + compiler [ label = "query-compiler.js" ]; + + subgraph cluster_watcher { + label = "query-watcher.js:handleQuery()" + query [ label = "{\l name: filePath,\l text: rawQueryText,\l originalText: original text from file,\l path: filePath,\l isStaticQuery: if it is,\l hash: hash of query\l}\l ", shape = box ]; + + } + + subgraph cluster_pageQueryRunner { + label = "page-query-runner.js" + queueQueryForPathname [ label = "queueQueryForPathname()" ]; + } + + compiler -> query [ label = "for each compiled query", lhead = cluster_watcher ]; + query -> queueQueryForPathname [ label = "queue for execution" ]; +} +``` + +Now let's learn about [Query Execution](/docs/behind-the-scenes-query-execution/). From 20f6d5bd9c75f0eb1dde517b8c4368853c916c24 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Tue, 4 Sep 2018 16:47:05 +1000 Subject: [PATCH 35/39] Added File Type stuff --- docs/docs/behind-the-scenes-terminology.md | 142 ++++++++++++++++++- docs/docs/node-creation-behind-the-scenes.md | 4 + docs/docs/schema-gql-type.md | 43 +++++- docs/docs/schema-sift.md | 2 +- 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/docs/docs/behind-the-scenes-terminology.md b/docs/docs/behind-the-scenes-terminology.md index 7802cb4fa4050..f484fd491c865 100644 --- a/docs/docs/behind-the-scenes-terminology.md +++ b/docs/docs/behind-the-scenes-terminology.md @@ -1,6 +1,101 @@ -## Terminology +--- +title: Terminology +--- -Read up on [page creation](/docs/page-creation/) first. +Throughout the Gatsby code, you'll see the below object fields and variables mentioned. Their definitions and reason for existence are define below. + +## Page + +### Page Object + +created by calls to [createPage](/docs/actions/#createPage) + +- path +- matchPath +- jsonName +- component +- componentChunkName +- internalComponentName (unused) +- context +- updatedAt + +The above fields are explained below + +### path + +The publicly accessible path in the web URL to access the page in question. E.g + +`/blog/2018-07-17-announcing-gatsby-preview/`. + +It is created when the page object is created (see [Page Creation](/docs/page-creation/)) + +### Redux `pages` namespace + +Contains a map of Page path -> page + +### matchPath + +Think of this instead as `client matchPath`. It is ignored when creating pages during the build. But on the frontend, when resolving the page from the path ([find-path.js]()), it is used (via [reach router](https://github.com/reach/router/blob/master/src/lib/utils.js)) to find the matching page. Note that the [pages are sorted](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/pages-writer.js#L38) so that those with matchPaths are at the end, so that explicit paths are matched first. + +This is also used by [gatsby-plugin-create-client-paths](/packages/gatsby-plugin-create-client-paths/?=client). It duplicates pages whose path match some client-only prefix (e.g `/app/`). The duplicated page has a `matchPath` so that it is resolved first on the front end. + +It is also used by [gatsby-plugin-netlify](http://localhost:8000/packages/gatsby-plugin-netlify/?=netlify) when creating `_redirects`. + +### jsonName + +The logical name for the query result of a page. Created during `createPage`. Name is constructed using kebabHash of page path. E.g. For above pagePath, it is: + +`blog-2018-07-17-announcing-gatsby-preview-995` + +### component + +The path on disk to the javascript file containing the React component. E.g + +`/src/templates/template-blog-post.js` + +This of this as `componentPath` instead. + +### Redux `components ` namespace + +Mapping from `component` (path) to its Page. It is created every time a page is created (by listening to `CREATE_PAGE`). + +```javascript +{ + `/src/templates/template-blog-post.js`: { + query: ``, + path: `/blog/2018-07-17-announcing-gatsby-preview/`, + jsonName: `blog-2018-07-17-announcing-gatsby-preview-995`, + componentPath: `/src/templates/template-blog-post.js`, + ...restOfPage + } +} +``` + +Query starts off as empty, but is set during the extractQueries phase by [query-watcher/handleQuery](TODO), once the query has compiled by relay (see [Query behind the scenes](TODO)) + +### componentChunkName + +The page.component, but passed (as above), kebab hashed. E.g, the componentChunkName for component + +`/src/templates/template-blog-post.js` + +is + +`component---src-templates-template-blog-post-js` + +TODO: Mention how used by webpack + +### internalComponentName + +If the path is `/`, internalComponentName = `ComponentIndex`. Otherwise, for a path of `/blog/foo`, it would be `ComponentBlogFoo`. + +Created as part of page, but appears to be unused. + +### page.context + +This is [merged with the page itself](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L153) and then is [passed to graphql](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js#L40) queries as the `context` parameter. + +## Query ### dataPath @@ -80,4 +175,45 @@ export const pageQuery = graphql` ` ``` -TODO: Consider Creating a standalone terminology page +## Webpack stuff + +### /public/${componentChunkName}-[chunkhash].js + +The final webpack js bundle for the blog template page + +E.g + +`/public/component---src-templates-template-blog-post-js-2df3a086e8d2cdf690aa.js` + +### /.cache/async-requires.js + +Generated javascript file that exports `components` and `data` fields. + +`components` is a mapping from `componentChunkName` to a function that imports the component's original source file path. This is used for code splitting. The import statement is a hint to webpack that that javascript file can be loaded later. The mapping And also provides a hint to the `componentChunkName` + +`data` is a function that imports `/.cache/data.json`. Which is code split in the same way + +E.g + +```js +exports.components = { + "component---src-templates-template-blog-post-js": () => + import("/Users/amarcar/dev/gatsbyjs/gatsby/www/src/templates/template-blog-post.js" /* webpackChunkName: "component---src-templates-template-blog-post-js" */), +} + +exports.data = () => + import("/Users/amarcar/dev/gatsbyjs/gatsby/www/.cache/data.json") +``` + +### .cache/data.json + +During the `pagesWriter` bootstrap phase (last phase), `pages-writer.js` writes this file to disk. It contains `dataPaths` and `pages`. + +`dataPaths` is the same as the definition above. + +`pages` is a dump of the redux `pages` component state. Each page contains: + +- componentChunkName +- jsonName +- path + diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation-behind-the-scenes.md index e41aceb04635e..79137fa4ff4d8 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation-behind-the-scenes.md @@ -68,3 +68,7 @@ Any nodes that aren't touched by the end of the `source-nodes` phase, are delete From a site developer's point of view, nodes are immutable. In the sense that if you simply change a node object, those changes will not be seen by other parts of Gatsby. To make a change to a node, it must be persisted to redux via an action. So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you want to add a transformer specific field? You can call [createNodeField]() and this will simply add your field to the node's `node.fields` object and then persists it to redux. This can then be referenced by other parts of your plugin at later stages of the build. + +## Node Tracking + +When a node is created, `createNode` will track all its fields against its nodeId. See [Node Tracking Docs](/docs/behind-the-scenes-dependencies/#root-node-tracking) for more. diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index 9ef19415aa933..f1c6c3ccf9431 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -112,7 +112,7 @@ The core of this step creates a GraphQL Field object, where the type is inferred If however, the value is an object or array, we recurse, using [inferObjectStructureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L317) to create the GraphQL fields. -in addition, if a value is a string that actually points to a file, then we hand off field inference to [types/file-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js) which creates a new type that resolves File-like strings to File types. The same goes for Strings that look like dates. These are handled by [types/date-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-date.js) which creates a `GraphQLDate` custom type. +In addition, Gatsby creates custom GraphQL types for `File` ([types/type-file.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)) and `Date` ([types/type-date.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)). If the value of our field is a string that looks like a filename or a date (handled by [should-infer](TODO) functions), then we return the appropariate custom type. ### Child/Parent fields @@ -163,3 +163,44 @@ When a node is created as a child of some node, that fact is stored as a `parent ### Plugin fields These are fields created by plugins that implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. These plugins return full GraphQL Field declarations, complete with type and resolve functions. + +### File types + +As described in [plain object or value field](#plain-object-or-value-field), if a string field value looks like a file path, then we infer `File` as the field's type. The creation of this type occurs in [type-file.js setFileNodeRootType()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js#L18). It is called just after we have created the GqlType for `File` (only called once). + +It creates a new GraphQL Field Config whose type is the just created `File` GqlType, and whose resolver converts a string into a File object. Here's how it works: + +Say we have a `data/posts.json` file that has been sourced (of type `File`), and then the [gatsby-transformer-json](/packages/gatsby-transformer-json) transformer creates a child node (of type `PostsJson`) + +```javascript +// data/posts.json +[ + { + "id": "1685001452849004065", + "text": "Venice is 👌", + "image": "images/BdiU-TTFP4h.jpg", + } +] +``` + +Notice that the image value looks like a file. Therefore, we'd like to query it as if it were a file, and get its relativePath, accessTime, etc. + +```graphql +{ + postsJson( id: { eq: "1685001452849004065" } ) { + image { + relativePath, + accessTime + } + } +} +``` + +The [File type resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js#L135) takes care of this. It gets the value (`images/BdiU-TTFP4h.jpg`). It then looks up this node's root NodeID via [node-tracking](TODO) which returns the original `data/posts.json` file. It creates a new filename by concatenating the field value onto the parent node's directory. + +I.e `data` + `images/BdiU-TTFP4h.jpg` = `data/images/BdiU-TTFP4h.jpg`. + +And then finally it searches redux for the first `File` node whose path matches this one. This is our proper resolved node. We're done! + + + diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 3d613a30d812e..15420dc274a4b 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -100,7 +100,7 @@ Note that the graphql-js library has NOT been invoked yet. We're instead calling The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). -After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. +After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. Since new fields on the node may have been created in this process, we call `trackInlineObjectsInRootNode()` to track these new objects. See [Node Tracking Docs](/docs/behind-the-scenes-dependencies/#root-node-tracking) for more. ### 5. Run sift query on all nodes From 2c7d5124cc16d50b0a4d3676090b103e8730f8a4 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Wed, 5 Sep 2018 13:39:14 +1000 Subject: [PATCH 36/39] typos --- docs/docs/behind-the-scenes.md | 2 +- docs/docs/schema-generation-behind-the-scenes.md | 8 ++++---- docs/docs/schema-gql-type.md | 6 +++--- docs/docs/schema-sift.md | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/docs/behind-the-scenes.md b/docs/docs/behind-the-scenes.md index 594e4ec167710..e4e3c4312d246 100644 --- a/docs/docs/behind-the-scenes.md +++ b/docs/docs/behind-the-scenes.md @@ -2,7 +2,7 @@ title: Behind the Scenes --- -Curious how Gatsby works under the hood? This pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works, or perhaps you're a plugin author and need to understand how core works to track down a bug? Come one, come all! +Curious how Gatsby works under the hood? The pages in this section describe how a Gatsby build works from an internal code/architecture point of view. It should be useful for anyone who needs to work on the internals of Gatsby, or for those who are simply curious how it all works, or perhaps you're a plugin author and need to understand how core works to track down a bug? Come one, come all! If you're looking for information on how to _use_ Gatsby to write your own site, or create a plugin, check out the rest of the Gatsby docs. This section is quite low level. diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation-behind-the-scenes.md index 184b87c1488d4..e53c67833f412 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation-behind-the-scenes.md @@ -10,17 +10,17 @@ Each sourced or transformed node has a `node.internal.type`, which is set by the During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. -The flow is summarized by the below graph. It shows the intermediate transformations that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNoteType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. +The flow is summarized by the below graph. It shows the intermediate transformations that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. ```dot digraph graphname { "pluginFields" [ label = "custom plugin fields\l{\l publicURL: {\l type: GraphQLString,\l resolve(file, a, c) { ... }\l }\l}\l ", shape = box ]; "typeNodes" [ label = "all redux nodes of type\le.g internal.type === `File`", shape = "box" ]; - "exampleValue" [ label = "exampleValue\l{\l relativePath: `bogs/my-blog.md`,\l accessTime: 8292387234\l}\l ", shape = "box" ]; + "exampleValue" [ label = "exampleValue\l{\l relativePath: `blogs/my-blog.md`,\l accessTime: 8292387234\l}\l ", shape = "box" ]; "resolve" [ label = "ProcessedNodeType.resolve()", shape = box ]; "gqlType" [ label = "gqlType (GraphQLObjectType)\l{\l fields,\l name: `File`\l}\l ", shape = box ]; "parentChild" [ label = "Parent/Children fields\lnode {\l childMarkdownRemark { html }\l parent { id }\l}\l ", shape = "box" ]; - "objectFields" [ label = "Object node fields\l node {\l relativePath,\l accessTime }\l }\l ", shape = "box" ]; + "objectFields" [ label = "Object node fields\l node {\l relativePath,\l accessTime\l}\l ", shape = "box" ]; "inputFilters" [ label = "InputFilters\lfile({\l relativePath: {\l eq: `blogs/my-blog.md`\l }\l})\l ", shape = box ] "pluginFields" -> "inputFilters"; @@ -49,7 +49,7 @@ To declare custom fields, plugins implement the [setFieldsOnGraphQLNodeType](/do #### 2. Create a "GQLType" -This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 2). This step is explained in detail in [GraphQL Node Types Creation](/docs/schema-gql-type). +This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 1). This step is explained in detail in [GraphQL Node Types Creation](/docs/schema-gql-type). #### 3. Create Input filters diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index f1c6c3ccf9431..cd1db462cf86d 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -96,7 +96,7 @@ Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which #### Foreign Key reference (`___NODE`) -If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is a foreign key reference to another node in redux. Check out the [Create a Source Plugin](/docs/create-source-plugin/#create-source-plugin) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L204). +If not a mapping field, it might instead end in `___NODE`, signifying that its value is an ID that is a foreign key reference to another node in redux. Check out [Create a Source Plugin](/docs/create-source-plugin/#create-source-plugin) for how this works from a user point of view. Behind the scenes, the field inference is handled by [inferFromFieldName](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L204). This is actually quite similar to the mapping case above. We remove the `___NODE` part of the field name. E.g `author___NODE` would become `author`. Then, we find our `linkedNode`. I.e given the example value for `author` (which would be an ID), we find its actual node in redux. Then, we find its type in processed types by its `internal.type`. Note, that also like in mapping fields, we can define the `linkedField` too. This can be specified via `nodeFieldname___NODE___linkedFieldName`. E.g for `author___NODE___name`, the linkedField would be `name` instead of `id`. @@ -152,13 +152,13 @@ query { } ``` -To resolve `file.childMarkdownRemark`, we filter over all the node's (that we're resolving) `children`, until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. +To resolve `file.childMarkdownRemark`, we take the node we're resolving, and filter over all of its `children` until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. But before we return from the resolve function, remember that we might be running this query within the context of a page. If that's the case, then whenever the node changes, the page will need to be rerendered. To record that fact, we call call [createPageDependency](TODO) with the node ID and the page, which is a field in the `context` object in the resolve function signature. #### parent field -When a node is created as a child of some node, that fact is stored as a `parent` field on the redux node whose value is the ID of the parent. The parent GraphQL field resolver simply looks up the resolving node's `parent` field, and then looks up the parent in the redux `nodes` namespace map. It also creates a [page dependency](TODO have this in own section), as is done by child resolvers. +When a node is created as a child of some node (parent), that fact is stored in the child's `parent` field. The value of which is the ID of the parent. The [GraphQL resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L57) for this field looks up the parent by that ID in redux and returns it. It also creates a [page dependency](TODO have this in own section), to record that the page being queried depends on the parent node. ### Plugin fields diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 15420dc274a4b..0ddfa9c709d83 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -16,7 +16,7 @@ Remember, at the point this resolve function is created, we have been iterating The `resolve()` function calls `run-sift.js`, and provides it with the following arguments: -- GraphQLArgs (as js object). Within a filter. E.g `wordcount: { paragraphs: { eg: 4 } }` +- GraphQLArgs (as js object). Within a filter. E.g `wordcount: { paragraphs: { eq: 4 } }` - All nodes in redux of this type. E.g where `internal.type == MmarkdownRemark'` - Context `path`, if present - typeName. E.g `markdownRemark` @@ -30,7 +30,7 @@ runSift({ filter: { // Exact args from GraphQL Query wordcount: { paragraphs: { - eg: 4 + eq: 4 } } } @@ -59,7 +59,7 @@ Sift expects all field names to be prepended by a `$`. The [siftify-args](https: - field key is`elemMatch`? Change to `$elemMatch`. Recurse on value object - field value is regex? Apply regex cleaning -- field value is globl, use [minimatch](https://www.npmjs.com/package/minimatch) library to convert to Regex +- field value is glob, use [minimatch](https://www.npmjs.com/package/minimatch) library to convert to Regex - normal value, prepend `$` to field name. So, the above query would become: @@ -68,7 +68,7 @@ So, the above query would become: { `$wordcount`: { `$paragraphs`: { - `$eg`: 4 + `$eq`: 4 } } } From 4be9afd35da4ea727cf169f9d3c2ad0de9fb5c72 Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 6 Sep 2018 11:34:43 +1000 Subject: [PATCH 37/39] More behind the scenes docs --- docs/docs/behind-the-scenes-terminology.md | 36 +++++----- docs/docs/how-plugins-apis-are-run.md | 8 +-- ...-behind-the-scenes.md => node-creation.md} | 10 +-- docs/docs/node-tracking.md | 62 +++++++++++++++++ docs/docs/page-creation.md | 9 +++ docs/docs/page-node-dependencies.md | 66 +++++++++++++++++++ docs/docs/query-behind-the-scenes.md | 8 +-- ...-query-execution.md => query-execution.md} | 30 ++++----- ...uery-extraction.md => query-extraction.md} | 10 +-- docs/docs/schema-connections.md | 6 +- ...ind-the-scenes.md => schema-generation.md} | 11 +++- docs/docs/schema-gql-type.md | 42 ++++++------ docs/docs/schema-sift.md | 18 +++-- ...queries.md => static-vs-normal-queries.md} | 0 www/src/data/sidebars/doc-links.yaml | 18 +++-- 15 files changed, 242 insertions(+), 92 deletions(-) rename docs/docs/{node-creation-behind-the-scenes.md => node-creation.md} (87%) create mode 100644 docs/docs/node-tracking.md create mode 100644 docs/docs/page-creation.md create mode 100644 docs/docs/page-node-dependencies.md rename docs/docs/{behind-the-scenes-query-execution.md => query-execution.md} (70%) rename docs/docs/{behind-the-scenes-query-extraction.md => query-extraction.md} (80%) rename docs/docs/{schema-generation-behind-the-scenes.md => schema-generation.md} (82%) rename docs/docs/{behind-the-scenes-static-vs-normal-queries.md => static-vs-normal-queries.md} (100%) diff --git a/docs/docs/behind-the-scenes-terminology.md b/docs/docs/behind-the-scenes-terminology.md index f484fd491c865..60cdb950b1c03 100644 --- a/docs/docs/behind-the-scenes-terminology.md +++ b/docs/docs/behind-the-scenes-terminology.md @@ -2,21 +2,21 @@ title: Terminology --- -Throughout the Gatsby code, you'll see the below object fields and variables mentioned. Their definitions and reason for existence are define below. +Throughout the Gatsby code, you'll see the below object fields and variables mentioned. Their definitions and reason for existence are defined below. ## Page ### Page Object -created by calls to [createPage](/docs/actions/#createPage) +created by calls to [createPage](/docs/actions/#createPage) (see [Page Creation](/docs/page-creation)). -- path -- matchPath -- jsonName -- component -- componentChunkName -- internalComponentName (unused) -- context +- [path](#path) +- [matchPath](#matchpath) +- [jsonName](#jsonname) +- [component](#component) +- [componentChunkName](#componentchunkname) +- [internalComponentName](#internalcomponentname) (unused) +- [context](#pagecontext) - updatedAt The above fields are explained below @@ -31,7 +31,7 @@ It is created when the page object is created (see [Page Creation](/docs/page-cr ### Redux `pages` namespace -Contains a map of Page path -> page +Contains a map of Page [path](#path) -> [Page object](#page-object). ### matchPath @@ -43,7 +43,7 @@ It is also used by [gatsby-plugin-netlify](http://localhost:8000/packages/gatsby ### jsonName -The logical name for the query result of a page. Created during `createPage`. Name is constructed using kebabHash of page path. E.g. For above pagePath, it is: +The logical name for the query result of a page. Created during [createPage](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions.js#L229). Name is constructed using kebabHash of page path. E.g. For above pagePath, it is: `blog-2018-07-17-announcing-gatsby-preview-995` @@ -53,11 +53,11 @@ The path on disk to the javascript file containing the React component. E.g `/src/templates/template-blog-post.js` -This of this as `componentPath` instead. +Think of this as `componentPath` instead. ### Redux `components ` namespace -Mapping from `component` (path) to its Page. It is created every time a page is created (by listening to `CREATE_PAGE`). +Mapping from `component` (path on disk) to its [Page object](#page-object). It is created every time a page is created (by listening to `CREATE_PAGE`). ```javascript { @@ -71,11 +71,11 @@ Mapping from `component` (path) to its Page. It is created every time a page is } ``` -Query starts off as empty, but is set during the extractQueries phase by [query-watcher/handleQuery](TODO), once the query has compiled by relay (see [Query behind the scenes](TODO)) +Query starts off as empty, but is set during the extractQueries phase by [query-watcher/handleQuery](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js#L68), once the query has compiled by relay (see [Query Extraction](/docs/query-extraction/)). ### componentChunkName -The page.component, but passed (as above), kebab hashed. E.g, the componentChunkName for component +The [page.component](#component) (path on disk), but passed (as above), kebab hashed. E.g, the componentChunkName for component `/src/templates/template-blog-post.js` @@ -89,7 +89,7 @@ TODO: Mention how used by webpack If the path is `/`, internalComponentName = `ComponentIndex`. Otherwise, for a path of `/blog/foo`, it would be `ComponentBlogFoo`. -Created as part of page, but appears to be unused. +Created as part of page, but currently unused. ### page.context @@ -103,9 +103,11 @@ Path to the page's query result. Relative to `/public/static/d/{modInt}`. Name i `621/path---blog-2018-07-17-announcing-gatsby-preview-995-a74-dwfQIanOJGe2gi27a9CLKHjamc` +Set after [Query Execution](/docs/query-execution/#save-query-results-to-redux-and-disk) has finished. + ### Redux `jsonDataPaths` namespace (dataPaths) -Map of page `jsonName` to `dataPath`. Updated whenever a new query is run (in [query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js)). e.g +Map of page [jsonName](#jsonname) to [dataPath](#datapath). Updated after [Query Execution](/docs/query-execution/#save-query-results-to-redux-and-disk). E.g ``` { diff --git a/docs/docs/how-plugins-apis-are-run.md b/docs/docs/how-plugins-apis-are-run.md index 72d5f4ea8cda3..640f0d7da2b82 100644 --- a/docs/docs/how-plugins-apis-are-run.md +++ b/docs/docs/how-plugins-apis-are-run.md @@ -32,7 +32,7 @@ Some API calls can take a while to finish. So every time an API is run, we creat - **resolve**: promise resolve callback to be called when the API has finished running - **startTime**: time that the API run was started - **span**: opentracing span for tracing builds -- **traceId**: optional args.traceId provided if API will result in further API calls (see below) +- **traceId**: optional args.traceId provided if API will result in further API calls ([see below](#using-traceid-to-await-downstream-api-calls)) We immediately place this object into an `apisRunningById` Map, where we track its execution. @@ -49,7 +49,7 @@ All actions take 3 arguments: 1. The core information required by the action. E.g for [createNode](/docs/actions/#createNode), we must pass a node 2. The plugin that is calling this action. E.g `createNode` uses this to assign the owner of the new node 3. An object with misc action options: - - **traceId**: See below + - **traceId**: [See below](#using-traceid-to-await-downstream-api-calls) - **parentSpan**: opentracing span (see [tracing docs](/docs/performance-tracing/)) Passing the plugin and action options on every single action call would be extremely painful for plugin/site authors. Since we know the plugin, traceId and parentSpan when we're running our API, we can rebind injected actions so these arguments are already provided. This is done in the [doubleBind](https://github.com/gatsbyjs/gatsby/blob/8029c6647ab38792bb0a7c135ab4b98ae70a2627/packages/gatsby/src/utils/api-runner-node.js#L14) step. @@ -60,6 +60,8 @@ Each plugin is run inside a [map-series](https://www.npmjs.com/package/map-serie ## Using traceID to await downstream API calls +The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes](/docs/node-apis/#sourceNodes)) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. + ```dot digraph { node [ shape="box" ]; @@ -90,8 +92,6 @@ digraph { } ``` -The majority of API calls result in one or more implementing plugins being called. We then wait for them all to complete, and return. But some plugins (e.g [sourceNodes](/docs/node-apis/#sourceNodes)) result in calls to actions that themselves call APIs. We need some way of tracing whether an API call originated from another API call, so that we can wait on all child calls to complete. The mechanism for this is the `traceId`. - 1. The traceID is passed as an argument to the original API runner. E.g ```javascript diff --git a/docs/docs/node-creation-behind-the-scenes.md b/docs/docs/node-creation.md similarity index 87% rename from docs/docs/node-creation-behind-the-scenes.md rename to docs/docs/node-creation.md index 79137fa4ff4d8..5720d5e5de989 100644 --- a/docs/docs/node-creation-behind-the-scenes.md +++ b/docs/docs/node-creation.md @@ -16,7 +16,7 @@ There are a few different scenarios for creating parent/child relationships. ### Node relationship storage model -Gatsby stores child nodes in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves. E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: +All nodes in Gatsby are stored in a flat structure in the redux `nodes` namespace. A node's `children` field is an array of node IDS, whose nodes are also at the top level of the redux namespace. Here's an example of the `nodes` namespace. ```javascript { @@ -26,7 +26,7 @@ Gatsby stores child nodes in redux as IDs in the parent's `children` field. And } ``` -An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. This has some implications on [child field inference](/docs/schema-gql-type/#child-fields-creation) in the Schema Generation phase. ### Explicitly recording a parent/child relationship @@ -51,7 +51,9 @@ Let's say you create the following node by passing it to `createNode` } ``` -The value for `baz` is itself an object. That value's parent is the top level object. In this case, Gatsby simply saves the top level node as is to redux. It doesn't attempt to extract `baz` into its own node. During schema compilation, Gatsby will infer the sub object's type while [creating the gqlType](/docs/schema-gql-type#plain-object-or-value-field). +The value for `baz` is itself an object. That value's parent is the top level object. In this case, Gatsby simply saves the top level node as is to redux. It doesn't attempt to extract `baz` into its own node. It does however track the subobject's root NodeID using [Node Tracking](/docs/node-tracking/) + +During schema compilation, Gatsby will infer the sub object's type while [creating the gqlType](/docs/schema-gql-type#plain-object-or-value-field). ## Fresh/stale nodes @@ -71,4 +73,4 @@ So, how do you add a field to an existing node? E.g perhaps in onCreateNode, you ## Node Tracking -When a node is created, `createNode` will track all its fields against its nodeId. See [Node Tracking Docs](/docs/behind-the-scenes-dependencies/#root-node-tracking) for more. +When a node is created, `createNode` will track all its fields against its nodeId. See [Node Tracking Docs](/docs/node-tracking/) for more. diff --git a/docs/docs/node-tracking.md b/docs/docs/node-tracking.md new file mode 100644 index 0000000000000..ee9e7b3a7317d --- /dev/null +++ b/docs/docs/node-tracking.md @@ -0,0 +1,62 @@ +--- +title: Node Tracking +--- + +## Track Nodes + +You may see calls to `trackInlineObjectsInRootNode()` and `findRootNodeAncestor()` in some parts of the code. These are both defined in [schema/node-tracking.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/node-tracking.js). Node tracking is the tracking of relationships between a node's object values (not children), and the node's ID. E.g Take, the following node: + +```javascript +let nodeA = { + id: `id2`, + internal: { + type: `footype` + }, + foo: { + myfile: "blog/my-blog.md", + b: 2 + }, + bar: 7, + parent: `id1`, + baz: [ { x: 8 }, 9 ] +} +``` + +Its sub objects are `foo` (value = `{ myfile: "blog/my-blog.md", b: 2}`), and those in the `baz` array (`{ x: 8 }`). Node tracking will track those back to the top level node's ID (`id2` in this case). The [trackInlineObjectsinRootNode()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/node-tracking.js#L32) function takes care of this and records those relationships in the [rootNodeMap](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/node-tracking.js#L9) WeakMap. E.g after calling `trackInlineObjectsInRootNode(nodeA)`, `rootNodeMap` would contain the following records: + +```javascript +// rootNodeMap: +{ + { blog: "blog/my-blog.md", b: 2 } => "id2", // from `foo` field + { x: 8 } => "id2", // from `baz` array + { // top level object is tracked too + id: `id2`, + internal: { // internal is not mapped + type: `footype` + }, + foo: { + blog: "blog/my-blog.md", + b: 2 + }, + bar: 7, + parent: `id1`, + baz: [ { x: 8 }, 9 ] + } => "id2" +} +``` + +## Find Root Nodes + +To access this information, `node-tracking.js` provides the [findRootNodeAncestor()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/node-tracking.js#L52) function. It takes an object, and looks up its parent's nodeID in `rootNodeMap`. It then finds the actual node in redux. It then gets that node's `parent` ID, and gets the parent node from redux. And continues in this way until the root node is found. + +In the above example, `nodeA` has parent `id1`. So `findRootNodeAncestor({ blog: "blog/my-blog.md", b: 2 })` would return the node for `id1` (the parent). + +## Why/Where? + +Where is node-tracking used? First up, nodes are tracked in 2 places. Firstly, in [createNode](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions.js#L539), every time a node is created, we link all its sub objects to the new NodeID. Nodes are also tracked whenever they are resolved in [run-sift](/docs/schema-sift/#3-resolve-inner-query-fields-on-all-nodes). This is necessary because [custom plugin fields](/docs/schema-input-gql/#inferring-input-filters-from-plugin-fields/) might return new objects that weren't created when the node was initially made. + +Now, where do we use this information? In 2 places. + +1. In the `File` type resolver. It is used to lookup the node's root, which should be of type `File`. We can then use that root node's base directory attribute to create the full path of the resolved field's value, and therefore find the actual `File` node that the string value is desribing. See [File GqlType inference](http://localhost:8000/docs/schema-gql-type/#file-types) for more info. +1. To recursively look up node descriptions in [type-conflict-reporter.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/type-conflict-reporter.js) + diff --git a/docs/docs/page-creation.md b/docs/docs/page-creation.md new file mode 100644 index 0000000000000..dee975bec7c19 --- /dev/null +++ b/docs/docs/page-creation.md @@ -0,0 +1,9 @@ +--- +title: Page Creation +--- + +This is a stub. Help our community expand it. + +Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your +pull request gets accepted. + diff --git a/docs/docs/page-node-dependencies.md b/docs/docs/page-node-dependencies.md new file mode 100644 index 0000000000000..f00d1a3c53c3b --- /dev/null +++ b/docs/docs/page-node-dependencies.md @@ -0,0 +1,66 @@ +--- +title: Page -> Node Dependency Tracking +--- + +In almost every GraphQL Resolver, you'll see the [createPageDependency](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions.js#L788), or [getNodeAndSavePathDependency](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/index.js#L198) functions. These are responsible for recording which nodes are dependended on by which pages. In `gatsby develop` mode, if a node's content changes, we re-run pages whose queries depend on that node. This is one of the things that makes `gatsby develop` so awesome. + +## How dependencies are recorded + +Recording of Page -> Node dependencies are handled by the [createPageDependency](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions.js#L788) action. It takes the page (in the form of its `path`), and either a `nodeId`, or `connection`. + +If a `nodeId` is passed, we're telling Gatsby that the page depends specifically on this node. So, if the node is changed, then the page's query needs to be re-executed. + +`connection` is a Type string. E.g `MarkdownRemark`, or `File`. If `createPageDependency` is called with a page path and a `connection`, we are telling Gatsby that this page depends on all nodes of this type. Therefore if any node of this type changes (e.g a change to a markdown node), then this page must be rebuilt. This variant is only called from [run-sift.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L264) when we're running a query such as `allFile`, or `allMarkdownRemark`. See [Schema Connections](/docs/schema-connections/) for more info. + +## How dependencies are stored + +Page -> Node dependencies are tracked via the `componentDataDependencies` redux namespace. `createPageDependency` is the only way to mutate it. The namespace is comprised of two sub structures: + +```javascript +{ + nodes: { ... }, // mapping nodeId -> pages + connections: { ... } // mapping of type names -> pages +} +``` + +**Nodes** is a map of nodeID to the set of pages that depend on that node. E.g + +```javascript +// state.componentDataDependencies.nodes +{ + `ID of Some MarkdownRemark node`: [ + `blogs/my-blog1`, + `blogs/my-blog2` + ], + `otherId`: [ `more pages`, ...]. + ... +} +``` + +**Connections** is a map of type name to the set of pages that depend on that type. e.g + +```javascript +// state.componentDataDependencies.connections +{ + `MarkdownRemark`: [ + `blogs/my-blog1`, + `blogs/my-blog2` + ], + `File`: [ `more pages`, ... ], + ... +} +``` + +## How dependency information is used + +Page -> Node dependencies are used entirely during query execution to figure out which nodes are "dirty", and therefore which page's queries need to be re-executed. This occurs in `page-query-runner.js` in the [findIdsWithoutDataDependencies](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L89) and [findDirtyIds](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L171) functions. This is described in greater detail in the [Query Execution](http://localhost:8000/docs/query-execution/) docs. + +## Other forms + +### add-page-dependency.js + +[redux/actions/add-page-dependency.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions/add-page-dependency.js) is a wrapper around the `createPageDependency` action that performs some additional performance optimizations. It should be used instead of the raw action. + +### getNodeAndSavePathDependency action + +The [getNodeAndSavePathDependency](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/index.js#L198) action simply calls `getNode`, and then calls `createPageDependency` using that result. It is a programmer convenience. diff --git a/docs/docs/query-behind-the-scenes.md b/docs/docs/query-behind-the-scenes.md index f375024b16d4d..ef078cf2b729f 100644 --- a/docs/docs/query-behind-the-scenes.md +++ b/docs/docs/query-behind-the-scenes.md @@ -2,16 +2,12 @@ title: How Queries Work --- -## How Queries work in Gatsby - We're talking about GraphQL queries here. These can be tagged graphql expressions at the bottom of your component source file (e.g [query for Gatsby frontpage](https://github.com/gatsbyjs/gatsby/blob/master/www/src/pages/index.js#L225)), StaticQueries within your components (e.g [showcase site details](https://github.com/gatsbyjs/gatsby/blob/master/www/src/components/showcase-details.js#L103)), or fragments created by plugins (e.g [gatsby-source-contentful](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-contentful/src/fragments.js)). Note that we are NOT talking about queries involved in the creation of your pages, which is usually performed in your site's gatsby-node.js (e.g [Gatby's website](https://github.com/gatsbyjs/gatsby/blob/master/www/gatsby-node.js#L85)). We're only talking about queries that are tied to particular pages or templates. Almost all logic to do with queries is in the internal-plugin [query-runner](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/query-runner). There are two steps involved in a Query's life time. The first is extracting it, and the second is running it. These are separated into two bootstrap phases. -1. [Query Extraction](/docs/behind-the-scenes-query-extraction/) -2. [Query Execution](/docs/behind-the-scenes-query-execution/) - -TODO: Diagram of query queue etc. +1. [Query Extraction](/docs/query-extraction/) +2. [Query Execution](/docs/query-execution/) diff --git a/docs/docs/behind-the-scenes-query-execution.md b/docs/docs/query-execution.md similarity index 70% rename from docs/docs/behind-the-scenes-query-execution.md rename to docs/docs/query-execution.md index 4d2d3a73e1197..0145bd3d16657 100644 --- a/docs/docs/behind-the-scenes-query-execution.md +++ b/docs/docs/query-execution.md @@ -46,18 +46,18 @@ digraph { subgraph cluster_queryRunner { label = "query-runner.js" - queryRunner [ label = "default" ]; graphqlJs [ label = "graphqlJs(schema, query, context, ...)" ]; result [ label = "Query Result" ]; - diskResult [ label = "/public/static/d/${dataPath}", shape = cylinder ]; - jsonDataPaths [ label = "jsonDataPaths\l(redux)", shape = cylinder ]; - queryRunner -> graphqlJs; graphqlJs -> result; - result -> diskResult; - result -> jsonDataPaths; } + diskResult [ label = "/public/static/d/${dataPath}", shape = cylinder ]; + jsonDataPaths [ label = "jsonDataPaths\l(redux)", shape = cylinder ]; + + result -> diskResult; + result -> jsonDataPaths; + extractQueries -> extractedQueryQ; componentsDD -> findIdsWithoutDD; components -> findIdsWithoutDD; @@ -65,21 +65,21 @@ digraph { queryJobs -> queryQ [ lhead = cluster_queryQueue ]; - queryQ -> queryRunner [ lhead = cluster_queryRunner ]; + queryQ -> graphqlJs [ lhead = cluster_queryRunner ]; } ``` #### Figuring out which queries need to be executed -The first thing this query does is figure out what queries even need to be run. You would think this would simply be a matter of running the Queries that were enqueued in [Extract Queries](/docs/behind-the-scenes-query-extraction/), but matters are complicated by support for `gatsby develop`. Below is the logic for figuring out which queries need to be executed (code is in [runQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L36)). +The first thing this query does is figure out what queries even need to be run. You would think this would simply be a matter of running the Queries that were enqueued in [Extract Queries](/docs/query-extraction/), but matters are complicated by support for `gatsby develop`. Below is the logic for figuring out which queries need to be executed (code is in [runQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L36)). ##### Already queued queries -All queries queued after being extracted. +All queries queued after being extracted (from `query-watcher.js`). ##### Queries without node dependencies -All queries whose component path isn't listed in `componentDataDependencies`. As a recap, in [Schema Generation](/docs/schema-generation-behind-the-scenes/), we showed that all Type resolvers record a dependency between the page whose query we're running and any nodes that were successfully resolved. So, If a component is declared in the `components` redux namespace, but is *not* contained in `componentDataDependencies`, then by definition, the query has not been run. Therefore we need to run it. Checkout [Node/Page Dependencies](http://localhost:8000/docs/behind-the-scenes-dependencies/) for more info. The code for this step is in [findIdsWithoutDataDependencies](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L89). +All queries whose component path isn't listed in `componentDataDependencies`. As a recap, in [Schema Generation](/docs/schema-generation/), we showed that all Type resolvers record a dependency between the page whose query we're running and any nodes that were successfully resolved. So, If a component is declared in the `components` redux namespace (occurs during [Page Creation](/docs/page-creation/)), but is *not* contained in `componentDataDependencies`, then by definition, the query has not been run. Therefore we need to run it. Checkout [Page -> Node Dependencies](/docs/page-node-dependencies/) for more info. The code for this step is in [findIdsWithoutDataDependencies](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/page-query-runner.js#L89). ##### Pages that depend on dirty nodes @@ -109,15 +109,15 @@ This Query Job contains everything we need to execute the query (and do things l #### Query Queue Execution -[query-queue.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) creates a [better-queue](https://www.npmjs.com/package/better-queue) queue that offers advanced features parallel execution, which is handy since querys do not depend on each other so we can take advantage of this. Every time an item is consumed from the queue, we call [query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js) where we finally actually execute the query! +[query-queue.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-queue.js) creates a [better-queue](https://www.npmjs.com/package/better-queue) queue that offers advanced features like parallel execution, which is handy since querys do not depend on each other so we can take advantage of this. Every time an item is consumed from the queue, we call [query-runner.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-runner.js) where we finally actually execute the query! Query execution involves calling the [graphql-js](https://graphql.org/graphql-js/) library with 3 pieces of information: -1. The Gatsby schema that was inferred during [Schema Generation](/docs/schema-generation-behind-the-scenes/). -1. The raw query text. Obtained from the Query Job -1. The Context, also from the Query Job. Has the page's `path` amongst other things. +1. The Gatsby schema that was inferred during [Schema Generation](/docs/schema-generation/). +1. The raw query text. Obtained from the Query Job. +1. The Context, also from the Query Job. Has the page's `path` amongst other things so that we can record [Page -> Node Dependencies](/docs/page-node-dependencies/). -Graphql-js will parse the query, and execute the top level query. E.g `allMarkdownRemark( limit: 10 )` or `file( relativePath: { eq: "blog/my-blog.md" } )`. These will invoke the resolvers defined in [Schema Connections](/docs/schema-connections/) or [GQL Type](/docs/schema-gql-type/), which both use sift to query over all nodes of the type in redux. The result will be passed through the inner part of the graphql query where each type's resolver will be invoked. The vast majority of these will be `identity` functions that just return the field value. Some however could call a [custom plugin field](/docs/schema-gql-type/#plugin-fields) resolver. These in turn might perform side effects such as generating images. This is why the query execution phase of bootstrap often takes the longest. +Graphql-js will parse the query, and executes the top level query. E.g `allMarkdownRemark( limit: 10 )` or `file( relativePath: { eq: "blog/my-blog.md" } )`. These will invoke the resolvers defined in [Schema Connections](/docs/schema-connections/) or [GQL Type](/docs/schema-gql-type/), which both use sift to query over all nodes of the type in redux. The result will be passed through the inner part of the graphql query where each type's resolver will be invoked. The vast majority of these will be `identity` functions that just return the field value. Some however could call a [custom plugin field](/docs/schema-gql-type/#plugin-fields) resolver. These in turn might perform side effects such as generating images. This is why the query execution phase of bootstrap often takes the longest. Finally, a result is returned. diff --git a/docs/docs/behind-the-scenes-query-extraction.md b/docs/docs/query-extraction.md similarity index 80% rename from docs/docs/behind-the-scenes-query-extraction.md rename to docs/docs/query-extraction.md index c74df8041e08e..32e68ed23dfd6 100644 --- a/docs/docs/behind-the-scenes-query-extraction.md +++ b/docs/docs/query-extraction.md @@ -4,7 +4,7 @@ title: Query Extraction ### Extracting Queries from Files -Up until now, we have [sourced all nodes](/docs/node-creation-behind-the-scenes/) into redux, [inferred a schema](/docs/schema-generation-behind-the-scenes/) from them, and [created all pages](/docs/page-creation/). The next step is to extract and compile all graphql queries from our source files. The entrypoint to this phase is [query-watcher extractQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js), which immediately compiles all graphql queries by calling into [query-compiler.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js). +Up until now, we have [sourced all nodes](/docs/node-creation/) into redux, [inferred a schema](/docs/schema-generation/) from them, and [created all pages](/docs/page-creation/). The next step is to extract and compile all graphql queries from our source files. The entrypoint to this phase is [query-watcher extractQueries()](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js), which immediately compiles all graphql queries by calling into [query-compiler.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-compiler.js). #### Query Compilation @@ -20,7 +20,7 @@ digraph { fragments [ label = "fragments. e.g\l.cache/fragments/fragment1.js", shape = cylinder ]; srcFiles [ label = "source files. e.g\lsrc/pages/my-page.js", shape = cylinder ]; components [ label = "redux.state.components\l(via createPage)", shape = cylinder ]; - schema [ label = "Gatsby schema", shape = cylinder ]; + schema [ label = "Gatsby schema", shape = cylinder, URL = "/docs/schema-generation/" ]; subgraph cluster_compiler { label = "query-compiler.js"; @@ -52,9 +52,9 @@ digraph { We're now in the [handleQuery](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/internal-plugins/query-runner/query-watcher.js#L68) function. -If the query is a `StaticQuery`, we call the `replaceStaticQuery` action to save it to to the `staticQueryComponents` namespace which is a mapping from a component's path to an object that contains the raw GraphQL Query amonst other things. More details in [Static Queries](/docs/behind-the-scenes-static-vs-normal-queries/). We also remove component's `jsonName` from the `components` redux namespace. See [Component/Page dependencies](/docs/behind-the-scenes-dependencies/). +If the query is a `StaticQuery`, we call the `replaceStaticQuery` action to save it to to the `staticQueryComponents` namespace which is a mapping from a component's path to an object that contains the raw GraphQL Query amonst other things. More details in [Static Queries](/docs/static-vs-normal-queries/). We also remove component's `jsonName` from the `components` redux namespace. See [Page -> Node Dependencies](/docs/page-node-dependencies/). -If the query is just a normal every day query (not StaticQuery), then we update its component's `query` in the redux `components` namespace via the `replaceComponentQuery` action. +If the query is just a normal every day query (not StaticQuery), then we update its component's `query` in the redux `components` namespace via the [replaceComponentQuery](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/actions.js#L827) action. ```dot digraph { @@ -107,4 +107,4 @@ digraph { } ``` -Now let's learn about [Query Execution](/docs/behind-the-scenes-query-execution/). +Now let's learn about [Query Execution](/docs/query-execution/). diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md index d5dde2fa40cd2..fbc9d9a06638c 100644 --- a/docs/docs/schema-connections.md +++ b/docs/docs/schema-connections.md @@ -4,7 +4,7 @@ title: Schema connections ## What are schema connections? -So far in schema generation, we have covered how [GraphQL types are inferred](/docs/schema-gql-type), how [query arguments for types](/docs/schema-input-gql) are created, and how [sift resolvers](/docs/schema-sift) work. But all of these only allow querying down to a single node of a type. Schema connections is the ability to query over collections of nodes of a type. For example, if we want to query all markdown nodes by some criteria, it will allow us to write queries such as: +So far in schema generation, we have covered how [GraphQL types are inferred](/docs/schema-gql-type), how [query arguments for types](/docs/schema-input-gql) are created, and how [sift resolvers](/docs/schema-sift) work. But all of these only allow querying down to a single node of a type. Schema connections is the ability to query over **collections** of nodes of a type. For example, if we want to query all markdown nodes by some criteria, it will allow us to write queries such as: ```graphql { @@ -28,7 +28,7 @@ _Fun Fact: This stuff is all based on [relay connections](https://facebook.githu The ConnectionType also defines input args to perform paging using the `skip/limit` pattern. The actual logic for paging is defined in the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library in [arrayconnection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/arrayconnection.js). It is invoked as the last part of the [run-sift](/docs/schema-sift#5-run-sift-query-on-all-nodes) function. To aid in paging, the ConnectionType also defines a `pageInfo` field with a `hasNextPage` field. -The ConnectionType is defined in the [graphql-skip-limit connection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/connection.js) file. Its construction function takes a Type, and it creates a connectionType for that type. E.g passing in `MarkdownRemark` Type would result in a `MarkdownRemarkConnection` type whose `edges` field would be of type `MarkdownRemarkEdge`. +The ConnectionType is defined in the [graphql-skip-limit connection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/connection.js) file. Its construction function takes a Type, and uses it to create a connectionType. E.g passing in `MarkdownRemark` Type would result in a `MarkdownRemarkConnection` type whose `edges` field would be of type `MarkdownRemarkEdge`. ### GroupConnection @@ -88,7 +88,7 @@ Just like in [gql type input filters](/docs/schema-input-gql), we must generate ### Sorting -A `sort` argument can be added to the `Connection` type (not the `GroupConnection` type). You can sort by any (possibly nested( field in the connection results. These are enums that are created via the same mechanism described in [enum fields](#field-enum-value). Except that the inference of these enums occurs in [infer-graphql-input-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L302). +A `sort` argument can be added to the `Connection` type (not the `GroupConnection` type). You can sort by any (possibly nested) field in the connection results. These are enums that are created via the same mechanism described in [enum fields](#field-enum-value). Except that the inference of these enums occurs in [infer-graphql-input-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L302). The Sort Input Type itself is created in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L49) and implemented by [create-sort-field.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/create-sort-field.js). The actual sorting occurs in run-sift (below). diff --git a/docs/docs/schema-generation-behind-the-scenes.md b/docs/docs/schema-generation.md similarity index 82% rename from docs/docs/schema-generation-behind-the-scenes.md rename to docs/docs/schema-generation.md index e53c67833f412..9c81c1a34096a 100644 --- a/docs/docs/schema-generation-behind-the-scenes.md +++ b/docs/docs/schema-generation.md @@ -10,14 +10,14 @@ Each sourced or transformed node has a `node.internal.type`, which is set by the During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. -The flow is summarized by the below graph. It shows the intermediate transformations that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. +The flow is summarized by the below graph. It shows the intermediate transformations or relevant parts of the user's graphql query that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. ```dot digraph graphname { "pluginFields" [ label = "custom plugin fields\l{\l publicURL: {\l type: GraphQLString,\l resolve(file, a, c) { ... }\l }\l}\l ", shape = box ]; "typeNodes" [ label = "all redux nodes of type\le.g internal.type === `File`", shape = "box" ]; "exampleValue" [ label = "exampleValue\l{\l relativePath: `blogs/my-blog.md`,\l accessTime: 8292387234\l}\l ", shape = "box" ]; - "resolve" [ label = "ProcessedNodeType.resolve()", shape = box ]; + "resolve" [ label = "ProcessedNodeType\l including final resolve()", shape = box ]; "gqlType" [ label = "gqlType (GraphQLObjectType)\l{\l fields,\l name: `File`\l}\l ", shape = box ]; "parentChild" [ label = "Parent/Children fields\lnode {\l childMarkdownRemark { html }\l parent { id }\l}\l ", shape = "box" ]; "objectFields" [ label = "Object node fields\l node {\l relativePath,\l accessTime\l}\l ", shape = "box" ]; @@ -39,7 +39,7 @@ digraph graphname { ### For each unique Type -The majority of schema generation code occurs in [build-node-types.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js). The below steps will be executed for each unique type. +The majority of schema generation code kicks off in [build-node-types.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js). The below steps will be executed for each unique type. #### 1. Plugins create custom fields @@ -58,3 +58,8 @@ This step creates GraphQL input filters for each field so the objects can be que #### 4. ProcessedTypeNode creation with resolve implementation Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the input filters and gqlType created above, and implements a resolve function for it using sift. More detail in the [Querying with Sift](/docs/schema-sift) section. + +#### 5. Create Connections for each type + +We've inferred all GraphQL Types, and the ability to query for a single node. But now we need to be able to query for collections of that type (e.g `allMarkdownRemark`). [Schema Connections](/docs/schema-connections/) takes care of that. + diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-gql-type.md index cd1db462cf86d..e801fb309b42e 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-gql-type.md @@ -9,24 +9,24 @@ Gatsby creates a [GraphQLObjectType](https://graphql.org/graphql-js/type/#graphq When running a GraphQL query, there are a variety of fields that you will want to query. Let's take an example, say we have the below query: ```graphql - query { - file( relativePath: { eq: `blogs/2018-08-23/announcing-v2.md` } ) { - childMarkdownRemark { - frontmatter: { - title - } +{ + file( relativePath: { eq: `blogs/my-blog.md` } ) { + childMarkdownRemark { + frontmatter: { + title } } } +} ``` -When GraphQL runs, it will query all `file` nodes by their relativePath and return the first node that satisfies that query. Then, it will filter down the fields to return by the inner expression. I.e `{ childMarkdownRemark ... }`. The building of the query arguments is covered by the [Inferring Input Filters](/docs/schema-input-gql) doc. This section instead explains how the inner filter schema is generated. +When GraphQL runs, it will query all `file` nodes by their relativePath and return the first node that satisfies that query. Then, it will filter down the fields to return by the inner expression. I.e `{ childMarkdownRemark ... }`. The building of the query arguments is covered by the [Inferring Input Filters](/docs/schema-input-gql) doc. This section instead explains how the inner filter schema is generated (it must be generated before input filters are inferred). -During the [sourceNodes](/docs/node-apis/#sourceNodes) phase, let's say that [gatsby-source-filesystem](/packages/gatsby-source-filesystem) ran and created a bunch of File nodes. Then, different transformers react via [onCreateNode](/docs/node-apis/#onCreateNode), resulting in children of different `node.internal.type`s being created. +During the [sourceNodes](/docs/node-apis/#sourceNodes) phase, let's say that [gatsby-source-filesystem](/packages/gatsby-source-filesystem) ran and created a bunch of `File` nodes. Then, different transformers react via [onCreateNode](/docs/node-apis/#onCreateNode), resulting in children of different `node.internal.type`s being created. There are 3 categories of node fields that we can query. -#### Fields on the node object. E.g +#### Fields on the created node object. E.g ```graphql node { @@ -48,7 +48,7 @@ node { } ``` -#### fields created by plugins +#### fields created by setFieldsOnGraphQLNodeType ```graphql node { @@ -62,13 +62,13 @@ Each of these categories of fields is created in a different way, explained belo The Gatsby term for the GraphQLObjectType for a unique node type, is `gqlType`. GraphQLObjectTypes are simply objects that define the type name and fields. The field definitions are created by the [createNodeFields](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L48) function in `build-node-types.js`. -An important thing to note is that all gqlTypes are created before their fields are inferred. This allows fields to be of types that haven't yet been created due to their order of compilation. This is accomplished by the use of `fields` [being a function](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L167). +An important thing to note is that all gqlTypes are created before their fields are inferred. This allows fields to be of types that haven't yet been created due to their order of compilation. This is accomplished by the use of `fields` [being a function](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L167) (basically lazy functions). -The first step in inferring GraphQL Fields is to generate an `exampleValue`. It is the result of merging all fields of all nodes of the type in question. This `exampleValue` will therefore contain all potential field names and values, which allows us to infer each field's types. The logic to create this exampleValue is in [getExampleValues](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L305). +The first step in inferring GraphQL Fields is to generate an `exampleValue`. It is the result of merging all fields of all nodes of the type in question. This `exampleValue` will therefore contain all potential field names and values, which allows us to infer each field's types. The logic to create it is in [getExampleValues](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L305). With the exampleValue in hand, we can use each of its key/values to infer the Type's fields (broken down by the 3 categories above). -### Object Node Fields +### Fields on the created node object Fields on the node that were created directly by the source and transform plugins. E.g for `File` type, these would be `relativePath`, `size`, `accessTime` etc. @@ -92,7 +92,7 @@ mapping: { The field generaton in this case is handled by [inferFromMapping](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L129). The first step is to find the type that is mapped to. In this case, `AuthorYaml`. This is known as the `linkedType`. That type will have a field to link by. In this case `name`. If one is not supplied, it defaults to `id`. This field is known as `linkedField` -Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which we look up in list of other `gqlTypes`). The field resolver will get the value for the node (in this case, the author string), and then search through the react nodes till it finds one whose type is `AuthorYaml` and whose `name` field matches the author string. +Now we can create a GraphQL Field declaration whose type is `AuthorYaml` (which we look up in list of other `gqlTypes`). The field resolver will get the value for the node (in this case, the author string), and then search through the react nodes until it finds one whose type is `AuthorYaml` and whose `name` field matches the author string. #### Foreign Key reference (`___NODE`) @@ -100,7 +100,7 @@ If not a mapping field, it might instead end in `___NODE`, signifying that its v This is actually quite similar to the mapping case above. We remove the `___NODE` part of the field name. E.g `author___NODE` would become `author`. Then, we find our `linkedNode`. I.e given the example value for `author` (which would be an ID), we find its actual node in redux. Then, we find its type in processed types by its `internal.type`. Note, that also like in mapping fields, we can define the `linkedField` too. This can be specified via `nodeFieldname___NODE___linkedFieldName`. E.g for `author___NODE___name`, the linkedField would be `name` instead of `id`. -Now we can return a new GraphQL Field object, whose type is the one found above. Its resolver searches through all redux nodes until it finds one with the matching ID. As usual, it also creates a [page dependency](TODO), from the query context's path to the node ID. +Now we can return a new GraphQL Field object, whose type is the one found above. Its resolver searches through all redux nodes until it finds one with the matching ID. As usual, it also creates a [page dependency](/docs/page-node-dependencies/), from the query context's path to the node ID. If the foreign key value is an array of IDs, then instead of returning a Field declaration for a single field, we return a `GraphQLUnionType`, which is a union of all the distinct linked types in the array. @@ -112,7 +112,7 @@ The core of this step creates a GraphQL Field object, where the type is inferred If however, the value is an object or array, we recurse, using [inferObjectStructureFromNodes](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L317) to create the GraphQL fields. -In addition, Gatsby creates custom GraphQL types for `File` ([types/type-file.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)) and `Date` ([types/type-date.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)). If the value of our field is a string that looks like a filename or a date (handled by [should-infer](TODO) functions), then we return the appropariate custom type. +In addition, Gatsby creates custom GraphQL types for `File` ([types/type-file.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)) and `Date` ([types/type-date.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js)). If the value of our field is a string that looks like a filename or a date (handled by [should-infer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-type.js#L52) functions), then we return the appropariate custom type. ### Child/Parent fields @@ -120,7 +120,7 @@ In addition, Gatsby creates custom GraphQL types for `File` ([types/type-file.js Let's continue with the `File` type example. There are many transformer plugins that implement `onCreateNode` for `File` nodes. These produce `File` children that are of their own type. E.g `markdownRemark`, `postsJson`. -Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [node creation for more](/docs/node-creation-behind-the-scenes/#node-relationship-storage-model)). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: +Gatsby stores these children in redux as IDs in the parent's `children` field. And then stores those child nodes as full redux nodes themselves (see [Node Creation](/docs/node-creation/#node-relationship-storage-model) for more). E.g for a File node with two children, it will be stored in the redux `nodes` namespace as: ```javascript { @@ -152,13 +152,13 @@ query { } ``` -To resolve `file.childMarkdownRemark`, we take the node we're resolving, and filter over all of its `children` until we find one of type `markdownRemark`, which is returned. Remember that in redux, `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. +To resolve `file.childMarkdownRemark`, we take the node we're resolving, and filter over all of its `children` until we find one of type `markdownRemark`, which is returned. Remember that `children` is a collection of IDs. So as part of this, we lookup the node by ID in redux too. -But before we return from the resolve function, remember that we might be running this query within the context of a page. If that's the case, then whenever the node changes, the page will need to be rerendered. To record that fact, we call call [createPageDependency](TODO) with the node ID and the page, which is a field in the `context` object in the resolve function signature. +But before we return from the resolve function, remember that we might be running this query within the context of a page. If that's the case, then whenever the node changes, the page will need to be rerendered. To record that fact, we call call [createPageDependency](/docs/page-node-dependencies/) with the node ID and the page, which is a field in the `context` object in the resolve function signature. #### parent field -When a node is created as a child of some node (parent), that fact is stored in the child's `parent` field. The value of which is the ID of the parent. The [GraphQL resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L57) for this field looks up the parent by that ID in redux and returns it. It also creates a [page dependency](TODO have this in own section), to record that the page being queried depends on the parent node. +When a node is created as a child of some node (parent), that fact is stored in the child's `parent` field. The value of which is the ID of the parent. The [GraphQL resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-types.js#L57) for this field looks up the parent by that ID in redux and returns it. It also creates a [page dependency](/docs/page-node-dependencies/), to record that the page being queried depends on the parent node. ### Plugin fields @@ -196,7 +196,7 @@ Notice that the image value looks like a file. Therefore, we'd like to query it } ``` -The [File type resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js#L135) takes care of this. It gets the value (`images/BdiU-TTFP4h.jpg`). It then looks up this node's root NodeID via [node-tracking](TODO) which returns the original `data/posts.json` file. It creates a new filename by concatenating the field value onto the parent node's directory. +The [File type resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/types/type-file.js#L135) takes care of this. It gets the value (`images/BdiU-TTFP4h.jpg`). It then looks up this node's root NodeID via [Node Tracking](/docs/node-tracking/) which returns the original `data/posts.json` file. It creates a new filename by concatenating the field value onto the parent node's directory. I.e `data` + `images/BdiU-TTFP4h.jpg` = `data/images/BdiU-TTFP4h.jpg`. diff --git a/docs/docs/schema-sift.md b/docs/docs/schema-sift.md index 0ddfa9c709d83..180512cb89322 100644 --- a/docs/docs/schema-sift.md +++ b/docs/docs/schema-sift.md @@ -18,7 +18,7 @@ The `resolve()` function calls `run-sift.js`, and provides it with the following - GraphQLArgs (as js object). Within a filter. E.g `wordcount: { paragraphs: { eq: 4 } }` - All nodes in redux of this type. E.g where `internal.type == MmarkdownRemark'` -- Context `path`, if present +- Context `path`, if being called as part of a [page query](/docs/query-execution/#query-queue-execution) - typeName. E.g `markdownRemark` - gqlType. See [more on gqlType](/docs/schema-gql-type) @@ -36,7 +36,7 @@ runSift({ } }, nodes: ${latestNodes}, - path: context.path, // E.g /blog/2018-08-23/introducing-v2 + path: context.path, // E.g /blogs/my-blog typeName: `markdownRemark`, type: ${gqlType} }) @@ -100,18 +100,22 @@ Note that the graphql-js library has NOT been invoked yet. We're instead calling The resolve method in this case would return a paragraph node, which also needs to be properly resolved. So We descend the `fieldsToSift` arg tree and perform the above operation on the paragraph node (using the found paragraph gqlType). -After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. Since new fields on the node may have been created in this process, we call `trackInlineObjectsInRootNode()` to track these new objects. See [Node Tracking Docs](/docs/behind-the-scenes-dependencies/#root-node-tracking) for more. +After `resolveRecursive` has finished, we will have "realized" all the query fields in each node, giving us confidence that we can perform the query with all the data being there. + +### 4. Track newly realized fields + +Since new fields on the node may have been created in this process, we call `trackInlineObjectsInRootNode()` to track these new objects. See [Node Tracking](/docs/node-tracking/) docs for more. ### 5. Run sift query on all nodes -Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the sift query to all those nodes. This step is handled by [tempPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L214). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. +Now that we've realized all fields that need to be queried, on all nodes of this type, we are finally ready to apply the sift query. This step is handled by [tempPromise](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/run-sift.js#L214). It simply concatenates all the top level objects in the args tree together with a sift `$and` expression, and then iterates over all nodes returning the first one that satisfies the sift expression. -In the case that `connection === true` (argument passed to run-sift), then instead of just choosing the first argument, we will select ALL nodes that match the sift query. If the GraphQL query specified `sort`, `skip`, or `limit` fields, then we use the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library to filter down to the appropriate results. See [schema-connections](/docs/schema-connections) for more info. +In the case that `connection === true` (argument passed to run-sift), then instead of just choosing the first argument, we will select ALL nodes that match the sift query. If the GraphQL query specified `sort`, `skip`, or `limit` fields, then we use the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library to filter down to the appropriate results. See [Schema Connections](/docs/schema-connections) for more info. ### 6. Create Page dependency if required -Assuming we find a node (or multiple if `connection` === true), we finish off by recording the page that initiated the query (in the `path` field depends on the found node. More on this in [create page dependency](TODO). +Assuming we find a node (or multiple if `connection` === true), we finish off by recording the page that initiated the query (in the `path` field) depends on the found node. More on this in [Page -> Node Dependencies](/docs/page-node-dependencies/). -## Note about plugin resolve side effects +## Note about plugin resolver side effects As [mentioned above](#3-resolve-inner-query-fields-on-all-nodes), `run-sift` must "realize" all query fields before querying over them. This involves calling the resolvers of custom plugins on **each node of that type**. Therefore, if a resolver performs side effects, then these will be triggered, regardless of whether the field result actually matches the query. diff --git a/docs/docs/behind-the-scenes-static-vs-normal-queries.md b/docs/docs/static-vs-normal-queries.md similarity index 100% rename from docs/docs/behind-the-scenes-static-vs-normal-queries.md rename to docs/docs/static-vs-normal-queries.md diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 859ce38645d44..d5a05351423f2 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -261,9 +261,9 @@ - title: How APIS/Plugins Are Run link: /docs/how-plugins-apis-are-run/ - title: Node Creation - link: /docs/node-creation-behind-the-scenes/ + link: /docs/node-creation/ - title: Schema Generation - link: /docs/schema-generation-behind-the-scenes/ + link: /docs/schema-generation/ items: - title: Building the GqlType link: /docs/schema-gql-type/ @@ -273,17 +273,21 @@ link: /docs/schema-sift/ - title: Connections link: /docs/schema-connections/ - - title: Page Creation + - title: Page Creation* link: /docs/page-creation/ - - title: Node/Page Dependencies - link: /docs/behind-the-scenes-dependencies/ + - title: Page -> Node Dependencies + link: /docs/page-node-dependencies/ + - title: Node Tracking + link: /docs/node-tracking/ - title: Queries link: /docs/query-behind-the-scenes/ items: - title: Query Extraction - link: /docs/behind-the-scenes-query-extraction/ + link: /docs/query-extraction/ - title: Query Execution - link: /docs/behind-the-scenes-query-execution/ + link: /docs/query-execution/ + - title: Normal vs StaticQueries + link: /docs/static-vs-normal-queries/ - title: Data Storage (Redux)* link: /docs/data-storage-redux/ - title: Build Caching* From a0aeb71d7280f638a649b56c53b3b78b32fc75df Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 6 Sep 2018 13:09:39 +1000 Subject: [PATCH 38/39] add internal data bridge docs --- docs/docs/internal-data-bridge.md | 51 ++++++++++++++++++++++++++++ www/src/data/sidebars/doc-links.yaml | 2 ++ 2 files changed, 53 insertions(+) create mode 100644 docs/docs/internal-data-bridge.md diff --git a/docs/docs/internal-data-bridge.md b/docs/docs/internal-data-bridge.md new file mode 100644 index 0000000000000..d59a6e5aa9ec9 --- /dev/null +++ b/docs/docs/internal-data-bridge.md @@ -0,0 +1,51 @@ +--- +title: Internal Data Bridge +--- + +The Internal Data Bridge is an internal Gatsby plugin located at [internal-plugins/internal-data-bridge](TODO). Its purpose is to create nodes representing pages, plugins, and site config so that they can be introspected for arbitrary purposes. As of writing, the only usage of this is by the [gatsby-plugin-sitemap](TODO) which uses it to... yes you guessed it, create a site map of your site. + +## Example usage + +As a site developer, you can use it write queries to introspect your site's information. E.g get all the jsonNames of your pages. + +```graphql +{ + allSitePage( limit: 10 ) { + edges { + node { + jsonName + } + } + } +} +``` + +Or, get a list of all gatsby plugins that you're using + +```graphql +{ + allSitePlugin(limit: 10) { + edges { + node { + name + } + } + } +} +``` + +## Internal types + +The internal data bridge creates 3 types of nodes that can be introspected. + +### Site + +This is a node that contains fields from your site's `gatsby-config.js`, as well as program information such as host and port for gatsby develop. + +### SitePlugin + +A Node for each plugin in your `gatsby-config.js` that contains the full contents of the plugin's `package.json`. + +### SitePage + +Internal Data Bridge implements [onCreatePage](TODO) and creates a node of type `SitePage` that represents the created Page. Which allows you to introspect all pages created for your site. diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index d5a05351423f2..954a1956130a1 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -279,6 +279,8 @@ link: /docs/page-node-dependencies/ - title: Node Tracking link: /docs/node-tracking/ + - title: Internal Data Bridge + link: /docs/internal-data-bridge/ - title: Queries link: /docs/query-behind-the-scenes/ items: From f5e53ba1de283af298e2456dbc184deb65c9d7ac Mon Sep 17 00:00:00 2001 From: Anthony Marcar Date: Thu, 6 Sep 2018 13:48:21 +1000 Subject: [PATCH 39/39] filled in page creation docs --- docs/docs/internal-data-bridge.md | 4 ++-- docs/docs/page-creation.md | 18 +++++++++++++++--- www/src/data/sidebars/doc-links.yaml | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/docs/internal-data-bridge.md b/docs/docs/internal-data-bridge.md index d59a6e5aa9ec9..7f6c5143e5a21 100644 --- a/docs/docs/internal-data-bridge.md +++ b/docs/docs/internal-data-bridge.md @@ -2,7 +2,7 @@ title: Internal Data Bridge --- -The Internal Data Bridge is an internal Gatsby plugin located at [internal-plugins/internal-data-bridge](TODO). Its purpose is to create nodes representing pages, plugins, and site config so that they can be introspected for arbitrary purposes. As of writing, the only usage of this is by the [gatsby-plugin-sitemap](TODO) which uses it to... yes you guessed it, create a site map of your site. +The Internal Data Bridge is an internal Gatsby plugin located at [internal-plugins/internal-data-bridge](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/internal-plugins/internal-data-bridge). Its purpose is to create nodes representing pages, plugins, and site config so that they can be introspected for arbitrary purposes. As of writing, the only usage of this is by the [gatsby-plugin-sitemap](/packages/gatsby-plugin-sitemap) which uses it to... yes you guessed it, create a site map of your site. ## Example usage @@ -48,4 +48,4 @@ A Node for each plugin in your `gatsby-config.js` that contains the full content ### SitePage -Internal Data Bridge implements [onCreatePage](TODO) and creates a node of type `SitePage` that represents the created Page. Which allows you to introspect all pages created for your site. +Internal Data Bridge implements [onCreatePage](/docs/node-apis/#onCreatePage) and creates a node of type `SitePage` that represents the created Page. Which allows you to introspect all pages created for your site. diff --git a/docs/docs/page-creation.md b/docs/docs/page-creation.md index dee975bec7c19..3a145cdf25363 100644 --- a/docs/docs/page-creation.md +++ b/docs/docs/page-creation.md @@ -2,8 +2,20 @@ title: Page Creation --- -This is a stub. Help our community expand it. +A page is created by calling the [createPage](/docs/actions/#createPage) action. There are two main side effects that occur when a page is created. -Please use the [Gatsby Style Guide](/docs/gatsby-style-guide/) to ensure your -pull request gets accepted. +1. The `pages` redux namespace is updated +1. The `components` redux namespace is updated +1. `onCreatePage` API is executed +## Update Pages redux namespace + +The `pages` redux namespace is a map of page `path` to page object. The [pages reducer](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/redux/reducers/pages.js) takes care of updating this on a `CREATE_PAGE` action. It also creates a [Foreign Key Reference](/docs/schema-gql-type/#foreign-key-reference-___node) to plugin that created the page by adding a `pluginCreator___NODE` field. + +## Update Components redux namespace + +The `components` redux namespace is a map of [componentPath](/docs/behind-the-scenes-terminology/#component) (file with React component) to the Component object. A Component object is simply the Page object but with an empty query string (that will be set during [Query Extraction](/docs/query-extraction/#store-queries-in-redux)). + +## onCreatePage API + +Every time a page is created, plugins have the opportunity to handle its [onCreatePage](/docs/node-apis/#onCreatePage) event. This is used for things like creating `SitePage` nodes in [Internal Data Bridge](/docs/internal-data-bridge/), and for "path" related plugins such as [gatsby-plugin-create-client-paths](/packages/gatsby-plugin-create-client-paths/) and [gatsby-plugin-remove-trailing-slashes](/packages/gatsby-plugin-remove-trailing-slashes/). diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 954a1956130a1..60adfe5ebbb77 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -273,7 +273,7 @@ link: /docs/schema-sift/ - title: Connections link: /docs/schema-connections/ - - title: Page Creation* + - title: Page Creation link: /docs/page-creation/ - title: Page -> Node Dependencies link: /docs/page-node-dependencies/