Skip to content

Issue with native modules (.node) resolving incorrectly in VSCode extension built with esbuild #4154

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

Open
aspire-shatheesh opened this issue Apr 22, 2025 · 8 comments

Comments

@aspire-shatheesh
Copy link

Hi,

I am developing a VSCode extension that uses a private NPM package. This private package, in turn, depends on the @xenova/transformers and @lancedb packages.

lancedb includes a platform-specific binary. When the project is built using esbuild, it generates a file named lancedb.linux-x64-gnu-V6OX6RLO.node in the /out directory. However, when I run the extension, I encounter an error stating that native_js_1.Connection.new() is undefined.

From the log and code snippet below, we can see that nativeBinding is just a string ("./lancedb.linux-x64-gnu-V6OX6RLO.node") rather than an actual module object. As a result, the call to native_js_1.Connection.new(uri, opts) fails.

If I rename the file from lancedb.linux-x64-gnu-V6OX6RLO.node to lancedb.linux-x64-gnu.node, the module loads correctly.

Error:

TypeError: Cannot read properties of undefined (reading 'new') {stack: 'TypeError: Cannot read properties of undefine…e/ide/vscode-extn/out/extension.js:177487:22)', message: "Cannot read properties of undefined (reading 'new')"}

Code from extension.js

const nativeConn = await native_js_1.Connection.new(uri, opts);

log of native_js_1

{Connection: undefined, Index: undefined, RecordBatchIterator: undefined, NativeMergeInsertBuilder: undefined, Query: undefined, …}

Excerpts from extension.js to demonstrate that lancedb resolves to a string instead of a module object:

/ node_modules/@lancedb/lancedb-linux-x64-gnu/lancedb.linux-x64-gnu.node
var require_lancedb_linux_x64_gnu = __commonJS({
  "node_modules/@lancedb/lancedb-linux-x64-gnu/lancedb.linux-x64-gnu.node"(exports2, module2) {
    module2.exports = "./lancedb.linux-x64-gnu-V6OX6RLO.node";
  }
});
// node_modules/@lancedb/lancedb/dist/native.js
var require_native2 = __commonJS({
  "node_modules/@lancedb/lancedb/dist/native.js"(exports2, module2) {
    "use strict";
    var { existsSync: existsSync5, readFileSync: readFileSync4 } = require("fs");
    var { join: join5 } = require("path");
    var { platform, arch } = process;
    var nativeBinding = null;
    var localFileExisted = false;
    var loadError = null;
    function isMusl() {
      if (!process.report || typeof process.report.getReport !== "function") {
        try {
          const lddPath = require("child_process").execSync("which ldd").toString().trim();
          return readFileSync4(lddPath, "utf8").includes("musl");
        } catch (e) {
          return true;
        }
      } else {
        const { glibcVersionRuntime } = process.report.getReport().header;
        return !glibcVersionRuntime;
      }
    }
    switch (platform) {
      case "android":
        switch (arch) {
          case "arm64":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.android-arm64.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.android-arm64.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-android-arm64");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          case "arm":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.android-arm-eabi.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.android-arm-eabi.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-android-arm-eabi");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          default:
            throw new Error(Unsupported architecture on Android ${arch});
        }
        break;
      case "win32":
        switch (arch) {
          case "x64":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.win32-x64-msvc.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.win32-x64-msvc.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-win32-x64-msvc");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          case "ia32":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.win32-ia32-msvc.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.win32-ia32-msvc.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-win32-ia32-msvc");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          case "arm64":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.win32-arm64-msvc.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.win32-arm64-msvc.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-win32-arm64-msvc");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          default:
            throw new Error(Unsupported architecture on Windows: ${arch});
        }
        break;
      case "darwin":
        localFileExisted = existsSync5(join5(__dirname, "lancedb.darwin-universal.node"));
        try {
          if (localFileExisted) {
            nativeBinding = require("./lancedb.darwin-universal.node");
          } else {
            nativeBinding = require("@lancedb/lancedb-darwin-universal");
          }
          break;
        } catch {
        }
        switch (arch) {
          case "x64":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.darwin-x64.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.darwin-x64.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-darwin-x64");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          case "arm64":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.darwin-arm64.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.darwin-arm64.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-darwin-arm64");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          default:
            throw new Error(Unsupported architecture on macOS: ${arch});
        }
        break;
      case "freebsd":
        if (arch !== "x64") {
          throw new Error(Unsupported architecture on FreeBSD: ${arch});
        }
        localFileExisted = existsSync5(join5(__dirname, "lancedb.freebsd-x64.node"));
        try {
          if (localFileExisted) {
            nativeBinding = require("./lancedb.freebsd-x64.node");
          } else {
            nativeBinding = require("@lancedb/lancedb-freebsd-x64");
          }
        } catch (e) {
          loadError = e;
        }
        break;
      case "linux":
        switch (arch) {
          case "x64":
            if (isMusl()) {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-x64-musl.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-x64-musl.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-x64-musl");
                }
              } catch (e) {
                loadError = e;
              }
            } else {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-x64-gnu.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-x64-gnu.node");
                } else {
                  console.log('loading native bindings....');
                  
                  nativeBinding = require_lancedb_linux_x64_gnu();
                  console.log('type---- ',  typeof nativeBinding);
                  
                }
              } catch (e) {
                loadError = e;
              }
            }
            break;
          case "arm64":
            if (isMusl()) {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-arm64-musl.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-arm64-musl.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-arm64-musl");
                }
              } catch (e) {
                loadError = e;
              }
            } else {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-arm64-gnu.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-arm64-gnu.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-arm64-gnu");
                }
              } catch (e) {
                loadError = e;
              }
            }
            break;
          case "arm":
            if (isMusl()) {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-arm-musleabihf.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-arm-musleabihf.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-arm-musleabihf");
                }
              } catch (e) {
                loadError = e;
              }
            } else {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-arm-gnueabihf.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-arm-gnueabihf.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-arm-gnueabihf");
                }
              } catch (e) {
                loadError = e;
              }
            }
            break;
          case "riscv64":
            if (isMusl()) {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-riscv64-musl.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-riscv64-musl.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-riscv64-musl");
                }
              } catch (e) {
                loadError = e;
              }
            } else {
              localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-riscv64-gnu.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-riscv64-gnu.node");
                } else {
                  nativeBinding = require("@lancedb/lancedb-linux-riscv64-gnu");
                }
              } catch (e) {
                loadError = e;
              }
            }
            break;
          case "s390x":
            localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-s390x-gnu.node"));
            try {
              if (localFileExisted) {
                nativeBinding = require("./lancedb.linux-s390x-gnu.node");
              } else {
                nativeBinding = require("@lancedb/lancedb-linux-s390x-gnu");
              }
            } catch (e) {
              loadError = e;
            }
            break;
          default:
            throw new Error(Unsupported architecture on Linux: ${arch});
        }
        break;
      default:
        throw new Error(Unsupported OS: ${platform}, architecture: ${arch});
    }
    console.log("🔍 Platform:", platform);
console.log("🔍 Arch:", arch);
console.log("📁 Local native file existed:", localFileExisted);
console.log("📦 Native binding object:", nativeBinding);
if (loadError) {
  console.error("❌ Error while loading native binding:", loadError);
}
    if (!nativeBinding) {
      if (loadError) {
        throw loadError;
      }
      throw new Error(Failed to load native binding);
    }
    var { Connection, Index, RecordBatchIterator, NativeMergeInsertBuilder, Query: Query2, VectorQuery, Table, WriteMode } = nativeBinding;
    module2.exports.Connection = Connection;
    module2.exports.Index = Index;
    module2.exports.RecordBatchIterator = RecordBatchIterator;
    module2.exports.NativeMergeInsertBuilder = NativeMergeInsertBuilder;
    module2.exports.Query = Query2;
    module2.exports.VectorQuery = VectorQuery;
    module2.exports.Table = Table;
    module2.exports.WriteMode = WriteMode;
  }
});

Log response

loading native bindings....
extensionHostProcess.js:179
type---- string
extensionHostProcess.js:179
🔍 Platform: linux
extensionHostProcess.js:179
🔍 Arch: x64
extensionHostProcess.js:179
📁 Local native file existed: false
extensionHostProcess.js:179
📦 Native binding object: ./lancedb.linux-x64-gnu-V6OX6RLO.node
extensionHostProcess.js:179`

Similar Issue with Sharp package

I'm encountering a similar problem with the sharp package as well . In the /out directory, a native binary file named sharp-linux-x64-DQNVFNDI.node is present after the esbuild process.

However, at runtime, the require_sharp call resolves to a string path instead of loading the native module. As a result, any function calls on require_sharp fail because it is not the expected module object. I am not clear how to get this working in vscode extension. Any help would be appreciated

// node_modules/sharp/build/Release/sharp-linux-x64.node
var require_sharp_linux_x64 = __commonJS({
  "node_modules/sharp/build/Release/sharp-linux-x64.node"(exports2, module2) {
    module2.exports = "./sharp-linux-x64-DQNVFNDI.node";
  }
});

// require("../build/Release/sharp-*.node") in node_modules/sharp/lib/sharp.js
var globRequire_build_Release_sharp_node;
var init_2 = __esm({
  'require("../build/Release/sharp-*.node") in node_modules/sharp/lib/sharp.js'() {
    globRequire_build_Release_sharp_node = __glob({
      "../build/Release/sharp-linux-x64.node": () => require_sharp_linux_x64()
    });
  }
});

// node_modules/sharp/lib/sharp.js
var require_sharp = __commonJS({
  "node_modules/sharp/lib/sharp.js"(exports2, module2) {
    "use strict";
    init_importMetaUrl();
    init_2();
    var platformAndArch = require_platform()();
    try {
      module2.exports = globRequire_build_Release_sharp_node(../build/Release/sharp-${platformAndArch}.node);
    } catch (err2) {
      const help = ["", 'Something went wrong installing the "sharp" module', "", err2.message, "", "Possible solutions:"];
      if (/dylib/.test(err2.message) && /Incompatible library version/.test(err2.message)) {
        help.push('- Update Homebrew: "brew update && brew upgrade vips"');
      } else {
        const [platform, arch] = platformAndArch.split("-");
        if (platform === "linux" && /Module did not self-register/.test(err2.message)) {
          help.push("- Using worker threads? See https://sharp.pixelplumbing.com/install#worker-threads");
        }
        help.push(
          '- Install with verbose logging and look for errors: "npm install --ignore-scripts=false --foreground-scripts --verbose sharp"',
          - Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"
        );
      }
      help.push(
        "- Consult the installation documentation: https://sharp.pixelplumbing.com/install"
      );
      if (process.platform === "win32" || /symbol/.test(err2.message)) {
        const loadedModule = Object.keys(require.cache).find((i2) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i2));
        if (loadedModule) {
          const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
          help.push(- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp");
        }
      }
      throw new Error(help.join("\n"));
    }
  }
});`

My esbuild setting

const buildOptions = {
  entryPoints: ['src/extension.ts'],
  bundle: true,
  platform: 'node',
  outfile: 'out/extension.js',
  external: [
    'vscode',
  ],
  format: 'cjs',
  define: appEnv,
  minify: shouldMinify,
  sourcemap: shouldSourcemap,
  treeShaking: true,
  loader: {
    ".node": "file",
  },
};

Queries:
This may be happening because esbuild is treating .node files as static assets and not bundling or resolving them correctly as native modules.

  1. Could this be resolved by marking these native modules (like sharp and lancedb) as external in the esbuild config?
  2. If so, is there a recommended folder structure or placement for these .node files so that they are correctly required at runtime in vscode extension?
  3. Is there a best practice / recommended way for bundling native binaries in a VSCode extension using esbuild?
  4. Any guidance or examples of how to structure this setup for native bindings would be appreciated.
@evanw
Copy link
Owner

evanw commented Apr 22, 2025

By default, attempting to bundle a .node file with esbuild should give the error No loader is configured for ".node" files. So I'm guessing that you configured esbuild to somehow handle .node files. How you configured esbuild determines what happens with these files. You have not posted any reproduction instructions here so I can't say what's happening in your case.

You asked about marking as external and best practice. Indeed, marking dependencies as external is best practice when bundling for node (which is the case here as VSCode runs Electron which runs node). This is covered in the getting started instructions for node:

You also may not want to bundle your dependencies with esbuild. There are many node-specific features that esbuild doesn't support while bundling such as __dirname, import.meta.url, fs.readFileSync, and *.node native binary modules. You can exclude all of your dependencies from the bundle by setting packages to external ... If you do this, your dependencies must still be present on the file system at run-time since they are no longer included in the bundle.

Not all packages are bundler-friendly, so the most robust and general way to handle dependencies is to leave them unbundled, and to make sure they are present alongside your code at run-time (just like writing normal JavaScript code for node without using esbuild). Some libraries may support being bundled and it may be ok to bundle them, but that's something you have to evaluate on a case-by-case basis. I recommend starting off by excluding all dependencies using --packages=external and only doing something more advanced if you need to.

@aspire-shatheesh
Copy link
Author

@evanw thanks for taking time to respond. I will mark the native modules as external and give it a try.

This is my esbuild configuration for reference

const buildOptions = {
  entryPoints: ['src/extension.ts'],
  bundle: true,
  platform: 'node',
  outfile: 'out/extension.js',
  external: [
    'vscode',
  ],
  format: 'cjs',
  define: appEnv,
  minify: shouldMinify,
  sourcemap: shouldSourcemap,
  treeShaking: true,
  loader: {
    ".node": "file",
    '.wasm': 'file',
  },
  inject: ["./importMetaUrl.js"],
  define: { ...appEnv, "import.meta.url": "importMetaUrl" },
};

I have a question about the bundled code and wanted to understand whether it's a bug or expected behavior. My out folder has lancedb.linux-x64-gnu-V6OX6RLO.node and below is an excerpt from the bundled extension.js

var require_lancedb_linux_x64_gnu = __commonJS({
  "node_modules/@lancedb/lancedb-linux-x64-gnu/lancedb.linux-x64-gnu.node"(exports2, module2) {
    module2.exports = "./lancedb.linux-x64-gnu-V6OX6RLO.node";
  }
});

localFileExisted = existsSync5(join5(__dirname, "lancedb.linux-x64-gnu.node"));
              try {
                if (localFileExisted) {
                  nativeBinding = require("./lancedb.linux-x64-gnu.node");
                } else {
                  nativeBinding = require_lancedb_linux_x64_gnu();
                }

nativeBinding = require_lancedb_linux_x64_gnu();

The require_lancedb_linux_x64_gnu() call resolves to a string rather than a module, so changing it to nativeBinding = require(require_lancedb_linux_x64_gnu()) works. I wanted to check if this is a bug with the bundler or something related to the respective Node modules. I've posted a detailed script in the initial post in case it's helpful.

@hyrious
Copy link

hyrious commented Apr 23, 2025

@aspire-shatheesh The file loader returns the relative path to the target file. You can find reasonable examples in the documentation.

@aspire-shatheesh
Copy link
Author

@hyrious thankyou. The node libraries such as lancedb provided prebuilt binaries in .node format. So to make it work, do i need to remove file loader in esbuild config and instead mark it as external ? Just confirming if that is the way forward

@evanw
Copy link
Owner

evanw commented Apr 23, 2025

Yes, you should mark it as external.

The file loader transforms the require() call into something that returns a string literal instead of a module object, which will break pretty much any library using native node modules as the library won't be expecting a string. The file loader is only intended to be used by code that expects the bundler to be doing this transform (the primary use case for the file loader is to bundle legacy code that uses Webpack's file-loader).

The copy loader would be closer, and might actually work for bundling some .node modules. It copies the imported file into the output directory and then rewrites the import path to point to the copied file. But that only works if the code in the .node file is completely independent of the output location. If it needs to load other files in the package directory, then the transform done by the copy loader will break it because the directory structure is now different than the original package.

So neither the file nor the copy loaders are general solutions, which is why I don't recommend them. By far the easiest solution is to just not bundle your dependencies (i.e. to mark them as external). You'd only want to use copy for advanced use cases, such as if you are authoring the .node module yourself, you have full control of the source code, and you test everything thoroughly. I assume that's not relevant for your situation.

@hyrious
Copy link

hyrious commented Apr 23, 2025

In VS Code you can publish platform specific extensions. Simply adds --target linux-x64 in the vsce commands and related files in node_modules will be scanned and included in the extension package.

@aspire-shatheesh
Copy link
Author

aspire-shatheesh commented Apr 25, 2025

@evanw and @hyrious thanks for the input and guidance. I’ve now marked the native modules as external. Below is my updated esbuild config and now I am half way through it.

const buildOptions = {
  entryPoints: ['src/extension.ts'],
  bundle: true,
  platform: 'node',
  outfile: 'out/extension.js',
  external: [
    'vscode',
    'esbuild',
    'pg-hstore',
    'onnxruntime-node',
    '@lancedb',
    'sharp',
    'sqlite3'
  ],
  format: 'cjs',
  minify: true,
  sourcemap: false,
  treeShaking: true,
  inject: ["./importMetaUrl.js"],
  define: { ...appEnv, "import.meta.url": "importMetaUrl" },
};

The current node_modules directory is around 200+ MB. Since I'm marking certain packages as externals, I now need to manually copy these node_modules into the /out directory when packaging the VSCode extension using vsce.

However, I'm encountering errors related to missing dependencies of @lancedb—for example, the apache-arrow module, which is a dependency of @lancedb. To work around this, I had to write a script to recursively traverse all of @lancedb's dependencies and copy them into node_modules as well. I have pasted the script below incase if required.

I have few questions:

  1. Is writing a custom script to recursively traverse and copy transitive dependencies the correct approach, or am I missing something? Is there a better way to manage dependencies when marking modules as external?
  2. Is it possible for esbuild to perform tree-shaking or only mark the required node_modules that need to be copied or used?
  3. Is it possible to load large binaries at runtime from a CDN instead of packaging them with the VSCode extension (i.e., bundling them inside node_modules)?
function copyPackage(pkgName, baseDir = rootNodeModules) {
  const pkgPath = require.resolve(path.join(baseDir, pkgName));
  const pkgRoot = findPackageRoot(pkgPath);

  if (visited.has(pkgRoot)) return;
  visited.add(pkgRoot);

  const relativePath = path.relative(rootNodeModules, pkgRoot);
  const destPath = path.join(outNodeModules, relativePath);

  console.log(`Copying ${pkgName} → ${destPath}`);
  fse.ensureDirSync(path.dirname(destPath));
  fse.copySync(pkgRoot, destPath);

  const { dependencies, optionalDependencies, peerDependencies } = getPackageDependencies(pkgRoot);

  for (const dep of [...dependencies, ...optionalDependencies, ...peerDependencies]) {
    try {
      const nestedBase = path.join(pkgRoot, 'node_modules');
      if (fs.existsSync(path.join(nestedBase, dep))) {
        copyPackage(dep, nestedBase); // nested dep
      } else {
        copyPackage(dep); // top-level dep
      }
    } catch (err) {
      console.warn(`Failed to copy "${dep}" (optional or peer dependency) — skipping.`);
    }
  }
}

function findPackageRoot(startPath) {
  let dir = path.dirname(startPath);
  while (dir !== path.parse(dir).root) {
    const maybePkgJson = path.join(dir, 'package.json');
    if (fs.existsSync(maybePkgJson)) {
      return dir;
    }
    dir = path.dirname(dir);
  }
  throw new Error(`Unable to find package.json starting from ${startPath}`);
}

function copyExternals(packages = []) {
  if (!Array.isArray(packages)) {
    throw new Error('Expected an array of package names.');
  }

  for (const pkg of packages) {
    copyPackage(pkg);
  }

  console.log(`Copied ${visited.size} packages into preserved node_modules structure.`);
}

// iteratively copy all the dependencies and transitive dependencies
const externals = ['@lancedb/lancedb', 'onnxruntime-node', 'sharp', 'sqlite3'];
await copyExternals(externals);

Once again, thank you so much for taking the time to help with my questions and challenges—I really appreciate your support.

@hyrious
Copy link

hyrious commented Apr 25, 2025

The current node_modules directory is around 200+ MB.

Before answering your questions, it is totally fine to have a VS Code extension that big. For example ms-vscode.vscode-speech is about 170 MB on macOS.

Is writing a custom script to recursively traverse and copy transitive dependencies the correct approach, or am I missing something? Is there a better way to manage dependencies when marking modules as external?

You can have npm do this thing for you. In these steps:

  1. Make sure all external modules are in package.json's "dependencies" field, and everything not external are in package.json's "devDependencies" field. You only have to list direct dependencies which are literally imported in your source code.

  2. Build the extension like what you have done. Difference is that the external option can now be replaced with:

    import pkg from './package.json'
    await build({
      ...,
      external: Object.keys(pkg.dependencies),
    });
  3. Prepare the package for vsce. You can either copy the package.json and out/extension.js to a temp folder or just reuse current working directory, the later will be more simpler when in the CI.

  4. Run npm install --omit=dev. It means to install everything except devDependencies. As a result you only installed all packages referenced by dependencies.

    Note that npm install only downloads dependencies for current platform. If you want to do something cross platform you will need --cpu and --os to override it. You can also use the CI (like GitHub action) to really use different OS.

  5. Run vsce publish to publish it.

Is it possible for esbuild to perform tree-shaking or only mark the required node_modules that need to be copied or used?

AFAIK there's no such usage in esbuild. You may be able to run a full bundle (including everything) to help you find out used files in node_modules, but not all packages can be bundled by esbuild, especially those with native node extensions. So simply no.

Is it possible to load large binaries at runtime from a CDN instead of packaging them with the VSCode extension (i.e., bundling them inside node_modules)?

VS Code extensions, when activated, run in Node.js environment (it is slightly different than bare Node.js since it is simulated by electron). That means you can do everything in a VS Code extension like in Node.js, including downloading something from the network and importing them in the runtime. You can even ask your users to have npm installed globally and run npm install at runtime to finish your installation.

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

No branches or pull requests

3 participants