@ossy/app 0.15.13 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -25
- package/cli/build.js +441 -435
- package/cli/dev.js +116 -143
- package/cli/server.js +25 -12
- package/package.json +13 -14
- package/scripts/ensure-build-stubs.mjs +1 -2
- package/src/index.js +6 -1
- package/cli/Api.js +0 -3
- package/cli/client.js +0 -8
- package/cli/default-app.jsx +0 -11
- package/cli/page-shell-default.jsx +0 -9
package/cli/build.js
CHANGED
|
@@ -5,28 +5,95 @@ import { rollup } from 'rollup';
|
|
|
5
5
|
import babel from '@rollup/plugin-babel';
|
|
6
6
|
import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
|
|
7
7
|
import resolveCommonJsDependencies from '@rollup/plugin-commonjs'
|
|
8
|
-
import
|
|
9
|
-
import minifyJS from '@rollup/plugin-terser'
|
|
8
|
+
import { minify as minifyWithTerser } from 'terser'
|
|
10
9
|
// import typescript from '@rollup/plugin-typescript'
|
|
11
|
-
import preserveDirectives from "rollup-plugin-preserve-directives"
|
|
12
10
|
import json from "@rollup/plugin-json"
|
|
11
|
+
import nodeExternals from 'rollup-plugin-node-externals'
|
|
13
12
|
import copy from 'rollup-plugin-copy';
|
|
14
13
|
import replace from '@rollup/plugin-replace';
|
|
15
14
|
import arg from 'arg'
|
|
16
15
|
import { ensureBuildStubs } from '../scripts/ensure-build-stubs.mjs'
|
|
17
|
-
import {
|
|
16
|
+
import { createRequire } from 'node:module'
|
|
18
17
|
// import inject from '@rollup/plugin-inject'
|
|
19
18
|
|
|
20
19
|
const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
21
20
|
const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
22
21
|
const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
|
|
22
|
+
const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.js$/
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const NODE_MODULES_SEG = `${path.sep}node_modules${path.sep}`
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Keep third-party code out of the Rollup graph for the server build.
|
|
28
|
+
* `preserveModules` + `@rollup/plugin-commonjs` otherwise emits `_virtual/*` chunks that import
|
|
29
|
+
* `{ __require }` from real `node_modules` paths, which Node ESM cannot execute.
|
|
30
|
+
*
|
|
31
|
+
* Rules:
|
|
32
|
+
* - Resolved absolute path under `node_modules` → external (any npm dep, including transitive).
|
|
33
|
+
* - Unresolved bare specifier → external so Node loads it (covers `react`, `lodash`, customers’
|
|
34
|
+
* `@acme/*`, etc.). Exception: `@ossy/*` is bundled (framework packages; monorepo paths or
|
|
35
|
+
* published tarballs — avoids relying on Node resolving those bare ids from `build/server.js`).
|
|
36
|
+
*
|
|
37
|
+
* React’s main entry is still CJS; there is no separate official ESM prod file for `import 'react'`.
|
|
38
|
+
* Leaving it external is correct: Node applies default export interop when Rollup does not rewrite it.
|
|
39
|
+
*/
|
|
40
|
+
export function ossyServerExternal (id, _importer, _isResolved) {
|
|
41
|
+
if (!id || id[0] === '\0') return false
|
|
42
|
+
if (path.isAbsolute(id)) {
|
|
43
|
+
return id.includes(NODE_MODULES_SEG)
|
|
44
|
+
}
|
|
45
|
+
if (id.startsWith('.')) return false
|
|
46
|
+
if (id.startsWith('@ossy/')) return false
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Written next to `*.resource.js` under `src/resource-templates/` when that dir exists. */
|
|
51
|
+
export const OSSY_RESOURCE_TEMPLATES_OUT = '.ossy-system-templates.generated.js'
|
|
52
|
+
|
|
53
|
+
/** Rollup output paths for JS served to the browser (see `entryFileNames` / `chunkFileNames`). */
|
|
54
|
+
const BROWSER_STATIC_PREFIX = 'public/static/'
|
|
55
|
+
|
|
56
|
+
function minifyBrowserStaticChunks () {
|
|
57
|
+
return {
|
|
58
|
+
name: 'minify-browser-static-chunks',
|
|
59
|
+
async renderChunk (code, chunk, outputOptions) {
|
|
60
|
+
const fileName = chunk.fileName
|
|
61
|
+
if (!fileName || !fileName.startsWith(BROWSER_STATIC_PREFIX)) {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
const useSourceMap =
|
|
65
|
+
outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
66
|
+
const fmt = outputOptions.format
|
|
67
|
+
const result = await minifyWithTerser(code, {
|
|
68
|
+
sourceMap: useSourceMap,
|
|
69
|
+
module: fmt === 'es' || fmt === 'esm',
|
|
70
|
+
})
|
|
71
|
+
const out = result.code ?? code
|
|
72
|
+
if (useSourceMap && result.map) {
|
|
73
|
+
const map =
|
|
74
|
+
typeof result.map === 'string' ? JSON.parse(result.map) : result.map
|
|
75
|
+
return { code: out, map }
|
|
76
|
+
}
|
|
77
|
+
return out
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Subfolder under `build/` for generated pages/api/task entry stubs. */
|
|
25
83
|
export const OSSY_GEN_DIRNAME = '.ossy'
|
|
26
84
|
export const OSSY_GEN_PAGES_BASENAME = 'pages.generated.jsx'
|
|
27
85
|
export const OSSY_GEN_API_BASENAME = 'api.generated.js'
|
|
28
86
|
export const OSSY_GEN_TASKS_BASENAME = 'tasks.generated.js'
|
|
29
87
|
|
|
88
|
+
/** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
|
|
89
|
+
const HYDRATE_STUB_PREFIX = 'hydrate-'
|
|
90
|
+
const HYDRATE_STUB_SUFFIX = '.jsx'
|
|
91
|
+
|
|
92
|
+
/** Rollup input chunk name for a page id (safe identifier; id may contain `-`). */
|
|
93
|
+
export function hydrateEntryName (pageId) {
|
|
94
|
+
return `hydrate__${pageId}`
|
|
95
|
+
}
|
|
96
|
+
|
|
30
97
|
export function ossyGeneratedDir (buildPath) {
|
|
31
98
|
return path.join(buildPath, OSSY_GEN_DIRNAME)
|
|
32
99
|
}
|
|
@@ -37,33 +104,142 @@ export function ensureOssyGeneratedDir (buildPath) {
|
|
|
37
104
|
return dir
|
|
38
105
|
}
|
|
39
106
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
107
|
+
function relToGeneratedImport (generatedAbs, targetAbs) {
|
|
108
|
+
return path.relative(path.dirname(generatedAbs), targetAbs).replace(/\\/g, '/')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Deletes the entire build output dir, then recreates `build/.ossy` (generated stubs are written next). */
|
|
112
|
+
export function resetOssyBuildDir (buildPath) {
|
|
113
|
+
fs.rmSync(buildPath, { recursive: true, force: true })
|
|
114
|
+
ensureOssyGeneratedDir(buildPath)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resourceTemplatesDir (cwd = process.cwd()) {
|
|
118
|
+
return path.join(cwd, 'src', 'resource-templates')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function discoverResourceTemplateFiles (templatesDir) {
|
|
122
|
+
if (!fs.existsSync(templatesDir) || !fs.statSync(templatesDir).isDirectory()) {
|
|
123
|
+
return []
|
|
47
124
|
}
|
|
48
|
-
return
|
|
125
|
+
return fs
|
|
126
|
+
.readdirSync(templatesDir)
|
|
127
|
+
.filter((n) => RESOURCE_TEMPLATE_FILE_PATTERN.test(n))
|
|
128
|
+
.map((n) => path.join(templatesDir, n))
|
|
129
|
+
.sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
|
|
49
130
|
}
|
|
50
131
|
|
|
51
|
-
function
|
|
52
|
-
|
|
132
|
+
export function generateResourceTemplatesBarrelSource ({ outputAbs, templateFilesAbs }) {
|
|
133
|
+
const lines = [
|
|
134
|
+
'// Generated by @ossy/app — do not edit',
|
|
135
|
+
'',
|
|
136
|
+
]
|
|
137
|
+
templateFilesAbs.forEach((f, i) => {
|
|
138
|
+
const rel = relToGeneratedImport(outputAbs, f)
|
|
139
|
+
lines.push(`import _resource${i} from './${rel}'`)
|
|
140
|
+
})
|
|
141
|
+
lines.push('')
|
|
142
|
+
lines.push(
|
|
143
|
+
'/** Built-in resource templates merged into every workspace (with imported templates) in the API. */'
|
|
144
|
+
)
|
|
145
|
+
lines.push('export const SystemTemplates = [')
|
|
146
|
+
templateFilesAbs.forEach((_, i) => {
|
|
147
|
+
lines.push(` _resource${i},`)
|
|
148
|
+
})
|
|
149
|
+
lines.push(']')
|
|
150
|
+
lines.push('')
|
|
151
|
+
return lines.join('\n')
|
|
53
152
|
}
|
|
54
153
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
154
|
+
/**
|
|
155
|
+
* If `src/resource-templates/` exists, writes `.ossy-system-templates.generated.js` there.
|
|
156
|
+
* No-op when the directory is missing (e.g. website packages). Runs during `build` / `dev`.
|
|
157
|
+
*/
|
|
158
|
+
export function writeResourceTemplatesBarrelIfPresent ({ cwd = process.cwd(), log = true } = {}) {
|
|
159
|
+
const dir = resourceTemplatesDir(cwd)
|
|
160
|
+
if (!fs.existsSync(dir)) {
|
|
161
|
+
return { wrote: false, count: 0, path: null }
|
|
162
|
+
}
|
|
163
|
+
const files = discoverResourceTemplateFiles(dir)
|
|
164
|
+
const outAbs = path.join(dir, OSSY_RESOURCE_TEMPLATES_OUT)
|
|
165
|
+
fs.writeFileSync(outAbs, generateResourceTemplatesBarrelSource({ outputAbs: outAbs, templateFilesAbs: files }), 'utf8')
|
|
166
|
+
if (log) {
|
|
167
|
+
console.log(
|
|
168
|
+
`[@ossy/app][resource-templates] merged ${files.length} template(s) → ${path.relative(cwd, outAbs)}`
|
|
169
|
+
)
|
|
66
170
|
}
|
|
171
|
+
return { wrote: true, count: files.length, path: outAbs }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Shared Rollup plugins (virtual path replaces, resolve, JSX).
|
|
176
|
+
* Server builds pass `nodeExternals: true` so `dependencies` stay importable from Node at runtime.
|
|
177
|
+
*/
|
|
178
|
+
export function createOssyRollupPlugins ({
|
|
179
|
+
pagesGeneratedPath,
|
|
180
|
+
apiSourcePath,
|
|
181
|
+
middlewareSourcePath,
|
|
182
|
+
configSourcePath,
|
|
183
|
+
nodeEnv,
|
|
184
|
+
nodeExternals: useNodeExternals = false,
|
|
185
|
+
preferBuiltins = true,
|
|
186
|
+
copyPublicFrom,
|
|
187
|
+
buildPath,
|
|
188
|
+
}) {
|
|
189
|
+
const plugins = [
|
|
190
|
+
replace({
|
|
191
|
+
preventAssignment: true,
|
|
192
|
+
delimiters: ['%%', '%%'],
|
|
193
|
+
'@ossy/pages/source-file': pagesGeneratedPath,
|
|
194
|
+
}),
|
|
195
|
+
replace({
|
|
196
|
+
preventAssignment: true,
|
|
197
|
+
delimiters: ['%%', '%%'],
|
|
198
|
+
'@ossy/api/source-file': apiSourcePath,
|
|
199
|
+
}),
|
|
200
|
+
replace({
|
|
201
|
+
preventAssignment: true,
|
|
202
|
+
delimiters: ['%%', '%%'],
|
|
203
|
+
'@ossy/middleware/source-file': middlewareSourcePath,
|
|
204
|
+
}),
|
|
205
|
+
replace({
|
|
206
|
+
preventAssignment: true,
|
|
207
|
+
delimiters: ['%%', '%%'],
|
|
208
|
+
'@ossy/config/source-file': configSourcePath,
|
|
209
|
+
}),
|
|
210
|
+
replace({
|
|
211
|
+
preventAssignment: true,
|
|
212
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
213
|
+
}),
|
|
214
|
+
json(),
|
|
215
|
+
]
|
|
216
|
+
if (useNodeExternals) {
|
|
217
|
+
plugins.push(
|
|
218
|
+
nodeExternals({
|
|
219
|
+
deps: true,
|
|
220
|
+
devDeps: false,
|
|
221
|
+
peerDeps: true,
|
|
222
|
+
packagePath: path.join(process.cwd(), 'package.json'),
|
|
223
|
+
})
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
plugins.push(
|
|
227
|
+
resolveCommonJsDependencies(),
|
|
228
|
+
resolveDependencies({ preferBuiltins }),
|
|
229
|
+
babel({
|
|
230
|
+
babelHelpers: 'bundled',
|
|
231
|
+
extensions: ['.jsx', '.tsx'],
|
|
232
|
+
presets: ['@babel/preset-react'],
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
236
|
+
plugins.push(
|
|
237
|
+
copy({
|
|
238
|
+
targets: [{ src: `${copyPublicFrom}/**/*`, dest: path.join(buildPath, 'public') }],
|
|
239
|
+
})
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
return plugins
|
|
67
243
|
}
|
|
68
244
|
|
|
69
245
|
export function discoverApiFiles(srcDir) {
|
|
@@ -103,19 +279,22 @@ export function discoverTaskFiles(srcDir) {
|
|
|
103
279
|
}
|
|
104
280
|
|
|
105
281
|
/**
|
|
106
|
-
* Merges
|
|
107
|
-
* for the Ossy API router ({ id, path, handle }).
|
|
282
|
+
* Merges every `*.api.js` (and `.api.mjs` / `.api.cjs`) under `src/` into one default export array
|
|
283
|
+
* for the Ossy API router ({ id, path, handle }). With no API files, emits `export default []`.
|
|
108
284
|
*/
|
|
109
|
-
export function generateApiModule ({ generatedPath, apiFiles
|
|
285
|
+
export function generateApiModule ({ generatedPath, apiFiles }) {
|
|
286
|
+
if (apiFiles.length === 0) {
|
|
287
|
+
return [
|
|
288
|
+
'// Generated by @ossy/app — do not edit',
|
|
289
|
+
'',
|
|
290
|
+
'export default []',
|
|
291
|
+
'',
|
|
292
|
+
].join('\n')
|
|
293
|
+
}
|
|
110
294
|
const lines = [
|
|
111
295
|
'// Generated by @ossy/app — do not edit',
|
|
112
296
|
'',
|
|
113
297
|
]
|
|
114
|
-
const hasLegacy = legacyPath && fs.existsSync(legacyPath)
|
|
115
|
-
if (hasLegacy) {
|
|
116
|
-
const rel = relToGeneratedImport(generatedPath, legacyPath)
|
|
117
|
-
lines.push(`import _legacyApi from './${rel}'`)
|
|
118
|
-
}
|
|
119
298
|
apiFiles.forEach((f, i) => {
|
|
120
299
|
const rel = relToGeneratedImport(generatedPath, f)
|
|
121
300
|
lines.push(`import * as _api${i} from './${rel}'`)
|
|
@@ -130,13 +309,7 @@ export function generateApiModule ({ generatedPath, apiFiles, legacyPath }) {
|
|
|
130
309
|
'',
|
|
131
310
|
'export default [',
|
|
132
311
|
)
|
|
133
|
-
const parts =
|
|
134
|
-
if (hasLegacy) {
|
|
135
|
-
parts.push(' ..._normalizeApiExport({ default: _legacyApi }),')
|
|
136
|
-
}
|
|
137
|
-
apiFiles.forEach((_, i) => {
|
|
138
|
-
parts.push(` ..._normalizeApiExport(_api${i}),`)
|
|
139
|
-
})
|
|
312
|
+
const parts = apiFiles.map((_, i) => ` ..._normalizeApiExport(_api${i}),`)
|
|
140
313
|
lines.push(parts.join('\n'))
|
|
141
314
|
lines.push(']')
|
|
142
315
|
lines.push('')
|
|
@@ -144,60 +317,29 @@ export function generateApiModule ({ generatedPath, apiFiles, legacyPath }) {
|
|
|
144
317
|
}
|
|
145
318
|
|
|
146
319
|
/**
|
|
147
|
-
*
|
|
320
|
+
* Writes `build/.ossy/api.generated.js` and returns its path for `@ossy/api/source-file`.
|
|
321
|
+
* Always uses the generated file so the Rollup replace target stays stable.
|
|
148
322
|
*/
|
|
149
|
-
export function resolveApiSource ({
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
const defaultStub = path.resolve(scriptDir, 'api.js')
|
|
153
|
-
const bp = path.resolve(buildPath ?? path.join(cwd, 'build'))
|
|
154
|
-
ensureOssyGeneratedDir(bp)
|
|
155
|
-
const generatedPath = path.join(ossyGeneratedDir(bp), OSSY_GEN_API_BASENAME)
|
|
156
|
-
|
|
157
|
-
if (explicitApiSource) {
|
|
158
|
-
const p = path.isAbsolute(explicitApiSource)
|
|
159
|
-
? explicitApiSource
|
|
160
|
-
: path.resolve(cwd, explicitApiSource)
|
|
161
|
-
if (fs.existsSync(p)) {
|
|
162
|
-
return { apiSourcePath: p, apiOverviewFiles: [p], usedGenerated: false }
|
|
163
|
-
}
|
|
164
|
-
return { apiSourcePath: defaultStub, apiOverviewFiles: [], usedGenerated: false }
|
|
165
|
-
}
|
|
166
|
-
|
|
323
|
+
export function resolveApiSource ({ srcDir, buildPath }) {
|
|
324
|
+
ensureOssyGeneratedDir(buildPath)
|
|
325
|
+
const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
|
|
167
326
|
const apiFiles = discoverApiFiles(srcDir)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
generatedPath,
|
|
174
|
-
generateApiModule({
|
|
175
|
-
generatedPath,
|
|
176
|
-
apiFiles,
|
|
177
|
-
legacyPath: hasLegacy ? legacyPath : null,
|
|
178
|
-
})
|
|
179
|
-
)
|
|
180
|
-
const overview = [...(hasLegacy ? [legacyPath] : []), ...apiFiles]
|
|
181
|
-
return { apiSourcePath: generatedPath, apiOverviewFiles: overview, usedGenerated: true }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { apiSourcePath: defaultStub, apiOverviewFiles: [], usedGenerated: false }
|
|
327
|
+
fs.writeFileSync(
|
|
328
|
+
generatedPath,
|
|
329
|
+
generateApiModule({ generatedPath, apiFiles })
|
|
330
|
+
)
|
|
331
|
+
return { apiSourcePath: generatedPath, apiOverviewFiles: apiFiles }
|
|
185
332
|
}
|
|
186
333
|
|
|
187
334
|
/**
|
|
188
335
|
* Merges `src/tasks.js` (optional) and every `*.task.js` under `src/` into one default export array
|
|
189
336
|
* of job handlers `{ type, handler }` for the Ossy worker.
|
|
190
337
|
*/
|
|
191
|
-
export function generateTaskModule ({ generatedPath, taskFiles
|
|
338
|
+
export function generateTaskModule ({ generatedPath, taskFiles }) {
|
|
192
339
|
const lines = [
|
|
193
340
|
'// Generated by @ossy/app — do not edit',
|
|
194
341
|
'',
|
|
195
342
|
]
|
|
196
|
-
const hasLegacy = legacyPath && fs.existsSync(legacyPath)
|
|
197
|
-
if (hasLegacy) {
|
|
198
|
-
const rel = relToGeneratedImport(generatedPath, legacyPath)
|
|
199
|
-
lines.push(`import _legacyTasks from './${rel}'`)
|
|
200
|
-
}
|
|
201
343
|
taskFiles.forEach((f, i) => {
|
|
202
344
|
const rel = relToGeneratedImport(generatedPath, f)
|
|
203
345
|
lines.push(`import * as _task${i} from './${rel}'`)
|
|
@@ -213,9 +355,6 @@ export function generateTaskModule ({ generatedPath, taskFiles, legacyPath }) {
|
|
|
213
355
|
'export default [',
|
|
214
356
|
)
|
|
215
357
|
const parts = []
|
|
216
|
-
if (hasLegacy) {
|
|
217
|
-
parts.push(' ..._normalizeTaskExport({ default: _legacyTasks }),')
|
|
218
|
-
}
|
|
219
358
|
taskFiles.forEach((_, i) => {
|
|
220
359
|
parts.push(` ..._normalizeTaskExport(_task${i}),`)
|
|
221
360
|
})
|
|
@@ -228,45 +367,28 @@ export function generateTaskModule ({ generatedPath, taskFiles, legacyPath }) {
|
|
|
228
367
|
/**
|
|
229
368
|
* Resolves the Rollup entry for @ossy/tasks/source-file and which files to list in the worker build overview.
|
|
230
369
|
*/
|
|
231
|
-
export function resolveTaskSource ({
|
|
232
|
-
const srcDir = path.resolve(cwd, pagesOpt)
|
|
233
|
-
const legacyPath = path.resolve(cwd, 'src/tasks.js')
|
|
370
|
+
export function resolveTaskSource ({ srcDir, scriptDir, buildPath }) {
|
|
234
371
|
const defaultStub = path.resolve(scriptDir, 'tasks.js')
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const generatedPath = path.join(ossyGeneratedDir(bp), OSSY_GEN_TASKS_BASENAME)
|
|
238
|
-
|
|
239
|
-
if (explicitTaskSource) {
|
|
240
|
-
const p = path.isAbsolute(explicitTaskSource)
|
|
241
|
-
? explicitTaskSource
|
|
242
|
-
: path.resolve(cwd, explicitTaskSource)
|
|
243
|
-
if (fs.existsSync(p)) {
|
|
244
|
-
return { taskSourcePath: p, taskOverviewFiles: [p], usedGenerated: false }
|
|
245
|
-
}
|
|
246
|
-
return { taskSourcePath: defaultStub, taskOverviewFiles: [], usedGenerated: false }
|
|
247
|
-
}
|
|
372
|
+
ensureOssyGeneratedDir(buildPath)
|
|
373
|
+
const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_TASKS_BASENAME)
|
|
248
374
|
|
|
249
375
|
const taskFiles = discoverTaskFiles(srcDir)
|
|
250
|
-
|
|
251
|
-
if (taskFiles.length > 0 || hasLegacy) {
|
|
376
|
+
if (taskFiles.length > 0) {
|
|
252
377
|
fs.writeFileSync(
|
|
253
378
|
generatedPath,
|
|
254
379
|
generateTaskModule({
|
|
255
380
|
generatedPath,
|
|
256
381
|
taskFiles,
|
|
257
|
-
legacyPath: hasLegacy ? legacyPath : null,
|
|
258
382
|
})
|
|
259
383
|
)
|
|
260
|
-
|
|
261
|
-
return { taskSourcePath: generatedPath, taskOverviewFiles: overview, usedGenerated: true }
|
|
384
|
+
return { taskSourcePath: generatedPath, taskOverviewFiles: taskFiles, usedGenerated: true }
|
|
262
385
|
}
|
|
263
386
|
|
|
264
387
|
return { taskSourcePath: defaultStub, taskOverviewFiles: [], usedGenerated: false }
|
|
265
388
|
}
|
|
266
389
|
|
|
267
390
|
export function discoverPageFiles(srcDir) {
|
|
268
|
-
|
|
269
|
-
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
391
|
+
if (!fs.existsSync(srcDir) || !fs.statSync(srcDir).isDirectory()) {
|
|
270
392
|
return []
|
|
271
393
|
}
|
|
272
394
|
const files = []
|
|
@@ -278,7 +400,7 @@ export function discoverPageFiles(srcDir) {
|
|
|
278
400
|
else if (PAGE_FILE_PATTERN.test(e.name)) files.push(full)
|
|
279
401
|
}
|
|
280
402
|
}
|
|
281
|
-
walk(
|
|
403
|
+
walk(srcDir)
|
|
282
404
|
return files.sort()
|
|
283
405
|
}
|
|
284
406
|
|
|
@@ -291,29 +413,105 @@ export function filePathToRoute(filePath, srcDir) {
|
|
|
291
413
|
return { id, path: routePath }
|
|
292
414
|
}
|
|
293
415
|
|
|
294
|
-
/**
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Basename for `/static/hydrate-<id>.js` must match `route.id` after `metadata` is merged in `toPage`
|
|
418
|
+
* (`{ ...derived, ...metadata }`). Uses a light `metadata` scan when possible.
|
|
419
|
+
*/
|
|
420
|
+
export function clientHydrateIdForPage (pageAbsPath, srcDir) {
|
|
421
|
+
const derived = filePathToRoute(pageAbsPath, srcDir)
|
|
422
|
+
let src = ''
|
|
423
|
+
try {
|
|
424
|
+
src = fs.readFileSync(pageAbsPath, 'utf8')
|
|
425
|
+
} catch {
|
|
426
|
+
return derived.id
|
|
427
|
+
}
|
|
428
|
+
const metaIdx = src.indexOf('export const metadata')
|
|
429
|
+
if (metaIdx === -1) return derived.id
|
|
430
|
+
const after = src.slice(metaIdx)
|
|
431
|
+
const idMatch = after.match(/\bid\s*:\s*['"]([^'"]+)['"]/)
|
|
432
|
+
return idMatch ? idMatch[1] : derived.id
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* One client entry per page: imports only that page module and hydrates the document.
|
|
437
|
+
* Keeps the same `toPage` shape as `generatePagesModule` so SSR and client trees match.
|
|
438
|
+
*/
|
|
439
|
+
export function generatePageHydrateModule ({ pageAbsPath, stubAbsPath, srcDir }) {
|
|
440
|
+
const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
|
|
441
|
+
const { id, path: routePath } = filePathToRoute(pageAbsPath, srcDir)
|
|
442
|
+
const pathLiteral = JSON.stringify(routePath)
|
|
443
|
+
const idLiteral = JSON.stringify(id)
|
|
444
|
+
return [
|
|
445
|
+
'// Generated by @ossy/app — do not edit',
|
|
446
|
+
'',
|
|
447
|
+
"import React, { cloneElement } from 'react'",
|
|
448
|
+
"import 'react-dom'",
|
|
449
|
+
"import { hydrateRoot } from 'react-dom/client'",
|
|
450
|
+
`import * as _page from './${rel}'`,
|
|
451
|
+
'',
|
|
452
|
+
'function toPage(mod, derived) {',
|
|
453
|
+
' const meta = mod?.metadata || {}',
|
|
454
|
+
' const def = mod?.default',
|
|
455
|
+
' if (typeof def === \'function\') {',
|
|
456
|
+
' return { ...derived, ...meta, element: React.createElement(def) }',
|
|
457
|
+
' }',
|
|
458
|
+
' return { ...derived, ...meta, ...(def || {}) }',
|
|
459
|
+
'}',
|
|
460
|
+
'',
|
|
461
|
+
`const _route = toPage(_page, { id: ${idLiteral}, path: ${pathLiteral} })`,
|
|
462
|
+
'const initialConfig = window.__INITIAL_APP_CONFIG__ || {}',
|
|
463
|
+
'const rootTree = _route?.element',
|
|
464
|
+
' ? cloneElement(_route.element, initialConfig)',
|
|
465
|
+
" : React.createElement('p', null, 'Not found')",
|
|
466
|
+
'hydrateRoot(document, rootTree)',
|
|
467
|
+
'',
|
|
468
|
+
].join('\n')
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Writes `hydrate-<id>.jsx` for each page; removes stale `hydrate-*` outputs in `ossyDir` first. */
|
|
472
|
+
export function writePageHydrateStubs (pageFiles, srcDir, ossyDir) {
|
|
473
|
+
if (!fs.existsSync(ossyDir)) fs.mkdirSync(ossyDir, { recursive: true })
|
|
474
|
+
for (const ent of fs.readdirSync(ossyDir, { withFileTypes: true })) {
|
|
475
|
+
const full = path.join(ossyDir, ent.name)
|
|
476
|
+
if (ent.isDirectory() && ent.name.startsWith(HYDRATE_STUB_PREFIX)) {
|
|
477
|
+
fs.rmSync(full, { recursive: true, force: true })
|
|
478
|
+
} else if (
|
|
479
|
+
ent.isFile() &&
|
|
480
|
+
ent.name.startsWith(HYDRATE_STUB_PREFIX) &&
|
|
481
|
+
ent.name.endsWith(HYDRATE_STUB_SUFFIX)
|
|
482
|
+
) {
|
|
483
|
+
fs.rmSync(full, { force: true })
|
|
484
|
+
}
|
|
301
485
|
}
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
486
|
+
const seenIds = new Set()
|
|
487
|
+
for (const f of pageFiles) {
|
|
488
|
+
const hydrateId = clientHydrateIdForPage(f, srcDir)
|
|
489
|
+
if (seenIds.has(hydrateId)) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`[@ossy/app] Duplicate client hydrate id "${hydrateId}" (${f}). Per-page hydrate bundles need unique ids.`
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
seenIds.add(hydrateId)
|
|
495
|
+
const stubPath = path.join(ossyDir, `${HYDRATE_STUB_PREFIX}${hydrateId}${HYDRATE_STUB_SUFFIX}`)
|
|
496
|
+
fs.mkdirSync(path.dirname(stubPath), { recursive: true })
|
|
497
|
+
fs.writeFileSync(
|
|
498
|
+
stubPath,
|
|
499
|
+
generatePageHydrateModule({ pageAbsPath: f, stubAbsPath: stubPath, srcDir })
|
|
500
|
+
)
|
|
306
501
|
}
|
|
307
|
-
return siteRoot
|
|
308
502
|
}
|
|
309
503
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
504
|
+
export function buildClientHydrateInput (pageFiles, srcDir, ossyDir) {
|
|
505
|
+
const input = {}
|
|
506
|
+
for (const f of pageFiles) {
|
|
507
|
+
const hydrateId = clientHydrateIdForPage(f, srcDir)
|
|
508
|
+
const stubPath = path.join(ossyDir, `${HYDRATE_STUB_PREFIX}${hydrateId}${HYDRATE_STUB_SUFFIX}`)
|
|
509
|
+
input[hydrateEntryName(hydrateId)] = stubPath
|
|
510
|
+
}
|
|
511
|
+
return input
|
|
313
512
|
}
|
|
314
513
|
|
|
315
|
-
export function generatePagesModule (pageFiles,
|
|
316
|
-
const resolvedSrc = path.resolve(cwd, srcDir)
|
|
514
|
+
export function generatePagesModule (pageFiles, srcDir, generatedPath) {
|
|
317
515
|
const lines = [
|
|
318
516
|
"import React from 'react'",
|
|
319
517
|
...pageFiles.map((f, i) => {
|
|
@@ -332,8 +530,7 @@ export function generatePagesModule (pageFiles, cwd, srcDir, generatedPath) {
|
|
|
332
530
|
'',
|
|
333
531
|
'export default [',
|
|
334
532
|
...pageFiles.map((f, i) => {
|
|
335
|
-
const
|
|
336
|
-
const { id, path: defaultPath } = filePathToRoute(f, base)
|
|
533
|
+
const { id, path: defaultPath } = filePathToRoute(f, srcDir)
|
|
337
534
|
const pathStr = JSON.stringify(defaultPath)
|
|
338
535
|
return ` toPage(_page${i}, { id: '${id}', path: ${pathStr} }),`
|
|
339
536
|
}),
|
|
@@ -342,81 +539,61 @@ export function generatePagesModule (pageFiles, cwd, srcDir, generatedPath) {
|
|
|
342
539
|
return lines.join('\n')
|
|
343
540
|
}
|
|
344
541
|
|
|
345
|
-
function
|
|
346
|
-
if (Array.isArray(cfg.modules) && cfg.modules.length) {
|
|
347
|
-
return cfg.modules
|
|
348
|
-
}
|
|
349
|
-
if (Array.isArray(cfg.pagesModules) && cfg.pagesModules.length) {
|
|
350
|
-
warn?.('[@ossy/app][build] `pagesModules` is deprecated; use `modules` in src/config.js')
|
|
351
|
-
return cfg.pagesModules
|
|
352
|
-
}
|
|
353
|
-
if (typeof cfg.pagesModule === 'string') {
|
|
354
|
-
warn?.('[@ossy/app][build] `pagesModule` is deprecated; use `modules: [ ... ]` in src/config.js')
|
|
355
|
-
return [cfg.pagesModule]
|
|
356
|
-
}
|
|
357
|
-
return []
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
export async function discoverModulePageFiles({ cwd, configPath }) {
|
|
542
|
+
export async function discoverModulePageFiles({ configPath }) {
|
|
361
543
|
if (!configPath || !fs.existsSync(configPath)) return []
|
|
362
544
|
try {
|
|
363
545
|
// Try a cheap static parse first so we don't depend on the config file being
|
|
364
546
|
// importable (configs often import theme/template modules that may not be
|
|
365
547
|
// resolvable in the build-time node context).
|
|
366
548
|
const cfgSource = fs.readFileSync(configPath, 'utf8')
|
|
367
|
-
const
|
|
368
|
-
let warnedLegacy = false
|
|
369
|
-
const warnOnce = (msg) => {
|
|
370
|
-
if (!warnedLegacy) {
|
|
371
|
-
console.warn(msg)
|
|
372
|
-
warnedLegacy = true
|
|
373
|
-
}
|
|
374
|
-
}
|
|
549
|
+
const modules = []
|
|
375
550
|
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (pagesModulesArr && !modulesArr) {
|
|
381
|
-
warnOnce('[@ossy/app][build] `pagesModules` is deprecated; use `modules` in src/config.js')
|
|
382
|
-
}
|
|
551
|
+
// pagesModules: ['a', "b"]
|
|
552
|
+
const arrMatch = cfgSource.match(/pagesModules\s*:\s*\[([^\]]*)\]/m)
|
|
553
|
+
if (arrMatch?.[1]) {
|
|
554
|
+
const body = arrMatch[1]
|
|
383
555
|
const re = /['"]([^'"]+)['"]/g
|
|
384
556
|
let m
|
|
385
|
-
while ((m = re.exec(
|
|
557
|
+
while ((m = re.exec(body)) !== null) modules.push(m[1])
|
|
386
558
|
}
|
|
387
559
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
moduleNames.push(singlePagesModule[1])
|
|
393
|
-
}
|
|
560
|
+
// pagesModule: 'a'
|
|
561
|
+
if (modules.length === 0) {
|
|
562
|
+
const singleMatch = cfgSource.match(/pagesModule\s*:\s*['"]([^'"]+)['"]/m)
|
|
563
|
+
if (singleMatch?.[1]) modules.push(singleMatch[1])
|
|
394
564
|
}
|
|
395
565
|
|
|
396
|
-
|
|
397
|
-
const req = createRequire(path.resolve(cwd, 'package.json'))
|
|
566
|
+
if (modules.length) {
|
|
567
|
+
const req = createRequire(path.resolve(process.cwd(), 'package.json'))
|
|
398
568
|
const files = []
|
|
399
|
-
for (const name of
|
|
569
|
+
for (const name of modules) {
|
|
400
570
|
const pkgJsonPath = req.resolve(`${name}/package.json`)
|
|
401
571
|
const pkgDir = path.dirname(pkgJsonPath)
|
|
402
|
-
const
|
|
403
|
-
files.push(...discoverPageFiles(
|
|
572
|
+
const modulePagesDir = path.join(pkgDir, 'src', 'pages')
|
|
573
|
+
files.push(...discoverPageFiles(modulePagesDir))
|
|
404
574
|
}
|
|
405
575
|
return files
|
|
406
576
|
}
|
|
407
577
|
|
|
408
|
-
if (moduleNames.length) {
|
|
409
|
-
return loadFromNames(moduleNames)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
578
|
const mod = await import(url.pathToFileURL(configPath))
|
|
413
579
|
const cfg = mod?.default ?? mod ?? {}
|
|
414
|
-
const modules2 =
|
|
580
|
+
const modules2 = Array.isArray(cfg.pagesModules)
|
|
581
|
+
? cfg.pagesModules
|
|
582
|
+
: (typeof cfg.pagesModule === 'string' ? [cfg.pagesModule] : [])
|
|
415
583
|
|
|
416
584
|
if (!modules2.length) return []
|
|
417
|
-
|
|
585
|
+
|
|
586
|
+
const req = createRequire(path.resolve(process.cwd(), 'package.json'))
|
|
587
|
+
const files = []
|
|
588
|
+
for (const name of modules2) {
|
|
589
|
+
const pkgJsonPath = req.resolve(`${name}/package.json`)
|
|
590
|
+
const pkgDir = path.dirname(pkgJsonPath)
|
|
591
|
+
const modulePagesDir = path.join(pkgDir, 'src', 'pages')
|
|
592
|
+
files.push(...discoverPageFiles(modulePagesDir))
|
|
593
|
+
}
|
|
594
|
+
return files
|
|
418
595
|
} catch (e) {
|
|
419
|
-
console.warn('[@ossy/app][build]
|
|
596
|
+
console.warn('[@ossy/app][build] pagesModules config could not be loaded; continuing without modules')
|
|
420
597
|
return []
|
|
421
598
|
}
|
|
422
599
|
}
|
|
@@ -459,25 +636,19 @@ export function printBuildOverview({
|
|
|
459
636
|
apiSourcePath,
|
|
460
637
|
apiOverviewFiles = [],
|
|
461
638
|
configPath,
|
|
462
|
-
isPageFiles,
|
|
463
639
|
pageFiles,
|
|
464
640
|
}) {
|
|
465
|
-
const rel = (p) => path.relative(process.cwd(), p)
|
|
466
|
-
const
|
|
467
|
-
const srcDir = path.resolve(cwd, 'src')
|
|
641
|
+
const rel = (p) => p ? path.relative(process.cwd(), p) : undefined
|
|
642
|
+
const srcDir = path.resolve(process.cwd(), 'src')
|
|
468
643
|
console.log('\n \x1b[1mBuild overview\x1b[0m')
|
|
469
|
-
console.log(' ' + '─'.repeat(50))
|
|
470
|
-
console.log(` \x1b[36mPages:\x1b[0m ${rel(pagesSourcePath)}`)
|
|
471
644
|
if (fs.existsSync(configPath)) {
|
|
472
645
|
console.log(` \x1b[36mConfig:\x1b[0m ${rel(configPath)}`)
|
|
473
646
|
}
|
|
474
|
-
console.log(` \x1b[36mAPI:\x1b[0m ${rel(apiSourcePath)}`)
|
|
475
647
|
console.log(' ' + '─'.repeat(50))
|
|
476
648
|
|
|
477
|
-
const pages =
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
: parsePagesFromSource(pagesSourcePath)
|
|
649
|
+
const pages = pageFiles?.length
|
|
650
|
+
? pageFiles.map((f) => filePathToRoute(f, srcDir))
|
|
651
|
+
: parsePagesFromSource(pagesSourcePath)
|
|
481
652
|
if (pages.length > 0) {
|
|
482
653
|
console.log(' \x1b[36mRoutes:\x1b[0m')
|
|
483
654
|
const maxId = Math.max(6, ...pages.map((p) => String(p.id).length))
|
|
@@ -510,192 +681,58 @@ export function printBuildOverview({
|
|
|
510
681
|
console.log(' ' + '─'.repeat(50) + '\n')
|
|
511
682
|
}
|
|
512
683
|
|
|
513
|
-
const WORKER_EXTERNAL_IDS = new Set([
|
|
514
|
-
...builtinModules,
|
|
515
|
-
...builtinModules.map((m) => `node:${m}`),
|
|
516
|
-
'dotenv',
|
|
517
|
-
'dotenv/config',
|
|
518
|
-
'@ossy/sdk',
|
|
519
|
-
'openai',
|
|
520
|
-
'sharp',
|
|
521
|
-
])
|
|
522
|
-
|
|
523
|
-
function isWorkerExternal(id) {
|
|
524
|
-
if (id.startsWith('.') || path.isAbsolute(id)) return false
|
|
525
|
-
if (WORKER_EXTERNAL_IDS.has(id)) return true
|
|
526
|
-
if (id.startsWith('node:')) return true
|
|
527
|
-
if (id === '@ossy/sdk' || id.startsWith('@ossy/sdk/')) return true
|
|
528
|
-
if (id === 'openai' || id.startsWith('openai/')) return true
|
|
529
|
-
if (id === 'sharp' || id.startsWith('sharp/')) return true
|
|
530
|
-
if (id === 'dotenv' || id.startsWith('dotenv/')) return true
|
|
531
|
-
return false
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Worker-only bundle: discovers `*.task.js` (and optional `src/tasks.js`), writes `build/.ossy/tasks.generated.js`
|
|
536
|
-
* when needed, and emits `build/worker.js`. Invoke via `npx @ossy/cli build --worker`.
|
|
537
|
-
*/
|
|
538
|
-
export async function buildWorker(cliArgs) {
|
|
539
|
-
console.log('[@ossy/app][build][worker] Starting...')
|
|
540
|
-
|
|
541
|
-
const workerArgv = cliArgs.filter((a) => a !== '--worker')
|
|
542
|
-
const options = arg(
|
|
543
|
-
{
|
|
544
|
-
'--pages': String,
|
|
545
|
-
'--p': '--pages',
|
|
546
|
-
'--destination': String,
|
|
547
|
-
'--d': '--destination',
|
|
548
|
-
'--task-source': String,
|
|
549
|
-
},
|
|
550
|
-
{ argv: workerArgv }
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
554
|
-
const cwd = process.cwd()
|
|
555
|
-
const pagesOpt = options['--pages'] || 'src'
|
|
556
|
-
const buildPath = path.resolve(options['--destination'] || 'build')
|
|
557
|
-
const { taskSourcePath, taskOverviewFiles, usedGenerated } = resolveTaskSource({
|
|
558
|
-
cwd,
|
|
559
|
-
pagesOpt,
|
|
560
|
-
scriptDir,
|
|
561
|
-
explicitTaskSource: options['--task-source'],
|
|
562
|
-
buildPath,
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
const workerEntryPath = path.resolve(scriptDir, 'worker-entry.js')
|
|
566
|
-
if (!fs.existsSync(workerEntryPath)) {
|
|
567
|
-
throw new Error(`[@ossy/app][build][worker] Missing worker entry: ${workerEntryPath}`)
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
console.log('\n \x1b[1mWorker build\x1b[0m')
|
|
571
|
-
console.log(' ' + '─'.repeat(50))
|
|
572
|
-
console.log(` \x1b[36mTasks module:\x1b[0m ${path.relative(cwd, taskSourcePath)}`)
|
|
573
|
-
if (taskOverviewFiles.length > 0) {
|
|
574
|
-
console.log(' \x1b[36mTask files:\x1b[0m')
|
|
575
|
-
taskOverviewFiles.forEach((f) => console.log(` ${path.relative(cwd, f)}`))
|
|
576
|
-
} else if (!usedGenerated) {
|
|
577
|
-
console.log(' \x1b[33m(no *.task.js; using empty task list stub)\x1b[0m')
|
|
578
|
-
}
|
|
579
|
-
console.log(' ' + '─'.repeat(50) + '\n')
|
|
580
|
-
|
|
581
|
-
const bundle = await rollup({
|
|
582
|
-
input: workerEntryPath,
|
|
583
|
-
external: isWorkerExternal,
|
|
584
|
-
plugins: [
|
|
585
|
-
ossyCleanBuildDirPlugin(buildPath),
|
|
586
|
-
replace({
|
|
587
|
-
preventAssignment: true,
|
|
588
|
-
delimiters: ['%%', '%%'],
|
|
589
|
-
'@ossy/tasks/source-file': taskSourcePath,
|
|
590
|
-
}),
|
|
591
|
-
replace({
|
|
592
|
-
preventAssignment: true,
|
|
593
|
-
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
594
|
-
}),
|
|
595
|
-
json(),
|
|
596
|
-
resolveCommonJsDependencies(),
|
|
597
|
-
resolveDependencies({ preferBuiltins: true }),
|
|
598
|
-
babel({
|
|
599
|
-
babelHelpers: 'bundled',
|
|
600
|
-
presets: ['@babel/preset-env', '@babel/preset-react'],
|
|
601
|
-
}),
|
|
602
|
-
],
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
await bundle.write({
|
|
606
|
-
dir: buildPath,
|
|
607
|
-
entryFileNames: 'worker.js',
|
|
608
|
-
chunkFileNames: '[name]-[hash].js',
|
|
609
|
-
format: 'esm',
|
|
610
|
-
})
|
|
611
|
-
|
|
612
|
-
console.log('[@ossy/app][build][worker] Finished')
|
|
613
|
-
}
|
|
614
|
-
|
|
615
684
|
export const build = async (cliArgs) => {
|
|
616
685
|
const options = arg({
|
|
617
|
-
'--pages': String,
|
|
618
|
-
'--p': '--pages',
|
|
619
|
-
|
|
620
|
-
'--destination': String,
|
|
621
|
-
'--d': '--destination',
|
|
622
|
-
|
|
623
686
|
'--config': String,
|
|
624
687
|
'-c': '--config',
|
|
625
|
-
|
|
626
|
-
'--api-source': String,
|
|
627
|
-
'--worker': Boolean,
|
|
628
|
-
'--task-source': String,
|
|
629
688
|
}, { argv: cliArgs })
|
|
630
689
|
|
|
631
|
-
if (options['--worker']) {
|
|
632
|
-
return buildWorker(cliArgs)
|
|
633
|
-
}
|
|
634
|
-
|
|
635
690
|
console.log('[@ossy/app][build] Starting...')
|
|
636
691
|
|
|
637
692
|
|
|
638
693
|
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
const buildPath = path.resolve(options['--destination'] || 'build')
|
|
642
|
-
ensureOssyGeneratedDir(buildPath)
|
|
643
|
-
const srcDir = path.resolve(pagesOpt)
|
|
694
|
+
const buildPath = path.resolve('build')
|
|
695
|
+
const srcDir = path.resolve('src')
|
|
644
696
|
const configPath = path.resolve(options['--config'] || 'src/config.js');
|
|
645
697
|
const pageFiles = discoverPageFiles(srcDir)
|
|
646
|
-
const modulePageFiles = await discoverModulePageFiles({
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
)
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
} else {
|
|
663
|
-
throw new Error(`[@ossy/app][build] No pages found. Create *.page.jsx files in src/, or src/pages.jsx`);
|
|
664
|
-
}
|
|
698
|
+
const modulePageFiles = await discoverModulePageFiles({ configPath })
|
|
699
|
+
|
|
700
|
+
resetOssyBuildDir(buildPath)
|
|
701
|
+
|
|
702
|
+
writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
|
|
703
|
+
|
|
704
|
+
const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
|
|
705
|
+
|
|
706
|
+
const allPageFiles = [...pageFiles, ...modulePageFiles]
|
|
707
|
+
fs.writeFileSync(
|
|
708
|
+
pagesGeneratedPath,
|
|
709
|
+
generatePagesModule(allPageFiles, srcDir, pagesGeneratedPath)
|
|
710
|
+
)
|
|
711
|
+
const ossyDir = ossyGeneratedDir(buildPath)
|
|
712
|
+
writePageHydrateStubs(allPageFiles, srcDir, ossyDir)
|
|
713
|
+
const clientHydrateInput = buildClientHydrateInput(allPageFiles, srcDir, ossyDir)
|
|
665
714
|
|
|
666
715
|
const {
|
|
667
716
|
apiSourcePath: resolvedApi,
|
|
668
717
|
apiOverviewFiles,
|
|
669
718
|
} = resolveApiSource({
|
|
670
|
-
|
|
671
|
-
pagesOpt,
|
|
672
|
-
scriptDir,
|
|
673
|
-
explicitApiSource: options['--api-source'],
|
|
719
|
+
srcDir,
|
|
674
720
|
buildPath,
|
|
675
721
|
})
|
|
676
722
|
let apiSourcePath = resolvedApi
|
|
677
|
-
let middlewareSourcePath = path.resolve(
|
|
723
|
+
let middlewareSourcePath = path.resolve('src/middleware.js');
|
|
678
724
|
const publicDir = path.resolve('public')
|
|
679
725
|
|
|
680
|
-
const inputClient = path.resolve(scriptDir, 'client.js')
|
|
681
726
|
const inputServer = path.resolve(scriptDir, 'server.js')
|
|
682
727
|
|
|
683
|
-
const inputFiles = [inputClient, inputServer]
|
|
684
|
-
|
|
685
|
-
const appEntryPath = path.resolve(scriptDir, 'default-app.jsx')
|
|
686
728
|
printBuildOverview({
|
|
687
|
-
pagesSourcePath:
|
|
729
|
+
pagesSourcePath: pagesGeneratedPath,
|
|
688
730
|
apiSourcePath,
|
|
689
731
|
apiOverviewFiles,
|
|
690
732
|
configPath,
|
|
691
|
-
|
|
692
|
-
pageFiles: isPageFiles ? [...pageFiles, ...modulePageFiles] : [],
|
|
733
|
+
pageFiles,
|
|
693
734
|
});
|
|
694
735
|
|
|
695
|
-
if (!fs.existsSync(apiSourcePath)) {
|
|
696
|
-
apiSourcePath = path.resolve(scriptDir, 'api.js')
|
|
697
|
-
}
|
|
698
|
-
|
|
699
736
|
if (!fs.existsSync(middlewareSourcePath)) {
|
|
700
737
|
middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
|
|
701
738
|
}
|
|
@@ -704,95 +741,64 @@ export const build = async (cliArgs) => {
|
|
|
704
741
|
? configPath
|
|
705
742
|
: path.resolve(scriptDir, 'default-config.js')
|
|
706
743
|
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
})
|
|
766
|
-
],
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
const outputOptions = [
|
|
770
|
-
{
|
|
771
|
-
dir: 'build',
|
|
772
|
-
// preserveModules: true,
|
|
773
|
-
entryFileNames: ({ name }) => {
|
|
774
|
-
|
|
775
|
-
const serverFileNames = ['server', 'api', 'middleware']
|
|
776
|
-
|
|
777
|
-
if (serverFileNames.includes(name)) {
|
|
778
|
-
return '[name].js'
|
|
779
|
-
} else if (name === 'client') {
|
|
780
|
-
return 'public/static/main.js'
|
|
781
|
-
} else if (name === 'config') {
|
|
782
|
-
return 'public/static/[name].js'
|
|
783
|
-
} else {
|
|
784
|
-
return 'public/static/[name].js'
|
|
785
|
-
}
|
|
786
|
-
},
|
|
787
|
-
chunkFileNames: 'public/static/[name]-[hash].js',
|
|
788
|
-
format: 'esm',
|
|
789
|
-
}
|
|
790
|
-
];
|
|
791
|
-
|
|
792
|
-
const bundle = await rollup(inputOptions);
|
|
793
|
-
|
|
794
|
-
for (const options of outputOptions) {
|
|
795
|
-
await bundle.write(options);
|
|
744
|
+
const sharedPluginOpts = {
|
|
745
|
+
pagesGeneratedPath,
|
|
746
|
+
apiSourcePath,
|
|
747
|
+
middlewareSourcePath,
|
|
748
|
+
configSourcePath,
|
|
749
|
+
nodeEnv: 'production',
|
|
750
|
+
buildPath,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const serverPlugins = createOssyRollupPlugins({
|
|
754
|
+
...sharedPluginOpts,
|
|
755
|
+
nodeExternals: true,
|
|
756
|
+
preferBuiltins: true,
|
|
757
|
+
copyPublicFrom: publicDir,
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
const serverBundle = await rollup({
|
|
761
|
+
input: { server: inputServer },
|
|
762
|
+
plugins: serverPlugins,
|
|
763
|
+
external: ossyServerExternal,
|
|
764
|
+
})
|
|
765
|
+
await serverBundle.write({
|
|
766
|
+
dir: buildPath,
|
|
767
|
+
format: 'esm',
|
|
768
|
+
preserveModules: true,
|
|
769
|
+
preserveModulesRoot: path.dirname(inputServer),
|
|
770
|
+
entryFileNames ({ name }) {
|
|
771
|
+
return name === 'server' ? 'server.js' : '[name].js'
|
|
772
|
+
},
|
|
773
|
+
assetFileNames: '[name][extname]',
|
|
774
|
+
})
|
|
775
|
+
await serverBundle.close()
|
|
776
|
+
|
|
777
|
+
const clientPlugins = createOssyRollupPlugins({
|
|
778
|
+
...sharedPluginOpts,
|
|
779
|
+
nodeExternals: false,
|
|
780
|
+
preferBuiltins: false,
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
if (Object.keys(clientHydrateInput).length > 0) {
|
|
784
|
+
const clientBundle = await rollup({
|
|
785
|
+
input: clientHydrateInput,
|
|
786
|
+
plugins: clientPlugins,
|
|
787
|
+
})
|
|
788
|
+
await clientBundle.write({
|
|
789
|
+
dir: buildPath,
|
|
790
|
+
format: 'esm',
|
|
791
|
+
entryFileNames ({ name }) {
|
|
792
|
+
if (name.startsWith('hydrate__')) {
|
|
793
|
+
const pageId = name.slice('hydrate__'.length)
|
|
794
|
+
return `public/static/hydrate-${pageId}.js`
|
|
795
|
+
}
|
|
796
|
+
return 'public/static/[name].js'
|
|
797
|
+
},
|
|
798
|
+
chunkFileNames: 'public/static/[name]-[hash].js',
|
|
799
|
+
plugins: [minifyBrowserStaticChunks()],
|
|
800
|
+
})
|
|
801
|
+
await clientBundle.close()
|
|
796
802
|
}
|
|
797
803
|
|
|
798
804
|
ensureBuildStubs(buildPath)
|