tungsten 0.4.8 → 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.
- package/dist/.build-complete +1 -1
- package/dist/Framework.d.ts +1 -0
- package/dist/Framework.d.ts.map +1 -1
- package/dist/Framework.js +30 -20
- package/dist/FrameworkConfig.d.ts +4 -0
- package/dist/FrameworkConfig.d.ts.map +1 -1
- package/dist/Servers/DevServer.d.ts +0 -1
- package/dist/Servers/DevServer.d.ts.map +1 -1
- package/dist/Servers/DevServer.js +2 -1
- package/dist/Servers/hono/TungstenServerHono.d.ts +1 -1
- package/dist/Servers/hono/TungstenServerHono.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.js +13 -20
- package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts +6 -6
- package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts.map +1 -1
- package/dist/Servers/tungstenCorePrivateEnvSchema.js +12 -22
- package/dist/Servers/tungstenCorePublicEnvSchema.d.ts +7 -7
- package/dist/Servers/tungstenCorePublicEnvSchema.d.ts.map +1 -1
- package/dist/Servers/tungstenCorePublicEnvSchema.js +6 -7
- package/dist/api/getApiRoutesHono.d.ts +1 -1
- package/dist/api/getApiRoutesHono.d.ts.map +1 -1
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +1 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -1
- package/dist/babelPlugins/BabelAddDevCacheBust.js +1 -0
- package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformComponentStyles.js +4 -2
- package/dist/babelPlugins/BabelTransformCssImports.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformCssImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformCssImports.js +1 -0
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformJsxExtensionsToJs.js +1 -0
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformPackageImports.js +75 -15
- package/dist/bin/args.d.ts +11 -0
- package/dist/bin/args.d.ts.map +1 -0
- package/dist/bin/args.js +77 -0
- package/dist/bin/commands/build.d.ts.map +1 -1
- package/dist/bin/commands/build.js +15 -4
- package/dist/bin/commands/buildHelp.js +4 -1
- package/dist/bin/commands/clean.d.ts.map +1 -1
- package/dist/bin/commands/clean.js +2 -0
- package/dist/bin/commands/compile.d.ts.map +1 -1
- package/dist/bin/commands/compile.js +76 -75
- package/dist/bin/commands/dev.d.ts.map +1 -1
- package/dist/bin/commands/dev.js +23 -8
- package/dist/bin/commands/devmon.d.ts.map +1 -1
- package/dist/bin/commands/devmon.js +26 -30
- package/dist/bin/commands/help.js +2 -2
- package/dist/bin/commands/start.d.ts.map +1 -1
- package/dist/bin/commands/start.js +2 -1
- package/dist/bin/commands/static.d.ts.map +1 -1
- package/dist/bin/commands/static.js +2 -0
- package/dist/bin/index.d.ts.map +1 -1
- package/dist/bin/index.js +35 -1
- package/dist/bin/moduleLoadAnalysis.d.ts +2 -0
- package/dist/bin/moduleLoadAnalysis.d.ts.map +1 -0
- package/dist/bin/moduleLoadAnalysis.js +243 -0
- package/dist/build/compileJavascript.d.ts +2 -0
- package/dist/build/compileJavascript.d.ts.map +1 -1
- package/dist/build/compileJavascript.js +71 -13
- package/dist/build/compileUserApp.d.ts +1 -1
- package/dist/build/compileUserApp.d.ts.map +1 -1
- package/dist/build/compileUserApp.js +4 -2
- package/dist/build/makeBabelUserDevConfig.d.ts +1 -1
- package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserDevConfig.js +7 -3
- package/dist/build/sassImportParser.d.ts.map +1 -1
- package/dist/build/sassImportParser.js +5 -2
- package/dist/build/tallyBabelPlugins.d.ts +3 -0
- package/dist/build/tallyBabelPlugins.d.ts.map +1 -0
- package/dist/build/tallyBabelPlugins.js +68 -0
- package/dist/env/getEnvSchemas.d.ts +3 -3
- package/dist/env/getEnvSchemas.d.ts.map +1 -1
- package/dist/env/getEnvSchemas.js +9 -15
- package/dist/env/setPrivateEnvValidator.d.ts +2 -2
- package/dist/env/setPrivateEnvValidator.d.ts.map +1 -1
- package/dist/env/setPublicEnvValidator.d.ts +2 -2
- package/dist/env/setPublicEnvValidator.d.ts.map +1 -1
- package/dist/frameworkConfigSchema.d.ts +61 -192
- package/dist/frameworkConfigSchema.d.ts.map +1 -1
- package/dist/frameworkConfigSchema.js +49 -62
- package/dist/getUserConfig.d.ts.map +1 -1
- package/dist/getUserConfig.js +5 -5
- package/dist/moduleReloader.d.ts +1 -3
- package/dist/moduleReloader.d.ts.map +1 -1
- package/dist/moduleReloader.js +17 -15
- package/dist/standardSchema.d.ts +37 -0
- package/dist/standardSchema.d.ts.map +1 -0
- package/dist/standardSchema.js +47 -0
- package/dist/timeProfiler.d.ts +6 -0
- package/dist/timeProfiler.d.ts.map +1 -1
- package/dist/timeProfiler.js +253 -10
- package/package.json +3 -5
|
@@ -1,52 +1,48 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// instead of just warning that a restart is needed.
|
|
1
|
+
// Supervisor for `tungsten dev`: runs it as a child and watches the same build-notification marker
|
|
2
|
+
// file as warnOnFrameworkChange.ts (dist/.build-complete in the framework package). Where that just
|
|
3
|
+
// warns you to restart by hand, this kills the dev child on a framework rebuild and respawns it.
|
|
5
4
|
import { spawn } from 'node:child_process';
|
|
6
5
|
import { join } from 'node:path';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
8
7
|
import chokidar from 'chokidar';
|
|
9
8
|
import { cliError } from "../../cliOutput.js";
|
|
10
9
|
import { getFrameworkDir } from "../../getFrameworkDir.js";
|
|
11
|
-
|
|
10
|
+
import { cliArgs } from "../args.js";
|
|
11
|
+
// devmon reads none of its arguments itself - it passes them straight through to the dev child
|
|
12
|
+
cliArgs({}, {
|
|
13
|
+
handledElsewhere: ['--no-boot', '--profile', '--profile-modules']
|
|
14
|
+
});
|
|
12
15
|
const initialArgs = process.argv.slice(3);
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
+
// a restart must not reopen the browser tab, so it always carries --no-boot
|
|
17
|
+
const restartArgs = initialArgs.includes('--no-boot') ? initialArgs : [...initialArgs, '--no-boot'];
|
|
18
|
+
const binIndexPath = fileURLToPath(new URL('../index.js', import.meta.url));
|
|
16
19
|
const buildMarkerPath = join(getFrameworkDir(), 'dist', '.build-complete');
|
|
17
20
|
let child = null;
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
// tells the child's exit handler that we asked for it, rather than the run being over
|
|
22
|
+
let restartWanted = false;
|
|
23
|
+
function startDev(devArgs) {
|
|
20
24
|
// __TUNGSTEN_DEVMON tells dev.ts to skip warnOnFrameworkChange() - devmon is already
|
|
21
25
|
// restarting it, so that warning would just be confusing noise here.
|
|
22
|
-
child = spawn(process.execPath, [binIndexPath, 'dev', ...
|
|
26
|
+
child = spawn(process.execPath, [binIndexPath, 'dev', ...devArgs], {
|
|
23
27
|
stdio: 'inherit',
|
|
24
28
|
env: {
|
|
25
29
|
...process.env,
|
|
26
30
|
__TUNGSTEN_DEVMON: 'true'
|
|
27
31
|
}
|
|
28
32
|
});
|
|
29
|
-
child.
|
|
33
|
+
child.once('exit', code => {
|
|
30
34
|
child = null;
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
// nobody asked for this, so dev ended on its own - it crashed, or you quit it
|
|
36
|
+
if (!restartWanted) process.exit(code ?? 0);
|
|
37
|
+
restartWanted = false;
|
|
38
|
+
startDev(restartArgs);
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
|
-
function
|
|
37
|
-
if (
|
|
38
|
-
|
|
41
|
+
function restartOnRebuild() {
|
|
42
|
+
if (restartWanted) return; // one is already on its way down
|
|
43
|
+
restartWanted = true;
|
|
39
44
|
cliError('tungsten framework rebuilt - restarting tungsten dev');
|
|
40
|
-
|
|
41
|
-
restarting = false;
|
|
42
|
-
startDev(restartArgs);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
child.once('exit', () => {
|
|
46
|
-
restarting = false;
|
|
47
|
-
startDev(restartArgs);
|
|
48
|
-
});
|
|
49
|
-
child.kill();
|
|
45
|
+
child?.kill();
|
|
50
46
|
}
|
|
51
47
|
startDev(initialArgs);
|
|
52
48
|
const watcher = chokidar.watch(buildMarkerPath, {
|
|
@@ -56,10 +52,10 @@ const watcher = chokidar.watch(buildMarkerPath, {
|
|
|
56
52
|
pollInterval: 20
|
|
57
53
|
}
|
|
58
54
|
});
|
|
59
|
-
watcher.on('all',
|
|
55
|
+
watcher.on('all', restartOnRebuild);
|
|
60
56
|
function shutdown() {
|
|
61
57
|
void watcher.close();
|
|
62
|
-
|
|
58
|
+
child?.kill();
|
|
63
59
|
process.exit(0);
|
|
64
60
|
}
|
|
65
61
|
process.on('SIGINT', shutdown);
|
|
@@ -3,9 +3,9 @@ const command = process.argv[2];
|
|
|
3
3
|
cliOut(`Usage: tungsten <command> [options]
|
|
4
4
|
|
|
5
5
|
Commands:
|
|
6
|
-
dev Start dev mode [--no-boot] [--profile]
|
|
6
|
+
dev Start dev mode [--no-boot] [--profile] [--profile-modules]
|
|
7
7
|
devmon Dev mode, auto-restarts on framework rebuilds (same options as dev)
|
|
8
|
-
build Production build [--profile] [--quiet]
|
|
8
|
+
build Production build [--profile] [--profile-modules] [--quiet]
|
|
9
9
|
start Start production server
|
|
10
10
|
static Preview a static site build
|
|
11
11
|
clean Delete build assets
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/start.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/start.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Framework } from "../../Framework.js";
|
|
2
|
+
import { cliArgs } from "../args.js";
|
|
2
3
|
//import dotenv from 'dotenv'
|
|
3
4
|
//dotenv.config({ path: '.private.env' })
|
|
4
|
-
|
|
5
|
+
cliArgs({}); // accepts nothing but --help
|
|
5
6
|
//// prep the framework
|
|
6
7
|
const framework = new Framework();
|
|
7
8
|
await framework.init();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/static.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/static.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import open from 'open';
|
|
2
2
|
import { getUserConfig } from "../../getUserConfig.js";
|
|
3
3
|
import { StaticSiteServer } from "../../Servers/StaticSiteServer.js";
|
|
4
|
+
import { cliArgs } from "../args.js";
|
|
5
|
+
cliArgs({}); // accepts nothing but --help
|
|
4
6
|
const staticServer = new StaticSiteServer(await getUserConfig());
|
|
5
7
|
const staticServerLocation = await staticServer.start();
|
|
6
8
|
open(staticServerLocation);
|
package/dist/bin/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin/index.ts"],"names":[],"mappings":"AA6GA,OAAO,EAAE,CAAA"}
|
package/dist/bin/index.js
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import { enableCompileCache, flushCompileCache } from 'node:module';
|
|
1
2
|
import { cliOut, cliWarn } from "../cliOutput.js";
|
|
3
|
+
import { beginStartupProfiling } from "../timeProfiler.js";
|
|
2
4
|
// get command line args
|
|
3
5
|
const args = process.argv.slice(2);
|
|
4
6
|
const command = args[0];
|
|
7
|
+
const profile = args.includes('--profile');
|
|
8
|
+
// V8 bytecode cache for the command's import tree, which is compiled on the dynamic imports below.
|
|
9
|
+
// Measured at ~14% off importing Framework.js once warm; the first run after a miss is ~8% slower
|
|
10
|
+
// while it populates, so a single run straight after a rebuild looks like a regression.
|
|
11
|
+
// NODE_DISABLE_COMPILE_CACHE=1 turns it off (NODE_COMPILE_CACHE does not - it names the cache
|
|
12
|
+
// directory, so a value of 0 makes a folder called 0).
|
|
13
|
+
const compileCache = enableCompileCache();
|
|
14
|
+
// reported under --profile because "is it even on?" is otherwise only answerable by inspecting
|
|
15
|
+
// the cache directory's timestamps - status 1 is enabled, 2 already enabled, 3 disabled
|
|
16
|
+
if (profile) {
|
|
17
|
+
cliOut(`compile cache: status ${compileCache.status}` + ` ${compileCache.directory ?? compileCache.message ?? ''}`);
|
|
18
|
+
}
|
|
19
|
+
// hooks have to be registered before the command module - and so its import tree - is loaded
|
|
20
|
+
const profileModulesArg = args.find(arg => arg === '--profile-modules' || arg.startsWith('--profile-modules='));
|
|
21
|
+
if (profileModulesArg !== undefined) {
|
|
22
|
+
const {
|
|
23
|
+
registerModuleLoadAnalysis
|
|
24
|
+
} = await import("./moduleLoadAnalysis.js");
|
|
25
|
+
registerModuleLoadAnalysis(profileModulesArg.split('=')[1]);
|
|
26
|
+
}
|
|
5
27
|
switch (command) {
|
|
6
28
|
case undefined:
|
|
7
29
|
case '':
|
|
@@ -15,6 +37,7 @@ switch (command) {
|
|
|
15
37
|
case 'dev':
|
|
16
38
|
{
|
|
17
39
|
cliOut(`Starting Tungsten dev mode`);
|
|
40
|
+
beginStartupProfiling(profile);
|
|
18
41
|
// set env before loading the next file, to set the env before file imports
|
|
19
42
|
process.env.__TUNGSTEN_ENV = 'development';
|
|
20
43
|
await import("./commands/dev.js");
|
|
@@ -35,6 +58,7 @@ switch (command) {
|
|
|
35
58
|
}
|
|
36
59
|
// announce before the import: resolving the build's dependencies is a long silent wait
|
|
37
60
|
if (!args.includes('--quiet')) cliOut(`Starting Tungsten production build`);
|
|
61
|
+
beginStartupProfiling(profile);
|
|
38
62
|
process.env.__TUNGSTEN_ENV = 'production';
|
|
39
63
|
process.env.__TUNGSTEN_DEBUG = 'true';
|
|
40
64
|
await import("./commands/build.js");
|
|
@@ -61,6 +85,7 @@ switch (command) {
|
|
|
61
85
|
}
|
|
62
86
|
case 'compile':
|
|
63
87
|
{
|
|
88
|
+
beginStartupProfiling(profile);
|
|
64
89
|
process.env.__TUNGSTEN_ENV = 'development';
|
|
65
90
|
await import("./commands/compile.js");
|
|
66
91
|
break;
|
|
@@ -75,4 +100,13 @@ switch (command) {
|
|
|
75
100
|
cliWarn(`tungsten: unknown command "${command}" (run "tungsten --help" for usage)`);
|
|
76
101
|
process.exit(1);
|
|
77
102
|
}
|
|
78
|
-
}
|
|
103
|
+
}
|
|
104
|
+
// Node only writes the compile cache when the process exits, and dev/start are killed rather than
|
|
105
|
+
// exited - signals skip exit handling - so they would populate the cache and then throw it away,
|
|
106
|
+
// paying the cold cost on every run forever. The import above has returned by here, which for a
|
|
107
|
+
// server command means it has finished starting, so this persists what that startup compiled.
|
|
108
|
+
flushCompileCache();
|
|
109
|
+
// A profiled dev run has measured everything it will measure once the server is up and the report
|
|
110
|
+
// has printed. Exit rather than leave a server to be killed - after the flush above, which a kill
|
|
111
|
+
// would have skipped. (dev.ts itself must not exit: its import returning is what reaches here.)
|
|
112
|
+
if (profile && command === 'dev') process.exit(0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moduleLoadAnalysis.d.ts","sourceRoot":"","sources":["../../src/bin/moduleLoadAnalysis.ts"],"names":[],"mappings":"AAmCA,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA0B9E"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// --profile-modules: records the real import graph and per-module cost through node's module
|
|
2
|
+
// hooks. Timing a subsystem on its own overstates it - it pays for everything it pulls in,
|
|
3
|
+
// including what a sibling would have pulled in anyway - so this splits each subsystem's cost
|
|
4
|
+
// into what only it needs and what it shares, and names who it shares with.
|
|
5
|
+
import { registerHooks } from 'node:module';
|
|
6
|
+
import { performance } from 'node:perf_hooks';
|
|
7
|
+
import chalk from 'chalk-extensions';
|
|
8
|
+
import { cliOut } from "../cliOutput.js";
|
|
9
|
+
import { addEndOfRunReport } from "../timeProfiler.js";
|
|
10
|
+
const records = new Map();
|
|
11
|
+
// The user's app enters the graph through an iterationQueryString() import, so its first module
|
|
12
|
+
// arrives as an explicitly tagged specifier - and recording stops there. Past that point the cost
|
|
13
|
+
// is the app's own dependencies, which tungsten cannot do anything about. Matching the specifier
|
|
14
|
+
// rather than the resolved url makes this independent of the order the registered hooks run in.
|
|
15
|
+
const appModulePattern = /[?&]t-(?:instance|reload)\b/;
|
|
16
|
+
let stoppedAtApp = false;
|
|
17
|
+
function recordFor(url) {
|
|
18
|
+
const existing = records.get(url);
|
|
19
|
+
if (existing !== undefined) return existing;
|
|
20
|
+
const created = {
|
|
21
|
+
url,
|
|
22
|
+
importers: new Set(),
|
|
23
|
+
ms: 0
|
|
24
|
+
};
|
|
25
|
+
records.set(url, created);
|
|
26
|
+
return created;
|
|
27
|
+
}
|
|
28
|
+
export function registerModuleLoadAnalysis(pivotHint) {
|
|
29
|
+
registerHooks({
|
|
30
|
+
resolve(specifier, context, next) {
|
|
31
|
+
if (appModulePattern.test(specifier)) stoppedAtApp = true;
|
|
32
|
+
if (stoppedAtApp) return next(specifier, context);
|
|
33
|
+
const start = performance.now();
|
|
34
|
+
const result = next(specifier, context);
|
|
35
|
+
const entry = recordFor(result.url);
|
|
36
|
+
entry.ms += performance.now() - start;
|
|
37
|
+
if (context.parentURL !== undefined) entry.importers.add(context.parentURL);
|
|
38
|
+
return result;
|
|
39
|
+
},
|
|
40
|
+
load(url, context, next) {
|
|
41
|
+
if (stoppedAtApp) return next(url, context);
|
|
42
|
+
const start = performance.now();
|
|
43
|
+
const result = next(url, context);
|
|
44
|
+
recordFor(url).ms += performance.now() - start;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Printed where each command reports its run total - by then everything is loaded, and dev
|
|
49
|
+
// has its server up rather than waiting to be killed. process.on('exit') cannot do this: a
|
|
50
|
+
// watcher ended with ctrl-c dies to the signal and never runs exit handlers. Kept as a
|
|
51
|
+
// fallback anyway, for a command that ends before it reports a total.
|
|
52
|
+
addEndOfRunReport(() => reportOnce(pivotHint));
|
|
53
|
+
process.on('exit', () => reportOnce(pivotHint));
|
|
54
|
+
}
|
|
55
|
+
let reported = false;
|
|
56
|
+
function reportOnce(pivotHint) {
|
|
57
|
+
if (reported) return;
|
|
58
|
+
reported = true;
|
|
59
|
+
reportModuleLoadAnalysis(pivotHint);
|
|
60
|
+
}
|
|
61
|
+
function ms(value) {
|
|
62
|
+
return `${Math.round(value).toLocaleString()}ms`;
|
|
63
|
+
}
|
|
64
|
+
function shorten(url) {
|
|
65
|
+
const path = url.startsWith('file:///') ? url.slice('file:///'.length) : url;
|
|
66
|
+
const marker = path.lastIndexOf('node_modules/');
|
|
67
|
+
const name = marker !== -1 ? path.slice(marker + 'node_modules/'.length) : path.split('/').slice(-3).join('/');
|
|
68
|
+
return name.length > 46 ? `…${name.slice(-45)}` : name;
|
|
69
|
+
}
|
|
70
|
+
// the npm package a module belongs to - tungsten's own files and node builtins have none
|
|
71
|
+
function packageOf(url) {
|
|
72
|
+
const marker = url.lastIndexOf('node_modules/');
|
|
73
|
+
if (marker === -1) return undefined;
|
|
74
|
+
const [scope, scoped] = url.slice(marker + 'node_modules/'.length).split('/');
|
|
75
|
+
if (scope === undefined) return undefined;
|
|
76
|
+
return scope.startsWith('@') ? `${scope}/${scoped ?? ''}` : scope;
|
|
77
|
+
}
|
|
78
|
+
// What dropping a package would actually save: everything reachable from it that nothing else
|
|
79
|
+
// reaches - its own modules plus the transitive dependencies it alone pulls in. A package's total
|
|
80
|
+
// size is the wrong number for that, because most of a small package can be dependencies it
|
|
81
|
+
// shares with something you are keeping.
|
|
82
|
+
function reportPackageCosts(loaded) {
|
|
83
|
+
const children = childGraph();
|
|
84
|
+
// Only packages entered from outside node_modules - tungsten's own code - are candidates.
|
|
85
|
+
// Counting nested packages too would leave every one of them at zero, since its modules are
|
|
86
|
+
// always reachable from whichever package depends on it as well as from itself.
|
|
87
|
+
const entries = new Map();
|
|
88
|
+
for (const entry of loaded) {
|
|
89
|
+
const name = packageOf(entry.url);
|
|
90
|
+
if (name === undefined) continue;
|
|
91
|
+
if (![...entry.importers].some(importer => packageOf(importer) === undefined)) continue;
|
|
92
|
+
entries.set(name, [...(entries.get(name) ?? []), entry.url]);
|
|
93
|
+
}
|
|
94
|
+
const owners = new Map();
|
|
95
|
+
for (const [name, urls] of entries) {
|
|
96
|
+
const reached = new Set();
|
|
97
|
+
for (const url of urls) for (const found of reachableFrom(url, children)) reached.add(found);
|
|
98
|
+
for (const url of reached) owners.set(url, [...(owners.get(url) ?? []), name]);
|
|
99
|
+
}
|
|
100
|
+
const totals = new Map();
|
|
101
|
+
for (const [url, names] of owners) {
|
|
102
|
+
const cost = costOf(url);
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
const stat = totals.get(name) ?? {
|
|
105
|
+
exclusive: 0,
|
|
106
|
+
shared: 0,
|
|
107
|
+
modules: 0
|
|
108
|
+
};
|
|
109
|
+
if (names.length === 1) {
|
|
110
|
+
stat.exclusive += cost;
|
|
111
|
+
stat.modules += 1;
|
|
112
|
+
} else stat.shared += cost;
|
|
113
|
+
totals.set(name, stat);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
cliOut('');
|
|
117
|
+
cliOut(chalk.mediumgrey`packages imported directly` + chalk.mediumgrey` - "only it" is what dropping the package would save, its own` + chalk.mediumgrey` modules plus the dependencies nothing else pulls in`);
|
|
118
|
+
cliOut(' ' + chalk.mediumgrey`${'package'.padEnd(30)}` + ' ' + chalk.mediumgrey`${'only it'.padStart(9)}` + ' ' + chalk.mediumgrey`${'shared'.padStart(9)}`);
|
|
119
|
+
const ranked = [...totals].sort((a, b) => b[1].exclusive - a[1].exclusive).slice(0, 15);
|
|
120
|
+
for (const [name, stat] of ranked) {
|
|
121
|
+
cliOut(' ' + chalk.yellow`${name.padEnd(30)}` + ' ' + chalk.orange`${ms(stat.exclusive).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(stat.shared).padStart(9)}` + ' ' + chalk.mediumgrey`${stat.modules} modules only it needs`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function childGraph() {
|
|
125
|
+
const children = new Map();
|
|
126
|
+
for (const entry of records.values()) {
|
|
127
|
+
for (const importer of entry.importers) {
|
|
128
|
+
const existing = children.get(importer);
|
|
129
|
+
if (existing === undefined) children.set(importer, new Set([entry.url]));else existing.add(entry.url);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return children;
|
|
133
|
+
}
|
|
134
|
+
function reachableFrom(root, children) {
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
const stack = [root];
|
|
137
|
+
for (let url = stack.pop(); url !== undefined; url = stack.pop()) {
|
|
138
|
+
if (seen.has(url)) continue;
|
|
139
|
+
seen.add(url);
|
|
140
|
+
for (const child of children.get(url) ?? []) stack.push(child);
|
|
141
|
+
}
|
|
142
|
+
return seen;
|
|
143
|
+
}
|
|
144
|
+
// Framework.js is the interesting fork for dev/build; compile has no Framework, so fall back to
|
|
145
|
+
// the command module. --profile-modules=<substring> overrides both.
|
|
146
|
+
function choosePivot(pivotHint) {
|
|
147
|
+
const urls = [...records.keys()];
|
|
148
|
+
if (pivotHint !== undefined && pivotHint !== '') return urls.find(url => url.includes(pivotHint));
|
|
149
|
+
return urls.find(url => url.endsWith('/Framework.js')) ?? urls.find(url => url.includes('/commands/'));
|
|
150
|
+
}
|
|
151
|
+
function costOf(url) {
|
|
152
|
+
return records.get(url)?.ms ?? 0;
|
|
153
|
+
}
|
|
154
|
+
function reportModuleLoadAnalysis(pivotHint) {
|
|
155
|
+
// snapshot first: printing loads chalk's template module, which would otherwise land in the
|
|
156
|
+
// listing below but not in the count above it
|
|
157
|
+
const loaded = [...records.values()];
|
|
158
|
+
if (loaded.length === 0) return;
|
|
159
|
+
const recorded = loaded.reduce((total, entry) => total + entry.ms, 0);
|
|
160
|
+
cliOut('');
|
|
161
|
+
const scope = stoppedAtApp ? `, stopping where the user's app starts loading` : ``;
|
|
162
|
+
const summary = ` - ${loaded.length.toLocaleString()} modules, ${ms(recorded)} in resolve + read` + `${scope} (V8 compile and module evaluation are not covered)`;
|
|
163
|
+
cliOut(chalk.yellow`module load analysis` + chalk.mediumgrey`${summary}`);
|
|
164
|
+
// what bundling tungsten's own dist would collapse, as against what lives in node_modules
|
|
165
|
+
const own = loaded.filter(entry => !entry.url.startsWith('node:') && packageOf(entry.url) === undefined);
|
|
166
|
+
const ownMs = own.reduce((total, entry) => total + entry.ms, 0);
|
|
167
|
+
const dependencies = loaded.length - own.length;
|
|
168
|
+
cliOut(chalk.mediumgrey` tungsten's own files: ${own.length.toLocaleString()} modules, ${ms(ownMs)}` + ` - dependencies: ${dependencies.toLocaleString()} modules, ${ms(recorded - ownMs)}`);
|
|
169
|
+
cliOut('');
|
|
170
|
+
cliOut(chalk.mediumgrey`heaviest modules`);
|
|
171
|
+
const heaviest = [...loaded].sort((a, b) => b.ms - a.ms).slice(0, 12);
|
|
172
|
+
for (const entry of heaviest) {
|
|
173
|
+
cliOut(' ' + chalk.yellow`${shorten(entry.url).padEnd(46)}` + ' ' + chalk.orange`${ms(entry.ms).padStart(9)}` + ' ' + chalk.mediumgrey`${`${entry.importers.size} importer${entry.importers.size === 1 ? '' : 's'}`}`);
|
|
174
|
+
}
|
|
175
|
+
reportPackageCosts(loaded);
|
|
176
|
+
const pivot = choosePivot(pivotHint);
|
|
177
|
+
if (pivot === undefined) return;
|
|
178
|
+
const children = childGraph();
|
|
179
|
+
const roots = [...(children.get(pivot) ?? [])].filter(url => !url.startsWith('node:'));
|
|
180
|
+
if (roots.length === 0) return;
|
|
181
|
+
// which roots can reach each module - one root means the cost is that root's alone
|
|
182
|
+
const owners = new Map();
|
|
183
|
+
for (const root of roots) {
|
|
184
|
+
for (const url of reachableFrom(root, children)) {
|
|
185
|
+
if (url === pivot) continue;
|
|
186
|
+
const existing = owners.get(url);
|
|
187
|
+
if (existing === undefined) owners.set(url, [root]);else existing.push(root);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const stats = new Map(roots.map(root => [root, {
|
|
191
|
+
exclusive: 0,
|
|
192
|
+
shared: 0
|
|
193
|
+
}]));
|
|
194
|
+
const groups = new Map();
|
|
195
|
+
let attributed = 0;
|
|
196
|
+
for (const [url, ownerList] of owners) {
|
|
197
|
+
const cost = costOf(url);
|
|
198
|
+
attributed += cost;
|
|
199
|
+
for (const root of ownerList) {
|
|
200
|
+
const stat = stats.get(root);
|
|
201
|
+
if (stat === undefined) continue;
|
|
202
|
+
if (ownerList.length === 1) stat.exclusive += cost;else stat.shared += cost;
|
|
203
|
+
}
|
|
204
|
+
if (ownerList.length === 1) continue;
|
|
205
|
+
const key = [...ownerList].sort().join('\n');
|
|
206
|
+
const group = groups.get(key);
|
|
207
|
+
if (group === undefined) groups.set(key, {
|
|
208
|
+
roots: [...ownerList].sort(),
|
|
209
|
+
ms: cost,
|
|
210
|
+
modules: 1
|
|
211
|
+
});else {
|
|
212
|
+
group.ms += cost;
|
|
213
|
+
group.modules += 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
cliOut('');
|
|
217
|
+
cliOut(chalk.mediumgrey`direct imports of ${shorten(pivot)}` + chalk.mediumgrey` - "alone" is what that import would cost with no siblings loaded`);
|
|
218
|
+
cliOut(' ' + chalk.mediumgrey`${'import'.padEnd(46)}` + ' ' + chalk.mediumgrey`${'only it'.padStart(9)}` + ' ' + chalk.mediumgrey`${'shared'.padStart(9)}` + ' ' + chalk.mediumgrey`${'alone'.padStart(9)}`);
|
|
219
|
+
const ranked = roots.map(root => ({
|
|
220
|
+
root,
|
|
221
|
+
...(stats.get(root) ?? {
|
|
222
|
+
exclusive: 0,
|
|
223
|
+
shared: 0
|
|
224
|
+
})
|
|
225
|
+
})).sort((a, b) => b.exclusive + b.shared - (a.exclusive + a.shared));
|
|
226
|
+
for (const {
|
|
227
|
+
root,
|
|
228
|
+
exclusive,
|
|
229
|
+
shared
|
|
230
|
+
} of ranked.slice(0, 15)) {
|
|
231
|
+
cliOut(' ' + chalk.yellow`${shorten(root).padEnd(46)}` + ' ' + chalk.orange`${ms(exclusive).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(shared).padStart(9)}` + ' ' + chalk.mediumgrey`${ms(exclusive + shared).padStart(9)}`);
|
|
232
|
+
}
|
|
233
|
+
if (ranked.length > 15) cliOut(' ' + chalk.mediumgrey`+ ${ranked.length - 15} more`);
|
|
234
|
+
const outside = `${ms(recorded - attributed)} outside ${shorten(pivot)} (the cli entry and its own imports)`;
|
|
235
|
+
cliOut(' ' + chalk.mediumgrey`${outside}`);
|
|
236
|
+
const sharedGroups = [...groups.values()].sort((a, b) => b.ms - a.ms).slice(0, 8);
|
|
237
|
+
if (sharedGroups.length === 0) return;
|
|
238
|
+
cliOut('');
|
|
239
|
+
cliOut(chalk.mediumgrey`shared subtrees - counted once here, but in every sharer's "alone" above`);
|
|
240
|
+
for (const group of sharedGroups) {
|
|
241
|
+
cliOut(' ' + chalk.orange`${ms(group.ms).padStart(9)}` + ' ' + chalk.mediumgrey`${`${group.modules} modules`.padEnd(14)}` + ' ' + chalk.yellow`${group.roots.map(shorten).join(' + ')}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -15,8 +15,10 @@ export type JavascriptCompilationCycle = {
|
|
|
15
15
|
export type CompileJavascriptOptions = {
|
|
16
16
|
quiet?: boolean;
|
|
17
17
|
packagesCacheLocation?: string;
|
|
18
|
+
packages?: boolean;
|
|
18
19
|
hmr?: boolean;
|
|
19
20
|
onCycleComplete?: ((cycle: JavascriptCompilationCycle) => void | Promise<void>) | undefined;
|
|
21
|
+
tests?: boolean;
|
|
20
22
|
};
|
|
21
23
|
export declare function compileJavascript(projectDir: string, appConfig: FrameworkConfigComplete, codeLocation: string, sourceLocation: string, styleImportBase: string, buildLocation: string, buildCodeLocation: string, mode: Mode, watch?: boolean, options?: CompileJavascriptOptions): Promise<CompileJavascriptResult>;
|
|
22
24
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compileJavascript.d.ts","sourceRoot":"","sources":["../../src/build/compileJavascript.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compileJavascript.d.ts","sourceRoot":"","sources":["../../src/build/compileJavascript.ts"],"names":[],"mappings":"AAGA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAO/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAiBtC,KAAK,uBAAuB,GAAG;IAC3B,OAAO,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IAClC,WAAW,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAA;IACtC,aAAa,EAAE,aAAa,CAAA;IAC5B,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACnC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAE9B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,eAAe,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAA;IAE3F,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAoBD,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EACtD,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EACrE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,UAAO,EAC1E,OAAO,GAAE,wBAA6B,GACvC,OAAO,CAAC,uBAAuB,CAAC,CA+blC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, dirname } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
2
3
|
import chokidar from 'chokidar';
|
|
3
|
-
import {
|
|
4
|
+
import { transformFileSync as babelTransformFileSync } from '@babel/core';
|
|
4
5
|
import fs, { promises as fsPromises } from 'fs';
|
|
5
6
|
import slash from 'slash';
|
|
6
7
|
import serializeJavascript from 'serialize-javascript';
|
|
@@ -8,13 +9,30 @@ import { globby } from 'globby';
|
|
|
8
9
|
import { packagesCacheFolder } from "../settings.js";
|
|
9
10
|
import { makeBabelUserDevConfig } from "./makeBabelUserDevConfig.js";
|
|
10
11
|
import { makeBabelUserProdConfig } from "./makeBabelUserProdConfig.js";
|
|
11
|
-
import { cliOut, cliLog, cliError } from "../cliOutput.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
12
|
+
import { cliOut, cliLog, cliError, cliWarn } from "../cliOutput.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
13
|
+
import { createTimeProfiler, isProfiling, tally } from "../timeProfiler.js";
|
|
14
|
+
import { tallyBabelPlugins } from "./tallyBabelPlugins.js";
|
|
12
15
|
import { TungstenError } from "../TungstenError.js";
|
|
13
16
|
import { getFrameworkDir } from "../getFrameworkDir.js";
|
|
17
|
+
// The graph writeDependencyGraph() left behind on the previous run, naming a compiled copy of every
|
|
18
|
+
// package file in the packages cache. Missing or unreadable just means everything is compiled from
|
|
19
|
+
// source, which is what happened on every run before this was read back.
|
|
20
|
+
async function loadDependencyGraph(location) {
|
|
21
|
+
if (!fs.existsSync(location)) return {};
|
|
22
|
+
try {
|
|
23
|
+
const graphModule = await import(pathToFileURL(location).href);
|
|
24
|
+
return graphModule.graph ?? {};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
cliWarn(`Could not read the package cache at ${location}, compiling packages from source`);
|
|
27
|
+
cliLog(error);
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
14
31
|
///// needs work to work in prod mode
|
|
15
32
|
///// see babel config
|
|
16
33
|
export async function compileJavascript(projectDir, appConfig, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, mode, watch = true, options = {}) {
|
|
17
34
|
if (options.quiet !== true) cliOut(`Compiling app code`);
|
|
35
|
+
const compilePackages = options.packages !== false;
|
|
18
36
|
let dependencyGraphDir = '';
|
|
19
37
|
let dependencyGraphLocation = '';
|
|
20
38
|
let oldDependencyGraph = {};
|
|
@@ -36,15 +54,23 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
36
54
|
if (mode === 'dev') {
|
|
37
55
|
dependencyGraphDir = options.packagesCacheLocation ?? join(projectDir, packagesCacheFolder);
|
|
38
56
|
dependencyGraphLocation = join(dependencyGraphDir, '.dependency-graph.js');
|
|
39
|
-
oldDependencyGraph =
|
|
57
|
+
oldDependencyGraph = await loadDependencyGraph(dependencyGraphLocation);
|
|
40
58
|
dependencyGraph = {};
|
|
41
59
|
importAliases = {};
|
|
42
60
|
promises = [];
|
|
43
|
-
config = makeBabelUserDevConfig(projectDir, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, dependencyGraphDir, skipPackageCompilationFiles, oldDependencyGraph, dependencyGraph, importAliases, promises, watch, options.hmr === true);
|
|
61
|
+
config = makeBabelUserDevConfig(projectDir, codeLocation, sourceLocation, styleImportBase, buildLocation, buildCodeLocation, dependencyGraphDir, skipPackageCompilationFiles, oldDependencyGraph, dependencyGraph, importAliases, promises, watch, options.hmr === true, compilePackages);
|
|
44
62
|
} else {
|
|
45
63
|
config = makeBabelUserProdConfig(projectDir, codeLocation, sourceLocation, styleImportBase, watch);
|
|
46
64
|
}
|
|
47
65
|
config.root = getFrameworkDir();
|
|
66
|
+
// per-plugin attribution of the transform's time, only when someone is looking: the wrappers
|
|
67
|
+
// run per AST node
|
|
68
|
+
if (isProfiling()) {
|
|
69
|
+
config.plugins = tallyBabelPlugins(config.plugins);
|
|
70
|
+
if (mode === 'dev' && compilePackages) {
|
|
71
|
+
cliWarn(`profiling serves linked packages from the packages cache -` + ` delete ${dependencyGraphDir} after rebuilding one, or this run compiles stale code`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
48
74
|
// compile with the API
|
|
49
75
|
const codeLocationLength = codeLocation.length + 1;
|
|
50
76
|
const transformPromises = [];
|
|
@@ -67,7 +93,13 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
67
93
|
failedFiles.delete(file);
|
|
68
94
|
return;
|
|
69
95
|
}
|
|
70
|
-
|
|
96
|
+
// Sync, so this can be tallied exactly - nothing else runs during it. Babel's work is
|
|
97
|
+
// cpu-bound on one thread either way, so the async API's 291 concurrent calls bought
|
|
98
|
+
// no parallelism, only scheduling. The plugins defer their async work into
|
|
99
|
+
// opts.promises rather than awaiting in a visitor, which is what makes sync viable.
|
|
100
|
+
const babelStart = performance.now();
|
|
101
|
+
const result = babelTransformFileSync(file, config);
|
|
102
|
+
tally('babel transform', performance.now() - babelStart);
|
|
71
103
|
if (result === null) throw new TungstenError(`Err: babel transform on file ${file} returned null`);
|
|
72
104
|
const {
|
|
73
105
|
code
|
|
@@ -174,6 +206,8 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
174
206
|
}
|
|
175
207
|
// get a list of files
|
|
176
208
|
const watchGlob = slash(`${codeLocation}/**/*.js{,x}`);
|
|
209
|
+
// typed rather than inferred from the pushes: it is captured by a closure below, which stops
|
|
210
|
+
// typescript widening it from the pushes
|
|
177
211
|
const ignored = [];
|
|
178
212
|
ignored.push(slash(`${buildLocation}/**`));
|
|
179
213
|
if (appConfig.ignoreDirs !== undefined && appConfig.ignoreDirs !== '') {
|
|
@@ -189,6 +223,17 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
189
223
|
ignored.push(appConfig.ignorePatterns);
|
|
190
224
|
}
|
|
191
225
|
}
|
|
226
|
+
// test files are a category, not noise: excluded here like ignoreDirs/ignorePatterns, but a
|
|
227
|
+
// test compile (`tungsten compile --tests`) wants them, where it would never want the ignored
|
|
228
|
+
if (options.tests !== true) {
|
|
229
|
+
if (appConfig.testDirs !== undefined && appConfig.testDirs !== '') {
|
|
230
|
+
const testDirs = typeof appConfig.testDirs === 'string' ? [appConfig.testDirs] : appConfig.testDirs;
|
|
231
|
+
ignored.push(...testDirs.map(folder => slash(`${codeLocation}/${folder}/**`)));
|
|
232
|
+
}
|
|
233
|
+
if (appConfig.testPatterns !== undefined && appConfig.testPatterns !== '') {
|
|
234
|
+
ignored.push(...(Array.isArray(appConfig.testPatterns) ? appConfig.testPatterns : [appConfig.testPatterns]));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
192
237
|
if (appConfig.exclude !== undefined) ignored.push(slash(`${codeLocation}/${appConfig.exclude}`));
|
|
193
238
|
//console.log('appConfig', appConfig)
|
|
194
239
|
//console.log('ignored', ignored)
|
|
@@ -197,11 +242,18 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
197
242
|
//cliWarn(JSON.stringify(ignored))
|
|
198
243
|
/// need to do an initial build before watching
|
|
199
244
|
// not sure what glob matcher chokidar uses
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
245
|
+
// phases record into the profiler module's shared timeline, so this instance need not be the
|
|
246
|
+
// one the command created - it only has to be enabled when --profile is
|
|
247
|
+
const profiler = createTimeProfiler(isProfiling());
|
|
248
|
+
const paths = await profiler.timeProfileAsync('find source files', () => globby([watchGlob, ...ignored.map(x => `!${x}`)]));
|
|
249
|
+
// the transforms are started inside the phase, not just awaited there: a resource is charged to
|
|
250
|
+
// the phase that created it, so starting them outside credits all the work to the parent
|
|
251
|
+
const transformResults = await profiler.timeProfileAsync(`transform ${paths.length} files`, () => {
|
|
252
|
+
paths.forEach(file => {
|
|
253
|
+
transformPromises.push(transformFile(file));
|
|
254
|
+
});
|
|
255
|
+
return Promise.allSettled(transformPromises);
|
|
203
256
|
});
|
|
204
|
-
const transformResults = await Promise.allSettled(transformPromises);
|
|
205
257
|
const transformFailures = transformResults.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
206
258
|
const settleDeferredWork = async () => {
|
|
207
259
|
// splice out (rather than slice+advance-a-pointer) so settled entries are freed for GC -
|
|
@@ -211,10 +263,14 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
211
263
|
const deferredResults = await Promise.allSettled(currentPromises);
|
|
212
264
|
return deferredResults.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
213
265
|
};
|
|
214
|
-
|
|
266
|
+
// the package walk the transforms queued - compiling, or now copying from the packages cache,
|
|
267
|
+
// every file of every imported package
|
|
268
|
+
const deferredFailures = await profiler.timeProfileAsync('settle package work', settleDeferredWork);
|
|
269
|
+
const initialFailures = [...transformFailures, ...deferredFailures];
|
|
215
270
|
const initialSuccess = initialFailures.length === 0 && failedFiles.size === 0;
|
|
216
271
|
function writeDependencyGraph() {
|
|
217
|
-
|
|
272
|
+
// without the package walk the graph is empty - don't create the cache dir to hold it
|
|
273
|
+
if (mode !== 'dev' || !compilePackages) return;
|
|
218
274
|
if (!fs.existsSync(dependencyGraphDir)) {
|
|
219
275
|
fs.mkdirSync(dependencyGraphDir, {
|
|
220
276
|
recursive: true
|
|
@@ -318,7 +374,9 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
318
374
|
}
|
|
319
375
|
});
|
|
320
376
|
}
|
|
321
|
-
|
|
377
|
+
// the watchers are created above but only scan the tree now - on a large app that scan is
|
|
378
|
+
// seconds, and it runs after every file has already been compiled
|
|
379
|
+
return profiler.timeProfileAsync('watchers ready', () => new Promise(resolve => {
|
|
322
380
|
const awaiting = new Map([[watcher, true]]);
|
|
323
381
|
if (copyWatcher) awaiting.set(copyWatcher, true);
|
|
324
382
|
watcher.on('ready', async () => {
|
|
@@ -348,5 +406,5 @@ export async function compileJavascript(projectDir, appConfig, codeLocation, sou
|
|
|
348
406
|
}
|
|
349
407
|
});
|
|
350
408
|
}
|
|
351
|
-
});
|
|
409
|
+
}));
|
|
352
410
|
}
|