@typed/vite-plugin 0.0.25 → 0.0.27
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/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/resolveTypedConfig.d.ts +7 -0
- package/dist/resolveTypedConfig.d.ts.map +1 -0
- package/dist/resolveTypedConfig.js +12 -0
- package/dist/resolveTypedConfig.js.map +1 -0
- package/dist/vite-plugin.d.ts +3 -55
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +102 -408
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +6 -4
- package/src/constants.ts +1 -0
- package/src/index.ts +2 -0
- package/src/resolveTypedConfig.test.ts +40 -0
- package/src/resolveTypedConfig.ts +21 -0
- package/src/vite-plugin.ts +144 -640
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +3 -0
package/src/vite-plugin.ts
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { readFile } from 'fs/promises'
|
|
1
|
+
import { existsSync, writeFileSync } from 'fs'
|
|
3
2
|
import { EOL } from 'os'
|
|
4
|
-
import { basename,
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
makeHtmlModule,
|
|
10
|
-
makeRuntimeModule,
|
|
11
|
-
readDirectory,
|
|
12
|
-
readModules,
|
|
13
|
-
readApiModules,
|
|
14
|
-
makeApiModule,
|
|
15
|
-
type ApiModuleTreeJson,
|
|
16
|
-
type ModuleTreeJsonWithFallback,
|
|
17
|
-
moduleTreeToJson,
|
|
18
|
-
apiModuleTreeToJson,
|
|
19
|
-
addOrUpdateBase,
|
|
20
|
-
} from '@typed/compiler'
|
|
3
|
+
import { basename, join, resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
import { pipe } from '@fp-ts/core/Function'
|
|
6
|
+
import * as Option from '@fp-ts/core/Option'
|
|
7
|
+
import { Compiler, getRelativePath, type ResolvedOptions } from '@typed/compiler'
|
|
21
8
|
import glob from 'fast-glob'
|
|
22
|
-
import { Project, SourceFile, ts, type CompilerOptions } from 'ts-morph'
|
|
23
9
|
// @ts-expect-error Unable to resolve types w/ NodeNext
|
|
24
10
|
import vavite from 'vavite'
|
|
25
|
-
import type { ConfigEnv,
|
|
11
|
+
import type { ConfigEnv, Plugin, PluginOption, UserConfig, ViteDevServer } from 'vite'
|
|
26
12
|
import compression from 'vite-plugin-compression'
|
|
27
13
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
28
14
|
|
|
15
|
+
import { PLUGIN_NAME } from './constants.js'
|
|
16
|
+
|
|
29
17
|
/**
|
|
30
18
|
* The Configuration for the Typed Plugin. All file paths can be relative to sourceDirectory or
|
|
31
19
|
* can be absolute, path.resolve is used to stitch things together.
|
|
@@ -81,433 +69,75 @@ export interface PluginOptions {
|
|
|
81
69
|
|
|
82
70
|
const cwd = process.cwd()
|
|
83
71
|
|
|
84
|
-
export const PLUGIN_NAME = '@typed/vite-plugin'
|
|
85
|
-
|
|
86
72
|
export interface TypedVitePlugin extends Plugin {
|
|
87
73
|
readonly name: typeof PLUGIN_NAME
|
|
88
74
|
readonly resolvedOptions: ResolvedOptions
|
|
89
75
|
}
|
|
90
76
|
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const HTML_VIRTUAL_ENTRYPOINT_PREFIX = 'html'
|
|
94
|
-
const API_VIRTUAL_ENTRYPOINT_PREFIX = 'api'
|
|
95
|
-
const EXPRESS_VIRTUAL_ENTRYPOINT_PREFIX = 'express'
|
|
96
|
-
const TYPED_CONFIG_IMPORT = 'typed:config'
|
|
97
|
-
|
|
98
|
-
const PREFIXES = [
|
|
99
|
-
RUNTIME_VIRTUAL_ENTRYPOINT_PREFIX,
|
|
100
|
-
BROWSER_VIRTUAL_ENTRYPOINT_PREFIX,
|
|
101
|
-
HTML_VIRTUAL_ENTRYPOINT_PREFIX,
|
|
102
|
-
API_VIRTUAL_ENTRYPOINT_PREFIX,
|
|
103
|
-
EXPRESS_VIRTUAL_ENTRYPOINT_PREFIX,
|
|
104
|
-
]
|
|
105
|
-
|
|
106
|
-
const VIRTUAL_ID_PREFIX = '\0'
|
|
107
|
-
|
|
108
|
-
export interface Manifest {
|
|
109
|
-
readonly entryFiles: EntryFile[]
|
|
110
|
-
|
|
111
|
-
readonly modules: {
|
|
112
|
-
[importer: string]: Record<string, ManifestEntry>
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface ClientManifest extends Manifest {
|
|
117
|
-
readonly entryFiles: HtmlEntryFile[]
|
|
118
|
-
|
|
119
|
-
readonly modules: {
|
|
120
|
-
[importer: string]: Record<string, ManifestEntry>
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export type EntryFile = HtmlEntryFile | TsEntryFile
|
|
125
|
-
|
|
126
|
-
export interface HtmlEntryFile {
|
|
127
|
-
readonly type: 'html'
|
|
128
|
-
readonly filePath: string
|
|
129
|
-
readonly imports: string[]
|
|
130
|
-
readonly basePath: string
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface TsEntryFile {
|
|
134
|
-
readonly type: 'ts'
|
|
135
|
-
readonly filePath: string
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export type ManifestEntry =
|
|
139
|
-
| ApiManifestEntry
|
|
140
|
-
| ExpressManifestEntry
|
|
141
|
-
| HtmlManifestEntry
|
|
142
|
-
| RuntimeManifestEntry
|
|
143
|
-
| BrowserManifestEntry
|
|
144
|
-
|
|
145
|
-
export interface ApiManifestEntry extends ApiModuleTreeJson {
|
|
146
|
-
readonly type: 'api'
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface ExpressManifestEntry extends ApiModuleTreeJson {
|
|
150
|
-
readonly type: 'express'
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export interface HtmlManifestEntry {
|
|
154
|
-
readonly type: 'html'
|
|
155
|
-
readonly filePath: string
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface BrowserManifestEntry extends ModuleTreeJsonWithFallback {
|
|
159
|
-
readonly type: 'browser'
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export interface RuntimeManifestEntry extends ModuleTreeJsonWithFallback {
|
|
163
|
-
readonly type: 'runtime'
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export interface ResolvedOptions {
|
|
167
|
-
readonly sourceDirectory: string
|
|
168
|
-
readonly tsConfig: string
|
|
169
|
-
readonly serverFilePath: string
|
|
170
|
-
readonly clientOutputDirectory: string
|
|
171
|
-
readonly serverOutputDirectory: string
|
|
172
|
-
readonly htmlFiles: readonly string[]
|
|
173
|
-
readonly debug: boolean
|
|
174
|
-
readonly saveGeneratedModules: boolean
|
|
175
|
-
readonly isStaticBuild: boolean
|
|
176
|
-
readonly base: string
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default function makePlugin({
|
|
180
|
-
sourceDirectory: directory,
|
|
181
|
-
tsConfig,
|
|
182
|
-
serverFilePath,
|
|
183
|
-
clientOutputDirectory,
|
|
184
|
-
serverOutputDirectory,
|
|
185
|
-
htmlFileGlobs,
|
|
186
|
-
debug = false,
|
|
187
|
-
saveGeneratedModules = false,
|
|
188
|
-
isStaticBuild = process.env.STATIC_BUILD === 'true',
|
|
189
|
-
}: PluginOptions): PluginOption[] {
|
|
190
|
-
// Resolved options
|
|
191
|
-
const sourceDirectory = resolve(cwd, directory)
|
|
192
|
-
const tsConfigFilePath = resolve(sourceDirectory, tsConfig ?? 'tsconfig.json')
|
|
193
|
-
const resolvedServerFilePath = resolve(sourceDirectory, serverFilePath ?? 'server.ts')
|
|
194
|
-
const resolvedServerOutputDirectory = resolve(
|
|
195
|
-
sourceDirectory,
|
|
196
|
-
serverOutputDirectory ?? 'dist/server',
|
|
197
|
-
)
|
|
198
|
-
const resolvedClientOutputDirectory = resolve(
|
|
199
|
-
sourceDirectory,
|
|
200
|
-
clientOutputDirectory ?? 'dist/client',
|
|
201
|
-
)
|
|
202
|
-
const defaultIncludeExcludeTs = {
|
|
203
|
-
include: ['**/*.ts', '**/*.tsx'],
|
|
204
|
-
exclude: ['dist/**/*'],
|
|
205
|
-
}
|
|
206
|
-
const resolvedEffectTsOptions = {
|
|
207
|
-
trace: defaultIncludeExcludeTs,
|
|
208
|
-
optimize: defaultIncludeExcludeTs,
|
|
209
|
-
debug: debug ? defaultIncludeExcludeTs : {},
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const resolvedOptions: ResolvedOptions = {
|
|
213
|
-
sourceDirectory,
|
|
214
|
-
tsConfig: tsConfigFilePath,
|
|
215
|
-
serverFilePath: resolvedServerFilePath,
|
|
216
|
-
serverOutputDirectory: resolvedServerOutputDirectory,
|
|
217
|
-
clientOutputDirectory: resolvedClientOutputDirectory,
|
|
218
|
-
htmlFiles: findHtmlFiles(sourceDirectory, htmlFileGlobs).map((p) =>
|
|
219
|
-
resolve(sourceDirectory, p),
|
|
220
|
-
),
|
|
221
|
-
debug,
|
|
222
|
-
saveGeneratedModules,
|
|
223
|
-
isStaticBuild,
|
|
224
|
-
base: '/',
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const dependentsMap = new Map<string, Set<string>>()
|
|
228
|
-
const filePathToModule = new Map<string, SourceFile>()
|
|
229
|
-
const manifest: Manifest = {
|
|
230
|
-
entryFiles: [],
|
|
231
|
-
modules: {},
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const addManifestEntry = (entry: ManifestEntry, importer: string, id: string) => {
|
|
235
|
-
if (!manifest.modules[importer]) {
|
|
236
|
-
manifest.modules[importer] = {}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
manifest.modules[importer][id] = entry
|
|
240
|
-
}
|
|
77
|
+
export default function makePlugin(pluginOptions: PluginOptions): PluginOption[] {
|
|
78
|
+
const options: ResolvedOptions = resolveOptions(pluginOptions)
|
|
241
79
|
|
|
80
|
+
let compiler: Compiler
|
|
242
81
|
let devServer: ViteDevServer
|
|
243
|
-
let logger: Logger
|
|
244
82
|
let isSsr = false
|
|
245
|
-
let project: Project
|
|
246
|
-
let transformers: ts.CustomTransformers
|
|
247
|
-
|
|
248
|
-
const serverExists = existsSync(resolvedServerFilePath)
|
|
249
83
|
|
|
250
84
|
const plugins: PluginOption[] = [
|
|
251
85
|
tsconfigPaths({
|
|
252
|
-
projects: [
|
|
86
|
+
projects: [options.tsConfig],
|
|
253
87
|
}),
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
88
|
+
pipe(
|
|
89
|
+
options.serverFilePath,
|
|
90
|
+
Option.filter(() => !options.isStaticBuild),
|
|
91
|
+
Option.map((serverEntry) =>
|
|
92
|
+
vavite({
|
|
93
|
+
serverEntry,
|
|
94
|
+
serveClientAssetsInDev: true,
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
Option.getOrNull,
|
|
98
|
+
),
|
|
260
99
|
]
|
|
261
100
|
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
info(`Setting up TypeScript project...`, logger)
|
|
268
|
-
project = setupTsProject(tsConfigFilePath)
|
|
269
|
-
info(`Setup TypeScript project.`, logger)
|
|
270
|
-
|
|
271
|
-
// Setup transformer for virtual modules.
|
|
272
|
-
transformers = {
|
|
273
|
-
before: [
|
|
274
|
-
// Types are weird for some reason
|
|
275
|
-
(effectTransformer as any as typeof effectTransformer.default)(
|
|
276
|
-
project.getProgram().compilerObject,
|
|
277
|
-
resolvedEffectTsOptions,
|
|
278
|
-
).before,
|
|
279
|
-
],
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const transpilerCompilerOptions = (): CompilerOptions => {
|
|
284
|
-
setupProject()
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
...project.getCompilerOptions(),
|
|
288
|
-
inlineSourceMap: false,
|
|
289
|
-
inlineSources: saveGeneratedModules,
|
|
290
|
-
sourceMap: true,
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const handleFileChange = async (path: string, event: 'create' | 'update' | 'delete') => {
|
|
295
|
-
if (/\.tsx?$/.test(path)) {
|
|
296
|
-
switch (event) {
|
|
297
|
-
case 'create': {
|
|
298
|
-
project.addSourceFileAtPath(path)
|
|
299
|
-
break
|
|
300
|
-
}
|
|
301
|
-
case 'update': {
|
|
302
|
-
const sourceFile = project.getSourceFile(path)
|
|
303
|
-
|
|
304
|
-
if (sourceFile) {
|
|
305
|
-
sourceFile.refreshFromFileSystemSync()
|
|
306
|
-
} else {
|
|
307
|
-
project.addSourceFileAtPath(path)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (devServer) {
|
|
311
|
-
const dependents = dependentsMap.get(path.replace(/.ts(x)?/, '.js$1'))
|
|
312
|
-
|
|
313
|
-
for (const dependent of dependents ?? []) {
|
|
314
|
-
const mod = devServer.moduleGraph.getModuleById(dependent)
|
|
315
|
-
|
|
316
|
-
if (mod) {
|
|
317
|
-
info(`reloading ${dependent}`, logger)
|
|
318
|
-
|
|
319
|
-
await devServer.reloadModule(mod)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
break
|
|
325
|
-
}
|
|
326
|
-
case 'delete': {
|
|
327
|
-
await project.getSourceFile(path)?.deleteImmediately()
|
|
328
|
-
break
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const buildRuntimeModule = async (importer: string, id: string) => {
|
|
335
|
-
// Setup the TypeScript project if it hasn't been already
|
|
336
|
-
setupProject()
|
|
337
|
-
|
|
338
|
-
const moduleDirectory = resolve(dirname(importer), parseModulesFromId(id, importer))
|
|
339
|
-
const relativeDirectory = relative(sourceDirectory, moduleDirectory)
|
|
340
|
-
const isBrowser = id.startsWith(BROWSER_VIRTUAL_ENTRYPOINT_PREFIX)
|
|
341
|
-
const moduleType = isBrowser ? 'browser' : 'runtime'
|
|
342
|
-
const filePath = `${moduleDirectory}.${moduleType}.__generated__.ts`
|
|
343
|
-
const directory = await readDirectory(moduleDirectory)
|
|
344
|
-
const moduleTree = readModules(project, directory)
|
|
345
|
-
|
|
346
|
-
addManifestEntry(
|
|
347
|
-
{
|
|
348
|
-
type: moduleType,
|
|
349
|
-
...moduleTreeToJson(sourceDirectory, moduleTree),
|
|
350
|
-
},
|
|
351
|
-
relative(sourceDirectory, importer),
|
|
352
|
-
id,
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
const sourceFile = makeRuntimeModule(project, moduleTree, importer, filePath, isBrowser)
|
|
356
|
-
|
|
357
|
-
addDependents(sourceFile)
|
|
358
|
-
|
|
359
|
-
info(`Built ${moduleType} module for ${relativeDirectory}.`, logger)
|
|
360
|
-
|
|
361
|
-
filePathToModule.set(filePath, sourceFile)
|
|
362
|
-
|
|
363
|
-
if (saveGeneratedModules) {
|
|
364
|
-
await sourceFile.save()
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return filePath
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const buildHtmlModule = async (importer: string, id: string) => {
|
|
371
|
-
// Setup the TypeScript project if it hasn't been already
|
|
372
|
-
setupProject()
|
|
373
|
-
|
|
374
|
-
const htmlFileName = parseModulesFromId(id, importer)
|
|
375
|
-
const htmlFilePath = resolve(dirname(importer), htmlFileName + '.html')
|
|
376
|
-
const relativeHtmlFilePath = relative(sourceDirectory, htmlFilePath)
|
|
377
|
-
let html = ''
|
|
378
|
-
|
|
379
|
-
// If there's a dev server, use it to transform the HTML for development
|
|
380
|
-
if (!isStaticBuild && devServer) {
|
|
381
|
-
html = (await readFile(htmlFilePath, 'utf-8')).toString()
|
|
382
|
-
html = await devServer.transformIndexHtml(
|
|
383
|
-
getRelativePath(sourceDirectory, htmlFilePath),
|
|
384
|
-
html,
|
|
385
|
-
)
|
|
386
|
-
} else {
|
|
387
|
-
// Otherwise, read the already transformed file from the output directory.
|
|
388
|
-
html = (
|
|
389
|
-
await readFile(resolve(resolvedClientOutputDirectory, relativeHtmlFilePath), 'utf-8')
|
|
390
|
-
).toString()
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const sourceFile = await makeHtmlModule({
|
|
394
|
-
project,
|
|
395
|
-
base: parseBasePath(html),
|
|
396
|
-
filePath: htmlFilePath,
|
|
397
|
-
html,
|
|
398
|
-
importer,
|
|
399
|
-
serverOutputDirectory: resolvedServerOutputDirectory,
|
|
400
|
-
clientOutputDirectory: resolvedClientOutputDirectory,
|
|
401
|
-
build: isStaticBuild ? 'static' : devServer ? 'development' : 'production',
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
addManifestEntry(
|
|
405
|
-
{
|
|
406
|
-
type: 'html',
|
|
407
|
-
filePath: relativeHtmlFilePath,
|
|
408
|
-
},
|
|
409
|
-
relative(sourceDirectory, importer),
|
|
410
|
-
id,
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
addDependents(sourceFile)
|
|
414
|
-
|
|
415
|
-
info(`Built html module for ${relativeHtmlFilePath}.`, logger)
|
|
416
|
-
|
|
417
|
-
const filePath = sourceFile.getFilePath()
|
|
418
|
-
|
|
419
|
-
filePathToModule.set(filePath, sourceFile)
|
|
420
|
-
|
|
421
|
-
if (saveGeneratedModules) {
|
|
422
|
-
await sourceFile.save()
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return filePath
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const buildApiModule = async (importer: string, id: string) => {
|
|
429
|
-
// Setup the TypeScript project if it hasn't been already
|
|
430
|
-
setupProject()
|
|
431
|
-
|
|
432
|
-
const importDirectory = dirname(importer)
|
|
433
|
-
const moduleName = parseModulesFromId(id, importer)
|
|
434
|
-
const moduleDirectory = resolve(importDirectory, moduleName)
|
|
435
|
-
const relativeDirectory = relative(sourceDirectory, moduleDirectory)
|
|
436
|
-
const moduleType = id.startsWith(EXPRESS_VIRTUAL_ENTRYPOINT_PREFIX) ? 'express' : 'api'
|
|
437
|
-
const directory = await readDirectory(moduleDirectory)
|
|
438
|
-
const moduleTree = readApiModules(project, directory)
|
|
439
|
-
const filePath = `${importDirectory}/${basename(
|
|
440
|
-
moduleName,
|
|
441
|
-
)}.${moduleType.toLowerCase()}.__generated__.ts`
|
|
442
|
-
|
|
443
|
-
const sourceFile = makeApiModule(
|
|
444
|
-
project,
|
|
445
|
-
moduleTree,
|
|
446
|
-
filePath,
|
|
447
|
-
importer,
|
|
448
|
-
id.startsWith(EXPRESS_VIRTUAL_ENTRYPOINT_PREFIX),
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
addManifestEntry(
|
|
452
|
-
{
|
|
453
|
-
type: moduleType,
|
|
454
|
-
...apiModuleTreeToJson(sourceDirectory, moduleTree),
|
|
455
|
-
},
|
|
456
|
-
relative(sourceDirectory, importer),
|
|
457
|
-
id,
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
addDependents(sourceFile)
|
|
461
|
-
|
|
462
|
-
info(`Built ${moduleType} module for ${relativeDirectory}.`, logger)
|
|
463
|
-
|
|
464
|
-
filePathToModule.set(filePath, sourceFile)
|
|
465
|
-
|
|
466
|
-
if (saveGeneratedModules) {
|
|
467
|
-
await sourceFile.save()
|
|
101
|
+
const getCompiler = () => {
|
|
102
|
+
if (compiler) {
|
|
103
|
+
return compiler
|
|
468
104
|
}
|
|
469
105
|
|
|
470
|
-
return
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const addDependents = (sourceFile: SourceFile) => {
|
|
474
|
-
const importer = sourceFile.getFilePath()
|
|
475
|
-
const imports = sourceFile
|
|
476
|
-
.getLiteralsReferencingOtherSourceFiles()
|
|
477
|
-
.map((i) => i.getLiteralValue())
|
|
478
|
-
|
|
479
|
-
for (const i of imports) {
|
|
480
|
-
const dependents = dependentsMap.get(i) ?? new Set()
|
|
481
|
-
|
|
482
|
-
dependents.add(importer)
|
|
483
|
-
dependentsMap.set(i, dependents)
|
|
484
|
-
}
|
|
106
|
+
return (compiler = new Compiler(PLUGIN_NAME, options))
|
|
485
107
|
}
|
|
486
108
|
|
|
487
109
|
const virtualModulePlugin: TypedVitePlugin = {
|
|
488
110
|
name: PLUGIN_NAME,
|
|
489
|
-
resolvedOptions,
|
|
111
|
+
resolvedOptions: options,
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Configures our production build using vavite
|
|
115
|
+
*/
|
|
490
116
|
config(config: UserConfig, env: ConfigEnv) {
|
|
491
117
|
isSsr = env.ssrBuild ?? false
|
|
492
118
|
|
|
493
119
|
// Configure Build steps when running with vavite
|
|
494
120
|
if (env.mode === 'multibuild') {
|
|
495
121
|
const clientBuild: UserConfig['build'] = {
|
|
496
|
-
outDir:
|
|
122
|
+
outDir: options.clientOutputDirectory,
|
|
497
123
|
rollupOptions: {
|
|
498
|
-
input: buildClientInput(
|
|
124
|
+
input: buildClientInput(options.htmlFiles),
|
|
499
125
|
},
|
|
500
126
|
}
|
|
501
127
|
|
|
502
|
-
const serverBuild
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
128
|
+
const serverBuild = pipe(
|
|
129
|
+
options.serverFilePath,
|
|
130
|
+
Option.map((index): UserConfig['build'] => ({
|
|
131
|
+
ssr: true,
|
|
132
|
+
outDir: options.serverOutputDirectory,
|
|
133
|
+
rollupOptions: {
|
|
134
|
+
input: {
|
|
135
|
+
index,
|
|
136
|
+
},
|
|
508
137
|
},
|
|
509
|
-
},
|
|
510
|
-
|
|
138
|
+
})),
|
|
139
|
+
Option.getOrNull,
|
|
140
|
+
)
|
|
511
141
|
|
|
512
142
|
;(config as any).buildSteps = [
|
|
513
143
|
{
|
|
@@ -515,63 +145,63 @@ export default function makePlugin({
|
|
|
515
145
|
// @ts-expect-error Unable to resolve types w/ NodeNext
|
|
516
146
|
config: { build: clientBuild, plugins: [compression()] },
|
|
517
147
|
},
|
|
518
|
-
|
|
519
|
-
? [
|
|
520
|
-
{
|
|
521
|
-
name: 'server',
|
|
522
|
-
config: { build: serverBuild },
|
|
523
|
-
},
|
|
524
|
-
]
|
|
525
|
-
: []),
|
|
148
|
+
serverBuild,
|
|
526
149
|
]
|
|
527
150
|
|
|
528
151
|
return
|
|
529
152
|
}
|
|
530
153
|
},
|
|
531
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Updates our resolved options with the correct base path
|
|
157
|
+
* and parses our input files for our manifest
|
|
158
|
+
*/
|
|
532
159
|
configResolved(resolvedConfig) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const input = resolvedConfig.build.rollupOptions.input
|
|
536
|
-
|
|
537
|
-
Object.assign(resolvedOptions, { base: resolvedConfig.base })
|
|
160
|
+
// Ensure options have the correct base path
|
|
161
|
+
Object.assign(options, { base: resolvedConfig.base })
|
|
538
162
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (typeof input === 'string') {
|
|
542
|
-
manifest.entryFiles.push(parseEntryFile(sourceDirectory, input))
|
|
543
|
-
} else if (Array.isArray(input)) {
|
|
544
|
-
manifest.entryFiles.push(...input.map((i) => parseEntryFile(sourceDirectory, i)))
|
|
545
|
-
} else {
|
|
546
|
-
manifest.entryFiles.push(
|
|
547
|
-
...Object.values(input).map((i) => parseEntryFile(sourceDirectory, i)),
|
|
548
|
-
)
|
|
549
|
-
}
|
|
163
|
+
getCompiler().parseInput(resolvedConfig.build.rollupOptions.input)
|
|
550
164
|
},
|
|
551
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Configures our dev server to watch for changes to our input files
|
|
168
|
+
* and exposes the dev server to our compiler methods
|
|
169
|
+
*/
|
|
552
170
|
configureServer(server) {
|
|
553
171
|
devServer = server
|
|
554
172
|
|
|
555
173
|
server.watcher.on('all', (event, path) => {
|
|
556
174
|
if (event === 'change') {
|
|
557
|
-
handleFileChange(path, 'update')
|
|
175
|
+
getCompiler().handleFileChange(path, 'update', server)
|
|
558
176
|
} else if (event === 'add') {
|
|
559
|
-
handleFileChange(path, 'create')
|
|
177
|
+
getCompiler().handleFileChange(path, 'create', server)
|
|
560
178
|
} else if (event === 'unlink') {
|
|
561
|
-
handleFileChange(path, 'delete')
|
|
179
|
+
getCompiler().handleFileChange(path, 'delete', server)
|
|
562
180
|
}
|
|
563
181
|
})
|
|
564
182
|
},
|
|
565
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Handles file changes
|
|
186
|
+
*/
|
|
566
187
|
async watchChange(path, { event }) {
|
|
567
|
-
handleFileChange(path, event)
|
|
188
|
+
getCompiler().handleFileChange(path, event, devServer)
|
|
568
189
|
},
|
|
569
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Type-check our project and fail the build if there are any errors.
|
|
193
|
+
* If successful, save our manifest to disk.
|
|
194
|
+
*/
|
|
570
195
|
async closeBundle() {
|
|
571
|
-
|
|
196
|
+
const { manifest, throwDiagnostics } = getCompiler()
|
|
197
|
+
|
|
198
|
+
// Throw any diagnostics that were collected during the build
|
|
199
|
+
throwDiagnostics()
|
|
200
|
+
|
|
201
|
+
if (Object.keys(manifest).length > 0 && !options.isStaticBuild) {
|
|
572
202
|
writeFileSync(
|
|
573
203
|
resolve(
|
|
574
|
-
isSsr ?
|
|
204
|
+
isSsr ? options.serverOutputDirectory : options.clientOutputDirectory,
|
|
575
205
|
'typed-manifest.json',
|
|
576
206
|
),
|
|
577
207
|
JSON.stringify(manifest, null, 2) + EOL,
|
|
@@ -579,82 +209,32 @@ export default function makePlugin({
|
|
|
579
209
|
}
|
|
580
210
|
},
|
|
581
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Resolve and build our virtual modules
|
|
214
|
+
*/
|
|
582
215
|
async resolveId(id: string, importer?: string) {
|
|
583
|
-
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (
|
|
588
|
-
id.startsWith(RUNTIME_VIRTUAL_ENTRYPOINT_PREFIX) ||
|
|
589
|
-
id.startsWith(BROWSER_VIRTUAL_ENTRYPOINT_PREFIX)
|
|
590
|
-
) {
|
|
591
|
-
return VIRTUAL_ID_PREFIX + (await buildRuntimeModule(importer, id))
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (id.startsWith(HTML_VIRTUAL_ENTRYPOINT_PREFIX)) {
|
|
595
|
-
return VIRTUAL_ID_PREFIX + (await buildHtmlModule(importer, id))
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
id.startsWith(API_VIRTUAL_ENTRYPOINT_PREFIX) ||
|
|
600
|
-
id.startsWith(EXPRESS_VIRTUAL_ENTRYPOINT_PREFIX)
|
|
601
|
-
) {
|
|
602
|
-
return VIRTUAL_ID_PREFIX + (await buildApiModule(importer, id))
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
if (id === TYPED_CONFIG_IMPORT) {
|
|
606
|
-
return VIRTUAL_ID_PREFIX + TYPED_CONFIG_IMPORT
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
importer = importer.replace(VIRTUAL_ID_PREFIX, '')
|
|
610
|
-
|
|
611
|
-
// Virtual modules have problems with resolving relative paths due to not
|
|
612
|
-
// having a real directory to work with thus the need to resolve them manually.
|
|
613
|
-
if (filePathToModule.has(importer) && id.startsWith('.')) {
|
|
614
|
-
return findRelativeFile(importer, id)
|
|
615
|
-
}
|
|
216
|
+
return await getCompiler().resolveId(id, importer, devServer)
|
|
616
217
|
},
|
|
617
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Load our virtual modules
|
|
221
|
+
*/
|
|
618
222
|
async load(id: string) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const sourceFile = filePathToModule.get(id) ?? project?.getSourceFile(id)
|
|
622
|
-
|
|
623
|
-
if (sourceFile) {
|
|
624
|
-
logDiagnostics(project, sourceFile, sourceDirectory, id, logger)
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
code: sourceFile.getFullText(),
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
if (id === TYPED_CONFIG_IMPORT) {
|
|
632
|
-
return {
|
|
633
|
-
code: Object.entries(resolvedOptions)
|
|
634
|
-
.map(([key, value]) => `export const ${key} = ${JSON.stringify(value)}`)
|
|
635
|
-
.join(EOL),
|
|
636
|
-
}
|
|
637
|
-
}
|
|
223
|
+
return await getCompiler().load(id)
|
|
638
224
|
},
|
|
639
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Transorm TypeScript modules
|
|
228
|
+
*/
|
|
640
229
|
transform(text: string, id: string) {
|
|
641
|
-
|
|
642
|
-
const output = ts.transpileModule(text, {
|
|
643
|
-
fileName: id,
|
|
644
|
-
compilerOptions: transpilerCompilerOptions(),
|
|
645
|
-
transformers,
|
|
646
|
-
})
|
|
647
|
-
|
|
648
|
-
return {
|
|
649
|
-
code: output.outputText,
|
|
650
|
-
map: output.sourceMapText,
|
|
651
|
-
}
|
|
652
|
-
}
|
|
230
|
+
return getCompiler().transpileTsModule(text, id, devServer)
|
|
653
231
|
},
|
|
654
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Transform HTML files
|
|
235
|
+
*/
|
|
655
236
|
transformIndexHtml(html: string) {
|
|
656
|
-
|
|
657
|
-
return addOrUpdateBase(html, resolvedOptions.base)
|
|
237
|
+
return getCompiler().transformHtml(html)
|
|
658
238
|
},
|
|
659
239
|
}
|
|
660
240
|
|
|
@@ -663,60 +243,68 @@ export default function makePlugin({
|
|
|
663
243
|
return plugins
|
|
664
244
|
}
|
|
665
245
|
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const jsPath = resolve(dir, id)
|
|
692
|
-
|
|
693
|
-
if (existsSync(jsPath)) {
|
|
694
|
-
return tsPath
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function parseModulesFromId(id: string, importer: string | undefined): string {
|
|
699
|
-
let pages = id
|
|
246
|
+
function resolveOptions({
|
|
247
|
+
sourceDirectory: directory,
|
|
248
|
+
tsConfig,
|
|
249
|
+
serverFilePath,
|
|
250
|
+
clientOutputDirectory,
|
|
251
|
+
serverOutputDirectory,
|
|
252
|
+
htmlFileGlobs,
|
|
253
|
+
debug = false,
|
|
254
|
+
saveGeneratedModules = false,
|
|
255
|
+
isStaticBuild = process.env.STATIC_BUILD === 'true',
|
|
256
|
+
}: PluginOptions): ResolvedOptions {
|
|
257
|
+
// Resolved options
|
|
258
|
+
const sourceDirectory = resolve(cwd, directory)
|
|
259
|
+
const tsConfigFilePath = resolve(sourceDirectory, tsConfig ?? 'tsconfig.json')
|
|
260
|
+
const resolvedServerFilePath = resolve(sourceDirectory, serverFilePath ?? 'server.ts')
|
|
261
|
+
const serverExists = existsSync(resolvedServerFilePath)
|
|
262
|
+
const resolvedServerOutputDirectory = resolve(
|
|
263
|
+
sourceDirectory,
|
|
264
|
+
serverOutputDirectory ?? 'dist/server',
|
|
265
|
+
)
|
|
266
|
+
const resolvedClientOutputDirectory = resolve(
|
|
267
|
+
sourceDirectory,
|
|
268
|
+
clientOutputDirectory ?? 'dist/client',
|
|
269
|
+
)
|
|
700
270
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
271
|
+
const exclusions = [
|
|
272
|
+
getRelativePath(sourceDirectory, join(resolvedServerOutputDirectory, '/**/*')),
|
|
273
|
+
getRelativePath(sourceDirectory, join(resolvedClientOutputDirectory, '/**/*')),
|
|
274
|
+
'**/node_modules/**',
|
|
275
|
+
]
|
|
704
276
|
|
|
705
|
-
|
|
706
|
-
|
|
277
|
+
const resolvedOptions: ResolvedOptions = {
|
|
278
|
+
base: '/',
|
|
279
|
+
clientOutputDirectory: resolvedClientOutputDirectory,
|
|
280
|
+
debug,
|
|
281
|
+
exclusions,
|
|
282
|
+
htmlFiles: findHtmlFiles(sourceDirectory, htmlFileGlobs, exclusions).map((p) =>
|
|
283
|
+
resolve(sourceDirectory, p),
|
|
284
|
+
),
|
|
285
|
+
isStaticBuild,
|
|
286
|
+
saveGeneratedModules,
|
|
287
|
+
serverFilePath: serverExists ? Option.some(resolvedServerFilePath) : Option.none(),
|
|
288
|
+
serverOutputDirectory: resolvedServerOutputDirectory,
|
|
289
|
+
sourceDirectory,
|
|
290
|
+
tsConfig: tsConfigFilePath,
|
|
707
291
|
}
|
|
708
292
|
|
|
709
|
-
return
|
|
293
|
+
return resolvedOptions
|
|
710
294
|
}
|
|
711
295
|
|
|
712
|
-
function findHtmlFiles(
|
|
296
|
+
function findHtmlFiles(
|
|
297
|
+
directory: string,
|
|
298
|
+
htmlFileGlobs: readonly string[] | undefined,
|
|
299
|
+
exclusions: readonly string[],
|
|
300
|
+
): readonly string[] {
|
|
713
301
|
if (htmlFileGlobs) {
|
|
714
302
|
// eslint-disable-next-line import/no-named-as-default-member
|
|
715
|
-
return glob.sync([...htmlFileGlobs], { cwd: directory })
|
|
303
|
+
return glob.sync([...htmlFileGlobs, ...exclusions.map((x) => '!' + x)], { cwd: directory })
|
|
716
304
|
}
|
|
717
305
|
|
|
718
306
|
// eslint-disable-next-line import/no-named-as-default-member
|
|
719
|
-
return glob.sync(['**/*.html',
|
|
307
|
+
return glob.sync(['**/*.html', ...exclusions.map((x) => '!' + x)], {
|
|
720
308
|
cwd: directory,
|
|
721
309
|
})
|
|
722
310
|
}
|
|
@@ -727,87 +315,3 @@ function buildClientInput(htmlFilePaths: readonly string[]) {
|
|
|
727
315
|
{},
|
|
728
316
|
)
|
|
729
317
|
}
|
|
730
|
-
|
|
731
|
-
function getRelativePath(from: string, to: string) {
|
|
732
|
-
const path = relative(from, to)
|
|
733
|
-
|
|
734
|
-
if (!path.startsWith('.')) {
|
|
735
|
-
return './' + path
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return path
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
function info(message: string, logger: Logger | undefined) {
|
|
742
|
-
if (logger) {
|
|
743
|
-
logger.info(`[${PLUGIN_NAME}]: ${message}`)
|
|
744
|
-
} else {
|
|
745
|
-
console.info(`[${PLUGIN_NAME}]:`, `${message}`)
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function parseEntryFile(sourceDirectory: string, filePath: string): EntryFile {
|
|
750
|
-
if (filePath.endsWith('.html')) {
|
|
751
|
-
return parseHtmlEntryFile(sourceDirectory, filePath)
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return parseTsEntryFile(sourceDirectory, filePath)
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function parseHtmlEntryFile(sourceDirectory: string, filePath: string): EntryFile {
|
|
758
|
-
const content = readFileSync(filePath, 'utf-8').toString()
|
|
759
|
-
|
|
760
|
-
return {
|
|
761
|
-
type: 'html',
|
|
762
|
-
filePath: relative(sourceDirectory, filePath),
|
|
763
|
-
imports: parseHtmlImports(sourceDirectory, content),
|
|
764
|
-
basePath: parseBasePath(content),
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
function parseHtmlImports(sourceDirectory: string, content: string) {
|
|
769
|
-
const imports: string[] = []
|
|
770
|
-
|
|
771
|
-
const matches = content.match(/<script[^>]*src="([^"]*)"[^>]*>/g)
|
|
772
|
-
|
|
773
|
-
if (matches) {
|
|
774
|
-
for (const match of matches) {
|
|
775
|
-
// If script is not type=module then skip
|
|
776
|
-
if (!match.includes('type="module"')) {
|
|
777
|
-
continue
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
const src = match.match(/src="([^"]*)"/)?.[1]
|
|
781
|
-
|
|
782
|
-
if (src) {
|
|
783
|
-
const fullPath = join(sourceDirectory, src)
|
|
784
|
-
const relativePath = relative(sourceDirectory, fullPath)
|
|
785
|
-
|
|
786
|
-
imports.push(relativePath)
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return imports
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function parseBasePath(content: string) {
|
|
795
|
-
const baseTag = content.match(/<base[^>]*>/)?.[0]
|
|
796
|
-
|
|
797
|
-
if (baseTag) {
|
|
798
|
-
const href = baseTag.match(/href="([^"]*)"/)?.[1]
|
|
799
|
-
|
|
800
|
-
if (href) {
|
|
801
|
-
return href
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return '/'
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function parseTsEntryFile(sourceDirectory: string, filePath: string): EntryFile {
|
|
809
|
-
return {
|
|
810
|
-
type: 'ts',
|
|
811
|
-
filePath: relative(sourceDirectory, filePath),
|
|
812
|
-
}
|
|
813
|
-
}
|