Using VSCode Web Server Variant

Last tested version is 1.94.2, may become invalid in a future version.

Pros

Cons

Guide

Download and extract https://update.code.visualstudio.com/latest/{target}/stable ({target} = server-win32-x64-web, server-linux-x64-web or others).

Then use ./bin/code-server(.bat) or write a custom ./run.sh:

cd $(dirname $0)
# export VSCODE_AGENT_FOLDER=$(pwd)/agent_folder # chaneg the data directory
# export UV_USE_IO_URING=0 # avoid bug in nodejs 21
node ./out/server-main.js --accept-server-license-terms --host 127.0.0.1 --port 8109 --connection-token=mytoken

And here's a patch.js to workaround some issues like offline webview, see comments for details:

import fs from "node:fs";
if (!fs.existsSync("./out/vs/server/node/server.cli.js"))
  throw Error("current dir wrong");
const patch = (filePath, replaceList) => {
  const bakPath = filePath + `.bak`;
  if (!fs.existsSync(bakPath)) fs.renameSync(filePath, bakPath);
  let content = fs.readFileSync(bakPath).toString();
  for (const [from, to] of replaceList) {
    const testFn = from instanceof RegExp ? "match" : "includes";
    const replaceFn = from instanceof RegExp ? "replace" : "replaceAll";
    if (!content[testFn](from))
      console.error("Patch entry not found", { from, to });
    content = content[replaceFn](from, to);
  }
  fs.writeFileSync(filePath, content);
};
patch("./out/vs/code/browser/workbench/workbench.js", [
  // > src/vs/workbench/services/environment/browser/environmentService.ts
  [`"https://{{uuid}}.vscode-cdn.net/{{quality}}/{{commit}}`, `baseUrl+"`], // Replace entry url with local server to allow offline work (for webview)
  // > src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
  [/(?<="extensions.autoUpdate":\{.+?,default:).+?,/, "false,"], // Set "extensions.autoUpdate" default = false. Because the "User Settings" is store in browser (indexedDB), so update will start unexpectedly if you open a page in incognito
]);
patch("./out/vs/workbench/contrib/webview/browser/pre/index.html", [
  // > src/vs/workbench/contrib/webview/browser/pre/index.html
  [/\'sha256.+?\'/, "'unsafe-inline'"], // Modify CSP (for webview)
  [/\.padStart\(52.+?\)/, "&&location.hostname"], // Bypass hostname vertify (for webview)
]);
const spdlogDirPath = "node_modules/@vscode/spdlog";
fs.rmSync(spdlogDirPath, { recursive: true, force: true });
fs.mkdirSync(spdlogDirPath);
fs.writeFileSync(
  spdlogDirPath + "/package.json",
  JSON.stringify({ version: "0.13.99", main: "index.js" }) + "\n"
);
fs.writeFileSync(
  spdlogDirPath + "/index.js",
  `(${(() => {
    const fs = require("node:fs");
    const path = require("node:path");
    function Logger(name, filepath) {
      fs.mkdirSync(path.dirname(filepath), { recursive: true });
      const fd = fs.openSync(filepath, "a");
      const write = (v) =>
        fs.write(fd, "[" + Date.now() + "] " + v.trimEnd() + "\n", () => {});
      this.debug = this.info = this.warn = write;
      this.error = this.critical = this.trace = write;
      this.getLevel = () => 2;
      this.setLevel = this.setPattern = this.clearFormatters = () => {};
      this.flush = () => fs.fsync(fd, () => {});
      this.drop = () => fs.close(fd);
    }
    const create = (name, filepath, maxSize, maxFiles) =>
      new Promise((resolve) => resolve(new Logger(name, filepath)));
    exports.createAsyncRotatingLogger = exports.createRotatingLogger = create;
    exports.setLevel = exports.setFlushOn = () => {};
    exports.version = 11100;
  }).toString()})();\n`
);
// rm -rf ~/.vscode-server/data/Cached*
// rm -rf ~/.vscode-server/extensions/redhat.java-*/jre/
// rm -rf ~/.vscode-server/extensions/ms-python.python-*/pythonFiles/lib/python/debugpy/_vendored/pydevd/pydevd_attach_to_process/
// rm -rf ~/.vscode-server/extensions/ms-python.python-*/out/client/extension.js.map*