@rpgjs/vite 5.0.0-alpha.0
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 +66 -0
- package/dist/directive-plugin.d.ts +5 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8501 -0
- package/dist/index.js.map +1 -0
- package/dist/module-config.d.ts +1 -0
- package/dist/remove-imports-plugin.d.ts +36 -0
- package/dist/tiled-map-folder-plugin.d.ts +48 -0
- package/package.json +32 -0
- package/src/directive-plugin.ts +80 -0
- package/src/index.ts +4 -0
- package/src/module-config.ts +122 -0
- package/src/remove-imports-plugin.ts +164 -0
- package/src/tiled-map-folder-plugin.ts +224 -0
- package/tests/directive-plugin.spec.ts +34 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { readFileSync, existsSync, statSync, readdirSync, copyFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join, extname, relative, dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
export interface DataFolderPluginOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Source folder containing the data files (TMX, TSX, images)
|
|
8
|
+
*/
|
|
9
|
+
sourceFolder: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Public path prefix for accessing the data files
|
|
13
|
+
* @default '/data'
|
|
14
|
+
*/
|
|
15
|
+
publicPath?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Target folder in build output for the data files
|
|
19
|
+
* @default 'assets/data'
|
|
20
|
+
*/
|
|
21
|
+
buildOutputPath?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* File extensions to include
|
|
25
|
+
* @default ['.tmx', '.tsx', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']
|
|
26
|
+
*/
|
|
27
|
+
allowedExtensions?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Vite plugin that serves a data folder in development mode and copies it to assets during build
|
|
32
|
+
*
|
|
33
|
+
* This plugin allows serving game data files (TMX maps, TSX tilesets, images) during development
|
|
34
|
+
* and automatically includes them in the build output for production deployment.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Configuration options for the plugin
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```js
|
|
40
|
+
* // In vite.config.ts
|
|
41
|
+
* import { defineConfig } from 'vite';
|
|
42
|
+
* import { dataFolderPlugin } from '@rpgjs/vite';
|
|
43
|
+
*
|
|
44
|
+
* export default defineConfig({
|
|
45
|
+
* plugins: [
|
|
46
|
+
* dataFolderPlugin({
|
|
47
|
+
* sourceFolder: './game-data',
|
|
48
|
+
* publicPath: '/data',
|
|
49
|
+
* buildOutputPath: 'assets/data'
|
|
50
|
+
* })
|
|
51
|
+
* ]
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function tiledMapFolderPlugin(options: DataFolderPluginOptions): Plugin {
|
|
56
|
+
const {
|
|
57
|
+
sourceFolder,
|
|
58
|
+
publicPath = '/data',
|
|
59
|
+
buildOutputPath = 'assets/data',
|
|
60
|
+
allowedExtensions = ['.tmx', '.tsx', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
63
|
+
let isBuild = false;
|
|
64
|
+
let outputDir = 'dist';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get MIME type based on file extension
|
|
68
|
+
*/
|
|
69
|
+
const getMimeType = (filePath: string): string => {
|
|
70
|
+
const ext = extname(filePath).toLowerCase();
|
|
71
|
+
const mimeTypes: Record<string, string> = {
|
|
72
|
+
'.tmx': 'application/xml',
|
|
73
|
+
'.tsx': 'application/xml',
|
|
74
|
+
'.png': 'image/png',
|
|
75
|
+
'.jpg': 'image/jpeg',
|
|
76
|
+
'.jpeg': 'image/jpeg',
|
|
77
|
+
'.gif': 'image/gif',
|
|
78
|
+
'.webp': 'image/webp',
|
|
79
|
+
'.svg': 'image/svg+xml'
|
|
80
|
+
};
|
|
81
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if file extension is allowed
|
|
86
|
+
*/
|
|
87
|
+
const isAllowedFile = (filePath: string): boolean => {
|
|
88
|
+
const ext = extname(filePath).toLowerCase();
|
|
89
|
+
return allowedExtensions.includes(ext);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Recursively get all files from a directory
|
|
94
|
+
*/
|
|
95
|
+
const getAllFiles = (dirPath: string, basePath: string = dirPath): string[] => {
|
|
96
|
+
const files: string[] = [];
|
|
97
|
+
|
|
98
|
+
if (!existsSync(dirPath)) {
|
|
99
|
+
return files;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const items = readdirSync(dirPath);
|
|
103
|
+
|
|
104
|
+
for (const item of items) {
|
|
105
|
+
const fullPath = join(dirPath, item);
|
|
106
|
+
const stat = statSync(fullPath);
|
|
107
|
+
|
|
108
|
+
if (stat.isDirectory()) {
|
|
109
|
+
files.push(...getAllFiles(fullPath, basePath));
|
|
110
|
+
} else if (isAllowedFile(fullPath)) {
|
|
111
|
+
files.push(fullPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return files;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Copy files to build output directory
|
|
120
|
+
*/
|
|
121
|
+
const copyFilesToBuild = (outputPath: string) => {
|
|
122
|
+
const files = getAllFiles(sourceFolder);
|
|
123
|
+
|
|
124
|
+
for (const filePath of files) {
|
|
125
|
+
const relativePath = relative(sourceFolder, filePath);
|
|
126
|
+
const targetPath = join(outputPath, buildOutputPath, relativePath);
|
|
127
|
+
const targetDir = dirname(targetPath);
|
|
128
|
+
|
|
129
|
+
// Create target directory if it doesn't exist
|
|
130
|
+
mkdirSync(targetDir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
// Copy file
|
|
133
|
+
copyFileSync(filePath, targetPath);
|
|
134
|
+
console.log(`📁 Copied data file: ${relativePath}`);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
name: 'data-folder',
|
|
140
|
+
enforce: 'pre',
|
|
141
|
+
|
|
142
|
+
configResolved(config) {
|
|
143
|
+
isBuild = config.command === 'build';
|
|
144
|
+
outputDir = config.build.outDir || 'dist';
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Handle build mode - copy files to output directory
|
|
148
|
+
generateBundle() {
|
|
149
|
+
if (isBuild) {
|
|
150
|
+
console.log(`📦 Copying data files from ${sourceFolder} to ${buildOutputPath}...`);
|
|
151
|
+
copyFilesToBuild(outputDir);
|
|
152
|
+
console.log('✅ Data files copied successfully');
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Handle development mode - serve files via middleware
|
|
157
|
+
configureServer(server) {
|
|
158
|
+
if (!existsSync(sourceFolder)) {
|
|
159
|
+
console.warn(`⚠️ Data folder not found: ${sourceFolder}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(`📁 Serving data folder: ${sourceFolder} at ${publicPath}`);
|
|
164
|
+
|
|
165
|
+
server.middlewares.use((req: any, res: any, next: any) => {
|
|
166
|
+
if (!req.url?.startsWith(publicPath)) {
|
|
167
|
+
return next();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Remove public path prefix to get relative file path
|
|
171
|
+
const relativePath = req.url.slice(publicPath.length);
|
|
172
|
+
|
|
173
|
+
// Remove leading slash if present
|
|
174
|
+
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
175
|
+
|
|
176
|
+
// Construct full file path
|
|
177
|
+
const filePath = join(sourceFolder, cleanPath);
|
|
178
|
+
|
|
179
|
+
// Security check - ensure file is within source folder
|
|
180
|
+
const cwd = typeof process !== 'undefined' ? process.cwd() : '';
|
|
181
|
+
const resolvedFilePath = join(cwd, filePath);
|
|
182
|
+
const resolvedSourceFolder = join(cwd, sourceFolder);
|
|
183
|
+
|
|
184
|
+
if (!resolvedFilePath.startsWith(resolvedSourceFolder)) {
|
|
185
|
+
res.statusCode = 403;
|
|
186
|
+
res.end('Forbidden');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if file exists and is allowed
|
|
191
|
+
if (!existsSync(filePath) || !isAllowedFile(filePath)) {
|
|
192
|
+
res.statusCode = 404;
|
|
193
|
+
res.end('Not Found');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check if it's a file (not directory)
|
|
198
|
+
const stat = statSync(filePath);
|
|
199
|
+
if (!stat.isFile()) {
|
|
200
|
+
res.statusCode = 404;
|
|
201
|
+
res.end('Not Found');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
// Read and serve the file
|
|
207
|
+
const fileContent = readFileSync(filePath);
|
|
208
|
+
const mimeType = getMimeType(filePath);
|
|
209
|
+
|
|
210
|
+
res.setHeader('Content-Type', mimeType);
|
|
211
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
212
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
213
|
+
res.end(fileContent);
|
|
214
|
+
|
|
215
|
+
console.log(`📄 Served data file: ${cleanPath}`);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`❌ Error serving file ${filePath}:`, error);
|
|
218
|
+
res.statusCode = 500;
|
|
219
|
+
res.end('Internal Server Error');
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { directivePlugin } from '../src/directive-plugin'
|
|
3
|
+
|
|
4
|
+
const pluginClient = directivePlugin({ side: 'client' })
|
|
5
|
+
const pluginServer = directivePlugin({ side: 'server' })
|
|
6
|
+
|
|
7
|
+
const fileSource = `'use client';\nexport function hello(){ return 'hi' }`
|
|
8
|
+
const funcSource = `export function foo(){\n 'use server';\n return 1;\n}\nexport const bar = () => {\n 'use client';\n return 2;\n}`
|
|
9
|
+
|
|
10
|
+
describe('directivePlugin', () => {
|
|
11
|
+
it('keeps file for matching side', async () => {
|
|
12
|
+
const res = await pluginClient.transform(fileSource, 'file.ts') as any
|
|
13
|
+
expect(res.code).toContain("export function hello")
|
|
14
|
+
expect(res.code).not.toContain('use client')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('removes file for opposite side', async () => {
|
|
18
|
+
const res = await pluginServer.transform(fileSource, 'file.ts') as any
|
|
19
|
+
expect(res.code.trim()).toBe('export default null;')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('keeps function for matching side', async () => {
|
|
23
|
+
const res = await pluginServer.transform(funcSource, 'func.ts') as any
|
|
24
|
+
expect(res.code).toContain('function foo()')
|
|
25
|
+
expect(res.code).not.toContain('use server')
|
|
26
|
+
expect(res.code).not.toContain('bar') // bar should be removed on server
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('removes function for opposite side', async () => {
|
|
30
|
+
const res = await pluginClient.transform(funcSource, 'func.ts') as any
|
|
31
|
+
expect(res.code).toContain('bar')
|
|
32
|
+
expect(res.code).not.toContain('foo')
|
|
33
|
+
})
|
|
34
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import dts from 'vite-plugin-dts'
|
|
3
|
+
|
|
4
|
+
// List of Node.js built-in modules to mark as external
|
|
5
|
+
const nodeBuiltins = [
|
|
6
|
+
'fs', 'path', 'os', 'crypto', 'util', 'events', 'stream', 'buffer',
|
|
7
|
+
'url', 'querystring', 'http', 'https', 'net', 'tls', 'child_process',
|
|
8
|
+
'cluster', 'dgram', 'dns', 'domain', 'readline', 'repl', 'tty', 'vm',
|
|
9
|
+
'zlib', 'assert', 'constants', 'module', 'perf_hooks', 'process',
|
|
10
|
+
'punycode', 'string_decoder', 'timers', 'trace_events', 'v8', 'worker_threads'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
plugins: [
|
|
15
|
+
dts({
|
|
16
|
+
include: ['src/**/*.ts'],
|
|
17
|
+
outDir: 'dist'
|
|
18
|
+
})
|
|
19
|
+
],
|
|
20
|
+
build: {
|
|
21
|
+
target: 'esnext',
|
|
22
|
+
sourcemap: true,
|
|
23
|
+
minify: false,
|
|
24
|
+
lib: {
|
|
25
|
+
entry: 'src/index.ts',
|
|
26
|
+
formats: ['es'],
|
|
27
|
+
fileName: 'index'
|
|
28
|
+
},
|
|
29
|
+
rollupOptions: {
|
|
30
|
+
external: [
|
|
31
|
+
// Mark all Node.js built-in modules as external
|
|
32
|
+
...nodeBuiltins,
|
|
33
|
+
// Also mark any module that starts with 'node:' as external
|
|
34
|
+
/^node:/,
|
|
35
|
+
// Mark vite as external since it's a peer dependency
|
|
36
|
+
'vite',
|
|
37
|
+
'vite-plugin-dts',
|
|
38
|
+
'@canvasengine/compiler',
|
|
39
|
+
'chokidar'
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|