@@ -3,38 +3,68 @@ import fs from 'fs/promises'
33
44import Page from './page.js'
55
6- export default async function createTree ( originalPath , rootPath ) {
6+ export default async function createTree ( originalPath , rootPath , previousTree ) {
77 const basePath = rootPath || originalPath
88
99 // On recursive runs, this is processing page.children items in `/<link>` format.
1010 // If the path exists as is, assume this is a directory with a child index.md.
1111 // Otherwise, assume it's a child .md file and add `.md` to the path.
1212 let filepath
13+ let mtime
14+ // This kills two birds with one stone. We (attempt to) read it as a file,
15+ // to find out if it's a directory or a file and whence we know that
16+ // we also collect it's modification time.
1317 try {
14- await fs . access ( originalPath )
15- filepath = `${ originalPath } /index.md`
16- } catch {
1718 filepath = `${ originalPath } .md`
19+ mtime = await getMtime ( filepath )
20+ } catch ( error ) {
21+ if ( error . code !== 'ENOENT' ) {
22+ throw error
23+ }
24+ filepath = `${ originalPath } /index.md`
25+ // Note, if this throws, that's quite fine. It usually means that
26+ // there's a `index.md` whose `children:` entry lists something that
27+ // doesn't exist on disk. So the writer who tries to preview the
28+ // page will see the error and it's hopefully clear what's actually
29+ // wrong.
30+ try {
31+ mtime = await getMtime ( filepath )
32+ } catch ( error ) {
33+ if ( error . code === 'ENOENT' && filepath . split ( path . sep ) . includes ( 'early-access' ) ) {
34+ // Do not throw an error if Early Access is not available.
35+ console . warn (
36+ `${ filepath } could not be turned into a Page, but is ignored because it's early-access`
37+ )
38+ return
39+ }
40+ throw error
41+ }
1842 }
1943
2044 const relativePath = filepath . replace ( `${ basePath } /` , '' )
2145
22- // Initialize the Page! This is where the file reads happen.
23- const page = await Page . init ( {
24- basePath,
25- relativePath,
26- languageCode : 'en' ,
27- } )
46+ // Reading in a file from disk is slow and best avoided if we can be
47+ // certain it isn't necessary. If the previous tree is known and that
48+ // tree's page node's `mtime` hasn't changed, we can use that instead.
49+ let page
50+ if ( previousTree && previousTree . page . mtime === mtime ) {
51+ // A save! We can use the same exact Page instance from the previous
52+ // tree because the assumption is that since the `.md` file it was
53+ // created from hasn't changed (on disk) the instance object wouldn't
54+ // change.
55+ page = previousTree . page
56+ } else {
57+ // Either the previous tree doesn't exist yet or the modification time
58+ // of the file on disk has changed.
59+ page = await Page . init ( {
60+ basePath,
61+ relativePath,
62+ languageCode : 'en' ,
63+ mtime,
64+ } )
65+ }
2866
2967 if ( ! page ) {
30- // Do not throw an error if Early Access is not available.
31- if ( relativePath . startsWith ( 'early-access' ) ) {
32- console . warn (
33- `${ relativePath } could not be turned into a Page, but is ignore because it's early-access`
34- )
35- return
36- }
37-
3868 throw Error ( `Cannot initialize page for ${ filepath } ` )
3969 }
4070
@@ -49,7 +79,12 @@ export default async function createTree(originalPath, rootPath) {
4979 item . childPages = (
5080 await Promise . all (
5181 item . page . children . map (
52- async ( child ) => await createTree ( path . posix . join ( originalPath , child ) , basePath )
82+ async ( child , i ) =>
83+ await createTree (
84+ path . posix . join ( originalPath , child ) ,
85+ basePath ,
86+ previousTree && previousTree . childPages [ i ]
87+ )
5388 )
5489 )
5590 ) . filter ( Boolean )
@@ -58,6 +93,22 @@ export default async function createTree(originalPath, rootPath) {
5893 return item
5994}
6095
96+ async function getMtime ( filePath ) {
97+ // Use mtimeMs, which is a regular floating point number, instead of the
98+ // mtime which is a Date based on that same number.
99+ // Otherwise, if we use the Date instances, we have to compare
100+ // them using `oneDate.getTime() === anotherDate.getTime()`.
101+ const { mtimeMs } = await fs . stat ( filePath )
102+ // The `mtimeMs` is a number like `1669827766942.7954`
103+ // From the docs:
104+ // "The timestamp indicating the last time this file was modified expressed
105+ // in nanoseconds since the POSIX Epoch."
106+ // But the number isn't actually all that important. We just need it to
107+ // later be able to know if it changed. We round it to the nearest
108+ // millisecond.
109+ return Math . round ( mtimeMs )
110+ }
111+
61112function assertUniqueChildren ( page ) {
62113 if ( page . children . length !== new Set ( page . children ) . size ) {
63114 const count = { }
0 commit comments