Skip to content

Commit ffbec4c

Browse files
authored
perf(gatsby-source-filesystem): dont JSON parse/stringify the node (#27597)
* perf(gatsby-source-filesystem): dont JSON parse/stringify the node * Explicit serialization, remove redundant keys (they are splat anyways) * Explicitly splat stats object * Add a snapshot test * Attempt to make the test more generic so it works on Windows too
1 parent ed31b35 commit ffbec4c

File tree

2 files changed

+168
-30
lines changed

2 files changed

+168
-30
lines changed
Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
11
const path = require(`path`)
22

33
const { createFileNode } = require(`../create-file-node`)
4+
const fs = require(`fs-extra`)
5+
6+
const fsStatBak = fs.stat
47

58
// FIXME: This test needs to not use snapshots because of file differences
69
// and locations across users and CI systems
710
describe(`create-file-node`, () => {
8-
it(`creates a file node`, () => {
11+
beforeEach(() => {
12+
// If this breaks, note that the actual values here are not relevant. They just need to be mocked because
13+
// otherwise the tests change due to changing timestamps. The returned object should mimic the real fs.stat
14+
// Note: async tests should run in serial so this mock should not cause cross test polution on this thread.
15+
fs.stat = jest.fn().mockResolvedValue(
16+
Promise.resolve({
17+
isDirectory() {
18+
return false
19+
},
20+
dev: 123456,
21+
mode: 123456,
22+
nlink: 123456,
23+
uid: 123456,
24+
rdev: 123456,
25+
blksize: 123456,
26+
ino: 123456,
27+
size: 123456,
28+
blocks: 123456,
29+
atimeMs: 123456,
30+
mtimeMs: 123456,
31+
ctimeMs: 123456,
32+
birthtimeMs: 123456,
33+
atime: new Date(123456),
34+
mtime: new Date(123456),
35+
ctime: new Date(123456),
36+
birthtime: new Date(123456),
37+
})
38+
)
39+
})
40+
41+
afterEach(() => {
42+
fs.stat = fsStatBak
43+
})
44+
45+
it(`creates a file node`, async () => {
946
const createNodeId = jest.fn()
1047
createNodeId.mockReturnValue(`uuid-from-gatsby`)
1148
return createFileNode(
@@ -14,4 +51,90 @@ describe(`create-file-node`, () => {
1451
{}
1552
)
1653
})
54+
55+
it(`records the shape of the node`, async () => {
56+
const dname = fs.mkdtempSync(`gatsby-create-file-node-test`).trim()
57+
try {
58+
const fname = path.join(dname, `f`)
59+
console.log(dname, fname)
60+
fs.writeFileSync(fname, `data`)
61+
try {
62+
const createNodeId = jest.fn()
63+
createNodeId.mockReturnValue(`uuid-from-gatsby`)
64+
65+
const node = await createFileNode(fname, createNodeId, {})
66+
67+
// Sanitize all filenames
68+
Object.keys(node).forEach(key => {
69+
if (typeof node[key] === `string`) {
70+
node[key] = node[key].replace(new RegExp(dname, `g`), `<DIR>`)
71+
node[key] = node[key].replace(new RegExp(fname, `g`), `<FILE>`)
72+
}
73+
})
74+
Object.keys(node.internal).forEach(key => {
75+
if (typeof node.internal[key] === `string`) {
76+
node.internal[key] = node.internal[key].replace(
77+
new RegExp(dname, `g`),
78+
`<DIR>`
79+
)
80+
node.internal[key] = node.internal[key].replace(
81+
new RegExp(fname, `g`),
82+
`<FILE>`
83+
)
84+
}
85+
})
86+
87+
// Note: this snapshot should update if the mock above is changed
88+
expect(node).toMatchInlineSnapshot(`
89+
Object {
90+
"absolutePath": "<DIR>/f",
91+
"accessTime": "1970-01-01T00:02:03.456Z",
92+
"atime": "1970-01-01T00:02:03.456Z",
93+
"atimeMs": 123456,
94+
"base": "f",
95+
"birthTime": "1970-01-01T00:02:03.456Z",
96+
"birthtime": "1970-01-01T00:02:03.456Z",
97+
"birthtimeMs": 123456,
98+
"blksize": 123456,
99+
"blocks": 123456,
100+
"changeTime": "1970-01-01T00:02:03.456Z",
101+
"children": Array [],
102+
"ctime": "1970-01-01T00:02:03.456Z",
103+
"ctimeMs": 123456,
104+
"dev": 123456,
105+
"dir": "<DIR>",
106+
"ext": "",
107+
"extension": "",
108+
"id": "uuid-from-gatsby",
109+
"ino": 123456,
110+
"internal": Object {
111+
"contentDigest": "8d777f385d3dfec8815d20f7496026dc",
112+
"description": "File \\"<DIR>/f\\"",
113+
"mediaType": "application/octet-stream",
114+
"type": "File",
115+
},
116+
"mode": 123456,
117+
"modifiedTime": "1970-01-01T00:02:03.456Z",
118+
"mtime": "1970-01-01T00:02:03.456Z",
119+
"mtimeMs": 123456,
120+
"name": "f",
121+
"nlink": 123456,
122+
"parent": null,
123+
"prettySize": "123 kB",
124+
"rdev": 123456,
125+
"relativeDirectory": "<DIR>",
126+
"relativePath": "<DIR>/f",
127+
"root": "",
128+
"size": 123456,
129+
"sourceInstanceName": "__PROGRAMMATIC__",
130+
"uid": 123456,
131+
}
132+
`)
133+
} finally {
134+
fs.unlinkSync(fname)
135+
}
136+
} finally {
137+
fs.rmdirSync(dname)
138+
}
139+
})
17140
})

packages/gatsby-source-filesystem/src/create-file-node.js

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,48 @@ exports.createFileNode = async (
4545
}
4646
}
4747

48-
// Stringify date objects.
49-
return JSON.parse(
50-
JSON.stringify({
51-
// Don't actually make the File id the absolute path as otherwise
52-
// people will use the id for that and ids shouldn't be treated as
53-
// useful information.
54-
id: createNodeId(pathToFile),
55-
children: [],
56-
parent: null,
57-
internal,
58-
sourceInstanceName: pluginOptions.name || `__PROGRAMMATIC__`,
59-
absolutePath: slashedFile.absolutePath,
60-
relativePath: slash(
61-
path.relative(
62-
pluginOptions.path || process.cwd(),
63-
slashedFile.absolutePath
64-
)
65-
),
66-
extension: slashedFile.ext.slice(1).toLowerCase(),
67-
size: stats.size,
68-
prettySize: prettyBytes(stats.size),
69-
modifiedTime: stats.mtime,
70-
accessTime: stats.atime,
71-
changeTime: stats.ctime,
72-
birthTime: stats.birthtime,
73-
...slashedFile,
74-
...stats,
75-
})
76-
)
48+
return {
49+
// Don't actually make the File id the absolute path as otherwise
50+
// people will use the id for that and ids shouldn't be treated as
51+
// useful information.
52+
id: createNodeId(pathToFile),
53+
children: [],
54+
parent: null,
55+
internal,
56+
sourceInstanceName: pluginOptions.name || `__PROGRAMMATIC__`,
57+
relativePath: slash(
58+
path.relative(
59+
pluginOptions.path || process.cwd(),
60+
slashedFile.absolutePath
61+
)
62+
),
63+
extension: slashedFile.ext.slice(1).toLowerCase(),
64+
prettySize: prettyBytes(stats.size),
65+
modifiedTime: stats.mtime.toJSON(),
66+
accessTime: stats.atime.toJSON(),
67+
changeTime: stats.ctime.toJSON(),
68+
birthTime: stats.birthtime.toJSON(),
69+
// Note: deprecate splatting the slashedFile object
70+
// Note: the object may contain different properties depending on File or Dir
71+
...slashedFile,
72+
// TODO: deprecate copying the entire object
73+
// Note: not splatting for perf reasons (make sure Date objects are serialized)
74+
dev: stats.dev,
75+
mode: stats.mode,
76+
nlink: stats.nlink,
77+
uid: stats.uid,
78+
rdev: stats.rdev,
79+
blksize: stats.blksize,
80+
ino: stats.ino,
81+
size: stats.size,
82+
blocks: stats.blocks,
83+
atimeMs: stats.atimeMs,
84+
mtimeMs: stats.mtimeMs,
85+
ctimeMs: stats.ctimeMs,
86+
birthtimeMs: stats.birthtimeMs,
87+
atime: stats.atime.toJSON(),
88+
mtime: stats.mtime.toJSON(),
89+
ctime: stats.ctime.toJSON(),
90+
birthtime: stats.birthtime.toJSON(),
91+
}
7792
}

0 commit comments

Comments
 (0)