UNPKG

16.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var tslib_1 = require("tslib");
4var fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
5var os_1 = tslib_1.__importDefault(require("os"));
6var path_1 = tslib_1.__importDefault(require("path"));
7var globs_1 = tslib_1.__importDefault(require("globs"));
8var helpers_1 = require("./helpers");
9var IMPORT_PATTERN = /@import\s+['"](.+)['"];/g;
10var COMMENT_PATTERN = /\/\/.*$/gm;
11var MULTILINE_COMMENT_PATTERN = /\/\*[\s\S]*?\*\//g;
12var DEFAULT_FILE_EXTENSION = ".scss";
13var ALLOWED_FILE_EXTENSIONS = [".scss", ".css"];
14var NODE_MODULES = "node_modules";
15var TILDE = "~";
16var Bundler = /** @class */ (function () {
17 function Bundler(fileRegistry, projectDirectory) {
18 if (fileRegistry === void 0) { fileRegistry = {}; }
19 this.fileRegistry = fileRegistry;
20 this.projectDirectory = projectDirectory;
21 // Full paths of used imports and their count
22 this.usedImports = {};
23 // Imports dictionary by file
24 this.importsByFile = {};
25 }
26 Bundler.prototype.bundle = function (file, dedupeGlobs, includePaths, ignoredImports) {
27 if (dedupeGlobs === void 0) { dedupeGlobs = []; }
28 if (includePaths === void 0) { includePaths = []; }
29 if (ignoredImports === void 0) { ignoredImports = []; }
30 return tslib_1.__awaiter(this, void 0, void 0, function () {
31 var contentPromise, dedupeFilesPromise, _a, content, dedupeFiles, ignoredImportsRegEx, _b;
32 return tslib_1.__generator(this, function (_c) {
33 switch (_c.label) {
34 case 0:
35 _c.trys.push([0, 3, , 4]);
36 if (this.projectDirectory != null) {
37 file = path_1.default.resolve(this.projectDirectory, file);
38 }
39 return [4 /*yield*/, fs_extra_1.default.access(file)];
40 case 1:
41 _c.sent();
42 contentPromise = fs_extra_1.default.readFile(file, "utf-8");
43 dedupeFilesPromise = this.globFilesOrEmpty(dedupeGlobs);
44 return [4 /*yield*/, Promise.all([contentPromise, dedupeFilesPromise])];
45 case 2:
46 _a = _c.sent(), content = _a[0], dedupeFiles = _a[1];
47 ignoredImportsRegEx = ignoredImports.map(function (ignoredImport) { return new RegExp(ignoredImport); });
48 return [2 /*return*/, this._bundle(file, content, dedupeFiles, includePaths, ignoredImportsRegEx)];
49 case 3:
50 _b = _c.sent();
51 return [2 /*return*/, {
52 filePath: file,
53 found: false
54 }];
55 case 4: return [2 /*return*/];
56 }
57 });
58 });
59 };
60 Bundler.prototype.isExtensionExists = function (importName) {
61 return ALLOWED_FILE_EXTENSIONS.some(function (extension) { return importName.indexOf(extension) !== -1; });
62 };
63 Bundler.prototype._bundle = function (filePath, content, dedupeFiles, includePaths, ignoredImports) {
64 return tslib_1.__awaiter(this, void 0, void 0, function () {
65 var dirname, importsPromises, imports, bundleResult, shouldCheckForDedupes, currentImports, _i, imports_1, imp, contentToReplace, currentImport, impContent, _a, bundledImport, childImports, timesUsed;
66 var _this = this;
67 return tslib_1.__generator(this, function (_b) {
68 switch (_b.label) {
69 case 0:
70 // Remove commented imports
71 content = this.removeImportsFromComments(content);
72 // Resolve path to work only with full paths
73 filePath = path_1.default.resolve(filePath);
74 dirname = path_1.default.dirname(filePath);
75 if (this.fileRegistry[filePath] == null) {
76 this.fileRegistry[filePath] = content;
77 }
78 importsPromises = helpers_1.matchAll(content, IMPORT_PATTERN).map(function (match) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
79 var importName, ignored, fullPath, tilde, importData;
80 return tslib_1.__generator(this, function (_a) {
81 switch (_a.label) {
82 case 0:
83 importName = match[1];
84 // Append extension if it's absent
85 if (!this.isExtensionExists(importName)) {
86 importName += DEFAULT_FILE_EXTENSION;
87 }
88 ignored = ignoredImports.findIndex(function (ignoredImportRegex) { return ignoredImportRegex.test(importName); }) !== -1;
89 tilde = importName.startsWith(TILDE);
90 if (tilde && this.projectDirectory != null) {
91 importName = "./" + NODE_MODULES + "/" + importName.substr(TILDE.length, importName.length);
92 fullPath = path_1.default.resolve(this.projectDirectory, importName);
93 }
94 else {
95 fullPath = path_1.default.resolve(dirname, importName);
96 }
97 importData = {
98 importString: match[0],
99 tilde: tilde,
100 path: importName,
101 fullPath: fullPath,
102 found: false,
103 ignored: ignored
104 };
105 return [4 /*yield*/, this.resolveImport(importData, includePaths)];
106 case 1:
107 _a.sent();
108 return [2 /*return*/, importData];
109 }
110 });
111 }); });
112 return [4 /*yield*/, Promise.all(importsPromises)];
113 case 1:
114 imports = _b.sent();
115 bundleResult = {
116 filePath: filePath,
117 found: true
118 };
119 shouldCheckForDedupes = dedupeFiles != null && dedupeFiles.length > 0;
120 currentImports = [];
121 _i = 0, imports_1 = imports;
122 _b.label = 2;
123 case 2:
124 if (!(_i < imports_1.length)) return [3 /*break*/, 11];
125 imp = imports_1[_i];
126 contentToReplace = void 0;
127 currentImport = void 0;
128 if (!!imp.found) return [3 /*break*/, 3];
129 // Add empty bundle result with found: false
130 currentImport = {
131 filePath: imp.fullPath,
132 tilde: imp.tilde,
133 found: false,
134 ignored: imp.ignored
135 };
136 return [3 /*break*/, 9];
137 case 3:
138 if (!(this.usedImports[imp.fullPath] == null)) return [3 /*break*/, 8];
139 // Add it to used imports
140 this.usedImports[imp.fullPath] = 1;
141 if (!(this.fileRegistry[imp.fullPath] == null)) return [3 /*break*/, 5];
142 return [4 /*yield*/, fs_extra_1.default.readFile(imp.fullPath, "utf-8")];
143 case 4:
144 _a = _b.sent();
145 return [3 /*break*/, 6];
146 case 5:
147 _a = this.fileRegistry[imp.fullPath];
148 _b.label = 6;
149 case 6:
150 impContent = _a;
151 return [4 /*yield*/, this._bundle(imp.fullPath, impContent, dedupeFiles, includePaths, ignoredImports)];
152 case 7:
153 bundledImport = _b.sent();
154 // Then add its bundled content to the registry
155 this.fileRegistry[imp.fullPath] = bundledImport.bundledContent;
156 // And whole BundleResult to current imports
157 currentImport = bundledImport;
158 return [3 /*break*/, 9];
159 case 8:
160 // File is in the registry
161 // Increment it's usage count
162 if (this.usedImports != null) {
163 this.usedImports[imp.fullPath]++;
164 }
165 childImports = [];
166 if (this.importsByFile != null) {
167 childImports = this.importsByFile[imp.fullPath];
168 }
169 // Construct and add result to current imports
170 currentImport = {
171 filePath: imp.fullPath,
172 tilde: imp.tilde,
173 found: true,
174 imports: childImports
175 };
176 _b.label = 9;
177 case 9:
178 if (imp.ignored) {
179 if (this.usedImports[imp.fullPath] > 1) {
180 contentToReplace = "";
181 }
182 else {
183 contentToReplace = imp.importString;
184 }
185 }
186 else {
187 // Take contentToReplace from the fileRegistry
188 contentToReplace = this.fileRegistry[imp.fullPath];
189 // If the content is not found
190 if (contentToReplace == null) {
191 // Indicate this with a comment for easier debugging
192 contentToReplace = "/*** IMPORTED FILE NOT FOUND ***/" + os_1.default.EOL + imp.importString + "/*** --- ***/";
193 }
194 // If usedImports dictionary is defined
195 if (shouldCheckForDedupes && this.usedImports != null) {
196 timesUsed = this.usedImports[imp.fullPath];
197 if (dedupeFiles.indexOf(imp.fullPath) !== -1 && timesUsed != null && timesUsed > 1) {
198 // Reset content to replace to an empty string to skip it
199 contentToReplace = "";
200 // And indicate that import was deduped
201 currentImport.deduped = true;
202 }
203 }
204 }
205 // Finally, replace import string with bundled content or a debug message
206 content = this.replaceLastOccurance(content, imp.importString, contentToReplace);
207 // And push current import into the list
208 currentImports.push(currentImport);
209 _b.label = 10;
210 case 10:
211 _i++;
212 return [3 /*break*/, 2];
213 case 11:
214 // Set result properties
215 bundleResult.bundledContent = content;
216 bundleResult.imports = currentImports;
217 if (this.importsByFile != null) {
218 this.importsByFile[filePath] = currentImports;
219 }
220 return [2 /*return*/, bundleResult];
221 }
222 });
223 });
224 };
225 Bundler.prototype.replaceLastOccurance = function (content, importString, contentToReplace) {
226 var index = content.lastIndexOf(importString);
227 return content.slice(0, index) + content.slice(index).replace(importString, contentToReplace);
228 };
229 Bundler.prototype.removeImportsFromComments = function (text) {
230 var patterns = [COMMENT_PATTERN, MULTILINE_COMMENT_PATTERN];
231 for (var _i = 0, patterns_1 = patterns; _i < patterns_1.length; _i++) {
232 var pattern = patterns_1[_i];
233 text = text.replace(pattern, function (x) { return x.replace(IMPORT_PATTERN, ""); });
234 }
235 return text;
236 };
237 Bundler.prototype.resolveImport = function (importData, includePaths) {
238 return tslib_1.__awaiter(this, void 0, void 0, function () {
239 var error_1, underscoredDirname, underscoredBasename, underscoredFilePath, underscoreErr_1, remainingIncludePaths;
240 return tslib_1.__generator(this, function (_a) {
241 switch (_a.label) {
242 case 0:
243 if (this.fileRegistry[importData.fullPath]) {
244 importData.found = true;
245 return [2 /*return*/, importData];
246 }
247 _a.label = 1;
248 case 1:
249 _a.trys.push([1, 3, , 8]);
250 return [4 /*yield*/, fs_extra_1.default.access(importData.fullPath)];
251 case 2:
252 _a.sent();
253 importData.found = true;
254 return [3 /*break*/, 8];
255 case 3:
256 error_1 = _a.sent();
257 underscoredDirname = path_1.default.dirname(importData.fullPath);
258 underscoredBasename = path_1.default.basename(importData.fullPath);
259 underscoredFilePath = path_1.default.join(underscoredDirname, "_" + underscoredBasename);
260 _a.label = 4;
261 case 4:
262 _a.trys.push([4, 6, , 7]);
263 return [4 /*yield*/, fs_extra_1.default.access(underscoredFilePath)];
264 case 5:
265 _a.sent();
266 importData.fullPath = underscoredFilePath;
267 importData.found = true;
268 return [3 /*break*/, 7];
269 case 6:
270 underscoreErr_1 = _a.sent();
271 // If there are any includePaths
272 if (includePaths.length) {
273 // Resolve fullPath using its first entry
274 importData.fullPath = path_1.default.resolve(includePaths[0], importData.path);
275 remainingIncludePaths = includePaths.slice(1);
276 return [2 /*return*/, this.resolveImport(importData, remainingIncludePaths)];
277 }
278 return [3 /*break*/, 7];
279 case 7: return [3 /*break*/, 8];
280 case 8: return [2 /*return*/, importData];
281 }
282 });
283 });
284 };
285 Bundler.prototype.globFilesOrEmpty = function (globsList) {
286 return tslib_1.__awaiter(this, void 0, void 0, function () {
287 return tslib_1.__generator(this, function (_a) {
288 return [2 /*return*/, new Promise(function (resolve, reject) {
289 if (globsList == null || globsList.length === 0) {
290 resolve([]);
291 return;
292 }
293 globs_1.default(globsList, function (error, files) {
294 if (error != null) {
295 reject(error);
296 }
297 var fullPaths = files.map(function (file) { return path_1.default.resolve(file); });
298 resolve(fullPaths);
299 });
300 })];
301 });
302 });
303 };
304 return Bundler;
305}());
306exports.Bundler = Bundler;