Add support for TC39 Explicit Resource Management (`using`/`await using` syntax)
Feature Request
Please support the (await) using syntax from the TC39 Explicit Resource Management proposal. Currently, attempting to use this syntax results in the error: This experimental syntax requires enabling the parser plugin: 'explicitResourceManagement'.
Is your feature request related to a bug?
N/A
What are the alternatives?
Alternative solutions I've considered:
- Manually configuring Babel with the
@babel/plugin-syntax-explicit-resource-managementplugin, but I'm unsure how to properly integrate this with the wxt framework. - Using traditional try/finally blocks or other resource management patterns, but these are more verbose and less readable than the new syntax.
Additional context
The Explicit Resource Management proposal (https://github.com/tc39/proposal-explicit-resource-management) is gaining significant adoption.
Current implementation status (from https://github.com/tc39/proposal-explicit-resource-management/issues/242):
- TypeScript has supported it since version 5.2
- Babel has supported it since approximately version 7.20.0
- esbuild (which powers Vite) specifically added support in version 0.18.7
- V8 has added support in Chrome 134
- Mozilla's SpiderMonkey engine is actively working on implementation
- Moddable XS implemented both sync and async versions in 2023
- Webkit has a meta issue but has seen no movement and is currently unassigned (as of Feb 2025)
As this syntax is becoming standard across the JavaScript ecosystem, it would be valuable for wxt to support this modern language feature that improves resource management patterns.
Can you share a reproduction? I would have thought that this already works.
here is the repro: https://github.com/aidenlx/using-test I've included disposable in both backgroud script and content script
when running pnpm dev, it throws:
[22:19:16] ERROR This experimental syntax requires enabling the parser plugin: "explicitResourceManagement". (entrypoints/content.ts:6:9)
4 | matches: ["*://*.google.com/*"],
5 | main() {
6 | using testDisposable = new TestDisposable();
| ^
7 | testDisposable.print();
8 | console.log("Hello content.");
Alright, so, this is an upstream issue with magicast. WXT uses it to remove the main function from content script and background entrypoints:
$ wxt build --debug
...
⚙ vite-node transformed entrypoint /Users/aklinker1/Development/local/wxt-issue-1528/entrypoints/content.ts
⚙ Original:
---
// import TestDisposable from "./disposable";
export default defineContentScript({
matches: ["*://*.google.com/*"],
main() {
// using testDisposable = new TestDisposable();
// testDisposable.print();
// console.log("Hello content.");
},
});
---
⚙ Transformed:
---
// import TestDisposable from "./disposable";
export default defineContentScript({
matches: ["*://*.google.com/*"],
});
---
...
Here's the actual code that does this, you can see it uses magicast: https://github.com/wxt-dev/wxt/blob/259cec9ea8c18886249f5613fa42756071eb7206/packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts
And here's magicast's list of babel plugins in use:
https://github.com/unjs/magicast/blob/50e2207842672e2c1c75898f0b1b97909f3b6c92/src/babel.ts#L34-L74
You should open a PR over there adding the explicitResourceManagement plugin to that list.
If you don't want to open a PR, the simple change is to not use using inside entrypoints' directly. Since WXT only uses magicast on entrypoints/* and entrypoints/*/index.ts, you can use it anywhere else in your project. It's a workaround, but you could do something like this:
// entrypoints/content/index.ts
import { main } from './main';
export default defineContentScript({
// ...
main,
});
// entrypoints/content/main.ts
import { ContentScriptContext } from 'wxt/client';
export function main(ctx: ContentScriptContext) {
using disposable = ...;
}
Thank you for the quick and detailed explanation! Since magicast's development seems to be on pause currently (no commits in the last 8 months), I'll use the workaround you suggested by moving my code with the using syntax out of the direct entrypoint files. This is a practical solution while we wait for potential updates. Deeply appreciate your help!
We could consider dropping magicast (and potentially any other dependencies that depend on babel) and use oxc-parser instead to modify the AST.
Edit: Looks like the OXC team needs to publish an napi binding for oxc_codegen: https://github.com/oxc-project/oxc/discussions/6854
Additional React Component Compatibility Issue
I've discovered that the using syntax compatibility issue extends to React components as well. When attempting to use this syntax within React components (not just in entrypoints), I encounter a similar parser error from the vite:react-babel plugin:
the repro is updated with react preset: https://github.com/aidenlx/using-test
2:17:03 PM [vite] (client) Pre-transform error: /Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx: Support for the experimental syntax 'explicitResourceManagement' isn't currently enabled (50:2):
48 |
49 | function queryDatabase() {
> 50 | using database = createDatabase();
| ^
51 | return database.query();
52 | }
53 |
If you already added the plugin for this syntax to your config, it's possible that your config isn't being loaded.
You can re-run Babel with the BABEL_SHOW_CONFIG_FOR environment variable to show the loaded configuration:
npx cross-env BABEL_SHOW_CONFIG_FOR=/Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx <your build command>
See https://babeljs.io/docs/configuration#print-effective-configs for more info.
Plugin: vite:react-babel
File: /Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx:50:1
48 |
49 | function queryDatabase() {
50 | using database = createDatabase();
| ^
51 | return database.query();
52 | }
2:17:03 PM [vite] Internal server error: /Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx: Support for the experimental syntax 'explicitResourceManagement' isn't currently enabled (50:2):
48 |
49 | function queryDatabase() {
> 50 | using database = createDatabase();
| ^
51 | return database.query();
52 | }
53 |
If you already added the plugin for this syntax to your config, it's possible that your config isn't being loaded.
You can re-run Babel with the BABEL_SHOW_CONFIG_FOR environment variable to show the loaded configuration:
npx cross-env BABEL_SHOW_CONFIG_FOR=/Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx <your build command>
See https://babeljs.io/docs/configuration#print-effective-configs for more info.
Plugin: vite:react-babel
File: /Users/aidenlx/repo/using-test/entrypoints/popup/App.tsx:50:1
48 |
49 | function queryDatabase() {
50 | using database = createDatabase();
| ^
51 | return database.query();
52 | }
at toParseError (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parse-error.ts:95:45)
at TypeScriptParserMixin.raise (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/tokenizer/index.ts:1497:19)
at TypeScriptParserMixin.expectPlugin (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/tokenizer/index.ts:1551:16)
at TypeScriptParserMixin.parseStatementContent (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:529:14)
at TypeScriptParserMixin.parseStatementContent (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/plugins/typescript/index.ts:3091:20)
at TypeScriptParserMixin.parseStatementLike (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:437:17)
at TypeScriptParserMixin.parseStatementListItem (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:386:17)
at TypeScriptParserMixin.parseBlockOrModuleBlockBody (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1403:16)
at TypeScriptParserMixin.parseBlockBody (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1376:10)
at TypeScriptParserMixin.parseBlock (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1344:10)
at TypeScriptParserMixin.parseFunctionBody (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/expression.ts:2558:24)
at TypeScriptParserMixin.parseFunctionBodyAndFinish (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/expression.ts:2527:10)
at TypeScriptParserMixin.parseFunctionBodyAndFinish (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/plugins/typescript/index.ts:2573:20)
at callback (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1650:12)
at TypeScriptParserMixin.withSmartMixTopicForbiddingContext (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/expression.ts:3090:14)
at TypeScriptParserMixin.parseFunction (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1648:10)
at TypeScriptParserMixin.parseFunctionStatement (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1036:17)
at TypeScriptParserMixin.parseStatementContent (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:480:21)
at TypeScriptParserMixin.parseStatementContent (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/plugins/typescript/index.ts:3091:20)
at TypeScriptParserMixin.parseStatementLike (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:437:17)
at TypeScriptParserMixin.parseModuleItem (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:374:17)
at TypeScriptParserMixin.parseBlockOrModuleBlockBody (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1402:16)
at TypeScriptParserMixin.parseBlockBody (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:1376:10)
at TypeScriptParserMixin.parseProgram (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:225:10)
at TypeScriptParserMixin.parseTopLevel (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/statement.ts:203:25)
at TypeScriptParserMixin.parse (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/parser/index.ts:90:10)
at TypeScriptParserMixin.parse (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/plugins/typescript/index.ts:4219:20)
at parse (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/parser/src/index.ts:92:38)
at parser (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/core/src/parser/index.ts:28:19)
at parser.next (<anonymous>)
at normalizeFile (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/core/src/transformation/normalize-file.ts:49:24)
at normalizeFile.next (<anonymous>)
at run (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/core/src/transformation/index.ts:40:36)
at run.next (<anonymous>)
at transform (/Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/core/src/transform.ts:29:20)
at transform.next (<anonymous>)
at step (/Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:261:32)
at /Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:273:13
at async.call.result.err.err (/Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:223:11)
at cb (/Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:189:28)
at /Users/aidenlx/repo/using-test/node_modules/.pnpm/@[email protected]/node_modules/@babel/core/src/gensync-utils/async.ts:90:7
at /Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:113:33
at step (/Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:287:14)
at /Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:273:13
at async.call.result.err.err (/Users/aidenlx/repo/using-test/node_modules/.pnpm/[email protected]/node_modules/gensync/index.js:223:11)
I'm not an expert with react... But that doesn't seem like it's something you should do. Just due to the nature of functional components, it seems like a bad idea. Especially if reacts vite plugin is telling you you can't do that.
But that error is unrelated to WXT.