Skip to content

Understanding perf issues with syncHostData and with #947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DanielRosenwasser opened this issue Feb 9, 2022 · 2 comments
Closed

Understanding perf issues with syncHostData and with #947

DanielRosenwasser opened this issue Feb 9, 2022 · 2 comments
Labels
question Further information is requested

Comments

@DanielRosenwasser
Copy link

There's a discussion over at microsoft/TypeScript#41051 around why Volar is interested in TypeScript supporting with statements. If you're okay with having a discussion here, I think I'd like to better understand some of the architectural constraints around this - specifically why there needs to be a synchronizeHostData step to grab the contents of a file.

(Of course, please prioritize #941 over this. This can wait, I would much rather you prioritize yourself first and respect your time.)

@johnsoncodehk
Copy link
Member

Thanks to focus on this! (The benefits of this improvement are huge, so I put this as a top priority.)

syncHostData() is TypeScript internal stuff, it will be execute when volar call any TS language service API after provide a new version to LanguageServiceHost.getProjectVersion.

Might not be able to explain it well in words, I can provide a sequence diagram later if it helps.

@johnsoncodehk
Copy link
Member

Hi @DanielRosenwasser, I'm sorry for the late reply, this problem is actually not easy to explain, after I thought about the sequence diagram, I think it's clearer with explain what actually happen.


<template>
    {{ | }}
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
    props: {
        msg: String
    }
});
</script>

When trigger auto-complete at |, we expected it will show msg with correct type in completion list, we need this steps to do it:

  1. Generate virtual TS code from script block.
const virtual_script_code = `
import { defineComponent } from 'vue';

export default defineComponent({
    props: {
        msg: String
    }
});`;
  1. Generate middle code to extra component context from component instance type.

(I know we can use typecheck API, but it can't avoid the performance issue, and I need make sure these behavior is reproducible when I wirte virtual files to system for maintainability.)

const virtual_middle_code = `
import Component from './virtual_script_code';

const ctx = new Component();
ctx.`; // intentionally append `.` to do auto-complete internal later
  1. Create TS language service by virtual files.
import * as ts from 'typescript';

let projectVersion = 1;

const virtualFiles = {
    '/virtual_script_code.ts': ts.ScriptSnapshot.fromString(virtual_script_code),
    '/virtual_middle_file.ts': ts.ScriptSnapshot.fromString(virtual_middle_code),
};

const languageService = ts.createLanguageService({
	getProjectVersion() {
		return projectVersion.toString();
	},
	getScriptFileNames() {
		return Object.keys(virtualFiles);
	},
	getScriptSnapshot(fileName) {
        return virtualFiles[fileName];
	},
    // ... other options is not important for explain
});
  1. Use getCompletionsAtPosition to get component context msg.
const completions = languageService.getCompletionsAtPosition('/virtual_middle_code.ts', 82 /* offset at `ctx.|` */);
const ctx = completions.entries.map(entry => entry.name); // ['msg']

The getCompletionsAtPosition calling is the reason of performance issue, because when call a language service API, if getProjectVersion has been changed, TS need to execute very expensive synchronizeHostData() at https://github.com/microsoft/TypeScript/blob/995e0a060116a905e41badf15506c3a94924eef6/src/services/services.ts#L1662.

  1. Use ctx to generate virtual code for template.
const virtual_template_code = `
import Component from './virtual_script_code';

const ctx = new Component();
${ctx.map(prop => `let ${prop} = ctx.${prop};`).join('\n')}

{ | }`;

/* =>
import Component from './virtual_script_code';

const ctx = new Component();
let msg = ctx.msg;

{ | }
*/
  1. Add template virtual file to language service.
virtualFiles['./virtual_template_code.ts'] = ts.ScriptSnapshot.fromString(virtual_template_code);
projectVersion++;
  1. Get template completion result.
const result = languageService.getCompletionsAtPosition('/virtual_template_code.ts', 100 /* offset at `|` in virtual template code */);

// ... mapping and response result to LSP

We calling getCompletionsAtPosition again here, and due to projectVersion is changed for update virtual tempalte code, synchronizeHostData() is execute again.


With with keyword workaround, steps became:

  1. Generate virtual TS code from script block.
const virtual_script_code = `
import { defineComponent } from 'vue';

export default defineComponent({
    props: {
        msg: String
    }
});`;
  1. Generate virtual TS code from template block.
const virtual_template_code = `
import Component from './virtual_script_code';

const ctx = new Component();

with (ctx) {
    { | };
}`;
  1. Create TS language service by virtual files.
import * as ts from 'typescript';

let projectVersion = 1;

const virtualFiles = {
    '/virtual_script_code.ts': ts.ScriptSnapshot.fromString(virtual_script_code),
    '/virtual_template_code.ts': ts.ScriptSnapshot.fromString(virtual_template_code),
};

const languageService = ts.createLanguageService({
	getProjectVersion() {
		return projectVersion.toString();
	},
	getScriptFileNames() {
		return Object.keys(virtualFiles);
	},
	getScriptSnapshot(fileName) {
        return virtualFiles[fileName];
	},
    // ... other options is not important for explain
});
  1. Get template completion result.
const result = languageService.getCompletionsAtPosition('/virtual_template_code.ts', 98 /* offset at `|` in virtual template code */);

// ... mapping and response result to LSP

You can see we only call getCompletionsAtPosition one times, in the final analysis we don't need to know the component context properties before generate virtual code, everything is processed inside TS in once API call.

@johnsoncodehk johnsoncodehk pinned this issue Mar 14, 2022
@johnsoncodehk johnsoncodehk unpinned this issue Apr 10, 2022
@johnsoncodehk johnsoncodehk added the question Further information is requested label May 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants