@unsetsoft/ryunix-presets 1.0.20 → 1.0.23-canary.3

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,13 +1,15 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunix-presets",
3
- "description": "Package with presets for different development environments.",
4
- "version": "1.0.20",
3
+ "description": "Package with presets for different development environments with MDX support.",
4
+ "version": "1.0.23-canary.3",
5
5
  "author": "Neyunse",
6
6
  "type": "module",
7
7
  "repository": "https://github.com/UnSetSoft/Ryunixjs",
8
8
  "license": "MIT",
9
9
  "keywords": [
10
- "ryunixjs"
10
+ "ryunixjs",
11
+ "mdx",
12
+ "markdown"
11
13
  ],
12
14
  "files": [
13
15
  "vite/",
@@ -15,7 +17,7 @@
15
17
  "webpack/"
16
18
  ],
17
19
  "peerDependencies": {
18
- "@unsetsoft/ryunixjs": "^1.2.1"
20
+ "@unsetsoft/ryunixjs": "^1.2.2"
19
21
  },
20
22
  "publishConfig": {
21
23
  "registry": "https://registry.npmjs.org"
@@ -41,6 +43,8 @@
41
43
  "@babel/preset-react": "7.28.5",
42
44
  "@eslint/eslintrc": "3.3.1",
43
45
  "@eslint/js": "9.38.0",
46
+ "@mdx-js/loader": "^3.1.1",
47
+ "@mdx-js/rollup": "^3.1.1",
44
48
  "@rollup/plugin-terser": "0.4.4",
45
49
  "@swc/core": "1.12.14",
46
50
  "babel-loader": "10.0.0",
@@ -48,10 +52,9 @@
48
52
  "copy-webpack-plugin": "13.0.1",
49
53
  "css-loader": "7.1.2",
50
54
  "css-minimizer-webpack-plugin": "7.0.2",
51
- "sass": "1.93.2",
52
- "sass-loader": "16.0.6",
53
55
  "dotenv-webpack": "8.1.1",
54
56
  "eslint": "9.38.0",
57
+ "eslint-plugin-mdx": "^3.1.5",
55
58
  "eslint-plugin-react": "^7.37.5",
56
59
  "eslint-webpack-plugin": "5.0.2",
57
60
  "file-loader": "6.2.0",
@@ -61,8 +64,17 @@
61
64
  "html-webpack-plugin": "5.6.4",
62
65
  "image-webpack-loader": "^8.1.0",
63
66
  "lodash": "4.17.21",
67
+ "markdown-loader": "^8.0.0",
64
68
  "mini-css-extract-plugin": "2.9.4",
69
+ "rehype-highlight": "^7.0.1",
70
+ "rehype-katex": "^7.0.1",
71
+ "rehype-slug": "^6.0.0",
72
+ "remark-frontmatter": "^5.0.0",
73
+ "remark-gfm": "^4.0.0",
74
+ "remark-math": "^6.0.0",
65
75
  "rollup": "4.52.5",
76
+ "sass": "1.93.2",
77
+ "sass-loader": "16.0.6",
66
78
  "style-loader": "4.0.0",
67
79
  "terminal-log": "1.0.1",
68
80
  "terser-webpack-plugin": "5.3.14",
@@ -72,5 +84,9 @@
72
84
  "webpack-cli": "6.0.1",
73
85
  "webpack-dev-server": "5.2.2",
74
86
  "yargs": "18.0.0"
87
+ },
88
+ "optionalDependencies": {
89
+ "rehype-highlight": "^7.0.1",
90
+ "remark-gfm": "^4.0.0"
75
91
  }
76
- }
92
+ }
@@ -14,6 +14,7 @@ import {
14
14
  } from '../utils/index.mjs'
15
15
  import { ESLint } from 'eslint'
16
16
  import eslintConfig from '../eslint.config.mjs'
17
+ import fs from 'fs'
17
18
  const lint = {
18
19
  command: 'lint',
19
20
  describe: 'Lint code',
@@ -72,7 +73,7 @@ const build = {
72
73
  return
73
74
  }
74
75
 
75
- if (defaultSettings.experimental.ssg.prerender.length > 0) {
76
+ if (fs.existsSync(resolveApp(process.cwd(), 'src/pages/routes.ryx'))) {
76
77
  await cleanBuildDirectory(
77
78
  resolveApp(
78
79
  process.cwd(),
@@ -96,8 +97,8 @@ const build = {
96
97
  const formattedTime =
97
98
  minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`
98
99
 
99
- if (defaultSettings.experimental.ssg.prerender.length > 0) {
100
- await Prerender()
100
+ if (defaultSettings.webpack.production) {
101
+ await Prerender(defaultSettings.webpack.output.buildDirectory)
101
102
  }
102
103
 
103
104
  logger.info(chalk.green('Compilation successful! 🎉'))
@@ -1,138 +1,47 @@
1
1
  /**
2
- * prerender metadata
3
- *
2
+ * Automatic Prerender - reads routes from manifest
4
3
  */
4
+ import { buildSSG } from '../utils/ssg.mjs'
5
5
  import { configFileExist } from '../utils/settingfile.cjs'
6
6
  import defaultSettings from '../utils/config.cjs'
7
7
  import { resolveApp } from '../utils/index.mjs'
8
8
  import fs from 'fs'
9
9
  import path from 'path'
10
10
 
11
- // proyect/.ryunix/static
12
- const buildDirectory = resolveApp(process.cwd(), '.ryunix/static')
13
- const indexFile = path.join(buildDirectory, 'index.html')
11
+ const Prerender = async (directory) => {
12
+ const buildDirectory = resolveApp(process.cwd(), directory)
14
13
 
15
- const siteMap = async (routes) => {
16
- if (!defaultSettings.experimental.ssg.sitemap.baseURL) {
17
- console.error(
18
- '❌ Base URL is not defined in the configuration file. Please set `experimental.ssg.sitemap.baseURL`.',
19
- )
20
- process.exit(1)
21
- }
22
-
23
- const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
24
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
25
- ${routes
26
- .map((route) => {
27
- const url = `${defaultSettings.experimental.ssg.sitemap.baseURL}${route.path === '/' ? '' : route.path}`
28
- const meta = route.meta || {}
29
- const sitemap_settings = route.sitemap || {}
30
- const lastmod = meta.lastmod || new Date().toISOString().split('T')[0]
31
-
32
- // sitemap settings
33
- const changefreq =
34
- sitemap_settings.changefreq ||
35
- meta.changefreq || // TODO: Remove meta.changefreq
36
- defaultSettings.experimental.ssg.sitemap.settings.changefreq
37
- const priority =
38
- sitemap_settings.priority ||
39
- meta.priority || // TODO: Remove meta.priority
40
- defaultSettings.experimental.ssg.sitemap.settings.priority
41
-
42
- return `<url>
43
- <loc>${url}</loc>
44
- <lastmod>${lastmod}</lastmod>
45
- <changefreq>${changefreq}</changefreq>
46
- <priority>${priority}</priority>
47
- </url>`
48
- })
49
- .join('\n')}
50
- </urlset>`
51
-
52
- await fs.writeFileSync(
53
- path.resolve(buildDirectory, 'sitemap.xml'),
54
- sitemap,
55
- 'utf-8',
56
- )
57
-
58
- console.log('✅ Sitemap created')
59
- }
60
-
61
- const Prerender = async () => {
62
14
  if (!configFileExist()) {
63
15
  console.error('❌ No configuration file found.')
64
16
  process.exit(1)
65
17
  }
66
18
 
67
- const template = fs.readFileSync(indexFile, 'utf-8')
19
+ const manifestPath = path.join(process.cwd(), directory, 'ssg', 'routes.json')
20
+ let routes = []
68
21
 
69
- for (const route of defaultSettings.experimental.ssg.prerender) {
70
- if (route.path === '/') continue // Exclude root path, handled by defaultSettings.static.seo.meta
71
- let html = template
72
- const meta = route?.meta || defaultSettings.static.seo.meta
73
-
74
- // title
75
- if (meta.title) {
76
- html = html.replace(/<title>.*<\/title>/, `<title>${meta.title}<\/title>`)
22
+ if (fs.existsSync(manifestPath)) {
23
+ try {
24
+ routes = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
25
+ console.log(`[SSG] Found ${routes.length} routes in manifest`)
26
+ } catch (error) {
27
+ console.error('[SSG] Error reading routes manifest:', error)
77
28
  }
29
+ }
78
30
 
79
- // Helper to add or replace metatag
80
- function upsertMetaTag(html, name, value) {
81
- if (value === undefined || value === null) return html
82
-
83
- const isProperty = name.startsWith('og:') || name.startsWith('twitter:')
84
- const attr = isProperty ? 'property' : 'name'
85
- const regex = new RegExp(`<meta ${attr}=["']${name}["'][^>]*>`, 'i')
86
- const tag = `<meta ${attr}="${name}" content="${value}">`
87
-
88
- if (regex.test(html)) {
89
- return html.replace(regex, tag)
90
- } else {
91
- return html.replace(/<\/head>/i, `${tag}\n<\/head>`)
92
- }
93
- }
94
-
95
- // Description
96
- html = upsertMetaTag(html, 'description', meta.description)
97
- // Keywords
98
- html = upsertMetaTag(html, 'keywords', meta.keywords)
99
-
100
- // dynamic metatags (excluding title, description, keywords, framework, mode)
101
- for (const [key, value] of Object.entries(meta)) {
102
- if (
103
- [
104
- 'title',
105
- 'description',
106
- 'keywords',
107
- 'framework',
108
- 'mode',
109
- 'viewport',
110
- ].includes(key)
111
- )
112
- continue
113
- html = upsertMetaTag(html, key, value)
114
- }
115
-
116
- const outputDir =
117
- route.path === '/'
118
- ? buildDirectory
119
- : path.join(buildDirectory, route.path)
120
-
121
- if (!fs.existsSync(outputDir)) {
122
- await fs.mkdirSync(outputDir, { recursive: true, force: true })
31
+ if (routes.length === 0) {
32
+ routes = defaultSettings.experimental?.ssg?.prerender || []
33
+ if (routes.length > 0) {
34
+ console.log(`[SSG] Using ${routes.length} routes from config`)
123
35
  }
124
-
125
- await fs.writeFileSync(path.join(outputDir, 'index.html'), html)
126
- console.log(`✅ Prerendered ${route.path}`)
127
36
  }
128
37
 
129
- // need base url and enable
130
- if (
131
- defaultSettings.experimental.ssg.sitemap.enable &&
132
- defaultSettings.experimental.ssg.sitemap.baseURL
133
- ) {
134
- await siteMap(defaultSettings.experimental.ssg.prerender)
38
+ if (routes.length === 0) {
39
+ console.log('[SSG] No routes to prerender, skipping SSG generation.')
40
+ return
135
41
  }
42
+
43
+ await buildSSG(routes, defaultSettings, buildDirectory)
44
+ console.log('✅ SSG build complete')
136
45
  }
137
46
 
138
47
  export default Prerender
@@ -1,11 +1,12 @@
1
1
  import config from './utils/config.cjs'
2
2
  import { resolveApp } from './utils/index.mjs'
3
3
  import { defineConfig } from 'eslint/config'
4
+
4
5
  const dir = process.cwd()
5
6
 
6
7
  const eslintConfig = defineConfig([
7
8
  {
8
- files: ['**/*.ryx', ...config?.eslint?.files],
9
+ files: ['**/*.ryx', '**/*.mdx', ...config?.eslint?.files],
9
10
  languageOptions: {
10
11
  ecmaVersion: 2021,
11
12
  sourceType: 'module',
@@ -14,17 +15,23 @@ const eslintConfig = defineConfig([
14
15
  ecmaFeatures: {
15
16
  jsx: true,
16
17
  },
17
- extraFileExtensions: ['.ryx'],
18
+ extraFileExtensions: ['.ryx', '.mdx'],
18
19
  },
19
20
  },
20
21
  settings: {
21
22
  react: {
22
- pragma: 'Ryunix.createElement', // Para JSX transpile a Ryunix.createElement
23
- fragment: 'Ryunix.Fragment', // Para fragmentos JSX
23
+ pragma: 'Ryunix.createElement',
24
+ fragment: 'Ryunix.Fragment',
24
25
  },
26
+ // MDX-specific settings
27
+ 'mdx/code-blocks': true,
25
28
  },
26
29
  plugins: config?.eslint?.plugins,
27
- rules: config?.eslint?.rules,
30
+ rules: {
31
+ ...config?.eslint?.rules,
32
+ // Disable some rules for MDX files that might conflict
33
+ 'react/jsx-no-undef': 'off', // MDX might use components not explicitly imported
34
+ },
28
35
  },
29
36
  ])
30
37
 
@@ -6,6 +6,8 @@ import { join, dirname } from 'path'
6
6
  import { fileURLToPath } from 'url'
7
7
  import logger from 'terminal-log'
8
8
  import chalk from 'chalk'
9
+ import { createRequire } from 'node:module'
10
+ import { readFile } from 'node:fs/promises'
9
11
 
10
12
  const resolveApp = (appDirectory, relativePath) =>
11
13
  resolve(appDirectory, relativePath)
@@ -58,19 +60,12 @@ const getEnviroment = () =>
58
60
  },
59
61
  )
60
62
 
63
+ const require = createRequire(import.meta.url)
64
+
61
65
  const getPackageVersion = async () => {
62
- const __dirname = dirname(fileURLToPath(import.meta.url)) // Para obtener el directorio actual
63
- const packageJsonPath = join(
64
- __dirname,
65
- '..',
66
- '..',
67
- '..',
68
- 'ryunixjs',
69
- 'package.json',
70
- )
71
- const data = await fs.readFile(packageJsonPath, 'utf-8')
72
- const packageJson = JSON.parse(data)
73
- return packageJson
66
+ const packageJsonPath = require.resolve('@unsetsoft/ryunixjs/package.json')
67
+ const data = await readFile(packageJsonPath, 'utf-8')
68
+ return JSON.parse(data)
74
69
  }
75
70
 
76
71
  async function cleanCacheDir(dirPath) {
@@ -0,0 +1,49 @@
1
+ export const getMDXLoaderConfig = (options = {}) => {
2
+ const {
3
+ buildDirectory = '.ryunix',
4
+ remarkPlugins = [],
5
+ rehypePlugins = [],
6
+ } = options
7
+
8
+ return {
9
+ test: /\.mdx$/,
10
+ exclude: /node_modules/,
11
+ use: [
12
+ {
13
+ loader: 'babel-loader',
14
+ options: {
15
+ presets: [
16
+ [
17
+ '@babel/preset-env',
18
+ {
19
+ targets: 'defaults and not IE 11',
20
+ useBuiltIns: false,
21
+ modules: false,
22
+ bugfixes: true,
23
+ },
24
+ ],
25
+ ],
26
+ cacheDirectory: `${buildDirectory}/cache/babel`,
27
+ plugins: [
28
+ [
29
+ '@babel/plugin-transform-react-jsx',
30
+ {
31
+ pragma: 'Ryunix.createElement',
32
+ pragmaFrag: 'Ryunix.Fragment',
33
+ },
34
+ ],
35
+ ],
36
+ },
37
+ },
38
+ {
39
+ loader: '@mdx-js/loader',
40
+ options: {
41
+ remarkPlugins: [...remarkPlugins],
42
+ rehypePlugins: [...rehypePlugins],
43
+ },
44
+ },
45
+ ],
46
+ }
47
+ }
48
+
49
+ export default getMDXLoaderConfig
@@ -0,0 +1,197 @@
1
+ /**
2
+ * SSG Utilities - Improved static site generation
3
+ */
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+
7
+ /**
8
+ * Extract routes from routes config for SSG
9
+ */
10
+ const extractSSGRoutes = (routes) => {
11
+ const ssgRoutes = []
12
+
13
+ const processRoute = (route, parentPath = '') => {
14
+ if (!route.path || route.path.includes(':')) return // Skip dynamic routes
15
+ if (route.NotFound || route.noRenderLink) return // Skip special routes
16
+
17
+ const fullPath = parentPath + route.path
18
+
19
+ ssgRoutes.push({
20
+ path: fullPath === '' ? '/' : fullPath,
21
+ component: route.component,
22
+ meta: route.meta || {},
23
+ sitemap: route.sitemap || {},
24
+ label: route.label,
25
+ })
26
+
27
+ // Process nested routes
28
+ if (route.subRoutes) {
29
+ route.subRoutes.forEach((subRoute) => {
30
+ processRoute(subRoute, fullPath)
31
+ })
32
+ }
33
+ }
34
+
35
+ routes.forEach((route) => processRoute(route))
36
+ return ssgRoutes
37
+ }
38
+
39
+ /**
40
+ * Generate robots.txt
41
+ */
42
+ const generateRobotsTxt = (baseURL, options = {}) => {
43
+ const { disallow = [], allow = [], userAgents = ['*'] } = options
44
+
45
+ let content = ''
46
+
47
+ userAgents.forEach((agent) => {
48
+ content += `User-agent: ${agent}\n`
49
+ allow.forEach((path) => (content += `Allow: ${path}\n`))
50
+ disallow.forEach((path) => (content += `Disallow: ${path}\n`))
51
+ })
52
+
53
+ content += `\nSitemap: ${baseURL}/sitemap.xml\n`
54
+
55
+ return content
56
+ }
57
+
58
+ /**
59
+ * Generate enhanced sitemap with all routes
60
+ */
61
+ const generateSitemap = (routes, baseURL, defaultSettings = {}) => {
62
+ const { changefreq = 'weekly', priority = '0.7' } = defaultSettings
63
+
64
+ const urls = routes
65
+ .map((route) => {
66
+ const url = `${baseURL}${route.path === '/' ? '' : route.path}`
67
+ const meta = route.meta || {}
68
+ const sitemap = route.sitemap || {}
69
+
70
+ const lastmod =
71
+ meta.lastmod ||
72
+ sitemap.lastmod ||
73
+ new Date().toISOString().split('T')[0]
74
+ const freq = sitemap.changefreq || meta.changefreq || changefreq
75
+ const prio = sitemap.priority || meta.priority || priority
76
+
77
+ return ` <url>
78
+ <loc>${url}</loc>
79
+ <lastmod>${lastmod}</lastmod>
80
+ <changefreq>${freq}</changefreq>
81
+ <priority>${prio}</priority>
82
+ </url>`
83
+ })
84
+ .join('\n')
85
+
86
+ return `<?xml version="1.0" encoding="UTF-8"?>
87
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
88
+ ${urls}
89
+ </urlset>`
90
+ }
91
+
92
+ /**
93
+ * Generate meta tags HTML
94
+ */
95
+ const generateMetaTags = (meta, defaultMeta = {}) => {
96
+ const tags = { ...defaultMeta, ...meta }
97
+ let html = ''
98
+
99
+ Object.entries(tags).forEach(([key, value]) => {
100
+ if (['title', 'canonical'].includes(key)) return
101
+
102
+ const isProperty = key.startsWith('og:') || key.startsWith('twitter:')
103
+ const attr = isProperty ? 'property' : 'name'
104
+ html += ` <meta ${attr}="${key}" content="${value}">\n`
105
+ })
106
+
107
+ return html
108
+ }
109
+
110
+ /**
111
+ * Prerender route to HTML
112
+ */
113
+ const prerenderRoute = async (route, template, config) => {
114
+ const meta = route.meta || config.static.seo.meta
115
+ let html = template
116
+
117
+ // Replace title
118
+ if (meta.title) {
119
+ html = html.replace(/<title>.*?<\/title>/, `<title>${meta.title}</title>`)
120
+ }
121
+
122
+ // Add/replace meta tags
123
+ const metaTags = generateMetaTags(meta, config.static.seo.meta)
124
+
125
+ // Remove existing meta tags (except framework/mode)
126
+ html = html.replace(/<meta name="(?!framework|mode).*?>\n/g, '')
127
+ html = html.replace(/<meta property=.*?>\n/g, '')
128
+
129
+ // Add new meta tags before </head>
130
+ html = html.replace(/<\/head>/, `${metaTags} </head>`)
131
+
132
+ // Add canonical if provided
133
+ if (meta.canonical) {
134
+ const canonical = ` <link rel="canonical" href="${meta.canonical}">\n`
135
+ html = html.replace(/<\/head>/, `${canonical} </head>`)
136
+ }
137
+
138
+ return html
139
+ }
140
+
141
+ /**
142
+ * Full SSG build process
143
+ */
144
+ const buildSSG = async (routesConfig, config, buildDir) => {
145
+ const routes = extractSSGRoutes(routesConfig)
146
+
147
+ // Template is in buildDir/static/index.html
148
+ const templatePath = path.join(buildDir, 'static', 'index.html')
149
+
150
+ if (!fs.existsSync(templatePath)) {
151
+ console.error('❌ Template not found at:', templatePath)
152
+ return
153
+ }
154
+
155
+ const template = fs.readFileSync(templatePath, 'utf-8')
156
+
157
+ for (const route of routes) {
158
+ const html = await prerenderRoute(route, template, config)
159
+
160
+ const outputDir =
161
+ route.path === '/'
162
+ ? path.join(buildDir, 'static')
163
+ : path.join(buildDir, 'static', route.path)
164
+
165
+ if (!fs.existsSync(outputDir)) {
166
+ fs.mkdirSync(outputDir, { recursive: true })
167
+ }
168
+
169
+ fs.writeFileSync(path.join(outputDir, 'index.html'), html)
170
+ console.log(`✅ Prerendered ${route.path}`)
171
+ }
172
+
173
+ // Generate sitemap in static/
174
+ if (config.experimental?.ssg?.sitemap?.enable) {
175
+ const baseURL = config.experimental.ssg.sitemap.baseURL
176
+ const sitemap = generateSitemap(
177
+ routes,
178
+ baseURL,
179
+ config.experimental.ssg.sitemap.settings,
180
+ )
181
+ fs.writeFileSync(path.join(buildDir, 'static', 'sitemap.xml'), sitemap)
182
+ console.log('✅ Sitemap created')
183
+
184
+ const robots = generateRobotsTxt(baseURL, config.experimental?.ssg?.robots)
185
+ fs.writeFileSync(path.join(buildDir, 'static', 'robots.txt'), robots)
186
+ console.log('✅ Robots.txt created')
187
+ }
188
+ }
189
+
190
+ export {
191
+ extractSSGRoutes,
192
+ generateSitemap,
193
+ generateRobotsTxt,
194
+ generateMetaTags,
195
+ prerenderRoute,
196
+ buildSSG,
197
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Webpack plugin to extract routes for SSG
3
+ */
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+
7
+ class RyunixRoutesPlugin {
8
+ constructor(options = {}) {
9
+ this.routesPath = options.routesPath || 'src/pages/routes.ryx'
10
+ this.outputPath = options.outputPath || '.ryunix/ssg/routes.json'
11
+ }
12
+
13
+ apply(compiler) {
14
+ compiler.hooks.emit.tapAsync(
15
+ 'RyunixRoutesPlugin',
16
+ (compilation, callback) => {
17
+ // Skip in development mode
18
+ if (compiler.options.mode !== 'production') {
19
+ callback()
20
+ return
21
+ }
22
+
23
+ const routesFile = path.resolve(process.cwd(), this.routesPath)
24
+
25
+ if (!fs.existsSync(routesFile)) {
26
+ console.log(
27
+ '[SSG] No routes file found, skipping manifest generation.',
28
+ )
29
+ callback()
30
+ return
31
+ }
32
+
33
+ try {
34
+ const content = fs.readFileSync(routesFile, 'utf-8')
35
+ console.log('📄 Routes file size:', content.length, 'bytes')
36
+
37
+ const routes = this.parseRoutes(content)
38
+ console.log('✅ Extracted routes:', JSON.stringify(routes, null, 2))
39
+
40
+ const manifest = JSON.stringify(routes, null, 2)
41
+ const outputPath = path.resolve(process.cwd(), this.outputPath)
42
+
43
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true })
44
+ fs.writeFileSync(outputPath, manifest)
45
+ } catch (error) {
46
+ console.error('❌ Error generating routes manifest:', error)
47
+ }
48
+
49
+ callback()
50
+ },
51
+ )
52
+ }
53
+
54
+ parseRoutes(content) {
55
+ const routes = []
56
+
57
+ // Match route objects more loosely
58
+ const routeRegex = /\{\s*path:\s*["']([^"']+)["'][\s\S]*?\}/g
59
+ let match
60
+
61
+ while ((match = routeRegex.exec(content)) !== null) {
62
+ const path = match[1]
63
+
64
+ // Skip dynamic and special routes
65
+ if (path.includes(':') || path === '*') continue
66
+
67
+ const route = { path }
68
+
69
+ // Get full route block (find next closing brace at same level)
70
+ const startIdx = match.index
71
+ let braceCount = 1
72
+ let endIdx = startIdx + 1
73
+
74
+ for (let i = startIdx + 1; i < content.length; i++) {
75
+ if (content[i] === '{') braceCount++
76
+ if (content[i] === '}') braceCount--
77
+ if (braceCount === 0) {
78
+ endIdx = i
79
+ break
80
+ }
81
+ }
82
+
83
+ const routeBlock = content.substring(startIdx, endIdx + 1)
84
+
85
+ // Extract meta with nested braces support
86
+ const metaMatch = routeBlock.match(/meta:\s*\{([\s\S]*?)\}(?:\s*,|\s*\})/)
87
+ if (metaMatch) {
88
+ route.meta = this.parseObject(metaMatch[1])
89
+ }
90
+
91
+ // Extract sitemap
92
+ const sitemapMatch = routeBlock.match(/sitemap:\s*\{([^}]+)\}/)
93
+ if (sitemapMatch) {
94
+ route.sitemap = this.parseObject(sitemapMatch[1])
95
+ }
96
+
97
+ routes.push(route)
98
+ }
99
+
100
+ return routes
101
+ }
102
+
103
+ parseObject(str) {
104
+ const obj = {}
105
+ const pairs = str.match(/["']?[\w:]+["']?\s*:\s*["'][^"']*["']/g) || []
106
+
107
+ pairs.forEach((pair) => {
108
+ const [key, value] = pair
109
+ .split(':')
110
+ .map((s) => s.trim().replace(/["']/g, ''))
111
+ obj[key] = value
112
+ })
113
+
114
+ return obj
115
+ }
116
+ }
117
+
118
+ export default RyunixRoutesPlugin
@@ -15,13 +15,14 @@ import {
15
15
  resolveApp,
16
16
  RYUNIX_APP,
17
17
  } from './utils/index.mjs'
18
+ import { getMDXLoaderConfig } from './utils/mdx.mjs'
18
19
  import fs from 'fs'
19
20
  import config from './utils/config.cjs'
20
21
  import Dotenv from 'dotenv-webpack'
21
22
  import { getPackageVersion } from './utils/index.mjs'
23
+ import RyunixRoutesPlugin from './utils/ssgPlugin.mjs'
22
24
 
23
25
  const __filename = fileURLToPath(import.meta.url)
24
-
25
26
  const __dirname = dirname(__filename)
26
27
 
27
28
  let dir
@@ -48,7 +49,6 @@ function getAlias(object) {
48
49
  const { version } = await getPackageVersion()
49
50
 
50
51
  export default {
51
- // context: src
52
52
  experiments: {
53
53
  lazyCompilation: config.webpack.experiments.lazyCompilation,
54
54
  },
@@ -56,7 +56,6 @@ export default {
56
56
  entry: './main.ryx',
57
57
  devtool: config.webpack.production ? 'source-map' : false,
58
58
  output: {
59
- // path: .ryunix
60
59
  path: resolveApp(dir, `${config.webpack.output.buildDirectory}/static`),
61
60
  publicPath: '/',
62
61
  chunkFilename: './assets/js/[name].[fullhash:8].bundle.js',
@@ -126,8 +125,9 @@ export default {
126
125
  stats: 'errors-warnings',
127
126
  module: {
128
127
  rules: [
128
+ // JavaScript/JSX/Ryunix files
129
129
  {
130
- test: /\.(js|jsx|ryx)$/,
130
+ test: /\.(js|jsx|ryx|mdx|md)$/,
131
131
  exclude: /node_modules/,
132
132
  use: [
133
133
  'thread-loader',
@@ -163,6 +163,11 @@ export default {
163
163
  },
164
164
  ],
165
165
  },
166
+ // MDX files (with JSX support)
167
+ getMDXLoaderConfig({
168
+ buildDirectory: config.webpack.output.buildDirectory,
169
+ }),
170
+ // CSS/SASS files
166
171
  {
167
172
  test: /\.s[ac]ss|css$/i,
168
173
  exclude: /node_modules/,
@@ -171,9 +176,9 @@ export default {
171
176
  ? MiniCssExtractPlugin.loader
172
177
  : 'style-loader',
173
178
  'css-loader',
174
-
175
179
  ],
176
180
  },
181
+ // Images
177
182
  {
178
183
  test: /\.(jpg|jpeg|png|gif|svg|ico)$/,
179
184
  exclude: /node_modules/,
@@ -182,6 +187,7 @@ export default {
182
187
  filename: 'assets/images/[name].[hash][ext]',
183
188
  },
184
189
  },
190
+ // Media files
185
191
  {
186
192
  test: /\.(mp3|mp4|pdf)$/,
187
193
  exclude: /node_modules/,
@@ -190,6 +196,7 @@ export default {
190
196
  filename: 'assets/files/[name].[hash][ext]',
191
197
  },
192
198
  },
199
+ // User-defined rules from config
193
200
  ...config.webpack.module.rules,
194
201
  ],
195
202
  },
@@ -201,6 +208,8 @@ export default {
201
208
  '.js',
202
209
  '.jsx',
203
210
  '.ryx',
211
+ '.mdx', // Add MDX extension
212
+ '.md', // Add MD extension
204
213
  ...config.webpack.resolve.extensions,
205
214
  ],
206
215
  fallback: config.webpack.resolve.fallback,
@@ -215,13 +224,20 @@ export default {
215
224
  systemvars: false,
216
225
  ignoreStub: true,
217
226
  }),
227
+ new RyunixRoutesPlugin({
228
+ routesPath: resolveApp(dir, `${config.webpack.root}/pages/routes.ryx`),
229
+ outputPath: resolveApp(
230
+ dir,
231
+ `${config.webpack.output.buildDirectory}/ssg/routes.json`,
232
+ ),
233
+ }),
218
234
  new webpack.DefinePlugin({
219
235
  'ryunix.config.env': JSON.stringify(config.experimental.env),
220
236
  }),
221
237
  new ESLintPlugin({
222
238
  cwd: dir,
223
239
  files: ['**/*.ryx', ...config.eslint.files],
224
- extensions: ['js', 'ryx', 'jsx'],
240
+ extensions: ['js', 'ryx', 'jsx', 'mdx'], // Add MDX to linting
225
241
  emitError: true,
226
242
  emitWarning: true,
227
243
  failOnWarning: false,
@@ -254,8 +270,6 @@ export default {
254
270
  {
255
271
  from: resolveApp(dir, 'public'),
256
272
  to: resolveApp(dir, `${config.webpack.output.buildDirectory}/static`),
257
- // Exclude any html files (index.html or others) to avoid duplicate emission
258
- // when HtmlWebpackPlugin also generates index.html from a template.
259
273
  globOptions: {
260
274
  ignore: [
261
275
  '**/template.html',