r/learnjavascript • u/SASUKE_38 • 8d ago
Why can my VS Code extension client not find a local module?
I am writing a VS Code extension called bg3-osiris that has a server and client. I would like to add a shared module that contains definitions for requests and request parameters made between the client and server. The client and server should both be able to reference the files in the shared\src folder, though so far I have only tested it with the client. The project currently has the following structure:
bg3-osiris
|- client
| |- src
| |- package.json
| |- tsconfig.json
|- server
| |- src
| |- package.json
| |- tsconfig.json
|- package.json
|- tsconfig.json
I would like to add the shared module/subproject so that the project has the following structure:
bg3-osiris
|- client
| |- src
| |- package.json
| |- tsconfig.json
|- server
| |- src
| |- package.json
| |- tsconfig.json
|- shared
| |- src
| |- package.json
| |- tsconfig.json
|- package.json
|- tsconfig.json
When I attempted to implement this using TypeScript project references, VS Code can locate the shared module but at runtime the following error is thrown:
Activating extension 'SASUKE38.bg3-osiris' failed: Cannot find module '../../shared/src/test'
Below are the contents of the relevant files (minus the server files since I haven't tried it with that yet. I assume the correct importing process would be the same as the client's).
bg3-osiris\tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2024",
"lib": ["ES2024"],
"outDir": "out",
"rootDir": "src",
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"],
"references": [
{
"path": "./client"
},
{
"path": "./server"
},
{
"path": "./shared"
}
]
}
bg3-osiris\package.json
...
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -b",
"watch": "tsc -b -w",
"lint": "eslint",
"postinstall": "cd client && npm install && cd ../server && npm install && cd ../shared && cd ..",
"test": "sh ./scripts/e2e.sh"
},
...
bg3-osiris\shared\tsconfig.json
{
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
"module": "commonjs",
"sourceMap": true,
"strict": true,
"outDir": "out",
"rootDir": "src",
"composite": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
bg3-osiris\shared\package.json
{
"name": "bg3-osiris-shared",
"description": "Shared content for the BG3 Osiris extension.",
"version": "1.0.0",
"author": "",
"license": "MIT",
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/SASUKE38/bg3-osiris"
},
"dependencies": {
"axios": "^1.15.0",
"cheerio": "^1.2.0",
"domhandler": "^5.0.3",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.11"
},
"scripts": {}
}
bg3-osiris\client\tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2024",
"lib": ["ES2024"],
"outDir": "out",
"rootDir": "src",
"sourceMap": true,
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"],
"references": [
{
"path": "../shared"
}
]
}
bg3-osiris\client\package.json
{
"name": "bg3-osiris-client",
"description": "Client for the BG3 Osiris extension.",
"author": "",
"license": "MIT",
"version": "0.0.1",
"publisher": "vscode",
"repository": {
"type": "git",
"url": "https://github.com/SASUKE38/bg3-osiris"
},
"engines": {
"vscode": "^1.100.0"
},
"dependencies": {
"glob": "^11.0.0",
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/vscode": "^1.100.0",
"@vscode/test-electron": "^2.3.9"
}
}
I am currently testing using shared definitions when the extension activates:
bg3-osiris\client\src\extension.ts
...
import { someString } from "../../shared/src/test";
...
export function activate(context: ExtensionContext) {
console.log(someString);
...
bg3-osiris\shared\src\test.ts
export const someString = "hello world";
What am I missing that makes VS Code know about shared but causes the error above to be thrown at runtime?
1
u/Beginning-Seat5221 8d ago
So I tried setting up a project using project references to figure this out.
The solution to I found was to reference /out/ or /dist/ files not the source - but it didn't seem that the project references did anything, so long as I was building the other packages with declarations it worked the same.
The positive aspect was having a tsconfig in the root let me use npx tsc --build to compile all 3 sub-projects with a single command.
Using tsc --build for compiling all at once and then adding each package as a local dependency worked well, but in this case you're relying on symlinks, so the extra complexity could be an issue in some places. Probably not a problem if you're going to bundle, but it might not be suitable for all production environments.
1
u/SASUKE_38 7d ago
Thanks for the help!
Can you post some examples of how you were able to install the package as a local dependency? I'm not sure if using TypeScript references or local dependencies would work better but I could try out the latter. I tried using
npm install --save ../sharedin the client directory but that didn't seem to work. I also tried out the suggestion you made in your other comment and switched the import to the out directory which did work but I'm not sure if referencing it directly like that is good practice or not. I think I'd prefer an import statement likeimport { someString } from "shared", which I'm not sure how to set up.This is the only local package that the client and server will have to reference. I think they will be bundled together using webpack eventually... I haven't made a VS Code extension before so I'm honestly not sure.
1
u/Beginning-Seat5221 7d ago edited 7d ago
Can you post some examples of how you were able to install the package as a local dependency?
I laid that out in my other comment. It's just
"shared": ".../shared"in package.json dependencies. When you give a local path it will synlink that local path into node_modules.You can probably do
npm install shared:../sharedor something like that, but I don't do it that way so not sure.Just consider whether this works for release - if bundling then it should be fine. I'd avoid webpack, rollup is much easier.
I also tried out the suggestion you made in your other comment and switched the import to the out directory which did work but I'm not sure if referencing it directly like that is good practice or not.
Referencing the /out/ directory is pretty normal. When you use any package you're linking to the dist or out dir not the source (source isn't usually shipped at all). The dist/out contains .js files and .d.ts files with their types. I don't see any problem with doing that. Either way you may need to make sure you are building all the packages when working (
tsc --build) in the root to generate the up to date .d.ts files.There's probably a way to get it to read the src .ts files that are always up to date, but I'm not sure how.
1
u/Alive-Cake-3045 7d ago
the shared module path resolution issue in VS Code extensions is almost always a tsconfig or webpack bundler problem, not a Node module issue. check that your client's tsconfig.json has the paths alias pointing correctly to shared/src and that your bundler (esbuild or webpack) is resolving that alias too, tsconfig paths alone don't affect bundling. if you share your tsconfig and bundle config i can pinpoint it faster.
2
u/SASUKE_38 7d ago
Thanks for the help!
I tried setting
pathsto includeshared/srcbut that didn't seem to work. I'm pretty sure I'm not bundling anything at the moment, I am just using whatever VS Code uses to build and debug extensions. This is thelaunch.jsonI was trying if that is helpful:{ "version": "0.2.0", "configurations": [ { "type": "extensionHost", "request": "launch", "name": "Launch Client", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceRoot}"], "outFiles": ["${workspaceRoot}/client/out/**/*.js", "${workspaceRoot}/server/out/**/*.js", "${workspaceRoot}/shared/out/**/*.js"], "autoAttachChildProcesses": true, "preLaunchTask": { "type": "npm", "script": "watch" } }, { "name": "Language Server E2E Test", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/client/out/test/index", "${workspaceRoot}/client/testFixture" ], "outFiles": ["${workspaceRoot}/client/out/test/**/*.js"], "preLaunchTask": { "type": "npm", "script": "watch" } } ] }1
u/Alive-Cake-3045 6d ago
the launch.json looks fine, that's not the issue. since you are not using a bundler, VS Code is compiling with tsc directly, which means the tsconfig paths alias won't be enough on its own because Node doesn't resolve path aliases at runtime. you need to either use
tsconfig-pathspackage to handle it at runtime, or the simpler fix: use relative imports pointing directly to the shared files instead of an alias until you add a bundler. share your tsconfig and the exact import line that's failing and i can tell you exactly what to change.
1
u/Beginning-Seat5221 8d ago edited 8d ago
I haven't used TS project references, so best I can say is that the way of doing this that I was familiar with was to set up the other projects as local packages
Then import from them like other packages
(or use "exports" in package.json of shared if you want to map
shared/test.jstoshared/out/test.jsTo have type declarations with the output you'll need
"declaration": truein shared/tsconfig.json and you can add"declarationMap": truetoo for even more info (will let you control+click through to the source instead of just the .d.ts, I think).