Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Defer node mutation during querying #25479

Merged
merged 68 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1193d2e
Move bootstrap into machine
ascorbic Jun 25, 2020
696d5fa
Add parent span and query extraction
ascorbic Jun 26, 2020
77b7f3b
Add rebuildSchemaWithSitePage
ascorbic Jun 26, 2020
6f97db6
Use values from context
ascorbic Jun 26, 2020
36e8ef4
Remove logs
ascorbic Jun 26, 2020
07d0741
Add redirectListener
ascorbic Jun 26, 2020
cd3a909
Changes from review
ascorbic Jun 26, 2020
d2b615b
Log child state transitions
ascorbic Jun 26, 2020
01280a9
Add state machine for query running
ascorbic Jun 29, 2020
d049022
Changes from review
ascorbic Jul 1, 2020
2dad129
Changes from review
ascorbic Jul 1, 2020
1df526a
Merge branch 'feat/bootstrap-state-machine' into feat/query-state-mac…
ascorbic Jul 1, 2020
26420c4
Switch to reporter
ascorbic Jul 1, 2020
637c642
Merge branch 'master' into feat/bootstrap-state-machine
ascorbic Jul 1, 2020
4ff46bd
Merge branch 'master' into feat/bootstrap-state-machine
ascorbic Jul 1, 2020
7bc5056
Use assertStore
ascorbic Jul 1, 2020
cd82b3f
Merge branch 'feat/bootstrap-state-machine' into feat/query-state-mac…
ascorbic Jul 1, 2020
6f0ab66
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 1, 2020
2ce7b77
Remove unused action
ascorbic Jul 1, 2020
133099d
Remove unusued config
ascorbic Jul 1, 2020
e7f426e
Remove unusued config
ascorbic Jul 1, 2020
7da48a6
Add gql runner reset
ascorbic Jul 1, 2020
8f497be
Merge branch 'feat/query-state-machine' of github.com:gatsbyjs/gatsby…
ascorbic Jul 1, 2020
a4ac2dc
Handle node mutation queuing and batching in state machine
ascorbic Jul 2, 2020
6ca668f
Use new pagedata utils
ascorbic Jul 3, 2020
b701d7c
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
d3e7a59
Use develop queue
ascorbic Jul 3, 2020
ae0ece9
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
28e7691
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 3, 2020
896a2af
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
186eb0c
New xstate syntax
ascorbic Jul 3, 2020
c0acfe1
Work-around xstate bug
ascorbic Jul 3, 2020
12d5f20
Track first run
ascorbic Jul 3, 2020
78463b7
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
8987299
Track first run
ascorbic Jul 3, 2020
dc3fb42
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
fa05e0d
Disable --quiet in e2e
ascorbic Jul 3, 2020
4e8609f
Don't defer node mutation if we're outside the state machine
ascorbic Jul 3, 2020
425eafc
Re-quieten e2e
ascorbic Jul 3, 2020
aadc60b
Listen for query file changes
ascorbic Jul 6, 2020
33dc4fa
Lint
ascorbic Jul 6, 2020
50faf93
Handle webhook
ascorbic Jul 6, 2020
f9bdf3c
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 7, 2020
059798b
Changes from review
ascorbic Jul 7, 2020
da0b2ce
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 7, 2020
e9bf259
Fix typings
ascorbic Jul 7, 2020
2116857
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 7, 2020
e514a76
Changes from review
ascorbic Jul 7, 2020
9edd938
Typefix
ascorbic Jul 7, 2020
a32dc5f
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 9, 2020
b126a99
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 13, 2020
95e0291
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 13, 2020
9496046
feat(gatsby): Move final parts into develop state machine (#25716)
ascorbic Jul 14, 2020
1cab0be
Resolve api promises
ascorbic Jul 15, 2020
6966e76
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 15, 2020
00a975d
Remove unused action
ascorbic Jul 16, 2020
087ed94
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 16, 2020
c807968
Move logging into helper
ascorbic Jul 16, 2020
69f9a7a
Changes from review
ascorbic Jul 16, 2020
523478a
Manually save db
ascorbic Jul 17, 2020
b814215
Add comments
ascorbic Jul 17, 2020
38c05f5
Remove first run from query running
ascorbic Jul 17, 2020
98936a8
Refactor into separate data layer machines
ascorbic Jul 17, 2020
3f758f5
Fix condition
ascorbic Jul 17, 2020
f12ceec
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
e8ddb4d
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
e450d7f
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
0d672fc
Merge remote-tracking branch 'upstream/master' into feat/defer-node-m…
Jul 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/gatsby/src/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export async function bootstrap(
const bootstrapContext: IBuildContext = {
...initialContext,
parentSpan,
firstRun: true,
}

const context = {
Expand Down
220 changes: 16 additions & 204 deletions packages/gatsby/src/commands/develop-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,20 @@ import chalk from "chalk"
import telemetry from "gatsby-telemetry"
import express from "express"
import inspector from "inspector"
import { bootstrapSchemaHotReloader } from "../bootstrap/schema-hot-reloader"
import bootstrapPageHotReloader from "../bootstrap/page-hot-reloader"
import { initTracer } from "../utils/tracer"
import db from "../db"
import { detectPortInUseAndPrompt } from "../utils/detect-port-in-use-and-prompt"
import onExit from "signal-exit"
import queryUtil from "../query"
import queryWatcher from "../query/query-watcher"
import * as requiresWriter from "../bootstrap/requires-writer"
import { waitUntilAllJobsComplete } from "../utils/wait-until-jobs-complete"
import {
userPassesFeedbackRequestHeuristic,
showFeedbackRequest,
} from "../utils/feedback"
import { startRedirectListener } from "../bootstrap/redirects-writer"
import { markWebpackStatusAsPending } from "../utils/webpack-status"

import { IProgram, IDebugInfo } from "./types"
import {
startWebpackServer,
writeOutRequires,
IBuildContext,
initialize,
postBootstrap,
rebuildSchemaWithSitePage,
writeOutRedirects,
} from "../services"
import { boundActionCreators } from "../redux/actions"
import { ProgramStatus } from "../redux/types"
import {
MachineConfig,
AnyEventObject,
assign,
Machine,
DoneEventObject,
interpret,
Actor,
Interpreter,
State,
} from "xstate"
import { DataLayerResult, dataLayerMachine } from "../state-machines/data-layer"
import { IDataLayerContext } from "../state-machines/data-layer/types"
import { interpret } from "xstate"
import { globalTracer } from "opentracing"
import { IQueryRunningContext } from "../state-machines/query-running/types"
import { queryRunningMachine } from "../state-machines/query-running"
import { developMachine } from "../state-machines/develop"
import { logTransitions } from "../utils/state-machine-logging"

const tracer = globalTracer()

Expand Down Expand Up @@ -100,12 +69,14 @@ const openDebuggerPort = (debugInfo: IDebugInfo): void => {
}

module.exports = async (program: IDevelopArgs): Promise<void> => {
if (program.verbose) {
reporter.setVerbose(true)
}

if (program.debugInfo) {
openDebuggerPort(program.debugInfo)
}

const bootstrapSpan = tracer.startSpan(`bootstrap`)

// We want to prompt the feedback request when users quit develop
// assuming they pass the heuristic check to know they are a user
// we want to request feedback from, and we're not annoying them.
Expand Down Expand Up @@ -148,178 +119,19 @@ module.exports = async (program: IDevelopArgs): Promise<void> => {
}

const app = express()
const parentSpan = tracer.startSpan(`bootstrap`)

const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
id: `build`,
initial: `initializing`,
states: {
initializing: {
invoke: {
src: `initialize`,
onDone: {
target: `initializingDataLayer`,
actions: `assignStoreAndWorkerPool`,
},
},
},
initializingDataLayer: {
invoke: {
src: `initializeDataLayer`,
data: ({ parentSpan, store }: IBuildContext): IDataLayerContext => {
return { parentSpan, store, firstRun: true }
},
onDone: {
actions: `assignDataLayer`,
target: `finishingBootstrap`,
},
},
},
finishingBootstrap: {
invoke: {
src: async ({
gatsbyNodeGraphQLFunction,
}: IBuildContext): Promise<void> => {
// These were previously in `bootstrap()` but are now
// in part of the state machine that hasn't been added yet
await rebuildSchemaWithSitePage({ parentSpan: bootstrapSpan })

await writeOutRedirects({ parentSpan: bootstrapSpan })

startRedirectListener()
bootstrapSpan.finish()
await postBootstrap({ parentSpan: bootstrapSpan })

// These are the parts that weren't in bootstrap

// Start the createPages hot reloader.
bootstrapPageHotReloader(gatsbyNodeGraphQLFunction)

// Start the schema hot reloader.
bootstrapSchemaHotReloader()
},
onDone: {
target: `runningQueries`,
},
},
},
runningQueries: {
invoke: {
src: `runQueries`,
data: ({
program,
store,
parentSpan,
gatsbyNodeGraphQLFunction,
graphqlRunner,
firstRun,
}: IBuildContext): IQueryRunningContext => {
return {
firstRun,
program,
store,
parentSpan,
gatsbyNodeGraphQLFunction,
graphqlRunner,
}
},
onDone: {
target: `doingEverythingElse`,
},
},
},
doingEverythingElse: {
invoke: {
src: async ({ workerPool, store, app }): Promise<void> => {
// All the stuff that's not in the state machine yet

await writeOutRequires({ store })
boundActionCreators.setProgramStatus(
ProgramStatus.BOOTSTRAP_QUERY_RUNNING_FINISHED
)

await db.saveState()
const machine = developMachine.withContext({
program,
parentSpan,
app,
})

await waitUntilAllJobsComplete()
requiresWriter.startListener()
db.startAutosave()
queryUtil.startListeningToDevelopQueue({
graphqlTracing: program.graphqlTracing,
})
queryWatcher.startWatchDeletePage()
const service = interpret(machine)

await startWebpackServer({ program, app, workerPool, store })
},
onDone: {
actions: assign<IBuildContext, any>({ firstRun: false }),
},
},
},
},
if (program.verbose) {
logTransitions(service)
}

const service = interpret(
Machine(developConfig, {
services: {
initializeDataLayer: dataLayerMachine,
initialize,
runQueries: queryRunningMachine,
},
actions: {
assignStoreAndWorkerPool: assign<IBuildContext, DoneEventObject>(
(_context, event) => {
const { store, workerPool } = event.data
return {
store,
workerPool,
}
}
),
assignDataLayer: assign<IBuildContext, DoneEventObject>(
(_, { data }): DataLayerResult => data
),
},
}).withContext({ program, parentSpan: bootstrapSpan, app, firstRun: true })
)

const isInterpreter = <T>(
actor: Actor<T> | Interpreter<T>
): actor is Interpreter<T> => `machine` in actor

const listeners = new WeakSet()
let last: State<IBuildContext, AnyEventObject, any, any>

service.onTransition(state => {
if (!last) {
last = state
} else if (!state.changed || last.matches(state)) {
return
}
last = state
reporter.verbose(`Transition to ${JSON.stringify(state.value)}`)
// eslint-disable-next-line no-unused-expressions
service.children?.forEach(child => {
// We want to ensure we don't attach a listener to the same
// actor. We don't need to worry about detaching the listener
// because xstate handles that for us when the actor is stopped.

if (isInterpreter(child) && !listeners.has(child)) {
let sublast = child.state
child.onTransition(substate => {
if (!sublast) {
sublast = substate
} else if (!substate.changed || sublast.matches(substate)) {
return
}
sublast = substate
reporter.verbose(
`Transition to ${JSON.stringify(state.value)} > ${JSON.stringify(
substate.value
)}`
)
})
listeners.add(child)
}
})
})
service.start()
}
1 change: 1 addition & 0 deletions packages/gatsby/src/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface IProgram {
inspect?: number
inspectBrk?: number
graphqlTracing?: boolean
verbose?: boolean
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
setStore?: (store: Store<IGatsbyState, AnyAction>) => void
}

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/src/query/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ const enqueueExtractedPageComponent = componentPath => {

module.exports = {
calcInitialDirtyQueryIds,
calcDirtyQueryIds,
processPageQueries,
processStaticQueries,
groupQueryIds,
Expand Down
25 changes: 12 additions & 13 deletions packages/gatsby/src/query/query-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
* - Whenever a query changes, re-run all pages that rely on this query.
***/

const _ = require(`lodash`)
const chokidar = require(`chokidar`)

const path = require(`path`)
Expand Down Expand Up @@ -196,8 +195,7 @@ exports.extractQueries = ({ parentSpan } = {}) => {
// During development start watching files to recompile & run
// queries on the fly.

// TODO: move this into a spawned service, and emit events rather than
// directly triggering the compilation
// TODO: move this into a spawned service
if (process.env.NODE_ENV !== `production`) {
watch(store.getState().program.directory)
}
Expand All @@ -222,10 +220,6 @@ const watchComponent = componentPath => {
}
}

const debounceCompile = _.debounce(() => {
updateStateAndRunQueries()
}, 100)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this debounce completely removed or just moved around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to debounce anymore, because we're not actually compiling on these events, just dispatching an event to the state machine to say that we'd like to compile when ready.

const watch = async rootDir => {
if (watcher) return

Expand All @@ -238,13 +232,18 @@ const watch = async rootDir => {
})

watcher = chokidar
.watch([
slash(path.join(rootDir, `/src/**/*.{js,jsx,ts,tsx}`)),
...packagePaths,
])
.watch(
[slash(path.join(rootDir, `/src/**/*.{js,jsx,ts,tsx}`)), ...packagePaths],
{ ignoreInitial: true }
)
.on(`change`, path => {
report.pendingActivity({ id: `query-extraction` })
debounceCompile()
emitter.emit(`QUERY_FILE_CHANGED`, path)
})
.on(`add`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
})
.on(`unlink`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
pvdz marked this conversation as resolved.
Show resolved Hide resolved
})
wardpeet marked this conversation as resolved.
Show resolved Hide resolved

filesToWatch.forEach(filePath => watcher.add(filePath))
Expand Down
12 changes: 9 additions & 3 deletions packages/gatsby/src/services/calculate-dirty-queries.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { calcInitialDirtyQueryIds, groupQueryIds } from "../query"
import {
calcInitialDirtyQueryIds,
calcDirtyQueryIds,
groupQueryIds,
} from "../query"
import { IGroupedQueryIds } from "./"
import { IQueryRunningContext } from "../state-machines/query-running/types"
import { assertStore } from "../utils/assert-store"

export async function calculateDirtyQueries({
store,
firstRun,
}: Partial<IQueryRunningContext>): Promise<{
queryIds: IGroupedQueryIds
}> {
assertStore(store)

const state = store.getState()
// TODO: Check filesDirty from context

const queryIds = calcInitialDirtyQueryIds(state)
const queryIds = firstRun
? calcInitialDirtyQueryIds(state)
: calcDirtyQueryIds(state)
Comment on lines +20 to +22
Copy link
Contributor

@wardpeet wardpeet Jul 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do the firstRun guard inside the statemachine instead of here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think you're right: this should be two different services.

return { queryIds: groupQueryIds(queryIds) }
}
3 changes: 2 additions & 1 deletion packages/gatsby/src/services/create-pages-statefully.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IDataLayerContext } from "../state-machines/data-layer/types"
export async function createPagesStatefully({
parentSpan,
gatsbyNodeGraphQLFunction,
deferNodeMutation,
}: Partial<IDataLayerContext>): Promise<void> {
// A variant on createPages for plugins that want to
// have full control over adding/removing pages. The normal
Expand All @@ -21,7 +22,7 @@ export async function createPagesStatefully({
traceId: `initial-createPagesStatefully`,
waitForCascadingActions: true,
parentSpan: activity.span,
// deferNodeMutation: true, //later
deferNodeMutation,
},
{
activity,
Expand Down
Loading