UNPKG

eyeglass

Version:
219 lines (193 loc) 7.2 kB
import EyeglassModules, { resetGlobalCaches as resetGlobalModuleCaches } from "./modules/EyeglassModules"; import ModuleFunctions from "./modules/ModuleFunctions"; import ModuleImporter from "./importers/ModuleImporter"; import AssetImporter from "./importers/AssetImporter"; import FSImporter from "./importers/FSImporter"; import Options, {Options as Opts, Config, ForbiddenOptions, EyeglassConfig} from "./util/Options"; import Assets from "./assets/Assets"; import deprecator, { DeprecateFn } from "./util/deprecator"; import semverChecker from "./util/semverChecker"; import * as fs from "fs-extra"; import { IEyeglass } from "./IEyeglass"; import type { SassFunction, AsyncImporter } from "node-sass"; import { SassImplementation, helpers as sassHelpers, isSassImplementation } from "./util/SassImplementation"; import { UnsafeDict, Dict } from "./util/typescriptUtils"; import heimdall = require("heimdalljs"); import { SimpleCache } from "./util/SimpleCache"; import { resetGlobalCaches as resetGlobalFSCaches } from "./util/perf"; import eyeglassVersion from "./util/version"; import debugGenerator from "debug"; const debug = debugGenerator("eyeglass:initialization"); export function resetGlobalCaches(): void { resetGlobalModuleCaches(); resetGlobalFSCaches(); } export default class Eyeglass implements IEyeglass { static VERSION: string = eyeglassVersion.string; deprecate: DeprecateFn; options: Config; assets: Assets; modules: EyeglassModules; private onceCache: SimpleCache<true>; constructor(options: Opts) { if (arguments.length === 2) { _forbidNodeSassArg(arguments[1]); } let timer = heimdall.start("eyeglass:instantiation"); this.onceCache = new SimpleCache<true>(); try { // an interface for deprecation warnings this.deprecate = deprecator(options); this.options = new Options(options); this.assets = new Assets(this, this.options.eyeglass.engines.sass); this.modules = new EyeglassModules( this.options.eyeglass.root, this.options, this.options.eyeglass.modules, ); fs.mkdirpSync(this.options.eyeglass.cacheDir); semverChecker(this, this.options.eyeglass.engines.sass, this.options.eyeglass, Eyeglass.VERSION); checkMissingDependencies.call(this); checkDependencyConflicts.call(this); // initialize all the modules this.modules.init(this, this.options.eyeglass.engines.sass); // add importers and functions addImporters.call(this); addFunctions.call(this); forbidProperties.call(this, ["enableImportOnce"]); // auto-add asset paths specified via options if (this.options.eyeglass.assets.sources) { for (let assetSource of this.options.eyeglass.assets.sources) { this.assets.addSource(assetSource.directory, assetSource); } } } catch(e) { // typescript needs this catch & throw to convince it that the instance properties are initialized. throw e; } finally { timer.stop(); } } static helpers(sass: SassImplementation): ReturnType<typeof sassHelpers> { return sassHelpers(sass); } once<R>(key: string, firstTime: () => R): R | undefined; // eslint-disable-next-line no-dupe-class-members once<R>(key: string, firstTime: () => R, otherwise: () => R): R; // eslint-disable-next-line no-dupe-class-members once<R>(key: string, firstTime: () => R, otherwise?: () => R): R | undefined { if (this.onceCache.has(key)) { if (otherwise) { return otherwise(); } else { return; } } else { this.onceCache.set(key, true); return firstTime(); } } } export function _forbidNodeSassArg(arg: unknown): Pick<EyeglassConfig, "engines"> | void { if (isSassImplementation(arg)) { // throw an error throw new Error([ "You may no longer pass `sass` directly to Eyeglass. Instead pass it as an option:", "var options = eyeglass({", " /* sassOptions */", " ...", " eyeglass: {", " engines: {", " sass: require('node-sass')", " }", " }", "});" ].join("\n ")); } } const VERSION_WARNINGS_ISSUED: Dict<boolean> = {}; function checkDependencyConflicts(this: IEyeglass): void { let conflicts = this.modules.issues.dependencies.versions; let strictMode = this.options.eyeglass.strictModuleVersions; if (typeof strictMode === "undefined") { strictMode = "warn"; } for (let conflict of conflicts) { let message = `Version conflict for eyeglass module '${conflict.name}': ${conflict.requested.version} was requested but it was globally resolved to ${conflict.resolved.version}.`; if (strictMode === false) { debug(message); } else if (strictMode === "warn") { if (!VERSION_WARNINGS_ISSUED[message]) { console.error(`WARNING: ${message}`); VERSION_WARNINGS_ISSUED[message] = true; } } else if (strictMode === true) { throw new Error(message); } } } function checkMissingDependencies(this: IEyeglass): void { let missing = this.modules.issues.dependencies.missing; if (missing.length) { let warning = ["The following dependencies were not found:"]; warning.push.apply(warning, missing.map(function(dep) { return " " + dep; })); warning.push("You might need to `npm install` the above."); // eslint-disable-next-line no-console console.warn(warning.join("\n")); } } function addImporters(this: IEyeglass): void { let fsImporter = FSImporter( this, this.options.eyeglass.engines.sass, this.options, this.options.importer as AsyncImporter ); let assetImporter = AssetImporter( this, this.options.eyeglass.engines.sass, this.options, fsImporter ); this.options.importer = ModuleImporter( this, this.options.eyeglass.engines.sass, this.options, assetImporter ); } function addFunctions(this: IEyeglass): void { this.options.functions = ModuleFunctions( this, this.options.eyeglass.engines.sass, this.options, this.options.functions as UnsafeDict<SassFunction> // The type of @types/node-sass/Options["functions"] is bad. ); } function forbidProperties(this: IEyeglass, properties: Array<keyof ForbiddenOptions | "enableImportOnce">): void { for (let prop of properties) { Object.defineProperty(this, prop, { get: function(this: IEyeglass) { throw new Error( "The property `" + prop + "` may no longer be accessed directly on eyeglass. " + "Instead, you'll find the value on `eyeglass.options.eyeglass." + prop + "`" ); }, set: function(this: IEyeglass, _value: EyeglassConfig[typeof prop]) { throw new Error( "The property `" + prop + "` may no longer be set directly on eyeglass. " + "Instead, you should pass this as an option to eyeglass:" + "\n var options = eyeglass({" + "\n /* sassOptions */" + "\n ..." + "\n eyeglass: {" + "\n " + prop + ": ..." + "\n }" + "\n });" ); } }); } }