Skip to content

Commit 4a7d468

Browse files
committed
feat: create Vite plugin sexy declare type
* feat: initial plugin * feat: update lint config * fix: resolve issue to hoisting * chore: update metadata * docs: create * ci: prepare release
1 parent 86e40b5 commit 4a7d468

12 files changed

Lines changed: 376 additions & 71 deletions

File tree

.changeset/odd-islands-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vite-plugin-sexy-declare-type': major
3+
---
4+
5+
First release

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public-hoist-pattern[]=eslint*

packages/vite-plugin-react-stylish-svg/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
],
2525
"scripts": {
2626
"build": "tsdown",
27-
"dev": "tsdown --watch"
27+
"dev": "tsdown --watch",
28+
"lint": "eslint src"
2829
},
2930
"main": "./dist/index.js",
3031
"module": "./dist/index.js",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2025 StyleList94
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Vite Plugin Sexy Declare Type
2+
3+
Zero-config TypeScript declaration file generator for Vite library mode
4+
5+
[![npm](https://img.shields.io/npm/v/vite-plugin-sexy-declare-type)](https://www.npmjs.com/package/vite-plugin-sexy-declare-type)
6+
7+
## Features
8+
9+
- ✨ Zero configuration - works out of the box
10+
- 🎯 Automatic entry detection from Vite library config
11+
- ⚡ Fast declaration generation using TypeScript Compiler API
12+
13+
## Getting Started
14+
15+
### Requires
16+
17+
- Vite 7.0.0+
18+
- TypeScript 5.0.0+
19+
20+
### Install
21+
22+
```bash
23+
pnpm add -D vite-plugin-sexy-declare-type
24+
```
25+
26+
## Quick Start
27+
28+
### Add plugin to Vite config
29+
30+
`vite.config.ts`
31+
32+
```typescript
33+
import { defineConfig } from 'vite';
34+
import sexyDeclareType from 'vite-plugin-sexy-declare-type';
35+
36+
export default defineConfig({
37+
build: {
38+
lib: {
39+
entry: './src/index.ts',
40+
formats: ['es'],
41+
},
42+
},
43+
plugins: [sexyDeclareType()],
44+
});
45+
```
46+
47+
That's it! When you run `vite build`, declaration files (`.d.ts`) will be automatically generated alongside your build output.
48+
49+
### Type Checking
50+
51+
This plugin focuses on **declaration file generation**, not type validation. For production builds, always run type checking first:
52+
53+
```json
54+
{
55+
"scripts": {
56+
"build": "tsc --noEmit && vite build"
57+
}
58+
}
59+
```
60+
61+
This ensures:
62+
63+
- ✅ Type errors are caught before build
64+
- ✅ Clean build output without warnings
65+
- ✅ Production-ready type declarations
66+
67+
## Limitations
68+
69+
- **Library Mode Only**: Only works with Vite's library mode (`build.lib`)
70+
- **No tsconfig Inheritance**: Uses hardcoded TypeScript options for maximum compatibility
71+
- May generate declarations even with type errors in your code
72+
- Run `tsc --noEmit` first for comprehensive type checking
73+
74+
## Troubleshooting
75+
76+
### Declarations not generated
77+
78+
Check if library mode is configured:
79+
80+
```typescript
81+
export default defineConfig({
82+
build: {
83+
lib: {
84+
// ← Must have this
85+
entry: './src/index.ts',
86+
},
87+
},
88+
});
89+
```
90+
91+
### Types seem incorrect
92+
93+
The plugin uses simplified TypeScript config for broad compatibility. If you need precise type validation:
94+
95+
1. **Run type check before build**:
96+
97+
```bash
98+
pnpm tsc --noEmit
99+
```
100+
101+
2. **Update to build script**:
102+
103+
```json
104+
{
105+
"scripts": {
106+
"build": "tsc --noEmit && vite build"
107+
}
108+
}
109+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { config } from '@plugin-baby/eslint-config/base';
2+
3+
/** @type {import("eslint").Linter.Config} */
4+
export default [
5+
...config,
6+
{
7+
files: ['src/**/*.ts'],
8+
rules: {
9+
'no-console': 'off',
10+
},
11+
},
12+
];
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "vite-plugin-sexy-declare-type",
3+
"version": "0.0.0",
4+
"description": "Zero-config TypeScript declaration file generator for Vite library mode",
5+
"author": "StyleList94",
6+
"license": "MIT",
7+
"homepage": "https://github.com/StyleList94/plugin-baby/tree/main/packages/vite-plugin-sexy-declare-type#readme",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/StyleList94/plugin-baby.git",
11+
"directory": "packages/vite-plugin-sexy-declare-type"
12+
},
13+
"bugs": {
14+
"url": "https://github.com/StyleList94/plugin-baby/issues"
15+
},
16+
"scripts": {
17+
"build": "tsdown",
18+
"dev": "tsdown --watch",
19+
"lint": "eslint src"
20+
},
21+
"keywords": [
22+
"vite-plugin",
23+
"typescript",
24+
"declaration"
25+
],
26+
"type": "module",
27+
"main": "./dist/index.js",
28+
"module": "./dist/index.js",
29+
"types": "./dist/index.d.ts",
30+
"exports": {
31+
".": {
32+
"types": "./dist/index.d.ts",
33+
"default": "./dist/index.js"
34+
}
35+
},
36+
"files": [
37+
"dist"
38+
],
39+
"engines": {
40+
"node": ">=20.11.0"
41+
},
42+
"peerDependencies": {
43+
"typescript": ">=5.0.0",
44+
"vite": ">=7.0.0"
45+
},
46+
"devDependencies": {
47+
"@plugin-baby/eslint-config": "workspace:*",
48+
"@plugin-baby/typescript-config": "workspace:*",
49+
"@types/node": "^22.18.8",
50+
"tsdown": "^0.15.6",
51+
"typescript": "^5.9.3",
52+
"vite": "^7.1.9"
53+
}
54+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { Plugin, ResolvedConfig } from 'vite';
2+
3+
import path from 'node:path';
4+
import { styleText } from 'node:util';
5+
6+
import ts from 'typescript';
7+
8+
export default function sexyDeclareType(): Plugin {
9+
let config: ResolvedConfig;
10+
let entryFiles: string[] = [];
11+
12+
return {
13+
name: 'vite-plugin-sexy-declare-type',
14+
15+
configResolved(resolvedConfig) {
16+
config = resolvedConfig;
17+
const libConfig = config.build.lib;
18+
19+
if (!libConfig) {
20+
console.warn(
21+
'[sexy-declare-type] Only works with Vite library mode. Skipping.',
22+
);
23+
return;
24+
}
25+
26+
const entry = typeof libConfig === 'object' ? libConfig.entry : undefined;
27+
28+
if (!entry) {
29+
console.warn(
30+
'[sexy-declare-type] No entry found in library config. Skipping.',
31+
);
32+
return;
33+
}
34+
35+
if (typeof entry === 'string') {
36+
entryFiles = [path.resolve(config.root, entry)];
37+
} else if (Array.isArray(entry)) {
38+
entryFiles = entry.map((e) => path.resolve(config.root, e));
39+
} else {
40+
entryFiles = Object.values(entry).map((e) =>
41+
path.resolve(config.root, e),
42+
);
43+
}
44+
},
45+
46+
closeBundle: async () => {
47+
if (entryFiles.length === 0) return;
48+
49+
const startTime = performance.now();
50+
51+
const compilerOptions: ts.CompilerOptions = {
52+
declaration: true,
53+
emitDeclarationOnly: true,
54+
skipLibCheck: true,
55+
target: ts.ScriptTarget.ESNext,
56+
module: ts.ModuleKind.ESNext,
57+
moduleResolution: ts.ModuleResolutionKind.Bundler,
58+
outDir: path.resolve(config.root, config.build.outDir),
59+
esModuleInterop: true,
60+
jsx: ts.JsxEmit.Preserve,
61+
};
62+
63+
const host = ts.createCompilerHost(compilerOptions);
64+
const program = ts.createProgram(entryFiles, compilerOptions, host);
65+
program.emit(undefined, undefined, undefined, true);
66+
67+
const duration = ((performance.now() - startTime) / 1000).toFixed(2);
68+
const filesCount = program
69+
.getSourceFiles()
70+
.filter((f) => !f.fileName.includes('node_modules')).length;
71+
72+
console.log(
73+
`${styleText('green', '✓')} Sexy declarations ready in ${styleText('dim', `${duration}s`)} ${styleText('green', `(${filesCount} files)`)}`,
74+
);
75+
},
76+
};
77+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "@plugin-baby/typescript-config/tsdown.json",
3+
"compilerOptions": {
4+
"outDir": "./dist"
5+
},
6+
"include": ["src"],
7+
"exclude": ["node_modules", "dist"]
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from 'tsdown';
2+
3+
export default defineConfig({
4+
entry: ['./src/index.ts'],
5+
external: [
6+
'typescript', // TypeScript를 번들에 포함시키지 않음
7+
'vite', // Vite도 external
8+
],
9+
});

0 commit comments

Comments
 (0)