wxt icon indicating copy to clipboard operation
wxt copied to clipboard

Don't require WXT modules to define their own module augmentation

Open aklinker1 opened this issue 8 months ago • 2 comments

I attempted to set this up, but ran into an error as soon as I tried building a WXT module that adds types to the WXT config.

$ pnpm tsdown
ℹ tsdown v0.10.0 powered by rolldown v1.0.0-beta.8-commit.151352b                               tsdown 6:04:57 PM
[tsdown 6:04:57 PM] ℹ Using tsdown config: /Users/aklinker1/Development/github.com/wxt-dev/wxt/packages/analytics/tsdown.config.ts
[tsdown 6:04:57 PM] ℹ entry: modules/analytics/index.ts, modules/analytics/client.ts, modules/analytics/background-plugin.ts, modules/analytics/types.ts, modules/analytics/providers/google-analytics-4.ts, modules/analytics/providers/umami.ts
ℹ Using tsconfig: tsconfig.json                                                                 tsdown 6:04:57 PM
ℹ [ESM] Build start                                                                             tsdown 6:04:57 PM

 FATAL  Build failed with 2 errors:                                                              tsdown 6:04:58 PM

[UNRESOLVED_IMPORT] Error: Could not resolve '../pkg' in ../../node_modules/.pnpm/[email protected]/node_modules/lightningcss/node/index.js
    ╭─[ ../../node_modules/.pnpm/[email protected]/node_modules/lightningcss/node/index.js:17:28 ]
    │
 17 │   module.exports = require(../pkg);
    │                            ────┬───
    │                                ╰───── Module not found.
────╯

[UNLOADABLE_DEPENDENCY] Error: Could not load ../../node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.node
    ╭─[ ../../node_modules/.pnpm/[email protected]/node_modules/fsevents/fsevents.js:13:24 ]
    │
 13 │ const Native = require("./fsevents.node");
    │                        ────────┬────────
    │                                ╰────────── stream did not contain valid UTF-8
────╯


    at normalizeErrors (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected]/node_modules/rolldown/dist/shared/src-DvIQg0VF.mjs:935:18)
    at handleOutputErrors (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected]/node_modules/rolldown/dist/shared/src-DvIQg0VF.mjs:1758:34)
    at transformToRollupOutput (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected]/node_modules/rolldown/dist/shared/src-DvIQg0VF.mjs:1752:2)
    at RolldownBuild.write (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected]/node_modules/rolldown/dist/shared/src-DvIQg0VF.mjs:4168:11)
    at async build (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected]/node_modules/rolldown/dist/shared/src-DvIQg0VF.mjs:4210:22)
    at async /Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/index.js:536:5
    at async Promise.all (index 0)
    at async rebuild (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/index.js:527:3)
    at async buildSingle (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/index.js:519:2)
    at async Promise.all (index 0)
    at async build (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/index.js:491:19)
    at async CAC.<anonymous> (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/run.js:19:2)
    at async runCLI (/Users/aklinker1/Development/github.com/wxt-dev/wxt/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/tsdown/dist/run.js:37:3)

This is caused by the weird circular dependency between WXT and the module agumentation :/

The solution is simple: don't do module augmentation. instead, when a config key is listed in the module options, WXT should generate the module augmentation instead of requiring the module author to include it in the module code.

import { defineWxtModule } from 'wxt/modules';
- import 'wxt';

- export interface MyModuleOptions {
+ export interface ModuleOptions {
  // Add your build-time options here...
}
- declare module 'wxt' {
-   export interface InlineConfig {
-     // Add types for the "myModule" key in wxt.config.ts
-     myModule: MyModuleOptions;
-   }
- }

export default defineWxtModule<AnalyticModuleOptions>({
  configKey: 'myModule',

  // Build time config is available via the second argument of setup
  setup(wxt, options) {
    console.log(options);
  },
});

Then in the project's .wxt directory, we generate something like the following:

// .wxt/module-build-options.d.ts
import 'wxt';

declare module 'wxt' {
  export interface InlineConfig {
    myModule: import("my-module").ModuleConfig
  }
}

Good news, this shouldn't be a breaking change! Regardless of if we migrate to tsdown, this is something that should be done.

Originally posted by @aklinker1 in #1628

aklinker1 avatar Apr 28 '25 23:04 aklinker1

We also need to support runtime config. Should be easy to add to the generated type declaration.

// .wxt/module-runtime-options.d.ts
import 'wxt/utils/define-app-config';

declare module 'wxt/utils/define-app-config' {
  export interface WxtAppConfig {
    myModule: import("my-module").ModuleRuntimeOptions;
  }
}

aklinker1 avatar Apr 28 '25 23:04 aklinker1

Main problem will be detecting if ModuleOptions or ModuleRuntimeOptions types are included in a module... Ideally, we don't have to detect anything. Just check if a configKey or runtimeConfigKey exists.

aklinker1 avatar Apr 28 '25 23:04 aklinker1

Other thing to consider is how to make options optional. Maybe type the keys as configKey?: boolean | { name: string, optional: boolean }... How does nuxt do it?

aklinker1 avatar Apr 29 '25 06:04 aklinker1

Nuxt recommends to generate type(or any file) via addTypeTemplate, or via prepare:types hook.

If we have to support runtime config, How about adding addTypeTemplate and change docs?

SimYunSup avatar May 03 '25 01:05 SimYunSup

Nuxt recommends to generate type(or any file) via addTypeTemplate, or via prepare:types hook.

If we have to support runtime config, How about adding addTypeTemplate and change docs?

That's a good idea. For runtime module options, it's probably better for the module author to generate a module augmentation in the .wxt directory.

I was struggling with how to automatically generate the runtime augmentation, do we use a specific export name? But if the type is exported from the module, it could cause node types to leak into runtime code... But this will solve all those issues!

That said, I'll hold off on adding a full template system like Nuxt for now. It's possible to do this in the prepare:types hook, so that should be good enough for now.

aklinker1 avatar May 03 '25 02:05 aklinker1