解决无法调试 vscode 扩展的历程与总结

850 阅读4分钟

该文章来自 commit 注释

fix: can't Activating extension and add a breakpoint

error mssage: Activating extension 'xx' failed: Cannot find module 'xx\src\extension.js'

reason: the 'main' field in package.json is wrongly set './src/extension.js'

solution: change to './out/extension.js'.

But this time, there is not exist out folder because we set 'noEmit' in tsconfig for using 'allowImportingTsExtensions'. So we need ignore ts ext when import module in ts.

disable the noEmit and allowImportingTsExtensions config in tsconfig. And then change './command.ts' to './command' in extension.ts.

When we del the '.ts' ext, there emit a error in extension.ts.

error message: : Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './command.js'?

reason: we had set "type": "module" in package.json. By default, the ts suffix is not automatically matched.

So we can del 'type' config in package.json as a solution. Or change tsconfig to:

    "module": "ESNext",
    "moduleResolution": "Bundler",

4.

When you change tsconfig to:

    "module": "ESNext",
    "moduleResolution": "Bundler",

you will get another error:

error message: Activating extension 'xx' failed: require() of ES Module xx\out\extension.js from xx\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js not supported. Instead change the require of extension.js in xx\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js to a dynamic import() which is available in all CommonJS modules..

Put simply, loader.js can't import extension.js, because extension.js is a ES Module. And It is recommended to change the require() call to a dynamic import() call in loader.js. Because dynamic import() is a syntax supported by both ES Module and CommonJS modules.

But we can't modify loader.js, so we can only change extension.js to CommonJS module. To do this, we need modify the value of 'module' in tsconfig.json to 'CommonJS'.

This time, there is another error in tsconfig.json: Option 'bundler' can only be used when 'module' is set to 'es2015' or later.

When the 'module' value in tsconfig.json is set to 'CommonJS', the value of 'moduleResolution' can only be set to 'Node', 'Node10' or 'Classic'. But I can't recommend set to 'Classic' because it will emit another error.

error in node_modules@types\node\globals.d.ts: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?

So we set moduleResolution to 'Node' or ignore moduleResolution config.

If you debug vscode extension again in this time, you will find a error same above: tell us can't import a ES module using require().

This because the 'type' in package.json of working root is set to 'module'. We don't del this 'type' config, just add a new package.json in the 'out' folder which include '"type": "commonjs"' config

Oh~ We finally solved everything! You might find a question, how fix can't add a breakpoint, not answer. In fact, this issue has solve when we delete the '.ts' extension in src/extension.ts.


上面说了很多内容,而且全部都是用英文写的,并且是边调试变写的,所以过程非常详细。现在弄懂的来做个简单的中文总结:

先回答一个简单的:无法断点。这个和 vscode 的 ts 模块解析有关,简单地说 ts 模块解析不推荐我们使用 ts 扩展名。因为 ts 不添加扩展名时处理速度更快,详细原理可以查看 www.typescriptlang.org/docs/handbo…

然后是调试扩展时无法启动问题,主要在解决的也是这个问题。之所以过程如此曲折,是因为不熟悉 tsconfig。最开始的问题其实就是 package.json 中的 main 字段写错了,该字段应该指向编译后的 js 文件,而不是原 ts 文件。所以重点就在于让 vscode 能给找到编译后的 extension.js 文件。

由于历史遗留问题(其实也就一两天前啦)我在 tsconfig 中配置了 noEmit 和 allowImportingTsExtensions,当初配置这两个字段的原因是 eslint 报错:在 extension.ts 文件中,如果导入时不提供后缀名,那么调用时就会提示 @typescript-eslint/no-unsafe-call 报错。

然后就是调试时提供模块不兼容问题。这个我感觉不全是我的锅吧,如果 loader.js 中能够使用动态的 import() ,它就是可以同时导入 CommonJS 和 ES Module 两种模块(这个知识点来自 ChatGPT)。没办法,我们无法修改 loader.js,所以只能修改我们的 extension.js 模块为 CommonJS 了。

不过,由于 out 目录中没有 package.json 文件,所以会自动使用工作目录中的 package.json 文件。而我又在 package.json 中设置了 type 为 module 类型。之所以这样设置,其实也算是“历史遗留问题😀”,因为想要在 build.js 中直接使用 import 直接导入 json 文件。

总的来说,解决无法调试 vscode 扩展需要注意以下三步:

  1. package.json 中的 main 字段要正确指向编译后的 js 文件
  2. ts 文件导入模块时不推荐使用扩展名
  3. (唯一比较可惜的是)需要在 out 目录中手动创建 package.json 文件

无需手动创建 package.json 文件:

// .vscode/tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "npm",
			"script": "watch",
			"problemMatcher": "$tsc-watch",
			"isBackground": true,
			"presentation": {
				"reveal": "never"
			},
			"group": {
				"kind": "build",
				// "isDefault": true
			}
		},
		{
			"label": "debug misc",
			"type": "process",
			// 使用 pwsh.exe 运行命令
			"command": "pwsh",
			"args": [
				"-Command",
				// "New-Item -ItemType Directory -Force -Path 'out' && Write-Output '{\"type\":\"commonjs\"}' | Out-File -FilePath 'out/package.json' -Encoding UTF8",
				// 由于该 task 依赖与 npm: watch 命令,所以可以确保 out 路径的存在。但考虑某某些极端情况下 npm: watch 不会生成新的 js 文件,所以还是保留上面命令
				"New-Item -ItemType Directory -Force -Path 'out' && Write-Output '{\"type\":\"commonjs\"}' | Out-File -FilePath 'out/package.json' -Encoding UTF8",
			],
			"dependsOn": ["npm: watch"],
			"group": {
				"kind": "build",
				"isDefault": true
			}
		},
	]
}