@hardimpactdev/craft-ui 0.0.6 → 0.0.7

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.
@@ -0,0 +1,13 @@
1
+ import { Alias } from 'vite';
2
+ import { CraftConfigOptions } from './types.js';
3
+ /**
4
+ * Configure Vite resolve options for the Craft stack
5
+ *
6
+ * Handles two scenarios:
7
+ * 1. Normal: Just returns dedupe config and any custom aliases
8
+ * 2. Local development: Adds aliases for symlinked craft-ui to enable HMR
9
+ */
10
+ export declare function getResolveConfig(options: CraftConfigOptions): {
11
+ dedupe: string[];
12
+ alias: Alias[];
13
+ };
@@ -1,21 +1,6 @@
1
- interface viteConfigOptions {
2
- laravel?: {
3
- input?: string[];
4
- refresh?: boolean;
5
- };
6
- plugins?: any[];
7
- aliases?: any[];
8
- ui?: {
9
- localPath?: string;
10
- };
11
- }
12
- export declare function defineCraftConfig(options?: viteConfigOptions): import('vite').UserConfigFnObject;
13
- export declare function pluginConfig(options: viteConfigOptions): ({
14
- name: string;
15
- enforce: string;
16
- }[] | import('vite').PluginOption)[];
17
- export declare function aliasConfig(aliases?: any[], ui?: any): {
18
- dedupe: string[];
19
- alias: any[];
20
- };
21
- export {};
1
+ import { CraftConfigOptions } from './types.js';
2
+ export type { CraftConfigOptions } from './types.js';
3
+ export { getPlugins } from './plugins.js';
4
+ export { getServerConfig } from './server.js';
5
+ export { getResolveConfig } from './aliases.js';
6
+ export declare function defineCraftConfig(options?: CraftConfigOptions): import('vite').UserConfigFnObject;
@@ -0,0 +1,17 @@
1
+ import { CraftConfigOptions } from './types.js';
2
+ /**
3
+ * Configure all Vite plugins for the Craft stack
4
+ *
5
+ * Includes:
6
+ * - craft: Virtual module for Inertia app initialization
7
+ * - laravel: Laravel Vite integration
8
+ * - tailwindcss: Tailwind CSS v4
9
+ * - i18n: Laravel Vue i18n
10
+ * - vue: Vue 3 SFC support
11
+ * - vueDevTools: Vue DevTools integration
12
+ * - run: Auto-run artisan commands on file changes (dev only)
13
+ */
14
+ export declare function getPlugins(options: CraftConfigOptions): (false | {
15
+ name: string;
16
+ enforce: string;
17
+ }[] | import('vite').Plugin<any> | import('vite').PluginOption[] | Promise<import('vite').Plugin<any> | (false | null | undefined) | import('vite').PluginOption[]> | null | undefined)[];
@@ -0,0 +1,10 @@
1
+ import { ServerOptions } from 'vite';
2
+ /**
3
+ * Configure Vite dev server for Laravel apps behind a reverse proxy (Caddy/nginx)
4
+ *
5
+ * Handles:
6
+ * - HMR WebSocket connection through the proxy (wss://app.test:443)
7
+ * - Correct origin for CORS and asset URLs
8
+ * - Binding to all interfaces for proxy access
9
+ */
10
+ export declare function getServerConfig(mode: string): ServerOptions;
@@ -0,0 +1,64 @@
1
+ import { Alias } from 'vite';
2
+ /**
3
+ * Configuration options for defineCraftConfig
4
+ */
5
+ export interface CraftConfigOptions {
6
+ /**
7
+ * Laravel Vite plugin options
8
+ */
9
+ laravel?: {
10
+ /** Entry points for the application */
11
+ input?: string[];
12
+ /** Enable file refresh on changes */
13
+ refresh?: boolean;
14
+ };
15
+ /**
16
+ * Additional Vite plugins to include
17
+ */
18
+ plugins?: any[];
19
+ /**
20
+ * Additional Vite aliases
21
+ */
22
+ aliases?: Alias[];
23
+ /**
24
+ * Local UI library configuration for development with symlinked packages
25
+ */
26
+ ui?: LocalUiOptions;
27
+ }
28
+ /**
29
+ * Configuration for local UI library development
30
+ *
31
+ * When developing craft-ui alongside an app, this enables HMR by:
32
+ * 1. Redirecting package imports to source files instead of dist/
33
+ * 2. Resolving conflicting aliases (like @/) based on importer location
34
+ */
35
+ export interface LocalUiOptions {
36
+ /**
37
+ * Relative path from app root to the symlinked craft-ui directory
38
+ * @example '../craft-ui'
39
+ */
40
+ localPath?: string;
41
+ }
42
+ /**
43
+ * Internal alias configuration for local package resolution
44
+ */
45
+ export interface LocalAliasConfig {
46
+ /** Regex pattern to match imports */
47
+ pattern: RegExp;
48
+ /** The replacement path or alias */
49
+ replacement: string;
50
+ /**
51
+ * Absolute path to the library (for importer detection)
52
+ * If set along with basePaths, enables context-aware resolution
53
+ */
54
+ libraryPath?: string;
55
+ /**
56
+ * Base paths for context-aware resolution
57
+ */
58
+ basePaths?: {
59
+ /** Path to use when import is from the app */
60
+ app: string;
61
+ /** Path to use when import is from the library */
62
+ library: string;
63
+ };
64
+ }
@@ -0,0 +1,62 @@
1
+ import path from "path";
2
+ function getResolveConfig(options) {
3
+ const aliases = [...options.aliases || []];
4
+ if (isDevMode() && options.ui?.localPath) {
5
+ aliases.push(...createLocalDevAliases(options.ui.localPath));
6
+ }
7
+ return {
8
+ // Prevent duplicate instances of packages that must be singletons
9
+ dedupe: ["@inertiajs/vue3", "@tailwindcss/vite"],
10
+ alias: aliases
11
+ };
12
+ }
13
+ function createLocalDevAliases(localPath) {
14
+ const absoluteLibraryPath = path.resolve(process.cwd(), localPath);
15
+ const configs = [
16
+ // Handle @/ alias - resolve based on importer location
17
+ {
18
+ pattern: /^@\//,
19
+ replacement: "@/",
20
+ libraryPath: absoluteLibraryPath,
21
+ basePaths: {
22
+ app: "./resources/js",
23
+ library: path.join(localPath, "src")
24
+ }
25
+ },
26
+ // Redirect package imports to source
27
+ {
28
+ pattern: /^@hardimpactdev\/craft-ui/,
29
+ replacement: path.join(localPath, "index.ts")
30
+ }
31
+ ];
32
+ return configs.map(createAliasFromConfig);
33
+ }
34
+ function createAliasFromConfig(config) {
35
+ if (!config.libraryPath || !config.basePaths) {
36
+ return {
37
+ find: config.pattern,
38
+ replacement: path.resolve(process.cwd(), config.replacement)
39
+ };
40
+ }
41
+ return {
42
+ find: config.pattern,
43
+ replacement: config.replacement,
44
+ customResolver: createContextAwareResolver(config)
45
+ };
46
+ }
47
+ function createContextAwareResolver(config) {
48
+ return async function customResolver(source, importer) {
49
+ const isFromLibrary = importer?.includes(config.libraryPath);
50
+ const basePath = isFromLibrary ? config.basePaths.library : config.basePaths.app;
51
+ const importPath = source.replace(config.replacement, "");
52
+ const resolvedPath = path.resolve(process.cwd(), basePath, importPath);
53
+ const result = await this.resolve(resolvedPath);
54
+ return result?.id;
55
+ };
56
+ }
57
+ function isDevMode() {
58
+ return !process.argv.includes("build");
59
+ }
60
+ export {
61
+ getResolveConfig
62
+ };
@@ -1,166 +1,23 @@
1
- import { defineConfig, loadEnv } from "vite";
2
- import vue from "@vitejs/plugin-vue";
3
- import path from "path";
4
- import laravel from "laravel-vite-plugin";
5
- import { run } from "vite-plugin-run";
6
- import { craft } from "./craftPlugin.js";
7
- import tailwindcss from "@tailwindcss/vite";
8
- import i18n from "laravel-vue-i18n/vite";
9
- import vueDevTools from "vite-plugin-vue-devtools";
1
+ import { defineConfig } from "vite";
2
+ import { getPlugins } from "./plugins.js";
3
+ import { getServerConfig } from "./server.js";
4
+ import { getResolveConfig } from "./aliases.js";
5
+ import { getPlugins as getPlugins2 } from "./plugins.js";
6
+ import { getServerConfig as getServerConfig2 } from "./server.js";
7
+ import { getResolveConfig as getResolveConfig2 } from "./aliases.js";
10
8
  function defineCraftConfig(options = {}) {
11
- return defineConfig(({ mode }) => {
12
- const serverConfig = getServerConfig(mode);
13
- return {
14
- plugins: [
15
- ...pluginConfig(options),
16
- ...options.plugins || [],
17
- // Plugin to enforce allowedHosts after laravel-vite-plugin
18
- // This bypasses Vite's DNS rebinding protection which conflicts with reverse proxy setups
19
- {
20
- name: "craft-allowed-hosts",
21
- enforce: "post",
22
- config() {
23
- return {
24
- server: {
25
- allowedHosts: true
26
- }
27
- };
28
- }
29
- }
30
- ],
31
- resolve: {
32
- ...aliasConfig(options.aliases, options.ui)
33
- },
34
- server: serverConfig
35
- };
36
- });
37
- }
38
- function getServerConfig(mode) {
39
- const env = loadEnv(mode, process.cwd());
40
- const appUrl = env.VITE_APP_URL;
41
- if (!appUrl) {
42
- return { host: "0.0.0.0" };
43
- }
44
- try {
45
- const url = new URL(appUrl);
46
- const isHttps = url.protocol === "https:";
47
- return {
48
- host: "0.0.0.0",
49
- origin: appUrl,
50
- allowedHosts: true,
51
- hmr: {
52
- host: url.hostname,
53
- protocol: isHttps ? "wss" : "ws",
54
- clientPort: isHttps ? 443 : parseInt(url.port) || 80
55
- }
56
- };
57
- } catch {
58
- return { host: "0.0.0.0" };
59
- }
60
- }
61
- function pluginConfig(options) {
62
- return [
63
- craft(),
64
- laravel({
65
- input: options.laravel?.input || ["resources/js/app.ts"],
66
- refresh: options.laravel?.refresh || true
67
- }),
68
- tailwindcss(),
69
- i18n(),
70
- vue({
71
- template: {
72
- transformAssetUrls: {
73
- base: null,
74
- includeAbsolute: false
75
- },
76
- compilerOptions: {
77
- isCustomElement: (tag) => tag === "trix-editor"
78
- }
79
- }
80
- }),
81
- runConfiguration(),
82
- vueDevTools({
83
- appendTo: "virtual:craft",
84
- launchEditor: import.meta.env?.VITE_EDITOR || "cursor"
85
- })
86
- ].filter(Boolean);
87
- }
88
- function runConfiguration() {
89
- if (false) {
90
- return null;
91
- }
92
- return run([
93
- {
94
- name: "waymaker",
95
- run: ["php", "artisan", "waymaker:generate"],
96
- pattern: ["app/**/Http/**/*.php"]
97
- },
98
- {
99
- name: "wayfinder",
100
- run: ["php", "artisan", "wayfinder:generate"],
101
- pattern: ["routes/*.php", "app/**/Http/**/*.php"]
102
- },
103
- {
104
- name: "typescript",
105
- run: ["php", "artisan", "typescript:transform"],
106
- pattern: ["app/{Data,Enums}/**/*.php"]
107
- }
108
- ]);
109
- }
110
- function aliasConfig(aliases = [], ui = {}) {
111
- if (!process.argv.includes("build") && ui.localPath) {
112
- const absoluteLibraryPath = path.resolve(process.cwd(), ui.localPath);
113
- const localAliases = [
114
- // The following alias will make the @ alias work correctly depending on the importer path.
115
- {
116
- regex: /^@\//,
117
- replacement: "@/",
118
- libraryPath: absoluteLibraryPath,
119
- aliasLocalBasePath: "./resources/js",
120
- aliasExternalBasePath: path.join(ui.localPath, "src")
121
- },
122
- // The following alias config will change all package references like import { Dialog } from '@hardimpactdev/craft-ui';
123
- // to the library's index.ts file instead.
124
- {
125
- regex: /^@hardimpactdev\/craft-ui/,
126
- replacement: path.join(ui.localPath, "index.ts")
127
- }
128
- ];
129
- aliases.push(...aliasLocalPackage(localAliases));
130
- }
131
- return {
132
- dedupe: ["@inertiajs/vue3", "@tailwindcss/vite"],
133
- alias: aliases
134
- };
135
- }
136
- function aliasLocalPackage(aliases) {
137
- return aliases.map((alias) => {
138
- if (!alias.libraryPath && !alias.aliasLocalBasePath && !alias.aliasExternalBasePath) {
139
- return {
140
- find: alias.regex,
141
- replacement: path.resolve(process.cwd(), alias.replacement)
142
- };
143
- }
144
- return {
145
- find: alias.regex,
146
- replacement: alias.replacement,
147
- async customResolver(source, importer) {
148
- let resolvedPath = "";
149
- resolvedPath = path.resolve(
150
- // get the directory name of the importer
151
- process.cwd(),
152
- // if the importer string includes the folder name, use the external path, otherwise use the local path
153
- importer?.includes(alias.libraryPath) ? alias.aliasExternalBasePath : alias.aliasLocalBasePath,
154
- // remove the alias replacement from the source path
155
- source.replace(alias.replacement, "")
156
- );
157
- return (await this.resolve(resolvedPath))?.id;
158
- }
159
- };
160
- });
9
+ return defineConfig(({ mode }) => ({
10
+ plugins: [
11
+ ...getPlugins(options),
12
+ ...options.plugins || []
13
+ ],
14
+ resolve: getResolveConfig(options),
15
+ server: getServerConfig(mode)
16
+ }));
161
17
  }
162
18
  export {
163
- aliasConfig,
164
19
  defineCraftConfig,
165
- pluginConfig
20
+ getPlugins2 as getPlugins,
21
+ getResolveConfig2 as getResolveConfig,
22
+ getServerConfig2 as getServerConfig
166
23
  };
@@ -0,0 +1,59 @@
1
+ import vue from "@vitejs/plugin-vue";
2
+ import laravel from "laravel-vite-plugin";
3
+ import { run } from "vite-plugin-run";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+ import i18n from "laravel-vue-i18n/vite";
6
+ import vueDevTools from "vite-plugin-vue-devtools";
7
+ import { craft } from "./craftPlugin.js";
8
+ function getPlugins(options) {
9
+ return [
10
+ craft(),
11
+ laravel({
12
+ input: options.laravel?.input || ["resources/js/app.ts"],
13
+ refresh: options.laravel?.refresh ?? true
14
+ }),
15
+ tailwindcss(),
16
+ i18n(),
17
+ vue({
18
+ template: {
19
+ transformAssetUrls: {
20
+ base: null,
21
+ includeAbsolute: false
22
+ },
23
+ compilerOptions: {
24
+ isCustomElement: (tag) => tag === "trix-editor"
25
+ }
26
+ }
27
+ }),
28
+ getArtisanRunners(),
29
+ vueDevTools({
30
+ appendTo: "virtual:craft",
31
+ launchEditor: import.meta.env?.VITE_EDITOR || "cursor"
32
+ })
33
+ ].filter(Boolean);
34
+ }
35
+ function getArtisanRunners() {
36
+ if (false) {
37
+ return null;
38
+ }
39
+ return run([
40
+ {
41
+ name: "waymaker",
42
+ run: ["php", "artisan", "waymaker:generate"],
43
+ pattern: ["app/**/Http/**/*.php"]
44
+ },
45
+ {
46
+ name: "wayfinder",
47
+ run: ["php", "artisan", "wayfinder:generate"],
48
+ pattern: ["routes/*.php", "app/**/Http/**/*.php"]
49
+ },
50
+ {
51
+ name: "typescript",
52
+ run: ["php", "artisan", "typescript:transform"],
53
+ pattern: ["app/{Data,Enums}/**/*.php"]
54
+ }
55
+ ]);
56
+ }
57
+ export {
58
+ getPlugins
59
+ };
@@ -0,0 +1,30 @@
1
+ import { loadEnv } from "vite";
2
+ function getServerConfig(mode) {
3
+ const env = loadEnv(mode, process.cwd());
4
+ const appUrl = env.VITE_APP_URL;
5
+ if (!appUrl) {
6
+ return { host: "0.0.0.0" };
7
+ }
8
+ try {
9
+ const url = new URL(appUrl);
10
+ const isHttps = url.protocol === "https:";
11
+ return {
12
+ // Accept connections from reverse proxy
13
+ host: "0.0.0.0",
14
+ // Tell Vite the public origin for asset URLs
15
+ origin: appUrl,
16
+ // Configure HMR to connect through the reverse proxy
17
+ // Without this, browser would try localhost:5173 directly
18
+ hmr: {
19
+ host: url.hostname,
20
+ protocol: isHttps ? "wss" : "ws",
21
+ clientPort: isHttps ? 443 : parseInt(url.port) || 80
22
+ }
23
+ };
24
+ } catch {
25
+ return { host: "0.0.0.0" };
26
+ }
27
+ }
28
+ export {
29
+ getServerConfig
30
+ };
@@ -0,0 +1,132 @@
1
+ import path from 'path';
2
+ import type { Alias } from 'vite';
3
+ import type { CraftConfigOptions, LocalAliasConfig } from './types.js';
4
+
5
+ /**
6
+ * Configure Vite resolve options for the Craft stack
7
+ *
8
+ * Handles two scenarios:
9
+ * 1. Normal: Just returns dedupe config and any custom aliases
10
+ * 2. Local development: Adds aliases for symlinked craft-ui to enable HMR
11
+ */
12
+ export function getResolveConfig(options: CraftConfigOptions) {
13
+ const aliases: Alias[] = [...(options.aliases || [])];
14
+
15
+ // Add local development aliases when craft-ui is symlinked
16
+ if (isDevMode() && options.ui?.localPath) {
17
+ aliases.push(...createLocalDevAliases(options.ui.localPath));
18
+ }
19
+
20
+ return {
21
+ // Prevent duplicate instances of packages that must be singletons
22
+ dedupe: ['@inertiajs/vue3', '@tailwindcss/vite'],
23
+ alias: aliases,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Create aliases for local craft-ui development
29
+ *
30
+ * When craft-ui is symlinked (e.g., for development), we need to:
31
+ *
32
+ * 1. Redirect package imports to source files
33
+ * `import { Button } from '@hardimpactdev/craft-ui'`
34
+ * → resolves to `../craft-ui/index.ts` instead of `dist/`
35
+ * This enables HMR (dist files can't hot-reload)
36
+ *
37
+ * 2. Handle the @/ alias conflict
38
+ * Both the app and craft-ui use @/ as an alias:
39
+ * - App: @/ → ./resources/js
40
+ * - craft-ui: @/ → ./src
41
+ *
42
+ * When a file in craft-ui imports `@/lib/utils`, we need to resolve
43
+ * it to craft-ui/src/lib/utils, not app/resources/js/lib/utils.
44
+ * The resolver checks WHERE the import comes from to decide.
45
+ */
46
+ function createLocalDevAliases(localPath: string): Alias[] {
47
+ const absoluteLibraryPath = path.resolve(process.cwd(), localPath);
48
+
49
+ const configs: LocalAliasConfig[] = [
50
+ // Handle @/ alias - resolve based on importer location
51
+ {
52
+ pattern: /^@\//,
53
+ replacement: '@/',
54
+ libraryPath: absoluteLibraryPath,
55
+ basePaths: {
56
+ app: './resources/js',
57
+ library: path.join(localPath, 'src'),
58
+ },
59
+ },
60
+
61
+ // Redirect package imports to source
62
+ {
63
+ pattern: /^@hardimpactdev\/craft-ui/,
64
+ replacement: path.join(localPath, 'index.ts'),
65
+ },
66
+ ];
67
+
68
+ return configs.map(createAliasFromConfig);
69
+ }
70
+
71
+ /**
72
+ * Create a Vite alias from our config format
73
+ */
74
+ function createAliasFromConfig(config: LocalAliasConfig): Alias {
75
+ // Simple redirect (no context-aware resolution needed)
76
+ if (!config.libraryPath || !config.basePaths) {
77
+ return {
78
+ find: config.pattern,
79
+ replacement: path.resolve(process.cwd(), config.replacement),
80
+ };
81
+ }
82
+
83
+ // Context-aware resolution: choose base path based on importer location
84
+ return {
85
+ find: config.pattern,
86
+ replacement: config.replacement,
87
+ customResolver: createContextAwareResolver(config),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Create a resolver that picks the right base path based on where the import comes from
93
+ *
94
+ * @example
95
+ * // Import from app file → use app base path
96
+ * // resources/js/pages/Home.vue imports @/lib/utils
97
+ * // → resolves to resources/js/lib/utils
98
+ *
99
+ * // Import from library file → use library base path
100
+ * // ../craft-ui/src/components/Button.vue imports @/lib/utils
101
+ * // → resolves to ../craft-ui/src/lib/utils
102
+ */
103
+ function createContextAwareResolver(config: LocalAliasConfig) {
104
+ return async function customResolver(
105
+ this: { resolve: (id: string, importer?: string) => Promise<{ id: string } | null> },
106
+ source: string,
107
+ importer: string | undefined
108
+ ): Promise<string | undefined> {
109
+ // Determine if the import is coming from the library or the app
110
+ const isFromLibrary = importer?.includes(config.libraryPath!);
111
+ const basePath = isFromLibrary ? config.basePaths!.library : config.basePaths!.app;
112
+
113
+ // Build the full path by combining:
114
+ // 1. Current working directory
115
+ // 2. The appropriate base path (app or library)
116
+ // 3. The import path (minus the alias prefix)
117
+ const importPath = source.replace(config.replacement, '');
118
+ const resolvedPath = path.resolve(process.cwd(), basePath, importPath);
119
+
120
+ // Use Vite's resolver to get the final path (handles extensions, index files, etc.)
121
+ const result = await this.resolve(resolvedPath);
122
+ return result?.id;
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Check if we're in development mode
128
+ * Used to skip local dev aliases during production builds
129
+ */
130
+ function isDevMode(): boolean {
131
+ return !process.argv.includes('build');
132
+ }
@@ -1,213 +1,24 @@
1
- import { defineConfig, loadEnv } from 'vite';
2
- import vue from '@vitejs/plugin-vue';
3
- import path from 'path';
4
- import laravel from 'laravel-vite-plugin';
5
- import { run } from 'vite-plugin-run';
6
- import { craft } from './craftPlugin.js';
7
- import tailwindcss from '@tailwindcss/vite';
8
- import i18n from 'laravel-vue-i18n/vite';
9
- import vueDevTools from 'vite-plugin-vue-devtools';
10
-
11
- interface viteConfigOptions {
12
- laravel?: {
13
- input?: string[];
14
- refresh?: boolean;
15
- };
16
- plugins?: any[];
17
- aliases?: any[];
18
- ui?: {
19
- localPath?: string;
20
- };
21
- }
22
-
23
- export function defineCraftConfig(options: viteConfigOptions = {}) {
24
- return defineConfig(({ mode }) => {
25
- const serverConfig = getServerConfig(mode);
26
- return {
27
- plugins: [
28
- ...pluginConfig(options),
29
- ...(options.plugins || []),
30
- // Plugin to enforce allowedHosts after laravel-vite-plugin
31
- // This bypasses Vite's DNS rebinding protection which conflicts with reverse proxy setups
32
- {
33
- name: 'craft-allowed-hosts',
34
- enforce: 'post' as const,
35
- config() {
36
- return {
37
- server: {
38
- allowedHosts: true as const,
39
- },
40
- };
41
- },
42
- },
43
- ],
44
- resolve: {
45
- ...aliasConfig(options.aliases, options.ui),
46
- },
47
- server: serverConfig,
48
- };
49
- });
50
- }
51
-
52
- function getServerConfig(mode: string) {
53
- // Only load VITE_* prefixed env vars (Vite's default secure behavior)
54
- const env = loadEnv(mode, process.cwd());
55
- const appUrl = env.VITE_APP_URL;
56
-
57
- if (!appUrl) {
58
- return { host: '0.0.0.0' };
59
- }
60
-
61
- try {
62
- const url = new URL(appUrl);
63
- const isHttps = url.protocol === 'https:';
64
-
65
- return {
66
- host: '0.0.0.0',
67
- origin: appUrl,
68
- allowedHosts: true as const,
69
- hmr: {
70
- host: url.hostname,
71
- protocol: isHttps ? 'wss' : 'ws',
72
- clientPort: isHttps ? 443 : (parseInt(url.port) || 80),
73
- },
74
- };
75
- } catch {
76
- return { host: '0.0.0.0' };
77
- }
78
- }
79
-
80
- export function pluginConfig(options: viteConfigOptions) {
81
- return [
82
- craft(),
83
- laravel({
84
- input: options.laravel?.input || ['resources/js/app.ts'],
85
- refresh: options.laravel?.refresh || true,
86
- }),
87
- tailwindcss(),
88
- i18n(),
89
- vue({
90
- template: {
91
- transformAssetUrls: {
92
- base: null,
93
- includeAbsolute: false,
94
- },
95
- compilerOptions: {
96
- isCustomElement: (tag) => tag === 'trix-editor',
97
- },
98
- },
99
- }),
100
- runConfiguration(),
101
- vueDevTools({
102
- appendTo: 'virtual:craft',
103
- launchEditor: import.meta.env?.VITE_EDITOR || 'cursor',
104
- }),
105
- ].filter(Boolean);
106
- }
107
-
108
- function runConfiguration() {
109
- if (process.env.NODE_ENV !== 'development') {
110
- return null;
111
- }
112
-
113
- return run([
114
- {
115
- name: 'waymaker',
116
- run: ['php', 'artisan', 'waymaker:generate'],
117
- pattern: ['app/**/Http/**/*.php'],
118
- },
119
- {
120
- name: 'wayfinder',
121
- run: ['php', 'artisan', 'wayfinder:generate'],
122
- pattern: ['routes/*.php', 'app/**/Http/**/*.php'],
123
- },
124
- {
125
- name: 'typescript',
126
- run: ['php', 'artisan', 'typescript:transform'],
127
- pattern: ['app/{Data,Enums}/**/*.php'],
128
- },
129
- ]);
130
- }
131
-
132
- interface LocalAlias {
133
- regex: RegExp;
134
- replacement: string;
135
- libraryPath?: string;
136
- aliasLocalBasePath?: string;
137
- aliasExternalBasePath?: string;
138
- }
139
-
140
- export function aliasConfig(aliases: any[] = [], ui: any = {}) {
141
- if (!process.argv.includes('build') && ui.localPath) {
142
-
143
- const absoluteLibraryPath = path.resolve(process.cwd(), ui.localPath);
144
-
145
- const localAliases = [
146
- // The following alias will make the @ alias work correctly depending on the importer path.
147
- {
148
- regex: /^@\//,
149
- replacement: "@/",
150
- libraryPath: absoluteLibraryPath,
151
- aliasLocalBasePath: "./resources/js",
152
- aliasExternalBasePath: path.join(ui.localPath, "src"),
153
- },
154
- // The following alias config will change all package references like import { Dialog } from '@hardimpactdev/craft-ui';
155
- // to the library's index.ts file instead.
156
- {
157
- regex: /^@hardimpactdev\/craft-ui/,
158
- replacement: path.join(ui.localPath, "index.ts"),
159
- },
160
- ];
161
-
162
- aliases.push(...aliasLocalPackage(localAliases as LocalAlias[]));
163
- }
164
-
165
- return {
166
- dedupe: ['@inertiajs/vue3', '@tailwindcss/vite'],
167
- alias: aliases,
168
- };
169
- }
170
-
171
- interface CustomResolverContext {
172
- resolve: (id: string, importer?: string) => Promise<{ id: string } | null>;
173
- }
174
-
175
- function aliasLocalPackage(aliases: Array<LocalAlias>) {
176
-
177
- return aliases.map((alias) => {
178
-
179
- // if the alias has no external path, folder name, or local path, use the replacement as the path
180
- // For example library references like import { Dialog } from '@hardimpactdev/craft-ui'; will be resolved to the library's index.ts file instead.
181
- // This will make HMR work correctly. Allowing to update the library and see the changes in the target project without hard page reloads.
182
- if (!alias.libraryPath && !alias.aliasLocalBasePath && !alias.aliasExternalBasePath) {
183
- return {
184
- find: alias.regex,
185
- replacement: path.resolve(process.cwd(), alias.replacement),
186
- };
187
- }
188
-
189
- // When using an alias both in a library and the target project, we need to resolve the alias correctly based on the importer path.
190
- // This allows for example to use the @ alias both in the library and the target project.
191
- return {
192
- find: alias.regex,
193
- replacement: alias.replacement,
194
- async customResolver(this: CustomResolverContext, source: any, importer: any) {
195
- let resolvedPath = '';
196
-
197
- resolvedPath = path.resolve(
198
- // get the directory name of the importer
199
- process.cwd(),
200
-
201
- // if the importer string includes the folder name, use the external path, otherwise use the local path
202
- importer?.includes(alias.libraryPath) ? alias.aliasExternalBasePath! : alias.aliasLocalBasePath!,
203
-
204
- // remove the alias replacement from the source path
205
- source.replace(alias.replacement, ''),
206
- );
207
-
208
- // use Vite's (in fact, rollup's) resolution function
209
- return (await this.resolve(resolvedPath))?.id;
210
- },
211
- };
212
- });
1
+ import { defineConfig } from 'vite';
2
+ import { getPlugins } from './plugins.js';
3
+ import { getServerConfig } from './server.js';
4
+ import { getResolveConfig } from './aliases.js';
5
+ import type { CraftConfigOptions } from './types.js';
6
+
7
+ // Re-export types for consumers
8
+ export type { CraftConfigOptions } from './types.js';
9
+
10
+ // Re-export individual modules for advanced customization
11
+ export { getPlugins } from './plugins.js';
12
+ export { getServerConfig } from './server.js';
13
+ export { getResolveConfig } from './aliases.js';
14
+
15
+ export function defineCraftConfig(options: CraftConfigOptions = {}) {
16
+ return defineConfig(({ mode }) => ({
17
+ plugins: [
18
+ ...getPlugins(options),
19
+ ...(options.plugins || []),
20
+ ],
21
+ resolve: getResolveConfig(options),
22
+ server: getServerConfig(mode),
23
+ }));
213
24
  }
@@ -0,0 +1,86 @@
1
+ import vue from '@vitejs/plugin-vue';
2
+ import laravel from 'laravel-vite-plugin';
3
+ import { run } from 'vite-plugin-run';
4
+ import tailwindcss from '@tailwindcss/vite';
5
+ import i18n from 'laravel-vue-i18n/vite';
6
+ import vueDevTools from 'vite-plugin-vue-devtools';
7
+ import { craft } from './craftPlugin.js';
8
+ import type { CraftConfigOptions } from './types.js';
9
+
10
+ /**
11
+ * Configure all Vite plugins for the Craft stack
12
+ *
13
+ * Includes:
14
+ * - craft: Virtual module for Inertia app initialization
15
+ * - laravel: Laravel Vite integration
16
+ * - tailwindcss: Tailwind CSS v4
17
+ * - i18n: Laravel Vue i18n
18
+ * - vue: Vue 3 SFC support
19
+ * - vueDevTools: Vue DevTools integration
20
+ * - run: Auto-run artisan commands on file changes (dev only)
21
+ */
22
+ export function getPlugins(options: CraftConfigOptions) {
23
+ return [
24
+ craft(),
25
+
26
+ laravel({
27
+ input: options.laravel?.input || ['resources/js/app.ts'],
28
+ refresh: options.laravel?.refresh ?? true,
29
+ }),
30
+
31
+ tailwindcss(),
32
+
33
+ i18n(),
34
+
35
+ vue({
36
+ template: {
37
+ transformAssetUrls: {
38
+ base: null,
39
+ includeAbsolute: false,
40
+ },
41
+ compilerOptions: {
42
+ isCustomElement: (tag) => tag === 'trix-editor',
43
+ },
44
+ },
45
+ }),
46
+
47
+ getArtisanRunners(),
48
+
49
+ vueDevTools({
50
+ appendTo: 'virtual:craft',
51
+ launchEditor: import.meta.env?.VITE_EDITOR || 'cursor',
52
+ }),
53
+ ].filter(Boolean);
54
+ }
55
+
56
+ /**
57
+ * Configure artisan command runners for development
58
+ *
59
+ * Auto-runs:
60
+ * - waymaker:generate on controller changes
61
+ * - wayfinder:generate on route/controller changes
62
+ * - typescript:transform on DTO/Enum changes
63
+ */
64
+ function getArtisanRunners() {
65
+ if (process.env.NODE_ENV !== 'development') {
66
+ return null;
67
+ }
68
+
69
+ return run([
70
+ {
71
+ name: 'waymaker',
72
+ run: ['php', 'artisan', 'waymaker:generate'],
73
+ pattern: ['app/**/Http/**/*.php'],
74
+ },
75
+ {
76
+ name: 'wayfinder',
77
+ run: ['php', 'artisan', 'wayfinder:generate'],
78
+ pattern: ['routes/*.php', 'app/**/Http/**/*.php'],
79
+ },
80
+ {
81
+ name: 'typescript',
82
+ run: ['php', 'artisan', 'typescript:transform'],
83
+ pattern: ['app/{Data,Enums}/**/*.php'],
84
+ },
85
+ ]);
86
+ }
@@ -0,0 +1,42 @@
1
+ import { loadEnv } from 'vite';
2
+ import type { ServerOptions } from 'vite';
3
+
4
+ /**
5
+ * Configure Vite dev server for Laravel apps behind a reverse proxy (Caddy/nginx)
6
+ *
7
+ * Handles:
8
+ * - HMR WebSocket connection through the proxy (wss://app.test:443)
9
+ * - Correct origin for CORS and asset URLs
10
+ * - Binding to all interfaces for proxy access
11
+ */
12
+ export function getServerConfig(mode: string): ServerOptions {
13
+ const env = loadEnv(mode, process.cwd());
14
+ const appUrl = env.VITE_APP_URL;
15
+
16
+ if (!appUrl) {
17
+ return { host: '0.0.0.0' };
18
+ }
19
+
20
+ try {
21
+ const url = new URL(appUrl);
22
+ const isHttps = url.protocol === 'https:';
23
+
24
+ return {
25
+ // Accept connections from reverse proxy
26
+ host: '0.0.0.0',
27
+
28
+ // Tell Vite the public origin for asset URLs
29
+ origin: appUrl,
30
+
31
+ // Configure HMR to connect through the reverse proxy
32
+ // Without this, browser would try localhost:5173 directly
33
+ hmr: {
34
+ host: url.hostname,
35
+ protocol: isHttps ? 'wss' : 'ws',
36
+ clientPort: isHttps ? 443 : (parseInt(url.port) || 80),
37
+ },
38
+ };
39
+ } catch {
40
+ return { host: '0.0.0.0' };
41
+ }
42
+ }
@@ -0,0 +1,73 @@
1
+ import type { Alias } from 'vite';
2
+
3
+ /**
4
+ * Configuration options for defineCraftConfig
5
+ */
6
+ export interface CraftConfigOptions {
7
+ /**
8
+ * Laravel Vite plugin options
9
+ */
10
+ laravel?: {
11
+ /** Entry points for the application */
12
+ input?: string[];
13
+ /** Enable file refresh on changes */
14
+ refresh?: boolean;
15
+ };
16
+
17
+ /**
18
+ * Additional Vite plugins to include
19
+ */
20
+ plugins?: any[];
21
+
22
+ /**
23
+ * Additional Vite aliases
24
+ */
25
+ aliases?: Alias[];
26
+
27
+ /**
28
+ * Local UI library configuration for development with symlinked packages
29
+ */
30
+ ui?: LocalUiOptions;
31
+ }
32
+
33
+ /**
34
+ * Configuration for local UI library development
35
+ *
36
+ * When developing craft-ui alongside an app, this enables HMR by:
37
+ * 1. Redirecting package imports to source files instead of dist/
38
+ * 2. Resolving conflicting aliases (like @/) based on importer location
39
+ */
40
+ export interface LocalUiOptions {
41
+ /**
42
+ * Relative path from app root to the symlinked craft-ui directory
43
+ * @example '../craft-ui'
44
+ */
45
+ localPath?: string;
46
+ }
47
+
48
+ /**
49
+ * Internal alias configuration for local package resolution
50
+ */
51
+ export interface LocalAliasConfig {
52
+ /** Regex pattern to match imports */
53
+ pattern: RegExp;
54
+
55
+ /** The replacement path or alias */
56
+ replacement: string;
57
+
58
+ /**
59
+ * Absolute path to the library (for importer detection)
60
+ * If set along with basePaths, enables context-aware resolution
61
+ */
62
+ libraryPath?: string;
63
+
64
+ /**
65
+ * Base paths for context-aware resolution
66
+ */
67
+ basePaths?: {
68
+ /** Path to use when import is from the app */
69
+ app: string;
70
+ /** Path to use when import is from the library */
71
+ library: string;
72
+ };
73
+ }
File without changes
package/package.json CHANGED
@@ -56,7 +56,7 @@
56
56
  "types": "./dist/src/vite/defineCraftConfig.d.ts"
57
57
  }
58
58
  },
59
- "version": "0.0.6",
59
+ "version": "0.0.7",
60
60
  "type": "module",
61
61
  "scripts": {
62
62
  "dev": "vite",