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): Handle webpack in state machine #25815

Merged
merged 2 commits into from
Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 1 addition & 3 deletions packages/gatsby/src/commands/develop-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ const openDebuggerPort = (debugInfo: IDebugInfo): void => {
}

module.exports = async (program: IDevelopArgs): Promise<void> => {
if (program.verbose) {
Copy link
Contributor

Choose a reason for hiding this comment

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

😂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ikr

reporter.setVerbose(true)
}
reporter.setVerbose(program.verbose)

if (program.debugInfo) {
openDebuggerPort(program.debugInfo)
Expand Down
6 changes: 3 additions & 3 deletions packages/gatsby/src/query/query-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,13 @@ const watch = async rootDir => {
{ ignoreInitial: true }
)
.on(`change`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
emitter.emit(`SOURCE_FILE_CHANGED`, path)
})
.on(`add`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
emitter.emit(`SOURCE_FILE_CHANGED`, path)
})
.on(`unlink`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
emitter.emit(`SOURCE_FILE_CHANGED`, path)
})

filesToWatch.forEach(filePath => watcher.add(filePath))
Expand Down
3 changes: 3 additions & 0 deletions packages/gatsby/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { runPageQueries } from "./run-page-queries"

import { waitUntilAllJobsComplete } from "../utils/wait-until-jobs-complete"
import { runMutationBatch } from "./run-mutation-batch"
import { recompile } from "./recompile"

export * from "./types"

Expand All @@ -40,6 +41,7 @@ export {
startWebpackServer,
rebuildSchemaWithSitePage,
runMutationBatch,
recompile,
}

export const buildServices: Record<string, ServiceConfig<IBuildContext>> = {
Expand All @@ -59,4 +61,5 @@ export const buildServices: Record<string, ServiceConfig<IBuildContext>> = {
writeOutRedirects,
startWebpackServer,
rebuildSchemaWithSitePage,
recompile,
}
12 changes: 3 additions & 9 deletions packages/gatsby/src/services/listen-for-mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,21 @@ export const listenForMutations: InvokeCallback = (callback: Sender<any>) => {
callback({ type: `ADD_NODE_MUTATION`, payload: event })
}

const emitFileChange = (event: unknown): void => {
const emitSourceChange = (event: unknown): void => {
callback({ type: `SOURCE_FILE_CHANGED`, payload: event })
}

const emitQueryChange = (event: unknown): void => {
callback({ type: `QUERY_FILE_CHANGED`, payload: event })
}

const emitWebhook = (event: unknown): void => {
callback({ type: `WEBHOOK_RECEIVED`, payload: event })
}

emitter.on(`ENQUEUE_NODE_MUTATION`, emitMutation)
emitter.on(`WEBHOOK_RECEIVED`, emitWebhook)
emitter.on(`SOURCE_FILE_CHANGED`, emitFileChange)
emitter.on(`QUERY_FILE_CHANGED`, emitQueryChange)
emitter.on(`SOURCE_FILE_CHANGED`, emitSourceChange)

return function unsubscribeFromMutationListening(): void {
emitter.off(`ENQUEUE_NODE_MUTATION`, emitMutation)
emitter.off(`SOURCE_FILE_CHANGED`, emitFileChange)
emitter.off(`WEBHOOK_RECEIVED`, emitWebhook)
emitter.off(`QUERY_FILE_CHANGED`, emitQueryChange)
emitter.off(`SOURCE_FILE_CHANGED`, emitSourceChange)
}
}
12 changes: 12 additions & 0 deletions packages/gatsby/src/services/listen-to-webpack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Compiler } from "webpack"
import { InvokeCallback } from "xstate"
import reporter from "gatsby-cli/lib/reporter"

export const createWebpackWatcher = (compiler: Compiler): InvokeCallback => (
callback
): void => {
compiler.hooks.invalid.tap(`file invalidation`, file => {
reporter.verbose(`Webpack file changed: ${file}`)
callback({ type: `SOURCE_FILE_CHANGED`, file })
})
}
27 changes: 27 additions & 0 deletions packages/gatsby/src/services/recompile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable no-unused-expressions */
import { IBuildContext } from "./types"
import { Stats } from "webpack"
import reporter from "gatsby-cli/lib/reporter"
import { emitter } from "../redux"

export async function recompile({
webpackWatching,
}: IBuildContext): Promise<Stats> {
if (!webpackWatching) {
reporter.panic(`Missing compiler`)
}
// Promisify the event-based API. We do this using emitter
// because you can't "untap" a webpack watcher, and we just want
// one compilation.

return new Promise(resolve => {
function finish(stats: Stats): void {
emitter.off(`COMPILATION_DONE`, finish)
resolve(stats)
}
emitter.on(`COMPILATION_DONE`, finish)
webpackWatching.resume()
// Suspending is just a flag, so it's safe to re-suspend right away
webpackWatching.suspend()
})
}
3 changes: 3 additions & 0 deletions packages/gatsby/src/services/start-webpack-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "../utils/webpack-status"
import { enqueueFlush } from "../utils/page-data"
import mapTemplatesToStaticQueryHashes from "../utils/map-templates-to-static-query-hashes"
import { emitter } from "../redux"

export async function startWebpackServer({
program,
Expand All @@ -42,6 +43,7 @@ export async function startWebpackServer({
websocketManager,
webpackWatching,
} = await startServer(program, app, workerPool)
webpackWatching.suspend()

compiler.hooks.invalid.tap(`log compiling`, function () {
if (!webpackActivity) {
Expand Down Expand Up @@ -160,6 +162,7 @@ export async function startWebpackServer({

markWebpackStatusAsDone()
done()
emitter.emit(`COMPILATION_DONE`, stats)
resolve({ compiler, websocketManager, webpackWatching })
})
})
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby/src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ export interface IBuildContext {
compiler?: Compiler
websocketManager?: WebsocketManager
webpackWatching?: IWebpackWatchingPauseResume
webpackListener?: Actor<unknown, AnyEventObject>
queryFilesDirty?: boolean
sourceFilesDirty?: boolean
}
21 changes: 21 additions & 0 deletions packages/gatsby/src/state-machines/develop/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { assertStore } from "../../utils/assert-store"
import { saveState } from "../../db"
import reporter from "gatsby-cli/lib/reporter"
import { ProgramStatus } from "../../redux/types"
import { createWebpackWatcher } from "../../services/listen-to-webpack"

/**
* These are the deferred redux actions sent from api-runner-node
Expand Down Expand Up @@ -79,6 +80,14 @@ export const markQueryFilesDirty = assign<IBuildContext>({
queryFilesDirty: true,
})

export const markSourceFilesDirty = assign<IBuildContext>({
sourceFilesDirty: true,
})

export const markSourceFilesClean = assign<IBuildContext>({
sourceFilesDirty: false,
})

export const assignServiceResult = assign<IBuildContext, DoneEventObject>(
(_context, { data }): DataLayerResult => data
)
Expand All @@ -98,6 +107,15 @@ export const assignServers = assign<IBuildContext, AnyEventObject>(
}
)

export const spawnWebpackListener = assign<IBuildContext, AnyEventObject>({
webpackListener: ({ compiler }) => {
if (!compiler) {
return undefined
}
return spawn(createWebpackWatcher(compiler))
},
})

export const assignWebhookBody = assign<IBuildContext, AnyEventObject>({
webhookBody: (_context, { payload }) => payload?.webhookBody,
})
Expand Down Expand Up @@ -135,6 +153,9 @@ export const buildActions: ActionFunctionMap<IBuildContext, AnyEventObject> = {
assignWebhookBody,
clearWebhookBody,
finishParentSpan,
spawnWebpackListener,
markSourceFilesDirty,
markSourceFilesClean,
saveDbState,
setQueryRunningFinished,
}
44 changes: 30 additions & 14 deletions packages/gatsby/src/state-machines/develop/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
ADD_NODE_MUTATION: {
actions: `addNodeMutation`,
},
// Sent by query watcher, these are chokidar file events. They mean we
// need to extract queries
QUERY_FILE_CHANGED: {
actions: `markQueryFilesDirty`,
// Sent when webpack or chokidar sees a changed file
SOURCE_FILE_CHANGED: {
actions: `markSourceFilesDirty`,
},
// These are calls to the refresh endpoint. Also used by Gatsby Preview.
// Saves the webhook body from the event into context, then reloads data
Expand All @@ -37,7 +36,7 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
on: {
// Ignore mutation events because we'll be running everything anyway
ADD_NODE_MUTATION: undefined,
QUERY_FILE_CHANGED: undefined,
SOURCE_FILE_CHANGED: undefined,
WEBHOOK_RECEIVED: undefined,
},
invoke: {
Expand All @@ -55,8 +54,6 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
ADD_NODE_MUTATION: {
actions: [`markNodesDirty`, `callApi`],
},
// Ignore, because we're about to extract them anyway
QUERY_FILE_CHANGED: undefined,
},
invoke: {
src: `initializeData`,
Expand Down Expand Up @@ -85,8 +82,8 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
// Running page and static queries and generating the SSRed HTML and page data
runningQueries: {
on: {
QUERY_FILE_CHANGED: {
actions: forwardTo(`run-queries`),
SOURCE_FILE_CHANGED: {
actions: [forwardTo(`run-queries`), `markSourceFilesDirty`],
},
},
invoke: {
Expand Down Expand Up @@ -118,34 +115,53 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
actions: `setQueryRunningFinished`,
cond: ({ compiler }: IBuildContext): boolean => !compiler,
},
{
// If source files have changed, then recompile the JS bundle
target: `recompiling`,
cond: ({ sourceFilesDirty }: IBuildContext): boolean =>
!!sourceFilesDirty,
},
{
// ...otherwise just wait.
target: `waiting`,
},
],
},
},
// Recompile the JS bundle
recompiling: {
invoke: {
src: `recompile`,
onDone: {
actions: `markSourceFilesClean`,
target: `waiting`,
},
},
},
// Spin up webpack and socket.io
startingDevServers: {
invoke: {
src: `startWebpackServer`,
onDone: {
target: `waiting`,
actions: `assignServers`,
actions: [
`assignServers`,
`spawnWebpackListener`,
`markSourceFilesClean`,
],
},
},
},
// Idle, waiting for events that make us rebuild
waiting: {
// We may want to save this is more places, but this should do for now
entry: `saveDbState`,
on: {
// Forward these events to the child machine, so it can handle batching
ADD_NODE_MUTATION: {
actions: forwardTo(`waiting`),
},
QUERY_FILE_CHANGED: {
actions: forwardTo(`waiting`),
SOURCE_FILE_CHANGED: {
actions: [forwardTo(`waiting`), `markSourceFilesDirty`],
},
// This event is sent from the child
EXTRACT_QUERIES_NOW: {
Expand Down Expand Up @@ -177,7 +193,7 @@ const developConfig: MachineConfig<IBuildContext, any, AnyEventObject> = {
actions: [`markNodesDirty`, `callApi`],
},
// Ignore, because we're about to extract them anyway
QUERY_FILE_CHANGED: undefined,
SOURCE_FILE_CHANGED: undefined,
},
invoke: {
src: `reloadData`,
Expand Down
10 changes: 8 additions & 2 deletions packages/gatsby/src/state-machines/develop/services.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IBuildContext, startWebpackServer, initialize } from "../../services"
import {
IBuildContext,
startWebpackServer,
initialize,
recompile,
} from "../../services"
import {
initializeDataMachine,
reloadDataMachine,
Expand All @@ -15,5 +20,6 @@ export const developServices: Record<string, ServiceConfig<IBuildContext>> = {
initialize: initialize,
runQueries: queryRunningMachine,
waitForMutations: waitingMachine,
startWebpackServer: startWebpackServer,
startWebpackServer,
recompile,
}
5 changes: 5 additions & 0 deletions packages/gatsby/src/state-machines/query-running/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { queryActions } from "./actions"
export const queryStates: MachineConfig<IQueryRunningContext, any, any> = {
initial: `extractingQueries`,
id: `queryRunningMachine`,
on: {
SOURCE_FILE_CHANGED: {
target: `extractingQueries`,
},
},
context: {},
states: {
extractingQueries: {
Expand Down
25 changes: 23 additions & 2 deletions packages/gatsby/src/state-machines/waiting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,33 @@ export const waitingStates: MachineConfig<IWaitingContext, any, any> = {
},
// We only listen for this when idling because if we receive it at any
// other point we're already going to create pages etc
QUERY_FILE_CHANGED: {
SOURCE_FILE_CHANGED: {
target: `aggregatingFileChanges`,
},
},
},
aggregatingFileChanges: {
// Sigh. This is because webpack doesn't expose the Watchpack
// aggregated file invalidation events. If we compile immediately,
// we won't pick up the changed files
after: {
// The aggregation timeout
200: {
Copy link
Contributor

Choose a reason for hiding this comment

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

How did you choose this number? Is this internal webpack's timeout?

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, this is the default value that webpack passes to watchpack.

actions: `extractQueries`,
target: `idle`,
},
},
on: {
ADD_NODE_MUTATION: {
actions: `addNodeMutation`,
},
SOURCE_FILE_CHANGED: {
target: undefined,
// External self-transition reset the timer
internal: false,
},
},
},

batchingNodeMutations: {
// Check if the batch is already full on entry
always: {
Expand Down