Skip to content

Commit

Permalink
Merge branch 'perf_tuning'
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseppstein committed Mar 23, 2019
2 parents 5982efb + b1de2db commit fc08edf
Show file tree
Hide file tree
Showing 21 changed files with 331 additions and 273 deletions.
2 changes: 1 addition & 1 deletion packages/ember-cli-eyeglass/package.json
Expand Up @@ -37,7 +37,7 @@
"broccoli-stew": "^2.0.0",
"chai": "^4.1.2",
"eager": "file:./tests/dummy/lib/eager/",
"ember-cli": "^3.3.0",
"ember-cli": "^3.8.1",
"ember-cli-app-version": "^3.1.0",
"ember-cli-babel": "^6.16.0",
"ember-cli-dependency-checker": "^3.0.0",
Expand Down
12 changes: 4 additions & 8 deletions packages/ember-cli-eyeglass/src/index.ts
@@ -1,4 +1,5 @@
import EyeglassCompiler = require('broccoli-eyeglass');
import Eyeglass = require('eyeglass');
import findHost from "./findHost";
import Funnel = require('broccoli-funnel');
import MergeTrees = require('broccoli-merge-trees');
Expand Down Expand Up @@ -78,6 +79,9 @@ function localEyeglassAddons(addon): Array<{path: string}> {

const EMBER_CLI_EYEGLASS = {
name: 'ember-cli-eyeglass',
postBuild(result) {
Eyeglass.resetGlobalCaches();
},
setupPreprocessorRegistry(type, registry) {
let addon = this;

Expand All @@ -92,14 +96,6 @@ const EMBER_CLI_EYEGLASS = {
let host = findHost(addon);
let inApp = (host === addon.app);

if (path.posix.join(sassDir, '/**/*') === '**/*') {
// limit to only files in the sass directory,
// but don't bother funneling if we just want everything anyways e.g. **/*
tree = new Funnel(tree, {
include: [ path.join(sassDir, '/**/*') ]
});
}

let extracted = this.extractConfig(host, addon);

extracted.cssDir = cssDir;
Expand Down
4 changes: 3 additions & 1 deletion packages/eyeglass/package.json
Expand Up @@ -44,6 +44,7 @@
"json-stable-stringify": "^1.0.1",
"lodash.includes": "^4.3.0",
"lodash.merge": "^4.6.1",
"lru-cache": "^5.1.1",
"node-sass": "^4.0.0 || ^3.10.1",
"node-sass-utils": "^1.1.2",
"semver": "^5.6.0"
Expand All @@ -57,17 +58,18 @@
"@types/json-stable-stringify": "^1.0.32",
"@types/lodash.includes": "^4.3.4",
"@types/lodash.merge": "^4.6.4",
"@types/lru-cache": "^5.1.0",
"@types/node": "^10.7.1",
"@types/node-sass": "^4.11.0",
"@types/package-json": "^5.0.0",
"@types/semver": "^5.5.0",
"@typescript-eslint/eslint-plugin": "^1.2.0",
"@typescript-eslint/parser": "^1.2.0",
"eslint": "^5.12.1",
"heimdalljs": "^0.2.6",
"grunt": "^1.0.3",
"grunt-release": "^0.14.0",
"handlebars": "^4.0.12",
"heimdalljs": "^0.2.6",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"should": "^13.2.3",
Expand Down
28 changes: 27 additions & 1 deletion packages/eyeglass/src/Eyeglass.ts
@@ -1,4 +1,4 @@
import EyeglassModules from "./modules/EyeglassModules";
import EyeglassModules, { resetGlobalCaches as resetGlobalModuleCaches } from "./modules/EyeglassModules";
import ModuleFunctions from "./modules/ModuleFunctions";
import ModuleImporter from "./importers/ModuleImporter";
import AssetImporter from "./importers/AssetImporter";
Expand All @@ -15,19 +15,28 @@ import { SassImplementation, helpers as sassHelpers } from "./util/SassImplement
import { AsyncImporter } from "node-sass";
import { UnsafeDict } from "./util/typescriptUtils";
import heimdall = require("heimdalljs");
import { SimpleCache } from "./util/SimpleCache";
import { resetGlobalCaches as resetGlobalFSCaches } from "./util/perf";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg: PackageJson = require("../package.json");

export function resetGlobalCaches(): void {
resetGlobalModuleCaches();
resetGlobalFSCaches();
}

export default class Eyeglass implements IEyeglass {
static VERSION = pkg.version!;

deprecate: DeprecateFn;
options: Config;
assets: Assets;
modules: EyeglassModules;
private onceCache: SimpleCache<true>;

constructor(options: Opts, deprecatedNodeSassArg?: SassImplementation) {
let timer = heimdall.start("eyeglass:instantiation");
this.onceCache = new SimpleCache<true>();
try {
// an interface for deprecation warnings
this.deprecate = deprecator(options);
Expand Down Expand Up @@ -80,6 +89,23 @@ export default class Eyeglass implements IEyeglass {
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();
}
}
}

function checkMissingDependencies(this: IEyeglass): void {
Expand Down
2 changes: 2 additions & 0 deletions packages/eyeglass/src/IEyeglass.ts
Expand Up @@ -4,6 +4,8 @@ import { DeprecateFn } from "./util/deprecator";
import EyeglassModules from "./modules/EyeglassModules";

export interface IEyeglass {
once<R>(key: string, firstTime: () => R): R | undefined;
once<R>(key: string, firstTime: () => R, otherwise: () => R): R;
modules: EyeglassModules;
deprecate: DeprecateFn;
options: Config;
Expand Down
115 changes: 80 additions & 35 deletions packages/eyeglass/src/assets/Assets.ts
Expand Up @@ -4,7 +4,7 @@ import * as path from "path";
import { IEyeglass } from "../IEyeglass";
import * as debug from "../util/debug";
import { AssetSourceOptions } from "../util/Options";
import { isType, SassImplementation, SassTypeError } from "../util/SassImplementation";
import { isType, SassImplementation, SassTypeError, isSassMap, isSassString } from "../util/SassImplementation";
import * as sass from "node-sass";
import { URI } from "../util/URI";

Expand Down Expand Up @@ -40,7 +40,6 @@ interface Installs {
export default class Assets implements Resolves, Installs {
// need types for sass utils
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sassUtils: any;
eyeglass: IEyeglass;
/**
* Assets declared by the application.
Expand All @@ -52,8 +51,9 @@ export default class Assets implements Resolves, Installs {
moduleCollections: Array<AssetsCollection>;
AssetCollection: () => AssetsCollection;
AssetPathEntry: (src: string, options: AssetSourceOptions) => AssetsSource;
constructor(eyeglass: IEyeglass, sass: SassImplementation) {
this.sassUtils = require("node-sass-utils")(sass);
sassImpl: typeof sass;
constructor(eyeglass: IEyeglass, sassImpl: SassImplementation) {
this.sassImpl = sassImpl;
this.eyeglass = eyeglass;
// create a master collection
this.collection = new AssetsCollection(eyeglass.options);
Expand Down Expand Up @@ -132,18 +132,28 @@ export default class Assets implements Resolves, Installs {

// normalize the uri and resolve it

let data = this.resolveAssetDefaults($assetsMap, uri.getPath());
if (data) {
let filepath = URI.restore(data.coerce.get("filepath"));
let $data = this.resolveAssetDefaults($assetsMap, uri.getPath());
if ($data) {
let filepath: string | undefined;
let assetUri: string | undefined;
for (let i = 0; i < $data.getLength(); i++) {
let k = ($data.getKey(i) as sass.types.String).getValue();
let v = ($data.getValue(i) as sass.types.String).getValue();
if (k === "filepath") {
filepath = v;
} else if (k === "uri") {
assetUri = v;
}
}

// create the URI
let fullUri = URI.join(
options.httpRoot,
options.assets.httpPrefix,
data.coerce.get("uri")
assetUri
);

assets.resolve(filepath, fullUri, function(error, assetInfo) {
assets.resolve(filepath!, fullUri, function(error, assetInfo) {
if (error || !isPresent(assetInfo)) {
cb(errorFor(error, "Unable to resolve asset"));
} else {
Expand All @@ -163,7 +173,7 @@ export default class Assets implements Resolves, Installs {
uri.addQuery(assetInfo.query);
}

assets.install(filepath, assetInfo.path, function(error, file) {
assets.install(filepath!, assetInfo.path, function(error, file) {
if (error) {
cb(errorFor(error, "Unable to install asset"));
} else {
Expand Down Expand Up @@ -220,19 +230,24 @@ export default class Assets implements Resolves, Installs {

let dest = path.join(options.buildDir, uri);

try {
if (options.installWithSymlinks) {
fs.mkdirpSync(path.dirname(dest));
this.eyeglass.once(`install:${dest}`, () => {
try {
if (options.installWithSymlinks) {
fs.mkdirpSync(path.dirname(dest));

ensureSymlink(file, dest);
} else {
// we explicitly use copySync rather than copy to avoid starving system resources
fs.copySync(file, dest);
ensureSymlink(file, dest);
} else {
// we explicitly use copySync rather than copy to avoid starving system resources
fs.copySync(file, dest);
}
cb(null, dest);
} catch (error) {
cb(errorFor(error, `Failed to install asset from ${file}:\n`));
}
}, () => {
cb(null, dest);
} catch (error) {
cb(errorFor(error, `Failed to install asset from ${file}:\n`));
}
});

} else {
cb(null, file);
}
Expand All @@ -247,32 +262,62 @@ export default class Assets implements Resolves, Installs {
installer(assetFile, assetUri, oldInstaller, cb);
};
}

// need types for sass utils
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private resolveAssetDefaults(registeredAssetsMap: sass.types.Map, relativePath: string): any {
registeredAssetsMap = this.sassUtils.handleEmptyMap(registeredAssetsMap);
this.sassUtils.assertType(registeredAssetsMap, "map");
private resolveAssetDefaults($registeredAssetsMap: sass.types.Map, relativePath: string): sass.types.Map | undefined {

let registeredAssets = this.sassUtils.castToJs(registeredAssetsMap);
let appAssets: sass.types.Map | undefined;
let moduleAssets: sass.types.Map | undefined;
let moduleName: string | undefined, moduleRelativePath: string | undefined;
let slashAt = relativePath.indexOf("/");
if (slashAt > 0) {
moduleName = relativePath.substring(0,slashAt);
moduleRelativePath = relativePath.substring(slashAt + 1)
}

let appAssets = registeredAssets.coerce.get(null);
let size = $registeredAssetsMap.getLength();
for (let i = 0; i < size; i++) {
let k = $registeredAssetsMap.getKey(i);
if (k === this.sassImpl.NULL) {
let v = $registeredAssetsMap.getValue(i);
if (isSassMap(this.sassImpl, v)) {
appAssets = v;
}
} else if (isSassString(this.sassImpl, k) && k.getValue() === moduleName) {
let v = $registeredAssetsMap.getValue(i);
if (isSassMap(this.sassImpl, v)) {
moduleAssets = v;
}
}
}

if (appAssets) {
// XXX sassUtils.assertType(appAssets, "map");
let appAsset = appAssets.coerce.get(relativePath);
if (appAsset) {
return appAsset;
let size = appAssets.getLength();
for (let i = 0; i < size; i++) {
let k = appAssets.getKey(i);
if (isSassString(this.sassImpl, k) && k.getValue() === relativePath) {
let v = appAssets.getValue(i);
if (isSassMap(this.sassImpl, v)) {
return v;
}
}
}
}

let segments = relativePath.split("/");
let moduleName = segments.shift();
let moduleRelativePath = segments.join("/");
let moduleAssets = registeredAssets.coerce.get(moduleName);
if (moduleAssets) {
// XXX sassUtils.assertType(moduleAssets, "map");
return moduleAssets.coerce.get(moduleRelativePath);
let size = moduleAssets.getLength();
for (let i = 0; i < size; i++) {
let k = moduleAssets.getKey(i);
if (isSassString(this.sassImpl, k) && k.getValue() === moduleRelativePath) {
let v = moduleAssets.getValue(i);
if (isSassMap(this.sassImpl, v)) {
return v;
}
}
}
}
return;
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/eyeglass/src/functions/fs.ts
Expand Up @@ -8,6 +8,7 @@ import { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import * as nodeSass from "node-sass";
import { unreachable } from "../util/assertions";
import { EyeglassFunctions } from "./EyeglassFunctions";
import { realpathSync } from "../util/perf";

function pathInSandboxDir(fsPath: string, sandboxDir: string): boolean {
if (path.relative(sandboxDir, fsPath).match(/^\.\./)) {
Expand Down Expand Up @@ -166,7 +167,7 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
done(sass.types.Error(err.message));
} else {
try {
let realpath = fs.realpathSync(filename);
let realpath = realpathSync(filename);

done(
sassUtils.castToSass({
Expand Down
16 changes: 6 additions & 10 deletions packages/eyeglass/src/importers/AssetImporter.ts
Expand Up @@ -26,24 +26,20 @@ const AssetImporter: ImporterFactory = function (eyeglass, sass, options, fallba
let mod: EyeglassModule | null;

function importAssetsFor(mod: HasAssets): void {
let contents;
let file = "autoGenerated:" + URI.join(mod.name || ROOT_NAME, "assets");
let contents: () => string;
function getAssetImport(): string {
// XXX what is the correct behavior for when mod.name isn't found?
return mod.assets ? mod.assets.asAssetImport(mod.name) : "";
}
// allow build tools to specify a function to cache the imports
if (isPresent(mod.assets) && isPresent(options.assetsCache)) {
contents = options.assetsCache(
mod.assets.cacheKey(mod.name || ROOT_NAME),
getAssetImport
);
contents = options.assetsCache.bind(options.assetsCache,
mod.assets.cacheKey(mod.name || ROOT_NAME), getAssetImport);
} else {
contents = getAssetImport();
contents = getAssetImport;
}
importUtils.importOnce({
contents: contents,
file: "autoGenerated:" + URI.join(mod.name || ROOT_NAME, "assets")
}, done);
importUtils.importOnce({file, contents}, done);
}

let isRelativeImport = URI.isRelative(uri);
Expand Down
2 changes: 1 addition & 1 deletion packages/eyeglass/src/importers/FSImporter.ts
@@ -1,5 +1,5 @@
import * as path from "path";
import { existsSync } from "fs";
import { existsSync } from "../util/perf";
import ImportUtilities from "./ImportUtilities";
import { ImporterFactory } from "./ImporterFactory";
import { AsyncImporter } from "node-sass";
Expand Down

0 comments on commit fc08edf

Please sign in to comment.