@eventuras/vite-config 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @eventuras/vite-config
2
+
3
+ Shared Vite configurations for Eventuras monorepo libraries.
4
+
5
+ ## Overview
6
+
7
+ This package provides reusable Vite configuration presets for different types of libraries in the Eventuras monorepo. It helps maintain consistency, reduces duplication, and makes it easier to update build configurations across all libraries.
8
+
9
+ ## Presets
10
+
11
+ ### Vanilla Library (`vanilla-lib`)
12
+
13
+ For plain TypeScript libraries without React.
14
+
15
+ ```typescript
16
+ // vite.config.ts
17
+ import { defineVanillaLibConfig } from '@eventuras/vite-config/vanilla-lib';
18
+ import { resolve } from 'path';
19
+
20
+ export default defineVanillaLibConfig({
21
+ entry: 'src/index.ts',
22
+ name: 'MyLibrary',
23
+ external: ['some-external-dep'],
24
+ });
25
+ ```
26
+
27
+ ### React Library (`react-lib`)
28
+
29
+ For React component libraries and utilities.
30
+
31
+ ```typescript
32
+ // vite.config.ts
33
+ import { defineReactLibConfig } from '@eventuras/vite-config/react-lib';
34
+ import { resolve } from 'path';
35
+
36
+ export default defineReactLibConfig({
37
+ entry: 'src/index.ts',
38
+ // Or use glob for multiple entry points:
39
+ // entry: 'src/**/index.ts',
40
+ external: ['@eventuras/ratio-ui'],
41
+ tailwind: true, // Enable Tailwind CSS
42
+ preserveModules: true, // Keep source structure in dist
43
+ useSWC: false, // Use Babel (default) or SWC for React transform
44
+ });
45
+ ```
46
+
47
+ **Features:**
48
+ - React plugin (Babel or SWC)
49
+ - TypeScript declaration generation
50
+ - Optional Tailwind CSS support
51
+ - 'use client' directive preservation for RSC
52
+ - Configurable module preservation
53
+ - Auto-excludes test files and stories from types
54
+
55
+ ### Next.js Library (`next-lib`)
56
+
57
+ For React libraries compatible with Next.js (includes Next.js externals).
58
+
59
+ ```typescript
60
+ // vite.config.ts
61
+ import { defineNextLibConfig } from '@eventuras/vite-config/next-lib';
62
+ import { resolve } from 'path';
63
+
64
+ export default defineNextLibConfig({
65
+ entry: {
66
+ 'Image/index': resolve(__dirname, 'src/Image/index.ts'),
67
+ 'Link/index': resolve(__dirname, 'src/Link/index.ts'),
68
+ index: resolve(__dirname, 'src/index.ts'),
69
+ },
70
+ external: ['@eventuras/ratio-ui'],
71
+ tailwind: true,
72
+ });
73
+ ```
74
+
75
+ **Features:**
76
+ - All React library features
77
+ - Next.js externals (next, next/image, next/link, etc.)
78
+ - Always preserves 'use client' directives
79
+
80
+ ## Configuration Options
81
+
82
+ ### Common Options
83
+
84
+ All presets support these options:
85
+
86
+ - **`entry`**: Library entry point(s)
87
+ - String: `'src/index.ts'`
88
+ - Object: `{ index: 'src/index.ts', utils: 'src/utils.ts' }`
89
+ - Glob (React only): `'src/**/index.ts'`
90
+
91
+ - **`external`**: Additional dependencies to exclude from bundle
92
+ - Array of strings or RegExp patterns
93
+ - Common externals (react, next) are already included
94
+
95
+ - **`viteConfig`**: Additional Vite config to merge
96
+
97
+ ### React-Specific Options
98
+
99
+ - **`tailwind`**: Enable Tailwind CSS support (default: `false`)
100
+ - **`preserveModules`**: Keep source structure in output (default: `true`)
101
+ - **`preserveUseClientDirectives`**: Preserve 'use client' for RSC (default: `true`)
102
+ - **`useSWC`**: Use SWC instead of Babel (default: `false`)
103
+ - **`dts`**: TypeScript declaration options
104
+ - `entryRoot`: Source root (default: `'src'`)
105
+ - `outDir`: Output directory (default: `'dist'`)
106
+ - `rollupTypes`: Bundle types into single file (default: `false`)
107
+
108
+ ## Migration Guide
109
+
110
+ ### Before (duplicated config in each library)
111
+
112
+ ```typescript
113
+ // libs/my-lib/vite.config.ts
114
+ import react from '@vitejs/plugin-react';
115
+ import { resolve } from 'path';
116
+ import { defineConfig } from 'vite';
117
+ import dts from 'vite-plugin-dts';
118
+
119
+ export default defineConfig({
120
+ plugins: [
121
+ react(),
122
+ dts({
123
+ include: ['src/**/*'],
124
+ exclude: ['src/**/*.stories.tsx'],
125
+ }),
126
+ ],
127
+ build: {
128
+ lib: {
129
+ entry: resolve(__dirname, 'src/index.ts'),
130
+ formats: ['es'],
131
+ },
132
+ rollupOptions: {
133
+ external: ['react', 'react-dom', 'react/jsx-runtime'],
134
+ },
135
+ },
136
+ });
137
+ ```
138
+
139
+ ### After (using shared config)
140
+
141
+ ```typescript
142
+ // libs/my-lib/vite.config.ts
143
+ import { defineReactLibConfig } from '@eventuras/vite-config/react-lib';
144
+
145
+ export default defineReactLibConfig({
146
+ entry: 'src/index.ts',
147
+ });
148
+ ```
149
+
150
+ ## Best Practices
151
+
152
+ 1. **Use the most specific preset**: If building for Next.js, use `next-lib` instead of `react-lib`
153
+ 2. **Only specify what's different**: The presets have sensible defaults
154
+ 3. **Use glob patterns for multi-entry libraries**: `entry: 'src/**/index.ts'`
155
+ 4. **Enable SWC for faster builds**: `useSWC: true` (in development)
156
+ 5. **Keep modules preserved**: `preserveModules: true` for better tree-shaking
157
+
158
+ ## Troubleshooting
159
+
160
+ ### Types not generated
161
+ - Check that your TypeScript files are in `src/`
162
+ - Ensure test files use `.test.ts` or `.spec.ts` extensions
163
+ - Check `dts.entryRoot` and `dts.outDir` options
164
+
165
+ ### 'use client' directives missing
166
+ - Ensure `preserveUseClientDirectives: true` (default for Next.js)
167
+ - Check that source files have 'use client' at the top
168
+
169
+ ### Build errors with Next.js
170
+ - Use `next-lib` preset instead of `react-lib`
171
+ - Check that Next.js packages are in `external` array
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@eventuras/vite-config",
3
+ "version": "0.2.2",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/losol/origo.git",
8
+ "directory": "config/vite-config"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ "./base": "./src/base.ts",
13
+ "./react-lib": "./src/react-lib.ts",
14
+ "./vanilla-lib": "./src/vanilla-lib.ts",
15
+ "./next-lib": "./src/next-lib.ts"
16
+ },
17
+ "files": [
18
+ "src"
19
+ ],
20
+ "dependencies": {
21
+ "@tailwindcss/vite": "^4.3.2",
22
+ "@vitejs/plugin-react": "^6.0.3",
23
+ "@vitejs/plugin-react-swc": "^4.3.0",
24
+ "glob": "^13.0.6",
25
+ "vite-plugin-dts": "^5.0.3"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^6.0.2",
29
+ "vite": "^8.1.0"
30
+ },
31
+ "peerDependencies": {
32
+ "vite": "^7.0.0 || ^8.0.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }
package/src/base.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig, type UserConfig } from 'vite';
2
+
3
+ /**
4
+ * Base Vite configuration with common defaults for all Eventuras packages.
5
+ */
6
+ export function defineBaseConfig(config: UserConfig = {}): UserConfig {
7
+ return defineConfig({
8
+ ...config,
9
+ build: {
10
+ ...config.build,
11
+ // Common build optimizations
12
+ minify: false, // Libraries should not be minified
13
+ sourcemap: true, // Always generate sourcemaps for debugging
14
+ },
15
+ });
16
+ }
@@ -0,0 +1,45 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { builtinModules } from 'node:module';
3
+ import { resolve } from 'node:path';
4
+
5
+ interface PackageJson {
6
+ dependencies?: Record<string, string>;
7
+ peerDependencies?: Record<string, string>;
8
+ optionalDependencies?: Record<string, string>;
9
+ }
10
+
11
+ /**
12
+ * Reads the consumer package's package.json and returns RegExp patterns that
13
+ * mark every runtime dependency (and its subpaths) as external.
14
+ *
15
+ * Bundling runtime deps into a library is harmful: it duplicates code, breaks
16
+ * `instanceof` checks across module boundaries (the consumer's class identity
17
+ * differs from the bundled copy), and inflates installed size. Consumers
18
+ * already install these deps via the lib's own package.json.
19
+ */
20
+ export function getRuntimeDependencyExternals(cwd: string = process.cwd()): RegExp[] {
21
+ const pkgPath = resolve(cwd, 'package.json');
22
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson;
23
+
24
+ const names = [
25
+ ...Object.keys(pkg.dependencies ?? {}),
26
+ ...Object.keys(pkg.peerDependencies ?? {}),
27
+ ...Object.keys(pkg.optionalDependencies ?? {}),
28
+ ];
29
+
30
+ return names.map(name => new RegExp(`^${escapeRegex(name)}(/.*)?$`));
31
+ }
32
+
33
+ /**
34
+ * Externalize Node built-ins in both forms — `node:fs` (always matched by the
35
+ * regex) and the bare `fs` form (matched against the explicit list, since the
36
+ * names overlap with userland packages and can't be regex-distinguished).
37
+ */
38
+ export const NODE_BUILTINS_EXTERNAL: (string | RegExp)[] = [
39
+ /^node:/,
40
+ ...builtinModules,
41
+ ];
42
+
43
+ function escapeRegex(s: string): string {
44
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
45
+ }
@@ -0,0 +1,36 @@
1
+ import type { UserConfig } from 'vite';
2
+ import { defineReactLibConfig, type ReactLibConfig } from './react-lib.ts';
3
+
4
+ export interface NextLibConfig extends Omit<ReactLibConfig, 'external'> {
5
+ /**
6
+ * Additional external dependencies beyond the Next.js defaults and the
7
+ * package.json auto-externalization (via the underlying React preset).
8
+ */
9
+ external?: (string | RegExp)[];
10
+ }
11
+
12
+ /**
13
+ * Vite configuration preset for Next.js-compatible React libraries.
14
+ * Extends the React library config with Next.js-specific externals and defaults.
15
+ */
16
+ export function defineNextLibConfig(config: NextLibConfig): UserConfig {
17
+ const { external = [], ...reactConfig } = config;
18
+
19
+ // Next.js externals - include all next/* paths to avoid bundling internal modules
20
+ const nextExternals = [
21
+ 'next',
22
+ 'next/image',
23
+ 'next/link',
24
+ 'next/navigation',
25
+ 'next/router',
26
+ 'next/headers',
27
+ /^next\//, // Externalize all next/* imports
28
+ ];
29
+
30
+ return defineReactLibConfig({
31
+ ...reactConfig,
32
+ external: [...nextExternals, ...external],
33
+ // Next.js libraries should always preserve client directives
34
+ preserveUseClientDirectives: true,
35
+ });
36
+ }
@@ -0,0 +1,239 @@
1
+ import { defineConfig, type UserConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import dts from 'vite-plugin-dts';
5
+ import { resolve } from 'node:path';
6
+ import { glob } from 'glob';
7
+ import fs from 'node:fs';
8
+
9
+ import { getRuntimeDependencyExternals, NODE_BUILTINS_EXTERNAL } from './externals.ts';
10
+
11
+ /**
12
+ * Plugin to preserve 'use client' directives in React Server Components.
13
+ * This ensures client-side code is properly marked when building for Next.js.
14
+ */
15
+ function preserveUseClient() {
16
+ return {
17
+ name: 'preserve-use-client',
18
+ enforce: 'post' as const,
19
+ generateBundle(_options: any, bundle: any) {
20
+ for (const [_fileName, chunk] of Object.entries(bundle)) {
21
+ if ((chunk as any).type === 'chunk') {
22
+ const chunkData = chunk as any;
23
+ // Check if any source module had 'use client'
24
+ const hasClientDirective = chunkData.moduleIds?.some((id: string) => {
25
+ try {
26
+ const content = fs.readFileSync(id, 'utf-8');
27
+ // Remove leading comments to check for 'use client'
28
+ const withoutComments = content
29
+ .replace(/^(\s*\/\/.*\n)+/, '')
30
+ .replace(/^(\s*\/\*[\s\S]*?\*\/\s*)/, '');
31
+ return (
32
+ withoutComments.trimStart().startsWith("'use client'") ||
33
+ withoutComments.trimStart().startsWith('"use client"')
34
+ );
35
+ } catch {
36
+ return false;
37
+ }
38
+ });
39
+
40
+ if (hasClientDirective) {
41
+ const codeStart = chunkData.code.trimStart();
42
+ if (!codeStart.startsWith("'use client'") && !codeStart.startsWith('"use client"')) {
43
+ chunkData.code = `'use client';\n${chunkData.code}`;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ },
49
+ };
50
+ }
51
+
52
+ export interface ReactLibConfig {
53
+ /**
54
+ * Library entry point(s). Can be:
55
+ * - A string path: 'src/index.ts'
56
+ * - An object with multiple entries: { index: 'src/index.ts', utils: 'src/utils.ts' }
57
+ * - A glob pattern: 'src/** /index.ts' (auto-discovers multiple entry points)
58
+ */
59
+ entry: string | Record<string, string>;
60
+
61
+ /**
62
+ * Additional external dependencies to exclude from the bundle, on top of the
63
+ * runtime deps read from the consumer's package.json and the React core
64
+ * externals. Pass `false` to opt out of package.json auto-externalization.
65
+ */
66
+ external?: (string | RegExp)[] | false;
67
+
68
+ /**
69
+ * Enable Tailwind CSS support via @tailwindcss/vite plugin.
70
+ * @default false
71
+ */
72
+ tailwind?: boolean;
73
+
74
+ /**
75
+ * Preserve module structure in output.
76
+ * When true, creates a dist structure mirroring src structure.
77
+ * When false, bundles everything into fewer files.
78
+ * @default true
79
+ */
80
+ preserveModules?: boolean;
81
+
82
+ /**
83
+ * Enable 'use client' directive preservation for Next.js React Server Components.
84
+ * @default true
85
+ */
86
+ preserveUseClientDirectives?: boolean;
87
+
88
+ /**
89
+ * Use SWC instead of Babel for React transformation (faster builds).
90
+ * @default false
91
+ */
92
+ useSWC?: boolean;
93
+
94
+ /**
95
+ * TypeScript declaration options.
96
+ */
97
+ dts?: {
98
+ /**
99
+ * Entry root for DTS generation.
100
+ * @default 'src'
101
+ */
102
+ entryRoot?: string;
103
+ /**
104
+ * Output directory for .d.ts files.
105
+ * @default 'dist'
106
+ */
107
+ outDir?: string;
108
+ /**
109
+ * Roll up types into a single .d.ts file.
110
+ * @default false
111
+ */
112
+ rollupTypes?: boolean;
113
+ };
114
+
115
+ /**
116
+ * Additional Vite configuration to merge.
117
+ */
118
+ viteConfig?: UserConfig;
119
+ }
120
+
121
+ /**
122
+ * Vite configuration preset for React libraries.
123
+ * Includes React plugin, TypeScript declarations, and common React externals.
124
+ */
125
+ export function defineReactLibConfig(config: ReactLibConfig): UserConfig {
126
+ const {
127
+ entry,
128
+ external,
129
+ tailwind = false,
130
+ preserveModules = true,
131
+ preserveUseClientDirectives = true,
132
+ useSWC = false,
133
+ dts: dtsOptions = {},
134
+ viteConfig = {},
135
+ } = config;
136
+
137
+ const autoExternals =
138
+ external === false ? [] : getRuntimeDependencyExternals();
139
+ const userExternals = Array.isArray(external) ? external : [];
140
+
141
+ // Determine entry points
142
+ let entryPoints: string | string[] | Record<string, string>;
143
+ if (typeof entry === 'string' && entry.includes('*')) {
144
+ entryPoints = glob.sync(resolve(process.cwd(), entry));
145
+ } else if (typeof entry === 'string') {
146
+ entryPoints = resolve(process.cwd(), entry);
147
+ } else {
148
+ entryPoints = entry;
149
+ }
150
+
151
+ // Common React externals
152
+ const reactExternals = ['react', 'react-dom', 'react/jsx-runtime'];
153
+
154
+ const plugins: any[] = [];
155
+
156
+ // Add React plugin (SWC or standard)
157
+ if (useSWC) {
158
+ // Dynamically import SWC plugin only when needed
159
+ const reactSwc = require('@vitejs/plugin-react-swc').default;
160
+ plugins.push(reactSwc());
161
+ } else {
162
+ plugins.push(react());
163
+ }
164
+
165
+ // Add Tailwind if enabled
166
+ if (tailwind) {
167
+ plugins.push(tailwindcss());
168
+ }
169
+
170
+ // Add DTS plugin
171
+ plugins.push(
172
+ dts({
173
+ entryRoot: dtsOptions.entryRoot || 'src',
174
+ outDir: dtsOptions.outDir || 'dist',
175
+ include: ['src/**/*'],
176
+ exclude: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/*.stories.tsx'],
177
+ copyDtsFiles: true,
178
+ rollupTypes: dtsOptions.rollupTypes || false,
179
+ })
180
+ );
181
+
182
+ // Add use client preservation if enabled
183
+ if (preserveUseClientDirectives) {
184
+ plugins.push(preserveUseClient());
185
+ }
186
+
187
+ // Add custom plugins from viteConfig
188
+ if (viteConfig.plugins) {
189
+ plugins.push(...viteConfig.plugins);
190
+ }
191
+
192
+ return defineConfig({
193
+ plugins,
194
+ resolve: {
195
+ alias: {
196
+ '@': resolve(process.cwd(), './src'),
197
+ ...viteConfig.resolve?.alias,
198
+ },
199
+ ...viteConfig.resolve,
200
+ },
201
+ build: {
202
+ minify: false,
203
+ sourcemap: true,
204
+ lib: {
205
+ entry: entryPoints,
206
+ formats: ['es'],
207
+ },
208
+ rollupOptions: {
209
+ ...viteConfig.build?.rollupOptions,
210
+ external: [
211
+ ...reactExternals,
212
+ ...autoExternals,
213
+ ...NODE_BUILTINS_EXTERNAL,
214
+ ...userExternals,
215
+ ],
216
+ output: {
217
+ preserveModules,
218
+ ...(preserveModules && {
219
+ preserveModulesRoot: 'src',
220
+ entryFileNames: '[name].js',
221
+ }),
222
+ ...viteConfig.build?.rollupOptions?.output,
223
+ },
224
+ },
225
+ // Spread other build options from viteConfig except lib and rollupOptions
226
+ ...Object.fromEntries(
227
+ Object.entries(viteConfig.build || {}).filter(
228
+ ([key]) => key !== 'lib' && key !== 'rollupOptions'
229
+ )
230
+ ),
231
+ },
232
+ // Spread other viteConfig options except build, plugins, and resolve
233
+ ...Object.fromEntries(
234
+ Object.entries(viteConfig).filter(
235
+ ([key]) => key !== 'build' && key !== 'plugins' && key !== 'resolve'
236
+ )
237
+ ),
238
+ });
239
+ }
@@ -0,0 +1,90 @@
1
+ import { defineConfig, type UserConfig } from 'vite';
2
+ import dts from 'vite-plugin-dts';
3
+ import { resolve } from 'node:path';
4
+
5
+ import { getRuntimeDependencyExternals, NODE_BUILTINS_EXTERNAL } from './externals.ts';
6
+
7
+ export interface VanillaLibConfig {
8
+ /**
9
+ * Library entry point(s). Can be a string or an object with multiple entries.
10
+ * @example 'src/index.ts'
11
+ * @example { index: 'src/index.ts', utils: 'src/utils.ts' }
12
+ */
13
+ entry: string | Record<string, string>;
14
+
15
+ /**
16
+ * Library name for UMD builds (optional).
17
+ */
18
+ name?: string;
19
+
20
+ /**
21
+ * Additional external dependencies to exclude from the bundle, on top of the
22
+ * runtime deps read from the consumer's package.json. Pass `false` to opt out
23
+ * of the package.json auto-externalization (rarely what you want).
24
+ */
25
+ external?: (string | RegExp)[] | false;
26
+
27
+ /**
28
+ * Additional Vite configuration to merge.
29
+ */
30
+ viteConfig?: UserConfig;
31
+ }
32
+
33
+ /**
34
+ * Vite configuration preset for vanilla TypeScript libraries.
35
+ *
36
+ * Defaults that matter for libraries:
37
+ * - All runtime deps (dependencies + peerDependencies + optionalDependencies)
38
+ * are externalized so consumers get one copy from their node_modules instead
39
+ * of inlined+mangled copies inside the published bundle.
40
+ * - Minification is OFF — consumers minify their own bundle, and unminified
41
+ * output preserves class names (so `instanceof` and stack traces work).
42
+ * - Sourcemaps ON for debuggable consumer stack traces.
43
+ */
44
+ export function defineVanillaLibConfig(config: VanillaLibConfig): UserConfig {
45
+ const { entry, name, external, viteConfig = {} } = config;
46
+
47
+ const autoExternals =
48
+ external === false ? [] : getRuntimeDependencyExternals();
49
+ const userExternals = Array.isArray(external) ? external : [];
50
+
51
+ return defineConfig({
52
+ plugins: [
53
+ dts({
54
+ include: ['src/**/*'],
55
+ exclude: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
56
+ }),
57
+ ...(viteConfig.plugins || []),
58
+ ],
59
+ build: {
60
+ minify: false,
61
+ sourcemap: true,
62
+ lib: {
63
+ entry: typeof entry === 'string' ? resolve(process.cwd(), entry) : entry,
64
+ ...(name && { name }),
65
+ formats: ['es'],
66
+ fileName: (_format, entryName) => `${entryName}.js`,
67
+ },
68
+ rollupOptions: {
69
+ ...viteConfig.build?.rollupOptions,
70
+ external: [...autoExternals, ...NODE_BUILTINS_EXTERNAL, ...userExternals],
71
+ output: {
72
+ preserveModules: false,
73
+ ...viteConfig.build?.rollupOptions?.output,
74
+ },
75
+ },
76
+ // Spread other build options from viteConfig except lib and rollupOptions
77
+ ...Object.fromEntries(
78
+ Object.entries(viteConfig.build || {}).filter(
79
+ ([key]) => key !== 'lib' && key !== 'rollupOptions'
80
+ )
81
+ ),
82
+ },
83
+ // Spread other viteConfig options except build and plugins
84
+ ...Object.fromEntries(
85
+ Object.entries(viteConfig).filter(
86
+ ([key]) => key !== 'build' && key !== 'plugins'
87
+ )
88
+ ),
89
+ });
90
+ }