Skip to content

Commit 10c6b28

Browse files
authored
fix: performance regression for projects using correct module graphs
Fix massive performance regression for projects using correct module graphs
2 parents 8d017bf + eaa8f48 commit 10c6b28

16 files changed

+239
-49
lines changed

ember-language-server.code-workspace

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
},
6+
{
7+
"path": "../vscode-ember"
8+
}
9+
],
10+
"settings": {}
11+
}

src/builtin-addons/core/script-completion-provider.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export default class ScriptCompletionProvider {
8484
}
8585

8686
if (!this.meta.projectAddonsInfoInitialized) {
87-
await mGetProjectAddonsInfo(root);
87+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
8888
this.enableRegistryCache('projectAddonsInfoInitialized');
8989
this.project.invalidateRegistry();
9090
}
@@ -107,7 +107,7 @@ export default class ScriptCompletionProvider {
107107
}
108108

109109
if (!this.meta.projectAddonsInfoInitialized) {
110-
await mGetProjectAddonsInfo(root);
110+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
111111
this.enableRegistryCache('projectAddonsInfoInitialized');
112112
this.project.invalidateRegistry();
113113
}
@@ -130,7 +130,7 @@ export default class ScriptCompletionProvider {
130130
}
131131

132132
if (!this.meta.projectAddonsInfoInitialized) {
133-
await mGetProjectAddonsInfo(root);
133+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
134134
this.enableRegistryCache('projectAddonsInfoInitialized');
135135
this.project.invalidateRegistry();
136136
}
@@ -185,7 +185,7 @@ export default class ScriptCompletionProvider {
185185
}
186186

187187
if (!this.meta.projectAddonsInfoInitialized) {
188-
await mGetProjectAddonsInfo(root);
188+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
189189
this.enableRegistryCache('projectAddonsInfoInitialized');
190190
this.project.invalidateRegistry();
191191
}

src/builtin-addons/core/script-definition-provider.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type ItemType = 'Model' | 'Transform' | 'Service';
2929
// eslint-disable-line
3030
type LayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => string[];
3131
type AsyncLayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => Promise<string[]>;
32+
type ProjectAwareCollectionFn = (project: Project, itemName: string, podModulePrefix?: string) => Promise<string[]>;
3233

3334
function joinPaths(...args: string[]) {
3435
return ['.ts', '.js'].map((extName: string) => {
@@ -40,7 +41,8 @@ function joinPaths(...args: string[]) {
4041
}
4142

4243
class PathResolvers {
43-
[key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn;
44+
[key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn | ProjectAwareCollectionFn;
45+
4446
muModelPaths(root: string, modelName: string) {
4547
return joinPaths(root, 'src', 'data', 'models', modelName, 'model');
4648
}
@@ -68,11 +70,11 @@ class PathResolvers {
6870
podServicePaths(root: string, modelName: string, podPrefix: string) {
6971
return joinPaths(root, 'app', podPrefix, modelName, 'service');
7072
}
71-
async addonServicePaths(root: string, serviceName: string): Promise<string[]> {
72-
return await getAddonPathsForType(root, 'services', serviceName);
73+
async addonServicePaths(project: Project, serviceName: string): Promise<string[]> {
74+
return await getAddonPathsForType(project, 'services', serviceName);
7375
}
74-
async addonImportPaths(root: string, pathName: string) {
75-
return await getAddonImport(root, pathName);
76+
async addonImportPaths(project: Project, pathName: string) {
77+
return await getAddonImport(project, pathName);
7678
}
7779
classicImportPaths(root: string, pathName: string) {
7880
const pathParts = pathName.split('/');
@@ -123,7 +125,7 @@ export default class CoreScriptDefinitionProvider {
123125
guessedPaths.push(pathLocation);
124126
});
125127

126-
const addonImports = await this.resolvers.addonImportPaths(root, importPath);
128+
const addonImports = await this.resolvers.addonImportPaths(this.project, importPath);
127129

128130
addonImports.forEach((pathLocation: string) => {
129131
guessedPaths.push(pathLocation);
@@ -148,7 +150,7 @@ export default class CoreScriptDefinitionProvider {
148150
}
149151

150152
if (fnName === 'Service') {
151-
const paths = await this.resolvers.addonServicePaths(root, typeName);
153+
const paths = await this.resolvers.addonServicePaths(this.project, typeName);
152154

153155
paths.forEach((item: string) => {
154156
guessedPaths.push(item);

src/builtin-addons/core/template-completion-provider.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export default class TemplateCompletionProvider {
178178
await mListComponents(project);
179179
this.enableRegistryCache('componentsRegistryInitialized');
180180

181-
await mGetProjectAddonsInfo(project.root);
181+
await mGetProjectAddonsInfo(project.root, project.dependencyMap);
182182
this.enableRegistryCache('projectAddonsInfoInitialized');
183183

184184
this.project.invalidateRegistry();
@@ -195,7 +195,7 @@ export default class TemplateCompletionProvider {
195195
const items: CompletionItem[] = [];
196196

197197
if (!this.meta.projectAddonsInfoInitialized) {
198-
await mGetProjectAddonsInfo(root);
198+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
199199
this.enableRegistryCache('projectAddonsInfoInitialized');
200200
this.project.invalidateRegistry();
201201
}
@@ -256,7 +256,7 @@ export default class TemplateCompletionProvider {
256256
}
257257
async getMustachePathCandidates(root: string) {
258258
if (!this.meta.projectAddonsInfoInitialized) {
259-
await mGetProjectAddonsInfo(root);
259+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
260260
this.enableRegistryCache('projectAddonsInfoInitialized');
261261
this.project.invalidateRegistry();
262262
}
@@ -305,7 +305,7 @@ export default class TemplateCompletionProvider {
305305
}
306306
async getBlockPathCandidates(root: string): Promise<CompletionItem[]> {
307307
if (!this.meta.projectAddonsInfoInitialized) {
308-
await mGetProjectAddonsInfo(root);
308+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
309309
this.enableRegistryCache('projectAddonsInfoInitialized');
310310
this.project.invalidateRegistry();
311311
}
@@ -340,7 +340,7 @@ export default class TemplateCompletionProvider {
340340
}
341341

342342
if (!this.meta.projectAddonsInfoInitialized) {
343-
await mGetProjectAddonsInfo(this.project.root);
343+
await mGetProjectAddonsInfo(this.project.root, this.project.dependencyMap);
344344
this.enableRegistryCache('projectAddonsInfoInitialized');
345345
this.project.invalidateRegistry();
346346
}
@@ -447,7 +447,7 @@ export default class TemplateCompletionProvider {
447447
}
448448

449449
if (!this.meta.projectAddonsInfoInitialized) {
450-
await mGetProjectAddonsInfo(root);
450+
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
451451
this.enableRegistryCache('projectAddonsInfoInitialized');
452452
this.project.invalidateRegistry();
453453
}

src/project-roots.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export default class ProjectRoots {
8787
ignore: ['**/.git/**', '**/bower_components/**', '**/dist/**', '**/node_modules/**', '**/tmp/**'],
8888
});
8989

90+
logInfo(`ELS: Found ${roots.length} roots for ${workspaceRoot}`);
91+
92+
const start = Date.now();
93+
9094
for (const rootPath of roots) {
9195
const filePath = path.join(workspaceRoot, rootPath);
9296
const fullPath = path.dirname(filePath);
@@ -103,6 +107,8 @@ export default class ProjectRoots {
103107
await this.onProjectAdd(fullPath);
104108
}
105109
}
110+
111+
logInfo(`ELS: iterating roots took ${Date.now() - start}ms`);
106112
}
107113

108114
async initialize(workspaceRoot: string) {
@@ -122,6 +128,8 @@ export default class ProjectRoots {
122128
if (this.projects.has(projectPath)) {
123129
const project = this.projects.get(projectPath) as Project;
124130

131+
logInfo(`Project already existed at ${projectPath}`);
132+
125133
return {
126134
initIssues: project.initIssues,
127135
providers: project.providers,
@@ -154,12 +162,16 @@ export default class ProjectRoots {
154162
};
155163
}
156164

165+
logInfo(`Initializing new project at ${projectPath} with ${this.localAddons.length} ELS addons.`);
166+
157167
const project = new Project(projectPath, this.localAddons, info);
158168

169+
const start = Date.now();
170+
159171
await project.initialize(this.server);
160172

161173
this.projects.set(projectPath, project);
162-
logInfo(`Ember CLI project added at ${projectPath}`);
174+
logInfo(`Ember CLI project added at ${projectPath}. (took ${Date.now() - start}ms)`);
163175
await project.init(this.server);
164176

165177
return {

src/project.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ export interface Executors {
3535
}
3636

3737
export class Project extends BaseProject {
38+
// Don't traverse dependencies we've already seen.
39+
// correct package graph can sort of throw us in to cycles if we don't keep track of this.
40+
41+
dependencyMap: Map<
42+
string,
43+
{
44+
package: PackageInfo;
45+
root: string;
46+
}
47+
> = new Map();
48+
3849
providers!: ProjectProviders;
3950
builtinProviders!: ProjectProviders;
4051
addonsMeta: AddonMeta[] = [];
@@ -148,7 +159,7 @@ export class Project extends BaseProject {
148159
this.providers = emptyProjectProviders();
149160
this.flags.enableEagerRegistryInitialization = false;
150161
} else if (server.options.type === 'node') {
151-
this.providers = await collectProjectProviders(this.root, this.addons);
162+
this.providers = await collectProjectProviders(this.root, this.addons, this.dependencyMap);
152163
} else {
153164
throw new Error(`Unknown server type: "${server.options.type}"`);
154165
}

src/server.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,14 @@ export default class Server {
168168
hoverProvider!: HoverProvider;
169169
codeActionProvider!: CodeActionProvider;
170170
async executeInitializers() {
171-
logInfo('UELS: executeInitializers');
171+
logInfo('ELS: executeInitializers');
172+
logInfo(`ELS: ${this.initializers.length} initializers`);
172173

173174
for (const initializer of this.initializers) {
174175
await initializer();
175176
}
176177

178+
logInfo(`ELS: clearing initializers because they've been initialized`);
177179
this.initializers = [];
178180
}
179181
private onInitialized() {
@@ -216,7 +218,7 @@ export default class Server {
216218
};
217219
}
218220

219-
await mGetProjectAddonsInfo(project.root);
221+
await mGetProjectAddonsInfo(project.root, project.dependencyMap);
220222
project.invalidateRegistry();
221223

222224
return {
@@ -524,6 +526,8 @@ export default class Server {
524526
this.initializers.push(async () => {
525527
await this.projectRoots.initialize(rootPath as string);
526528

529+
logInfo(`Found ${workspaceFolders?.length ?? 0} workspace folders for ${rootPath}`);
530+
527531
if (workspaceFolders && Array.isArray(workspaceFolders)) {
528532
for (const folder of workspaceFolders) {
529533
const folderPath = URI.parse(folder.uri).fsPath;

src/utils/addon-api.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from './layout-helpers';
1212
import { TextDocument } from 'vscode-languageserver-textdocument';
1313
import * as path from 'path';
14-
import { log, logInfo, logError, safeStringify } from './logger';
14+
import { log, logInfo, logError, safeStringify, instrumentTime } from './logger';
1515
import Server from '../server';
1616
import ASTPath from './../glimmer-utils';
1717
import DAGMap from 'dag-map';
@@ -187,8 +187,17 @@ function requireUncached(module: string) {
187187
return result;
188188
}
189189

190-
export async function collectProjectProviders(root: string, addons: string[]): Promise<ProjectProviders> {
191-
const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([getProjectAddonsRoots(root), getProjectInRepoAddonsRoots(root)]);
190+
export async function collectProjectProviders(root: string, addons: string[], dependencyMap: Project['dependencyMap']): Promise<ProjectProviders> {
191+
const time = instrumentTime(`collectProjectProviders(${root})`);
192+
193+
time.log(`Starting`);
194+
const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([
195+
getProjectAddonsRoots(root, dependencyMap),
196+
getProjectInRepoAddonsRoots(root, dependencyMap),
197+
]);
198+
199+
time.log(`found roots`);
200+
192201
const roots = addons
193202
.concat([root])
194203
.concat(projectAddonsRoots, projectInRepoAddonsRoots)
@@ -226,6 +235,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P
226235
}
227236
}
228237

238+
time.log(`found ELS addons`);
239+
229240
const result: {
230241
definitionProviders: DefinitionResolveFunction[];
231242
referencesProviders: ReferenceResolveFunction[];
@@ -332,6 +343,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P
332343
}
333344
});
334345

346+
time.log(`finished crawling dagMap`);
347+
335348
return result;
336349
}
337350

src/utils/definition-helpers.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { URI } from 'vscode-uri';
66

77
import { podModulePrefixForRoot, hasAddonFolderInPath, getProjectAddonsRoots, getProjectInRepoAddonsRoots, asyncFilter } from './layout-helpers';
88
import { fsProvider } from '../fs-provider';
9+
import { Project } from '..';
910

1011
const mProjectAddonsRoots = memoize(getProjectAddonsRoots, {
1112
length: 3,
@@ -166,7 +167,8 @@ export function getPathsForComponentTemplates(root: string, maybeComponentName:
166167
return paths;
167168
}
168169

169-
export async function getAddonImport(root: string, importPath: string) {
170+
export async function getAddonImport(project: Project, importPath: string) {
171+
const { root, dependencyMap } = project;
170172
const importParts = importPath.split('/');
171173
let addonName = importParts.shift();
172174

@@ -179,7 +181,7 @@ export async function getAddonImport(root: string, importPath: string) {
179181
}
180182

181183
const items: string[] = [];
182-
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]);
184+
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]);
183185

184186
const roots = items.concat(addonRoots, inRepoRoots);
185187
let existingPaths: string[] = [];
@@ -228,9 +230,10 @@ export async function getAddonImport(root: string, importPath: string) {
228230
return existingPaths;
229231
}
230232

231-
export async function getAddonPathsForType(root: string, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) {
233+
export async function getAddonPathsForType(project: Project, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) {
234+
const { root, dependencyMap } = project;
232235
const items: string[] = [];
233-
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]);
236+
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]);
234237
const roots = items.concat(addonRoots, inRepoRoots);
235238
let existingPaths: string[] = [];
236239
let hasValidPath = false;

0 commit comments

Comments
 (0)