@platformatic/vite 2.0.0-alpha.10

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/index.js ADDED
@@ -0,0 +1,440 @@
1
+ import fastifyStatic from '@fastify/static'
2
+ import {
3
+ BaseStackable,
4
+ transformConfig as basicTransformConfig,
5
+ cleanBasePath,
6
+ createServerListener,
7
+ ensureTrailingSlash,
8
+ errors,
9
+ getServerUrl,
10
+ importFile,
11
+ resolvePackage,
12
+ schemaOptions
13
+ } from '@platformatic/basic'
14
+ import { ConfigManager } from '@platformatic/config'
15
+ import { NodeStackable } from '@platformatic/node'
16
+ import fastify from 'fastify'
17
+ import { existsSync } from 'node:fs'
18
+ import { readFile, writeFile } from 'node:fs/promises'
19
+ import { dirname, resolve } from 'node:path'
20
+ import { pathToFileURL } from 'node:url'
21
+ import { satisfies } from 'semver'
22
+ import { packageJson, schema } from './lib/schema.js'
23
+
24
+ const supportedVersions = '^5.0.0'
25
+
26
+ export class ViteStackable extends BaseStackable {
27
+ #vite
28
+ #app
29
+ #server
30
+ #basePath
31
+
32
+ constructor (options, root, configManager) {
33
+ super('vite', packageJson.version, options, root, configManager)
34
+ }
35
+
36
+ async init () {
37
+ this.#vite = dirname(resolvePackage(this.root, 'vite'))
38
+ const vitePackage = JSON.parse(await readFile(resolve(this.#vite, 'package.json'), 'utf-8'))
39
+
40
+ /* c8 ignore next 3 */
41
+ if (!satisfies(vitePackage.version, supportedVersions)) {
42
+ throw new errors.UnsupportedVersion('vite', vitePackage.version, supportedVersions)
43
+ }
44
+ }
45
+
46
+ async start ({ listen }) {
47
+ // Make this idempotent
48
+ if (this.url) {
49
+ return this.url
50
+ }
51
+
52
+ if (this.isProduction) {
53
+ await this.#startProduction(listen)
54
+ } else {
55
+ await this.#startDevelopment(listen)
56
+ }
57
+ }
58
+
59
+ async stop () {
60
+ if (this.subprocess) {
61
+ return this.stopCommand()
62
+ }
63
+
64
+ return this.#app.close()
65
+ }
66
+
67
+ async build () {
68
+ const config = this.configManager.current
69
+ const command = config.application.commands.build
70
+ const configFile = config.vite.configFile ? resolve(this.root, config.vite.configFile) : undefined
71
+ let basePath = config.application?.basePath
72
+ ? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
73
+ : undefined
74
+ let outDir
75
+
76
+ if (command) {
77
+ return this.buildWithCommand(command, basePath)
78
+ }
79
+
80
+ await this.init()
81
+ const { build } = await importFile(resolve(this.#vite, 'dist/node/index.js'))
82
+
83
+ await build({
84
+ root: this.root,
85
+ base: basePath,
86
+ mode: 'production',
87
+ configFile,
88
+ logLevel: this.logger.level,
89
+ build: {
90
+ outDir: config.application.outputDirectory
91
+ },
92
+ plugins: [
93
+ {
94
+ name: 'platformatic-build',
95
+ configResolved: config => {
96
+ basePath = ensureTrailingSlash(cleanBasePath(config.base))
97
+ outDir = resolve(this.root, config.build.outDir)
98
+ }
99
+ }
100
+ ]
101
+ })
102
+
103
+ await writeFile(resolve(outDir, '.platformatic-build.json'), JSON.stringify({ basePath }), 'utf-8')
104
+ }
105
+
106
+ /* c8 ignore next 5 */
107
+ async getWatchConfig () {
108
+ return {
109
+ enabled: false
110
+ }
111
+ }
112
+
113
+ // This is only used in production mode
114
+ async inject (injectParams, onInject) {
115
+ const res = await this.#app.inject(injectParams, onInject)
116
+
117
+ /* c8 ignore next 3 */
118
+ if (onInject) {
119
+ return
120
+ }
121
+
122
+ // Since inject might be called from the main thread directly via ITC, let's clean it up
123
+ const { statusCode, headers, body, payload, rawPayload } = res
124
+ return { statusCode, headers, body, payload, rawPayload }
125
+ }
126
+
127
+ getMeta () {
128
+ let composer = { prefix: this.servicePrefix, wantsAbsoluteUrls: true, needsRootRedirect: true }
129
+
130
+ if (this.isProduction) {
131
+ composer = {
132
+ tcp: typeof this.url !== 'undefined',
133
+ url: this.url,
134
+ prefix: (this.subprocessConfig?.base ?? this.#basePath).replace(/(^\/)|(\/$)/g, ''),
135
+ wantsAbsoluteUrls: true,
136
+ needsRootRedirect: true
137
+ }
138
+ } else if (this.url) {
139
+ if (!this.#basePath) {
140
+ const config = this.subprocessConfig ?? this.#app.config
141
+ this.#basePath = config.base.replace(/(^\/)|(\/$)/g, '')
142
+ }
143
+
144
+ composer = {
145
+ tcp: true,
146
+ url: this.url,
147
+ prefix: this.#basePath.replace(/(^\/)|(\/$)/g, ''),
148
+ wantsAbsoluteUrls: true,
149
+ needsRootRedirect: true
150
+ }
151
+ }
152
+
153
+ return { composer }
154
+ }
155
+
156
+ _getVite () {
157
+ return this.#app
158
+ }
159
+
160
+ async #startDevelopment () {
161
+ const config = this.configManager.current
162
+ const command = this.configManager.current.application.commands.development
163
+
164
+ this.#basePath = config.application?.basePath
165
+ ? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
166
+ : undefined
167
+
168
+ if (command) {
169
+ return this.startWithCommand(command)
170
+ }
171
+
172
+ // Prepare options
173
+ const { hostname, port, https, cors } = this.serverConfig ?? {}
174
+ const configFile = config.vite.configFile ? resolve(this.root, config.vite.configFile) : undefined
175
+
176
+ const serverOptions = {
177
+ host: hostname || '127.0.0.1',
178
+ port: port || 0,
179
+ strictPort: false,
180
+ https,
181
+ cors,
182
+ origin: 'http://localhost',
183
+ hmr: true,
184
+ fs: {
185
+ strict: config.vite.devServer.strict
186
+ }
187
+ }
188
+
189
+ // Require Vite
190
+ const serverPromise = createServerListener((this.isEntrypoint ? serverOptions?.port : undefined) ?? true)
191
+ const { createServer } = await importFile(resolve(this.#vite, 'dist/node/index.js'))
192
+
193
+ // Create the server and listen
194
+ this.#app = await createServer({
195
+ root: this.root,
196
+ base: this.#basePath,
197
+ mode: 'development',
198
+ configFile,
199
+ logLevel: this.logger.level,
200
+ clearScreen: false,
201
+ optimizeDeps: { force: false },
202
+ server: serverOptions
203
+ })
204
+
205
+ await this.#app.listen()
206
+ this.#server = await serverPromise
207
+ this.url = getServerUrl(this.#server)
208
+ }
209
+
210
+ async #startProduction (listen) {
211
+ const config = this.configManager.current
212
+ const command = this.configManager.current.application.commands.production
213
+
214
+ this.#basePath = config.application?.basePath
215
+ ? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
216
+ : undefined
217
+
218
+ if (command) {
219
+ return this.startWithCommand(command)
220
+ }
221
+
222
+ if (this.#app && listen) {
223
+ const serverOptions = this.serverConfig
224
+ await this.#app.listen({ host: serverOptions?.hostname || '127.0.0.1', port: serverOptions?.port || 0 })
225
+ this.url = getServerUrl(this.#app.server)
226
+ return this.url
227
+ }
228
+
229
+ this.#app = fastify({ logger: { level: 'info' } })
230
+
231
+ const outputDirectory = resolve(this.root, config.application.outputDirectory)
232
+ this.verifyOutputDirectory(outputDirectory)
233
+ const buildInfoPath = resolve(outputDirectory, '.platformatic-build.json')
234
+
235
+ if (!this.#basePath && existsSync(buildInfoPath)) {
236
+ try {
237
+ const buildInfo = JSON.parse(await readFile(buildInfoPath, 'utf-8'))
238
+ this.#basePath = buildInfo.basePath
239
+ } catch (e) {
240
+ console.log(e)
241
+ }
242
+ }
243
+
244
+ await this.#app.register(fastifyStatic, {
245
+ root: outputDirectory,
246
+ prefix: this.#basePath,
247
+ prefixAvoidTrailingSlash: true,
248
+ schemaHide: true
249
+ })
250
+
251
+ await this.#app.ready()
252
+ }
253
+ }
254
+
255
+ export class ViteSSRStackable extends NodeStackable {
256
+ #basePath
257
+
258
+ constructor (options, root, configManager) {
259
+ super(options, root, configManager)
260
+
261
+ this.type = 'vite'
262
+ }
263
+
264
+ async init () {
265
+ const config = this.configManager.current
266
+
267
+ this.#basePath = config.application?.basePath
268
+ ? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
269
+ : undefined
270
+
271
+ this.registerGlobals({
272
+ id: this.id,
273
+ // Always use URL to avoid serialization problem in Windows
274
+ root: pathToFileURL(this.root).toString(),
275
+ basePath: this.#basePath,
276
+ logLevel: this.logger.level
277
+ })
278
+ }
279
+
280
+ async start ({ listen }) {
281
+ // Make this idempotent
282
+ /* c8 ignore next 3 */
283
+ if (this.url) {
284
+ return this.url
285
+ }
286
+
287
+ const config = this.configManager.current
288
+ const command = config.application.commands[this.isProduction ? 'production' : 'development']
289
+
290
+ if (command) {
291
+ return this.startWithCommand(command)
292
+ }
293
+
294
+ if (this.isProduction) {
295
+ const clientDirectory = config.vite.ssr.clientDirectory
296
+ this.verifyOutputDirectory(
297
+ resolve(this.root, clientDirectory, config.application.outputDirectory, clientDirectory)
298
+ )
299
+ }
300
+
301
+ await super.start({ listen })
302
+ await super._listen()
303
+ }
304
+
305
+ async build () {
306
+ const config = this.configManager.current
307
+ const command = config.application.commands.build
308
+ const configFile = config.vite.configFile ? resolve(this.root, config.vite.configFile) : undefined
309
+ let basePath = config.application?.basePath
310
+ ? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
311
+ : undefined
312
+
313
+ if (command) {
314
+ return this.buildWithCommand(command, basePath)
315
+ }
316
+
317
+ const clientDirectory = config.vite.ssr.clientDirectory
318
+ const serverDirectory = config.vite.ssr.serverDirectory
319
+ let clientOutDir = resolve(this.root, clientDirectory, config.application.outputDirectory, clientDirectory)
320
+
321
+ await this.init()
322
+ const vite = dirname(resolvePackage(this.root, 'vite'))
323
+ const { build } = await importFile(resolve(vite, 'dist/node/index.js'))
324
+
325
+ // Build the client
326
+ await build({
327
+ root: resolve(this.root, clientDirectory),
328
+ base: basePath,
329
+ mode: 'production',
330
+ configFile,
331
+ logLevel: this.logger.level,
332
+ build: {
333
+ outDir: clientOutDir,
334
+ ssrManifest: true
335
+ },
336
+ plugins: [
337
+ {
338
+ name: 'platformatic-build',
339
+ configResolved: config => {
340
+ basePath = ensureTrailingSlash(cleanBasePath(config.base))
341
+ clientOutDir = resolve(this.root, clientDirectory, config.build.outDir)
342
+ }
343
+ }
344
+ ]
345
+ })
346
+
347
+ await writeFile(resolve(clientOutDir, '.platformatic-build.json'), JSON.stringify({ basePath }), 'utf-8')
348
+
349
+ // Build the server
350
+ await build({
351
+ root: this.root,
352
+ base: basePath,
353
+ mode: 'production',
354
+ configFile,
355
+ logLevel: this.logger.level,
356
+ build: {
357
+ outDir: resolve(this.root, clientDirectory, config.application.outputDirectory, serverDirectory),
358
+ ssr: resolve(this.root, clientDirectory, 'index.js')
359
+ }
360
+ })
361
+ }
362
+
363
+ getMeta () {
364
+ let composer = { prefix: this.servicePrefix, wantsAbsoluteUrls: true, needsRootRedirect: true }
365
+
366
+ if (this.url) {
367
+ if (!this.#basePath) {
368
+ const application = this._getApplication()
369
+ const config = application.vite.devServer?.config ?? application.vite.config.vite
370
+ this.#basePath = (config.base ?? '').replace(/(^\/)|(\/$)/g, '')
371
+ }
372
+
373
+ composer = {
374
+ tcp: true,
375
+ url: this.url,
376
+ prefix: this.#basePath ?? this.servicePrefix,
377
+ wantsAbsoluteUrls: true,
378
+ needsRootRedirect: true
379
+ }
380
+ }
381
+
382
+ return { composer }
383
+ }
384
+
385
+ _findEntrypoint () {
386
+ const config = this.configManager.current.vite ?? {}
387
+ return resolve(this.root, config.ssr.entrypoint)
388
+ }
389
+ }
390
+
391
+ /* c8 ignore next 9 */
392
+ export function transformConfig () {
393
+ if (this.current.watch === undefined) {
394
+ this.current.watch = { enabled: false }
395
+ }
396
+
397
+ if (typeof this.current.watch !== 'object') {
398
+ this.current.watch = { enabled: this.current.watch || false }
399
+ }
400
+
401
+ if (this.current.vite.ssr === true) {
402
+ this.current.vite.ssr = {
403
+ enabled: true,
404
+ entrypoint: 'server.js',
405
+ clientDirectory: 'client',
406
+ serverDirectory: 'server'
407
+ }
408
+ }
409
+
410
+ basicTransformConfig.call(this)
411
+ }
412
+
413
+ export async function buildStackable (opts) {
414
+ const root = opts.context.directory
415
+
416
+ const configManager = new ConfigManager({ schema, source: opts.config ?? {}, schemaOptions, transformConfig })
417
+ await configManager.parseAndValidate()
418
+
419
+ // When in SSR mode, we use ViteSSRStackable, which is a subclass of @platformatic/node
420
+ const viteConfig = configManager.current.vite ?? {}
421
+
422
+ if (viteConfig.ssr?.enabled) {
423
+ return new ViteSSRStackable(opts, root, configManager)
424
+ }
425
+
426
+ return new ViteStackable(opts, root, configManager)
427
+ }
428
+
429
+ export { schema, schemaComponents } from './lib/schema.js'
430
+
431
+ export default {
432
+ configType: 'vite',
433
+ configManagerConfig: {
434
+ schemaOptions,
435
+ transformConfig
436
+ },
437
+ buildStackable,
438
+ schema,
439
+ version: packageJson.version
440
+ }
package/lib/schema.js ADDED
@@ -0,0 +1,70 @@
1
+ import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
2
+ import { schemaComponents as utilsSchemaComponents } from '@platformatic/utils'
3
+ import { readFileSync } from 'node:fs'
4
+
5
+ export const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
6
+
7
+ const vite = {
8
+ type: 'object',
9
+ properties: {
10
+ configFile: {
11
+ oneOf: [{ type: 'string' }, { type: 'boolean' }]
12
+ },
13
+ devServer: {
14
+ type: 'object',
15
+ properties: {
16
+ strict: {
17
+ type: 'boolean',
18
+ // This required to avoid showing error users when the node_modules
19
+ // for vite or similar are in some nested parent folders
20
+ default: false
21
+ }
22
+ },
23
+ additionalProperties: false,
24
+ default: {}
25
+ },
26
+ ssr: {
27
+ oneOf: [
28
+ {
29
+ type: 'object',
30
+ properties: {
31
+ enabled: { type: 'boolean' },
32
+ entrypoint: { type: 'string', default: 'server.js' },
33
+ clientDirectory: { type: 'string', default: 'client' },
34
+ serverDirectory: { type: 'string', default: 'server' }
35
+ },
36
+ required: ['entrypoint'],
37
+ additionalProperties: false
38
+ },
39
+ { type: 'boolean' }
40
+ ],
41
+ default: false
42
+ }
43
+ },
44
+ default: {},
45
+ additionalProperties: false
46
+ }
47
+
48
+ export const schemaComponents = { vite }
49
+
50
+ export const schema = {
51
+ $id: `https://schemas.platformatic.dev/@platformatic/vite/${packageJson.version}.json`,
52
+ $schema: 'http://json-schema.org/draft-07/schema#',
53
+ title: 'Platformatic Vite Stackable',
54
+ type: 'object',
55
+ properties: {
56
+ $schema: {
57
+ type: 'string'
58
+ },
59
+ server: utilsSchemaComponents.server,
60
+ watch: basicSchemaComponents.watch,
61
+ application: basicSchemaComponents.application,
62
+ vite
63
+ },
64
+ additionalProperties: false
65
+ }
66
+
67
+ /* c8 ignore next 3 */
68
+ if (process.argv[1] === import.meta.filename) {
69
+ console.log(JSON.stringify(schema, null, 2))
70
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@platformatic/vite",
3
+ "version": "2.0.0-alpha.10",
4
+ "description": "Platformatic Vite Stackable",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/platformatic/platformatic.git"
10
+ },
11
+ "author": "Paolo Insogna <paolo@cowtech.it>",
12
+ "license": "Apache-2.0",
13
+ "bugs": {
14
+ "url": "https://github.com/platformatic/platformatic/issues"
15
+ },
16
+ "homepage": "https://github.com/platformatic/platformatic#readme",
17
+ "dependencies": {
18
+ "@fastify/static": "^7.0.4",
19
+ "fastify": "5.0.0-alpha.4",
20
+ "semver": "^7.6.3",
21
+ "@platformatic/config": "2.0.0-alpha.10",
22
+ "@platformatic/basic": "2.0.0-alpha.10",
23
+ "@platformatic/node": "2.0.0-alpha.10",
24
+ "@platformatic/utils": "2.0.0-alpha.10"
25
+ },
26
+ "devDependencies": {
27
+ "@fastify/vite": "7.0.0-alpha.1",
28
+ "borp": "^0.17.0",
29
+ "eslint": "9",
30
+ "json-schema-to-typescript": "^15.0.1",
31
+ "neostandard": "^0.11.1",
32
+ "react": "^18.3.1",
33
+ "react-dom": "^18.3.1",
34
+ "typescript": "^5.5.4",
35
+ "vite": "^5.4.0",
36
+ "ws": "^8.18.0",
37
+ "@platformatic/service": "2.0.0-alpha.10",
38
+ "@platformatic/composer": "2.0.0-alpha.10"
39
+ },
40
+ "scripts": {
41
+ "test": "npm run lint && borp --concurrency=1 --no-timeout",
42
+ "coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --no-timeout",
43
+ "gen-schema": "node lib/schema.js > schema.json",
44
+ "gen-types": "json2ts > config.d.ts < schema.json",
45
+ "build": "pnpm run gen-schema && pnpm run gen-types",
46
+ "lint": "eslint"
47
+ }
48
+ }