1
1
import { cpus } from 'os'
2
- import path from 'path'
2
+ import { parse , ParsedPath } from 'path'
3
3
4
4
import { getDeployStore } from '@netlify/blobs'
5
5
import { NetlifyPluginConstants } from '@netlify/build'
6
- import { copy , move , remove } from 'fs-extra/esm'
6
+ import { copy , move , mkdirp } from 'fs-extra/esm'
7
7
import { globby } from 'globby'
8
8
import pLimit from 'p-limit'
9
9
10
10
import { buildCacheValue } from './cache.js'
11
11
import { BUILD_DIR } from './constants.js'
12
+ import { EnhancedNetlifyPluginConstants } from './types.js'
13
+
14
+ type ContentPath = ParsedPath & {
15
+ relative : string
16
+ absolute : string
17
+ publish : string
18
+ }
12
19
13
20
/**
14
21
* Move the Next.js build output from the publish dir to a temp dir
15
22
*/
16
- export const stashBuildOutput = async ( { PUBLISH_DIR } : NetlifyPluginConstants ) => {
17
- await move ( PUBLISH_DIR , `${ BUILD_DIR } /.next` , { overwrite : true } )
23
+ export const stashBuildOutput = async ( { PUBLISH_DIR } : NetlifyPluginConstants ) : Promise < void > => {
24
+ return move ( PUBLISH_DIR , `${ BUILD_DIR } /.next` , { overwrite : true } )
25
+ }
18
26
19
- // remove prerendered content from the standalone build (it's also in the main build dir)
20
- const prerenderedContent = await getPrerenderedContent ( `${ BUILD_DIR } /.next/standalone` , false )
21
- await Promise . all (
22
- prerenderedContent . map ( ( file : string ) => remove ( `${ BUILD_DIR } /.next/standalone/${ file } ` ) ) ,
23
- ) . catch ( ( error ) => console . error ( error ) )
27
+ /**
28
+ * Glob the build output for static page content we can upload to the CDN
29
+ */
30
+ const getStaticContent = async ( cwd : string ) : Promise < ContentPath [ ] > => {
31
+ const content = await globby ( [ `server/pages/**/*.+(html|json)` ] , {
32
+ cwd,
33
+ extglob : true ,
34
+ } )
35
+ return content
36
+ . map ( ( path ) => parsePath ( path , cwd ) )
37
+ . filter ( ( path ) => filterStatic ( path , content , 'keep' ) )
24
38
}
25
39
26
40
/**
27
- * Glob for prerendered content in the build output
41
+ * Glob the build output for prerendered content we can upload to the blob store
28
42
*/
29
- const getPrerenderedContent = async ( cwd : string , get = true ) : Promise < string [ ] > => {
30
- // TODO: test this
31
- return await globby (
32
- get
33
- ? [ `cache/fetch-cache/*` , `server/+(app|pages)/**/*.+(html|body)` ]
34
- : [
35
- `cache/fetch-cache/*` ,
36
- `server/+(app|pages)/**/*.+(html|json|rsc|body|meta)` ,
37
- `!server/**/*.js.nft.{html,json}` ,
38
- ] ,
39
- { cwd, extglob : true } ,
43
+ const getPrerenderedContent = async ( cwd : string ) : Promise < ContentPath [ ] > => {
44
+ const content = await globby (
45
+ [ `cache/fetch-cache/*` , `server/+(app|pages)/**/*.+(html|body|json)` ] ,
46
+ {
47
+ cwd,
48
+ extglob : true ,
49
+ } ,
40
50
)
51
+ return content
52
+ . map ( ( path ) => parsePath ( path , cwd ) )
53
+ . filter ( ( path ) => filterStatic ( path , content , 'omit' ) )
41
54
}
42
55
43
56
/**
44
- * Upload prerendered content from the main build dir to the blob store
57
+ * Glob the build output for JS content we can bundle with the server handler
58
+ */
59
+ export const getServerContent = async ( cwd : string ) : Promise < ContentPath [ ] > => {
60
+ const content = await globby ( [ `**` , `!server/+(app|pages)/**/*.+(html|body|json|rsc|meta)` ] , {
61
+ cwd,
62
+ extglob : true ,
63
+ } )
64
+ return content . map ( ( path ) => parsePath ( path , cwd ) )
65
+ }
66
+
67
+ /**
68
+ * Upload prerendered content to the blob store and remove it from the bundle
45
69
*/
46
70
export const storePrerenderedContent = async ( {
47
71
NETLIFY_API_TOKEN ,
48
72
NETLIFY_API_HOST ,
49
73
SITE_ID ,
50
- } : NetlifyPluginConstants & { NETLIFY_API_TOKEN : string ; NETLIFY_API_HOST : string } ) => {
74
+ } : EnhancedNetlifyPluginConstants ) : Promise < void [ ] > => {
51
75
if ( ! process . env . DEPLOY_ID ) {
52
76
// TODO: maybe change to logging
53
77
throw new Error (
@@ -61,28 +85,52 @@ export const storePrerenderedContent = async ({
61
85
token : NETLIFY_API_TOKEN ,
62
86
apiURL : `https://${ NETLIFY_API_HOST } ` ,
63
87
} )
64
-
65
- // todo: Check out setFiles within Blobs.js to see how to upload files to blob storage
66
88
const limit = pLimit ( Math . max ( 2 , cpus ( ) . length ) )
67
89
68
- const prerenderedContent = await getPrerenderedContent ( `${ BUILD_DIR } /.next` )
90
+ const content = await getPrerenderedContent ( `${ BUILD_DIR } /.next/standalone /.next` )
69
91
return await Promise . all (
70
- prerenderedContent . map ( async ( rawPath : string ) => {
71
- // TODO: test this with files that have a double extension
72
- const ext = path . extname ( rawPath )
73
- const key = rawPath . replace ( ext , '' )
74
- const value = await buildCacheValue ( key , ext )
92
+ content . map ( ( path : ContentPath ) => {
93
+ const { dir, name, ext } = path
94
+ const key = `${ dir } /${ name } `
95
+ const value = buildCacheValue ( key , ext )
75
96
return limit ( ( ) => blob . setJSON ( key , value ) )
76
97
} ) ,
77
98
)
78
99
}
79
100
80
101
/**
81
- * Move static assets to the publish dir so they are uploaded to the CDN
102
+ * Move static content to the publish dir so it is uploaded to the CDN
82
103
*/
83
- export const publishStaticAssets = ( { PUBLISH_DIR } : NetlifyPluginConstants ) => {
84
- return Promise . all ( [
104
+ export const publishStaticContent = async ( {
105
+ PUBLISH_DIR ,
106
+ } : NetlifyPluginConstants ) : Promise < void [ ] > => {
107
+ const content = await getStaticContent ( `${ BUILD_DIR } /.next/standalone/.next` )
108
+ return await Promise . all ( [
109
+ mkdirp ( PUBLISH_DIR ) ,
85
110
copy ( 'public' , PUBLISH_DIR ) ,
86
111
copy ( `${ BUILD_DIR } /.next/static/` , `${ PUBLISH_DIR } /_next/static` ) ,
112
+ ...content . map ( ( path : ContentPath ) => copy ( path . absolute , `${ PUBLISH_DIR } /${ path . publish } ` ) ) ,
87
113
] )
88
114
}
115
+
116
+ /**
117
+ * Keep or remove static content based on whether it has a corresponding JSON file
118
+ */
119
+ const filterStatic = (
120
+ { dir, name, ext } : ContentPath ,
121
+ content : string [ ] ,
122
+ type : 'keep' | 'omit' ,
123
+ ) : boolean =>
124
+ type === 'keep'
125
+ ? dir . startsWith ( 'server/pages' ) && ! content . includes ( `${ dir } /${ name } .json` )
126
+ : ext !== '.json' &&
127
+ ( ! dir . startsWith ( 'server/pages' ) || content . includes ( `${ dir } /${ name } .json` ) )
128
+ /**
129
+ * Parse a file path into an object with file path variants
130
+ */
131
+ const parsePath = ( path : string , cwd : string ) : ContentPath => ( {
132
+ ...parse ( path ) ,
133
+ relative : path ,
134
+ absolute : `${ cwd } /${ path } ` ,
135
+ publish : path . replace ( / ^ s e r v e r \/ ( a p p | p a g e s ) \/ / , '' ) ,
136
+ } )
0 commit comments