UNPKG

9.28 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 return new (P || (P = Promise))(function (resolve, reject) {
4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 step((generator = generator.apply(thisArg, _arguments || [])).next());
8 });
9};
10var __importDefault = (this && this.__importDefault) || function (mod) {
11 return (mod && mod.__esModule) ? mod : { "default": mod };
12};
13Object.defineProperty(exports, "__esModule", { value: true });
14const debug_1 = __importDefault(require("debug"));
15const util_1 = require("./util");
16const lodash_1 = require("lodash");
17const enhanced_resolve_1 = require("enhanced-resolve");
18const pkg_up_1 = __importDefault(require("pkg-up"));
19const path_1 = require("path");
20const debug = debug_1.default('ember-auto-import:splitter');
21const resolver = enhanced_resolve_1.ResolverFactory.createResolver({
22 // upstream types seem to be broken here
23 fileSystem: new enhanced_resolve_1.CachedInputFileSystem(new enhanced_resolve_1.NodeJsInputFileSystem(), 4000),
24 extensions: ['.js', '.json'],
25 mainFields: ['browser', 'module', 'main']
26});
27class Splitter {
28 constructor(options) {
29 this.options = options;
30 this.lastDeps = null;
31 this.packageVersions = new Map();
32 }
33 deps() {
34 return __awaiter(this, void 0, void 0, function* () {
35 if (this.importsChanged()) {
36 this.lastDeps = yield this.computeDeps(this.options.analyzers);
37 debug('output %s', new LazyPrintDeps(this.lastDeps));
38 }
39 return this.lastDeps;
40 });
41 }
42 importsChanged() {
43 let imports = [...this.options.analyzers.keys()].map(analyzer => analyzer.imports);
44 if (!this.lastImports || !util_1.shallowEqual(this.lastImports, imports)) {
45 this.lastImports = imports;
46 return true;
47 }
48 return false;
49 }
50 computeTargets(analyzers) {
51 return __awaiter(this, void 0, void 0, function* () {
52 let specifiers = new Map();
53 let imports = lodash_1.flatten([...analyzers.keys()].map(analyzer => analyzer.imports));
54 yield Promise.all(imports.map((imp) => __awaiter(this, void 0, void 0, function* () {
55 if (imp.specifier[0] === '.' || imp.specifier[0] === '/') {
56 // we're only trying to identify imports of external NPM
57 // packages, so relative imports are never relevant.
58 return;
59 }
60 let aliasedSpecifier = imp.package.aliasFor(imp.specifier);
61 let parts = aliasedSpecifier.split('/');
62 let packageName;
63 if (aliasedSpecifier[0] === '@') {
64 packageName = `${parts[0]}/${parts[1]}`;
65 }
66 else {
67 packageName = parts[0];
68 }
69 if (imp.package.excludesDependency(packageName)) {
70 // This package has been explicitly excluded.
71 return;
72 }
73 if (!imp.package.hasDependency(packageName) ||
74 imp.package.isEmberAddonDependency(packageName)) {
75 return;
76 }
77 imp.package.assertAllowedDependency(packageName);
78 let entrypoint = yield resolveEntrypoint(aliasedSpecifier, imp.package);
79 let seenAlready = specifiers.get(imp.specifier);
80 if (seenAlready) {
81 yield this.assertSafeVersion(seenAlready, imp, entrypoint);
82 seenAlready.importedBy.push(imp);
83 }
84 else {
85 specifiers.set(imp.specifier, {
86 specifier: imp.specifier,
87 entrypoint,
88 importedBy: [imp]
89 });
90 }
91 })));
92 return specifiers;
93 });
94 }
95 versionOfPackage(entrypoint) {
96 return __awaiter(this, void 0, void 0, function* () {
97 if (this.packageVersions.has(entrypoint)) {
98 return this.packageVersions.get(entrypoint);
99 }
100 let pkgPath = yield pkg_up_1.default(path_1.dirname(entrypoint));
101 let version = null;
102 if (pkgPath) {
103 let pkg = require(pkgPath);
104 version = pkg.version;
105 }
106 this.packageVersions.set(entrypoint, version);
107 return version;
108 });
109 }
110 assertSafeVersion(have, nextImport, entrypoint) {
111 return __awaiter(this, void 0, void 0, function* () {
112 if (have.entrypoint === entrypoint) {
113 // both import statements are resolving to the exact same entrypoint --
114 // this is the normal and happy case
115 return;
116 }
117 let [haveVersion, nextVersion] = yield Promise.all([
118 this.versionOfPackage(have.entrypoint),
119 this.versionOfPackage(entrypoint)
120 ]);
121 if (haveVersion !== nextVersion) {
122 throw new Error(`${nextImport.package.name} and ${have.importedBy[0].package.name} are using different versions of ${have.specifier} (${nextVersion} located at ${entrypoint} vs ${haveVersion} located at ${have.entrypoint})`);
123 }
124 });
125 }
126 computeDeps(analyzers) {
127 return __awaiter(this, void 0, void 0, function* () {
128 let targets = yield this.computeTargets(analyzers);
129 let deps = new Map();
130 this.options.bundles.names.forEach(bundleName => {
131 deps.set(bundleName, { staticImports: [], dynamicImports: [] });
132 });
133 for (let target of targets.values()) {
134 let [dynamicUses, staticUses] = lodash_1.partition(target.importedBy, imp => imp.isDynamic);
135 if (staticUses.length > 0) {
136 let bundleName = this.chooseBundle(staticUses);
137 deps.get(bundleName).staticImports.push(target);
138 }
139 if (dynamicUses.length > 0) {
140 let bundleName = this.chooseBundle(dynamicUses);
141 deps.get(bundleName).dynamicImports.push(target);
142 }
143 }
144 this.sortDependencies(deps);
145 return deps;
146 });
147 }
148 sortDependencies(deps) {
149 for (const bundle of deps.values()) {
150 this.sortBundle(bundle);
151 }
152 }
153 sortBundle(bundle) {
154 for (const imports of lodash_1.values(bundle)) {
155 imports.sort((a, b) => a.specifier.localeCompare(b.specifier));
156 }
157 }
158 // given that a module is imported by the given list of paths, which
159 // bundle should it go in?
160 chooseBundle(importedBy) {
161 let usedInBundles = {};
162 importedBy.forEach(usage => {
163 usedInBundles[this.bundleForPath(usage)] = true;
164 });
165 return this.options.bundles.names.find(bundle => usedInBundles[bundle]);
166 }
167 bundleForPath(usage) {
168 let bundleName = this.options.bundles.bundleForPath(usage.path);
169 if (this.options.bundles.names.indexOf(bundleName) === -1) {
170 throw new Error(`bundleForPath("${usage.path}") returned ${bundleName}" but the only configured bundle names are ${this.options.bundles.names.join(',')}`);
171 }
172 debug('bundleForPath("%s")=%s', usage.path, bundleName);
173 return bundleName;
174 }
175}
176exports.default = Splitter;
177function resolveEntrypoint(specifier, pkg) {
178 return __awaiter(this, void 0, void 0, function* () {
179 return new Promise((resolvePromise, reject) => {
180 // upstream types seem to be out of date here
181 resolver.resolve({}, pkg.root, specifier, {}, (err, path) => {
182 if (err) {
183 reject(err);
184 }
185 else {
186 resolvePromise(path);
187 }
188 });
189 });
190 });
191}
192class LazyPrintDeps {
193 constructor(deps) {
194 this.deps = deps;
195 }
196 describeResolvedImport(imp) {
197 return {
198 specifier: imp.specifier,
199 entrypoint: imp.entrypoint,
200 importedBy: imp.importedBy.map(this.describeImport.bind(this))
201 };
202 }
203 describeImport(imp) {
204 return {
205 package: imp.package.name,
206 path: imp.path,
207 isDynamic: imp.isDynamic
208 };
209 }
210 toString() {
211 let output = {};
212 for (let [bundle, { staticImports, dynamicImports }] of this.deps.entries()) {
213 output[bundle] = {
214 static: staticImports.map(this.describeResolvedImport.bind(this)),
215 dynamic: dynamicImports.map(this.describeResolvedImport.bind(this))
216 };
217 }
218 return JSON.stringify(output, null, 2);
219 }
220}
221//# sourceMappingURL=splitter.js.map
\No newline at end of file