Skip to content

Commit

Permalink
feat(gatsby): make dev ssr bundling lazy (#28149)
Browse files Browse the repository at this point in the history
* feat(gatsby): make dev ssr bundling lazy

* fix tests

* fix race condition

* typescript 😅

* await getPageData to ensure it's been created before SSRing

* instance isn't set in SSR

* Fix a few more instances of this env var

* Avoid setting anything for experimental features

Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>
  • Loading branch information
KyleAMathews and pieh authored Nov 23, 2020
1 parent 4148877 commit 70b81a6
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 55 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby/cache-dir/__tests__/static-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jest.mock(
`$virtual/ssr-sync-requires`,
() => {
return {
components: {
ssrComponents: {
"page-component---src-pages-test-js": () => null,
},
}
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/cache-dir/ssr-develop-static-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default (pagePath, isClientOnlyPage, callback) => {
}

const pageElement = createElement(
syncRequires.components[componentChunkName],
syncRequires.ssrComponents[componentChunkName],
props
)

Expand Down
85 changes: 65 additions & 20 deletions packages/gatsby/src/bootstrap/requires-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ const createHash = (
matchPaths: Array<IGatsbyPageMatchPath>,
components: Array<IGatsbyPageComponent>,
cleanedClientVisitedPageComponents: Array<IGatsbyPageComponent>,
notVisitedPageComponents: Array<IGatsbyPageComponent>
notVisitedPageComponents: Array<IGatsbyPageComponent>,
cleanedSSRVisitedPageComponents: Array<IGatsbyPageComponent>
): string =>
crypto
.createHash(`md5`)
Expand All @@ -174,6 +175,7 @@ const createHash = (
components,
cleanedClientVisitedPageComponents,
notVisitedPageComponents,
cleanedSSRVisitedPageComponents,
})
)
.digest(`hex`)
Expand All @@ -184,11 +186,27 @@ export const writeAll = async (state: IGatsbyState): Promise<boolean> => {
const pages = [...state.pages.values()]
const matchPaths = getMatchPaths(pages)
const components = getComponents(pages)
let cleanedSSRVisitedPageComponents: Array<IGatsbyPageComponent> = []

if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
const ssrVisitedPageComponents = [
...(state.visitedPages.get(`server`)?.values() || []),
]

// Remove any page components that no longer exist.
cleanedSSRVisitedPageComponents = components.filter(c =>
ssrVisitedPageComponents.some(s => s === c.componentChunkName)
)
}

let cleanedClientVisitedPageComponents: Array<IGatsbyPageComponent> = []
let notVisitedPageComponents: Array<IGatsbyPageComponent> = []

let cleanedClientVisitedPageComponents: Array<IGatsbyPageComponent> = components
let notVisitedPageComponents: Array<IGatsbyPageComponent> = components
if (process.env.GATSBY_EXPERIMENTAL_LAZY_DEVJS) {
const clientVisitedPageComponents = [...state.clientVisitedPages.values()]
const clientVisitedPageComponents = [
...(state.visitedPages.get(`client`)?.values() || []),
]

// Remove any page components that no longer exist.
cleanedClientVisitedPageComponents = components.filter(component =>
clientVisitedPageComponents.some(
Expand All @@ -211,7 +229,8 @@ export const writeAll = async (state: IGatsbyState): Promise<boolean> => {
matchPaths,
components,
cleanedClientVisitedPageComponents,
notVisitedPageComponents
notVisitedPageComponents,
cleanedSSRVisitedPageComponents
)

if (newHash === lastHash) {
Expand All @@ -229,6 +248,25 @@ export const writeAll = async (state: IGatsbyState): Promise<boolean> => {
const hotMethod =
process.env.GATSBY_HOT_LOADER !== `fast-refresh` ? `hot` : ``

if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
// Create file with sync requires of visited page components files.
let lazySyncRequires = `${hotImport}
// prefer default export if available
const preferDefault = m => (m && m.default) || m
\n\n`
lazySyncRequires += `exports.ssrComponents = {\n${cleanedSSRVisitedPageComponents
.map(
(c: IGatsbyPageComponent): string =>
` "${
c.componentChunkName
}": ${hotMethod}(preferDefault(require("${joinPath(c.component)}")))`
)
.join(`,\n`)}
}\n\n`

writeModule(`$virtual/ssr-sync-requires`, lazySyncRequires)
}

// Create file with sync requires of components/json files.
let syncRequires = `${hotImport}
Expand All @@ -245,10 +283,6 @@ const preferDefault = m => (m && m.default) || m
.join(`,\n`)}
}\n\n`

// webpack only seems to trigger re-renders once per virtual
// file so we need a seperate one for each webpack instance.
writeModule(`$virtual/ssr-sync-requires`, syncRequires)

if (process.env.GATSBY_EXPERIMENTAL_LAZY_DEVJS) {
// Create file with sync requires of visited page components files.
let lazyClientSyncRequires = `${hotImport}
Expand Down Expand Up @@ -331,17 +365,6 @@ const preferDefault = m => (m && m.default) || m
return true
}

if (process.env.GATSBY_EXPERIMENTAL_LAZY_DEVJS) {
/**
* Start listening to CREATE_CLIENT_VISITED_PAGE events so we can rewrite
* files as required
*/
emitter.on(`CREATE_CLIENT_VISITED_PAGE`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
writeAll(store.getState())
})
}

const debouncedWriteAll = _.debounce(
async (): Promise<void> => {
const activity = reporter.activityTimer(`write out requires`, {
Expand All @@ -359,6 +382,28 @@ const debouncedWriteAll = _.debounce(
}
)

if (process.env.GATSBY_EXPERIMENTAL_LAZY_DEVJS) {
/**
* Start listening to CREATE_CLIENT_VISITED_PAGE events so we can rewrite
* files as required
*/
emitter.on(`CREATE_CLIENT_VISITED_PAGE`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})
}

if (process.env.GATSBY_EXPERIMENTAL_DEV_SSR) {
/**
* Start listening to CREATE_SERVER_VISITED_PAGE events so we can rewrite
* files as required
*/
emitter.on(`CREATE_SERVER_VISITED_PAGE`, (): void => {
reporter.pendingActivity({ id: `requires-writer` })
debouncedWriteAll()
})
}

/**
* Start listening to CREATE/DELETE_PAGE events so we can rewrite
* files as required
Expand Down
13 changes: 13 additions & 0 deletions packages/gatsby/src/redux/actions/public.js
Original file line number Diff line number Diff line change
Expand Up @@ -1404,4 +1404,17 @@ actions.createClientVisitedPage = (chunkName: string) => {
}
}

/**
* Record that a page was visited on the server..
*
* @param {Object} $0
* @param {string} $0.id the chunkName for the page component.
*/
actions.createServerVisitedPage = (chunkName: string) => {
return {
type: `CREATE_SERVER_VISITED_PAGE`,
payload: { componentChunkName: chunkName },
}
}

module.exports = { actions }
29 changes: 0 additions & 29 deletions packages/gatsby/src/redux/reducers/client-visited-page.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/gatsby/src/redux/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { schemaCustomizationReducer } from "./schema-customization"
import { inferenceMetadataReducer } from "./inference-metadata"
import { staticQueriesByTemplateReducer } from "./static-queries-by-template"
import { queriesReducer } from "./queries"
import { clientVisitedPageReducer } from "./client-visited-page"
import { visitedPagesReducer } from "./visited-page"

/**
* @property exports.nodesTouched Set<string>
Expand All @@ -44,7 +44,7 @@ export {
configReducer as config,
schemaReducer as schema,
pagesReducer as pages,
clientVisitedPageReducer as clientVisitedPages,
visitedPagesReducer as visitedPages,
statusReducer as status,
componentsReducer as components,
staticQueryComponentsReducer as staticQueryComponents,
Expand Down
56 changes: 56 additions & 0 deletions packages/gatsby/src/redux/reducers/visited-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
IGatsbyState,
IDeleteCacheAction,
ICreateClientVisitedPage,
ICreateServerVisitedPage,
} from "../types"

type StateMap = Map<"client" | "server", Set<string>>

// The develop server always wants these page components.
const createDefault = (): StateMap => {
const defaults = new Set<string>()
defaults.add(`component---cache-dev-404-page-js`)
defaults.add(`component---src-pages-404-js`)

const state: StateMap = new Map([
[`client`, new Set(defaults)],
[`server`, new Set(defaults)],
])

return state
}

export const visitedPagesReducer = (
state: IGatsbyState["visitedPages"] = createDefault(),
action:
| IDeleteCacheAction
| ICreateClientVisitedPage
| ICreateServerVisitedPage
): IGatsbyState["visitedPages"] => {
switch (action.type) {
case `DELETE_CACHE`:
return createDefault()

case `CREATE_CLIENT_VISITED_PAGE`: {
const client = state.get(`client`)
if (client) {
client.add(action.payload.componentChunkName)
}

return state
}

case `CREATE_SERVER_VISITED_PAGE`: {
const server = state.get(`server`)
if (server) {
server.add(action.payload.componentChunkName)
}

return state
}

default:
return state
}
}
8 changes: 7 additions & 1 deletion packages/gatsby/src/redux/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export interface IGatsbyState {
}
pageDataStats: Map<SystemPath, number>
pageData: Map<Identifier, string>
clientVisitedPages: Set<string>
visitedPages: Map<string, Set<string>>
}

export interface ICachedReduxState {
Expand Down Expand Up @@ -603,6 +603,12 @@ export interface ICreateClientVisitedPage {
plugin?: IGatsbyPlugin
}

export interface ICreateServerVisitedPage {
type: `CREATE_SERVER_VISITED_PAGE`
payload: IGatsbyPage
plugin?: IGatsbyPlugin
}

export interface ICreatePageAction {
type: `CREATE_PAGE`
payload: IGatsbyPage
Expand Down
Loading

0 comments on commit 70b81a6

Please sign in to comment.