jiek 2.1.11 → 2.1.13-alpha.1

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.
@@ -1,23 +1,27 @@
1
- import fs from 'node:fs'
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'
2
2
  import { createRequire } from 'node:module'
3
3
  import path from 'node:path'
4
+ import process from 'node:process'
4
5
 
5
6
  import { confirm } from '@inquirer/prompts'
6
7
  import { MultiBar, Presets } from 'cli-progress'
7
8
  import { program } from 'commander'
8
9
  import { execaCommand } from 'execa'
10
+ import type { renderView } from 'vite-bundle-analyzer'
9
11
 
12
+ import type { RollupBuildEvent } from '#~/bridge.ts'
10
13
  import { entriesDescription, filterDescription, outdirDescription } from '#~/commands/descriptions.ts'
11
14
  import { IS_WORKSPACE } from '#~/commands/meta.ts'
15
+ import type { TemplateOptions } from '#~/rollup/base.ts'
16
+ import { BUILDER_TYPES, BUILDER_TYPE_PACKAGE_NAME_MAP } from '#~/rollup/base.ts'
17
+ import type { Module } from '#~/rollup/bundle-analyzer.ts'
18
+ import { createServer } from '#~/server.ts'
12
19
  import type { ProjectsGraph } from '#~/utils/filterSupport.ts'
13
20
  import { filterPackagesGraph, getSelectedProjectsGraph } from '#~/utils/filterSupport.ts'
14
21
  import { getWD } from '#~/utils/getWD.ts'
15
22
  import { loadConfig } from '#~/utils/loadConfig.ts'
16
23
  import { tsRegisterName } from '#~/utils/tsRegister.ts'
17
24
 
18
- import type { RollupProgressEvent, TemplateOptions } from '../rollup/base'
19
- import { BUILDER_TYPE_PACKAGE_NAME_MAP, BUILDER_TYPES } from '../rollup/base'
20
-
21
25
  declare module 'jiek' {
22
26
  export interface Config {
23
27
  build?: TemplateOptions & {
@@ -46,6 +50,20 @@ ${isDefault ? 'This command is the default command.' : ''}
46
50
  `.trim()
47
51
 
48
52
  interface BuildOptions {
53
+ ana?: boolean
54
+ /**
55
+ * @default '.jk-analyses'
56
+ */
57
+ 'ana.dir': string
58
+ /**
59
+ * @default 'server'
60
+ */
61
+ 'ana.mode': string
62
+ 'ana.open'?: boolean
63
+ /**
64
+ * @default 'parsed'
65
+ */
66
+ 'ana.size': string
49
67
  /**
50
68
  * Auto-detect the builder from the installed dependencies.
51
69
  * If the builder is not installed, it will prompt the user to install it.
@@ -61,6 +79,12 @@ interface BuildOptions {
61
79
  */
62
80
  outdir: string
63
81
  watch: boolean
82
+ /**
83
+ * The port of the server.
84
+ *
85
+ * @default 8888
86
+ */
87
+ port: number
64
88
  silent: boolean
65
89
  verbose: boolean
66
90
  entries?: string
@@ -99,7 +123,7 @@ interface BuildOptions {
99
123
  async function checkDependency(dependency: string) {
100
124
  try {
101
125
  require.resolve(dependency)
102
- } catch (e) {
126
+ } catch {
103
127
  console.error(`The package '${dependency}' is not installed, please install it first.`)
104
128
  const { notWorkspace } = getWD()
105
129
  const command = `pnpm install -${notWorkspace ? '' : 'w'}D ${dependency}`
@@ -138,7 +162,7 @@ ${entriesDescription}
138
162
  If you pass the --entries option, it will merge into the entries of the command.
139
163
  `.trim()
140
164
 
141
- const command = isDefault
165
+ let command = isDefault
142
166
  ? (() => {
143
167
  const c = program
144
168
  .name('jb/jiek-build')
@@ -153,9 +177,10 @@ const command = isDefault
153
177
  : program
154
178
  .command(`build [${IS_WORKSPACE ? 'filters' : 'entries'}]`)
155
179
 
156
- command
180
+ command = command
157
181
  .description(description)
158
182
  .option('-t, --type <TYPE>', `The type of build, support ${BUILDER_TYPES.map(s => `"${s}"`).join(', ')}.`, v => {
183
+ // eslint-disable-next-line ts/no-unsafe-argument
159
184
  if (!BUILDER_TYPES.includes(v as any)) {
160
185
  throw new Error(`The value of 'type' must be ${BUILDER_TYPES.map(s => `"${s}"`).join(', ')}`)
161
186
  }
@@ -183,13 +208,32 @@ command
183
208
  'Only output minify files, but dts files will still be output, it only replaces the js files.',
184
209
  parseBoolean
185
210
  )
211
+
212
+ command = command
186
213
  .option('--tsconfig <TSCONFIG>', 'The path of the tsconfig file which is used to generate js and dts files.', String)
187
214
  .option('--dtsconfig <DTSCONFIG>', 'The path of the tsconfig file which is used to generate dts files.', String)
215
+
216
+ command = command
188
217
  .option('-w, --watch', 'Watch the file changes.', parseBoolean)
218
+ .option('-p, --port <PORT>', 'The port of the server.', Number.parseInt, 8888)
219
+
220
+ command = command
221
+ .option('--ana', 'Enable the bundle analyzer.', parseBoolean)
222
+ .option('--ana.dir <DIR>', 'The directory of the bundle analyzer.', '.jk-analyses')
223
+ .option('--ana.mode <MODE>', 'The mode of the bundle analyzer, support "static", "json" and "server".', 'server')
224
+ .option('--ana.open', 'Open the bundle analyzer in the browser.', parseBoolean)
225
+ .option(
226
+ '--ana.size <SIZE>',
227
+ 'The default size of the bundle analyzer, support "stat", "parsed" and "gzip".',
228
+ 'parsed'
229
+ )
230
+
231
+ command = command
189
232
  .option('-s, --silent', "Don't display logs.", parseBoolean)
190
233
  .option('-v, --verbose', 'Display debug logs.', parseBoolean)
234
+
235
+ command
191
236
  .action(async (commandFiltersOrEntries: string | undefined, options: BuildOptions) => {
192
- /* eslint-disable prefer-const */
193
237
  let {
194
238
  type,
195
239
  outdir,
@@ -207,10 +251,9 @@ command
207
251
  tsconfig,
208
252
  dtsconfig
209
253
  } = options
210
- /* eslint-enable prefer-const */
211
254
  const resolvedType = type ?? DEFAULT_BUILDER_TYPE
212
255
  if (!withoutJs) {
213
- await checkDependency(BUILDER_TYPE_PACKAGE_NAME_MAP[resolvedType]!)
256
+ await checkDependency(BUILDER_TYPE_PACKAGE_NAME_MAP[resolvedType])
214
257
  if (minifyType === 'builder') {
215
258
  minifyType = resolvedType
216
259
  }
@@ -220,7 +263,7 @@ command
220
263
  {
221
264
  ...BUILDER_TYPE_PACKAGE_NAME_MAP,
222
265
  terser: '@rollup/plugin-terser'
223
- }[resolvedType]!
266
+ }[resolvedType]
224
267
  )
225
268
  }
226
269
  let shouldPassThrough = false
@@ -238,6 +281,50 @@ command
238
281
  },
239
282
  [] as string[]
240
283
  )
284
+
285
+ const modules: Module[] = []
286
+ const cjsModules: Module[] = []
287
+ const esmModules: Module[] = []
288
+ let render: typeof renderView | undefined
289
+ const analyzer = options.ana
290
+ ? {
291
+ dir: options['ana.dir'],
292
+ mode: options['ana.mode'],
293
+ open: options['ana.open'],
294
+ size: options['ana.size']
295
+ }
296
+ : undefined
297
+ if (
298
+ options.ana
299
+ && ![
300
+ 'stat',
301
+ 'parsed',
302
+ 'gzip'
303
+ ].includes(analyzer?.size ?? '')
304
+ ) {
305
+ throw new Error('The value of `ana.size` must be "stat", "parsed" or "gzip"')
306
+ }
307
+ const server = analyzer && createServer(options.port, 'localhost')
308
+
309
+ if (analyzer) {
310
+ await checkDependency('vite-bundle-analyzer')
311
+ const { renderView } = await import('vite-bundle-analyzer')
312
+ render = renderView
313
+ }
314
+ const anaPaths = new Set<string>()
315
+ const refreshAnalyzer = async (subPath = '', renderModules = modules) => {
316
+ if (!(analyzer && server && render)) return
317
+ const p = `/ana${subPath}`
318
+ anaPaths.add(p)
319
+ void server.renderTo(
320
+ p,
321
+ await render(renderModules, {
322
+ title: `Jiek Analyzer - ${subPath}`,
323
+ mode: analyzer.size as 'stat' | 'parsed' | 'gzip'
324
+ })
325
+ )
326
+ }
327
+
241
328
  const { build } = loadConfig()
242
329
  silent = silent ?? build?.silent ?? false
243
330
 
@@ -256,7 +343,7 @@ command
256
343
  entries = undefined
257
344
  }
258
345
  const env = {
259
- ...process.env,
346
+ JIEK_ANALYZER: analyzer && JSON.stringify(analyzer),
260
347
  JIEK_BUILDER: type,
261
348
  JIEK_OUT_DIR: outdir,
262
349
  JIEK_CLEAN: String(!noClean),
@@ -268,7 +355,8 @@ command
268
355
  JIEK_ONLY_MINIFY: String(onlyMin),
269
356
  JIEK_MINIFY_TYPE: minifyType,
270
357
  JIEK_TSCONFIG: tsconfig,
271
- JIEK_DTSCONFIG: dtsconfig
358
+ JIEK_DTSCONFIG: dtsconfig,
359
+ ...process.env
272
360
  }
273
361
 
274
362
  const multiBars = new MultiBar({
@@ -285,12 +373,15 @@ command
285
373
  throw new Error('no package found')
286
374
  }
287
375
  const wdNodeModules = path.resolve(wd, 'node_modules')
288
- if (!fs.existsSync(wdNodeModules)) {
289
- fs.mkdirSync(wdNodeModules)
376
+ if (!existsSync(wdNodeModules)) {
377
+ mkdirSync(wdNodeModules)
290
378
  }
291
- const jiekTempDir = (...paths: string[]) => path.resolve(wdNodeModules, '.jiek', ...paths)
292
- if (!fs.existsSync(jiekTempDir())) {
293
- fs.mkdirSync(jiekTempDir())
379
+ const resolveByJiekTemp = (...paths: string[]) => path.resolve(wdNodeModules, '.jiek', ...paths)
380
+ const jiekTemp = resolveByJiekTemp()
381
+ if (!existsSync(jiekTemp)) {
382
+ try {
383
+ mkdirSync(jiekTemp)
384
+ } catch {}
294
385
  }
295
386
 
296
387
  const rollupBinaryPath = require.resolve('rollup')
@@ -298,18 +389,35 @@ command
298
389
  let i = 0
299
390
  await Promise.all(
300
391
  Object.entries(value).map(async ([dir, manifest]) => {
301
- if (!manifest.name) {
392
+ if (manifest.name == null) {
302
393
  throw new Error('package.json must have a name field')
303
394
  }
395
+ if (analyzer) {
396
+ const anaDir = path.resolve(dir, analyzer.dir)
397
+ if (!existsSync(anaDir)) {
398
+ mkdirSync(anaDir, { recursive: true })
399
+ }
400
+ const gitIgnorePath = path.resolve(anaDir, '.gitignore')
401
+ if (!existsSync(gitIgnorePath)) {
402
+ writeFileSync(gitIgnorePath, '*\n!.gitignore\n')
403
+ }
404
+ const npmIgnorePath = path.resolve(anaDir, '.npmignore')
405
+ if (!existsSync(npmIgnorePath)) {
406
+ writeFileSync(npmIgnorePath, '*\n')
407
+ }
408
+ if (!statSync(anaDir).isDirectory()) {
409
+ throw new Error(`The directory '${anaDir}' is not a directory.`)
410
+ }
411
+ }
304
412
 
305
413
  // TODO support auto build child packages in workspaces
306
414
  const escapeManifestName = manifest.name.replace(/^@/g, '').replace(/\//g, '+')
307
- const configFile = jiekTempDir(
415
+ const configFile = resolveByJiekTemp(
308
416
  `${escapeManifestName ?? `anonymous-${i++}`}.rollup.config.js`
309
417
  )
310
- fs.writeFileSync(configFile, FILE_TEMPLATE(manifest))
418
+ writeFileSync(configFile, FILE_TEMPLATE(manifest))
311
419
  const command = [rollupBinaryPath, '--silent', '-c', configFile]
312
- if (tsRegisterName) {
420
+ if (tsRegisterName != null) {
313
421
  command.unshift(`node -r ${tsRegisterName}`)
314
422
  }
315
423
  if (watch) {
@@ -329,92 +437,152 @@ command
329
437
  const times: Record<string, number> = {}
330
438
  const locks: Record<string, boolean> = {}
331
439
  let inputMaxLen = 10
332
- child.on('message', (e: RollupProgressEvent) => {
333
- if (e.type === 'debug') console.log(...(Array.isArray(e.data) ? e.data : [e.data]))
334
- })
335
- !silent && child.on('message', (e: RollupProgressEvent) => {
336
- if (e.type === 'init') {
337
- const { leafMap, targetsLength } = e.data
338
- const leafs = Array
339
- .from(leafMap.entries())
340
- .flatMap(([input, pathAndCondiions]) =>
341
- pathAndCondiions.map(([path, ...conditions]) => ({
342
- input,
343
- path,
344
- conditions
345
- }))
440
+ child.on('message', (e: RollupBuildEvent) => {
441
+ if (
442
+ silent && [
443
+ 'init',
444
+ 'progress',
445
+ 'watchChange'
446
+ ].includes(e.type)
447
+ ) return
448
+ switch (e.type) {
449
+ case 'init': {
450
+ const { leafMap, targetsLength } = e.data
451
+ const leafs = Array
452
+ .from(leafMap.entries())
453
+ .flatMap(([input, pathAndCondiions]) =>
454
+ pathAndCondiions.map(([path, ...conditions]) => ({
455
+ input,
456
+ path,
457
+ conditions
458
+ }))
459
+ )
460
+ let initMessage = `Package '${manifest.name}' has ${targetsLength} targets to build`
461
+ if (watch) {
462
+ initMessage += ' and watching...'
463
+ }
464
+ // eslint-disable-next-line no-console
465
+ console.log(initMessage)
466
+ leafs.forEach(({ input }) => {
467
+ inputMaxLen = Math.max(inputMaxLen, input.length)
468
+ })
469
+ leafs.forEach(({ input, path }) => {
470
+ const key = `${input}:${path}`
471
+ // eslint-disable-next-line ts/strict-boolean-expressions
472
+ if (bars[key]) return
473
+ bars[key] = multiBars.create(50, 0, {
474
+ pkgName: manifest.name,
475
+ input: input.padEnd(inputMaxLen + 5),
476
+ status: 'waiting'.padEnd(10)
477
+ }, {
478
+ barsize: 20,
479
+ linewrap: true
480
+ })
481
+ })
482
+ break
483
+ }
484
+ case 'progress': {
485
+ const {
486
+ path,
487
+ tags,
488
+ input,
489
+ event,
490
+ message
491
+ } = e.data
492
+ const bar = bars[`${input}:${path}`]
493
+ // eslint-disable-next-line ts/strict-boolean-expressions
494
+ if (!bar) return
495
+ const time = times[`${input}:${path}`]
496
+ bar.update(
497
+ {
498
+ start: 0,
499
+ resolve: 20,
500
+ end: 50
501
+ }[event ?? 'start'] ?? 0,
502
+ {
503
+ input: (
504
+ time
505
+ ? `${input}(x${time.toString().padStart(2, '0')})`
506
+ : input
507
+ ).padEnd(inputMaxLen + 5),
508
+ status: event?.padEnd(10),
509
+ message: `${tags?.join(', ')}: ${message}`
510
+ }
346
511
  )
347
- let initMessage = `Package '${manifest.name}' has ${targetsLength} targets to build`
348
- if (watch) {
349
- initMessage += ' and watching...'
512
+ break
350
513
  }
351
- console.log(initMessage)
352
- leafs.forEach(({ input }) => {
353
- inputMaxLen = Math.max(inputMaxLen, input.length)
354
- })
355
- leafs.forEach(({ input, path }) => {
514
+ case 'watchChange': {
515
+ const {
516
+ path,
517
+ input
518
+ } = e.data
356
519
  const key = `${input}:${path}`
357
- if (bars[key]) return
358
- bars[key] = multiBars.create(50, 0, {
359
- pkgName: manifest.name,
360
- input: input.padEnd(inputMaxLen + 5),
361
- status: 'waiting'.padEnd(10)
362
- }, {
363
- barsize: 20,
364
- linewrap: true
365
- })
366
- })
367
- }
368
- if (e.type === 'progress') {
369
- const {
370
- path,
371
- tags,
372
- input,
373
- event,
374
- message
375
- } = e.data
376
- const bar = bars[`${input}:${path}`]
377
- if (!bar) return
378
- const time = times[`${input}:${path}`]
379
- bar.update(
380
- {
381
- start: 0,
382
- resolve: 20,
383
- end: 50
384
- }[event ?? 'start'] ?? 0,
385
- {
386
- input: (
387
- time
388
- ? `${input}(x${time.toString().padStart(2, '0')})`
389
- : input
390
- ).padEnd(inputMaxLen + 5),
391
- status: event?.padEnd(10),
392
- message: `${tags?.join(', ')}: ${message}`
520
+ const bar = bars[key]
521
+ // eslint-disable-next-line ts/strict-boolean-expressions
522
+ if (!bar) return
523
+ let time = times[key] ?? 1
524
+ if (!locks[key]) {
525
+ time += 1
526
+ times[key] = time
527
+ setTimeout(() => {
528
+ locks[key] = false
529
+ }, 100)
530
+ bar.update(0, {
531
+ input: `${input}(x${time.toString().padStart(2, '0')})`.padEnd(inputMaxLen + 5),
532
+ status: 'watching'.padEnd(10),
533
+ message: 'watching...'
534
+ })
393
535
  }
394
- )
395
- }
396
- if (e.type === 'watchChange') {
397
- const {
398
- path,
399
- input
400
- } = e.data
401
- const key = `${input}:${path}`
402
- const bar = bars[key]
403
- if (!bar) return
404
- let time = times[key] ?? 1
405
- if (!locks[key]) {
406
- time += 1
407
- times[key] = time
408
- setTimeout(() => {
409
- locks[key] = false
410
- }, 100)
411
- bar.update(0, {
412
- input: `${input}(x${time.toString().padStart(2, '0')})`.padEnd(inputMaxLen + 5),
413
- status: 'watching'.padEnd(10),
414
- message: 'watching...'
536
+ locks[key] = true
537
+ break
538
+ }
539
+ case 'modulesAnalyze': {
540
+ const {
541
+ data: {
542
+ type,
543
+ path,
544
+ modules: pkgModules
545
+ }
546
+ } = e
547
+ pkgModules.forEach(m => {
548
+ const newM = {
549
+ ...m,
550
+ filename: `${manifest.name}/${m.filename}`,
551
+ label: `${manifest.name}/${m.label}`
552
+ }
553
+ const pushOrReplace = (arr: Module[]) => {
554
+ const index = arr.findIndex(({ filename }) => filename === newM.filename)
555
+ if (index === -1) {
556
+ arr.push(newM)
557
+ } else {
558
+ arr[index] = newM
559
+ }
560
+ }
561
+ pushOrReplace(modules)
562
+ if (type === 'esm') {
563
+ pushOrReplace(esmModules)
564
+ }
565
+ if (type === 'cjs') {
566
+ pushOrReplace(cjsModules)
567
+ }
415
568
  })
569
+ void refreshAnalyzer()
570
+ void refreshAnalyzer(
571
+ `/${type}`,
572
+ {
573
+ cjs: cjsModules,
574
+ esm: esmModules
575
+ }[type]
576
+ )
577
+ void refreshAnalyzer(`/${type}/${manifest.name}/${path.slice(2)}`, pkgModules)
578
+ break
416
579
  }
417
- locks[key] = true
580
+ case 'debug': {
581
+ // eslint-disable-next-line no-console,ts/no-unsafe-argument
582
+ console.log(...(Array.isArray(e.data) ? e.data : [e.data]))
583
+ break
584
+ }
585
+ default:
418
586
  }
419
587
  })
420
588
  await new Promise<void>((resolve, reject) => {
@@ -431,6 +599,7 @@ command
431
599
  })
432
600
  )
433
601
  }
602
+
434
603
  const commandFilters = IS_WORKSPACE ? commandFiltersOrEntries : undefined
435
604
  const filters = [
436
605
  ...new Set([
@@ -455,5 +624,20 @@ command
455
624
  }
456
625
  } finally {
457
626
  multiBars.stop()
627
+ let message = 'The build is complete'
628
+ if (analyzer) {
629
+ message += ` and the analyzer is running at http://localhost:${options.port}/ana in ${analyzer.mode} mode.\n`
630
+ message += analyzer.open ? ' The browser will open automatically.\n' : ''
631
+ if (anaPaths.size > 0) {
632
+ message += `The analyzer has ${anaPaths.size} pages:\n${
633
+ Array
634
+ .from(anaPaths)
635
+ .map(p => `http://localhost:${options.port}${p}`)
636
+ .join('\n')
637
+ }`
638
+ }
639
+ }
640
+ // eslint-disable-next-line no-console
641
+ !silent && console.log(message)
458
642
  }
459
643
  })