Skip to content

Commit

Permalink
feature(gatsby): Extract non-css-in-js css and add add to <head> when…
Browse files Browse the repository at this point in the history
… 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 <misiek.piechowiak@gmail.com>

* 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 <misiek.piechowiak@gmail.com>
  • Loading branch information
KyleAMathews and pieh authored Jan 4, 2021
1 parent 55882f2 commit 121ccbf
Show file tree
Hide file tree
Showing 20 changed files with 180 additions and 20 deletions.
1 change: 1 addition & 0 deletions integration-tests/ssr/__mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
2 changes: 1 addition & 1 deletion integration-tests/ssr/__tests__/__snapshots__/ssr.js.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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>"`;
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>"`;
exports[`SSR it generates an error page correctly 1`] = `
"<head>
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/ssr/__tests__/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ describe(`SSR`, () => {
}, 400)
}, 400)
})
})
}, 15000)
})
1 change: 1 addition & 0 deletions integration-tests/ssr/gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./sample.css"
2 changes: 1 addition & 1 deletion integration-tests/ssr/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ module.exports = {
github: `sidharthachatterjee`,
moreInfo: `Sid is amazing`,
},
plugins: [],
plugins: ["gatsby-plugin-postcss"],
}
13 changes: 12 additions & 1 deletion integration-tests/ssr/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
module.exports = {
testPathIgnorePatterns: [`/node_modules/`, `__tests__/fixtures`, `.cache`],
testPathIgnorePatterns: [
`/node_modules/`,
`__tests__/fixtures`,
`.cache`,
`src/test`,
],
transform: {
"^.+\\.[jt]sx?$": `<rootDir>../../jest-transformer.js`,
},
moduleNameMapper: {
"\\.(css)$": `<rootDir>/__mocks__/styleMock.js`,
},
}
4 changes: 3 additions & 1 deletion integration-tests/ssr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions integration-tests/ssr/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
plugins: [require("tailwindcss"), require("autoprefixer")],
}
6 changes: 6 additions & 0 deletions integration-tests/ssr/sample.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
body {
background: tomato;
color: black;
font-style: italic;
font-weight: 400;
}
7 changes: 6 additions & 1 deletion integration-tests/ssr/src/pages/index.js
Original file line number Diff line number Diff line change
@@ -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`
Expand All @@ -11,5 +12,9 @@ export default function Inline() {
}
}
`)
return <div>{site.siteMetadata.title}</div>
return (
<div>
<h1 className="hi">{site.siteMetadata.title}</h1>
</div>
)
}
4 changes: 4 additions & 0 deletions integration-tests/ssr/src/pages/usingtailwind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from "react"
import "../styles/tailwind.css"

export default () => <h1 className="text-3xl">This is a 3xl text</h1>
5 changes: 5 additions & 0 deletions integration-tests/ssr/src/styles/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@tailwind base;

@tailwind components;

@tailwind utilities;
3 changes: 3 additions & 0 deletions integration-tests/ssr/src/test.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.hi {
color: blue;
}
1 change: 1 addition & 0 deletions integration-tests/ssr/src/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./test.css"
10 changes: 10 additions & 0 deletions integration-tests/ssr/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
purge: [
'./src/**/*.js',
],
theme: {
extend: {}
},
variants: {},
plugins: []
}
4 changes: 3 additions & 1 deletion integration-tests/ssr/test-output.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions packages/gatsby/cache-dir/__tests__/static-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`, () => {
Expand All @@ -22,7 +21,7 @@ jest.mock(
() => {
return {
ssrComponents: {
"page-component---src-pages-test-js": () => null,
"page-component---src-pages-about-js": () => null,
},
}
},
Expand Down Expand Up @@ -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__ = ``
Expand Down
67 changes: 65 additions & 2 deletions packages/gatsby/cache-dir/ssr-develop-static-entry.js
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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`)
Expand Down Expand Up @@ -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(
<link
data-identity={`gatsby-dev-css`}
key={style.name}
rel="stylesheet"
type="text/css"
href={`${__PATH_PREFIX__}/${style.name}`}
/>
)
})

const createElement = React.createElement

Expand Down
30 changes: 22 additions & 8 deletions packages/gatsby/src/utils/webpack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
},

Expand Down Expand Up @@ -690,8 +706,6 @@ export const createWebpackUtils = (

plugins.extractText = (options: any): Plugin =>
new MiniCssExtractPlugin({
filename: `[name].[contenthash].css`,
chunkFilename: `[name].[contenthash].css`,
...options,
})

Expand Down
13 changes: 12 additions & 1 deletion packages/gatsby/src/utils/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 121ccbf

Please sign in to comment.