tungsten 0.4.9 → 0.4.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.
Files changed (93) hide show
  1. package/dist/.build-complete +1 -1
  2. package/dist/Framework.d.ts +1 -0
  3. package/dist/Framework.d.ts.map +1 -1
  4. package/dist/Framework.js +30 -20
  5. package/dist/FrameworkConfig.d.ts +4 -0
  6. package/dist/FrameworkConfig.d.ts.map +1 -1
  7. package/dist/Servers/DevServer.d.ts +0 -1
  8. package/dist/Servers/DevServer.d.ts.map +1 -1
  9. package/dist/Servers/DevServer.js +2 -1
  10. package/dist/Servers/hono/TungstenServerHono.d.ts +1 -1
  11. package/dist/Servers/hono/TungstenServerHono.d.ts.map +1 -1
  12. package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
  13. package/dist/Servers/loadEnvsDev.js +13 -20
  14. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts +6 -6
  15. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts.map +1 -1
  16. package/dist/Servers/tungstenCorePrivateEnvSchema.js +12 -22
  17. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts +7 -7
  18. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts.map +1 -1
  19. package/dist/Servers/tungstenCorePublicEnvSchema.js +6 -7
  20. package/dist/api/getApiRoutesHono.d.ts +1 -1
  21. package/dist/api/getApiRoutesHono.d.ts.map +1 -1
  22. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +1 -0
  23. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -1
  24. package/dist/babelPlugins/BabelAddDevCacheBust.js +1 -0
  25. package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
  26. package/dist/babelPlugins/BabelTransformComponentStyles.js +4 -2
  27. package/dist/babelPlugins/BabelTransformCssImports.d.ts +1 -0
  28. package/dist/babelPlugins/BabelTransformCssImports.d.ts.map +1 -1
  29. package/dist/babelPlugins/BabelTransformCssImports.js +1 -0
  30. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts +1 -0
  31. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts.map +1 -1
  32. package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.js +1 -0
  33. package/dist/babelPlugins/BabelTransformPackageImports.d.ts +1 -0
  34. package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
  35. package/dist/babelPlugins/BabelTransformPackageImports.js +75 -15
  36. package/dist/bin/args.d.ts +11 -0
  37. package/dist/bin/args.d.ts.map +1 -0
  38. package/dist/bin/args.js +77 -0
  39. package/dist/bin/commands/build.d.ts.map +1 -1
  40. package/dist/bin/commands/build.js +15 -4
  41. package/dist/bin/commands/buildHelp.js +4 -1
  42. package/dist/bin/commands/clean.d.ts.map +1 -1
  43. package/dist/bin/commands/clean.js +2 -0
  44. package/dist/bin/commands/compile.d.ts.map +1 -1
  45. package/dist/bin/commands/compile.js +73 -81
  46. package/dist/bin/commands/dev.d.ts.map +1 -1
  47. package/dist/bin/commands/dev.js +23 -8
  48. package/dist/bin/commands/devmon.d.ts.map +1 -1
  49. package/dist/bin/commands/devmon.js +26 -30
  50. package/dist/bin/commands/help.js +2 -2
  51. package/dist/bin/commands/start.d.ts.map +1 -1
  52. package/dist/bin/commands/start.js +2 -1
  53. package/dist/bin/commands/static.d.ts.map +1 -1
  54. package/dist/bin/commands/static.js +2 -0
  55. package/dist/bin/index.d.ts.map +1 -1
  56. package/dist/bin/index.js +35 -1
  57. package/dist/bin/moduleLoadAnalysis.d.ts +2 -0
  58. package/dist/bin/moduleLoadAnalysis.d.ts.map +1 -0
  59. package/dist/bin/moduleLoadAnalysis.js +243 -0
  60. package/dist/build/compileJavascript.d.ts +1 -0
  61. package/dist/build/compileJavascript.d.ts.map +1 -1
  62. package/dist/build/compileJavascript.js +67 -11
  63. package/dist/build/compileUserApp.d.ts +1 -1
  64. package/dist/build/compileUserApp.d.ts.map +1 -1
  65. package/dist/build/compileUserApp.js +4 -2
  66. package/dist/build/sassImportParser.d.ts.map +1 -1
  67. package/dist/build/sassImportParser.js +5 -2
  68. package/dist/build/tallyBabelPlugins.d.ts +3 -0
  69. package/dist/build/tallyBabelPlugins.d.ts.map +1 -0
  70. package/dist/build/tallyBabelPlugins.js +68 -0
  71. package/dist/componentCss/tungsten.css +2 -2
  72. package/dist/env/getEnvSchemas.d.ts +3 -3
  73. package/dist/env/getEnvSchemas.d.ts.map +1 -1
  74. package/dist/env/getEnvSchemas.js +9 -15
  75. package/dist/env/setPrivateEnvValidator.d.ts +2 -2
  76. package/dist/env/setPrivateEnvValidator.d.ts.map +1 -1
  77. package/dist/env/setPublicEnvValidator.d.ts +2 -2
  78. package/dist/env/setPublicEnvValidator.d.ts.map +1 -1
  79. package/dist/frameworkConfigSchema.d.ts +61 -192
  80. package/dist/frameworkConfigSchema.d.ts.map +1 -1
  81. package/dist/frameworkConfigSchema.js +49 -62
  82. package/dist/getUserConfig.d.ts.map +1 -1
  83. package/dist/getUserConfig.js +5 -5
  84. package/dist/moduleReloader.d.ts +1 -3
  85. package/dist/moduleReloader.d.ts.map +1 -1
  86. package/dist/moduleReloader.js +17 -15
  87. package/dist/standardSchema.d.ts +37 -0
  88. package/dist/standardSchema.d.ts.map +1 -0
  89. package/dist/standardSchema.js +47 -0
  90. package/dist/timeProfiler.d.ts +6 -0
  91. package/dist/timeProfiler.d.ts.map +1 -1
  92. package/dist/timeProfiler.js +253 -10
  93. package/package.json +3 -4
@@ -14,7 +14,7 @@ import precinct from 'precinct';
14
14
  //import sxyPrecinct from 'sxy-precinct'
15
15
  import { existsSync, mkdirSync, promises as fsPromises, readFileSync, writeFileSync } from 'fs';
16
16
  import { createRequire } from 'module';
17
- import { transformFileAsync as babelTransformFileAsync, transformAsync as babelTransformAsync } from '@babel/core';
17
+ import { transformFileSync as babelTransformFileSync, transformSync as babelTransformSync } from '@babel/core';
18
18
  import babelPluginSyntaxImportAssertionsModule from '@babel/plugin-syntax-import-assertions';
19
19
  import { arrays } from 'sxy-lib';
20
20
  const babelPluginSyntaxImportAssertions = babelPluginSyntaxImportAssertionsModule.default;
@@ -32,6 +32,7 @@ import { isBareImport } from "./isBareImport.js";
32
32
  import { resolveBareImport } from "./transformPackageImports/resolveBareImport.js";
33
33
  import { TungstenError } from "../TungstenError.js";
34
34
  import { throwError } from "../throwError.js";
35
+ import { isProfiling, tally } from "../timeProfiler.js";
35
36
  //import { copyPackageModulesToPackagesFolder } from '../build/copyPackageModulesToPackagesFolder.js'
36
37
  const require = createRequire(import.meta.url);
37
38
  function debug(...texts) {
@@ -209,9 +210,11 @@ export function MakeBabelTransformPackageImports(opts) {
209
210
  const {
210
211
  file
211
212
  } = fileInfo;
213
+ const precinctStart = performance.now();
212
214
  const precinctResults = precinct.paperwork(file, {
213
215
  includeCore: false
214
216
  });
217
+ tally('precinct.paperwork', performance.now() - precinctStart);
215
218
  const deps = precinctResults.map(d => {
216
219
  //console.log('getFileDependencies', 'd', d)
217
220
  return isBareImport(d) ? applyImportAliases(d, fileInfo.type) : d;
@@ -343,7 +346,11 @@ export function MakeBabelTransformPackageImports(opts) {
343
346
  };
344
347
  }
345
348
  async function babelFileEsm(file /*, packageInfo*/, nonEsmDeps, onStylesheet) {
346
- const result = await babelTransformFileAsync(file, makeModifyImportsEsmBabelOptions(nonEsmDeps.map(d => d.webLocation), onStylesheet));
349
+ // Sync, so it can be tallied: these run concurrently in the deferred package work, where a
350
+ // wall clock around an async call would count the same time once per file in flight.
351
+ const esmStart = performance.now();
352
+ const result = babelTransformFileSync(file, makeModifyImportsEsmBabelOptions(nonEsmDeps.map(d => d.webLocation), onStylesheet));
353
+ tally('babel transform (packages)', performance.now() - esmStart);
347
354
  if (result === null) {
348
355
  throw new TungstenError(`BabelTransformPackageImports error: sub file babel transform (1) returned null: ${file}`);
349
356
  }
@@ -377,7 +384,9 @@ export function MakeBabelTransformPackageImports(opts) {
377
384
  async function babelFileCjs(file, cacheDest, finalDest, packageInfo, nonEsmDeps) {
378
385
  //console.log(chalk.yellow`cjs file ${file}`)
379
386
  const nonEsmDepsWebPath = nonEsmDeps.map(fi => fi.webLocation);
380
- let result = await babelTransformFileAsync(file, makeCjsToEsmBabelOptions(nonEsmDepsWebPath));
387
+ const cjsStart = performance.now();
388
+ let result = babelTransformFileSync(file, makeCjsToEsmBabelOptions(nonEsmDepsWebPath));
389
+ tally('babel transform (packages)', performance.now() - cjsStart);
381
390
  if (result === null) {
382
391
  throw new TungstenError(`BabelTransformPackageImports error: sub file babel transform (2) returned null: ${file}`);
383
392
  }
@@ -390,10 +399,12 @@ export function MakeBabelTransformPackageImports(opts) {
390
399
  });
391
400
  //console.log('writing file', intermedCacheLocation)
392
401
  await fsPromises.writeFile(intermedCacheLocation, result.code); // rel contains the slash at start
393
- result = await babelTransformAsync(result.code, {
402
+ const cjsImportsStart = performance.now();
403
+ result = babelTransformSync(result.code, {
394
404
  filename: file,
395
405
  ...makeModifyImportsCjsBabelOptions(nonEsmDepsWebPath)
396
406
  });
407
+ tally('babel transform (packages)', performance.now() - cjsImportsStart);
397
408
  if (result === null) {
398
409
  throw new TungstenError(`BabelTransformPackageImports error: sub file babel transform (3) returned null: ${file}
399
410
  `);
@@ -516,35 +527,64 @@ export function MakeBabelTransformPackageImports(opts) {
516
527
  // throw error
517
528
  // }
518
529
  }
530
+ // Guarded like processFile's processedFilesCache, and for the same reason: the walk below
531
+ // follows dependencies, which form cycles and diamonds. Without this a cycle never terminates
532
+ // and a shared dependency is copied once per path that reaches it.
533
+ const copiedFromCache = new Set();
519
534
  async function copyFileFromCache(fileIdentifier, cacheNode) {
520
- //
535
+ if (copiedFromCache.has(fileIdentifier)) return;
536
+ copiedFromCache.add(fileIdentifier);
521
537
  await fsPromises.mkdir(dirname(cacheNode.finalLocation), {
522
538
  recursive: true
523
539
  });
524
540
  await fsPromises.copyFile(cacheNode.cacheLocation, cacheNode.finalLocation);
525
541
  if (cacheNode.dependencies) {
526
- processCacheDependencies(cacheNode.dependencies);
542
+ await processCacheDependencies(cacheNode.dependencies);
527
543
  }
528
544
  addToDependencyGraphFromCacheNode(fileIdentifier, cacheNode, cacheNode.dependencies);
529
545
  }
546
+ // Only reached for a subtree cachedSubtreeIsUsable() already accepted, so both failures below
547
+ // mean the cache changed during the build. Recompiling from here is not possible - an identifier
548
+ // cannot be resolved back to a source file - and a half-copied package is worse than stopping.
530
549
  async function processCacheDependencies(dependencies) {
531
550
  for (const dep of dependencies) {
532
551
  const cacheNode = opts.oldDependencyGraph[dep];
533
552
  if (cacheNode === undefined) {
534
- throw new TungstenError(`processCacheDependencies() Error: ${dep} not found in existing dependency graph`);
553
+ throw new TungstenError(`processCacheDependencies() Error: ${dep} left the package cache mid-build.` + ` Delete ${packagesCacheLocation} and run again`);
535
554
  }
536
- //const cacheNode = opts.oldDependencyGraph[dep]
537
555
  if (existsSync(cacheNode.originalLocation)) {
538
556
  await copyFileFromCache(dep, cacheNode);
539
557
  } else {
540
- throw new TungstenError(`processCacheDependencies() Error: source file ${cacheNode.originalLocation}` + ` for ${dep} does not exist.` + ` Perhaps the package version was increased, and we need to process anew`);
541
- // find the import/require specification
542
- // resolve with sxy-filing-cabinet
543
- // resolveFile
544
- // processFile
558
+ throw new TungstenError(`processCacheDependencies() Error: source file ${cacheNode.originalLocation}` + ` for ${dep} was removed mid-build.` + ` Delete ${packagesCacheLocation} and run again`);
545
559
  }
546
560
  }
547
561
  }
562
+ // A cached file is only reusable if its whole dependency closure is too: processCacheDependencies
563
+ // walks that closure from identifiers alone and has no way to resolve one back to a source file
564
+ // to compile instead, so the entire subtree is checked before any of it is used.
565
+ const subtreeUsable = new Map();
566
+ function cachedSubtreeIsUsable(fileIdentifier, visiting) {
567
+ const settled = subtreeUsable.get(fileIdentifier);
568
+ if (settled !== undefined) return settled;
569
+ // a cycle is taken as usable here, so the rest of the walk decides the subtree
570
+ if (visiting.has(fileIdentifier)) return true;
571
+ const node = opts.oldDependencyGraph[fileIdentifier];
572
+ visiting.add(fileIdentifier);
573
+ // Profiling serves linked packages from the cache too, so a run measures what a real app
574
+ // pays rather than recompiling a linked framework's every file. The cache key is the
575
+ // version, which a linked package changes underneath, so a profiled run can compile stale
576
+ // code after that package is rebuilt until the packages cache is deleted - it warns.
577
+ const usable = node !== undefined && (isProfiling() || cacheKeyIdentifiesContent(node.originalLocation)) && existsSync(node.originalLocation) && existsSync(node.cacheLocation) && (node.dependencies ?? []).every(dep => cachedSubtreeIsUsable(dep, visiting));
578
+ visiting.delete(fileIdentifier);
579
+ subtreeUsable.set(fileIdentifier, usable);
580
+ return usable;
581
+ }
582
+ // The cache key carries the package version, which is only a fingerprint of the content for
583
+ // something installed. A linked package changes underneath an unchanged version, and the graph
584
+ // records no mtime or hash to catch that, so those are always compiled afresh.
585
+ function cacheKeyIdentifiesContent(originalLocation) {
586
+ return slash(originalLocation).includes('/node_modules/');
587
+ }
548
588
  const processedFilesCache = {};
549
589
  async function processFile(fileInfo, sourceFilename, history) {
550
590
  const {
@@ -554,7 +594,12 @@ export function MakeBabelTransformPackageImports(opts) {
554
594
  return processedFilesCache[file];
555
595
  } else {
556
596
  processedFilesCache[file] = fileInfo.packageInfo;
557
- await doProcessFile(fileInfo, sourceFilename, history);
597
+ const cacheNode = opts.oldDependencyGraph[fileInfo.fileIdentifier];
598
+ if (cacheNode !== undefined && cachedSubtreeIsUsable(fileInfo.fileIdentifier, new Set())) {
599
+ await copyFileFromCache(fileInfo.fileIdentifier, cacheNode);
600
+ } else {
601
+ await doProcessFile(fileInfo, sourceFilename, history);
602
+ }
558
603
  return processedFilesCache[file];
559
604
  }
560
605
  }
@@ -725,7 +770,22 @@ export function MakeBabelTransformPackageImports(opts) {
725
770
  // const location = source.value
726
771
  // }
727
772
  // }
773
+ // Resolution is this visitor's whole cost - ~2ms of node_modules walking per bare import -
774
+ // and the same specifier from the same directory resolves identically within a run
775
+ // (tungsten/jsx-runtime, from every component). Keyed on the importer's directory because
776
+ // both resolvers start their walk there. Lives as long as the plugin, like
777
+ // importedPackageFiles, so a package changed in place mid-session is stale in both.
778
+ const resolvedImports = new Map();
779
+ function resolveImportTarget(location, fromDir, requireFrom) {
780
+ const key = `${fromDir}\0${location}`;
781
+ const known = resolvedImports.get(key);
782
+ if (known !== undefined) return known;
783
+ const target = resolveBareImport(location, [fromDir]) ?? requireFrom.resolve(location);
784
+ resolvedImports.set(key, target);
785
+ return target;
786
+ }
728
787
  return {
788
+ name: 'BabelTransformPackageImports',
729
789
  pre(state) {
730
790
  const stateFilename = state.opts.filename;
731
791
  if (typeof stateFilename !== 'string') {
@@ -766,7 +826,7 @@ export function MakeBabelTransformPackageImports(opts) {
766
826
  // the real file location of the file being imported.
767
827
  // User code is served as ESM, so it resolves with the 'import'
768
828
  // condition - see resolvePrecinctDependencies()
769
- const importTarget = resolveBareImport(location, [dirname(filename)]) ?? require.resolve(location);
829
+ const importTarget = resolveImportTarget(location, dirname(filename), require);
770
830
  // Should do some checking here to show helpful errors about bad imports
771
831
  if (importTarget === location) {
772
832
  //console.log()
@@ -0,0 +1,11 @@
1
+ type Value<Spec> = Spec extends `${string} ${string}...` ? string[] | undefined : Spec extends `${string} ${string}` ? string | undefined : boolean;
2
+ type Values<Specs> = {
3
+ [Name in keyof Specs]: Value<Specs[Name]>;
4
+ };
5
+ type Options = {
6
+ handledElsewhere?: readonly string[];
7
+ help?: readonly string[];
8
+ };
9
+ export declare function cliArgs<const Specs extends Record<string, string>>(specs: Specs, options?: Options): Values<Specs>;
10
+ export {};
11
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/bin/args.ts"],"names":[],"mappings":"AAQA,KAAK,KAAK,CAAC,IAAI,IACX,IAAI,SAAS,GAAG,MAAM,IAAI,MAAM,KAAK,GAAG,MAAM,EAAE,GAAG,SAAS,GACtD,IAAI,SAAS,GAAG,MAAM,IAAI,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,GACnD,OAAO,CAAA;AAErB,KAAK,MAAM,CAAC,KAAK,IAAI;KAAG,IAAI,IAAI,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;CAAE,CAAA;AAElE,KAAK,OAAO,GAAG;IACX,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3B,CAAA;AAED,wBAAgB,OAAO,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9D,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,OAAY,GACtB,MAAM,CAAC,KAAK,CAAC,CA6Df"}
@@ -0,0 +1,77 @@
1
+ import { cliError, cliOut } from "../cliOutput.js";
2
+ export function cliArgs(specs, options = {}) {
3
+ // after node and the script come the command word and its arguments
4
+ const [command = '', ...given] = process.argv.slice(2);
5
+ const {
6
+ handledElsewhere = [],
7
+ help = []
8
+ } = options;
9
+ const accepted = [...Object.values(specs), ...handledElsewhere];
10
+ const usage = `Usage: tungsten ${command}` + accepted.map(part => ` [${part}]`).join('');
11
+ if (given.includes('--help') || given.includes('-h')) {
12
+ cliOut([usage, ...help].join('\n'));
13
+ process.exit(0);
14
+ }
15
+ // a declaration rather than an arrow, so the checks below narrow past its never return
16
+ function fail(message) {
17
+ cliError(`tungsten ${command}: ${message}`);
18
+ cliOut(usage);
19
+ process.exit(1);
20
+ }
21
+ const shapes = Object.values(specs).map(shapeOf);
22
+ const shapeByFlag = new Map(shapes.map(shape => [shape.flag, shape]));
23
+ const flagsGiven = new Set();
24
+ const valuesGiven = new Map();
25
+ const unknown = [];
26
+ const remaining = given.values();
27
+ for (const arg of remaining) {
28
+ const [flag, inlineValue] = splitInlineValue(arg);
29
+ const shape = shapeByFlag.get(flag);
30
+ if (shape === undefined) {
31
+ if (!handledElsewhere.includes(flag)) unknown.push(arg);
32
+ continue;
33
+ }
34
+ if (!shape.takesValue) {
35
+ if (inlineValue !== undefined) fail(`${flag} takes no value`);
36
+ flagsGiven.add(flag);
37
+ continue;
38
+ }
39
+ // "--output dist" takes its value from the same iteration, so the loop never sees it as a flag
40
+ const value = inlineValue ?? remaining.next().value;
41
+ // "--output --watch" is a value left out, not a value named --watch
42
+ if (value === undefined || value.startsWith('--')) fail(`${flag} requires a value`);
43
+ valuesGiven.set(flag, [...(valuesGiven.get(flag) ?? []), value]);
44
+ }
45
+ if (unknown.length > 0) fail(`unknown argument${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`);
46
+ const parsed = {};
47
+ for (const [name, spec] of Object.entries(specs)) {
48
+ const {
49
+ flag,
50
+ takesValue,
51
+ repeats
52
+ } = shapeOf(spec);
53
+ if (takesValue) {
54
+ const values = valuesGiven.get(flag);
55
+ parsed[name] = repeats ? values : values?.at(-1);
56
+ } else {
57
+ // --no-clean names what it turns off, so it reads true until given
58
+ const on = flagsGiven.has(flag);
59
+ parsed[name] = flag.startsWith('--no-') ? !on : on;
60
+ }
61
+ }
62
+ return parsed; // eslint-disable-line no-type-assertion/no-type-assertion -- built from specs' own keys
63
+ }
64
+ // '--ignore-dirs <dir>...' -> its flag, that it takes a value, and that it may be given more than once
65
+ function shapeOf(spec) {
66
+ const space = spec.indexOf(' ');
67
+ return {
68
+ flag: space === -1 ? spec : spec.slice(0, space),
69
+ takesValue: space !== -1,
70
+ repeats: spec.endsWith('...')
71
+ };
72
+ }
73
+ // '--output=dist' -> ['--output', 'dist'], '--quiet' -> ['--quiet', undefined]
74
+ function splitInlineValue(arg) {
75
+ const equals = arg.indexOf('=');
76
+ return equals === -1 ? [arg, undefined] : [arg.slice(0, equals), arg.slice(equals + 1)];
77
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/build.ts"],"names":[],"mappings":"AA2DA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/build.ts"],"names":[],"mappings":"AAqEA,OAAO,EAAE,CAAA"}
@@ -3,10 +3,20 @@ import chalk from 'chalk-extensions';
3
3
  import { Framework } from "../../Framework.js";
4
4
  import { cliOut, cliNews, cliWarn } from "../../cliOutput.js";
5
5
  import { staticSiteFolder } from "../../settings.js";
6
- import { createTimeProfiler } from "../../timeProfiler.js";
7
- const args = process.argv.slice(3);
8
- const profile = args.includes('--profile');
9
- const quiet = args.includes('--quiet');
6
+ import { createTimeProfiler, reportRunTotal, reportStartupPhase } from "../../timeProfiler.js";
7
+ import { cliArgs } from "../args.js";
8
+ // first statement in the body, so it closes off the import evaluation above
9
+ reportStartupPhase('load build modules');
10
+ // --help never reaches here - bin/index.ts routes it to buildHelp.ts before this module loads
11
+ const {
12
+ profile,
13
+ quiet
14
+ } = cliArgs({
15
+ profile: '--profile',
16
+ quiet: '--quiet'
17
+ }, {
18
+ handledElsewhere: ['--profile-modules']
19
+ });
10
20
  //// prep the app
11
21
  const sxyFramework = new Framework();
12
22
  sxyFramework.profiler = createTimeProfiler(profile);
@@ -32,6 +42,7 @@ if (sxyFramework.config.staticSite) {
32
42
  } else if (!quiet) {
33
43
  cliNews(`Production build complete`);
34
44
  }
45
+ reportRunTotal('total (build)');
35
46
  // The build no longer holds anything open, so it ends by draining the event loop - hard-exiting
36
47
  // here used to abort the process outright when an fs-extra copy still had a chmod in flight
37
48
  // (libuv `!(handle->flags & UV_HANDLE_CLOSING)`). Safety net only, in case a future change
@@ -1,7 +1,10 @@
1
1
  // kept out of build.ts so --help does not pay for resolving the build's import tree
2
2
  import { cliOut } from "../../cliOutput.js";
3
- cliOut(`Usage: tungsten build [--profile] [--quiet]
3
+ cliOut(`Usage: tungsten build [--profile] [--profile-modules] [--quiet]
4
4
 
5
5
  Options:
6
6
  --profile report the time taken by each build phase
7
+ --profile-modules[=<path substring>]
8
+ report where module load time goes, splitting each import into what
9
+ only it needs and what it shares with its siblings
7
10
  --quiet suppress progress output; warnings and errors still print`);
@@ -1 +1 @@
1
- {"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/clean.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/clean.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,CAAA"}
@@ -3,6 +3,8 @@ import chalk from 'chalk-extensions';
3
3
  import { Framework } from "../../Framework.js";
4
4
  import { cliOut } from "../../cliOutput.js";
5
5
  import { packageNameCapitalized } from "../../settings.js";
6
+ import { cliArgs } from "../args.js";
7
+ cliArgs({}); // accepts nothing but --help
6
8
  //// prep the app
7
9
  const sxyFramework = new Framework();
8
10
  await sxyFramework.init();
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/compile.ts"],"names":[],"mappings":"AAkSA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/compile.ts"],"names":[],"mappings":"AAoRA,OAAO,EAAE,CAAA"}
@@ -11,92 +11,80 @@ import { createInstanceConfigFile } from "../../build/createInstanceConfigFile.j
11
11
  import { getUserConfig } from "../../getUserConfig.js";
12
12
  import { buildCodeSubPath, assetsFolder, clientFolder, componentCssFolder, devBuildOuterFolder, frameworkCssFolder, packagesCacheFolder, packagesFolder, publicFolder, staticFolder } from "../../settings.js";
13
13
  import { cliError, cliNews, cliOut } from "../../cliOutput.js";
14
- const targetOptions = {
15
- '--js': 'js',
16
- '--css': 'css',
17
- '--client': 'client',
18
- '--assets': 'assets',
19
- '--public': 'public'
14
+ import { createTimeProfiler, reportRunTotal, reportStartupPhase } from "../../timeProfiler.js";
15
+ import { cliArgs } from "../args.js";
16
+ // first statement in the body, so it closes off the import evaluation above
17
+ reportStartupPhase('load compile modules');
18
+ const {
19
+ js,
20
+ css,
21
+ client,
22
+ assets,
23
+ publicFiles,
24
+ all,
25
+ watch,
26
+ clean,
27
+ quiet,
28
+ packages,
29
+ profile,
30
+ output: outputOption,
31
+ readyFile: readyFileOption,
32
+ ignoreDirs,
33
+ ignorePatterns,
34
+ tests
35
+ } = cliArgs({
36
+ js: '--js',
37
+ css: '--css',
38
+ client: '--client',
39
+ assets: '--assets',
40
+ publicFiles: '--public',
41
+ all: '--all',
42
+ watch: '--watch',
43
+ clean: '--no-clean',
44
+ quiet: '--quiet',
45
+ packages: '--packages',
46
+ profile: '--profile',
47
+ output: '--output <dir>',
48
+ readyFile: '--ready-file <file>',
49
+ ignoreDirs: '--ignore-dirs <dir>...',
50
+ ignorePatterns: '--ignore-patterns <pattern>...',
51
+ tests: '--tests'
52
+ }, {
53
+ handledElsewhere: ['--profile-modules'],
54
+ help: ['With no target option (--js, --css, --client, --assets, --public), all targets are compiled.', ' --profile reports the time taken by each compile phase', ' --profile-modules[=<path substring>] reports where module load time goes,', ' splitting each import into what only it needs and what it shares', ' --packages compiles imported packages into the build\'s .packages tree', ' for the dev server to serve. Off by default: compiled code left to', ' node\'s own resolution, as tests use, needs none of it.', ' --ignore-dirs and --ignore-patterns override the config entries of the same name.', ' Repeat either to give several values, or pass an empty value to ignore nothing.', ' --tests also compiles the files the config\'s testPatterns match, which every', ' other compile leaves out.']
55
+ });
56
+ const allTargets = ['js', 'css', 'client', 'assets', 'public'];
57
+ const targetGiven = {
58
+ js,
59
+ css,
60
+ client,
61
+ assets,
62
+ public: publicFiles
20
63
  };
21
- const args = process.argv.slice(3);
22
- const usage = 'Usage: tungsten compile [targets] [--all] [--watch] [--no-clean] [--packages]' + ' [--output <dir>] [--ready-file <file>] [--quiet]' + ' [--ignore-dirs <dir>] [--ignore-patterns <pattern>]';
23
- if (args.includes('--help') || args.includes('-h')) {
24
- cliOut(usage);
25
- cliOut('Targets: --js, --css, --client, --assets, --public');
26
- cliOut('With no target options, all targets are compiled.');
27
- cliOut('Options: --output <dir>, --ready-file <file>, --quiet');
28
- cliOut(' --packages compiles imported packages into the build\'s .packages tree');
29
- cliOut(' for the dev server to serve. Off by default: compiled code left to');
30
- cliOut(' node\'s own resolution, as tests use, needs none of it.');
31
- cliOut(' --ignore-dirs <dir>, --ignore-patterns <pattern>');
32
- cliOut(' override the config entries of the same name. Repeat the option to');
33
- cliOut(' give several values, or pass an empty value to ignore nothing.');
34
- process.exit(0);
35
- }
36
- const commandOptions = new Set(['--all', '--watch', '--no-clean', '--quiet', '--packages']);
37
- const valueOptions = new Set(['--output', '--ready-file', '--ignore-dirs', '--ignore-patterns']);
38
- // values are collected per option so that the list options can be repeated
39
- const givenOptionValues = new Map();
40
- const unknownOptions = [];
41
- let consumedPosition = -1;
42
- for (const [position, arg] of args.entries()) {
43
- if (position === consumedPosition) continue; // already taken as an option value
44
- if (arg in targetOptions || commandOptions.has(arg)) continue;
45
- if (valueOptions.has(arg)) {
46
- const value = args[position + 1];
47
- if (value === undefined || value.startsWith('--')) {
48
- cliError(`${arg} requires a value`);
49
- process.exit(1);
50
- }
51
- const values = givenOptionValues.get(arg);
52
- if (values === undefined) givenOptionValues.set(arg, [value]);else values.push(value);
53
- consumedPosition = position + 1;
54
- } else {
55
- unknownOptions.push(arg);
56
- }
57
- }
58
- if (unknownOptions.length > 0) {
59
- cliError(`Unknown compile option${unknownOptions.length === 1 ? '' : 's'}: ${unknownOptions.join(', ')}`);
60
- cliOut(usage);
61
- process.exit(1);
62
- }
63
- function optionValue(option) {
64
- return givenOptionValues.get(option)?.at(-1);
65
- }
64
+ const requestedTargets = allTargets.filter(target => targetGiven[target]);
65
+ const targets = new Set(all || requestedTargets.length === 0 ? allTargets : requestedTargets);
66
66
  // undefined leaves the config entry alone, '' clears it
67
- function listOptionValue(option) {
68
- const values = givenOptionValues.get(option);
67
+ function configOverride(values) {
69
68
  if (values === undefined) return undefined;
70
69
  const entries = values.filter(value => value !== '');
71
70
  return entries.length === 0 ? '' : entries;
72
71
  }
73
- const outputOption = optionValue('--output');
74
- const readyFileOption = optionValue('--ready-file');
75
- const ignoreDirsOption = listOptionValue('--ignore-dirs');
76
- const ignorePatternsOption = listOptionValue('--ignore-patterns');
77
- const requestedTargets = new Set();
78
- for (const arg of args) {
79
- const target = targetOptions[arg];
80
- if (target !== undefined) requestedTargets.add(target);
81
- }
82
- const targets = requestedTargets.size === 0 || args.includes('--all') ? new Set(['js', 'css', 'client', 'assets', 'public']) : requestedTargets;
83
- const watch = args.includes('--watch');
84
- const clean = !args.includes('--no-clean');
85
- const quiet = args.includes('--quiet');
86
- const compilePackages = args.includes('--packages');
87
- if (compilePackages && !targets.has('js')) {
72
+ const ignoreDirsOption = configOverride(ignoreDirs);
73
+ const ignorePatternsOption = configOverride(ignorePatterns);
74
+ const profiler = createTimeProfiler(profile);
75
+ if (packages && !targets.has('js')) {
88
76
  cliError('--packages applies to the js target, which is not being compiled');
89
77
  process.exit(1);
90
78
  }
91
79
  const projectDir = process.cwd();
92
- const config = await getUserConfig({
80
+ const config = await profiler.timeProfileAsync('init', () => getUserConfig({
93
81
  ...(ignoreDirsOption !== undefined ? {
94
82
  ignoreDirs: ignoreDirsOption
95
83
  } : {}),
96
84
  ...(ignorePatternsOption !== undefined ? {
97
85
  ignorePatterns: ignorePatternsOption
98
86
  } : {})
99
- });
87
+ }));
100
88
  const codeLocation = join(projectDir, config.codeDir);
101
89
  const relSourceDir = config.sourceDir !== undefined && config.sourceDir !== '' ? config.sourceDir : config.codeDir;
102
90
  const sourceLocation = join(projectDir, relSourceDir);
@@ -144,14 +132,15 @@ if (clean) {
144
132
  if (targets.has('assets')) cleanTasks.push(removeRecursive(join(buildLocation, assetsFolder)));
145
133
  if (targets.has('public')) cleanTasks.push(removeRecursive(join(buildLocation, publicFolder)));
146
134
  cliOut(`Cleaning compile output: ${[...targets].join(', ')}`);
147
- await Promise.all(cleanTasks);
135
+ await profiler.timeProfileAsync('clean', () => Promise.all(cleanTasks));
148
136
  }
149
137
  const compilationTasks = [];
150
138
  let javascriptInitialSuccess = true;
151
139
  if (targets.has('js')) {
152
- const javascriptTask = compileJavascript(projectDir, config, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, 'dev', watch, {
140
+ const javascriptTask = profiler.timeProfileAsync('compile javascript', () => compileJavascript(projectDir, config, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, 'dev', watch, {
153
141
  quiet,
154
- packages: compilePackages,
142
+ packages,
143
+ tests,
155
144
  packagesCacheLocation,
156
145
  onCycleComplete: watch ? async cycle => {
157
146
  if (cycle.success) {
@@ -162,29 +151,31 @@ if (targets.has('js')) {
162
151
  cliError(`Compile cycle failed: js${cycle.file !== undefined ? ` (${cycle.file})` : ''}`);
163
152
  }
164
153
  } : undefined
165
- });
154
+ }));
166
155
  compilationTasks.push(javascriptTask.then(result => {
167
156
  javascriptInitialSuccess = result.initialSuccess;
168
157
  return result;
169
158
  }));
170
159
  }
171
160
  if (targets.has('css')) {
172
- compilationTasks.push(compileStyles(projectDir, config, styleImportBase, buildLocation, watch));
161
+ compilationTasks.push(profiler.timeProfileAsync('compile styles', () => compileStyles(projectDir, config, styleImportBase, buildLocation, watch)));
173
162
  }
174
163
  if (targets.has('client')) {
175
- compilationTasks.push(createInstanceConfigFile(config, 'dev', buildLocation));
176
- compilationTasks.push(createClientFiles(config, 'dev', buildLocation));
164
+ compilationTasks.push(profiler.timeProfileAsync('create instance config', () => createInstanceConfigFile(config, 'dev', buildLocation)));
165
+ compilationTasks.push(profiler.timeProfileAsync('create client files', () => createClientFiles(config, 'dev', buildLocation)));
177
166
  }
178
167
  if (targets.has('assets')) {
179
168
  // copyAssets resolves to undefined when there is no assets dir - nothing to skip here
180
- compilationTasks.push(copyAssets(projectDir, config, buildLocation, watch));
169
+ compilationTasks.push(profiler.timeProfileAsync('copy assets', () => copyAssets(projectDir, config, buildLocation, watch)));
181
170
  }
182
171
  if (targets.has('public')) {
183
- compilationTasks.push(copyPublicFiles(projectDir, config, buildLocation, watch));
172
+ compilationTasks.push(profiler.timeProfileAsync('copy public files', () => copyPublicFiles(projectDir, config, buildLocation, watch)));
184
173
  }
185
- const compilationResults = await Promise.allSettled(compilationTasks);
174
+ const compilationResults = await profiler.timeProfileAsync('compile targets', () => Promise.allSettled(compilationTasks));
186
175
  // after the js task: the package walk that records the stylesheets runs inside it
187
- if (targets.has('js')) await writeFrameworkCss(buildLocation);
176
+ if (targets.has('js')) {
177
+ await profiler.timeProfileAsync('write framework css', () => writeFrameworkCss(buildLocation));
178
+ }
188
179
  const failures = compilationResults.filter(result => result.status === 'rejected').map(result => result.reason);
189
180
  const initialSuccess = failures.length === 0 && javascriptInitialSuccess;
190
181
  if (initialSuccess) {
@@ -194,6 +185,7 @@ if (initialSuccess) {
194
185
  await clearReadyFile();
195
186
  cliError(`Compile failed: ${[...targets].join(', ')}`);
196
187
  }
188
+ reportRunTotal('total (compile)');
197
189
  if (!watch && !initialSuccess) {
198
190
  throw new AggregateError(failures, 'Compilation failed');
199
191
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/dev.ts"],"names":[],"mappings":"AAsDA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/dev.ts"],"names":[],"mappings":"AAuEA,OAAO,EAAE,CAAA"}
@@ -2,16 +2,29 @@ import open from 'open';
2
2
  import { cliOut } from "../../cliOutput.js";
3
3
  import { Framework } from "../../Framework.js";
4
4
  import { warnOnFrameworkChange } from "../../warnOnFrameworkChange.js";
5
- import { createTimeProfiler } from "../../timeProfiler.js";
5
+ import { createTimeProfiler, reportRunTotal, reportStartupPhase } from "../../timeProfiler.js";
6
+ import { cliArgs } from "../args.js";
6
7
  //import dotenv from 'dotenv'
7
8
  //dotenv.config({ path: '.private.env' })
8
- const args = process.argv.slice(3);
9
- const boot = !args.includes('--no-boot');
10
- const profile = args.includes('--profile');
9
+ // first statement in the body, so it closes off the import evaluation above
10
+ reportStartupPhase('load dev modules');
11
+ const {
12
+ boot,
13
+ profile,
14
+ tests
15
+ } = cliArgs({
16
+ boot: '--no-boot',
17
+ profile: '--profile',
18
+ // also compile the files config.testDirs/testPatterns match - normally only a test compile does
19
+ tests: '--tests'
20
+ }, {
21
+ handledElsewhere: ['--profile-modules']
22
+ });
11
23
  //// prep the app
12
24
  const sxyFramework = new Framework();
13
25
  sxyFramework.profiler = createTimeProfiler(profile);
14
- await sxyFramework.init();
26
+ sxyFramework.tests = tests;
27
+ const config = await sxyFramework.init();
15
28
  //console.log('framework ready')
16
29
  // build for dev
17
30
  cliOut('Removing old builds');
@@ -26,12 +39,14 @@ const devServer = await sxyFramework.createDevServer();
26
39
  const devServerLocation = await sxyFramework.profiler.timeProfileAsync('start dev server', () => devServer.start());
27
40
  // devmon already restarts this process itself on a framework rebuild, so the warning
28
41
  // (which just tells you to restart manually) would be redundant noise under it - see devmon.ts.
29
- if (sxyFramework.config?.warnOnFrameworkChange && process.env.__TUNGSTEN_DEVMON !== 'true') {
42
+ if (config.warnOnFrameworkChange && process.env.__TUNGSTEN_DEVMON !== 'true') {
30
43
  warnOnFrameworkChange();
31
44
  }
32
45
  // do a dummy request to trigger any error immediately during dev
33
46
  //axios.get(sxyFramework.devServer.location)
34
47
  if (boot) {
35
48
  cliOut('Booting to the dev site in your browser');
36
- open(devServerLocation);
37
- }
49
+ // awaited only so it can be timed - open resolves once the browser process is spawned
50
+ await sxyFramework.profiler.timeProfileAsync('boot browser', () => open(devServerLocation));
51
+ }
52
+ reportRunTotal('total (dev startup)');
@@ -1 +1 @@
1
- {"version":3,"file":"devmon.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/devmon.ts"],"names":[],"mappings":"AAqEA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"devmon.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/devmon.ts"],"names":[],"mappings":"AAiEA,OAAO,EAAE,CAAA"}