feat: Add prepare:tsconfig hook for updating .wxt/tsconfig.json file (#2450)
This commit is contained in:
@@ -17,28 +17,14 @@ Or if you're in a monorepo, you may not want to extend the config. If you don't
|
||||
/// <reference path="./.wxt/wxt.d.ts" />
|
||||
```
|
||||
|
||||
## Compiler Options
|
||||
|
||||
To specify custom compiler options, add them in `<rootDir>/tsconfig.json`:
|
||||
|
||||
```jsonc
|
||||
// <rootDir>/tsconfig.json
|
||||
{
|
||||
"extends": ".wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## TSConfig Paths
|
||||
|
||||
WXT provides a default set of path aliases.
|
||||
|
||||
| Alias | To | Example |
|
||||
| ----- | ------------- | ----------------------------------------------- |
|
||||
| `~~` | `<rootDir>/*` | `import "~~/scripts"` |
|
||||
| `@@` | `<rootDir>/*` | `import "@@/scripts"` |
|
||||
| `~~` | `<rootDir>/*` | `import "~~/package.json"` |
|
||||
| `@@` | `<rootDir>/*` | `import "@@/package.json"` |
|
||||
| `~` | `<srcDir>/*` | `import { toLowerCase } from "~/utils/strings"` |
|
||||
| `@` | `<srcDir>/*` | `import { toLowerCase } from "@/utils/strings"` |
|
||||
|
||||
@@ -63,3 +49,34 @@ export default defineConfig({
|
||||
import { fakeTab } from 'testing/fake-objects';
|
||||
import { toLowerCase } from 'strings';
|
||||
```
|
||||
|
||||
## Custom Options
|
||||
|
||||
To specify custom compiler or top-level options, you have two options:
|
||||
|
||||
1. Override WXT's value completely by setting it in your root `tsconfig.json`:
|
||||
|
||||
```jsonc
|
||||
// <rootDir>/tsconfig.json
|
||||
{
|
||||
"extends": ".wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
2. Merge custom values into values generated by WXT via the `prepare:tsconfig` hook:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
'prepare:tsconfig': (wxt, { tsconfig }) => {
|
||||
tsconfig.compilerOptions.lib.push('WebWorker');
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Overriding is great for simple boolean values, but not for complex options like `paths` or `libs`, where you can't just "add" a value to the object from your `tsconfig.json` file. So WXT provides the hook that let's you add or merge or delete or do whatever you want to the config file before it's written to the `.wxt/` directory.
|
||||
|
||||
@@ -6,6 +6,7 @@ const hooks: WxtHooks = {
|
||||
ready: vi.fn(),
|
||||
'config:resolved': vi.fn(),
|
||||
'prepare:types': vi.fn(),
|
||||
'prepare:tsconfig': vi.fn(),
|
||||
'prepare:publicPaths': vi.fn(),
|
||||
'build:before': vi.fn(),
|
||||
'build:done': vi.fn(),
|
||||
@@ -56,6 +57,7 @@ describe('Hooks', () => {
|
||||
ready: true,
|
||||
'config:resolved': true,
|
||||
'prepare:types': true,
|
||||
'prepare:tsconfig': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': false,
|
||||
'build:done': false,
|
||||
@@ -88,6 +90,7 @@ describe('Hooks', () => {
|
||||
ready: true,
|
||||
'config:resolved': true,
|
||||
'prepare:types': true,
|
||||
'prepare:tsconfig': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
@@ -120,6 +123,7 @@ describe('Hooks', () => {
|
||||
ready: true,
|
||||
'config:resolved': true,
|
||||
'prepare:types': true,
|
||||
'prepare:tsconfig': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
@@ -152,6 +156,7 @@ describe('Hooks', () => {
|
||||
ready: true,
|
||||
'config:resolved': true,
|
||||
'prepare:types': true,
|
||||
'prepare:tsconfig': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
@@ -191,6 +196,7 @@ describe('Hooks', () => {
|
||||
ready: true,
|
||||
'config:resolved': true,
|
||||
'prepare:types': true,
|
||||
'prepare:tsconfig': true,
|
||||
'prepare:publicPaths': true,
|
||||
'build:before': true,
|
||||
'build:done': true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
describe('TypeScript Project', () => {
|
||||
it('should generate defined constants correctly', async () => {
|
||||
@@ -333,25 +334,63 @@ describe('TypeScript Project', () => {
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@": [".."],
|
||||
"@/*": ["../*"],
|
||||
"~": [".."],
|
||||
"~/*": ["../*"],
|
||||
"@@": [".."],
|
||||
"@@/*": ["../*"],
|
||||
"~~": [".."],
|
||||
"~~/*": ["../*"]
|
||||
"@": [
|
||||
".."
|
||||
],
|
||||
"@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~": [
|
||||
".."
|
||||
],
|
||||
"~/*": [
|
||||
"../*"
|
||||
],
|
||||
"@@": [
|
||||
".."
|
||||
],
|
||||
"@@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~~": [
|
||||
".."
|
||||
],
|
||||
"~~/*": [
|
||||
"../*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"../**/*",
|
||||
"./wxt.d.ts"
|
||||
],
|
||||
"exclude": ["../.output"]
|
||||
"exclude": [
|
||||
"../**/node_modules",
|
||||
"../.output"
|
||||
]
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
it("should allow updating the project's tsconfig via hooks", async () => {
|
||||
const project = new TestProject();
|
||||
project.setConfigFileConfig({});
|
||||
project.addFile('entrypoints/unlisted.html', '<html></html>');
|
||||
|
||||
await project.prepare({
|
||||
hooks: {
|
||||
'prepare:tsconfig': (_, { tsconfig }) => {
|
||||
tsconfig.test = 'test';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const actual = JSON.parse(
|
||||
await readFile(project.resolvePath('.wxt/tsconfig.json'), 'utf-8'),
|
||||
);
|
||||
expect(actual).toMatchObject({ test: 'test' });
|
||||
});
|
||||
|
||||
it('should generate correct path aliases for a custom srcDir', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('src/entrypoints/unlisted.html', '<html></html>');
|
||||
@@ -377,21 +416,40 @@ describe('TypeScript Project', () => {
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@": ["../src"],
|
||||
"@/*": ["../src/*"],
|
||||
"~": ["../src"],
|
||||
"~/*": ["../src/*"],
|
||||
"@@": [".."],
|
||||
"@@/*": ["../*"],
|
||||
"~~": [".."],
|
||||
"~~/*": ["../*"]
|
||||
"@": [
|
||||
"../src"
|
||||
],
|
||||
"@/*": [
|
||||
"../src/*"
|
||||
],
|
||||
"~": [
|
||||
"../src"
|
||||
],
|
||||
"~/*": [
|
||||
"../src/*"
|
||||
],
|
||||
"@@": [
|
||||
".."
|
||||
],
|
||||
"@@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~~": [
|
||||
".."
|
||||
],
|
||||
"~~/*": [
|
||||
"../*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"../**/*",
|
||||
"./wxt.d.ts"
|
||||
],
|
||||
"exclude": ["../.output"]
|
||||
"exclude": [
|
||||
"../**/node_modules",
|
||||
"../.output"
|
||||
]
|
||||
}"
|
||||
`);
|
||||
});
|
||||
@@ -425,23 +483,46 @@ describe('TypeScript Project', () => {
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"example": ["../example"],
|
||||
"example/*": ["../example/*"],
|
||||
"@": ["../src"],
|
||||
"@/*": ["../src/*"],
|
||||
"~": ["../src"],
|
||||
"~/*": ["../src/*"],
|
||||
"@@": [".."],
|
||||
"@@/*": ["../*"],
|
||||
"~~": [".."],
|
||||
"~~/*": ["../*"]
|
||||
"example": [
|
||||
"../example"
|
||||
],
|
||||
"example/*": [
|
||||
"../example/*"
|
||||
],
|
||||
"@": [
|
||||
"../src"
|
||||
],
|
||||
"@/*": [
|
||||
"../src/*"
|
||||
],
|
||||
"~": [
|
||||
"../src"
|
||||
],
|
||||
"~/*": [
|
||||
"../src/*"
|
||||
],
|
||||
"@@": [
|
||||
".."
|
||||
],
|
||||
"@@/*": [
|
||||
"../*"
|
||||
],
|
||||
"~~": [
|
||||
".."
|
||||
],
|
||||
"~~/*": [
|
||||
"../*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"../**/*",
|
||||
"./wxt.d.ts"
|
||||
],
|
||||
"exclude": ["../.output"]
|
||||
"exclude": [
|
||||
"../**/node_modules",
|
||||
"../.output"
|
||||
]
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -283,41 +283,42 @@ async function getTsConfigEntry(): Promise<WxtDirFileEntry> {
|
||||
if (res.startsWith('.') || res.startsWith('/')) return res;
|
||||
return './' + res;
|
||||
};
|
||||
const paths = Object.entries(wxt.config.alias)
|
||||
.flatMap(([alias, absolutePath]) => {
|
||||
const aliasPath = getTsconfigPath(absolutePath);
|
||||
return [
|
||||
`"${alias}": ["${aliasPath}"]`,
|
||||
`"${alias}/*": ["${aliasPath}/*"]`,
|
||||
];
|
||||
})
|
||||
.map((line) => ` ${line}`)
|
||||
.join(',\n');
|
||||
|
||||
const text = `{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
${paths}
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"${getTsconfigPath(wxt.config.root)}/**/*",
|
||||
"./wxt.d.ts"
|
||||
],
|
||||
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
|
||||
}`;
|
||||
const paths = Object.fromEntries(
|
||||
Object.entries(wxt.config.alias)
|
||||
.map(([alias, absolutePath]) => [alias, getTsconfigPath(absolutePath)])
|
||||
.flatMap(([alias, path]) => [
|
||||
// Add alias for files
|
||||
[alias, [path]],
|
||||
// Add alias for all files in a directory
|
||||
[alias + '/*', [path + '/*']],
|
||||
]),
|
||||
);
|
||||
|
||||
const tsconfig = {
|
||||
compilerOptions: {
|
||||
target: 'ESNext',
|
||||
module: 'ESNext',
|
||||
moduleResolution: 'Bundler',
|
||||
noEmit: true,
|
||||
esModuleInterop: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
resolveJsonModule: true,
|
||||
strict: true,
|
||||
skipLibCheck: true,
|
||||
paths,
|
||||
},
|
||||
include: [`${getTsconfigPath(wxt.config.root)}/**/*`, './wxt.d.ts'],
|
||||
exclude: [
|
||||
getTsconfigPath(wxt.config.root) + '/**/node_modules',
|
||||
getTsconfigPath(wxt.config.outBaseDir),
|
||||
],
|
||||
};
|
||||
|
||||
await wxt.hooks.callHook('prepare:tsconfig', wxt, { tsconfig });
|
||||
|
||||
return {
|
||||
path: 'tsconfig.json',
|
||||
text,
|
||||
text: JSON.stringify(tsconfig, null, 2),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1259,6 +1259,11 @@ export interface ServerInfo {
|
||||
origin: string;
|
||||
}
|
||||
|
||||
export type PrepareTsconfigs = {
|
||||
/** The JSON contents of the `.wxt/tsconfig.json` file. */
|
||||
tsconfig: any;
|
||||
};
|
||||
|
||||
export type HookResult = Promise<void> | void;
|
||||
|
||||
export interface WxtHooks {
|
||||
@@ -1298,6 +1303,17 @@ export interface WxtHooks {
|
||||
* });
|
||||
*/
|
||||
'prepare:types': (wxt: Wxt, entries: WxtDirEntry[]) => HookResult;
|
||||
/**
|
||||
* Called before WXT writes your tsconfig to the disk, allowing full
|
||||
* customization by modifying the object by reference.
|
||||
*
|
||||
* @since 0.20.28
|
||||
* @example
|
||||
* wxt.hooks.hook('prepare:tsconfig', (wxt, { tsconfig }) => {
|
||||
* tsconfig.compilerOptions.lib.push('WebWorker');
|
||||
* });
|
||||
*/
|
||||
'prepare:tsconfig': (wxt: Wxt, configs: PrepareTsconfigs) => HookResult;
|
||||
/**
|
||||
* Called before generating the list of public paths inside
|
||||
* `.wxt/types/paths.d.ts`. Use this hook to add additional paths (relative to
|
||||
|
||||
Reference in New Issue
Block a user