diff --git a/integration-tests/ssr/__mocks__/styleMock.js b/integration-tests/ssr/__mocks__/styleMock.js
new file mode 100644
index 0000000000000..4ba52ba2c8df6
--- /dev/null
+++ b/integration-tests/ssr/__mocks__/styleMock.js
@@ -0,0 +1 @@
+module.exports = {}
diff --git a/integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap b/integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap
index 716569a481059..fb84baa33e754 100644
--- a/integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap
+++ b/integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`SSR is run for a page when it is requested 1`] = `"
"`;
+exports[`SSR is run for a page when it is requested 1`] = `" "`;
exports[`SSR it generates an error page correctly 1`] = `
"
diff --git a/integration-tests/ssr/__tests__/ssr.js b/integration-tests/ssr/__tests__/ssr.js
index c09a8923a5bcf..25b0cccd0d95e 100644
--- a/integration-tests/ssr/__tests__/ssr.js
+++ b/integration-tests/ssr/__tests__/ssr.js
@@ -53,5 +53,5 @@ describe(`SSR`, () => {
}, 400)
}, 400)
})
- })
+ }, 15000)
})
diff --git a/integration-tests/ssr/gatsby-browser.js b/integration-tests/ssr/gatsby-browser.js
new file mode 100644
index 0000000000000..d30aa3ea07cd5
--- /dev/null
+++ b/integration-tests/ssr/gatsby-browser.js
@@ -0,0 +1 @@
+import "./sample.css"
diff --git a/integration-tests/ssr/gatsby-config.js b/integration-tests/ssr/gatsby-config.js
index cc785a9507538..7227b35bf4e61 100644
--- a/integration-tests/ssr/gatsby-config.js
+++ b/integration-tests/ssr/gatsby-config.js
@@ -6,5 +6,5 @@ module.exports = {
github: `sidharthachatterjee`,
moreInfo: `Sid is amazing`,
},
- plugins: [],
+ plugins: ["gatsby-plugin-postcss"],
}
diff --git a/integration-tests/ssr/jest.config.js b/integration-tests/ssr/jest.config.js
index 4e5a78b25d7bf..4abafe97fa92b 100644
--- a/integration-tests/ssr/jest.config.js
+++ b/integration-tests/ssr/jest.config.js
@@ -1,3 +1,14 @@
module.exports = {
- testPathIgnorePatterns: [`/node_modules/`, `__tests__/fixtures`, `.cache`],
+ testPathIgnorePatterns: [
+ `/node_modules/`,
+ `__tests__/fixtures`,
+ `.cache`,
+ `src/test`,
+ ],
+ transform: {
+ "^.+\\.[jt]sx?$": `../../jest-transformer.js`,
+ },
+ moduleNameMapper: {
+ "\\.(css)$": `/__mocks__/styleMock.js`,
+ },
}
diff --git a/integration-tests/ssr/package.json b/integration-tests/ssr/package.json
index f0b0b554ac26b..37b7c68e5e71f 100644
--- a/integration-tests/ssr/package.json
+++ b/integration-tests/ssr/package.json
@@ -8,8 +8,10 @@
},
"dependencies": {
"gatsby": "^2.27.0",
+ "gatsby-plugin-postcss": "^3.3.0",
"react": "^16.12.0",
- "react-dom": "^16.12.0"
+ "react-dom": "^16.12.0",
+ "tailwindcss": "1"
},
"devDependencies": {
"cross-env": "^5.0.2",
diff --git a/integration-tests/ssr/postcss.config.js b/integration-tests/ssr/postcss.config.js
new file mode 100644
index 0000000000000..fd147b48a0bdd
--- /dev/null
+++ b/integration-tests/ssr/postcss.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ plugins: [require("tailwindcss"), require("autoprefixer")],
+}
\ No newline at end of file
diff --git a/integration-tests/ssr/sample.css b/integration-tests/ssr/sample.css
new file mode 100644
index 0000000000000..bfbad3af78724
--- /dev/null
+++ b/integration-tests/ssr/sample.css
@@ -0,0 +1,6 @@
+body {
+ background: tomato;
+ color: black;
+ font-style: italic;
+ font-weight: 400;
+}
diff --git a/integration-tests/ssr/src/pages/index.js b/integration-tests/ssr/src/pages/index.js
index 850e99d4638bb..d2b3ba51878af 100644
--- a/integration-tests/ssr/src/pages/index.js
+++ b/integration-tests/ssr/src/pages/index.js
@@ -1,5 +1,6 @@
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
+const lazyImport = import(`../test`)
export default function Inline() {
const { site } = useStaticQuery(graphql`
@@ -11,5 +12,9 @@ export default function Inline() {
}
}
`)
- return {site.siteMetadata.title}
+ return (
+
+
{site.siteMetadata.title}
+
+ )
}
diff --git a/integration-tests/ssr/src/pages/usingtailwind.js b/integration-tests/ssr/src/pages/usingtailwind.js
new file mode 100644
index 0000000000000..5baf5467e110b
--- /dev/null
+++ b/integration-tests/ssr/src/pages/usingtailwind.js
@@ -0,0 +1,4 @@
+import React from "react"
+import "../styles/tailwind.css"
+
+export default () => This is a 3xl text
diff --git a/integration-tests/ssr/src/styles/tailwind.css b/integration-tests/ssr/src/styles/tailwind.css
new file mode 100644
index 0000000000000..7f393742af264
--- /dev/null
+++ b/integration-tests/ssr/src/styles/tailwind.css
@@ -0,0 +1,5 @@
+@tailwind base;
+
+@tailwind components;
+
+@tailwind utilities;
diff --git a/integration-tests/ssr/src/test.css b/integration-tests/ssr/src/test.css
new file mode 100644
index 0000000000000..7e6738ee67d84
--- /dev/null
+++ b/integration-tests/ssr/src/test.css
@@ -0,0 +1,3 @@
+.hi {
+ color: blue;
+}
diff --git a/integration-tests/ssr/src/test.js b/integration-tests/ssr/src/test.js
new file mode 100644
index 0000000000000..1a0ddf56abf67
--- /dev/null
+++ b/integration-tests/ssr/src/test.js
@@ -0,0 +1 @@
+import "./test.css"
diff --git a/integration-tests/ssr/tailwind.config.js b/integration-tests/ssr/tailwind.config.js
new file mode 100644
index 0000000000000..de644ca9ab7cf
--- /dev/null
+++ b/integration-tests/ssr/tailwind.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ purge: [
+ './src/**/*.js',
+ ],
+ theme: {
+ extend: {}
+ },
+ variants: {},
+ plugins: []
+}
diff --git a/integration-tests/ssr/test-output.js b/integration-tests/ssr/test-output.js
index d785a42f32c7e..34664599e4ce4 100644
--- a/integration-tests/ssr/test-output.js
+++ b/integration-tests/ssr/test-output.js
@@ -21,8 +21,10 @@
const $ = cheerio.load(htmlStr)
// There are many script tag differences
$(`script`).remove()
- // Only added in production. Dev uses css-loader
+ // Only added in production
$(`#gatsby-global-css`).remove()
+ // Only added in development
+ $(`link[data-identity='gatsby-dev-css']`).remove()
// Only in prod
$(`link[rel="preload"]`).remove()
// Only in prod
diff --git a/packages/gatsby/cache-dir/__tests__/static-entry.js b/packages/gatsby/cache-dir/__tests__/static-entry.js
index 4f26a9c6bb6dd..e6c4c1351f650 100644
--- a/packages/gatsby/cache-dir/__tests__/static-entry.js
+++ b/packages/gatsby/cache-dir/__tests__/static-entry.js
@@ -2,7 +2,6 @@ import React from "react"
import fs from "fs"
const { join } = require(`path`)
-import ssrDevelopStaticEntry from "../ssr-develop-static-entry"
import developStaticEntry from "../develop-static-entry"
jest.mock(`fs`, () => {
@@ -22,7 +21,7 @@ jest.mock(
() => {
return {
ssrComponents: {
- "page-component---src-pages-test-js": () => null,
+ "page-component---src-pages-about-js": () => null,
},
}
},
@@ -151,8 +150,27 @@ const fakeComponentsPluginFactory = type => {
}
}
+const SSR_DEV_MOCK_FILE_INFO = {
+ [`${process.cwd()}/public/webpack.stats.json`]: `{}`,
+ [join(
+ process.cwd(),
+ `/public/page-data/about/page-data.json`
+ )]: JSON.stringify({
+ componentChunkName: `page-component---src-pages-about-js`,
+ path: `/about/`,
+ webpackCompilationHash: `1234567890abcdef1234`,
+ staticQueryHashes: [],
+ }),
+ [join(process.cwd(), `/public/page-data/app-data.json`)]: JSON.stringify({
+ webpackCompilationHash: `1234567890abcdef1234`,
+ }),
+}
+
describe(`develop-static-entry`, () => {
+ let ssrDevelopStaticEntry
beforeEach(() => {
+ fs.readFileSync.mockImplementation(file => SSR_DEV_MOCK_FILE_INFO[file])
+ ssrDevelopStaticEntry = require(`../ssr-develop-static-entry`).default
global.__PATH_PREFIX__ = ``
global.__BASE_PATH__ = ``
global.__ASSET_PREFIX__ = ``
diff --git a/packages/gatsby/cache-dir/ssr-develop-static-entry.js b/packages/gatsby/cache-dir/ssr-develop-static-entry.js
index f35cb8a3db0c8..c321d307f0539 100644
--- a/packages/gatsby/cache-dir/ssr-develop-static-entry.js
+++ b/packages/gatsby/cache-dir/ssr-develop-static-entry.js
@@ -1,7 +1,7 @@
import React from "react"
import fs from "fs"
import { renderToString, renderToStaticMarkup } from "react-dom/server"
-import { merge } from "lodash"
+import { get, merge, isObject, flatten, uniqBy, concat } from "lodash"
import { join } from "path"
import apiRunner from "./api-runner-ssr"
import { grabMatchParams } from "./find-path"
@@ -20,6 +20,10 @@ const testRequireError = (moduleName, err) => {
return regex.test(firstLine)
}
+const stats = JSON.parse(
+ fs.readFileSync(`${process.cwd()}/public/webpack.stats.json`, `utf-8`)
+)
+
let Html
try {
Html = require(`../src/html`)
@@ -111,7 +115,66 @@ export default (pagePath, isClientOnlyPage, callback) => {
const pageData = getPageData(pagePath)
- const componentChunkName = pageData?.componentChunkName
+ const { componentChunkName, staticQueryHashes = [] } = pageData
+
+ let scriptsAndStyles = flatten(
+ [`commons`].map(chunkKey => {
+ const fetchKey = `assetsByChunkName[${chunkKey}]`
+
+ let chunks = get(stats, fetchKey)
+ const namedChunkGroups = get(stats, `namedChunkGroups`)
+
+ if (!chunks) {
+ return null
+ }
+
+ chunks = chunks.map(chunk => {
+ if (chunk === `/`) {
+ return null
+ }
+ return { rel: `preload`, name: chunk }
+ })
+
+ namedChunkGroups[chunkKey].assets.forEach(asset =>
+ chunks.push({ rel: `preload`, name: asset })
+ )
+
+ const childAssets = namedChunkGroups[chunkKey].childAssets
+ for (const rel in childAssets) {
+ chunks = concat(
+ chunks,
+ childAssets[rel].map(chunk => {
+ return { rel, name: chunk }
+ })
+ )
+ }
+
+ return chunks
+ })
+ )
+ .filter(s => isObject(s))
+ .sort((s1, s2) => (s1.rel == `preload` ? -1 : 1)) // given priority to preload
+
+ scriptsAndStyles = uniqBy(scriptsAndStyles, item => item.name)
+
+ const styles = scriptsAndStyles.filter(
+ style => style.name && style.name.endsWith(`.css`)
+ )
+
+ styles
+ .slice(0)
+ .reverse()
+ .forEach(style => {
+ headComponents.unshift(
+
+ )
+ })
const createElement = React.createElement
diff --git a/packages/gatsby/src/utils/webpack-utils.ts b/packages/gatsby/src/utils/webpack-utils.ts
index 869540cc7ee17..77c942504f122 100644
--- a/packages/gatsby/src/utils/webpack-utils.ts
+++ b/packages/gatsby/src/utils/webpack-utils.ts
@@ -195,12 +195,28 @@ export const createWebpackUtils = (
},
miniCssExtract: (options = {}) => {
- return {
- options,
- // use MiniCssExtractPlugin only on production builds
- loader: PRODUCTION
- ? MiniCssExtractPlugin.loader
- : require.resolve(`style-loader`),
+ if (PRODUCTION) {
+ // production always uses MiniCssExtractPlugin
+ return {
+ loader: MiniCssExtractPlugin.loader,
+ options,
+ }
+ } else if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
+ // develop with ssr also uses MiniCssExtractPlugin
+ return {
+ loader: MiniCssExtractPlugin.loader,
+ options: {
+ ...options,
+ // enable hmr for browser bundle, ssr bundle doesn't need it
+ hmr: stage === `develop`,
+ },
+ }
+ } else {
+ // develop without ssr is using style-loader
+ return {
+ loader: require.resolve(`style-loader`),
+ options,
+ }
}
},
@@ -690,8 +706,6 @@ export const createWebpackUtils = (
plugins.extractText = (options: any): Plugin =>
new MiniCssExtractPlugin({
- filename: `[name].[contenthash].css`,
- chunkFilename: `[name].[contenthash].css`,
...options,
})
diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js
index 68a5a6ecd1d7c..669d941995a28 100644
--- a/packages/gatsby/src/utils/webpack.config.js
+++ b/packages/gatsby/src/utils/webpack.config.js
@@ -230,10 +230,21 @@ module.exports = async (
plugins.eslintGraphqlSchemaReload(),
])
.filter(Boolean)
+ if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
+ // Don't use the default mini-css-extract-plugin setup as that
+ // breaks hmr.
+ configPlugins.push(
+ plugins.extractText({ filename: `[name].css` }),
+ plugins.extractStats()
+ )
+ }
break
case `build-javascript`: {
configPlugins = configPlugins.concat([
- plugins.extractText(),
+ plugins.extractText({
+ filename: `[name].[contenthash].css`,
+ chunkFilename: `[name].[contenthash].css`,
+ }),
// Write out stats object mapping named dynamic imports (aka page
// components) to all their async chunks.
plugins.extractStats(),