1 |
|
2 |
|
3 | import type {Environment} from './types';
|
4 |
|
5 | import type Graph from './Graph';
|
6 | import type {AssetGraphNode, BundleGraphNode} from './types';
|
7 |
|
8 | import path from 'path';
|
9 |
|
10 | const COLORS = {
|
11 | root: 'gray',
|
12 | asset: 'green',
|
13 | dependency: 'orange',
|
14 | transformer_request: 'cyan',
|
15 | file: 'gray',
|
16 | default: 'white',
|
17 | };
|
18 |
|
19 | const TYPE_COLORS = {
|
20 | bundle: 'blue',
|
21 | contains: 'grey',
|
22 | references: 'red',
|
23 | };
|
24 |
|
25 | export default async function dumpGraphToGraphViz(
|
26 | // $FlowFixMe
|
27 | graph: Graph<AssetGraphNode> | Graph<BundleGraphNode>,
|
28 | name: string,
|
29 | ): Promise<void> {
|
30 | if (
|
31 | process.env.PARCEL_BUILD_ENV === 'production' ||
|
32 | process.env.PARCEL_DUMP_GRAPHVIZ == null
|
33 | ) {
|
34 | return;
|
35 | }
|
36 | const graphviz = require('graphviz');
|
37 | const tempy = require('tempy');
|
38 | let g = graphviz.digraph('G');
|
39 | let nodes = Array.from(graph.nodes.values());
|
40 | for (let node of nodes) {
|
41 | let n = g.addNode(node.id);
|
42 |
|
43 | n.set('color', COLORS[node.type || 'default']);
|
44 | n.set('shape', 'box');
|
45 | n.set('style', 'filled');
|
46 | let label = `${node.type || 'No Type'}: [${node.id}]: `;
|
47 | if (node.type === 'dependency') {
|
48 | label += node.value.moduleSpecifier;
|
49 | let parts = [];
|
50 | if (node.value.isEntry) parts.push('entry');
|
51 | if (node.value.isAsync) parts.push('async');
|
52 | if (node.value.isWeak) parts.push('weak');
|
53 | if (node.value.isOptional) parts.push('optional');
|
54 | if (node.value.isDeferred) parts.push('deferred');
|
55 | if (parts.length) label += ' (' + parts.join(', ') + ')';
|
56 | if (node.value.env) label += ` (${getEnvDescription(node.value.env)})`;
|
57 | } else if (node.type === 'asset') {
|
58 | label += path.basename(node.value.filePath) + '#' + node.value.type;
|
59 | } else if (node.type === 'asset_group') {
|
60 | if (node.deferred) label += '(deferred)';
|
61 | } else if (node.type === 'file') {
|
62 | label += path.basename(node.value.filePath);
|
63 | } else if (node.type === 'transformer_request') {
|
64 | label +=
|
65 | path.basename(node.value.filePath) +
|
66 | ` (${getEnvDescription(node.value.env)})`;
|
67 | } else if (node.type === 'bundle') {
|
68 | let parts = [];
|
69 | if (node.value.isEntry) parts.push('entry');
|
70 | if (node.value.isInline) parts.push('inline');
|
71 | if (parts.length) label += ' (' + parts.join(', ') + ')';
|
72 | if (node.value.env) label += ` (${getEnvDescription(node.value.env)})`;
|
73 | } else if (node.type === 'request') {
|
74 | label = node.value.type + ':' + node.id;
|
75 | }
|
76 | n.set('label', label);
|
77 | }
|
78 | for (let edge of graph.getAllEdges()) {
|
79 | let gEdge = g.addEdge(edge.from, edge.to);
|
80 | let color = edge.type != null ? TYPE_COLORS[edge.type] : null;
|
81 | if (color != null) {
|
82 | gEdge.set('color', color);
|
83 | }
|
84 | }
|
85 | let tmp = tempy.file({name: `${name}.png`});
|
86 | await g.output('png', tmp);
|
87 |
|
88 | console.log('Dumped', tmp);
|
89 | }
|
90 |
|
91 | function getEnvDescription(env: Environment) {
|
92 | let description;
|
93 | if (typeof env.engines.browsers === 'string') {
|
94 | description = `${env.context}: ${env.engines.browsers}`;
|
95 | } else if (Array.isArray(env.engines.browsers)) {
|
96 | description = `${env.context}: ${env.engines.browsers.join(', ')}`;
|
97 | } else if (env.engines.node) {
|
98 | description = `node: ${env.engines.node}`;
|
99 | }
|
100 |
|
101 | return description ?? '';
|
102 | }
|