UNPKG

ember-cli-eyeglass

Version:
344 lines 15.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; const EyeglassCompiler = require("broccoli-eyeglass"); const Eyeglass = require("eyeglass"); const findHost_1 = __importDefault(require("./findHost")); const funnel = require("broccoli-funnel"); const MergeTrees = require("broccoli-merge-trees"); const path = __importStar(require("path")); const cloneDeep = require("lodash.clonedeep"); const defaultsDeep = require("lodash.defaultsdeep"); const broccoli_ln_s_1 = require("./broccoli-ln-s"); const debugGenerator = require("debug"); const BroccoliDebug = require("broccoli-debug"); const url_1 = require("url"); const debug = debugGenerator("ember-cli-eyeglass"); const debugSetup = debug.extend("setup"); const debugBuild = debug.extend("build"); const debugCache = debug.extend("cache"); const debugAssets = debug.extend("assets"); const g = global; if (!g.EYEGLASS) { g.EYEGLASS = { infoPerAddon: new WeakMap(), infoPerApp: new WeakMap(), projectInfo: { apps: [], } }; } const EYEGLASS_INFO_PER_ADDON = g.EYEGLASS.infoPerAddon; const EYEGLASS_INFO_PER_APP = g.EYEGLASS.infoPerApp; const APPS = g.EYEGLASS.projectInfo.apps; //eslint-disable-next-line @typescript-eslint/no-explicit-any function isLazyEngine(addon) { if (addon.lazyLoading === true) { // pre-ember-engines 0.5.6 lazyLoading flag return true; } if (addon.lazyLoading && addon.lazyLoading.enabled === true) { return true; } return false; } //eslint-disable-next-line @typescript-eslint/no-explicit-any function embroiderEnabled(config) { var _a; return (_a = config.embroiderEnabled) !== null && _a !== void 0 ? _a : false; } //eslint-disable-next-line @typescript-eslint/no-explicit-any function getDefaultAssetHttpPrefix(parent, config) { // the default http prefix differs between Ember app and lazy Ember engine // iterate over the parent's chain and look for a lazy engine or there are // no more parents, which means we've reached the Ember app project let current = parent; while (current.parent) { if (isLazyEngine(current) && !embroiderEnabled(config)) { // only lazy engines will inline their assets in the engines-dist folder return `engines-dist/${current.name}/assets`; } else if (isEngine(current)) { return `${current.name}/assets`; } current = current.parent; } // at this point, the highlevel container is Ember app and we should use the default 'assets' prefix return 'assets'; } /* addon.addons forms a tree(graph?) of addon objects that allow us to traverse the * ember addon dependencies. However there's no path information in the addon object, * but each addon object has some disconnected metadata in addon.addonPackages * with the path info. Unfortunately there's no shared information that would * allow us to connect which addon packages are actually which addon objects. * It would be great if ember-cli didn't throw that information on the ground * while building these objects. It would also be marvelous if we knew which * addons came from a local addon declaration and which ones came from node * modules. **/ function localEyeglassAddons(addon) { let paths = new Array(); if (typeof addon.addons !== 'object' || typeof addon.addonPackages !== 'object') { return paths; } let packages = Object.keys(addon.addonPackages); for (let i = 0; i < packages.length; i++) { let p = addon.addonPackages[packages[i]]; // Note: this will end up creating manual addons for things in node modules // that are actually auto discovered, these manual modules will get deduped // out. but we need to add all of them because the some eyeglass modules // for addons & engines won't get autodiscovered otherwise unless the // addons/engines are themselves eyeglass modules (which we don't want to require). if (p.pkg.keywords.some(kw => kw == 'eyeglass-module')) { paths.push({ path: p.path }); } } // TODO: if there's a cycle in the addon graph don't recurse. for (let i = 0; i < addon.addons.length; i++) { paths = paths.concat(localEyeglassAddons(addon.addons[i])); } return paths; } const EMBER_CLI_EYEGLASS = { name: require("../package.json").name, included(parent) { this._super.included.apply(this, arguments); this.initSelf(); }, initSelf() { if (EYEGLASS_INFO_PER_ADDON.has(this)) return; let app = findHost_1.default(this); if (!app) return; let isApp = (this.app === app); let name = app.name; if (!isApp) { let thisName = typeof this.parent.name === "function" ? this.parent.name() : this.parent.name; name = `${name}/${thisName}`; } let parentPath = this.parent.root; debugSetup("Initializing %s with eyeglass support for %s at %s", isApp ? "app" : "addon", name, parentPath); if (isApp) { APPS.push(app); // we create the symlinker in persistent mode because there's not a good // way yet to recreate the symlinks when sass files are cached. I would // worry about it more but it seems like the dist directory is cumulative // across builds anyway. EYEGLASS_INFO_PER_APP.set(app, { sessionCache: new Map(), assets: new broccoli_ln_s_1.BroccoliSymbolicLinker({}, { annotation: app.name, persistentOutput: true }) }); } let addonInfo = { isApp, name, parentPath, app, isEngine: isEngine(this.parent), assets: new broccoli_ln_s_1.BroccoliSymbolicLinker({}, { annotation: `Eyeglass Assets for ${app.name}/${name}`, persistentOutput: true, }), }; EYEGLASS_INFO_PER_ADDON.set(this, addonInfo); }, postBuild(_result) { debugBuild("Build Succeeded."); this._resetCaches(); }, _resetCaches() { debugCache("clearing eyeglass global cache"); Eyeglass.resetGlobalCaches(); for (let app of APPS) { let appInfo = EYEGLASS_INFO_PER_APP.get(app); let addonInfo = EYEGLASS_INFO_PER_ADDON.get(this); let extracted = this.extractConfig(addonInfo.app, this); if (!embroiderEnabled(extracted)) { appInfo.assets.reset(); } debugCache("clearing %d cached items from the eyeglass build cache for %s", appInfo.sessionCache.size, app.name); appInfo.sessionCache.clear(); } }, buildError(_error) { debugBuild("Build Failed."); this._resetCaches(); }, postprocessTree(type, tree) { let addon = this; let addonInfo = EYEGLASS_INFO_PER_ADDON.get(this); let extracted = this.extractConfig(addonInfo.app, addon); // This code is intended only for classic ember builds. // (There's no "all" postprocess tree in embroider.) // Skip this entirely for embroider builds (we'll include // assets on a per-addon basis in the CSS tree). if (embroiderEnabled(extracted)) { return tree; } if (type === "all" && addonInfo.isApp) { debugBuild("Merging eyeglass asset tree with the '%s' tree", type); let appInfo = EYEGLASS_INFO_PER_APP.get(addonInfo.app); return new MergeTrees([tree, appInfo.assets], { overwrite: true }); } else { return tree; } }, setupPreprocessorRegistry(type, registry) { let addon = this; registry.add('css', { name: 'eyeglass', ext: 'scss', toTree: (tree, inputPath, outputPath) => { // These start with a slash and that messes things up. let cssDir = outputPath.slice(1) || './'; let sassDir = inputPath.slice(1) || './'; let { app, name, isApp } = EYEGLASS_INFO_PER_ADDON.get(this); tree = new BroccoliDebug(tree, `ember-cli-eyeglass:${name}:input`); let extracted = this.extractConfig(app, addon); extracted.cssDir = cssDir; extracted.sassDir = sassDir; const config = this.setupConfig(extracted); debugSetup("Broccoli Configuration for %s: %O", name, config); let httpRoot = config.eyeglass && config.eyeglass.httpRoot || "/"; let addonInfo = EYEGLASS_INFO_PER_ADDON.get(this); let compiler = new EyeglassCompiler(tree, config); compiler.events.on("cached-asset", (absolutePathToSource, httpPathToOutput) => { debugBuild("will symlink %s to %s", absolutePathToSource, httpPathToOutput); try { this.linkAsset(absolutePathToSource, httpRoot, httpPathToOutput); } catch (e) { // pass this only happens with a cache after downgrading ember-cli. } }); if (embroiderEnabled(config)) { compiler.events.on("build", () => { addonInfo.assets.reset(); }); } let withoutSassFiles = funnel(tree, { srcDir: (isApp && !embroiderEnabled(config)) ? 'app/styles' : undefined, destDir: isApp ? 'assets' : undefined, exclude: ['**/*.s{a,c}ss'], allowEmpty: true, }); let trees = [withoutSassFiles, compiler]; if (embroiderEnabled(config)) { // Push the addon assets tree on to this build only if we're using embroider. // (For classic builds, the postprocess tree will handle this.) trees.push(addonInfo.assets); } let result = new MergeTrees(trees, { overwrite: true }); return new BroccoliDebug(result, `ember-cli-eyeglass:${name}:output`); } }); }, extractConfig(host, addon) { const isNestedAddon = typeof addon.parent.parent === 'object'; // setup eyeglass for this project's configuration const hostConfig = cloneDeep(host.options.eyeglass || {}); const addonConfig = isNestedAddon ? cloneDeep(addon.parent.options.eyeglass || {}) : {}; return defaultsDeep(addonConfig, hostConfig); }, linkAsset(srcFile, httpRoot, destUri, config) { let rootPath = httpRoot.startsWith("/") ? httpRoot.substring(1) : httpRoot; let destPath = destUri.startsWith("/") ? destUri.substring(1) : destUri; if (process.platform === "win32") { destPath = convertURLToPath(destPath); rootPath = convertURLToPath(rootPath); } if (destPath.startsWith(rootPath)) { destPath = path.relative(rootPath, destPath); } let assets; if (embroiderEnabled(config)) { assets = EYEGLASS_INFO_PER_ADDON.get(this).assets; } else { let { app } = EYEGLASS_INFO_PER_ADDON.get(this); assets = EYEGLASS_INFO_PER_APP.get(app).assets; } debugAssets("Will link asset %s to %s to expose it at %s relative to %s", srcFile, destPath, destUri, httpRoot); return assets.ln_s(srcFile, destPath); }, setupConfig(config, options) { let { isApp, app, parentPath } = EYEGLASS_INFO_PER_ADDON.get(this); let { sessionCache } = EYEGLASS_INFO_PER_APP.get(app); config.sessionCache = sessionCache; config.annotation = `EyeglassCompiler(${parentPath})`; if (!config.sourceFiles && !config.discover) { config.sourceFiles = [isApp ? 'app.scss' : 'addon.scss']; } config.assets = ['public', 'app'].concat(config.assets || []); config.eyeglass = config.eyeglass || {}; // XXX We don't set the root anywhere but I'm not sure what might break if we do. // config.eyeglass.root = parentPath; config.eyeglass.httpRoot = config.eyeglass.httpRoot || config["httpRoot"]; if (config.persistentCache) { let cacheDir = parentPath.replace(/\//g, "$"); config.persistentCache += `/${cacheDir}`; } config.assetsHttpPrefix = config.assetsHttpPrefix || getDefaultAssetHttpPrefix(this.parent, config); if (config.eyeglass.modules) { config.eyeglass.modules = config.eyeglass.modules.concat(localEyeglassAddons(this.parent)); } else { config.eyeglass.modules = localEyeglassAddons(this.parent); } let originalConfigureEyeglass = config.configureEyeglass; config.configureEyeglass = (eyeglass, sass, details) => { eyeglass.assets.installer((file, uri, fallbackInstaller, cb) => { try { cb(null, this.linkAsset(file, eyeglass.options.eyeglass.httpRoot || "/", uri, config)); } catch (e) { cb(e); } }); if (originalConfigureEyeglass) { originalConfigureEyeglass(eyeglass, sass, details); } }; // If building an app, rename app.css to <project>.css per Ember conventions. // Otherwise, we're building an addon, so rename addon.css to <name-of-addon>.css. let originalGenerator = config.optionsGenerator; config.optionsGenerator = (sassFile, cssFile, sassOptions, compilationCallback) => { if (isApp) { cssFile = cssFile.replace(/app\.css$/, `${this.app.name}.css`); } else { cssFile = cssFile.replace(/addon\.css$/, `${this.parent.name}.css`); } if (originalGenerator) { originalGenerator(sassFile, cssFile, sassOptions, compilationCallback); } else { compilationCallback(cssFile, sassOptions); } }; return config; } }; function isEngine(appOrAddon) { let keywords = appOrAddon._packageInfo.pkg.keywords || new Array(); return keywords.includes("ember-engine"); } function convertURLToPath(fragment) { return (new url_1.URL(`file://${fragment}`)).pathname; } module.exports = EMBER_CLI_EYEGLASS; //# sourceMappingURL=index.js.map