diff --git a/examples/using-javascript-transforms/.gitignore b/examples/using-javascript-transforms/.gitignore
new file mode 100644
index 0000000000000..8f5b35a4a9cbc
--- /dev/null
+++ b/examples/using-javascript-transforms/.gitignore
@@ -0,0 +1,3 @@
+public
+.cache
+node_modules
diff --git a/examples/using-javascript-transforms/README.md b/examples/using-javascript-transforms/README.md
new file mode 100644
index 0000000000000..3c7efcd9aff4b
--- /dev/null
+++ b/examples/using-javascript-transforms/README.md
@@ -0,0 +1,8 @@
+# Using Javascript Transforms
+## Where we get fancy what we can do with gatsby
+### An exploration of the javascript ecosystem in Gatsby
+TODO, ALL OF THIS.
+
+It mixes javascript and remark, uses scss and bulma.io, has use case examples for graphql in layouts, and some "manual" page creation with the help of the jsFrontmatter transformer.
+
+For the time being, read the comments within the files themselves.
diff --git a/examples/using-javascript-transforms/gatsby-config.js b/examples/using-javascript-transforms/gatsby-config.js
new file mode 100644
index 0000000000000..459c1c177635b
--- /dev/null
+++ b/examples/using-javascript-transforms/gatsby-config.js
@@ -0,0 +1,53 @@
+module.exports = {
+ siteMetadata: {
+ title: "Fancy Javascript Example",
+ siteDescr: "We get fancy with some javascript here.",
+ siteAuthor: "Jacob Bolda",
+
+ siteEmailUrl: "me@x.com",
+ siteEmailPretty: "me@x.com",
+ siteTwitterUrl: "https://twitter.com/jacob_bolda",
+ siteTwitterPretty: "@jacob_bolda",
+ },
+ plugins: [
+ {
+ resolve: `gatsby-source-filesystem`,
+ options: {
+ name: `pages`,
+ path: `${__dirname}/src/mainPages/`,
+ },
+ },
+ {
+ resolve: `gatsby-source-filesystem`,
+ options: {
+ name: `articles`,
+ path: `${__dirname}/src/articles/`,
+ },
+ },
+ `gatsby-transformer-javascript-static-exports`,
+ `gatsby-transformer-yaml`,
+ {
+ resolve: `gatsby-transformer-remark`,
+ options: {
+ plugins: [
+ {
+ resolve: `gatsby-remark-images`,
+ options: {
+ maxWidth: 690,
+ },
+ },
+ {
+ resolve: `gatsby-remark-responsive-iframe`,
+ options: {},
+ },
+ `gatsby-remark-prismjs`,
+ `gatsby-remark-copy-linked-files`,
+ `gatsby-remark-smartypants`,
+ ],
+ },
+ },
+ `gatsby-plugin-sharp`,
+ `gatsby-plugin-postcss-sass`,
+ `gatsby-plugin-offline`,
+ ],
+}
\ No newline at end of file
diff --git a/examples/using-javascript-transforms/gatsby-node.js b/examples/using-javascript-transforms/gatsby-node.js
new file mode 100644
index 0000000000000..8198ac851fdb4
--- /dev/null
+++ b/examples/using-javascript-transforms/gatsby-node.js
@@ -0,0 +1,125 @@
+const path = require('path')
+
+exports.onCreateNode = ({ node, boundActionCreators, getNode }) => {
+ const { createNodeField } = boundActionCreators
+ let slug
+ if (node.internal.type === `MarkdownRemark` || node.internal.type === `JSFrontmatter`) {
+ const fileNode = getNode(node.parent)
+ const parsedFilePath = path.parse(fileNode.relativePath)
+ if (parsedFilePath.name !== `index` && parsedFilePath.dir !== ``) {
+ slug = `/${parsedFilePath.dir}/${parsedFilePath.name}/`
+ } else if (parsedFilePath.dir === ``) {
+ slug = `/${parsedFilePath.name}/`
+ } else {
+ slug = `/${parsedFilePath.dir}/`
+ }
+
+ // Add slug as a field on the node.
+ createNodeField({ node, name: `slug`, value: slug })
+ }
+}
+
+exports.createPages = ({ graphql, boundActionCreators }) => {
+ const { createPage } = boundActionCreators
+
+ return new Promise((resolve, reject) => {
+ const pages = []
+ const markdownTemplate = path.resolve("src/templates/markdown.js")
+ const jsTemplate = path.resolve("src/templates/javascript.js")
+
+ // Query for all markdown "nodes" and for the slug we previously created.
+ resolve(
+ graphql(
+ `
+ {
+ allMarkdownRemark {
+ edges {
+ node {
+ frontmatter {
+ layoutType
+ path
+ }
+ fields {
+ slug
+ }
+ }
+ }
+ }
+ allJsFrontmatter {
+ edges {
+ node {
+ fileAbsolutePath
+ data {
+ layoutType
+ path
+ }
+ fields {
+ slug
+ }
+ }
+ }
+ }
+ }
+ `
+ ).then(result => {
+ if (result.errors) {
+ console.log(result.errors)
+ console.log(result)
+ reject(result.errors)
+ }
+
+ // Create from markdown
+ result.data.allMarkdownRemark.edges.forEach(edge => {
+ let frontmatter = edge.node.frontmatter;
+ // ideally we would want to use layoutType to
+ // decide which (nested) layout to use, but
+ // gatsby currently doesnt support this.
+ if (frontmatter.layoutType === 'post' ||
+ frontmatter.layoutType === 'page') {
+ createPage({
+ path: frontmatter.path, // required
+ component: markdownTemplate,
+ context: {
+ layoutType: frontmatter.layoutType,
+ slug: edge.node.fields.slug,
+ },
+ })
+ }
+ })
+
+ // Create pages from javascript
+ // Gatsby will, by default, createPages for javascript in the
+ // /pages directory. We purposely don't have a folder with this name
+ // so that we can go full manual mode.
+ result.data.allJsFrontmatter.edges.forEach(edge => {
+ let frontmatter = edge.node.data;
+ // see above
+ if (frontmatter.layoutType === 'post' ||
+ frontmatter.layoutType === 'page') {
+ createPage({
+ path: frontmatter.path, // required
+ // Note, we can't have a template, but rather require the file directly.
+ // Templates are for converting non-react into react. jsFrontmatter
+ // picks up all of the javascript files. We have only written these in react.
+ component: path.resolve(edge.node.fileAbsolutePath),
+ context: {
+ layoutType: frontmatter.layoutType,
+ slug: edge.node.fields.slug,
+ },
+ })
+ } else if (edge.node.fields.slug === '/index/') {
+ createPage({
+ path: '/', // required, we don't have frontmatter for this page hence separate if()
+ component: path.resolve(edge.node.fileAbsolutePath),
+ context: {
+ slug: edge.node.fields.slug,
+ },
+ })
+ }
+ })
+
+ return
+ })
+ )
+ })
+}
diff --git a/examples/using-javascript-transforms/package.json b/examples/using-javascript-transforms/package.json
new file mode 100644
index 0000000000000..7b179531f84c7
--- /dev/null
+++ b/examples/using-javascript-transforms/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "gatsby-example-using-javascript-transforms",
+ "version": "1.0.0",
+ "description": "example site and blog",
+ "main": "index.js",
+ "scripts": {
+ "develop": "node_modules/.bin/gatsby develop",
+ "dev:hard": "rm -rf .cache && rm -rf public && npm run develop",
+ "build": "npm run clean & .\\node_modules\\.bin\\gatsby build",
+ "serve-build": "node_modules/.bin/gatsby serve-build",
+ "clean:public": "rm -rf public & mkdir public",
+ "clean": "npm run clean:public",
+ "lint": "./node_modules/.bin/eslint --ext .js,.jsx --ignore-pattern public .",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [
+ "personal",
+ "blog"
+ ],
+ "author": "jbolda",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/gatsbyjs/gatsby/issues"
+ },
+ "homepage": "https://www.gatsbyjs.org",
+ "dependencies": {
+ "bulma": "0.4.2",
+ "d3": "4.9.1",
+ "gatsby": "^1.1.0",
+ "gatsby-link": "^1.0.9",
+ "gatsby-plugin-offline": "^1.0.1",
+ "gatsby-plugin-postcss-sass": "^1.0.1",
+ "gatsby-plugin-sharp": "^1.0.0",
+ "gatsby-remark-copy-linked-files": "^1.0.1",
+ "gatsby-remark-prismjs": "^1.1.0",
+ "gatsby-remark-responsive-iframe": "^1.0.1",
+ "gatsby-remark-images": "^1.1.0",
+ "gatsby-remark-smartypants": "^1.0.1",
+ "gatsby-source-filesystem": "^1.0.1",
+ "gatsby-source-contentful": "^1.1.0",
+ "gatsby-transformer-javascript-static-exports": "^1.0.1",
+ "gatsby-transformer-remark": "^1.1.0",
+ "gatsby-transformer-sharp": "^1.0.1",
+ "gatsby-transformer-yaml": "^1.0.0",
+ "moment": "^2.14.1",
+ "normalize.css": "^7.0.0",
+ "prop-types": "^15.5.8",
+ "react-helmet": "^3.1.0"
+ }
+}
diff --git a/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md b/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md
new file mode 100644
index 0000000000000..6cdc33dda30f8
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md
@@ -0,0 +1,19 @@
+---
+title: First Post About First Post
+written: "2017-01-22"
+updated: "2017-03-04"
+layoutType: post
+path: "a-first-post"
+category: "Beginnings"
+description: "From humble beginnings to... space?"
+---
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi.
+
+Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis.
+
+Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna.
+
+Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum.
+
+Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat.
diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md
new file mode 100644
index 0000000000000..3bf16d3870bb6
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md
@@ -0,0 +1,13 @@
+---
+what: text for Choropleth on d3v4
+---
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi.
+
+Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis.
+
+Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna.
+
+Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum.
+
+Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat.
diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js
new file mode 100644
index 0000000000000..371fe6fdfa91a
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js
@@ -0,0 +1,209 @@
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+var d3 = require('d3');
+import PostPublished from '../../components/PostPublished';
+import HelmetBlock from '../../components/HelmetBlock';
+// We have to include these components on every javascript page
+// as we cannot use templates and layouts cannot query for data.
+// Logically it would make sense to put these on the parents, but
+// for now they have to be children of EVERY post.
+
+
+// this is one method to export data and make it usable elsewhere
+exports.data = {
+ title: 'Choropleth on d3v4',
+ written: '2017-03-09',
+ updated: '2017-04-28',
+ layoutType: 'post',
+ path: 'choropleth-on-d3v4',
+ category: 'data science',
+ description: 'Things about the choropleth.'
+}
+
+class choroplethBase extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+
+ componentDidMount() {
+ this.d3Node = d3.select('div#states');
+ let measurements = {
+ width: this.d3Node._groups[0][0].clientWidth,
+ height: this.d3Node._groups[0][0].clientHeight
+ }
+ let space = graph.setup(this.d3Node, measurements);
+
+ /*
+ we begin drawing here, grab the data and use it to draw
+ */
+
+ d3.queue()
+ .defer(d3.json, stateDataURL)
+ .defer(d3.csv,statisticsDataURL)
+ .awaitAll(function(error, results) {
+ let states = results[0].states;
+ let stats = results[1];
+ let mergedData = mergeData(states, 'abbrev', stats, 'Abbreviation')
+ graph.draw(space, mergedData, measurements);
+ });
+ }
+
+ componentWillUnmount () {
+ d3.select('svg').remove();
+ }
+
+ render() {
+ let data = this.props.data.markdownRemark;
+ let html = data.html;
+ let frontmatter = this.props.data.jsFrontmatter.data;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default choroplethBase;
+
+var graph = {}; // we namespace our d3 graph into setup and draw
+
+var stateDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/states.json';
+var statisticsDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/stats.csv';
+
+graph.setup = (selection, measurements) => {
+// the path string is drawn expecting:
+ // a width of 950px
+ // a height of 600px
+ // which gives an aspect ratio of 1.6
+
+ let svg = selection.append('svg')
+ .attr('width', measurements.width)
+ .attr('height', measurements.width / 1.6);
+
+ return svg;
+}
+
+graph.draw = (svg, data, measurements) => {
+/*
+our data expects an array of objects
+each object is expected to have:
+name: tooltip - the full name of the state
+abbrev: mergeData - used as the key to merge the json and csv
+low: tooltip, color domain
+high: tooltip, color domain
+average: tooltip, path fill
+*/
+ let color = d3.scaleQuantize()
+ .range(["rgb(237,248,233)",
+ "rgb(186,228,179)",
+ "rgb(116,196,118)",
+ "rgb(49,163,84)",
+ "rgb(0,109,44)"]);
+
+ color.domain([
+ d3.min(data, function(d) { return d.low; }),
+ d3.max(data, function(d) { return d.high; })
+ ]);
+
+ let scaleFactor = measurements.width / 950;
+
+ let states = svg.selectAll('path.states')
+ .data(data);
+
+ let drawStates = states.enter().append('path')
+ .attr('class', 'state')
+ .attr('id', d => d.abbrev)
+ .attr('stroke', 'gray')
+ .attr('d', d => d.path)
+ .attr('transform', 'scale('+scaleFactor+')')
+ .style('fill', d => color(d.average))
+ .on('mouseover', mouseOver)
+ .on('mouseout', mouseOut);
+}
+
+
+let tooltipHtml = (d) => {
+ return '
'+d.name+'
'+
+ '
Low
'+(d.low)+'
'+
+ '
High
'+(d.high)+'
'+
+ '
Avg
'+(d.average)+'
'+
+ '
';
+}
+
+let mouseOver = (d) => {
+ let tooltip = d3.select('#tooltip')
+ .html(tooltipHtml(d))
+ .style('opacity', .9)
+ .style('left', (d3.event.pageX) + 'px')
+ .style('top', (d3.event.pageY - 28) + 'px');
+
+ tooltip.transition().duration(200)
+}
+
+let mouseOut = () => {
+ d3.select('#tooltip')
+ .transition().duration(500)
+ .style('opacity', 0);
+}
+
+function scale (scaleFactor,width,height) {
+ return d3.geoTransform({
+ point: function(x, y) {
+ this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
+ }
+ });
+ }
+
+let mergeData = (d1, d1key, d2, d2key) => {
+ let data = [];
+ d1.forEach((s1) => {
+ d2.forEach((s2) => {
+ if (s1[d1key] === s2[d2key]) {
+ data.push(Object.assign({}, s1, s2))
+ }
+ })
+ })
+
+ return data;
+};
+
+// We want to keep this component mostly about the code
+// so we write our explanation with markdown and manually pull it in here.
+// Within the config, we loop all of the markdown and createPages. However,
+// it will ignore any files appended with an _underscore. We can still manually
+// query for it here, and get the transformed html though because remark transforms
+// any markdown based node.
+export const pageQuery = graphql`
+query choroplethOnD3v4($slug: String!) {
+ markdownRemark(fields: { slug: { eq: "/2017-03-09-choropleth-on-d3v4/_choropleth/" }}) {
+ html
+ }
+ jsFrontmatter(fields: {slug: {eq: $slug}}) {
+ data {
+ error
+ layoutType
+ path
+ title
+ written
+ category
+ description
+ updated
+ }
+ }
+}
+`
diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss
new file mode 100644
index 0000000000000..cc0fb0c5877ad
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss
@@ -0,0 +1,46 @@
+ .state{
+ fill: none;
+ stroke: #a9a9a9;
+ stroke-width: 1;
+ }
+ .state:hover{
+ fill-opacity:0.5;
+ }
+ #tooltip {
+ position: absolute;
+ text-align: center;
+ padding: 20px;
+ margin: 10px;
+ font: 12px sans-serif;
+ background: lightsteelblue;
+ border: 1px;
+ border-radius: 2px;
+ pointer-events: none;
+ }
+ #tooltip h4{
+ margin:0;
+ font-size:14px;
+ }
+ #tooltip{
+ background:rgba(0,0,0,0.9);
+ border:1px solid grey;
+ border-radius:5px;
+ font-size:12px;
+ width:auto;
+ padding:4px;
+ color:white;
+ opacity:0;
+ }
+ #tooltip table{
+ table-layout:fixed;
+ }
+ #tooltip tr td{
+ padding:0;
+ margin:0;
+ }
+ #tooltip tr td:nth-child(1){
+ width:50px;
+ }
+ #tooltip tr td:nth-child(2){
+ text-align:center;
+ }
\ No newline at end of file
diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md
new file mode 100644
index 0000000000000..3bf16d3870bb6
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md
@@ -0,0 +1,13 @@
+---
+what: text for Choropleth on d3v4
+---
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi.
+
+Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis.
+
+Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna.
+
+Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum.
+
+Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat.
diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js
new file mode 100644
index 0000000000000..fa15bafad2988
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js
@@ -0,0 +1,208 @@
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+var d3 = require('d3');
+import PostPublished from '../../components/PostPublished';
+import HelmetBlock from '../../components/HelmetBlock';
+// We have to include these components on every javascript page
+// as we cannot use templates and layouts cannot query for data.
+// Logically it would make sense to put these on the parents, but
+// for now they have to be children of EVERY post.
+
+
+// this is an additional method to export data and make it usable elsewhere
+export const data = {
+ title: 'Alternate Choropleth on d3v4',
+ written: '2017-05-30',
+ layoutType: 'post',
+ path: 'choropleth-on-d3v4-alternate',
+ category: 'data science',
+ description: 'Even more things about the choropleth. No, seriously.'
+}
+
+class choroplethAltBase extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+
+ componentDidMount() {
+ this.d3Node = d3.select('div#states');
+ let measurements = {
+ width: this.d3Node._groups[0][0].clientWidth,
+ height: this.d3Node._groups[0][0].clientHeight
+ }
+ let space = graph.setup(this.d3Node, measurements);
+
+ /*
+ we begin drawing here, grab the data and use it to draw
+ */
+
+ d3.queue()
+ .defer(d3.json, stateDataURL)
+ .defer(d3.csv,statisticsDataURL)
+ .awaitAll(function(error, results) {
+ let states = results[0].states;
+ let stats = results[1];
+ let mergedData = mergeData(states, 'abbrev', stats, 'Abbreviation')
+ graph.draw(space, mergedData, measurements);
+ });
+ }
+
+ componentWillUnmount () {
+ d3.select('svg').remove();
+ }
+
+ render() {
+ let data = this.props.data.markdownRemark;
+ let html = data.html;
+ let frontmatter = this.props.data.jsFrontmatter.data;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default choroplethAltBase;
+
+var graph = {}; // we namespace our d3 graph into setup and draw
+
+var stateDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/states.json';
+var statisticsDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/stats.csv';
+
+graph.setup = (selection, measurements) => {
+// the path string is drawn expecting:
+ // a width of 950px
+ // a height of 600px
+ // which gives an aspect ratio of 1.6
+
+ let svg = selection.append('svg')
+ .attr('width', measurements.width)
+ .attr('height', measurements.width / 1.6);
+
+ return svg;
+}
+
+graph.draw = (svg, data, measurements) => {
+/*
+our data expects an array of objects
+each object is expected to have:
+name: tooltip - the full name of the state
+abbrev: mergeData - used as the key to merge the json and csv
+low: tooltip, color domain
+high: tooltip, color domain
+average: tooltip, path fill
+*/
+ let color = d3.scaleQuantize()
+ .range(["rgb(237,248,233)",
+ "rgb(186,228,179)",
+ "rgb(116,196,118)",
+ "rgb(49,163,84)",
+ "rgb(0,109,44)"]);
+
+ color.domain([
+ d3.min(data, function(d) { return d.low; }),
+ d3.max(data, function(d) { return d.high; })
+ ]);
+
+ let scaleFactor = measurements.width / 950;
+
+ let states = svg.selectAll('path.states')
+ .data(data);
+
+ let drawStates = states.enter().append('path')
+ .attr('class', 'state')
+ .attr('id', d => d.abbrev)
+ .attr('stroke', 'gray')
+ .attr('d', d => d.path)
+ .attr('transform', 'scale('+scaleFactor+')')
+ .style('fill', d => color(d.average))
+ .on('mouseover', mouseOver)
+ .on('mouseout', mouseOut);
+}
+
+
+let tooltipHtml = (d) => {
+ return '
'+d.name+'
'+
+ '
Low
'+(d.low)+'
'+
+ '
High
'+(d.high)+'
'+
+ '
Avg
'+(d.average)+'
'+
+ '
';
+}
+
+let mouseOver = (d) => {
+ let tooltip = d3.select('#tooltip')
+ .html(tooltipHtml(d))
+ .style('opacity', .9)
+ .style('left', (d3.event.pageX) + 'px')
+ .style('top', (d3.event.pageY - 28) + 'px');
+
+ tooltip.transition().duration(200)
+}
+
+let mouseOut = () => {
+ d3.select('#tooltip')
+ .transition().duration(500)
+ .style('opacity', 0);
+}
+
+function scale (scaleFactor,width,height) {
+ return d3.geoTransform({
+ point: function(x, y) {
+ this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
+ }
+ });
+ }
+
+let mergeData = (d1, d1key, d2, d2key) => {
+ let data = [];
+ d1.forEach((s1) => {
+ d2.forEach((s2) => {
+ if (s1[d1key] === s2[d2key]) {
+ data.push(Object.assign({}, s1, s2))
+ }
+ })
+ })
+
+ return data;
+};
+
+// We want to keep this component mostly about the code
+// so we write our explanation with markdown and manually pull it in here.
+// Within the config, we loop all of the markdown and createPages. However,
+// it will ignore any files appended with an _underscore. We can still manually
+// query for it here, and get the transformed html though because remark transforms
+// any markdown based node.
+export const pageQuery = graphql`
+query choroplethOnD3v4Alt($slug: String!) {
+ markdownRemark(fields: { slug: { eq: "/2017-05-30-choropleth-on-d3v4-alternate/_choropleth/" }}) {
+ html
+ }
+ jsFrontmatter(fields: {slug: {eq: $slug}}) {
+ data {
+ error
+ layoutType
+ path
+ title
+ written
+ category
+ description
+ updated
+ }
+ }
+}
+`
diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss
new file mode 100644
index 0000000000000..cc0fb0c5877ad
--- /dev/null
+++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss
@@ -0,0 +1,46 @@
+ .state{
+ fill: none;
+ stroke: #a9a9a9;
+ stroke-width: 1;
+ }
+ .state:hover{
+ fill-opacity:0.5;
+ }
+ #tooltip {
+ position: absolute;
+ text-align: center;
+ padding: 20px;
+ margin: 10px;
+ font: 12px sans-serif;
+ background: lightsteelblue;
+ border: 1px;
+ border-radius: 2px;
+ pointer-events: none;
+ }
+ #tooltip h4{
+ margin:0;
+ font-size:14px;
+ }
+ #tooltip{
+ background:rgba(0,0,0,0.9);
+ border:1px solid grey;
+ border-radius:5px;
+ font-size:12px;
+ width:auto;
+ padding:4px;
+ color:white;
+ opacity:0;
+ }
+ #tooltip table{
+ table-layout:fixed;
+ }
+ #tooltip tr td{
+ padding:0;
+ margin:0;
+ }
+ #tooltip tr td:nth-child(1){
+ width:50px;
+ }
+ #tooltip tr td:nth-child(2){
+ text-align:center;
+ }
\ No newline at end of file
diff --git a/examples/using-javascript-transforms/src/components/HelmetBlock/index.js b/examples/using-javascript-transforms/src/components/HelmetBlock/index.js
new file mode 100644
index 0000000000000..ce65a61647f36
--- /dev/null
+++ b/examples/using-javascript-transforms/src/components/HelmetBlock/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import Helmet from 'react-helmet';
+import moment from 'moment';
+
+class HelmetBlock extends React.Component {
+ render() {
+ const frontmatter = this.props;
+ return (
+
+
+
+ );
+ }
+}
+
+export default HelmetBlock;
diff --git a/examples/using-javascript-transforms/src/components/PostPublished/index.js b/examples/using-javascript-transforms/src/components/PostPublished/index.js
new file mode 100644
index 0000000000000..15f9750820186
--- /dev/null
+++ b/examples/using-javascript-transforms/src/components/PostPublished/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import Helmet from 'react-helmet';
+import moment from 'moment';
+
+class PostPublished extends React.Component {
+ render() {
+ const frontmatter = this;
+
+ if (frontmatter.updated === null) {
+ var published = (
+
+
published { moment(frontmatter.written).format('D MMM YYYY') }
+
+ );
+ } else {
+ var published = (
+
+
originally published { moment(frontmatter.written).format('D MMM YYYY') } and
+ updated { moment(frontmatter.updated).format('D MMM YYYY') }
+ );
+ }
+}
+
+export default BlogPostTemplate;
diff --git a/examples/using-javascript-transforms/src/layouts/future-blog-post.js b/examples/using-javascript-transforms/src/layouts/future-blog-post.js
new file mode 100644
index 0000000000000..e0f8f96c602ab
--- /dev/null
+++ b/examples/using-javascript-transforms/src/layouts/future-blog-post.js
@@ -0,0 +1,86 @@
+/*
+We don't actually use this anywhere currently, but would love to. It makes sense to include
+ these data-type agnostic, but blog post specific components here. However, we cannot run graphql
+ queries within the layouts, so all of our data is in the children of this component. This is wrapping
+ both the markdown converted into a react component AND our javascript which is required directly.
+ Since we cannot use this component, we move these components into templates/markdown.js and each
+ javascript file will need to add these components.
+*/
+
+import React from 'react';
+import Link from 'gatsby-link';
+import Helmet from 'react-helmet';
+import moment from 'moment';
+
+class BlogPostTemplate extends React.Component {
+ render() {
+ let frontmatter = this.props.data;
+ let siteMetadata = this.props.siteMetadata;
+
+ const home = (
+
+
+
+
+ Home
+
+
+
+
+ );
+
+ if (frontmatter.updated === null) {
+ var published = (
+
+
published { moment(frontmatter.written).format('D MMM YYYY') }
+
+ );
+ } else {
+ var published = (
+
+
originally published { moment(frontmatter.written).format('D MMM YYYY') } and
+ updated { moment(frontmatter.updated).format('D MMM YYYY') }
+ );
+ }
+}
+
+export default BlogPostTemplate;
diff --git a/examples/using-javascript-transforms/src/layouts/index.js b/examples/using-javascript-transforms/src/layouts/index.js
new file mode 100644
index 0000000000000..624c71a2ea299
--- /dev/null
+++ b/examples/using-javascript-transforms/src/layouts/index.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import siteMetadata from '../components/metadata.yaml';
+import '../static/css/base.scss';
+require(`prismjs/themes/prism-coy.css`)
+
+import InsetPage from './inset-page';
+import BlogPost from './blog-post';
+
+class MasterLayout extends React.Component {
+ static propTypes = {
+ location: PropTypes.object.isRequired
+ }
+
+ render() {
+ let location = this.props.location.pathname;
+ let jimmyPage // you jimmy a lock until it opens, so same thing here ;)
+ // It would be ideal to run a graphql query to use the layoutType, but
+ // layouts do not yet have that support.
+ if (location === '/') {
+ jimmyPage = this.props.children()
+ } else if (location === '/about' || location === '/contact') {
+ jimmyPage =
+ } else {
+ jimmyPage =
+ };
+
+ return (
+
+
+ { jimmyPage }
+
+ );
+ }
+}
+
+export default MasterLayout;
+
+// this is a placeholder, does not actually work
+export const pageQuery = graphql`
+ query LayoutBySlug($slug: String!) {
+ allJsFrontmatter {
+ edges {
+ node {
+ data {
+ layoutType
+ }
+ }
+ }
+ }
+ markdownRemark(fields: { slug: { eq: $slug }}) {
+ html
+ frontmatter {
+ layoutType
+ }
+ }
+ }
+`
diff --git a/examples/using-javascript-transforms/src/layouts/inset-page.js b/examples/using-javascript-transforms/src/layouts/inset-page.js
new file mode 100644
index 0000000000000..f2db887f084c3
--- /dev/null
+++ b/examples/using-javascript-transforms/src/layouts/inset-page.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import SiteSidebar from '../components/SiteSidebar';
+// It would be nice to have props with data that we
+// that we could push down into the sidebar. We can't
+// so we just cheat and pull it out of a yaml for the
+// time being.
+
+class InsetPageTemplate extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+ { this.props.children() }
+
+
+
+
+
+ )
+ }
+}
+
+export default InsetPageTemplate;
diff --git a/examples/using-javascript-transforms/src/mainPages/404.md b/examples/using-javascript-transforms/src/mainPages/404.md
new file mode 100644
index 0000000000000..43cffaa6b802d
--- /dev/null
+++ b/examples/using-javascript-transforms/src/mainPages/404.md
@@ -0,0 +1,7 @@
+---
+path: /404.html
+layoutType: page
+---
+# NOT FOUND
+
+You just hit a route that doesn't exist... the panda is sad.
diff --git a/examples/using-javascript-transforms/src/mainPages/contact.js b/examples/using-javascript-transforms/src/mainPages/contact.js
new file mode 100644
index 0000000000000..95d0abdbe48c7
--- /dev/null
+++ b/examples/using-javascript-transforms/src/mainPages/contact.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import Helmet from 'react-helmet';
+import SiteLinks from '../components/SiteLinks';
+
+exports.data = {
+ layoutType: 'page',
+ path: 'contact'
+}
+
+class ContactMe extends React.Component {
+ render() {
+
+ return (
+
+
+ I would love to hear from you!
+
+
+
+ );
+ }
+}
+
+export default ContactMe;
diff --git a/examples/using-javascript-transforms/src/mainPages/index.js b/examples/using-javascript-transforms/src/mainPages/index.js
new file mode 100644
index 0000000000000..bd76bc6c64b15
--- /dev/null
+++ b/examples/using-javascript-transforms/src/mainPages/index.js
@@ -0,0 +1,139 @@
+import React from 'react';
+import Link from 'gatsby-link';
+import Helmet from 'react-helmet';
+import sortBy from 'lodash/sortBy';
+import moment from 'moment';
+import SiteSidebar from '../components/SiteSidebar';
+import siteMetadata from '../components/metadata.yaml';
+
+
+class SiteIndex extends React.Component {
+ render() {
+ this.props.data.siteMetadata = {...siteMetadata}
+
+ const pageLinks = []
+ let iteratorKey = 0
+ let pageRaw = [
+ ...this.props.data.allMarkdownRemark.edges,
+ ...this.props.data.allJsFrontmatter.edges
+ ]
+ let pageArray = []
+ pageRaw.forEach(page => {
+ if (typeof page.node.frontmatter === "object") {
+ if (typeof page.node.frontmatter.written != "undefined") {
+ pageArray.push(page.node.frontmatter)
+ }
+ } else if (typeof page.node.data === "object") {
+ if (typeof page.node.data.written != "undefined" && page.node.data.written != "") {
+ pageArray.push(page.node.data)
+ }
+ }
+ })
+
+ const sortedPages = sortBy(pageArray, (page) => page.updated || page.written).reverse()
+ sortedPages.forEach((page) => {
+ let frontmatter = page;
+
+ if (frontmatter.layoutType == 'post') {
+ iteratorKey += 1;
+ pageLinks.push(
+
+ )
+ }
+}
+
+export default markdownTemplate;
+
+export const pageQuery = graphql`
+ query futuremarkdownTemplateBySlug($slug: String!) {
+ markdownRemark(fields: { slug: { eq: $slug }}) {
+ html
+ frontmatter {
+ title
+ }
+ }
+ }
+`
diff --git a/examples/using-javascript-transforms/src/templates/markdown.js b/examples/using-javascript-transforms/src/templates/markdown.js
new file mode 100644
index 0000000000000..c929717972931
--- /dev/null
+++ b/examples/using-javascript-transforms/src/templates/markdown.js
@@ -0,0 +1,52 @@
+/*
+This is a temporary holdover until we can get queries on layouts. We only use markdown for posts
+ so we just put some of the layout type things in here such as react-helmet and post publish comments.
+*/
+
+
+import React from 'react';
+import moment from 'moment';
+import PostPublished from '../components/PostPublished';
+import HelmetBlock from '../components/HelmetBlock';
+
+class markdownTemplate extends React.Component {
+ render() {
+ const data = this.props.data.markdownRemark;
+ const html = data.html;
+ const frontmatter = data.frontmatter;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default markdownTemplate;
+
+export const pageQuery = graphql`
+ query markdownTemplateBySlug($slug: String!) {
+ markdownRemark(fields: { slug: { eq: $slug }}) {
+ html
+ frontmatter {
+ title
+ path
+ layoutType
+ written
+ updated
+ what
+ category
+ description
+ }
+ }
+ }
+`
diff --git a/packages/gatsby-transformer-javascript-static-exports/README.md b/packages/gatsby-transformer-javascript-static-exports/README.md
index 112981d86aa70..701948a24480a 100644
--- a/packages/gatsby-transformer-javascript-static-exports/README.md
+++ b/packages/gatsby-transformer-javascript-static-exports/README.md
@@ -50,7 +50,7 @@ export const data = {
## How to query
-You'd be able to query your letters like:
+You'd be able to query your data like:
```graphql
{