UNPKG

4.57 kBPlain TextView Raw
1import Splitter from './splitter';
2import Bundler from './bundler';
3import Analyzer from './analyzer';
4import Package from './package';
5import { buildDebugCallback } from 'broccoli-debug';
6import BundleConfig from './bundle-config';
7import Append from './broccoli-append';
8import { Tree } from 'broccoli-plugin';
9
10const debugTree = buildDebugCallback('ember-auto-import');
11const protocol = '__ember_auto_import_protocol_v1__';
12
13export default class AutoImport {
14 private primaryPackage: any;
15 private packages: Set<Package> = new Set();
16 private env: "development" | "test" | "production";
17 private consoleWrite: (msg: string) => void;
18 private analyzers: Map<Analyzer, Package> = new Map();
19 private bundles: BundleConfig;
20
21 static lookup(appOrAddon: any): AutoImport {
22 let g = global as any;
23 if (!g[protocol]) {
24 g[protocol] = new this(appOrAddon);
25 }
26 return g[protocol];
27 }
28
29 constructor(appOrAddon: any) {
30 this.primaryPackage = appOrAddon;
31 // _findHost is private API but it's been stable in ember-cli for two years.
32 let host = appOrAddon._findHost();
33 this.env = host.env;
34 this.bundles = new BundleConfig(host);
35 if (!this.env) {
36 throw new Error('Bug in ember-auto-import: did not discover environment');
37 }
38
39 this.consoleWrite = (...args) => appOrAddon.project.ui.write(...args);
40 }
41
42 isPrimary(appOrAddon: any) {
43 return this.primaryPackage === appOrAddon;
44 }
45
46 analyze(tree: Tree, appOrAddon: any) {
47 let pack = Package.lookup(appOrAddon);
48 this.packages.add(pack);
49 let analyzer = new Analyzer(
50 debugTree(tree, `preprocessor:input-${this.analyzers.size}`),
51 pack
52 );
53 this.analyzers.set(analyzer, pack);
54 return analyzer;
55 }
56
57 makeBundler(allAppTree: Tree) {
58 // The Splitter takes the set of imports from the Analyzer and
59 // decides which ones to include in which bundles
60 let splitter = new Splitter({
61 analyzers: this.analyzers,
62 bundles: this.bundles
63 });
64
65 // The Bundler asks the splitter for deps it should include and
66 // is responsible for packaging those deps up.
67 return new Bundler(allAppTree, {
68 splitter,
69 environment: this.env,
70 packages: this.packages,
71 consoleWrite: this.consoleWrite,
72 bundles: this.bundles
73 });
74 }
75
76 addTo(allAppTree: Tree) {
77 let bundler = debugTree(this.makeBundler(allAppTree), 'output');
78
79 let mappings = new Map();
80 for (let name of this.bundles.names) {
81 let byType = new Map();
82 mappings.set(`entrypoints/${name}`, byType);
83 for (let type of this.bundles.types) {
84 let target = this.bundles.bundleEntrypoint(name, type);
85 byType.set(type, target);
86 }
87 }
88
89 let passthrough = new Map();
90 passthrough.set('lazy', this.bundles.lazyChunkPath);
91
92 return new Append(allAppTree, bundler, {
93 mappings, passthrough
94 });
95 }
96
97 included(addonInstance: any) {
98 let host = addonInstance._findHost();
99 this.configureFingerprints(host);
100
101 // ember-cli as of 3.4-beta has introduced architectural changes that make
102 // it impossible for us to nicely emit the built dependencies via our own
103 // vendor and public trees, because it now considers those as *inputs* to
104 // the trees that we analyze, causing a circle, even though there is no
105 // real circular data dependency.
106 //
107 // We also cannot use postprocessTree('all'), because that only works in
108 // first-level addons.
109 //
110 // So we are forced to monkey patch EmberApp. We insert ourselves right at
111 // the beginning of addonPostprocessTree.
112 let original = host.addonPostprocessTree.bind(host);
113 host.addonPostprocessTree = (which: string, tree: Tree) => {
114 if (which === 'all') {
115 tree = this.addTo(tree);
116 }
117 return original(which, tree);
118 };
119 }
120
121 // We need to disable fingerprinting of chunks, because (1) they already
122 // have their own webpack-generated hashes and (2) the runtime loader code
123 // can't easily be told about broccoli-asset-rev's hashes.
124 private configureFingerprints(host: any) {
125 let pattern = 'assets/chunk.*.js';
126 if (!host.options.fingerprint) {
127 host.options.fingerprint = {};
128 }
129 if (!host.options.fingerprint.hasOwnProperty('exclude')) {
130 host.options.fingerprint.exclude = [pattern];
131 } else {
132 host.options.fingerprint.exclude.push(pattern);
133 }
134 }
135
136 updateFastBootManifest(manifest: { vendorFiles: string[] }) {
137 manifest.vendorFiles.push(`${this.bundles.lazyChunkPath}/auto-import-fastboot.js`);
138 }
139
140}