UNPKG

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