UNPKG

3.35 kBJavaScriptView Raw
1// @flow
2
3import type {Environment} from './types';
4
5import type Graph from './Graph';
6import type {AssetGraphNode, BundleGraphNode} from './types';
7
8import path from 'path';
9
10const COLORS = {
11 root: 'gray',
12 asset: 'green',
13 dependency: 'orange',
14 transformer_request: 'cyan',
15 file: 'gray',
16 default: 'white',
17};
18
19const TYPE_COLORS = {
20 bundle: 'blue',
21 contains: 'grey',
22 references: 'red',
23};
24
25export 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 // $FlowFixMe default is fine. Not every type needs to be in the map.
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 // eslint-disable-next-line no-console
88 console.log('Dumped', tmp);
89}
90
91function 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}