Skip to content
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

Virtual File Systems & Shopify Liquid VS Code extension for Web #529

Merged
merged 31 commits into from
Oct 25, 2024

Conversation

charlespwd
Copy link
Contributor

@charlespwd charlespwd commented Oct 8, 2024

What are you adding in this PR?

  • Major breaking changes in most packages (mostly internal APIs)
    • vscode-extension:
      • We now have a browser entry in our package.json
      • The src folder has been split into node, common and browser folders
      • The browser module starts the language server in a Web Worker
      • The node module starts the language server in a subprocess
      • Dropped support for CLI based language servers (legacy extension)
      • There's two new ways to test/debug the VS Code extension in browser contexts (see tophatting section below).
    • theme-check-*, and theme-language-server-*:
      • Replace AbsolutePath concerns with Uris.
        • Absolute paths don't make sense in browser
        • Absolute paths don't make sense with remote files
    • Replace all "fs-based" Dependencies with a single fs: AbstractFileSystem dependency
      • The old injections are now common implementations based on the abstract fs
      • For the CLI theme check, a NodeFileSystem is available (and used by default)
      • For the CLI language server, a NodeFileSystem is available (and used by default)
      • For the VS Code extension, a VsCodeFileSystem is available
        • This file system implementation queries the VS Code FileSystem API (only available in the extension host thread) for file contents. This query is done on the language server via non-standard LSP messages:
          • fs/readFile -> a TextDecoded vscode.workspace.fs.readFile(uri)
          • fs/readDirectory -> vscode.workspace.fs.readDirectory(uri)
          • fs/stat -> vscode.workspace.fs.stat(uri)
        • In node, the same happens but short circuits to the NodeFileSystem for file:/// URIs.
          • We don't need to send file contents over STDIO when we can query the FS directly
    • The codemirror-language-client's playground implements a mock "MainThreadFileSystem" that we could use as inspiration for online-store-web

Demo

vscode-for-web-vfs.mp4

Top-hatting

  • There's 2 new ways you can do this. They are documented in docs/contributing. But here's a video.

Debugging in Chromium VS Code for Web

The "true" E2E way. Demo worthy version of "showing it works in browser"

vscode-for-web-vfs-2.mp4

Debugging in the Desktop app

A faster, debuggable way that runs the browser extension in the desktop app. This disables all the Node APIs but it lets you debug the code.

vscode-for-web-vfs-desktop.mp4

Debugging the CodeMirror Language client

(Not new, but you should know that this flow is also updated to virtual file systems).

vscode-for-web-vfs-codemirror.mp4

What's next? Any followup issues?

  • Make loadConfig work in-browser
  • Make the prettier plugin work in-browser

What did you learn?

  • A couple of neat things that the vscode libraries already offer.
    • The web worker stuff is offered via BrowserMessageReader(worker) and BrowserMessageWriter(worker).
    • vscode.workspace.fs is pretty cool. It does a pretty clever the URIScheme -> FileSystemProvider mapping that makes it so we could eventually have a "ShopifyBackendFileSystem" that would write directly to the storefront.

Before you deploy

  • I included a bunch major bump changeset

};

const reader = new BrowserMessageReader(worker);
const writer = new BrowserMessageWriter(worker);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learned by looking at examples that there was an easier way :)

@@ -40,6 +40,7 @@
"dev:build": "webpack --mode development",
"dev:syntax": "yarn --cwd ./syntaxes dev",
"dev:watch": "webpack --mode development --watch",
"dev:web": "yarn build && vscode-test-web --permission=clipboard-read --permission=clipboard-write --browserType=chromium --extensionDevelopmentPath=.",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vscode-test-web is the one that lets you run chrome and have vscode run in it with your extension loaded. It was a real breeze ngl!

@@ -83,7 +85,8 @@
"activationEvents": [
"workspaceContains:**/.theme-check.yml"
],
"main": "./dist/extension.js",
"main": "./dist/node/extension.js",
"browser": "./dist/browser/extension.js",
Copy link
Contributor Author

@charlespwd charlespwd Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major difference is here: We have a different build for web that uses theme-language-server-browser instead of theme-language-server-node

"messages",
"verbose"
]
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added that stuff in there because I was tired of not having code completion in settings.json to enable the LSP logging

@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 13 times, most recently from bdbbc56 to 35b07d1 Compare October 15, 2024 18:15
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 3 times, most recently from 7c5b372 to 71d5086 Compare October 16, 2024 19:50
Comment on lines +1 to +29
/**
* The AbstractFileSystem interface is used to abstract file system operations.
*
* This way, the Theme Check library can be used in different environments,
* such as the browser, node.js or VS Code (which works with local files, remote
* files and on the web)
*/
export interface AbstractFileSystem {
stat(uri: string): Promise<FileStat>;
readFile(uri: string): Promise<string>;
readDirectory(uri: string): Promise<FileTuple[]>;
}

export enum FileType {
Unknown = 0,
File = 1,
Directory = 2,
SymbolicLink = 64,
}

export interface FileStat {
type: FileType;
size: number;
}

/** A vscode-uri string */
export type UriString = string;

export type FileTuple = [uri: UriString, fileType: FileType];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the most important file in this large diff.

Comment on lines -49 to -57
fileExists,
fileSize,
filesForURI,
findRootURI: findConfigurationRootURI,
getDefaultLocaleFactory,
getDefaultTranslationsFactory,
getDefaultSchemaLocaleFactory,
getDefaultSchemaTranslationsFactory,
getThemeSettingsSchemaForRootURI,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

Comment on lines -101 to +99
fileExists,
fileSize,
findRootURI: findConfigurationRootURI,
getDefaultLocaleFactory,
getDefaultTranslationsFactory,
getDefaultSchemaLocaleFactory,
getDefaultSchemaTranslationsFactory,
fs,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

@@ -48,7 +51,7 @@ export interface RequiredDependencies {
* @param uri - a file path
* @returns {Promise<Config>}
*/
loadConfig(uri: URI): Promise<Config>;
loadConfig(uri: URI, fileExists: (uri: string) => Promise<boolean>): Promise<Config>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fileExists here is a performance improvement. Wanted loadConfig to use findRoot and that thing depends on fileExists which should be the CachedFileSystem, not the raw one.

Comment on lines 84 to 83
{ scheme: 'file', language: 'liquid' },
{ scheme: 'file', language: 'plaintext' },
{ scheme: 'file', language: 'html' },
{ scheme: 'file', language: 'javascript' },
{ scheme: 'file', language: 'css' },
{ scheme: 'file', language: 'scss' },
{ scheme: 'file', language: 'json' },
{ scheme: 'file', language: 'jsonc' },
{ language: 'liquid' },
{ language: 'plaintext' },
{ language: 'html' },
{ language: 'javascript' },
{ language: 'css' },
{ language: 'scss' },
{ language: 'json' },
{ language: 'jsonc' },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing took me a while to figure out since I wrote this 3 years ago. We need to remove scheme in order for completion requests from vscode-vfs:// URIs to trigger.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes with the drawback of showing Document Link errors in git:// URIs. (which I believe are git diff URIs)...

Comment on lines +178 to +144
libraryTarget: 'var',
library: 'serverExportVar',
devtoolModuleFilenameTemplate: '../../[resource-path]',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason I need 2x a config that should look like one is because of this difference here. The worker has to be "import-less", but the extension needs a require('vscode') call in the output that gets shimmed.

@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 3bc7078 to 3919133 Compare October 17, 2024 16:03
We still have an optiomal dependency that is otherwise implemented by a
naive implementation (for CLI use cases), but in the language server we
prefer the buffer over the file in the file system...

Which makes me think we could get rid of that by actually making that
logic part of the getTranslationsFactory directly. Prefer theme files
over fs.
All that logic didn't need to be in the language server.

This makes the dep injection easier too because we no longer have this
upstream concern.
Replace with AbstractFileSystem-based implementation
Replace with abstract-file-system-based implementation

TODO make that shit faster. Seems highlighy unoptimized.
Replace with abstract-file-system-based implementation in common code.
- Adds support for VS Code for Web
- Adds support for remote files
You don't need to query VS Code _every time_ the user types something
for the same imformation. That's rather slow!

We'll invalidate the relevant caches when files are
saved/deleted/created/renamed.
Made to demo what it's like to use a VirtualFileSystem in a
non-vscode environment.
Comment on lines +69 to +82
client.onRequest('fs/readDirectory', async (uriString: string): Promise<FileTuple[]> => {
const results = await workspace.fs.readDirectory(Uri.parse(uriString));
return results.map(([name, type]) => [path.join(uriString, name), type]);
});

const textDecoder = new TextDecoder();
client.onRequest('fs/readFile', async (uriString: string): Promise<string> => {
const bytes = await workspace.fs.readFile(Uri.parse(uriString));
return textDecoder.decode(bytes);
});

client.onRequest('fs/stat', async (uriString: string): Promise<FileStat> => {
return workspace.fs.stat(Uri.parse(uriString));
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should have highlighted this code in the pairing session as well. This is the client code that handles the VsCodeFileSystem LSP requests. @aswamy @graygilmore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one that calls the VS Code FileSystem API

vscode.workspace.fs.readFile() // returns a UInt8Array (bytes)
// AbstractFileSystem.readFile is Promise<string>, this is why we do textDecoder.decode(bytes) on the result. 

I also removed the double reporting of parser errors. Now that theme check
is on the same parser as the prettier plugin, there's no point in showing
the error message reported from two different sources.

Fixes #533
Copy link
Contributor

@aswamy aswamy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry took some time, but we should be good. Minor comments + concerns, but nothing blocking atm.

Tophat complete:

  • Able to run VScode web, able to edit files, save it
  • Able to run VScode web in "sandbox" mode (vscode-test-web), able to edit files - does not persist file as expected
  • Able to run desktop VSCode with web extension, able to edit files, save it
  • Able to run Codemirror Client, able to edit files, save it

⚠️ Noticed that when you open any files in the repo, there is a weird error where we look for .theme-check.yml file in the wrong directory. It could be one of the rootPath functions are messed up because i saw this in all VScode scenarios above. However, Node extension had it as a thrown error, while Browser Extension had it as a info-message.

image

@@ -23,6 +23,7 @@
"build": "yarn workspaces run build",
"build:ci": "yarn workspaces run build:ci",
"build:ts": "yarn workspaces run build:ts",
"dev:web": "yarn --cwd packages/vscode-extension dev:web",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glob package fails if you don't have node v20-22. Could we add an .nvmrc to have v22?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Durr I have a .nvmrc but I git ignored/excluded it? Yeah def should do

packages/vscode-extension/src/common/formatter.ts Outdated Show resolved Hide resolved
packages/theme-language-server-common/src/translations.ts Outdated Show resolved Hide resolved
.vscode/launch.json Show resolved Hide resolved
packages/theme-check-node/src/temp.ts Show resolved Hide resolved
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 7b951fc to 6ef16d6 Compare October 25, 2024 14:22
@charlespwd charlespwd merged commit 4b574c1 into main Oct 25, 2024
6 checks passed
@charlespwd charlespwd deleted the feature/521-virtual-file-system branch October 25, 2024 14:41
@mgmanzella mgmanzella added #gsd:43130 Liquid DX and removed #gsd:43130 Liquid DX labels Oct 28, 2024
@charlespwd charlespwd added #gsd:43130 Liquid DX and removed #gsd:43130 Liquid DX labels Oct 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
#gsd:43130 Liquid DX
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants