diff --git a/e2e-tests/development-runtime/cypress/integration/slices/slice-via-create-page.js b/e2e-tests/development-runtime/cypress/integration/slices/slice-via-create-page.js
new file mode 100644
index 0000000000000..3e0b4cd6cecb5
--- /dev/null
+++ b/e2e-tests/development-runtime/cypress/integration/slices/slice-via-create-page.js
@@ -0,0 +1,25 @@
+import { allRecipes, allRecipeAuthors } from "../../../shared-data/slices"
+
+/**
+ * Test behaviour when a slice is created and passed `slices` option to createPage
+ */
+
+describe("Slice passed via createPage", () => {
+ it("Pages created with slices mapping have correct content", () => {
+ allRecipes.forEach(recipe => {
+ cy.visit(`recipe/${recipe.id}`).waitForRouteChange()
+
+ cy.getTestElement(`recipe-name`)
+ .invoke(`text`)
+ .should(`contain`, recipe.name)
+
+ cy.getTestElement(`recipe-description`)
+ .invoke(`text`)
+ .should(`contain`, recipe.description)
+
+ cy.getTestElement(`recipe-author-name`)
+ .invoke(`text`)
+ .should(`contain`, allRecipeAuthors.find(author => recipe.authorId === author.id).name)
+ })
+ })
+})
diff --git a/e2e-tests/development-runtime/cypress/integration/slices/slices.js b/e2e-tests/development-runtime/cypress/integration/slices/slices.js
new file mode 100644
index 0000000000000..5e2ec4156b110
--- /dev/null
+++ b/e2e-tests/development-runtime/cypress/integration/slices/slices.js
@@ -0,0 +1,33 @@
+/**
+ * Test basic Slices API behaviour like context, props, ....
+ */
+
+describe(`Slices`, () => {
+ beforeEach(() => {
+ cy.visit(`/`).waitForRouteChange()
+ })
+
+ it(`Slice content show on screen`, () => {
+ cy.getTestElement(`footer-static-text`)
+ .invoke(`text`)
+ .should(`contain`, `Built with`)
+ })
+
+ it(`Slice recieves context passed via createSlice`, () => {
+ cy.getTestElement(`footer-slice-context-value`)
+ .invoke(`text`)
+ .should(`contain`, `Gatsby`)
+ })
+
+ it(`Slice can take in props`, () => {
+ cy.getTestElement(`footer-props`)
+ .invoke(`text`)
+ .should(`contains`, `Gatsbyjs`)
+ })
+
+ it(`Slice can consume a context wrapped in WrapRootElement`, () => {
+ cy.getTestElement(`footer-context-derieved-value`)
+ .invoke(`text`)
+ .should(`contain`, `2`)
+ })
+})
diff --git a/e2e-tests/development-runtime/gatsby-node.js b/e2e-tests/development-runtime/gatsby-node.js
index d4fc084389919..591abf013737f 100644
--- a/e2e-tests/development-runtime/gatsby-node.js
+++ b/e2e-tests/development-runtime/gatsby-node.js
@@ -1,6 +1,7 @@
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
const headFunctionExportSharedData = require("./shared-data/head-function-export")
+const slicesData = require("./shared-data/slices")
const {
addRemoteFilePolyfillInterface,
polyfillImageServiceDevRoutes,
@@ -37,7 +38,8 @@ exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
const items = [
{
name: "photoA.jpg",
- url: "https://images.unsplash.com/photo-1517849845537-4d257902454a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2000&q=80",
+ url:
+ "https://images.unsplash.com/photo-1517849845537-4d257902454a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2000&q=80",
placeholderUrl:
"https://images.unsplash.com/photo-1517849845537-4d257902454a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=%width%&h=%height%",
mimeType: "image/jpg",
@@ -47,7 +49,8 @@ exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
},
{
name: "photoB.jpg",
- url: "https://images.unsplash.com/photo-1552053831-71594a27632d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&h=2000&q=10",
+ url:
+ "https://images.unsplash.com/photo-1552053831-71594a27632d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&h=2000&q=10",
mimeType: "image/jpg",
filename: "photo-1552053831.jpg",
width: 1247,
@@ -55,7 +58,8 @@ exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
},
{
name: "photoC.jpg",
- url: "https://images.unsplash.com/photo-1561037404-61cd46aa615b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2000&q=80",
+ url:
+ "https://images.unsplash.com/photo-1561037404-61cd46aa615b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2000&q=80",
placeholderUrl:
"https://images.unsplash.com/photo-1561037404-61cd46aa615b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=%width%&h=%height%",
mimeType: "image/jpg",
@@ -122,7 +126,7 @@ exports.onCreateNode = function onCreateNode({
* @type {import('gatsby').createPages}
*/
exports.createPages = async function createPages({
- actions: { createPage, createRedirect },
+ actions: { createPage, createRedirect, createSlice },
graphql,
}) {
const { data } = await graphql(`
@@ -172,6 +176,41 @@ exports.createPages = async function createPages({
})
})
+ //-------------------------Slice API----------------------------
+ createSlice({
+ id: `footer`,
+ component: path.resolve(`./src/components/footer.js`),
+ context: {
+ framework: slicesData.framework,
+ },
+ })
+
+ slicesData.allRecipeAuthors.forEach(({ id, name }) => {
+ createSlice({
+ id: `author-${id}`,
+ component: path.resolve(`./src/components/recipe-author.js`),
+ context: {
+ name,
+ id,
+ },
+ })
+ })
+
+ slicesData.allRecipes.forEach(({ authorId, id, name, description }) => {
+ createPage({
+ path: `/recipe/${id}`,
+ component: path.resolve(`./src/templates/recipe.js`),
+ context: {
+ description: description,
+ name,
+ },
+ slices: {
+ author: `author-${authorId}`,
+ },
+ })
+ })
+ //---------------------------------------------------------------
+
createPage({
path: `/안녕`,
component: path.resolve(`src/pages/page-2.js`),
diff --git a/e2e-tests/development-runtime/shared-data/slices.js b/e2e-tests/development-runtime/shared-data/slices.js
new file mode 100644
index 0000000000000..651111121772e
--- /dev/null
+++ b/e2e-tests/development-runtime/shared-data/slices.js
@@ -0,0 +1,25 @@
+const allRecipes = [
+ {
+ id: "r1",
+ name: "Jollof Rice",
+ description:
+ "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
+ authorId: "a-1",
+ },
+ {
+ id: "r2",
+ name: "Ewa Agoyin",
+ description:
+ "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
+ authorId: "a-2",
+ },
+]
+
+const allRecipeAuthors = [
+ { id: "a-1", name: "Jude" },
+ { id: "a-2", name: "Ty" },
+]
+
+const framework = "Gatsby"
+
+module.exports = { allRecipes, allRecipeAuthors, framework }
diff --git a/e2e-tests/development-runtime/src/components/footer.js b/e2e-tests/development-runtime/src/components/footer.js
new file mode 100644
index 0000000000000..d95fc72ff6266
--- /dev/null
+++ b/e2e-tests/development-runtime/src/components/footer.js
@@ -0,0 +1,23 @@
+import React, { useContext } from "react"
+import { ContextForSlices } from "../context-for-slices"
+
+// Use as a Slice
+function Footer({ framework, lang, sliceContext: { framework: frameworkViaContext }}) {
+ const { posts } = useContext(ContextForSlices)
+
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/e2e-tests/development-runtime/src/components/layout.js b/e2e-tests/development-runtime/src/components/layout.js
index ce4f8f7938738..f834dc8d6b563 100644
--- a/e2e-tests/development-runtime/src/components/layout.js
+++ b/e2e-tests/development-runtime/src/components/layout.js
@@ -1,6 +1,6 @@
import React from "react"
import PropTypes from "prop-types"
-import { StaticQuery, graphql } from "gatsby"
+import { StaticQuery, graphql, Slice } from "gatsby"
import Header from "./header"
import "./layout.css"
@@ -29,6 +29,12 @@ const Layout = ({ children }) => (
>
{children}
+
+
+ {/** The slice below doesn't exist but it shouldn't break build */}
+
+
+ {/** Insert this here and expect it to fail */}
>
)}
/>
diff --git a/e2e-tests/development-runtime/src/components/recipe-author.js b/e2e-tests/development-runtime/src/components/recipe-author.js
new file mode 100644
index 0000000000000..7d5f0ec09ba53
--- /dev/null
+++ b/e2e-tests/development-runtime/src/components/recipe-author.js
@@ -0,0 +1,15 @@
+import React from "react"
+
+// Use as a Slice
+function RecipeAuthor({ sliceContext: { name } }) {
+ return (
+
+ Written by{" "}
+
+ {name}
+
+
+ )
+}
+
+export default RecipeAuthor
diff --git a/e2e-tests/development-runtime/src/context-for-slices.js b/e2e-tests/development-runtime/src/context-for-slices.js
new file mode 100644
index 0000000000000..ac82422b7635f
--- /dev/null
+++ b/e2e-tests/development-runtime/src/context-for-slices.js
@@ -0,0 +1,25 @@
+import React from "react"
+
+const ContextForSlices = React.createContext()
+
+const ContextForSlicesProvider = ({ children }) => {
+ const contextValue = {
+ posts: [
+ {
+ title: "My first blog post",
+ content: "This is my first blog post",
+ },
+ {
+ title: "My second blog post",
+ content: "This is my second blog post",
+ },
+ ],
+ }
+ return (
+
+ {children}
+
+ )
+}
+
+export { ContextForSlices, ContextForSlicesProvider }
diff --git a/e2e-tests/development-runtime/src/templates/recipe.js b/e2e-tests/development-runtime/src/templates/recipe.js
new file mode 100644
index 0000000000000..5f2f3377e29b4
--- /dev/null
+++ b/e2e-tests/development-runtime/src/templates/recipe.js
@@ -0,0 +1,15 @@
+import React from "react"
+import { Slice } from "gatsby"
+import Layout from "../components/layout"
+
+const Recipe = ({ pageContext: { description, name } }) => {
+ return (
+
+ {name}
+ {description}
+
+
+ )
+}
+
+export default Recipe
diff --git a/e2e-tests/development-runtime/src/wrap-root-element.js b/e2e-tests/development-runtime/src/wrap-root-element.js
index 0eadae8422525..a136c16c29094 100644
--- a/e2e-tests/development-runtime/src/wrap-root-element.js
+++ b/e2e-tests/development-runtime/src/wrap-root-element.js
@@ -1,6 +1,7 @@
import React from "react"
import { StaticQuery, graphql, Script } from "gatsby"
import { scripts } from "../gatsby-script-scripts"
+import { ContextForSlicesProvider } from "./context-for-slices"
const WrapRootElement = ({ element }) => (
(
siteMetadata: { title },
},
}) => (
- <>
+
{element}
@@ -29,7 +30,7 @@ const WrapRootElement = ({ element }) => (
%TEST_HMR_IN_GATSBY_BROWSER%
- >
+
)}
/>
)
diff --git a/e2e-tests/production-runtime/cypress/integration/slices/slice-via-create-page.js b/e2e-tests/production-runtime/cypress/integration/slices/slice-via-create-page.js
new file mode 100644
index 0000000000000..3e0b4cd6cecb5
--- /dev/null
+++ b/e2e-tests/production-runtime/cypress/integration/slices/slice-via-create-page.js
@@ -0,0 +1,25 @@
+import { allRecipes, allRecipeAuthors } from "../../../shared-data/slices"
+
+/**
+ * Test behaviour when a slice is created and passed `slices` option to createPage
+ */
+
+describe("Slice passed via createPage", () => {
+ it("Pages created with slices mapping have correct content", () => {
+ allRecipes.forEach(recipe => {
+ cy.visit(`recipe/${recipe.id}`).waitForRouteChange()
+
+ cy.getTestElement(`recipe-name`)
+ .invoke(`text`)
+ .should(`contain`, recipe.name)
+
+ cy.getTestElement(`recipe-description`)
+ .invoke(`text`)
+ .should(`contain`, recipe.description)
+
+ cy.getTestElement(`recipe-author-name`)
+ .invoke(`text`)
+ .should(`contain`, allRecipeAuthors.find(author => recipe.authorId === author.id).name)
+ })
+ })
+})
diff --git a/e2e-tests/production-runtime/cypress/integration/slices/slices.js b/e2e-tests/production-runtime/cypress/integration/slices/slices.js
new file mode 100644
index 0000000000000..89b60cb89f9df
--- /dev/null
+++ b/e2e-tests/production-runtime/cypress/integration/slices/slices.js
@@ -0,0 +1,44 @@
+/**
+ * Test basic Slices API behaviour like context, props, ....
+ */
+
+Cypress.on(`uncaught:exception`, err => {
+ if (
+ (err.message.includes(`Minified React error #418`) ||
+ err.message.includes(`Minified React error #423`) ||
+ err.message.includes(`Minified React error #425`)) &&
+ Cypress.env(`TEST_PLUGIN_OFFLINE`)
+ ) {
+ return false
+ }
+})
+
+describe(`Slices`, () => {
+ beforeEach(() => {
+ cy.visit(`/`).waitForRouteChange()
+ })
+
+ it(`Slice content show on screen`, () => {
+ cy.getTestElement(`footer-static-text`)
+ .invoke(`text`)
+ .should(`contain`, `Built with`)
+ })
+
+ it(`Slice recieves context passed via createSlice`, () => {
+ cy.getTestElement(`footer-slice-context-value`)
+ .invoke(`text`)
+ .should(`contain`, `Gatsby`)
+ })
+
+ it(`Slice can take in props`, () => {
+ cy.getTestElement(`footer-props`)
+ .invoke(`text`)
+ .should(`contains`, `Gatsbyjs`)
+ })
+
+ it(`Slice can consume a context wrapped in WrapRootElement`, () => {
+ cy.getTestElement(`footer-context-derieved-value`)
+ .invoke(`text`)
+ .should(`contain`, `2`)
+ })
+})
diff --git a/e2e-tests/production-runtime/gatsby-node.ts b/e2e-tests/production-runtime/gatsby-node.ts
index 3d14eaea677df..17c6e7242e3ee 100644
--- a/e2e-tests/production-runtime/gatsby-node.ts
+++ b/e2e-tests/production-runtime/gatsby-node.ts
@@ -3,6 +3,7 @@ import * as fs from "fs-extra"
import { createContentDigest } from "gatsby-core-utils"
import { addRemoteFilePolyfillInterface } from "gatsby-plugin-utils/polyfill-remote-file"
import type { GatsbyNode } from "gatsby"
+import slicesData from "./shared-data/slices"
export const onPreBootstrap: GatsbyNode["onPreBootstrap"] = () => {
fs.copyFileSync(
@@ -124,8 +125,45 @@ export const sourceNodes: GatsbyNode["sourceNodes"] = ({
}
export const createPages: GatsbyNode["createPages"] = ({
- actions: { createPage, createRedirect },
+ actions: { createPage, createRedirect, createSlice },
}) => {
+
+ //-------------------------Slices API----------------------------
+ createSlice({
+ id: `footer`,
+ component: path.resolve(`./src/components/footer.js`),
+ context: {
+ framework: slicesData.framework
+ },
+ })
+
+ slicesData.allRecipeAuthors.forEach(({ id, name }) => {
+ createSlice({
+ id: `author-${id}`,
+ component: path.resolve(`./src/components/recipe-author.js`),
+ context: {
+ name,
+ id,
+ },
+ })
+ })
+
+ slicesData.allRecipes.forEach(({ authorId, id, name, description }) => {
+ createPage({
+ path: `/recipe/${id}`,
+ component: path.resolve(`./src/templates/recipe.js`),
+ context: {
+ description: description,
+ name,
+ },
+ slices: {
+ author: `author-${authorId}`,
+ },
+ })
+ })
+
+ //---------------------------------------------------------------
+
createPage({
path: `/안녕`,
component: path.resolve(`src/pages/page-2.js`),
diff --git a/e2e-tests/production-runtime/shared-data/slices.js b/e2e-tests/production-runtime/shared-data/slices.js
new file mode 100644
index 0000000000000..651111121772e
--- /dev/null
+++ b/e2e-tests/production-runtime/shared-data/slices.js
@@ -0,0 +1,25 @@
+const allRecipes = [
+ {
+ id: "r1",
+ name: "Jollof Rice",
+ description:
+ "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
+ authorId: "a-1",
+ },
+ {
+ id: "r2",
+ name: "Ewa Agoyin",
+ description:
+ "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).",
+ authorId: "a-2",
+ },
+]
+
+const allRecipeAuthors = [
+ { id: "a-1", name: "Jude" },
+ { id: "a-2", name: "Ty" },
+]
+
+const framework = "Gatsby"
+
+module.exports = { allRecipes, allRecipeAuthors, framework }
diff --git a/e2e-tests/production-runtime/src/components/footer.js b/e2e-tests/production-runtime/src/components/footer.js
new file mode 100644
index 0000000000000..d95fc72ff6266
--- /dev/null
+++ b/e2e-tests/production-runtime/src/components/footer.js
@@ -0,0 +1,23 @@
+import React, { useContext } from "react"
+import { ContextForSlices } from "../context-for-slices"
+
+// Use as a Slice
+function Footer({ framework, lang, sliceContext: { framework: frameworkViaContext }}) {
+ const { posts } = useContext(ContextForSlices)
+
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/e2e-tests/production-runtime/src/components/layout.js b/e2e-tests/production-runtime/src/components/layout.js
index 2659ce0cf585a..9207b6f582c68 100644
--- a/e2e-tests/production-runtime/src/components/layout.js
+++ b/e2e-tests/production-runtime/src/components/layout.js
@@ -1,6 +1,6 @@
import * as React from "react"
import PropTypes from "prop-types"
-import { StaticQuery, graphql } from "gatsby"
+import { StaticQuery, graphql, Slice } from "gatsby"
import Header from "./header"
import "./layout.css"
@@ -29,6 +29,10 @@ const Layout = ({ children }) => (
>
{children}
+
+
+ {/** The slice below doesn't exist but it shouldn't break build */}
+
)}
/>
diff --git a/e2e-tests/production-runtime/src/components/recipe-author.js b/e2e-tests/production-runtime/src/components/recipe-author.js
new file mode 100644
index 0000000000000..7d5f0ec09ba53
--- /dev/null
+++ b/e2e-tests/production-runtime/src/components/recipe-author.js
@@ -0,0 +1,15 @@
+import React from "react"
+
+// Use as a Slice
+function RecipeAuthor({ sliceContext: { name } }) {
+ return (
+
+ Written by{" "}
+
+ {name}
+
+
+ )
+}
+
+export default RecipeAuthor
diff --git a/e2e-tests/production-runtime/src/context-for-slices.js b/e2e-tests/production-runtime/src/context-for-slices.js
new file mode 100644
index 0000000000000..ac82422b7635f
--- /dev/null
+++ b/e2e-tests/production-runtime/src/context-for-slices.js
@@ -0,0 +1,25 @@
+import React from "react"
+
+const ContextForSlices = React.createContext()
+
+const ContextForSlicesProvider = ({ children }) => {
+ const contextValue = {
+ posts: [
+ {
+ title: "My first blog post",
+ content: "This is my first blog post",
+ },
+ {
+ title: "My second blog post",
+ content: "This is my second blog post",
+ },
+ ],
+ }
+ return (
+
+ {children}
+
+ )
+}
+
+export { ContextForSlices, ContextForSlicesProvider }
diff --git a/e2e-tests/production-runtime/src/pages/static-query.js b/e2e-tests/production-runtime/src/pages/static-query.js
index c9e6c6daa9ea0..c2a33c344b168 100644
--- a/e2e-tests/production-runtime/src/pages/static-query.js
+++ b/e2e-tests/production-runtime/src/pages/static-query.js
@@ -1,27 +1,29 @@
-import * as React from "react"
+import React, { useContext } from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"
import * as StaticQuery from "../components/static-query"
import * as UseStaticQuery from "../components/static-query/use-static-query"
-const StaticQueryPage = () => (
-
-
- StaticQuery
-
-
-
-
-
- useStaticQuery
-
-
-
-
-
-
-)
+const StaticQueryPage = () => {
+ return (
+
+
+ StaticQuery
+
+
+
+
+
+ useStaticQuery
+
+
+
+
+
+
+ )
+}
export const Head = () =>
diff --git a/e2e-tests/production-runtime/src/templates/recipe.js b/e2e-tests/production-runtime/src/templates/recipe.js
new file mode 100644
index 0000000000000..5f2f3377e29b4
--- /dev/null
+++ b/e2e-tests/production-runtime/src/templates/recipe.js
@@ -0,0 +1,15 @@
+import React from "react"
+import { Slice } from "gatsby"
+import Layout from "../components/layout"
+
+const Recipe = ({ pageContext: { description, name } }) => {
+ return (
+
+ {name}
+ {description}
+
+
+ )
+}
+
+export default Recipe
diff --git a/e2e-tests/production-runtime/src/wrap-page-element.js b/e2e-tests/production-runtime/src/wrap-page-element.js
index dc999aabafb36..46976bc21ae05 100644
--- a/e2e-tests/production-runtime/src/wrap-page-element.js
+++ b/e2e-tests/production-runtime/src/wrap-page-element.js
@@ -1,6 +1,7 @@
import * as React from "react"
import { Script } from "gatsby"
import { scripts } from "../gatsby-script-scripts"
+import { WrapRootTesterComponent } from "./wrap-root-context"
const gatsbyScriptTestPage = `/gatsby-script-ssr-browser-apis/`
@@ -14,6 +15,7 @@ export default ({ element, props }) => {
>
)}
+
>
)
}
diff --git a/e2e-tests/production-runtime/src/wrap-root-context.js b/e2e-tests/production-runtime/src/wrap-root-context.js
new file mode 100644
index 0000000000000..ad36d55252a76
--- /dev/null
+++ b/e2e-tests/production-runtime/src/wrap-root-context.js
@@ -0,0 +1,14 @@
+import React, { createContext, useContext } from "react"
+
+export const WrapRootContext = createContext({})
+
+export function WrapRootTesterComponent() {
+ const { title } = useContext(WrapRootContext)
+
+ return (
+
+ StaticQuery in wrapRootElement test (should show site title):
+ {title}
+
+ )
+}
diff --git a/e2e-tests/production-runtime/src/wrap-root-element.js b/e2e-tests/production-runtime/src/wrap-root-element.js
index 7e13e1bb02ac3..40c2f416cc397 100644
--- a/e2e-tests/production-runtime/src/wrap-root-element.js
+++ b/e2e-tests/production-runtime/src/wrap-root-element.js
@@ -1,6 +1,8 @@
import * as React from "react"
import { StaticQuery, graphql, Script } from "gatsby"
import { scripts } from "../gatsby-script-scripts"
+import { ContextForSlicesProvider } from "./context-for-slices"
+import { WrapRootContext } from "./wrap-root-context"
export default ({ element }) => {
return (
@@ -19,15 +21,18 @@ export default ({ element }) => {
siteMetadata: { title },
},
}) => (
- <>
- {element}
-
-
-
- StaticQuery in wrapRootElement test (should show site title):
- {title}
-
- >
+
+ {/* Instead of adding UI markup here, we just set provider
+ and actual UI is rendered in wrapPageElement. WrapRootElement
+ should never create UI elements as it will cause problems
+ for slices - it should only setup providers or components
+ resulting in side effects (for example Script components) */}
+
+ {element}
+
+
+
+
)}
/>
)
diff --git a/packages/gatsby/cache-dir/root.js b/packages/gatsby/cache-dir/root.js
index e9bf2eda942c6..772be8b8318ff 100644
--- a/packages/gatsby/cache-dir/root.js
+++ b/packages/gatsby/cache-dir/root.js
@@ -90,20 +90,30 @@ class LocationHandler extends React.Component {
}
return (
-
-
-
-
-
+
+ {locationAndPageResources => (
+
+
+
+
+
+
+
+
+
+ )}
+
)
}
}
diff --git a/packages/gatsby/cache-dir/static-entry.js b/packages/gatsby/cache-dir/static-entry.js
index 55b143c226be8..475170c202652 100644
--- a/packages/gatsby/cache-dir/static-entry.js
+++ b/packages/gatsby/cache-dir/static-entry.js
@@ -556,12 +556,9 @@ export async function renderSlice({ slice, staticQueryContext, props = {} }) {
}
const sliceElement = (
-
-
-
-
-
+
)
+
const sliceWrappedWithWrapRootElement = apiRunner(
`wrapRootElement`,
{ element: sliceElement },
@@ -571,15 +568,26 @@ export async function renderSlice({ slice, staticQueryContext, props = {} }) {
}
).pop()
+ const sliceWrappedWithWrapRootElementAndContexts = (
+
+
+ {sliceWrappedWithWrapRootElement}
+
+
+ )
+
const writableStream = new WritableAsPromise()
- const { pipe } = renderToPipeableStream(sliceWrappedWithWrapRootElement, {
- onAllReady() {
- pipe(writableStream)
- },
- onError(error) {
- writableStream.destroy(error)
- },
- })
+ const { pipe } = renderToPipeableStream(
+ sliceWrappedWithWrapRootElementAndContexts,
+ {
+ onAllReady() {
+ pipe(writableStream)
+ },
+ onError(error) {
+ writableStream.destroy(error)
+ },
+ }
+ )
return await writableStream
}