diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..025dd8c8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..af3ad128 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/CHANGELOG.md b/CHANGELOG.md index b1d5dff0..64455906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,173 @@ # Changelog +### [1.2.1](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.74...v1.2.1) (2025-07-25) + +## [1.2.0-canary.74](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.73...v1.2.0-canary.74) (2025-07-25) + +## [1.2.0-canary.73](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.72...v1.2.0-canary.73) (2025-07-25) + +## [1.2.0-canary.72](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.71...v1.2.0-canary.72) (2025-07-25) + +### Bug Fixes + +- change vercel yarn to prevent babel-loader error ([0bc6716](https://github.com/UnSetSoft/Ryunixjs/commit/0bc671646b2681bf45e336577309ac0b820b0a8f)) + +## [1.2.0-canary.71](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.70...v1.2.0-canary.71) (2025-07-25) + +## [1.2.0-canary.70](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.69...v1.2.0-canary.70) (2025-07-25) + +## [1.2.0-canary.69](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.68...v1.2.0-canary.69) (2025-07-25) + +## [1.2.0-canary.68](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.67...v1.2.0-canary.68) (2025-07-25) + +## [1.2.0-canary.67](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.66...v1.2.0-canary.67) (2025-07-25) + +## [1.2.0-canary.66](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.65...v1.2.0-canary.66) (2025-07-25) + +## [1.2.0-canary.65](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.64...v1.2.0-canary.65) (2025-07-25) + +## [1.2.0-canary.64](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.63...v1.2.0-canary.64) (2025-07-25) + +## [1.2.0-canary.63](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.62...v1.2.0-canary.63) (2025-07-25) + +## [1.2.0-canary.62](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.61...v1.2.0-canary.62) (2025-07-25) + +## [1.2.0-canary.61](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.60...v1.2.0-canary.61) (2025-07-25) + +## [1.2.0-canary.60](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.59...v1.2.0-canary.60) (2025-07-25) + +## [1.2.0-canary.59](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.58...v1.2.0-canary.59) (2025-07-25) + +## [1.2.0-canary.58](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.57...v1.2.0-canary.58) (2025-07-25) + +## [1.2.0-canary.57](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.56...v1.2.0-canary.57) (2025-07-25) + +## [1.2.0-canary.56](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.55...v1.2.0-canary.56) (2025-07-25) + +## [1.2.0-canary.55](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.54...v1.2.0-canary.55) (2025-07-25) + +## [1.2.0-canary.54](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.53...v1.2.0-canary.54) (2025-07-25) + +## [1.2.0-canary.53](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.52...v1.2.0-canary.53) (2025-07-25) + +## [1.2.0-canary.52](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.51...v1.2.0-canary.52) (2025-07-25) + +## [1.2.0-canary.51](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.50...v1.2.0-canary.51) (2025-07-25) + +## [1.2.0-canary.50](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.49...v1.2.0-canary.50) (2025-07-25) + +## [1.2.0-canary.49](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.48...v1.2.0-canary.49) (2025-07-25) + +## [1.2.0-canary.48](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.47...v1.2.0-canary.48) (2025-07-25) + +## [1.2.0-canary.47](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.46...v1.2.0-canary.47) (2025-07-25) + +## [1.2.0-canary.46](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.45...v1.2.0-canary.46) (2025-07-25) + +## [1.2.0-canary.45](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.44...v1.2.0-canary.45) (2025-07-25) + +## [1.2.0-canary.44](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.43...v1.2.0-canary.44) (2025-07-25) + +## [1.2.0-canary.43](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.42...v1.2.0-canary.43) (2025-07-25) + +## [1.2.0-canary.42](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.41...v1.2.0-canary.42) (2025-07-25) + +## [1.2.0-canary.41](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.40...v1.2.0-canary.41) (2025-07-25) + +## [1.2.0-canary.40](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.39...v1.2.0-canary.40) (2025-07-25) + +## [1.2.0-canary.39](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.38...v1.2.0-canary.39) (2025-07-25) + +## [1.2.0-canary.38](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.37...v1.2.0-canary.38) (2025-07-25) + +## [1.2.0-canary.37](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.36...v1.2.0-canary.37) (2025-07-25) + +## [1.2.0-canary.36](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.35...v1.2.0-canary.36) (2025-07-25) + +## [1.2.0-canary.35](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.34...v1.2.0-canary.35) (2025-07-25) + +## [1.2.0-canary.34](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.33...v1.2.0-canary.34) (2025-07-25) + +## [1.2.0-canary.33](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.32...v1.2.0-canary.33) (2025-06-26) + +## [1.2.0-canary.32](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.31...v1.2.0-canary.32) (2025-06-26) + +### Bug Fixes + +- update version to 1.2.0-canary.31 and add sitemap generation in prerender ([506d470](https://github.com/UnSetSoft/Ryunixjs/commit/506d470f2cae8fec2454ad56f70efde1b8820fb5)) + +## [1.2.0-canary.31](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.30...v1.2.0-canary.31) (2025-06-26) + +## [1.2.0-canary.30](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.29...v1.2.0-canary.30) (2025-06-26) + +## [1.2.0-canary.29](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.28...v1.2.0-canary.29) (2025-06-26) + +## [1.2.0-canary.28](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.27...v1.2.0-canary.28) (2025-06-26) + +### Bug Fixes + +- Upgrades version to 1.2.0-canary.27 and improves the cleanup of the build directory based on the prerender configuration. ([fbbcc92](https://github.com/UnSetSoft/Ryunixjs/commit/fbbcc9222cb3b86a38a79b4df3fdf2fb3b6370f9)) + +## [1.2.0-canary.27](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.26...v1.2.0-canary.27) (2025-06-25) + +## [1.2.0-canary.26](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.25...v1.2.0-canary.26) (2025-06-25) + +## [1.2.0-canary.25](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.24...v1.2.0-canary.25) (2025-06-25) + +## [1.2.0-canary.24](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.23...v1.2.0-canary.24) (2025-06-25) + +## [1.2.0-canary.23](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.22...v1.2.0-canary.23) (2025-06-25) + +## [1.2.0-canary.22](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.21...v1.2.0-canary.22) (2025-06-25) + +## [1.2.0-canary.21](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.20...v1.2.0-canary.21) (2025-06-25) + +## [1.2.0-canary.20](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.19...v1.2.0-canary.20) (2025-06-25) + +## [1.2.0-canary.19](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.18...v1.2.0-canary.19) (2025-06-25) + +## [1.2.0-canary.18](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.17...v1.2.0-canary.18) (2025-06-25) + +## [1.2.0-canary.17](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.16...v1.2.0-canary.17) (2025-06-25) + +## [1.2.0-canary.16](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.15...v1.2.0-canary.16) (2025-06-25) + +## [1.2.0-canary.15](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.14...v1.2.0-canary.15) (2025-06-25) + +## [1.2.0-canary.14](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.13...v1.2.0-canary.14) (2025-06-25) + +## [1.2.0-canary.13](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.12...v1.2.0-canary.13) (2025-06-25) + +## [1.2.0-canary.12](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.11...v1.2.0-canary.12) (2025-06-25) + +## [1.2.0-canary.11](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.10...v1.2.0-canary.11) (2025-06-25) + +## [1.2.0-canary.10](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.9...v1.2.0-canary.10) (2025-06-25) + +## [1.2.0-canary.9](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.8...v1.2.0-canary.9) (2025-06-25) + +## [1.2.0-canary.8](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.7...v1.2.0-canary.8) (2025-06-25) + +## [1.2.0-canary.7](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.6...v1.2.0-canary.7) (2025-06-25) + +## [1.2.0-canary.6](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.5...v1.2.0-canary.6) (2025-06-25) + +## [1.2.0-canary.5](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.4...v1.2.0-canary.5) (2025-06-25) + +## [1.2.0-canary.4](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.3...v1.2.0-canary.4) (2025-06-25) + +## [1.2.0-canary.3](https://github.com/UnSetSoft/Ryunixjs/compare/v1.2.0-canary.2...v1.2.0-canary.3) (2025-06-25) + +## [1.2.0-canary.2](https://github.com/UnSetSoft/Ryunixjs/compare/v1.1.37-canary.10...v1.2.0-canary.2) (2025-06-25) + +### Features + +- update changelog and package versions, improve configuration and add hook useMetadata for SEO ([fac9daa](https://github.com/UnSetSoft/Ryunixjs/commit/fac9daa1fda2fbd0db7f1982743a8154d9db3f10)) + +### [1.1.21](https://github.com/UnSetSoft/Ryunixjs/compare/v1.1.20-canary.14...v1.1.21) (2025-06-11) + +### [1.1.20](https://github.com/UnSetSoft/Ryunixjs/compare/v1.1.19-canary.35...v1.1.20) (2025-06-05) + ### [1.1.38](https://github.com/UnSetSoft/Ryunixjs/compare/v1.1.37-canary.10...v1.1.38) (2025-06-24) ### [1.1.37-canary.10](https://github.com/UnSetSoft/Ryunixjs/compare/v1.1.37-canary.9...v1.1.37-canary.10) (2025-06-24) diff --git a/package.json b/package.json index 23033275..540b76bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "RyunixJS", - "version": "1.1.38", + "version": "1.2.0-canary.74", "private": true, "homepage": "./", "workspaces": [ @@ -8,8 +8,8 @@ ], "license": "MIT", "scripts": { - "lint-eslint": "eslint . --max-warnings=0 --config eslint.config.mjs ", - "lint-fix": "yarn prettier-fix && eslint . --fix --max-warnings=0 --config eslint.config.mjs ", + "lint": "eslint . --max-warnings=0 --config eslint.config.mjs ", + "lint:fix": "yarn prettier-fix && eslint . --fix --max-warnings=0 --config eslint.config.mjs ", "prettier-check": "prettier -c .", "prettier-fix": "prettier -w .", "kg:init": "gmvu init", @@ -30,7 +30,7 @@ "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-transform-react-jsx": "7.27.1", "@eslint/eslintrc": "3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.31.0", "@kagarisoft/gmvu-cli": "1.0.5", "@swc/core": "1.12.6", "@typescript-eslint/eslint-plugin": "8.35.0", @@ -43,9 +43,11 @@ "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.2", "dotenv-webpack": "8.1.0", - "eslint": "9.29.0", + "eslint": "9.31.0", "eslint-plugin-eslint-plugin": "6.5.0", "eslint-plugin-import": "2.32.0", + "eslint-plugin-react": "^7.37.5", + "eslint-webpack-plugin": "5.0.2", "file-loader": "6.2.0", "glob": "11.0.3", "globals": "16.2.0", @@ -79,5 +81,5 @@ "standard-version": "9.5.0", "typescript": "5.8.3" }, - "packageManager": "yarn@4.9.1" + "packageManager": "yarn@4.9.2" } diff --git a/packages/cra/package.json b/packages/cra/package.json index 492caddc..d1568e81 100644 --- a/packages/cra/package.json +++ b/packages/cra/package.json @@ -1,7 +1,7 @@ { "name": "@unsetsoft/cra", "description": "Create Ryunix app", - "version": "1.1.16", + "version": "1.1.17", "author": "Neyunse", "repository": "https://github.com/UnSetSoft/Ryunixjs", "license": "MIT", diff --git a/packages/cra/templates/Rspack/package.json b/packages/cra/templates/Rspack/package.json index 041dba6b..1027992b 100644 --- a/packages/cra/templates/Rspack/package.json +++ b/packages/cra/templates/Rspack/package.json @@ -8,6 +8,6 @@ "build": "cross-env NODE_ENV=production rspack build" }, "engines": { - "node": "^18 || ^22" + "node": "^20 || ^22" } } diff --git a/packages/cra/templates/Vite/package.json b/packages/cra/templates/Vite/package.json index 6bc246b1..65714b38 100644 --- a/packages/cra/templates/Vite/package.json +++ b/packages/cra/templates/Vite/package.json @@ -11,6 +11,6 @@ "preview": "vite preview" }, "engines": { - "node": "^18 || ^22" + "node": "^20 || ^22" } } diff --git a/packages/cra/templates/Webpack/package.json b/packages/cra/templates/Webpack/package.json index 4aa6652a..344c03af 100644 --- a/packages/cra/templates/Webpack/package.json +++ b/packages/cra/templates/Webpack/package.json @@ -5,9 +5,11 @@ "homepage": "./", "scripts": { "dev": "ryunix server", - "build": "ryunix build" + "build": "ryunix build", + "lint": "ryunix lint", + "lint:fix": "ryunix lint --fix" }, "engines": { - "node": "^18 || ^22" + "node": "^20 || ^22" } } diff --git a/packages/cra/templates/Webpack/vercel.json b/packages/cra/templates/Webpack/vercel.json index 371f9f04..8fd93eef 100644 --- a/packages/cra/templates/Webpack/vercel.json +++ b/packages/cra/templates/Webpack/vercel.json @@ -1,8 +1,8 @@ { "outputDirectory": ".ryunix/static", - "buildCommand": "yarn build", - "devCommand": "yarn dev", - "installCommand": "yarn install", + "buildCommand": "npm run build", + "devCommand": "npm run dev", + "installCommand": "npm install", "framework": null, "rewrites": [ { diff --git a/packages/ryunix-presets/package.json b/packages/ryunix-presets/package.json index dadd9533..1dfab51d 100644 --- a/packages/ryunix-presets/package.json +++ b/packages/ryunix-presets/package.json @@ -21,6 +21,6 @@ "./rspack": "./rspack/index.js" }, "engines": { - "node": "^18 || ^22" + "node": "^20 || ^22" } } diff --git a/packages/ryunix-webpack/bin/index.mjs b/packages/ryunix-webpack/bin/index.mjs index a9674667..8a8e083f 100755 --- a/packages/ryunix-webpack/bin/index.mjs +++ b/packages/ryunix-webpack/bin/index.mjs @@ -6,6 +6,45 @@ import { compiler } from './compiler.mjs' import logger from 'terminal-log' import chalk from 'chalk' import defaultSettings from '../utils/config.cjs' +import Prerender from './prerender.mjs' +import { + cleanBuildDirectory, + convertFlatToClassic, + resolveApp, +} from '../utils/index.mjs' +import { ESLint } from 'eslint' +import eslintConfig from '../eslint.config.mjs' +const lint = { + command: 'lint', + describe: 'Lint code', + builder: { + fix: { + alias: 'f', + type: 'boolean', + default: false, + describe: 'Automatically fix problems', + }, + }, + handler: async (arg) => { + const classicConfig = eslintConfig[0] + + const fix = arg.fix + const eslint = new ESLint({ + cwd: process.cwd(), + overrideConfigFile: true, + overrideConfig: classicConfig, + fix, + }) + + const results = await eslint.lintFiles(defaultSettings.eslint.files) + + await ESLint.outputFixes(results) + + const formatter = await eslint.loadFormatter('stylish') + const report = formatter.format(results) + console.log(report) + }, +} const serv = { command: 'server', @@ -33,7 +72,16 @@ const build = { return } - compiler.run((err, stats) => { + if (defaultSettings.experimental.ssg.prerender.length > 0) { + await cleanBuildDirectory( + resolveApp( + process.cwd(), + `${defaultSettings.webpack.output.buildDirectory}/static`, + ), + ) + } + + compiler.run(async (err, stats) => { if (err || stats.hasErrors()) { logger.error(chalk.red('Error during compilation:')) logger.error(err || stats.toString('errors-only')) @@ -48,6 +96,10 @@ const build = { const formattedTime = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s` + if (defaultSettings.experimental.ssg.prerender.length > 0) { + await Prerender() + } + logger.info(chalk.green('Compilation successful! 🎉')) logger.info(`Done in ${formattedTime}`) @@ -60,4 +112,4 @@ const build = { }, } -yargs(hideBin(process.argv)).command(serv).command(build).parse() +yargs(hideBin(process.argv)).command(serv).command(build).command(lint).parse() diff --git a/packages/ryunix-webpack/bin/prerender.mjs b/packages/ryunix-webpack/bin/prerender.mjs new file mode 100644 index 00000000..b8886410 --- /dev/null +++ b/packages/ryunix-webpack/bin/prerender.mjs @@ -0,0 +1,141 @@ +/** + * prerender metadata + * + */ +import { configFileExist } from '../utils/settingfile.cjs' +import defaultSettings from '../utils/config.cjs' +import { resolveApp } from '../utils/index.mjs' +import fs from 'fs' +import path from 'path' + +// proyect/.ryunix/static +const buildDirectory = resolveApp(process.cwd(), '.ryunix/static') +const indexFile = path.join(buildDirectory, 'index.html') + +const siteMap = async (routes) => { + if (!defaultSettings.experimental.ssg.sitemap.baseURL) { + console.error( + '❌ Base URL is not defined in the configuration file. Please set `experimental.ssg.sitemap.baseURL`.', + ) + process.exit(1) + } + + const sitemap = ` + +${routes + .map((route) => { + const url = `${defaultSettings.experimental.ssg.sitemap.baseURL}${route.path === '/' ? '' : route.path}` + const meta = route.meta || {} + const sitemap_settings = route.sitemap || {} + const lastmod = meta.lastmod || new Date().toISOString().split('T')[0] + + // sitemap settings + const changefreq = + sitemap_settings.changefreq || + meta.changefreq || // TODO: Remove meta.changefreq + defaultSettings.experimental.ssg.sitemap.settings.changefreq + const priority = + sitemap_settings.priority || + meta.priority || // TODO: Remove meta.priority + defaultSettings.experimental.ssg.sitemap.settings.priority + + return ` + ${url} + ${lastmod} + ${changefreq} + ${priority} +` + }) + .join('\n')} +` + + await fs.writeFileSync( + path.resolve(buildDirectory, 'sitemap.xml'), + sitemap, + 'utf-8', + ) + + console.log('✅ Sitemap created') +} + +const Prerender = async () => { + if (!configFileExist()) { + console.error('❌ No configuration file found.') + process.exit(1) + } + + const template = fs.readFileSync(indexFile, 'utf-8') + + for (const route of defaultSettings.experimental.ssg.prerender) { + if (route.path === '/') continue // Exclude root path, handled by defaultSettings.static.seo.meta + let html = template + const meta = route?.meta || defaultSettings.static.seo.meta + + // title + if (meta.title) { + html = html.replace(/.*<\/title>/, `<title>${meta.title}`) + } + + // Helper to add or replace metatag + function upsertMetaTag(html, name, value) { + if (value === undefined || value === null) return html + + const isProperty = name.startsWith('og:') || name.startsWith('twitter:') + const attr = isProperty ? 'property' : 'name' + const regex = new RegExp( + ``, + 'i', + ) + const tag = `` + + if (regex.test(html)) { + return html.replace(regex, tag) + } else { + return html.replace(/<\/head>/i, `${tag}\n`) + } + } + + // Description + html = upsertMetaTag(html, 'description', meta.description) + // Keywords + html = upsertMetaTag(html, 'keywords', meta.keywords) + + // dynamic metatags (excluding title, description, keywords, framework, mode) + for (const [key, value] of Object.entries(meta)) { + if ( + [ + 'title', + 'description', + 'keywords', + 'framework', + 'mode', + 'viewport', + ].includes(key) + ) + continue + html = upsertMetaTag(html, key, value) + } + + const outputDir = + route.path === '/' + ? buildDirectory + : path.join(buildDirectory, route.path) + + if (!fs.existsSync(outputDir)) { + await fs.mkdirSync(outputDir, { recursive: true, force: true }) + } + + await fs.writeFileSync(path.join(outputDir, 'index.html'), html) + console.log(`✅ Prerendered ${route.path}`) + } + + // need base url and enable + if ( + defaultSettings.experimental.ssg.sitemap.enable && + defaultSettings.experimental.ssg.sitemap.baseURL + ) { + await siteMap(defaultSettings.experimental.ssg.prerender) + } +} + +export default Prerender diff --git a/packages/ryunix-webpack/eslint.config.mjs b/packages/ryunix-webpack/eslint.config.mjs index 982ce991..030eefa4 100644 --- a/packages/ryunix-webpack/eslint.config.mjs +++ b/packages/ryunix-webpack/eslint.config.mjs @@ -1,24 +1,11 @@ -import globals from 'globals' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import js from '@eslint/js' -import { FlatCompat } from '@eslint/eslintrc' import config from './utils/config.cjs' +import { resolveApp } from './utils/index.mjs' +import { defineConfig } from 'eslint/config' +const dir = process.cwd() -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const dir = path.dirname(path.join(__dirname, '..', '..')) -const compat = new FlatCompat({ - baseDirectory: dir, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}) - -export default [ - ...compat.extends('eslint:recommended'), +const eslintConfig = defineConfig([ { files: ['**/*.ryx', ...config?.eslint?.files], - languageOptions: { ecmaVersion: 2021, sourceType: 'module', @@ -27,29 +14,18 @@ export default [ ecmaFeatures: { jsx: true, }, + extraFileExtensions: ['.ryx'], }, }, - plugins: config?.eslint?.plugins, - rules: config?.eslint?.rules, - }, - ...compat.extends('eslint:recommended').map((config) => ({ - ...config, - files: ['**/*.ryx'], - })), - { - files: ['**/*.ryx', ...config?.eslint?.files], - - languageOptions: { - ecmaVersion: 2021, - sourceType: 'module', - - parserOptions: { - ecmaFeatures: { - jsx: true, - }, + settings: { + react: { + pragma: 'Ryunix.createElement', // Para JSX transpile a Ryunix.createElement + fragment: 'Ryunix.Fragment', // Para fragmentos JSX }, }, plugins: config?.eslint?.plugins, rules: config?.eslint?.rules, }, -] +]) + +export default eslintConfig diff --git a/packages/ryunix-webpack/package.json b/packages/ryunix-webpack/package.json index c96d1745..befecdce 100644 --- a/packages/ryunix-webpack/package.json +++ b/packages/ryunix-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@unsetsoft/ryunix-webpack", - "version": "1.1.38", + "version": "1.2.1", "license": "MIT", "bugs": { "url": "https://github.com/UnSetSoft/Ryunixjs/issues" @@ -22,7 +22,7 @@ "@babel/preset-env": "7.27.2", "@babel/preset-react": "7.27.1", "@eslint/eslintrc": "3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.31.0", "@rollup/plugin-terser": "0.4.4", "@swc/core": "1.12.6", "babel-loader": "10.0.0", @@ -31,7 +31,9 @@ "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.2", "dotenv-webpack": "8.1.0", - "eslint": "9.29.0", + "eslint": "9.31.0", + "eslint-plugin-react": "^7.37.5", + "eslint-webpack-plugin": "5.0.2", "file-loader": "6.2.0", "glob": "11.0.3", "globals": "16.2.0", diff --git a/packages/ryunix-webpack/template/index.html b/packages/ryunix-webpack/template/index.html index 43ab7f6e..823d6ece 100644 --- a/packages/ryunix-webpack/template/index.html +++ b/packages/ryunix-webpack/template/index.html @@ -9,7 +9,7 @@ version="<%= htmlWebpackPlugin.options.info.version %>" /> - + <%= htmlWebpackPlugin.options.title %> diff --git a/packages/ryunix-webpack/utils/config.cjs b/packages/ryunix-webpack/utils/config.cjs index e74a915c..03d4f145 100644 --- a/packages/ryunix-webpack/utils/config.cjs +++ b/packages/ryunix-webpack/utils/config.cjs @@ -1,14 +1,53 @@ 'use strict' const { getConfig } = require('./settingfile.cjs') - +const reactPlugin = require('eslint-plugin-react') const config = getConfig() const defaultSettings = { + experimental: { + ssg: { + sitemap: { + // allow sitemap generation + enable: config?.experimental?.ssg?.sitemap?.enable + ? config.experimental.ssg.sitemap.enable + : false, + // base url. ex: https://example.com + baseURL: config?.experimental?.ssg?.sitemap?.baseURL + ? config.experimental.ssg.sitemap.baseURL + : false, + // global settings for the sitemap. + settings: config?.experimental?.ssg?.sitemap?.settings + ? { + changefreq: config.experimental.ssg.sitemap.settings.changefreq + ? config.experimental.ssg.sitemap.settings.changefreq + : 'weekly', + priority: config.experimental.ssg.sitemap.settings.priority + ? config.experimental.ssg.sitemap.settings.priority + : '0.7', + } + : { + // default values + changefreq: 'weekly', + priority: '0.7', + }, + }, + // prerendered pages + // TODO: better form to import from routing system. but for now is the best method. + // NEXT: rename this! :) + prerender: config?.experimental?.ssg?.prerender + ? config.experimental.ssg.prerender + : [], + }, + env: config?.experimental?.env ? config.experimental.env : {}, + }, static: { favicon: config?.static?.favicon ? config.static.favicon : false, + // if is true, public/index.html are required. customTemplate: config?.static?.customTemplate ? config.static.customTemplate : false, + + // global SEO. seo: { pageLang: config?.static?.seo?.pageLang ? config.static.seo.pageLang @@ -18,18 +57,93 @@ const defaultSettings = { : 'Ryunix App', meta: config?.static?.seo?.meta ? config.static.seo.meta : {}, }, + ssg: {}, // TODO: Move experimental.ssg here. }, eslint: { files: config?.eslint?.files ? config.eslint.files : ['**/*.ryx'], - plugins: config?.eslint?.plugins ? config.eslint.plugins : {}, + plugins: config?.eslint?.plugins + ? { + react: reactPlugin, + ...config.eslint.plugins, + } + : { + react: reactPlugin, + }, rules: config?.eslint?.rules - ? config.eslint.rules + ? { + 'max-len': config.eslint.rules['max-len'] + ? config.eslint.rules['max-len'] + : ['error', { code: 400 }], + camelcase: config.eslint.rules.camelcase + ? config.eslint.rules.camelcase + : 'off', + 'no-unused-vars': config.eslint.rules['no-unused-vars'] + ? config.eslint.rules['no-unused-vars'] + : 'warn', + 'no-console': config.eslint.rules['no-console'] + ? config.eslint.rules['no-console'] + : 'off', + 'no-underscore-dangle': config.eslint.rules['no-underscore-dangle'] + ? config.eslint.rules['no-underscore-dangle'] + : ['error', { allow: ['id_', '_id'] }], + 'arrow-body-style': config.eslint.rules['arrow-body-style'] + ? config.eslint.rules['arrow-body-style'] + : 'off', + indent: config.eslint.rules.indent + ? config.eslint.rules.indent + : 'warn', + 'consistent-return': config.eslint.rules['consistent-return'] + ? config.eslint.rules['consistent-return'] + : 'off', + 'no-else-return': config.eslint.rules['no-else-return'] + ? config.eslint.rules['no-else-return'] + : 'off', + 'global-require': config.eslint.rules['global-require'] + ? config.eslint.rules['global-require'] + : 'off', + 'no-param-reassign': config.eslint.rules['no-param-reassign'] + ? config.eslint.rules['no-param-reassign'] + : ['error', { props: false }], + 'new-cap': config.eslint.rules['new-cap'] + ? config.eslint.rules['new-cap'] + : 'off', + 'arrow-parens': config.eslint.rules['arrow-parens'] + ? config.eslint.rules['arrow-parens'] + : 'off', + 'prefer-destructuring': config.eslint.rules['prefer-destructuring'] + ? config.eslint.rules['prefer-destructuring'] + : 'warn', + 'no-nested-ternary': config.eslint.rules['no-nested-ternary'] + ? config.eslint.rules['no-nested-ternary'] + : 'off', + 'react/jsx-uses-vars': config.eslint.rules['react/jsx-uses-vars'] + ? config.eslint.rules['react/jsx-uses-vars'] + : 'warn', + // by default only this + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', + ...config.eslint.rules, + } : { - 'no-unused-vars': 'off', - indent: ['error', 2], - 'linebreak-style': 1, - quotes: ['error', 'double'], + 'max-len': ['error', { code: 400 }], + camelcase: 'off', + 'no-unused-vars': 'warn', + 'no-console': 'off', + 'no-underscore-dangle': ['error', { allow: ['id_', '_id'] }], + 'arrow-body-style': 'off', + indent: 'warn', + 'consistent-return': 'off', + 'no-else-return': 'off', + 'global-require': 'off', + 'no-param-reassign': ['error', { props: false }], + 'new-cap': 'off', + 'arrow-parens': 'off', + 'prefer-destructuring': 'warn', + 'no-nested-ternary': 'off', + 'react/jsx-uses-vars': 'warn', + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', }, }, diff --git a/packages/ryunix-webpack/utils/index.mjs b/packages/ryunix-webpack/utils/index.mjs index 5e3e7ff2..fe451611 100644 --- a/packages/ryunix-webpack/utils/index.mjs +++ b/packages/ryunix-webpack/utils/index.mjs @@ -83,6 +83,114 @@ async function cleanCacheDir(dirPath) { } } +async function cleanBuildDirectory(dirPath) { + try { + await fs.access(dirPath) + await fs.rm(dirPath, { recursive: true, force: true }) + logger.info( + `static folder cleaned ${chalk.bold(chalk.green('successfully'))}`, + ) + } catch (err) { + // Directory does not exist or some error occurred + + if (err.code === 'ENOENT') { + logger.info(`static folder cleaned ${chalk.red('failed')}`) + } else { + throw err // or handle error accordingly + } + } +} + +// utils/convertFlatToClassic.js +export function convertFlatToClassic(configArray) { + const combined = { + env: {}, + globals: {}, + parserOptions: {}, + plugins: new Set(), + extends: new Set(), + rules: {}, + parser: undefined, + } + + const invalidKeys = new Set() + + for (const cfg of configArray) { + // Detectar keys inválidas + for (const key of Object.keys(cfg)) { + if ( + ![ + 'env', + 'globals', + 'parserOptions', + 'plugins', + 'extends', + 'rules', + 'parser', + 'languageOptions', + ].includes(key) + ) { + invalidKeys.add(key) + } + } + + // Combinar env + if (cfg.env) Object.assign(combined.env, cfg.env) + + // Combinar globals + if (cfg.globals) Object.assign(combined.globals, cfg.globals) + + // Combinar parserOptions + if (cfg.languageOptions?.parserOptions) { + Object.assign(combined.parserOptions, cfg.languageOptions.parserOptions) + } else if (cfg.parserOptions) { + Object.assign(combined.parserOptions, cfg.parserOptions) + } + + // Combinar plugins + if (cfg.plugins) { + if (Array.isArray(cfg.plugins)) { + cfg.plugins.forEach((p) => combined.plugins.add(p)) + } else if (typeof cfg.plugins === 'object' && cfg.plugins !== null) { + Object.keys(cfg.plugins).forEach((p) => combined.plugins.add(p)) + } + } + + // Combinar extends + if (cfg.extends) { + if (Array.isArray(cfg.extends)) { + cfg.extends.forEach((e) => combined.extends.add(e)) + } else if (typeof cfg.extends === 'string') { + combined.extends.add(cfg.extends) + } + } + + // Combinar reglas + if (cfg.rules) Object.assign(combined.rules, cfg.rules) + + // Parser + if (cfg.languageOptions?.parser) { + combined.parser = cfg.languageOptions.parser + } else if (cfg.parser) { + combined.parser = cfg.parser + } + } + + return { + env: Object.keys(combined.env).length ? combined.env : undefined, + globals: Object.keys(combined.globals).length + ? combined.globals + : undefined, + parserOptions: Object.keys(combined.parserOptions).length + ? combined.parserOptions + : undefined, + plugins: combined.plugins.size ? Array.from(combined.plugins) : undefined, + extends: combined.extends.size ? Array.from(combined.extends) : undefined, + rules: combined.rules, + parser: combined.parser, + } +} + export { getPackageManager, ENV_HASH, @@ -91,4 +199,5 @@ export { RYUNIX_APP, getPackageVersion, cleanCacheDir, + cleanBuildDirectory, } diff --git a/packages/ryunix-webpack/webpack.config.mjs b/packages/ryunix-webpack/webpack.config.mjs index 2bdccf75..eb8a3595 100644 --- a/packages/ryunix-webpack/webpack.config.mjs +++ b/packages/ryunix-webpack/webpack.config.mjs @@ -6,7 +6,8 @@ import webpack from 'webpack' import CssMinimizerPlugin from 'css-minimizer-webpack-plugin' import MiniCssExtractPlugin from 'mini-css-extract-plugin' import CopyWebpackPlugin from 'copy-webpack-plugin' - +import ESLintPlugin from 'eslint-webpack-plugin' +import eslintConfig from './eslint.config.mjs' import { getPackageManager, ENV_HASH, @@ -62,7 +63,7 @@ export default { assetModuleFilename: './assets/media/[name].[hash][ext]', filename: './assets/js/[name].[fullhash:8].bundle.js', devtoolModuleFilenameTemplate: 'ryunix/[resource-path]', - clean: true, + clean: config.experimental.ssg.prerender.length > 0 ? false : true, }, target: config.webpack.target, devServer: { @@ -211,7 +212,23 @@ export default { new Dotenv({ path: resolveApp(dir, '.env'), prefix: 'ryunix.env.RYUNIX_APP_', + systemvars: false, + ignoreStub: true, }), + new webpack.DefinePlugin({ + 'ryunix.config.env': JSON.stringify(config.experimental.env), + }), + new ESLintPlugin({ + cwd: dir, + files: ['**/*.ryx', ...config.eslint.files], + extensions: ['js', 'ryx', 'jsx'], + emitError: true, + emitWarning: true, + failOnWarning: false, + failOnError: false, + overrideConfigFile: true, + overrideConfig: eslintConfig[0], + }), new HtmlWebpackPlugin({ pageLang: config.static.seo.pageLang, title: config.static.seo.title, diff --git a/packages/ryunix/package.json b/packages/ryunix/package.json index f3d6529c..9f0c7c43 100644 --- a/packages/ryunix/package.json +++ b/packages/ryunix/package.json @@ -1,6 +1,6 @@ { "name": "@unsetsoft/ryunixjs", - "version": "1.1.38", + "version": "1.2.1", "license": "MIT", "main": "./dist/Ryunix.min.js", "types": "./dist/Ryunix.d.ts", @@ -22,7 +22,7 @@ }, "dependencies": { "@rollup/plugin-terser": "0.4.4", - "eslint": "9.29.0", + "eslint": "9.31.0", "lodash": "^4.17.21", "rollup": "^4.41.0" },