@unsetsoft/ryunix-presets 1.0.19 → 1.0.23-canary.1
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 +2 -2
- package/webpack/bin/index.mjs +4 -3
- package/webpack/bin/prerender.mjs +23 -114
- package/webpack/utils/index.mjs +7 -12
- package/webpack/utils/ssg.mjs +197 -0
- package/webpack/utils/ssgPlugin.mjs +118 -0
- package/webpack/webpack.config.mjs +8 -1
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.
|
|
4
|
+
"version": "1.0.23-canary.1",
|
|
5
5
|
"author": "Neyunse",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": "https://github.com/UnSetSoft/Ryunixjs",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"webpack/"
|
|
16
16
|
],
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@unsetsoft/ryunixjs": "^1.2.
|
|
18
|
+
"@unsetsoft/ryunixjs": "^1.2.2"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"registry": "https://registry.npmjs.org"
|
package/webpack/bin/index.mjs
CHANGED
|
@@ -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 (
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
12
|
-
const buildDirectory = resolveApp(process.cwd(),
|
|
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
|
|
19
|
+
const manifestPath = path.join(process.cwd(), directory, 'ssg', 'routes.json')
|
|
20
|
+
let routes = []
|
|
68
21
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
package/webpack/utils/index.mjs
CHANGED
|
@@ -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
|
|
63
|
-
const
|
|
64
|
-
|
|
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,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
|
|
@@ -19,6 +19,7 @@ import fs from 'fs'
|
|
|
19
19
|
import config from './utils/config.cjs'
|
|
20
20
|
import Dotenv from 'dotenv-webpack'
|
|
21
21
|
import { getPackageVersion } from './utils/index.mjs'
|
|
22
|
+
import RyunixRoutesPlugin from './utils/ssgPlugin.mjs'
|
|
22
23
|
|
|
23
24
|
const __filename = fileURLToPath(import.meta.url)
|
|
24
25
|
|
|
@@ -171,7 +172,6 @@ export default {
|
|
|
171
172
|
? MiniCssExtractPlugin.loader
|
|
172
173
|
: 'style-loader',
|
|
173
174
|
'css-loader',
|
|
174
|
-
'sass-loader',
|
|
175
175
|
],
|
|
176
176
|
},
|
|
177
177
|
{
|
|
@@ -215,6 +215,13 @@ export default {
|
|
|
215
215
|
systemvars: false,
|
|
216
216
|
ignoreStub: true,
|
|
217
217
|
}),
|
|
218
|
+
new RyunixRoutesPlugin({
|
|
219
|
+
routesPath: resolveApp(dir, `${config.webpack.root}/pages/routes.ryx`),
|
|
220
|
+
outputPath: resolveApp(
|
|
221
|
+
dir,
|
|
222
|
+
`${config.webpack.output.buildDirectory}/ssg/routes.json`,
|
|
223
|
+
),
|
|
224
|
+
}),
|
|
218
225
|
new webpack.DefinePlugin({
|
|
219
226
|
'ryunix.config.env': JSON.stringify(config.experimental.env),
|
|
220
227
|
}),
|