@unsetsoft/ryunix-presets 1.0.23-canary.9 → 1.0.25

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunix-presets",
3
3
  "description": "Package with presets for different development environments.",
4
- "version": "1.0.23-canary.9",
4
+ "version": "1.0.25",
5
5
  "author": "Neyunse",
6
6
  "type": "module",
7
7
  "repository": "https://github.com/UnSetSoft/Ryunixjs",
@@ -35,42 +35,49 @@
35
35
  "dependencies": {
36
36
  "@babel/cli": "7.28.3",
37
37
  "@babel/core": "7.28.5",
38
- "@babel/plugin-proposal-class-properties": "7.18.6",
39
38
  "@babel/plugin-transform-react-jsx": "7.27.1",
40
39
  "@babel/preset-env": "7.28.5",
41
40
  "@babel/preset-react": "7.28.5",
42
- "@eslint/eslintrc": "3.3.1",
43
- "@eslint/js": "9.38.0",
41
+ "@eslint/eslintrc": "3.3.3",
42
+ "@eslint/js": "9.39.2",
44
43
  "@rollup/plugin-terser": "0.4.4",
45
- "@swc/core": "1.12.14",
44
+ "@swc/core": "1.15.5",
46
45
  "babel-loader": "10.0.0",
47
46
  "chalk": "5.6.2",
48
47
  "copy-webpack-plugin": "13.0.1",
49
48
  "css-loader": "7.1.2",
50
- "css-minimizer-webpack-plugin": "7.0.2",
51
- "sass": "1.93.2",
49
+ "css-minimizer-webpack-plugin": "7.0.4",
50
+ "sass": "1.97.0",
52
51
  "sass-loader": "16.0.6",
53
52
  "dotenv-webpack": "8.1.1",
54
- "eslint": "9.38.0",
53
+ "eslint": "9.39.2",
55
54
  "eslint-plugin-react": "^7.37.5",
56
55
  "eslint-webpack-plugin": "5.0.2",
57
56
  "file-loader": "6.2.0",
58
- "glob": "11.0.3",
59
- "globals": "16.4.0",
57
+ "glob": "13.0.0",
58
+ "globals": "16.5.0",
60
59
  "html-loader": "5.1.0",
61
- "html-webpack-plugin": "5.6.4",
62
- "image-webpack-loader": "^8.1.0",
60
+ "html-webpack-plugin": "5.6.5",
63
61
  "lodash": "4.17.21",
64
62
  "mini-css-extract-plugin": "2.9.4",
65
- "rollup": "4.52.5",
63
+ "rollup": "4.53.5",
66
64
  "style-loader": "4.0.0",
67
65
  "terminal-log": "1.0.1",
68
- "terser-webpack-plugin": "5.3.14",
69
- "thread-loader": "4.0.4",
66
+ "terser-webpack-plugin": "5.3.16",
70
67
  "url-loader": "4.1.1",
71
- "webpack": "5.102.1",
68
+ "webpack": "5.104.0",
72
69
  "webpack-cli": "6.0.1",
73
70
  "webpack-dev-server": "5.2.2",
74
- "yargs": "18.0.0"
71
+ "thread-loader": "4.0.4",
72
+ "yargs": "18.0.0",
73
+ "@mdx-js/loader": "3.1.1",
74
+ "@mdx-js/rollup": "3.1.1",
75
+ "eslint-plugin-mdx": "3.6.2",
76
+ "remark-gfm": "4.0.1",
77
+ "rehype-highlight": "7.0.2",
78
+ "remark-frontmatter": "5.0.0",
79
+ "remark-mdx-frontmatter": "5.2.0",
80
+ "@remark-embedder/core": "3.0.3",
81
+ "@remark-embedder/transformer-oembed": "5.0.1"
75
82
  }
76
83
  }
@@ -49,11 +49,14 @@ const StartServer = async (cliSettings) => {
49
49
  `${defaultSettings.webpack.output.buildDirectory}/cache`,
50
50
  )
51
51
 
52
- if (!defaultSettings.webpack.production) {
52
+ const mode =
53
+ cliSettings.production || defaultSettings.webpack.production ? true : false
54
+
55
+ if (!mode) {
53
56
  cleanCacheDir(cacheDir)
54
57
  }
55
58
 
56
- webpackConfig.mode = 'development'
59
+ webpackConfig.mode = mode ? 'production' : 'development'
57
60
  const compiler = Webpack(webpackConfig)
58
61
  let port = webpackConfig.devServer.port || 3000
59
62
 
@@ -65,7 +68,7 @@ const StartServer = async (cliSettings) => {
65
68
  const devServerOptions = { ...webpackConfig.devServer, ...cliSettings }
66
69
  const server = new WebpackDevServer(devServerOptions, compiler)
67
70
 
68
- const devMode = Boolean(!defaultSettings.webpack.production)
71
+ const devMode = Boolean(!mode)
69
72
 
70
73
  const { version } = await getPackageVersion()
71
74
 
@@ -81,7 +84,7 @@ const StartServer = async (cliSettings) => {
81
84
  const envStatus = envPath()
82
85
  ? chalk.green('loaded')
83
86
  : chalk.yellow('not found')
84
- const modeLabel = defaultSettings.webpack.production
87
+ const modeLabel = mode
85
88
  ? chalk.green('production')
86
89
  : chalk.yellow('development')
87
90
 
@@ -109,4 +112,4 @@ const StartServer = async (cliSettings) => {
109
112
  await startServer()
110
113
  }
111
114
 
112
- export { StartServer }
115
+ export { StartServer as StartDevServer }
@@ -1,7 +1,7 @@
1
1
  #! /usr/bin/env node
2
2
  import yargs from 'yargs'
3
3
  import { hideBin } from 'yargs/helpers'
4
- import { StartServer } from './serve.mjs'
4
+ import { StartDevServer } from './dev.server.mjs'
5
5
  import { compiler } from './compiler.mjs'
6
6
  import logger from 'terminal-log'
7
7
  import chalk from 'chalk'
@@ -15,6 +15,14 @@ import {
15
15
  import { ESLint } from 'eslint'
16
16
  import eslintConfig from '../eslint.config.mjs'
17
17
  import fs from 'fs'
18
+ import { fileURLToPath } from 'url'
19
+ import { dirname, join } from 'path'
20
+ import server from './prod.server.mjs'
21
+ import config from '../utils/config.cjs'
22
+ const __filename = fileURLToPath(import.meta.url)
23
+
24
+ const __dirname = dirname(__filename)
25
+
18
26
  const lint = {
19
27
  command: 'lint',
20
28
  describe: 'Lint code',
@@ -47,16 +55,48 @@ const lint = {
47
55
  },
48
56
  }
49
57
 
50
- const serv = {
51
- command: 'server',
52
- describe: 'Run server',
58
+ const dev = {
59
+ command: 'dev',
60
+ describe: 'Run server for developer mode.',
53
61
  handler: async (arg) => {
62
+ if (defaultSettings.webpack.production) {
63
+ logger.error(
64
+ 'You need use development mode! change webpack.production to false in ryunix.config.js.',
65
+ )
66
+ return
67
+ }
54
68
  const open = Boolean(arg.browser) || false
55
69
  const settings = {
56
70
  open,
57
71
  }
58
72
 
59
- StartServer(settings)
73
+ StartDevServer(settings)
74
+ },
75
+ }
76
+
77
+ const prod = {
78
+ command: 'start',
79
+ describe: 'Run server for production mode. Requiere .ryunix/static',
80
+ handler: async (arg) => {
81
+ if (!defaultSettings.webpack.production) {
82
+ logger.error('You need use production mode!')
83
+ return
84
+ }
85
+
86
+ if (
87
+ !fs.existsSync(
88
+ join(process.cwd(), config.webpack.output.buildDirectory, 'static'),
89
+ )
90
+ ) {
91
+ logger.error('You need build first!')
92
+ return
93
+ }
94
+
95
+ server.listen(config.webpack.devServer.port, () => {
96
+ console.log(
97
+ `Server running at http://localhost:${config.webpack.devServer.port}/`,
98
+ )
99
+ })
60
100
  },
61
101
  }
62
102
 
@@ -113,4 +153,32 @@ const build = {
113
153
  },
114
154
  }
115
155
 
116
- yargs(hideBin(process.argv)).command(serv).command(build).command(lint).parse()
156
+ const extractHTML = {
157
+ command: 'customHtml',
158
+ describe: 'Extract HTML for customization',
159
+ handler: async (arg) => {
160
+ const runPath = process.cwd()
161
+
162
+ fs.copyFile(
163
+ join(__dirname, '..', 'template/index.html'),
164
+ join(runPath, 'public/index.html'),
165
+ (err) => {
166
+ if (err) {
167
+ console.error('Error extracting HTML: ', err.message)
168
+ return
169
+ }
170
+ console.log(
171
+ 'File extracted successfully. Now you can enable the template with static.customTemplate inside ryunix.config.js',
172
+ )
173
+ },
174
+ )
175
+ },
176
+ }
177
+
178
+ yargs(hideBin(process.argv))
179
+ .command(dev)
180
+ .command(build)
181
+ .command(prod)
182
+ .command(lint)
183
+ .command(extractHTML)
184
+ .parse()
@@ -22,12 +22,19 @@ const Prerender = async (directory) => {
22
22
  if (fs.existsSync(manifestPath)) {
23
23
  try {
24
24
  routes = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
25
- console.log(`[SSG] Found ${routes.length} routes in manifest`)
26
25
  } catch (error) {
27
26
  console.error('[SSG] Error reading routes manifest:', error)
28
27
  }
29
28
  }
30
29
 
30
+ const metaExist = routes.some((route) => route.meta)
31
+ if (metaExist && defaultSettings.static.seo.meta.length > 0) {
32
+ console.error(
33
+ '[Ryunix Error] You are mixing static and dynamic meta tags; you can only use one of the two. Remove static.seo.meta from ryunix.config.js.',
34
+ )
35
+ process.exit(1)
36
+ }
37
+
31
38
  if (routes.length === 0) {
32
39
  routes = defaultSettings.experimental?.ssg?.prerender || []
33
40
  if (routes.length > 0) {
@@ -0,0 +1,401 @@
1
+ import http from 'http'
2
+ import { promises as fs } from 'fs'
3
+ import path from 'path'
4
+ import { createHash } from 'crypto'
5
+ import zlib from 'zlib'
6
+ import { promisify } from 'util'
7
+ import { createReadStream } from 'fs'
8
+ import { pipeline } from 'stream/promises'
9
+ import config from '../utils/config.cjs'
10
+
11
+ const gzip = promisify(zlib.gzip)
12
+ const brotliCompress = promisify(zlib.brotliCompress)
13
+
14
+ // MIME types dictionary
15
+ const MIME_TYPES = {
16
+ '.js': 'application/javascript',
17
+ '.mjs': 'application/javascript',
18
+ '.css': 'text/css',
19
+ '.html': 'text/html',
20
+ '.json': 'application/json',
21
+ '.png': 'image/png',
22
+ '.jpg': 'image/jpeg',
23
+ '.jpeg': 'image/jpeg',
24
+ '.gif': 'image/gif',
25
+ '.svg': 'image/svg+xml',
26
+ '.woff': 'font/woff',
27
+ '.woff2': 'font/woff2',
28
+ '.ttf': 'font/ttf',
29
+ '.eot': 'application/vnd.ms-fontobject',
30
+ '.otf': 'font/otf',
31
+ '.wasm': 'application/wasm',
32
+ '.ico': 'image/x-icon',
33
+ '.mp3': 'audio/mpeg',
34
+ '.mp4': 'video/mp4',
35
+ '.pdf': 'application/pdf',
36
+ '.zip': 'application/zip',
37
+ '.gz': 'application/gzip',
38
+ '.tar': 'application/x-tar',
39
+ '.7z': 'application/x-7z-compressed',
40
+ '.rar': 'application/x-rar-compressed',
41
+ '.avi': 'video/x-msvideo',
42
+ '.mov': 'video/quicktime',
43
+ '.wmv': 'video/x-ms-wmv',
44
+ '.flv': 'video/x-flv',
45
+ '.webm': 'video/webm',
46
+ '.ogg': 'audio/ogg',
47
+ '.ogv': 'video/ogg',
48
+ '.m4v': 'video/mp4',
49
+ '.3gp': 'video/3gpp',
50
+ '.3g2': 'video/3gpp2',
51
+ '.mkv': 'video/x-matroska',
52
+ '.ts': 'video/mp2t',
53
+ }
54
+
55
+ // File cache for production server
56
+ const fileCache = new Map()
57
+ const MAX_CACHE_SIZE = 50 * 1024 * 1024 // 50MB
58
+ let currentCacheSize = 0
59
+
60
+ /**
61
+ * Get MIME type from file extension
62
+ */
63
+ const getMimeType = (filePath) => {
64
+ const ext = path.extname(filePath).toLowerCase()
65
+ return MIME_TYPES[ext] || 'application/octet-stream'
66
+ }
67
+
68
+ /**
69
+ * Validate path to prevent directory traversal attacks
70
+ */
71
+ const validatePath = (requestPath, rootDir) => {
72
+ try {
73
+ const normalizedPath = path.normalize(requestPath)
74
+ const resolvedPath = path.resolve(rootDir, normalizedPath.slice(1))
75
+
76
+ if (!resolvedPath.startsWith(rootDir)) {
77
+ return null
78
+ }
79
+
80
+ return resolvedPath
81
+ } catch {
82
+ return null
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generate ETag from file content
88
+ */
89
+ const generateETag = (content) => {
90
+ return createHash('md5').update(content).digest('hex')
91
+ }
92
+
93
+ /**
94
+ * Check compression support (Brotli preferred over Gzip)
95
+ */
96
+ const getAcceptedEncoding = (headers) => {
97
+ const encoding = headers['accept-encoding'] || ''
98
+ if (encoding.includes('br')) return 'br'
99
+ if (encoding.includes('gzip')) return 'gzip'
100
+ return null
101
+ }
102
+
103
+ /**
104
+ * Check if MIME type is compressible
105
+ */
106
+ const isCompressible = (mimeType) => {
107
+ return (
108
+ mimeType.startsWith('text/') ||
109
+ mimeType.includes('javascript') ||
110
+ mimeType.includes('json') ||
111
+ mimeType.includes('css')
112
+ )
113
+ }
114
+
115
+ /**
116
+ * Parse Range header
117
+ */
118
+ const parseRange = (rangeHeader, fileSize) => {
119
+ if (!rangeHeader) return null
120
+
121
+ const parts = rangeHeader.replace(/bytes=/, '').split('-')
122
+ const start = parseInt(parts[0], 10)
123
+ const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
124
+
125
+ if (isNaN(start) || isNaN(end) || start > end || end >= fileSize) {
126
+ return null
127
+ }
128
+
129
+ return { start, end, length: end - start + 1 }
130
+ }
131
+
132
+ /**
133
+ * Check if file should support range requests (media files)
134
+ */
135
+ const supportsRangeRequests = (mimeType) => {
136
+ return (
137
+ mimeType.startsWith('video/') ||
138
+ mimeType.startsWith('audio/') ||
139
+ mimeType === 'application/pdf'
140
+ )
141
+ }
142
+
143
+ /**
144
+ * Serve file with range support (for video/audio)
145
+ */
146
+ const serveWithRange = async (filePath, req, res, stats) => {
147
+ const mimeType = getMimeType(filePath)
148
+ const range = parseRange(req.headers.range, stats.size)
149
+
150
+ const headers = {
151
+ 'Content-Type': mimeType,
152
+ 'Accept-Ranges': 'bytes',
153
+ 'Cache-Control': 'public, max-age=31536000',
154
+ }
155
+
156
+ if (range) {
157
+ // Partial content
158
+ headers['Content-Range'] = `bytes ${range.start}-${range.end}/${stats.size}`
159
+ headers['Content-Length'] = range.length
160
+
161
+ res.writeHead(206, headers)
162
+
163
+ const stream = createReadStream(filePath, {
164
+ start: range.start,
165
+ end: range.end,
166
+ })
167
+ await pipeline(stream, res)
168
+ } else {
169
+ // Full content
170
+ headers['Content-Length'] = stats.size
171
+ res.writeHead(200, headers)
172
+
173
+ const stream = createReadStream(filePath)
174
+ await pipeline(stream, res)
175
+ }
176
+
177
+ return true
178
+ }
179
+
180
+ /**
181
+ * Serve static file with caching and compression
182
+ */
183
+ const serveStaticFile = async (filePath, req, res) => {
184
+ try {
185
+ let stats = await fs.stat(filePath)
186
+
187
+ // 👉 If is a directory
188
+ if (stats.isDirectory()) {
189
+ const indexPath = path.join(filePath, 'index.html')
190
+ await fs.access(indexPath)
191
+ stats = await fs.stat(indexPath)
192
+ filePath = indexPath
193
+ }
194
+
195
+ const mimeType = getMimeType(filePath)
196
+
197
+ // Use range requests for media files or large files
198
+ if (supportsRangeRequests(mimeType) || stats.size > 5 * 1024 * 1024) {
199
+ return await serveWithRange(filePath, req, res, stats)
200
+ }
201
+
202
+ let cached = fileCache.get(filePath)
203
+
204
+ if (!cached) {
205
+ // Read and cache file
206
+ const content = await fs.readFile(filePath)
207
+ const etag = generateETag(content)
208
+
209
+ // Compress if text-based content
210
+ let brotli = null
211
+ let gzipped = null
212
+
213
+ if (isCompressible(mimeType)) {
214
+ try {
215
+ ;[brotli, gzipped] = await Promise.all([
216
+ brotliCompress(content, {
217
+ params: {
218
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 6,
219
+ },
220
+ }),
221
+ gzip(content),
222
+ ])
223
+ } catch {
224
+ // Compression failed, serve uncompressed
225
+ }
226
+ }
227
+
228
+ cached = {
229
+ content,
230
+ brotli,
231
+ gzipped,
232
+ etag,
233
+ mimeType,
234
+ size: stats.size,
235
+ }
236
+
237
+ // Update cache
238
+ if (currentCacheSize + stats.size < MAX_CACHE_SIZE) {
239
+ fileCache.set(filePath, cached)
240
+ currentCacheSize += stats.size
241
+ }
242
+ }
243
+
244
+ // Check ETag for 304 Not Modified
245
+ if (req.headers['if-none-match'] === cached.etag) {
246
+ res.writeHead(304)
247
+ res.end()
248
+ return true
249
+ }
250
+
251
+ // Select best encoding
252
+ const encoding = getAcceptedEncoding(req.headers)
253
+ let responseContent = cached.content
254
+ let contentEncoding = null
255
+
256
+ if (encoding === 'br' && cached.brotli) {
257
+ responseContent = cached.brotli
258
+ contentEncoding = 'br'
259
+ } else if (encoding === 'gzip' && cached.gzipped) {
260
+ responseContent = cached.gzipped
261
+ contentEncoding = 'gzip'
262
+ }
263
+
264
+ const headers = {
265
+ 'Content-Type': cached.mimeType,
266
+ 'Content-Length': responseContent.length,
267
+ ETag: cached.etag,
268
+ 'Cache-Control': 'public, max-age=31536000',
269
+ }
270
+
271
+ if (contentEncoding) {
272
+ headers['Content-Encoding'] = contentEncoding
273
+ }
274
+
275
+ res.writeHead(200, headers)
276
+ res.end(responseContent)
277
+ return true
278
+ } catch (error) {
279
+ return false
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Serve HTML page with SPA fallback support
285
+ */
286
+ const serveHTMLPage = async (pathname, staticDir, req, res) => {
287
+ try {
288
+ const candidates = []
289
+
290
+ // / → /index.html
291
+ if (pathname === '/') {
292
+ candidates.push(path.join(staticDir, 'index.html'))
293
+ } else {
294
+ // /test → /test/index.html
295
+ candidates.push(path.join(staticDir, pathname, 'index.html'))
296
+
297
+ // /test → /test.html
298
+ candidates.push(path.join(staticDir, `${pathname}.html`))
299
+
300
+ // SPA fallback
301
+ candidates.push(path.join(staticDir, 'index.html'))
302
+ }
303
+
304
+ let pageFile = null
305
+
306
+ for (const file of candidates) {
307
+ try {
308
+ await fs.access(file)
309
+ pageFile = file
310
+ break
311
+ } catch {}
312
+ }
313
+
314
+ if (!pageFile) {
315
+ res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
316
+ res.end('404')
317
+ return
318
+ }
319
+
320
+ const content = await fs.readFile(pageFile, 'utf-8')
321
+ const etag = generateETag(Buffer.from(content))
322
+
323
+ if (req.headers['if-none-match'] === etag) {
324
+ res.writeHead(304)
325
+ res.end()
326
+ return
327
+ }
328
+
329
+ // Compress HTML
330
+ let responseContent = content
331
+ const headers = {
332
+ 'Content-Type': 'text/html; charset=utf-8',
333
+ ETag: etag,
334
+ 'Cache-Control': 'no-cache',
335
+ }
336
+
337
+ const encoding = getAcceptedEncoding(req.headers)
338
+
339
+ if (encoding === 'br') {
340
+ try {
341
+ responseContent = await brotliCompress(Buffer.from(content))
342
+ headers['Content-Encoding'] = 'br'
343
+ } catch {
344
+ // Fallback to uncompressed
345
+ }
346
+ } else if (encoding === 'gzip') {
347
+ try {
348
+ responseContent = await gzip(Buffer.from(content))
349
+ headers['Content-Encoding'] = 'gzip'
350
+ } catch {
351
+ // Fallback to uncompressed
352
+ }
353
+ }
354
+
355
+ headers['Content-Length'] = Buffer.byteLength(responseContent)
356
+
357
+ res.writeHead(200, headers)
358
+ res.end(responseContent)
359
+ } catch (error) {
360
+ res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' })
361
+ res.end('500')
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Request handler
367
+ */
368
+ const requestHandler = async (req, res) => {
369
+ const rootDir = process.cwd()
370
+ const staticDir = path.join(
371
+ rootDir,
372
+ config.webpack.output.buildDirectory,
373
+ 'static',
374
+ )
375
+
376
+ try {
377
+ const parsedUrl = new URL(req.url, `http://${req.headers.host}`)
378
+ const pathname = decodeURIComponent(parsedUrl.pathname)
379
+
380
+ const safePath = validatePath(pathname, staticDir)
381
+ if (!safePath) {
382
+ res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' })
383
+ res.end('403')
384
+ return
385
+ }
386
+
387
+ const fileServed = await serveStaticFile(safePath, req, res)
388
+
389
+ if (!fileServed) {
390
+ await serveHTMLPage(pathname, staticDir, req, res)
391
+ }
392
+ } catch (error) {
393
+ console.error('[Ryunix Server Error]:', error.message)
394
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' })
395
+ res.end('500')
396
+ }
397
+ }
398
+
399
+ const httpServ = http.createServer(requestHandler)
400
+
401
+ export default httpServ
@@ -1,15 +1,27 @@
1
1
  import config from './utils/config.cjs'
2
- import { resolveApp } from './utils/index.mjs'
3
2
  import { defineConfig } from 'eslint/config'
4
- const dir = process.cwd()
5
3
 
4
+ /**
5
+ * ESLint Configuration for Ryunix
6
+ *
7
+ * NOTE ABOUT MDX:
8
+ * .mdx and .md files are excluded from ESLint due to compatibility issues
9
+ * between eslint-plugin-mdx and ESM/flat config.
10
+ *
11
+ * Error: “Could not find ESLint Linter in require cache”
12
+ *
13
+ * MDX files are validated during compilation by @mdx-js/loader,
14
+ * which is sufficient for detecting syntax and JSX errors.
15
+ */
6
16
  const eslintConfig = defineConfig([
7
17
  {
8
18
  files: ['**/*.ryx', ...config?.eslint?.files],
19
+
20
+ ignores: ['**/*.mdx', '**/*.md', '**/node_modules/**'],
21
+
9
22
  languageOptions: {
10
23
  ecmaVersion: 2021,
11
24
  sourceType: 'module',
12
-
13
25
  parserOptions: {
14
26
  ecmaFeatures: {
15
27
  jsx: true,
@@ -19,8 +31,8 @@ const eslintConfig = defineConfig([
19
31
  },
20
32
  settings: {
21
33
  react: {
22
- pragma: 'Ryunix.createElement', // Para JSX transpile a Ryunix.createElement
23
- fragment: 'Ryunix.Fragment', // Para fragmentos JSX
34
+ pragma: 'Ryunix.createElement',
35
+ fragment: 'Ryunix.Fragment',
24
36
  },
25
37
  },
26
38
  plugins: config?.eslint?.plugins,