-
-
Notifications
You must be signed in to change notification settings - Fork 37
/
build.ts
201 lines (178 loc) Β· 6.48 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import { InputNextJSArgs, InputNextJSMetadata } from './types'
import { FabBuildStep } from '@fab/core'
import path from 'path'
import { InvalidConfigError, _log } from '@fab/cli'
import { preflightChecks } from './preflightChecks'
import globby from 'globby'
import fs from 'fs-extra'
import generateRenderer from './generateRenderer'
import webpack from 'webpack'
const log = _log(`@fab/input-nextjs`)
// @ts-ignore
import md5dir from 'md5-dir/promise'
const RENDERER = `generated-nextjs-renderers`
const WEBPACKED = `webpacked-nextjs-renderers`
export const build: FabBuildStep<InputNextJSArgs, InputNextJSMetadata> = async (
args,
proto_fab,
config_path,
skip_cache = false
) => {
// const { dir } = args
if (proto_fab.files!.size > 0) {
throw new InvalidConfigError(
`@fab/input-nextjs must be the first 'input' plugin in the chain.`
)
}
const config_dir = path.dirname(path.resolve(config_path))
const { next_dir_name, next_dir, asset_prefix } = await preflightChecks(config_dir)
// console.log({ next_dir_name, next_dir, asset_prefix })
log(`Reading files from π${next_dir}π`)
const pages_dir = path.join(next_dir, 'serverless', 'pages')
const static_dir = path.join(next_dir, 'static')
const public_dir = path.resolve(next_dir, '../public')
const pages_dir_hash = await md5dir(pages_dir)
// console.log({ pages_dir, pages_dir_hash })
log(`Finding all static HTML pages...`)
const html_files = await globby([`**/*.html`, `!_*`], { cwd: pages_dir })
await Promise.all(
html_files.map(async (filename) => {
proto_fab.files!.set(
'/' + filename,
await fs.readFile(path.join(pages_dir, filename))
)
})
)
log.tick(`Found π${html_files.length} static html pagesπ.`)
const cache_dir = path.join(config_dir, '.fab', '.cache')
const renderer_path = path.join(
cache_dir,
`${RENDERER}.${pages_dir_hash.slice(0, 7)}.js`
)
const render_code_src = await getRenderCode(
renderer_path,
pages_dir,
cache_dir,
skip_cache
)
// todo: hash & cache render_code
// Webpack this file to inject all the required shims, before rolling it up,
// since Webpack is way better at that job. Potentially this logic should be
// moved out into a separate module or into the core compiler.
const webpacked_output = path.join(cache_dir, `${WEBPACKED}.js`)
const shims_dir = path.resolve(__dirname, '../shims')
const mock_express_response_path = path.join(shims_dir, 'mock-express-response')
const entry_point = `
const renderers = require(${JSON.stringify(renderer_path)});
const MockExpressResponse = require(${JSON.stringify(mock_express_response_path)});
const MockReq = require('mock-express-request');
module.exports = { renderers, MockExpressResponse, MockReq }
`
const entry_file = path.join(cache_dir, 'entry-point.js')
await fs.writeFile(entry_file, entry_point)
await new Promise((resolve, reject) =>
webpack(
{
stats: 'verbose',
mode: 'production',
target: 'webworker',
entry: entry_file,
optimization: {
minimize: false,
},
output: {
path: path.dirname(webpacked_output),
filename: path.basename(webpacked_output),
library: 'server',
libraryTarget: 'commonjs2',
},
resolve: {
alias: {
fs: require.resolve('memfs'),
path: path.join(shims_dir, 'path-with-posix'),
'@ampproject/toolbox-optimizer': path.join(shims_dir, 'empty-object'),
critters: path.join(shims_dir, 'empty-object'),
http: path.join(shims_dir, 'http'),
net: path.join(shims_dir, 'net'),
https: path.join(shims_dir, 'empty-object'),
},
},
node: {
global: false,
},
plugins: [
/* Cloudflare Workers will explode if it even _sees_ `eval` in a file,
* even if it's never called. Replacing it with this will bypasses that.
* (It'll still explode if it's called, nothing we can do about that.) */
new webpack.DefinePlugin({
eval: 'HERE_NO_EVAL',
}),
],
},
(err, stats) => {
if (err || stats.hasErrors()) {
console.log('Build failed.')
console.log(err)
console.log(stats && stats.toJson().errors.toString())
reject()
}
resolve()
}
)
)
const webpacked_src = await fs.readFile(webpacked_output, 'utf8')
proto_fab.hypotheticals[`${RENDERER}.js`] = webpacked_src
log(`Finding all static assets`)
const asset_files = await globby([`**/*`], { cwd: static_dir })
if (asset_files.length > 0) {
for (const asset_file of asset_files) {
proto_fab.files.set(
`/_next/static/${asset_file}`,
await fs.readFile(path.resolve(static_dir, asset_file))
)
}
}
log.tick(`Found ${asset_files.length} assets.`)
log(`Finding all public files`)
const public_files = await globby([`**/*`], { cwd: public_dir })
if (public_files.length > 0) {
for (const public_file of public_files) {
proto_fab.files.set(
`/${public_file}`,
await fs.readFile(path.resolve(public_dir, public_file))
)
log.tick(`π€${public_file}π€`, 2)
}
}
}
async function getRenderCode(
renderer_path: string,
pages_dir: string,
cache_dir: string,
skip_cache: boolean
) {
/* Renderer path is fingerprinted with hash of the contents, so if it exists,
* we can reuse it unless we want to --skip-cache*/
if (await fs.pathExists(renderer_path)) {
const relative_path = path.relative(process.cwd(), renderer_path)
if (skip_cache) {
log.note(`Skipping cached renderer, regenerating π${relative_path}π`)
} else {
log(`Reusing NextJS renderer cache π${relative_path}π`)
return await fs.readFile(renderer_path, 'utf8')
}
}
log(`Finding all dynamic NextJS entry points`)
const js_renderers = await globby([`**/*.js`], { cwd: pages_dir })
const render_code = await generateRenderer(js_renderers, pages_dir)
log(`Found π${js_renderers.length} dynamic pagesπ.`)
// Write out the cache while cleaning out any old caches
await fs.ensureDir(cache_dir)
const previous_caches = await globby([`${RENDERER}.*.js`], { cwd: cache_dir })
await Promise.all(
previous_caches.map((cache) => fs.remove(path.join(cache_dir, cache)))
)
await fs.writeFile(renderer_path, render_code)
log.tick(`Wrote π${renderer_path}π`)
return render_code
}