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
JavaScript
;
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;