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

Shopify Liquid VS Code extension for Web #529

Open
wants to merge 27 commits into
base: main
Choose a base branch
from

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.

Debugging the CodeMirror Language client

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

TODO

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

Comment on lines 102 to 117
function createWorkerLanguageClient(
context: ExtensionContext,
clientOptions: LanguageClientOptions,
) {
// Create a worker. The worker main file implements the language server.
const serverMain = Uri.joinPath(context.extensionUri, 'dist', 'browser', 'server.js');
const worker = new Worker(serverMain.toString(true));

// create the language server client to communicate with the server running in the worker
return new LanguageClient(
'shopifyLiquid',
'Theme Check Language Server',
clientOptions,
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.

Major difference is that here we start the language server in a Web Worker instead of settingup STDIO and a subprocess.

@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 2 times, most recently from 7cb9d21 to ccf72aa Compare October 9, 2024 13:46
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 71a2c6b to 2ffbef6 Compare October 9, 2024 14:05
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 2ffbef6 to 4d7cefe Compare October 9, 2024 14:09
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from c22727f to e1aed1e Compare October 10, 2024 20:06
- Change `Config` interface to refer to `rootUri` instead of `root` as an absolutePath
- Get rid of `fileExists` DI in theme-check Dependencies
- Get rid of `fileExists` DI in theme-language-server Dependencies
- Add `fs` DI in theme-check Dependencies
- Add `fs` DI in theme-language-server Dependencies
- Add `MockFileSystem` implementation (for tests)
- Add `NodeFileSystem` implementation
- Add optional `fs` argument to `theme-language-server-node#startServer()`

The `fs` DI has the following interface (so far):

```ts
interface FileSystem {
  stat(uri: string): Promise<{ fileType, size }>;
  readFile(uri: string): Promise<string>;
  readDirectory(uri: string): Promise<[uri: string, fileType: FileType][]>;
}
```

theme-language-server-node now also accepts an optional `fs` implementation in startServer.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from c9b4e00 to 7bd1486 Compare October 15, 2024 13:42
Instead of having that logic be injected in, I added an abstract
implementation in theme-check-common that depends on the virtual file
system. We will no longer need to inject this behaviour :)
FileSystem is an ambient import which made the DX suffer when
refactoring code. It's also more obvious now that the FileSystem
interface is an interface that you need to implement, not something
concrete that you can use directly.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 2 times, most recently from 4f24bdc to ae0ac49 Compare October 15, 2024 17:29
Replace it with URIs. That way we won't need to worry about windows paths being weird.

The only place where we still have them is for places where we deal with `glob` and `loadConfig` in `theme-{check,language-server}-node`.

- SourceCode.absolutePath -> SourceCode.uri
- etc.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 2 times, most recently from 07b17c9 to dd53f1a Compare October 15, 2024 17:37
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.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 39b4dbe to 28c2318 Compare October 16, 2024 14:33
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.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 28c2318 to 6ec9236 Compare October 16, 2024 14:54
Replace with abstract-file-system-based implementation in common code.
- Adds support for VS Code for Web
- Adds support for remote files
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 7c5b372 to 71d5086 Compare October 16, 2024 19:50
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.
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.

Comment on lines +178 to +180
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.

Made to demo what it's like to use a VirtualFileSystem in a
non-vscode environment.
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch 2 times, most recently from 3919133 to 8a05d45 Compare October 17, 2024 18:05
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 8a05d45 to f3d1197 Compare October 17, 2024 18:06
@charlespwd charlespwd mentioned this pull request Oct 17, 2024
@charlespwd charlespwd force-pushed the feature/521-virtual-file-system branch from 95da0b8 to 3a7ea94 Compare October 18, 2024 14:45
@charlespwd charlespwd marked this pull request as ready for review October 18, 2024 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant