UNPKG

12.3 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5var __importStar = (this && this.__importStar) || function (mod) {
6 if (mod && mod.__esModule) return mod;
7 var result = {};
8 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
9 result["default"] = mod;
10 return result;
11};
12const EyeglassCompiler = require("broccoli-eyeglass");
13const Eyeglass = require("eyeglass");
14const findHost_1 = __importDefault(require("./findHost"));
15const MergeTrees = require("broccoli-merge-trees");
16const path = __importStar(require("path"));
17const cloneDeep = require("lodash.clonedeep");
18const defaultsDeep = require("lodash.defaultsdeep");
19const broccoli_ln_s_1 = require("./broccoli-ln-s");
20const debugGenerator = require("debug");
21const url_1 = require("url");
22const debug = debugGenerator("ember-cli-eyeglass");
23const debugSetup = debug.extend("setup");
24const debugBuild = debug.extend("build");
25const debugCache = debug.extend("cache");
26const debugAssets = debug.extend("assets");
27const g = global;
28if (!g.EYEGLASS) {
29 g.EYEGLASS = {
30 infoPerAddon: new WeakMap(),
31 infoPerApp: new WeakMap(),
32 projectInfo: {
33 apps: []
34 }
35 };
36}
37const EYEGLASS_INFO_PER_ADDON = g.EYEGLASS.infoPerAddon;
38const EYEGLASS_INFO_PER_APP = g.EYEGLASS.infoPerApp;
39const APPS = g.EYEGLASS.projectInfo.apps;
40//eslint-disable-next-line @typescript-eslint/no-explicit-any
41function isLazyEngine(addon) {
42 if (addon.lazyLoading === true) {
43 // pre-ember-engines 0.5.6 lazyLoading flag
44 return true;
45 }
46 if (addon.lazyLoading && addon.lazyLoading.enabled === true) {
47 return true;
48 }
49 return false;
50}
51//eslint-disable-next-line @typescript-eslint/no-explicit-any
52function getDefaultAssetHttpPrefix(parent) {
53 // the default http prefix differs between Ember app and lazy Ember engine
54 // iterate over the parent's chain and look for a lazy engine or there are
55 // no more parents, which means we've reached the Ember app project
56 let current = parent;
57 while (current.parent) {
58 if (isLazyEngine(current)) {
59 // only lazy engines will inline their assets in the engines-dist folder
60 return `engines-dist/${current.name}/assets`;
61 }
62 current = current.parent;
63 }
64 // at this point, the highlevel container is Ember app and we should use the default 'assets' prefix
65 return 'assets';
66}
67/* addon.addons forms a tree(graph?) of addon objects that allow us to traverse the
68 * ember addon dependencies. However there's no path information in the addon object,
69 * but each addon object has some disconnected metadata in addon.addonPackages
70 * with the path info. Unfortunately there's no shared information that would
71 * allow us to connect which addon packages are actually which addon objects.
72 * It would be great if ember-cli didn't throw that information on the ground
73 * while building these objects. It would also be marvelous if we knew which
74 * addons came from a local addon declaration and which ones came from node
75 * modules.
76 **/
77function localEyeglassAddons(addon) {
78 let paths = new Array();
79 if (typeof addon.addons !== 'object' ||
80 typeof addon.addonPackages !== 'object') {
81 return paths;
82 }
83 let packages = Object.keys(addon.addonPackages);
84 for (let i = 0; i < packages.length; i++) {
85 let p = addon.addonPackages[packages[i]];
86 // Note: this will end up creating manual addons for things in node modules
87 // that are actually auto discovered, these manual modules will get deduped
88 // out. but we need to add all of them because the some eyeglass modules
89 // for addons & engines won't get autodiscovered otherwise unless the
90 // addons/engines are themselves eyeglass modules (which we don't want to require).
91 if (p.pkg.keywords.some(kw => kw == 'eyeglass-module')) {
92 paths.push({ path: p.path });
93 }
94 }
95 // TODO: if there's a cycle in the addon graph don't recurse.
96 for (let i = 0; i < addon.addons.length; i++) {
97 paths = paths.concat(localEyeglassAddons(addon.addons[i]));
98 }
99 return paths;
100}
101const EMBER_CLI_EYEGLASS = {
102 name: require("../package.json").name,
103 included(parent) {
104 this._super.included.apply(this, arguments);
105 this.initSelf();
106 },
107 initSelf() {
108 if (EYEGLASS_INFO_PER_ADDON.has(this))
109 return;
110 let app = findHost_1.default(this);
111 if (!app)
112 return;
113 let isApp = (this.app === app);
114 let name = app.name;
115 if (!isApp) {
116 let thisName = typeof this.parent.name === "function" ? this.parent.name() : this.parent.name;
117 name = `${name}/${thisName}`;
118 }
119 let parentPath = this.parent.root;
120 debugSetup("Initializing %s with eyeglass support for %s at %s", isApp ? "app" : "addon", name, parentPath);
121 if (isApp) {
122 APPS.push(app);
123 // we create the symlinker in persistent mode because there's not a good
124 // way yet to recreate the symlinks when sass files are cached. I would
125 // worry about it more but it seems like the dist directory is cumulative
126 // across builds anyway.
127 EYEGLASS_INFO_PER_APP.set(app, {
128 sessionCache: new Map(),
129 assets: new broccoli_ln_s_1.BroccoliSymbolicLinker({}, { annotation: app.name, persistentOutput: true })
130 });
131 }
132 let addonInfo = { isApp, name, parentPath, app };
133 EYEGLASS_INFO_PER_ADDON.set(this, addonInfo);
134 },
135 postBuild(_result) {
136 debugBuild("Build Succeeded.");
137 this._resetCaches();
138 },
139 _resetCaches() {
140 debugCache("clearing eyeglass global cache");
141 Eyeglass.resetGlobalCaches();
142 for (let app of APPS) {
143 let appInfo = EYEGLASS_INFO_PER_APP.get(app);
144 appInfo.assets.reset();
145 debugCache("clearing %d cached items from the eyeglass build cache for %s", appInfo.sessionCache.size, app.name);
146 appInfo.sessionCache.clear();
147 }
148 },
149 buildError(_error) {
150 debugBuild("Build Failed.");
151 this._resetCaches();
152 },
153 postprocessTree(type, tree) {
154 let addonInfo = EYEGLASS_INFO_PER_ADDON.get(this);
155 if (type === "all" && addonInfo.isApp) {
156 debugBuild("Merging eyeglass asset tree with the '%s' tree", type);
157 let appInfo = EYEGLASS_INFO_PER_APP.get(addonInfo.app);
158 return new MergeTrees([tree, appInfo.assets], { overwrite: true });
159 }
160 else {
161 return tree;
162 }
163 },
164 setupPreprocessorRegistry(type, registry) {
165 let addon = this;
166 registry.add('css', {
167 name: 'eyeglass',
168 ext: 'scss',
169 toTree: (tree, inputPath, outputPath) => {
170 // These start with a slash and that messes things up.
171 let cssDir = outputPath.slice(1) || './';
172 let sassDir = inputPath.slice(1) || './';
173 let { app, name } = EYEGLASS_INFO_PER_ADDON.get(this);
174 let extracted = this.extractConfig(app, addon);
175 extracted.cssDir = cssDir;
176 extracted.sassDir = sassDir;
177 const config = this.setupConfig(extracted);
178 debugSetup("Broccoli Configuration for %s: %O", name, config);
179 let httpRoot = config.eyeglass && config.eyeglass.httpRoot || "/";
180 let compiler = new EyeglassCompiler(tree, config);
181 compiler.events.on("cached-asset", (absolutePathToSource, httpPathToOutput) => {
182 debugBuild("will symlink %s to %s", absolutePathToSource, httpPathToOutput);
183 try {
184 this.linkAsset(absolutePathToSource, httpRoot, httpPathToOutput);
185 }
186 catch (e) {
187 // pass this only happens with a cache after downgrading ember-cli.
188 }
189 });
190 return compiler;
191 }
192 });
193 },
194 extractConfig(host, addon) {
195 const isNestedAddon = typeof addon.parent.parent === 'object';
196 // setup eyeglass for this project's configuration
197 const hostConfig = cloneDeep(host.options.eyeglass || {});
198 const addonConfig = isNestedAddon ? cloneDeep(addon.parent.options.eyeglass || {}) : {};
199 return defaultsDeep(addonConfig, hostConfig);
200 },
201 linkAsset(srcFile, httpRoot, destUri) {
202 let rootPath = httpRoot.startsWith("/") ? httpRoot.substring(1) : httpRoot;
203 let destPath = destUri.startsWith("/") ? destUri.substring(1) : destUri;
204 if (process.platform === "win32") {
205 destPath = convertURLToPath(destPath);
206 rootPath = convertURLToPath(rootPath);
207 }
208 if (destPath.startsWith(rootPath)) {
209 destPath = path.relative(rootPath, destPath);
210 }
211 let { app } = EYEGLASS_INFO_PER_ADDON.get(this);
212 let { assets } = EYEGLASS_INFO_PER_APP.get(app);
213 debugAssets("Will link asset %s to %s to expose it at %s relative to %s", srcFile, destPath, destUri, httpRoot);
214 return assets.ln_s(srcFile, destPath);
215 },
216 setupConfig(config, options) {
217 let { isApp, app, parentPath } = EYEGLASS_INFO_PER_ADDON.get(this);
218 let { sessionCache } = EYEGLASS_INFO_PER_APP.get(app);
219 config.sessionCache = sessionCache;
220 config.annotation = `EyeglassCompiler(${parentPath})`;
221 if (!config.sourceFiles && !config.discover) {
222 config.sourceFiles = [isApp ? 'app.scss' : 'addon.scss'];
223 }
224 config.assets = ['public', 'app'].concat(config.assets || []);
225 config.eyeglass = config.eyeglass || {};
226 // XXX We don't set the root anywhere but I'm not sure what might break if we do.
227 // config.eyeglass.root = parentPath;
228 config.eyeglass.httpRoot = config.eyeglass.httpRoot || config["httpRoot"];
229 if (config.persistentCache) {
230 let cacheDir = parentPath.replace(/\//g, "$");
231 config.persistentCache += `/${cacheDir}`;
232 }
233 config.assetsHttpPrefix = config.assetsHttpPrefix || getDefaultAssetHttpPrefix(this.parent);
234 if (config.eyeglass.modules) {
235 config.eyeglass.modules =
236 config.eyeglass.modules.concat(localEyeglassAddons(this.parent));
237 }
238 else {
239 config.eyeglass.modules = localEyeglassAddons(this.parent);
240 }
241 let originalConfigureEyeglass = config.configureEyeglass;
242 config.configureEyeglass = (eyeglass, sass, details) => {
243 eyeglass.assets.installer((file, uri, fallbackInstaller, cb) => {
244 try {
245 cb(null, this.linkAsset(file, eyeglass.options.eyeglass.httpRoot || "/", uri));
246 }
247 catch (e) {
248 cb(e);
249 }
250 });
251 if (originalConfigureEyeglass) {
252 originalConfigureEyeglass(eyeglass, sass, details);
253 }
254 };
255 // If building an app, rename app.css to <project>.css per Ember conventions.
256 // Otherwise, we're building an addon, so rename addon.css to <name-of-addon>.css.
257 let originalGenerator = config.optionsGenerator;
258 config.optionsGenerator = (sassFile, cssFile, sassOptions, compilationCallback) => {
259 if (isApp) {
260 cssFile = cssFile.replace(/app\.css$/, `${this.app.name}.css`);
261 }
262 else {
263 cssFile = cssFile.replace(/addon\.css$/, `${this.parent.name}.css`);
264 }
265 if (originalGenerator) {
266 originalGenerator(sassFile, cssFile, sassOptions, compilationCallback);
267 }
268 else {
269 compilationCallback(cssFile, sassOptions);
270 }
271 };
272 return config;
273 }
274};
275function convertURLToPath(fragment) {
276 return (new url_1.URL(`file://${fragment}`)).pathname;
277}
278module.exports = EMBER_CLI_EYEGLASS;
279//# sourceMappingURL=index.js.map
\No newline at end of file