UNPKG

gulp-importer

Version:

A simple gulp plugin that allows importing any kind of file to any kind of file. Nevertheless, gulp-importer looks up through dependant files and automatically updates dependency.

640 lines (639 loc) 30.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var plugin_error_1 = __importDefault(require("plugin-error")); var through2_1 = __importDefault(require("through2")); var path_1 = __importDefault(require("path")); var fs_1 = __importDefault(require("fs")); var vinyl_1 = __importDefault(require("vinyl")); var vinyl_fs_1 = __importDefault(require("vinyl-fs")); var fancy_log_1 = __importDefault(require("fancy-log")); var fs_2 = require("fs"); var PLUGIN_NAME = "gulp-importer"; var RGX = /@{0,1}import\s+["']\s*(.*)\s*["'];{0,1}/gi; var defaults = { regexPattern: RGX, regexGroup: 1, encoding: "utf-8", importOnce: true, importRecursively: false, dependencyOutput: "primary", disableLog: false, detailedLog: false, requireExtension: true }; /** * Provides the gulp API for importing any kind of file to any kind of file. */ var Importer = /** @class */ (function () { /** * Initializes a new instance of this class. * @param options The configuration options. */ function Importer(options) { if (options === void 0) { options = {}; } this.options = options; this._cache = {}; /** A stream specific resolve stack, used to insure all chunks in a stream were appropriatly resolved. */ this._streamResolveStack = []; for (var key in defaults) if (!options.hasOwnProperty(key)) options[key] = defaults[key]; } Object.defineProperty(Importer.prototype, "cache", { /** The dependancy cache under watch. */ get: function () { return this._cache; }, enumerable: false, configurable: true }); /** * Resolves the import statements in the recieved buffers/streams. * @returns {Transform} The transform stream to be added to the pipe chain. */ Importer.prototype.execute = function (innerPl) { var that = this; return through2_1.default.obj(function (file, _enc, cb) { return __awaiter(this, void 0, void 0, function () { var _a, stream, err_1; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!that.validate(this, file, cb)) return [2 /*return*/]; _b.label = 1; case 1: _b.trys.push([1, 5, , 6]); if (!file.isBuffer()) return [3 /*break*/, 3]; _a = file; return [4 /*yield*/, that.resolveBuffer(file, innerPl)]; case 2: _a.contents = _b.sent(); return [3 /*break*/, 4]; case 3: if (file.isStream()) { stream = that.resolveStream(file, innerPl); stream.on("error", this.emit.bind(this, "error")); file.contents = file.contents.pipe(stream); } _b.label = 4; case 4: this.push(file); cb(); return [3 /*break*/, 6]; case 5: err_1 = _b.sent(); cb(new plugin_error_1.default(PLUGIN_NAME, err_1)); return [3 /*break*/, 6]; case 6: return [2 /*return*/]; } }); }); }); }; /** * Updates imports for dependancies when a primary file gets modified. * @returns The transform stream to be added to the pipe chain. */ Importer.prototype.updateDependency = function () { var that = this; return through2_1.default.obj(function (file, _, cb) { return __awaiter(this, void 0, void 0, function () { var logInfo, list, list, err_2; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!that.validate(this, file, cb)) return [2 /*return*/]; logInfo = function (num) { return fancy_log_1.default.info(num + " dependant file" + (num > 1 ? "s" : "") + "..."); }; _a.label = 1; case 1: _a.trys.push([1, 6, , 7]); if (!file.isBuffer()) return [3 /*break*/, 3]; return [4 /*yield*/, that.iterateCache(file.path, function (ref) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, that.resolveBufferRef(ref)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); })]; case 2: list = _a.sent(); if (!that.options.disableLog) logInfo(list.length); if (that.options.dependencyOutput !== "primary") list.forEach(function (ref) { return _this.push(ref); }); if (that.options.dependencyOutput !== "dependant") this.push(file); return [3 /*break*/, 5]; case 3: if (!file.isStream()) return [3 /*break*/, 5]; return [4 /*yield*/, that.iterateCache(file.path, function (ref) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, that.resolveStreamRef(ref)]; }); }); })]; case 4: list = _a.sent(); if (!that.options.disableLog) logInfo(list.length); list.forEach(function (ref) { return _this.push(ref); }); this.push(file); _a.label = 5; case 5: cb(); return [3 /*break*/, 7]; case 6: err_2 = _a.sent(); fancy_log_1.default.error(err_2.message); cb(new plugin_error_1.default(PLUGIN_NAME, err_2)); return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); }); }; /** * Iterates throught and modifies dependant files. * @param path The path to the primary file. * @param predicate The action to resolve a dependant file. * @returns The promise that represents the asynchronous operation, containing the resolved files. */ Importer.prototype.iterateCache = function (path, predicate) { return __awaiter(this, void 0, void 0, function () { var fileList, encoded, _a, _b, _c, _, file, refFile, e_1_1; var e_1, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: fileList = []; encoded = Importer.encode(path_1.default.resolve(path)); if (!this._cache.hasOwnProperty(encoded)) return [3 /*break*/, 8]; _e.label = 1; case 1: _e.trys.push([1, 6, 7, 8]); _a = __values(Object.entries(this._cache[encoded])), _b = _a.next(); _e.label = 2; case 2: if (!!_b.done) return [3 /*break*/, 5]; _c = __read(_b.value, 2), _ = _c[0], file = _c[1]; return [4 /*yield*/, predicate(file)]; case 3: refFile = _e.sent(); fileList.push(refFile); _e.label = 4; case 4: _b = _a.next(); return [3 /*break*/, 2]; case 5: return [3 /*break*/, 8]; case 6: e_1_1 = _e.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 8]; case 7: try { if (_b && !_b.done && (_d = _a.return)) _d.call(_a); } finally { if (e_1) throw e_1.error; } return [7 /*endfinally*/]; case 8: return [2 /*return*/, fileList]; } }); }); }; /** * Resolves buffers for the specified file. * @param file The file to resolve buffers for. * @returns The promise that represents the asynchronous operation, containing the resolved file. */ Importer.prototype.resolveBufferRef = function (file) { return __awaiter(this, void 0, void 0, function () { var refFile, _a, _b, error_1; var _c; return __generator(this, function (_d) { switch (_d.label) { case 0: _d.trys.push([0, 3, , 4]); _a = vinyl_1.default.bind; _c = { cwd: file.cwd, base: file.base, path: file.path }; return [4 /*yield*/, fs_2.promises.readFile(file.path)]; case 1: refFile = new (_a.apply(vinyl_1.default, [void 0, (_c.contents = _d.sent(), _c)]))(); _b = refFile; return [4 /*yield*/, this.resolveBuffer(refFile)]; case 2: _b.contents = _d.sent(); return [2 /*return*/, refFile]; case 3: error_1 = _d.sent(); throw new plugin_error_1.default(PLUGIN_NAME, error_1); case 4: return [2 /*return*/]; } }); }); }; /** * Resolves streaming content for the specified file. * @param file The file whose streaming contents should be resolved. * @returns The promise that represents the asynchronous operation, containing the resolved file. */ Importer.prototype.resolveStreamRef = function (file) { var rStream = fs_1.default.createReadStream(file.path, { encoding: this.options.encoding }); var tStream = this.resolveStream(file); return new vinyl_1.default({ cwd: file.cwd, base: file.base, path: file.path, contents: rStream.pipe(tStream) }); }; /** * Validates an input file. * @param stream The pipe stream. * @param file The input file. * @param cb The transform callback. * @returns The flag indicating whether the input file is valid. */ Importer.prototype.validate = function (stream, file, cb) { if (file.isNull()) { cb(null, file); return false; } if (file.path === undefined) { stream.emit("error", new plugin_error_1.default(PLUGIN_NAME, "The file path is undefined.")); cb(); return false; } return true; }; /** * Appends the speicifed dependancy path to primary relative cache to be triggered on modify. * @param dpnPath The dependant file to be added to the update cache. * @param prmPath The primary file that triggers the update. */ Importer.prototype.appendCache = function (dpnPath, prmPath) { var _a; dpnPath = Importer.encode(dpnPath); var file = new vinyl_1.default({ cwd: prmPath.cwd, base: prmPath.base, path: path_1.default.resolve(prmPath.path) }); var path = Importer.encode(file.path); if (!this._cache[dpnPath]) this._cache[dpnPath] = (_a = {}, _a[path] = file, _a); else if (!this._cache[dpnPath].hasOwnProperty(path)) { this._cache[dpnPath][path] = file; } }; /** * Returns the base64 encoded version of the specified value. * @param value The value to be encoded. * @returns The incoded version of the input value. */ Importer.encode = function (value) { return Buffer.from(value).toString('base64'); }; /** * Resolves the buffers for the specified file. * @param file The file whose buffer should be resolved. * @returns The resolved buffers. */ Importer.prototype.resolveBuffer = function (file, innerPl) { return __awaiter(this, void 0, void 0, function () { var content; return __generator(this, function (_a) { switch (_a.label) { case 0: content = file.contents.toString(this.options.encoding); return [4 /*yield*/, this.replace({ file: file, content: content, resolveStack: [], transformation: innerPl })]; case 1: content = _a.sent(); return [2 /*return*/, Buffer.from(content, this.options.encoding)]; } }); }); }; /** * Resolves the streaming content for the specified file. * @param file The file whose streaming content should be resolved. * @returns The transformed stream for the specified file. */ Importer.prototype.resolveStream = function (file, innerPl) { var _this = this; var that = this; var stream = (0, through2_1.default)(function (chunk, _, cb) { return __awaiter(this, void 0, void 0, function () { var content; return __generator(this, function (_a) { switch (_a.label) { case 0: content = Buffer.from(chunk, that.options.encoding).toString(); return [4 /*yield*/, that.replace({ file: file, content: content, resolveStack: that._streamResolveStack, transformation: innerPl })]; case 1: content = _a.sent(); this.push(content); cb(); return [2 /*return*/]; } }); }); }); stream.once("end", function () { return _this._streamResolveStack = []; }); return stream; }; /** * Applies imports on the specified content. * @param file The file apply imports for. * @param content The content to apply imports on. * @param resolveStack The resolve stack to cache the resolved imports. * @returns The resolved version of the specified content. */ Importer.prototype.replace = function (options) { var _a; return __awaiter(this, void 0, void 0, function () { var file, content, transformation, _b, _c, match, value, dPath, dContent, e_2_1; var e_2, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: file = options.file, content = options.content, transformation = options.transformation; _e.label = 1; case 1: _e.trys.push([1, 8, 9, 10]); _b = __values(content.matchAll(this.options.regexPattern)), _c = _b.next(); _e.label = 2; case 2: if (!!_c.done) return [3 /*break*/, 7]; match = _c.value; value = match[0]; dPath = path_1.default.resolve(path_1.default.parse(file.path).dir, match[(_a = this.options.regexGroup) !== null && _a !== void 0 ? _a : 1].trim()); // Ignore repeated imports.. if (this.options.importOnce) { if (options.resolveStack.includes(dPath)) { content = content.replace(value, ""); if (!this.options.disableLog) fancy_log_1.default.warn("Repeated import \"" + dPath + "\" in \"" + file.path + "\""); return [3 /*break*/, 6]; } else options.resolveStack.push(dPath); } return [4 /*yield*/, this.getDependencyContent(dPath, transformation)]; case 3: dContent = _e.sent(); if (!this.options.importRecursively) return [3 /*break*/, 5]; return [4 /*yield*/, this.replace({ file: new vinyl_1.default({ path: dPath }), content: dContent, resolveStack: [], transformation: transformation })]; case 4: dContent = _e.sent(); _e.label = 5; case 5: content = content.replace(value, dContent.replace(/\$/g, "$$$$")); this.appendCache(dPath, file); _e.label = 6; case 6: _c = _b.next(); return [3 /*break*/, 2]; case 7: return [3 /*break*/, 10]; case 8: e_2_1 = _e.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 10]; case 9: try { if (_c && !_c.done && (_d = _b.return)) _d.call(_b); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; case 10: if (this.options.detailedLog) fancy_log_1.default.info("Resolved imports for: " + file.path); return [2 /*return*/, content]; } }); }); }; /** * Gets the content of the file at the given path and applies the specified transformation, if any. * @param path The path of the desired file. * @param transformation The optional transformation pipeline to be applied. * @returns The promise that represents the asynchronous operation, containing the processed dependency content. */ Importer.prototype.getDependencyContent = function (path, transformation) { var _a; return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_b) { switch (_b.label) { case 0: if (!!!transformation) return [3 /*break*/, 2]; return [4 /*yield*/, this.transform(path, transformation)]; case 1: return [2 /*return*/, (_a = _b.sent()) !== null && _a !== void 0 ? _a : ""]; case 2: return [4 /*yield*/, this.readFile(path)]; case 3: return [2 /*return*/, _b.sent()]; } }); }); }; /** * Executes the specified transformation for the given input file. * @param path The path of the file to initiate the pipeline. * @param transformation The action for building the transformation pipeline. * @returns The content that's returned by the transformation operation. */ Importer.prototype.transform = function (path, transformation) { var _this = this; return new Promise(function (res, rej) { var that = _this; transformation(vinyl_fs_1.default.src(path)) .pipe(through2_1.default.obj(function (file, _, callback) { if (file.isNull()) { res(null); return; } else if (file.isBuffer()) { res(file.contents.toString(that.options.encoding)); } else if (file.isStream()) { var chunks_1 = []; file.contents .on('error', function (error) { return rej(error); }) .on('data', function (chunk) { return chunks_1.push(chunk); }) .on('end', function () { var result = Buffer.concat(chunks_1).toString(that.options.encoding); res(result); }); } callback(); })); }); }; /** * Reads the content of the file at the specified path. * @param path The path of the desired file. * @returns The promise that represents the asynchronous operation, containing the content of the file, if exists. */ Importer.prototype.readFile = function (path) { var _a; return __awaiter(this, void 0, void 0, function () { var content, _b, _c, error_2; return __generator(this, function (_d) { switch (_d.label) { case 0: _d.trys.push([0, 3, , 4]); _c = (_b = fs_2.promises).readFile; return [4 /*yield*/, this.normalizePath(path)]; case 1: return [4 /*yield*/, _c.apply(_b, [(_a = _d.sent()) !== null && _a !== void 0 ? _a : "", this.options.encoding])]; case 2: content = _d.sent(); if (content instanceof Buffer) return [2 /*return*/, content.toString()]; else return [2 /*return*/, content]; return [3 /*break*/, 4]; case 3: error_2 = _d.sent(); throw new plugin_error_1.default(PLUGIN_NAME, "The path \"" + path + "\" doesn't exist!"); case 4: return [2 /*return*/]; } }); }); }; /** * Returns a readable stream of the file at the specified path. * @param path The path of the desired file. * @returns The promise that represents the asynchronous operation, containing the readable stream of the file, if exists. */ Importer.prototype.readStream = function (path) { var _a; return __awaiter(this, void 0, void 0, function () { var _b, _c, error_3; return __generator(this, function (_d) { switch (_d.label) { case 0: _d.trys.push([0, 2, , 3]); _c = (_b = fs_1.default).createReadStream; return [4 /*yield*/, this.normalizePath(path)]; case 1: return [2 /*return*/, _c.apply(_b, [(_a = _d.sent()) !== null && _a !== void 0 ? _a : "", this.options.encoding])]; case 2: error_3 = _d.sent(); throw new plugin_error_1.default(PLUGIN_NAME, "The path \"" + path + "\" doesn't exist!"); case 3: return [2 /*return*/]; } }); }); }; /** * Normalizes the specified extensionless file path. * @param path The path to be normalized. * @returns The normalized version of the specified path. */ Importer.prototype.normalizePath = function (path) { return __awaiter(this, void 0, void 0, function () { var parsedPath_1, dirContent, match; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!!this.options.requireExtension) return [3 /*break*/, 2]; parsedPath_1 = path_1.default.parse(path); return [4 /*yield*/, fs_2.promises.readdir(parsedPath_1.dir)]; case 1: dirContent = _a.sent(); match = dirContent.find(function (base) { return base.startsWith(parsedPath_1.base); }); if (!match) return [2 /*return*/, null]; parsedPath_1.base = match; path = path_1.default.format(parsedPath_1); _a.label = 2; case 2: return [2 /*return*/, path]; } }); }); }; return Importer; }()); exports.default = Importer;