diff --git a/packages/gatsby/src/commands/develop-process.ts b/packages/gatsby/src/commands/develop-process.ts index 80e2e0c83b9b5..74eaaca13effa 100644 --- a/packages/gatsby/src/commands/develop-process.ts +++ b/packages/gatsby/src/commands/develop-process.ts @@ -69,9 +69,7 @@ const openDebuggerPort = (debugInfo: IDebugInfo): void => { } module.exports = async (program: IDevelopArgs): Promise => { - if (program.verbose) { - reporter.setVerbose(true) - } + reporter.setVerbose(program.verbose) if (program.debugInfo) { openDebuggerPort(program.debugInfo) diff --git a/packages/gatsby/src/services/index.ts b/packages/gatsby/src/services/index.ts index 7fc3a0c99f47c..46f86a7ae2132 100644 --- a/packages/gatsby/src/services/index.ts +++ b/packages/gatsby/src/services/index.ts @@ -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" @@ -40,6 +41,7 @@ export { startWebpackServer, rebuildSchemaWithSitePage, runMutationBatch, + recompile, } export const buildServices: Record> = { @@ -59,4 +61,5 @@ export const buildServices: Record> = { writeOutRedirects, startWebpackServer, rebuildSchemaWithSitePage, + recompile, } diff --git a/packages/gatsby/src/services/listen-for-mutations.ts b/packages/gatsby/src/services/listen-for-mutations.ts index dc21cf75edb95..415ab6b6bb99c 100644 --- a/packages/gatsby/src/services/listen-for-mutations.ts +++ b/packages/gatsby/src/services/listen-for-mutations.ts @@ -6,10 +6,6 @@ export const listenForMutations: InvokeCallback = (callback: Sender) => { callback({ type: `ADD_NODE_MUTATION`, payload: event }) } - const emitFileChange = (event: unknown): void => { - callback({ type: `SOURCE_FILE_CHANGED`, payload: event }) - } - const emitQueryChange = (event: unknown): void => { callback({ type: `QUERY_FILE_CHANGED`, payload: event }) } @@ -20,12 +16,10 @@ export const listenForMutations: InvokeCallback = (callback: Sender) => { emitter.on(`ENQUEUE_NODE_MUTATION`, emitMutation) emitter.on(`WEBHOOK_RECEIVED`, emitWebhook) - emitter.on(`SOURCE_FILE_CHANGED`, emitFileChange) emitter.on(`QUERY_FILE_CHANGED`, emitQueryChange) 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) } diff --git a/packages/gatsby/src/services/listen-to-webpack.ts b/packages/gatsby/src/services/listen-to-webpack.ts new file mode 100644 index 0000000000000..bfba134880752 --- /dev/null +++ b/packages/gatsby/src/services/listen-to-webpack.ts @@ -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 }) + }) +} diff --git a/packages/gatsby/src/services/recompile.ts b/packages/gatsby/src/services/recompile.ts new file mode 100644 index 0000000000000..fababfd537691 --- /dev/null +++ b/packages/gatsby/src/services/recompile.ts @@ -0,0 +1,28 @@ +/* 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 { + 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() + // We can imemdiately suspend, because it doesn't affect + // compilations in-progress + webpackWatching.suspend() + }) +} diff --git a/packages/gatsby/src/services/start-webpack-server.ts b/packages/gatsby/src/services/start-webpack-server.ts index 1a6f6e474471f..f9e900c9d9107 100644 --- a/packages/gatsby/src/services/start-webpack-server.ts +++ b/packages/gatsby/src/services/start-webpack-server.ts @@ -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, @@ -160,6 +161,8 @@ export async function startWebpackServer({ markWebpackStatusAsDone() done() + webpackWatching.suspend() + emitter.emit(`COMPILATION_DONE`, stats) resolve({ compiler, websocketManager, webpackWatching }) }) }) diff --git a/packages/gatsby/src/services/types.ts b/packages/gatsby/src/services/types.ts index 6a023df96242f..1f61e0a41e115 100644 --- a/packages/gatsby/src/services/types.ts +++ b/packages/gatsby/src/services/types.ts @@ -37,5 +37,7 @@ export interface IBuildContext { compiler?: Compiler websocketManager?: WebsocketManager webpackWatching?: IWebpackWatchingPauseResume + webpackListener?: Actor queryFilesDirty?: boolean + sourceFilesDirty?: boolean } diff --git a/packages/gatsby/src/state-machines/develop/actions.ts b/packages/gatsby/src/state-machines/develop/actions.ts index f2619fc6e76c0..56fc89edb4ae3 100644 --- a/packages/gatsby/src/state-machines/develop/actions.ts +++ b/packages/gatsby/src/state-machines/develop/actions.ts @@ -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 @@ -79,6 +80,14 @@ export const markQueryFilesDirty = assign({ queryFilesDirty: true, }) +export const markSourceFilesDirty = assign({ + sourceFilesDirty: true, +}) + +export const markSourceFilesClean = assign({ + sourceFilesDirty: false, +}) + export const assignServiceResult = assign( (_context, { data }): DataLayerResult => data ) @@ -98,6 +107,15 @@ export const assignServers = assign( } ) +export const spawnWebpackListener = assign({ + webpackListener: ({ compiler }) => { + if (!compiler) { + return undefined + } + return spawn(createWebpackWatcher(compiler)) + }, +}) + export const assignWebhookBody = assign({ webhookBody: (_context, { payload }) => payload?.webhookBody, }) @@ -135,6 +153,9 @@ export const buildActions: ActionFunctionMap = { assignWebhookBody, clearWebhookBody, finishParentSpan, + spawnWebpackListener, + markSourceFilesDirty, + markSourceFilesClean, saveDbState, setQueryRunningFinished, } diff --git a/packages/gatsby/src/state-machines/develop/index.ts b/packages/gatsby/src/state-machines/develop/index.ts index cd79e4d9831f4..6b5b155dc73b1 100644 --- a/packages/gatsby/src/state-machines/develop/index.ts +++ b/packages/gatsby/src/state-machines/develop/index.ts @@ -24,6 +24,10 @@ const developConfig: MachineConfig = { QUERY_FILE_CHANGED: { actions: `markQueryFilesDirty`, }, + // Sent when webpack 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 WEBHOOK_RECEIVED: { @@ -118,6 +122,12 @@ const developConfig: MachineConfig = { 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`, @@ -125,19 +135,32 @@ const developConfig: MachineConfig = { ], }, }, + // 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 diff --git a/packages/gatsby/src/state-machines/develop/services.ts b/packages/gatsby/src/state-machines/develop/services.ts index 6f94baaebd00c..e138ba3ba3fbb 100644 --- a/packages/gatsby/src/state-machines/develop/services.ts +++ b/packages/gatsby/src/state-machines/develop/services.ts @@ -1,4 +1,9 @@ -import { IBuildContext, startWebpackServer, initialize } from "../../services" +import { + IBuildContext, + startWebpackServer, + initialize, + recompile, +} from "../../services" import { initializeDataMachine, reloadDataMachine, @@ -15,5 +20,6 @@ export const developServices: Record> = { initialize: initialize, runQueries: queryRunningMachine, waitForMutations: waitingMachine, - startWebpackServer: startWebpackServer, + startWebpackServer, + recompile, }