Skip to content

Commit 121ccbf

Browse files
KyleAMathewspieh
andauthored
feature(gatsby): Extract non-css-in-js css and add add to <head> when SSRing in dev (#28471)
* feature(gatsby): Pause dev-ssr watching between page loads to avoid slowing down regular develop-js HMR * update snapshot * Don't double-resolve + add activity for building the SSR bundle * Add timeout for tests to ensure that dev server has time to bundle SSR + remove activity timers as not helpful * feature(gatsby): Extract and add CSS when SSRing in dev * Remove commented out code * get tests passing * WIP * Got hot-reloading working w/ mini-css-extract-plugin * remove mistakenly added file * remove change to yarn.lock * revert other mistakenly added files * Add an async module to test against * fix async module * Add postcss/tailwind * write webpack config for easy comparisons * that was a lot easier than I thought — just set hmr:true for non-production sites * cleanups * Don't need this since we're using <link> not <style> * pass in port * remove dev css from test comparisons * Update snapshots + add tailwind * cleanups * remove discarded changes * Move changes behind flag * Undo unnecesary changes * Update tests for signature change * Move more code behind the flag * dynamically set absolute URL for css files so works wherever it's hosted * start relative than make absolute * Remove now unused port * Remove changes from #28394 * use @pieh's suggested refactor in https://github.com/gatsbyjs/gatsby/pull/28471/files\#r546803732 * pass naming options for extractText in via options * Update packages/gatsby/src/utils/webpack.config.js Co-authored-by: Michal Piechowiak <[email protected]> * Update snapshot * Stop Jest from chocking on import of css * turned out we didn't need this * test(ssr): ignore src/test file (those are not tests) * test(ssr): update snapshot after removing inline script modyfing href Co-authored-by: Michal Piechowiak <[email protected]>
1 parent 55882f2 commit 121ccbf

20 files changed

+180
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {}

integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`SSR is run for a page when it is requested 1`] = `"<!DOCTYPE html><html><head><meta charSet=\\"utf-8\\"/><meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\"/><meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, shrink-to-fit=no\\"/><meta name=\\"note\\" content=\\"environment=development\\"/><script src=\\"/socket.io/socket.io.js\\"></script></head><body><div id=\\"___gatsby\\"><div style=\\"outline:none\\" tabindex=\\"-1\\" id=\\"gatsby-focus-wrapper\\"><div>Hello world</div></div><div id=\\"gatsby-announcer\\" style=\\"position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0\\" aria-live=\\"assertive\\" aria-atomic=\\"true\\"></div></div><script src=\\"/polyfill.js\\" nomodule=\\"\\"></script><script src=\\"/commons.js\\"></script></body></html>"`;
3+
exports[`SSR is run for a page when it is requested 1`] = `"<!DOCTYPE html><html><head><meta charSet=\\"utf-8\\"/><meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\"/><meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, shrink-to-fit=no\\"/><link data-identity=\\"gatsby-dev-css\\" rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"/commons.css\\"/><meta name=\\"note\\" content=\\"environment=development\\"/><script src=\\"/socket.io/socket.io.js\\"></script></head><body><div id=\\"___gatsby\\"><div style=\\"outline:none\\" tabindex=\\"-1\\" id=\\"gatsby-focus-wrapper\\"><div><h1 class=\\"hi\\">Hello world</h1></div></div><div id=\\"gatsby-announcer\\" style=\\"position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0\\" aria-live=\\"assertive\\" aria-atomic=\\"true\\"></div></div><script src=\\"/polyfill.js\\" nomodule=\\"\\"></script><script src=\\"/commons.js\\"></script></body></html>"`;
44
55
exports[`SSR it generates an error page correctly 1`] = `
66
"<head>

integration-tests/ssr/__tests__/ssr.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ describe(`SSR`, () => {
5353
}, 400)
5454
}, 400)
5555
})
56-
})
56+
}, 15000)
5757
})
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./sample.css"

integration-tests/ssr/gatsby-config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ module.exports = {
66
github: `sidharthachatterjee`,
77
moreInfo: `Sid is amazing`,
88
},
9-
plugins: [],
9+
plugins: ["gatsby-plugin-postcss"],
1010
}

integration-tests/ssr/jest.config.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
module.exports = {
2-
testPathIgnorePatterns: [`/node_modules/`, `__tests__/fixtures`, `.cache`],
2+
testPathIgnorePatterns: [
3+
`/node_modules/`,
4+
`__tests__/fixtures`,
5+
`.cache`,
6+
`src/test`,
7+
],
8+
transform: {
9+
"^.+\\.[jt]sx?$": `<rootDir>../../jest-transformer.js`,
10+
},
11+
moduleNameMapper: {
12+
"\\.(css)$": `<rootDir>/__mocks__/styleMock.js`,
13+
},
314
}

integration-tests/ssr/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
},
99
"dependencies": {
1010
"gatsby": "^2.27.0",
11+
"gatsby-plugin-postcss": "^3.3.0",
1112
"react": "^16.12.0",
12-
"react-dom": "^16.12.0"
13+
"react-dom": "^16.12.0",
14+
"tailwindcss": "1"
1315
},
1416
"devDependencies": {
1517
"cross-env": "^5.0.2",
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
plugins: [require("tailwindcss"), require("autoprefixer")],
3+
}

integration-tests/ssr/sample.css

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
body {
2+
background: tomato;
3+
color: black;
4+
font-style: italic;
5+
font-weight: 400;
6+
}

integration-tests/ssr/src/pages/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react"
22
import { useStaticQuery, graphql } from "gatsby"
3+
const lazyImport = import(`../test`)
34

45
export default function Inline() {
56
const { site } = useStaticQuery(graphql`
@@ -11,5 +12,9 @@ export default function Inline() {
1112
}
1213
}
1314
`)
14-
return <div>{site.siteMetadata.title}</div>
15+
return (
16+
<div>
17+
<h1 className="hi">{site.siteMetadata.title}</h1>
18+
</div>
19+
)
1520
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import React from "react"
2+
import "../styles/tailwind.css"
3+
4+
export default () => <h1 className="text-3xl">This is a 3xl text</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@tailwind base;
2+
3+
@tailwind components;
4+
5+
@tailwind utilities;

integration-tests/ssr/src/test.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.hi {
2+
color: blue;
3+
}

integration-tests/ssr/src/test.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./test.css"
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
purge: [
3+
'./src/**/*.js',
4+
],
5+
theme: {
6+
extend: {}
7+
},
8+
variants: {},
9+
plugins: []
10+
}

integration-tests/ssr/test-output.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
const $ = cheerio.load(htmlStr)
2222
// There are many script tag differences
2323
$(`script`).remove()
24-
// Only added in production. Dev uses css-loader
24+
// Only added in production
2525
$(`#gatsby-global-css`).remove()
26+
// Only added in development
27+
$(`link[data-identity='gatsby-dev-css']`).remove()
2628
// Only in prod
2729
$(`link[rel="preload"]`).remove()
2830
// Only in prod

packages/gatsby/cache-dir/__tests__/static-entry.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from "react"
22
import fs from "fs"
33
const { join } = require(`path`)
44

5-
import ssrDevelopStaticEntry from "../ssr-develop-static-entry"
65
import developStaticEntry from "../develop-static-entry"
76

87
jest.mock(`fs`, () => {
@@ -22,7 +21,7 @@ jest.mock(
2221
() => {
2322
return {
2423
ssrComponents: {
25-
"page-component---src-pages-test-js": () => null,
24+
"page-component---src-pages-about-js": () => null,
2625
},
2726
}
2827
},
@@ -151,8 +150,27 @@ const fakeComponentsPluginFactory = type => {
151150
}
152151
}
153152

153+
const SSR_DEV_MOCK_FILE_INFO = {
154+
[`${process.cwd()}/public/webpack.stats.json`]: `{}`,
155+
[join(
156+
process.cwd(),
157+
`/public/page-data/about/page-data.json`
158+
)]: JSON.stringify({
159+
componentChunkName: `page-component---src-pages-about-js`,
160+
path: `/about/`,
161+
webpackCompilationHash: `1234567890abcdef1234`,
162+
staticQueryHashes: [],
163+
}),
164+
[join(process.cwd(), `/public/page-data/app-data.json`)]: JSON.stringify({
165+
webpackCompilationHash: `1234567890abcdef1234`,
166+
}),
167+
}
168+
154169
describe(`develop-static-entry`, () => {
170+
let ssrDevelopStaticEntry
155171
beforeEach(() => {
172+
fs.readFileSync.mockImplementation(file => SSR_DEV_MOCK_FILE_INFO[file])
173+
ssrDevelopStaticEntry = require(`../ssr-develop-static-entry`).default
156174
global.__PATH_PREFIX__ = ``
157175
global.__BASE_PATH__ = ``
158176
global.__ASSET_PREFIX__ = ``

packages/gatsby/cache-dir/ssr-develop-static-entry.js

+65-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react"
22
import fs from "fs"
33
import { renderToString, renderToStaticMarkup } from "react-dom/server"
4-
import { merge } from "lodash"
4+
import { get, merge, isObject, flatten, uniqBy, concat } from "lodash"
55
import { join } from "path"
66
import apiRunner from "./api-runner-ssr"
77
import { grabMatchParams } from "./find-path"
@@ -20,6 +20,10 @@ const testRequireError = (moduleName, err) => {
2020
return regex.test(firstLine)
2121
}
2222

23+
const stats = JSON.parse(
24+
fs.readFileSync(`${process.cwd()}/public/webpack.stats.json`, `utf-8`)
25+
)
26+
2327
let Html
2428
try {
2529
Html = require(`../src/html`)
@@ -111,7 +115,66 @@ export default (pagePath, isClientOnlyPage, callback) => {
111115

112116
const pageData = getPageData(pagePath)
113117

114-
const componentChunkName = pageData?.componentChunkName
118+
const { componentChunkName, staticQueryHashes = [] } = pageData
119+
120+
let scriptsAndStyles = flatten(
121+
[`commons`].map(chunkKey => {
122+
const fetchKey = `assetsByChunkName[${chunkKey}]`
123+
124+
let chunks = get(stats, fetchKey)
125+
const namedChunkGroups = get(stats, `namedChunkGroups`)
126+
127+
if (!chunks) {
128+
return null
129+
}
130+
131+
chunks = chunks.map(chunk => {
132+
if (chunk === `/`) {
133+
return null
134+
}
135+
return { rel: `preload`, name: chunk }
136+
})
137+
138+
namedChunkGroups[chunkKey].assets.forEach(asset =>
139+
chunks.push({ rel: `preload`, name: asset })
140+
)
141+
142+
const childAssets = namedChunkGroups[chunkKey].childAssets
143+
for (const rel in childAssets) {
144+
chunks = concat(
145+
chunks,
146+
childAssets[rel].map(chunk => {
147+
return { rel, name: chunk }
148+
})
149+
)
150+
}
151+
152+
return chunks
153+
})
154+
)
155+
.filter(s => isObject(s))
156+
.sort((s1, s2) => (s1.rel == `preload` ? -1 : 1)) // given priority to preload
157+
158+
scriptsAndStyles = uniqBy(scriptsAndStyles, item => item.name)
159+
160+
const styles = scriptsAndStyles.filter(
161+
style => style.name && style.name.endsWith(`.css`)
162+
)
163+
164+
styles
165+
.slice(0)
166+
.reverse()
167+
.forEach(style => {
168+
headComponents.unshift(
169+
<link
170+
data-identity={`gatsby-dev-css`}
171+
key={style.name}
172+
rel="stylesheet"
173+
type="text/css"
174+
href={`${__PATH_PREFIX__}/${style.name}`}
175+
/>
176+
)
177+
})
115178

116179
const createElement = React.createElement
117180

packages/gatsby/src/utils/webpack-utils.ts

+22-8
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,28 @@ export const createWebpackUtils = (
195195
},
196196

197197
miniCssExtract: (options = {}) => {
198-
return {
199-
options,
200-
// use MiniCssExtractPlugin only on production builds
201-
loader: PRODUCTION
202-
? MiniCssExtractPlugin.loader
203-
: require.resolve(`style-loader`),
198+
if (PRODUCTION) {
199+
// production always uses MiniCssExtractPlugin
200+
return {
201+
loader: MiniCssExtractPlugin.loader,
202+
options,
203+
}
204+
} else if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
205+
// develop with ssr also uses MiniCssExtractPlugin
206+
return {
207+
loader: MiniCssExtractPlugin.loader,
208+
options: {
209+
...options,
210+
// enable hmr for browser bundle, ssr bundle doesn't need it
211+
hmr: stage === `develop`,
212+
},
213+
}
214+
} else {
215+
// develop without ssr is using style-loader
216+
return {
217+
loader: require.resolve(`style-loader`),
218+
options,
219+
}
204220
}
205221
},
206222

@@ -690,8 +706,6 @@ export const createWebpackUtils = (
690706

691707
plugins.extractText = (options: any): Plugin =>
692708
new MiniCssExtractPlugin({
693-
filename: `[name].[contenthash].css`,
694-
chunkFilename: `[name].[contenthash].css`,
695709
...options,
696710
})
697711

packages/gatsby/src/utils/webpack.config.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,21 @@ module.exports = async (
230230
plugins.eslintGraphqlSchemaReload(),
231231
])
232232
.filter(Boolean)
233+
if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
234+
// Don't use the default mini-css-extract-plugin setup as that
235+
// breaks hmr.
236+
configPlugins.push(
237+
plugins.extractText({ filename: `[name].css` }),
238+
plugins.extractStats()
239+
)
240+
}
233241
break
234242
case `build-javascript`: {
235243
configPlugins = configPlugins.concat([
236-
plugins.extractText(),
244+
plugins.extractText({
245+
filename: `[name].[contenthash].css`,
246+
chunkFilename: `[name].[contenthash].css`,
247+
}),
237248
// Write out stats object mapping named dynamic imports (aka page
238249
// components) to all their async chunks.
239250
plugins.extractStats(),

0 commit comments

Comments
 (0)