Skip to content

Commit ed9771b

Browse files
authoredJul 1, 2020
feat(gatsby): Use state machine for bootstrap in develop (#25305)
* Move bootstrap into machine * Add parent span and query extraction * Add rebuildSchemaWithSitePage * Use values from context * Remove logs * Add redirectListener * Changes from review * Changes from review * Changes from review * Use assertStore
1 parent 80654fb commit ed9771b

13 files changed

+410
-72
lines changed
 

‎packages/gatsby/src/commands/develop-process.ts

+129-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { bootstrap } from "../bootstrap"
2-
import { store } from "../redux"
31
import { syncStaticDir } from "../utils/get-static-dir"
42
import report from "gatsby-cli/lib/reporter"
53
import chalk from "chalk"
@@ -19,7 +17,7 @@ import {
1917
userPassesFeedbackRequestHeuristic,
2018
showFeedbackRequest,
2119
} from "../utils/feedback"
22-
20+
import { startRedirectListener } from "../bootstrap/redirects-writer"
2321
import { markWebpackStatusAsPending } from "../utils/webpack-status"
2422

2523
import { IProgram } from "./types"
@@ -29,9 +27,29 @@ import {
2927
runPageQueries,
3028
startWebpackServer,
3129
writeOutRequires,
30+
IBuildContext,
31+
initialize,
32+
postBootstrap,
33+
extractQueries,
34+
rebuildSchemaWithSitePage,
35+
writeOutRedirects,
3236
} from "../services"
3337
import { boundActionCreators } from "../redux/actions"
3438
import { ProgramStatus } from "../redux/types"
39+
import {
40+
MachineConfig,
41+
AnyEventObject,
42+
assign,
43+
Machine,
44+
DoneEventObject,
45+
interpret,
46+
} from "xstate"
47+
import { DataLayerResult, dataLayerMachine } from "../state-machines/data-layer"
48+
import { IDataLayerContext } from "../state-machines/data-layer/types"
49+
import { globalTracer } from "opentracing"
50+
import reporter from "gatsby-cli/lib/reporter"
51+
52+
const tracer = globalTracer()
3553

3654
// const isInteractive = process.stdout.isTTY
3755

@@ -67,6 +85,8 @@ process.on(`message`, msg => {
6785
})
6886

6987
module.exports = async (program: IProgram): Promise<void> => {
88+
const bootstrapSpan = tracer.startSpan(`bootstrap`)
89+
7090
// We want to prompt the feedback request when users quit develop
7191
// assuming they pass the heuristic check to know they are a user
7292
// we want to request feedback from, and we're not annoying them.
@@ -108,34 +128,114 @@ module.exports = async (program: IProgram): Promise<void> => {
108128
throw e
109129
}
110130

111-
// Start bootstrap process.
112-
const { gatsbyNodeGraphQLFunction, workerPool } = await bootstrap({ program })
113-
114-
// Start the createPages hot reloader.
115-
bootstrapPageHotReloader(gatsbyNodeGraphQLFunction)
116-
117-
// Start the schema hot reloader.
118-
bootstrapSchemaHotReloader()
131+
const app = express()
119132

120-
const { queryIds } = await calculateDirtyQueries({ store })
133+
const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
134+
id: `build`,
135+
initial: `initializing`,
136+
states: {
137+
initializing: {
138+
invoke: {
139+
src: `initialize`,
140+
onDone: {
141+
target: `initializingDataLayer`,
142+
actions: `assignStoreAndWorkerPool`,
143+
},
144+
},
145+
},
146+
initializingDataLayer: {
147+
invoke: {
148+
src: `initializeDataLayer`,
149+
data: ({ parentSpan, store }: IBuildContext): IDataLayerContext => {
150+
return { parentSpan, store, firstRun: true }
151+
},
152+
onDone: {
153+
actions: `assignDataLayer`,
154+
target: `doingEverythingElse`,
155+
},
156+
},
157+
},
158+
doingEverythingElse: {
159+
invoke: {
160+
src: async ({
161+
gatsbyNodeGraphQLFunction,
162+
graphqlRunner,
163+
workerPool,
164+
store,
165+
app,
166+
}): Promise<void> => {
167+
// All the stuff that's not in the state machine yet
168+
169+
// These were previously in `bootstrap()` but are now
170+
// in part of the state machine that hasn't been added yet
171+
await rebuildSchemaWithSitePage({ parentSpan: bootstrapSpan })
172+
173+
await extractQueries({ parentSpan: bootstrapSpan })
174+
await writeOutRedirects({ parentSpan: bootstrapSpan })
175+
176+
startRedirectListener()
177+
bootstrapSpan.finish()
178+
await postBootstrap({ parentSpan: bootstrapSpan })
179+
180+
// These are the parts that weren't in bootstrap
181+
182+
// Start the createPages hot reloader.
183+
bootstrapPageHotReloader(gatsbyNodeGraphQLFunction)
184+
185+
// Start the schema hot reloader.
186+
bootstrapSchemaHotReloader()
187+
188+
const { queryIds } = await calculateDirtyQueries({ store })
189+
190+
await runStaticQueries({ queryIds, store, program, graphqlRunner })
191+
await runPageQueries({ queryIds, store, program, graphqlRunner })
192+
await writeOutRequires({ store })
193+
boundActionCreators.setProgramStatus(
194+
ProgramStatus.BOOTSTRAP_QUERY_RUNNING_FINISHED
195+
)
196+
197+
await db.saveState()
198+
199+
await waitUntilAllJobsComplete()
200+
requiresWriter.startListener()
201+
db.startAutosave()
202+
queryUtil.startListeningToDevelopQueue({
203+
graphqlTracing: program.graphqlTracing,
204+
})
205+
queryWatcher.startWatchDeletePage()
206+
207+
await startWebpackServer({ program, app, workerPool })
208+
},
209+
},
210+
},
211+
},
212+
}
121213

122-
await runStaticQueries({ queryIds, store, program })
123-
await runPageQueries({ queryIds, store, program })
124-
await writeOutRequires({ store })
125-
boundActionCreators.setProgramStatus(
126-
ProgramStatus.BOOTSTRAP_QUERY_RUNNING_FINISHED
214+
const service = interpret(
215+
// eslint-disable-next-line new-cap
216+
Machine(developConfig, {
217+
services: {
218+
initializeDataLayer: dataLayerMachine,
219+
initialize,
220+
},
221+
actions: {
222+
assignStoreAndWorkerPool: assign<IBuildContext, DoneEventObject>(
223+
(_context, event) => {
224+
const { store, workerPool } = event.data
225+
return {
226+
store,
227+
workerPool,
228+
}
229+
}
230+
),
231+
assignDataLayer: assign<IBuildContext, DoneEventObject>(
232+
(_, { data }): DataLayerResult => data
233+
),
234+
},
235+
}).withContext({ program, parentSpan: bootstrapSpan, app })
127236
)
128-
129-
await db.saveState()
130-
131-
await waitUntilAllJobsComplete()
132-
requiresWriter.startListener()
133-
db.startAutosave()
134-
queryUtil.startListeningToDevelopQueue({
135-
graphqlTracing: program.graphqlTracing,
237+
service.onTransition(state => {
238+
reporter.verbose(`transition to ${JSON.stringify(state.value)}`)
136239
})
137-
queryWatcher.startWatchDeletePage()
138-
const app = express()
139-
140-
await startWebpackServer({ program, app, workerPool })
240+
service.start()
141241
}

‎packages/gatsby/src/services/build-schema.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { IBuildContext } from "./"
2-
31
import { build } from "../schema"
42
import reporter from "gatsby-cli/lib/reporter"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
54

65
export async function buildSchema({
76
parentSpan,
8-
}: Partial<IBuildContext>): Promise<void> {
7+
}: Partial<IDataLayerContext>): Promise<void> {
98
const activity = reporter.activityTimer(`building schema`, {
109
parentSpan,
1110
})

‎packages/gatsby/src/services/create-pages-statefully.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { IBuildContext } from "./"
2-
31
import reporter from "gatsby-cli/lib/reporter"
42
import apiRunnerNode from "../utils/api-runner-node"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
54

65
export async function createPagesStatefully({
76
parentSpan,
87
gatsbyNodeGraphQLFunction,
9-
}: Partial<IBuildContext>): Promise<void> {
8+
}: Partial<IDataLayerContext>): Promise<void> {
109
// A variant on createPages for plugins that want to
1110
// have full control over adding/removing pages. The normal
1211
// "createPages" API is called every time (during development)

‎packages/gatsby/src/services/create-pages.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { IBuildContext } from "./"
2-
31
import reporter from "gatsby-cli/lib/reporter"
42
import apiRunnerNode from "../utils/api-runner-node"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
54
import { assertStore } from "../utils/assert-store"
65

76
export async function createPages({
87
parentSpan,
98
gatsbyNodeGraphQLFunction,
109
store,
11-
}: Partial<IBuildContext>): Promise<void> {
10+
}: Partial<IDataLayerContext>): Promise<{
11+
deletedPages: string[]
12+
changedPages: string[]
13+
}> {
1214
assertStore(store)
1315
const activity = reporter.activityTimer(`createPages`, {
1416
parentSpan,
@@ -59,8 +61,8 @@ export async function createPages({
5961
// )
6062
// tim.end()
6163

62-
// return {
63-
// changedPages,
64-
// deletedPages,
65-
// }
64+
return {
65+
changedPages: [],
66+
deletedPages: [],
67+
}
6668
}

‎packages/gatsby/src/services/customize-schema.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import reporter from "gatsby-cli/lib/reporter"
22
import { createSchemaCustomization } from "../utils/create-schema-customization"
3-
import { IBuildContext } from "./"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
44

55
export async function customizeSchema({
66
parentSpan,
77
refresh, // webhookBody,//coming soon
8-
}: Partial<IBuildContext>): Promise<void> {
8+
}: Partial<IDataLayerContext>): Promise<void> {
99
const activity = reporter.activityTimer(`createSchemaCustomization`, {
1010
parentSpan,
1111
})

‎packages/gatsby/src/services/index.ts

+59-15
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,60 @@
1-
export { startWebpackServer } from "./start-webpack-server"
2-
export { rebuildSchemaWithSitePage } from "./rebuild-schema-with-site-pages"
3-
export { extractQueries } from "./extract-queries"
4-
export { writeOutRedirects } from "./write-out-redirects"
5-
export { postBootstrap } from "./post-bootstrap"
6-
export { buildSchema } from "./build-schema"
7-
export { createPages } from "./create-pages"
8-
export { createPagesStatefully } from "./create-pages-statefully"
9-
export { customizeSchema } from "./customize-schema"
10-
export { initialize } from "./initialize"
11-
export { sourceNodes } from "./source-nodes"
12-
export { writeOutRequires } from "./write-out-requires"
13-
export { calculateDirtyQueries } from "./calculate-dirty-queries"
14-
export { runStaticQueries } from "./run-static-queries"
15-
export { runPageQueries } from "./run-page-queries"
1+
import { ServiceConfig } from "xstate"
2+
import { IBuildContext } from "./"
3+
4+
import { startWebpackServer } from "./start-webpack-server"
5+
import { rebuildSchemaWithSitePage } from "./rebuild-schema-with-site-pages"
6+
import { extractQueries } from "./extract-queries"
7+
import { writeOutRedirects } from "./write-out-redirects"
8+
import { postBootstrap } from "./post-bootstrap"
9+
import { buildSchema } from "./build-schema"
10+
import { createPages } from "./create-pages"
11+
import { createPagesStatefully } from "./create-pages-statefully"
12+
import { customizeSchema } from "./customize-schema"
13+
import { initialize } from "./initialize"
14+
import { sourceNodes } from "./source-nodes"
15+
import { writeOutRequires } from "./write-out-requires"
16+
import { calculateDirtyQueries } from "./calculate-dirty-queries"
17+
import { runStaticQueries } from "./run-static-queries"
18+
import { runPageQueries } from "./run-page-queries"
19+
20+
import { waitUntilAllJobsComplete } from "../utils/wait-until-jobs-complete"
21+
1622
export * from "./types"
23+
24+
export {
25+
customizeSchema,
26+
sourceNodes,
27+
createPages,
28+
buildSchema,
29+
createPagesStatefully,
30+
extractQueries,
31+
writeOutRequires,
32+
calculateDirtyQueries,
33+
runStaticQueries,
34+
runPageQueries,
35+
initialize,
36+
waitUntilAllJobsComplete,
37+
postBootstrap,
38+
writeOutRedirects,
39+
startWebpackServer,
40+
rebuildSchemaWithSitePage,
41+
}
42+
43+
export const buildServices: Record<string, ServiceConfig<IBuildContext>> = {
44+
customizeSchema,
45+
sourceNodes,
46+
createPages,
47+
buildSchema,
48+
createPagesStatefully,
49+
extractQueries,
50+
writeOutRequires,
51+
calculateDirtyQueries,
52+
runStaticQueries,
53+
runPageQueries,
54+
initialize,
55+
waitUntilAllJobsComplete,
56+
postBootstrap,
57+
writeOutRedirects,
58+
startWebpackServer,
59+
rebuildSchemaWithSitePage,
60+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import reporter from "gatsby-cli/lib/reporter"
2-
import { IBuildContext } from "./"
32
import { emitter } from "../redux"
43
import apiRunnerNode from "../utils/api-runner-node"
4+
import { IDataLayerContext } from "../state-machines/data-layer/types"
5+
import { boundActionCreators } from "../redux/actions"
56

67
export async function postBootstrap({
78
parentSpan,
8-
}: Partial<IBuildContext>): Promise<void> {
9+
}: Partial<IDataLayerContext>): Promise<void> {
910
const activity = reporter.activityTimer(`onPostBootstrap`, {
1011
parentSpan,
1112
})
@@ -17,7 +18,5 @@ export async function postBootstrap({
1718
bootstrap finished - ${process.uptime().toFixed(3)}s
1819
`)
1920
emitter.emit(`BOOTSTRAP_FINISHED`, {})
20-
require(`../redux/actions`).boundActionCreators.setProgramStatus(
21-
`BOOTSTRAP_FINISHED`
22-
)
21+
boundActionCreators.setProgramStatus(`BOOTSTRAP_FINISHED`)
2322
}

‎packages/gatsby/src/services/source-nodes.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { IBuildContext } from "./"
21
import sourceNodesAndRemoveStaleNodes from "../utils/source-nodes"
32
import reporter from "gatsby-cli/lib/reporter"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
44
import { assertStore } from "../utils/assert-store"
55
// import { findChangedPages } from "../utils/check-for-changed-pages"
66
// import { IGatsbyPage } from "../redux/types"
@@ -9,7 +9,10 @@ export async function sourceNodes({
99
parentSpan,
1010
webhookBody,
1111
store,
12-
}: Partial<IBuildContext>): Promise<void> {
12+
}: Partial<IDataLayerContext>): Promise<{
13+
deletedPages: string[]
14+
changedPages: string[]
15+
}> {
1316
assertStore(store)
1417

1518
const activity = reporter.activityTimer(`source and transform nodes`, {
@@ -54,8 +57,8 @@ export async function sourceNodes({
5457
// tim.end()
5558

5659
activity.end()
57-
// return {
58-
// deletedPages,
59-
// changedPages,
60-
// }
60+
return {
61+
deletedPages: [],
62+
changedPages: [],
63+
}
6164
}

‎packages/gatsby/src/services/write-out-redirects.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { IBuildContext } from "./"
21
import reporter from "gatsby-cli/lib/reporter"
32
import { writeRedirects } from "../bootstrap/redirects-writer"
3+
import { IDataLayerContext } from "../state-machines/data-layer/types"
44

55
export async function writeOutRedirects({
66
parentSpan,
7-
}: Partial<IBuildContext>): Promise<void> {
7+
}: Partial<IDataLayerContext>): Promise<void> {
88
// Write out redirects.
99
const activity = reporter.activityTimer(`write out redirect data`, {
1010
parentSpan,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
assign,
3+
AnyEventObject,
4+
ActionFunction,
5+
AssignAction,
6+
DoneInvokeEvent,
7+
ActionFunctionMap,
8+
} from "xstate"
9+
import { createGraphQLRunner } from "../../bootstrap/create-graphql-runner"
10+
import reporter from "gatsby-cli/lib/reporter"
11+
import { IDataLayerContext } from "./types"
12+
import { assertStore } from "../../utils/assert-store"
13+
14+
const concatUnique = <T>(array1: T[] = [], array2: T[] = []): T[] =>
15+
Array.from(new Set(array1.concat(array2)))
16+
17+
type BuildMachineAction =
18+
| ActionFunction<IDataLayerContext, any>
19+
| AssignAction<IDataLayerContext, any>
20+
21+
export const assignChangedPages: BuildMachineAction = assign<
22+
IDataLayerContext,
23+
DoneInvokeEvent<{
24+
changedPages: string[]
25+
deletedPages: string[]
26+
}>
27+
>((context, event) => {
28+
return {
29+
pagesToBuild: concatUnique(context.pagesToBuild, event.data.changedPages),
30+
pagesToDelete: concatUnique(context.pagesToDelete, event.data.deletedPages),
31+
}
32+
})
33+
34+
export const assignGatsbyNodeGraphQL: BuildMachineAction = assign<
35+
IDataLayerContext
36+
>({
37+
gatsbyNodeGraphQLFunction: ({ store }: IDataLayerContext) => {
38+
assertStore(store)
39+
return createGraphQLRunner(store, reporter)
40+
},
41+
})
42+
43+
export const dataLayerActions: ActionFunctionMap<
44+
IDataLayerContext,
45+
AnyEventObject
46+
> = {
47+
assignChangedPages,
48+
assignGatsbyNodeGraphQL,
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { MachineConfig, Machine } from "xstate"
2+
import { dataLayerActions } from "./actions"
3+
import { IDataLayerContext } from "./types"
4+
import { dataLayerServices } from "./services"
5+
6+
export type DataLayerResult = Pick<
7+
IDataLayerContext,
8+
| "gatsbyNodeGraphQLFunction"
9+
| "graphqlRunner"
10+
| "pagesToBuild"
11+
| "pagesToDelete"
12+
>
13+
14+
const dataLayerStates: MachineConfig<IDataLayerContext, any, any> = {
15+
initial: `customizingSchema`,
16+
states: {
17+
customizingSchema: {
18+
invoke: {
19+
src: `customizeSchema`,
20+
id: `customizing-schema`,
21+
onDone: {
22+
target: `sourcingNodes`,
23+
},
24+
},
25+
},
26+
sourcingNodes: {
27+
invoke: {
28+
src: `sourceNodes`,
29+
id: `sourcing-nodes`,
30+
onDone: {
31+
target: `buildingSchema`,
32+
actions: `assignChangedPages`,
33+
},
34+
},
35+
},
36+
buildingSchema: {
37+
invoke: {
38+
id: `building-schema`,
39+
src: `buildSchema`,
40+
onDone: {
41+
target: `creatingPages`,
42+
actions: `assignGatsbyNodeGraphQL`,
43+
},
44+
},
45+
},
46+
creatingPages: {
47+
invoke: {
48+
id: `creating-pages`,
49+
src: `createPages`,
50+
onDone: [
51+
{
52+
target: `creatingPagesStatefully`,
53+
actions: `assignChangedPages`,
54+
cond: (context): boolean => !!context.firstRun,
55+
},
56+
{
57+
target: `done`,
58+
actions: `assignChangedPages`,
59+
},
60+
],
61+
},
62+
},
63+
creatingPagesStatefully: {
64+
invoke: {
65+
src: `createPagesStatefully`,
66+
id: `creating-pages-statefully`,
67+
onDone: {
68+
target: `done`,
69+
},
70+
},
71+
},
72+
done: {
73+
type: `final`,
74+
data: ({
75+
gatsbyNodeGraphQLFunction,
76+
graphqlRunner,
77+
pagesToBuild,
78+
pagesToDelete,
79+
}): DataLayerResult => {
80+
return {
81+
gatsbyNodeGraphQLFunction,
82+
graphqlRunner,
83+
pagesToBuild,
84+
pagesToDelete,
85+
}
86+
},
87+
},
88+
},
89+
}
90+
91+
export const dataLayerMachine = Machine(dataLayerStates, {
92+
actions: dataLayerActions,
93+
services: dataLayerServices,
94+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ServiceConfig } from "xstate"
2+
import {
3+
customizeSchema,
4+
createPages,
5+
createPagesStatefully,
6+
buildSchema,
7+
sourceNodes,
8+
} from "../../services"
9+
import { IDataLayerContext } from "./types"
10+
11+
export const dataLayerServices: Record<
12+
string,
13+
ServiceConfig<IDataLayerContext>
14+
> = {
15+
customizeSchema,
16+
sourceNodes,
17+
createPages,
18+
buildSchema,
19+
createPagesStatefully,
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Span } from "opentracing"
2+
import { IProgram } from "../../commands/types"
3+
import { Runner } from "../../bootstrap/create-graphql-runner"
4+
import { GraphQLRunner } from "../../query/graphql-runner"
5+
import { Store, AnyAction } from "redux"
6+
import { IGatsbyState } from "../../redux/types"
7+
import JestWorker from "jest-worker"
8+
export interface IGroupedQueryIds {
9+
pageQueryIds: string[]
10+
staticQueryIds: string[]
11+
}
12+
13+
export interface IMutationAction {
14+
type: string
15+
payload: unknown[]
16+
}
17+
export interface IDataLayerContext {
18+
firstRun?: boolean
19+
program?: IProgram
20+
store?: Store<IGatsbyState, AnyAction>
21+
parentSpan?: Span
22+
gatsbyNodeGraphQLFunction?: Runner
23+
graphqlRunner?: GraphQLRunner
24+
webhookBody?: Record<string, unknown>
25+
refresh?: boolean
26+
workerPool?: JestWorker
27+
pagesToBuild?: string[]
28+
pagesToDelete?: string[]
29+
}

0 commit comments

Comments
 (0)
Please sign in to comment.