Skip to content

Commit db36d69

Browse files
piehwardpeet
authored andcommitted
fix(gatsby-plugin-sharp): hot fix for "Generating image thumbnails" progress bar reporting duplicates that don't do actual processing (#20839)
1 parent 8a82ce8 commit db36d69

File tree

6 files changed

+124
-72
lines changed

6 files changed

+124
-72
lines changed

packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap

+2
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,7 @@ exports[`gatsby-plugin-sharp queueImageResizing with createJob file name works w
904904
"outputDir": "<PROJECT_ROOT>/public/static/1234",
905905
},
906906
Object {},
907+
undefined,
907908
],
908909
],
909910
"results": Array [
@@ -945,6 +946,7 @@ exports[`gatsby-plugin-sharp queueImageResizing with createJob should round heig
945946
"outputDir": "<PROJECT_ROOT>/public/static/1234",
946947
},
947948
Object {},
949+
undefined,
948950
],
949951
],
950952
"results": Array [

packages/gatsby-plugin-sharp/src/__tests__/utils.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
jest.mock(`gatsby-cli/lib/reporter`)
22
jest.mock(`progress`)
3-
const { createProgress } = require(`../utils`)
3+
const {
4+
createGatsbyProgressOrFallbackToExternalProgressBar,
5+
} = require(`../utils`)
46
const reporter = require(`gatsby-cli/lib/reporter`)
57
const progress = require(`progress`)
68

7-
describe(`createProgress`, () => {
9+
describe(`createGatsbyProgressOrFallbackToExternalProgressBar`, () => {
810
beforeEach(() => {
911
progress.mockClear()
1012
})
1113

1214
it(`should use createProgress from gatsby-cli when available`, () => {
13-
createProgress(`test`, reporter)
15+
createGatsbyProgressOrFallbackToExternalProgressBar(`test`, reporter)
1416
expect(reporter.createProgress).toBeCalled()
1517
expect(progress).not.toBeCalled()
1618
})
1719

1820
it(`should fallback to a local implementation when createProgress does not exists on reporter`, () => {
1921
reporter.createProgress = null
20-
const bar = createProgress(`test`, reporter)
22+
const bar = createGatsbyProgressOrFallbackToExternalProgressBar(
23+
`test`,
24+
reporter
25+
)
2126
expect(progress).toHaveBeenCalledTimes(1)
2227
expect(bar).toHaveProperty(`start`, expect.any(Function))
2328
expect(bar).toHaveProperty(`tick`, expect.any(Function))
@@ -26,7 +31,7 @@ describe(`createProgress`, () => {
2631
})
2732

2833
it(`should fallback to a local implementation when no reporter is present`, () => {
29-
const bar = createProgress(`test`)
34+
const bar = createGatsbyProgressOrFallbackToExternalProgressBar(`test`)
3035
expect(progress).toHaveBeenCalledTimes(1)
3136
expect(bar).toHaveProperty(`start`, expect.any(Function))
3237
expect(bar).toHaveProperty(`tick`, expect.any(Function))

packages/gatsby-plugin-sharp/src/gatsby-node.js

+40-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
const {
22
setBoundActionCreators,
3-
getProgressBar,
43
// queue: jobQueue,
54
// reportError,
65
} = require(`./index`)
6+
const { getProgressBar, createOrGetProgressBar } = require(`./utils`)
77

88
const { setPluginOptions } = require(`./plugin-options`)
99

@@ -63,9 +63,47 @@ const finishProgressBar = () => {
6363
exports.onPostBuild = () => finishProgressBar()
6464
exports.onCreateDevServer = () => finishProgressBar()
6565

66-
exports.onPreBootstrap = ({ actions }, pluginOptions) => {
66+
exports.onPreBootstrap = ({ actions, emitter, reporter }, pluginOptions) => {
6767
setBoundActionCreators(actions)
6868
setPluginOptions(pluginOptions)
69+
70+
// below is a hack / hot fix for confusing progress bar behaviour
71+
// that doesn't recognize duplicate jobs, as it's now
72+
// in gatsby core internals (if `createJobV2` is available)
73+
// we should remove this or make this code path not hit
74+
// (we should never use emitter in plugins)
75+
// as soon as possible (possibly by moving progress bar handling
76+
// inside jobs-manager in core)
77+
78+
if (emitter) {
79+
// track how many image transformation each job has
80+
// END_JOB_V2 doesn't contain that information
81+
// so we store it in <JobContentHash, TransformsCount> map
82+
// when job is created. Then retrieve that when job finishes
83+
// and remove that entry from the map.
84+
const imageCountInJobsMap = new Map()
85+
86+
emitter.on(`CREATE_JOB_V2`, action => {
87+
if (action.plugin.name === `gatsby-plugin-sharp`) {
88+
const job = action.payload.job
89+
const imageCount = job.args.operations.length
90+
imageCountInJobsMap.set(job.contentDigest, imageCount)
91+
const progress = createOrGetProgressBar(reporter)
92+
progress.addImageToProcess(imageCount)
93+
}
94+
})
95+
96+
emitter.on(`END_JOB_V2`, action => {
97+
if (action.plugin.name === `gatsby-plugin-sharp`) {
98+
const jobContentDigest = action.payload.jobContentDigest
99+
const imageCount = imageCountInJobsMap.get(jobContentDigest)
100+
const progress = createOrGetProgressBar(reporter)
101+
progress.tick(imageCount)
102+
imageCountInJobsMap.delete(jobContentDigest)
103+
}
104+
})
105+
}
106+
69107
// normalizedOptions = setPluginOptions(pluginOptions)
70108
}
71109

packages/gatsby-plugin-sharp/src/index.js

+4-59
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const {
1818
const { memoizedTraceSVG, notMemoizedtraceSVG } = require(`./trace-svg`)
1919
const duotone = require(`./duotone`)
2020
const { IMAGE_PROCESSING_JOB_NAME } = require(`./gatsby-worker`)
21-
const { createProgress } = require(`./utils`)
2221

2322
const imageSizeCache = new Map()
2423
const getImageSize = file => {
@@ -36,46 +35,6 @@ const getImageSize = file => {
3635
}
3736
}
3837

39-
let progressBar
40-
let pendingImagesCounter = 0
41-
let firstPass = true
42-
const createOrGetProgressBar = reporter => {
43-
if (!progressBar) {
44-
progressBar = createProgress(`Generating image thumbnails`, reporter)
45-
46-
const originalDoneFn = progressBar.done
47-
48-
// TODO this logic should be moved to the reporter.
49-
// when done is called we remove the progressbar instance and reset all the things
50-
// this will be called onPostBuild or when devserver is created
51-
progressBar.done = () => {
52-
originalDoneFn.call(progressBar)
53-
progressBar = null
54-
pendingImagesCounter = 0
55-
}
56-
57-
// when we create a progressBar for the second time so when .done() has been called before
58-
// we create a modified tick function that automatically stops the progressbar when total is reached
59-
// this is used for development as we're watching for changes
60-
if (!firstPass) {
61-
let progressBarCurrentValue = 0
62-
const originalTickFn = progressBar.tick
63-
progressBar.tick = (ticks = 1) => {
64-
originalTickFn.call(progressBar, ticks)
65-
progressBarCurrentValue += ticks
66-
67-
if (progressBarCurrentValue === pendingImagesCounter) {
68-
progressBar.done()
69-
}
70-
}
71-
}
72-
firstPass = false
73-
}
74-
75-
return progressBar
76-
}
77-
exports.getProgressBar = () => progressBar
78-
7938
// Bound action creators should be set when passed to onPreInit in gatsby-node.
8039
// ** It is NOT safe to just directly require the gatsby module **.
8140
// There is no guarantee that the module resolved is the module executing!
@@ -148,16 +107,6 @@ function prepareQueue({ file, args }) {
148107
}
149108

150109
function createJob(job, { reporter }) {
151-
const progressBar = createOrGetProgressBar(reporter)
152-
153-
if (pendingImagesCounter === 0) {
154-
progressBar.start()
155-
}
156-
157-
const transformsCount = job.args.operations.length
158-
pendingImagesCounter += transformsCount
159-
progressBar.total = pendingImagesCounter
160-
161110
// Jobs can be duplicates and usually are long running tasks.
162111
// Because of that we shouldn't use async/await and instead opt to use
163112
// .then() /.catch() handlers, because this allows V8 to release
@@ -169,16 +118,12 @@ function createJob(job, { reporter }) {
169118
if (boundActionCreators.createJobV2) {
170119
promise = boundActionCreators.createJobV2(job)
171120
} else {
172-
promise = scheduleJob(job, boundActionCreators)
121+
promise = scheduleJob(job, boundActionCreators, reporter)
173122
}
174123

175-
promise
176-
.catch(err => {
177-
reporter.panic(err)
178-
})
179-
.then(() => {
180-
progressBar.tick(transformsCount)
181-
})
124+
promise.catch(err => {
125+
reporter.panic(err)
126+
})
182127

183128
return promise
184129
}

packages/gatsby-plugin-sharp/src/scheduler.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const fs = require(`fs-extra`)
44
const got = require(`got`)
55
const { createContentDigest } = require(`gatsby-core-utils`)
66
const worker = require(`./gatsby-worker`)
7+
const { createOrGetProgressBar } = require(`./utils`)
78

89
const processImages = async (jobId, job, boundActionCreators) => {
910
try {
@@ -16,7 +17,7 @@ const processImages = async (jobId, job, boundActionCreators) => {
1617
}
1718

1819
const jobsInFlight = new Map()
19-
const scheduleJob = async (job, boundActionCreators) => {
20+
const scheduleJob = async (job, boundActionCreators, reporter) => {
2021
const inputPaths = job.inputPaths.filter(
2122
inputPath => !fs.existsSync(path.join(job.outputDir, inputPath))
2223
)
@@ -70,12 +71,16 @@ const scheduleJob = async (job, boundActionCreators) => {
7071
{ name: `gatsby-plugin-sharp` }
7172
)
7273

74+
const progressBar = createOrGetProgressBar(reporter)
75+
const transformsCount = job.args.operations.length
76+
progressBar.addImageToProcess(transformsCount)
77+
7378
const promise = new Promise((resolve, reject) => {
7479
setImmediate(() => {
75-
processImages(jobId, convertedJob, boundActionCreators).then(
76-
resolve,
77-
reject
78-
)
80+
processImages(jobId, convertedJob, boundActionCreators).then(result => {
81+
progressBar.tick(transformsCount)
82+
resolve(result)
83+
}, reject)
7984
})
8085
})
8186

packages/gatsby-plugin-sharp/src/utils.js

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
const ProgressBar = require(`progress`)
22

33
// TODO remove in V3
4-
export function createProgress(message, reporter) {
4+
function createGatsbyProgressOrFallbackToExternalProgressBar(
5+
message,
6+
reporter
7+
) {
58
if (reporter && reporter.createProgress) {
69
return reporter.createProgress(message)
710
}
@@ -26,3 +29,57 @@ export function createProgress(message, reporter) {
2629
},
2730
}
2831
}
32+
33+
let progressBar
34+
let pendingImagesCounter = 0
35+
let firstPass = true
36+
const createOrGetProgressBar = reporter => {
37+
if (!progressBar) {
38+
progressBar = createGatsbyProgressOrFallbackToExternalProgressBar(
39+
`Generating image thumbnails`,
40+
reporter
41+
)
42+
43+
const originalDoneFn = progressBar.done
44+
45+
// TODO this logic should be moved to the reporter.
46+
// when done is called we remove the progressbar instance and reset all the things
47+
// this will be called onPostBuild or when devserver is created
48+
progressBar.done = () => {
49+
originalDoneFn.call(progressBar)
50+
progressBar = null
51+
pendingImagesCounter = 0
52+
}
53+
54+
progressBar.addImageToProcess = imageCount => {
55+
if (pendingImagesCounter === 0) {
56+
progressBar.start()
57+
}
58+
pendingImagesCounter += imageCount
59+
progressBar.total = pendingImagesCounter
60+
}
61+
62+
// when we create a progressBar for the second time so when .done() has been called before
63+
// we create a modified tick function that automatically stops the progressbar when total is reached
64+
// this is used for development as we're watching for changes
65+
if (!firstPass) {
66+
let progressBarCurrentValue = 0
67+
const originalTickFn = progressBar.tick
68+
progressBar.tick = (ticks = 1) => {
69+
originalTickFn.call(progressBar, ticks)
70+
progressBarCurrentValue += ticks
71+
72+
if (progressBarCurrentValue === pendingImagesCounter) {
73+
progressBar.done()
74+
}
75+
}
76+
}
77+
firstPass = false
78+
}
79+
80+
return progressBar
81+
}
82+
83+
exports.createGatsbyProgressOrFallbackToExternalProgressBar = createGatsbyProgressOrFallbackToExternalProgressBar
84+
exports.createOrGetProgressBar = createOrGetProgressBar
85+
exports.getProgressBar = () => progressBar

0 commit comments

Comments
 (0)