diff --git a/commands/o2oa-component-x.js b/commands/o2oa-component-x.js index 76710f8..031239d 100644 --- a/commands/o2oa-component-x.js +++ b/commands/o2oa-component-x.js @@ -99,6 +99,63 @@ class componentFactory{ chalk.cyan(` ${chalk.gray('$')} ${'npm run dev'}`) ); } + + static async vue3_rsbuild(name, opts) { + const componentPath = 'x_component_'+name.replace(/\./g, '_'); + const templatePath = path.resolve(__dirname, options["vue3 rsbuild"]); + + if (existsSync(componentPath)){ + console.log(); + console.log(`👉 `+`${chalk.red('Can not Create Component "'+name+'", file already exists "'+componentPath+'" !')}`); + + return ''; + } + + const host = await ask("o2serverHost"); + const port = await ask("o2serverCenterPort"); + const webPort = await ask("o2serverWebPort"); + const isHttps = await ask("isHttps"); + + await fs.mkdir(componentPath); + await componentFactory.cpfile(componentPath, templatePath, { + projectName: name, + projectPath: componentPath, + o2serverHost: host, + o2serverCenterPort: port, + o2serverWebPort: webPort, + isHttps: isHttps + }); + + // if (packageManager==='yarn'){ + // await executeCommand(packageManager, ['add', '@o2oa/component'], componentPath); + // await executeCommand(packageManager, ['add', '@o2oa/oovm'], componentPath); + // await executeCommand(packageManager, ['add', '@o2oa/oovm-scripts', '--dve'], componentPath); + // }else{ + // await executeCommand(packageManager, ['install', '@o2oa/component', '-save'], componentPath); + // await executeCommand(packageManager, ['install', '@o2oa/oovm', '-save'], componentPath); + // await executeCommand(packageManager, ['install', '@o2oa/oovm-scripts', '-save-dev'], componentPath); + // } + + await executeCommand('npm', ['install', '@o2oa/component', '--save'], componentPath); + await executeCommand('npm', ['install', 'vue@^3.0.0', '--save'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/core@^1.6.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/plugin-vue@^1.0.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/plugin-umd@^1.0.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', 'uglify-js', '--save-dev'], componentPath); + await executeCommand('npm', ['install', 'axios', '--save-dev'], componentPath); + + await componentFactory.writeGulpAppFile(componentPath); + + console.log(); + console.log(`👉 `+`${chalk.green('O2OA Comonent "'+componentPath+'" Created!')}`); + console.log(); + console.log( + `👉 Get started with the following commands:\n\n` + + chalk.cyan(` ${chalk.gray('$')} cd ${componentPath}\n`) + + // chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn start' : packageManager === 'pnpm' ? 'pnpm run start' : 'npm run start'}`) + chalk.cyan(` ${chalk.gray('$')} ${'npm run dev'}`) + ); + } static async react_webpack(name, opts) { const componentPath = 'x_component_'+name.replace(/\./g, '_'); const templatePath = path.resolve(__dirname, options["react webpack"]); @@ -229,6 +286,63 @@ class componentFactory{ chalk.cyan(` ${chalk.gray('$')} ${'npm run dev'}`) ); } + static async react_rsbuild(name, opts) { + const componentPath = 'x_component_'+name.replace(/\./g, '_'); + const templatePath = path.resolve(__dirname, options["react rsbuild"]); + + if (existsSync(componentPath)){ + console.log(); + console.log(`👉 `+`${chalk.red('Can not Create Component "'+name+'", file already exists "'+componentPath+'" !')}`); + + return ''; + } + + const host = await ask("o2serverHost"); + const port = await ask("o2serverCenterPort"); + const webPort = await ask("o2serverWebPort"); + const isHttps = await ask("isHttps"); + + await fs.mkdir(componentPath); + await componentFactory.cpfile(componentPath, templatePath, { + projectName: name, + projectPath: componentPath, + o2serverHost: host, + o2serverCenterPort: port, + o2serverWebPort: webPort, + isHttps: isHttps + }); + + // if (packageManager==='yarn'){ + // await executeCommand(packageManager, ['add', '@o2oa/component'], componentPath); + // await executeCommand(packageManager, ['add', '@o2oa/oovm'], componentPath); + // await executeCommand(packageManager, ['add', '@o2oa/oovm-scripts', '--dve'], componentPath); + // }else{ + // await executeCommand(packageManager, ['install', '@o2oa/component', '-save'], componentPath); + // await executeCommand(packageManager, ['install', '@o2oa/oovm', '-save'], componentPath); + // await executeCommand(packageManager, ['install', '@o2oa/oovm-scripts', '-save-dev'], componentPath); + // } + + await executeCommand('npm', ['install', '@o2oa/component', '--save'], componentPath); + await executeCommand('npm', ['install', 'react@^18.0.0', '--save'], componentPath); + await executeCommand('npm', ['install', 'react-dom@^18.0.0', '--save'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/core@^1.6.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/plugin-react@^1.0.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', '@rsbuild/plugin-umd@^1.0.0', '--save-dev'], componentPath); + await executeCommand('npm', ['install', 'uglify-js', '--save-dev'], componentPath); + await executeCommand('npm', ['install', 'axios', '--save-dev'], componentPath); + + await componentFactory.writeGulpAppFile(componentPath); + + console.log(); + console.log(`👉 `+`${chalk.green('O2OA Comonent "'+componentPath+'" Created!')}`); + console.log(); + console.log( + `👉 Get started with the following commands:\n\n` + + chalk.cyan(` ${chalk.gray('$')} cd ${componentPath}\n`) + + // chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn start' : packageManager === 'pnpm' ? 'pnpm run start' : 'npm run start'}`) + chalk.cyan(` ${chalk.gray('$')} ${'npm run dev'}`) + ); + } static async o2_native(name, opts) { const componentPath = 'x_component_'+name.replace(/\./g, '_'); const templatePath = path.resolve(__dirname, options["o2_native"]); diff --git a/lib/options.js b/lib/options.js index f622ab4..d3df327 100644 --- a/lib/options.js +++ b/lib/options.js @@ -3,8 +3,10 @@ const options = { "oovm": "../template/oovm", "vue3 vue-cli": "../template/vue/vue-cli/preset.json", "vue3 vite": "../template/vue/vite", + "vue3 rsbuild": "../template/vue/rsbuild", "vue2": "../template/vue2/preset.json", "react webpack": "../template/react/webpack", - "react vite": "../template/react/vite" + "react vite": "../template/react/vite", + "react rsbuild": "../template/react/rsbuild" }; export default options; diff --git a/template/react/rsbuild/.gitignore b/template/react/rsbuild/.gitignore new file mode 100644 index 0000000..6f3092c --- /dev/null +++ b/template/react/rsbuild/.gitignore @@ -0,0 +1,16 @@ +# Local +.DS_Store +*.local +*.log* + +# Dist +node_modules +dist/ + +# Profile +.rspack-profile-*/ + +# IDE +.vscode/* +!.vscode/extensions.json +.idea diff --git a/template/react/rsbuild/AGENTS.md b/template/react/rsbuild/AGENTS.md new file mode 100644 index 0000000..d8a0883 --- /dev/null +++ b/template/react/rsbuild/AGENTS.md @@ -0,0 +1,14 @@ +# AGENTS.md + +You are an expert in JavaScript, Rsbuild, and web application development. You write maintainable, performant, and accessible code. + +## Commands + +- `npm run dev` - Start the dev server +- `npm run build` - Build the app for production +- `npm run preview` - Preview the production build locally + +## Docs + +- Rsbuild: https://rsbuild.rs/llms.txt +- Rspack: https://rspack.rs/llms.txt diff --git a/template/react/rsbuild/README.md b/template/react/rsbuild/README.md new file mode 100644 index 0000000..d34080e --- /dev/null +++ b/template/react/rsbuild/README.md @@ -0,0 +1,36 @@ +# O2OA Rsbuild project with React + +## Setup + +Install the dependencies: + +```bash +npm install +``` + +## Get started + +Start the dev server, and the app will be available at [http://localhost:7979](http://localhost:7979). + +```bash +npm run dev +``` + +Build the app for production: + +```bash +npm run build +``` + +Preview the production build locally: + +```bash +npm run preview +``` + +## Learn more + +To learn more about Rsbuild, check out the following resources: + +- [Rsbuild documentation](https://rsbuild.rs) - explore Rsbuild features and APIs. +- [Rsbuild GitHub repository](https://github.com/web-infra-dev/rsbuild) - your feedback and contributions are welcome! diff --git a/template/react/rsbuild/o2.config.json b/template/react/rsbuild/o2.config.json new file mode 100644 index 0000000..3ef4f1c --- /dev/null +++ b/template/react/rsbuild/o2.config.json @@ -0,0 +1,7 @@ +{ + "devServer": { + "host": "<%= o2serverHost %>", + "port": "<%= o2serverWebPort %>", + "https": <%= isHttps %> + } +} diff --git a/template/react/rsbuild/package.json b/template/react/rsbuild/package.json new file mode 100644 index 0000000..2450b26 --- /dev/null +++ b/template/react/rsbuild/package.json @@ -0,0 +1,17 @@ +{ + "name": "<%= projectPath %>", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --open", + "preview": "rsbuild preview", + "o2-deploy": "rsbuild build", + "o2-build": "rsbuild build" + }, + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/template/react/rsbuild/public/$Main/appicon.png b/template/react/rsbuild/public/$Main/appicon.png new file mode 100644 index 0000000..9f2ea0a Binary files /dev/null and b/template/react/rsbuild/public/$Main/appicon.png differ diff --git a/template/react/rsbuild/public/$Main/default/icon.png b/template/react/rsbuild/public/$Main/default/icon.png new file mode 100644 index 0000000..5f69e25 Binary files /dev/null and b/template/react/rsbuild/public/$Main/default/icon.png differ diff --git a/template/react/rsbuild/public/$Main/default/style.css b/template/react/rsbuild/public/$Main/default/style.css new file mode 100644 index 0000000..0b71849 --- /dev/null +++ b/template/react/rsbuild/public/$Main/default/style.css @@ -0,0 +1,3 @@ +.appContent { + text-align: center +} diff --git a/template/react/rsbuild/public/$Main/flatlnk.png b/template/react/rsbuild/public/$Main/flatlnk.png new file mode 100644 index 0000000..da4e868 Binary files /dev/null and b/template/react/rsbuild/public/$Main/flatlnk.png differ diff --git a/template/react/rsbuild/public/favicon.ico b/template/react/rsbuild/public/favicon.ico new file mode 100644 index 0000000..28d84f8 Binary files /dev/null and b/template/react/rsbuild/public/favicon.ico differ diff --git a/template/react/rsbuild/public/lp/en.js b/template/react/rsbuild/public/lp/en.js new file mode 100644 index 0000000..53ac000 --- /dev/null +++ b/template/react/rsbuild/public/lp/en.js @@ -0,0 +1,16 @@ +MWF.xApplication.<%= projectName %>.LP = { + "title": "<%= projectName %>", + + "welcome": "Welcome to O2OA Component with React", + "taskListTitle": "Here are your top 5 task", + + "taskTitle": "Title", + "taskProcess": "Process", + "taskTime": "Time", + + "openCalendar": "Open Calendar", + "openOrganization": "Open Organization", + "startProcess": "Start Process", + "createDocument": "Create Document", + "openInBrowser": "Open In Browser" +} diff --git a/template/react/rsbuild/public/lp/zh-cn.js b/template/react/rsbuild/public/lp/zh-cn.js new file mode 100644 index 0000000..f7a2315 --- /dev/null +++ b/template/react/rsbuild/public/lp/zh-cn.js @@ -0,0 +1,16 @@ +MWF.xApplication.<%= projectName %>.LP = { + "title": "<%= projectName %>", + + "welcome": "Welcome to O2OA Component with React", + "taskListTitle": "此处列出您的5个最新待办", + + "taskTitle": "标题", + "taskProcess": "流程", + "taskTime": "到达时间", + + "openCalendar": "打开日程管理", + "openOrganization": "打开组织管理", + "startProcess": "启动流程", + "createDocument": "创建信息", + "openInBrowser": "在新浏览器窗口中打开" +} diff --git a/template/react/rsbuild/rsbuild.config.mjs b/template/react/rsbuild/rsbuild.config.mjs new file mode 100644 index 0000000..c1cb4b3 --- /dev/null +++ b/template/react/rsbuild/rsbuild.config.mjs @@ -0,0 +1,13 @@ +import { defineConfig, mergeRsbuildConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import devConfig from './rsbuild.dev.config' +import prodConfig from './rsbuild.prod.config' + +export default defineConfig(({ envMode }) => { + const config = envMode === 'development' ? devConfig : prodConfig + const mergedConfig = mergeRsbuildConfig(config, { + plugins: [pluginReact()], + // Your config here + }) + return mergedConfig +}) diff --git a/template/react/rsbuild/rsbuild.dev.config.mjs b/template/react/rsbuild/rsbuild.dev.config.mjs new file mode 100644 index 0000000..78c8fdc --- /dev/null +++ b/template/react/rsbuild/rsbuild.dev.config.mjs @@ -0,0 +1,210 @@ +import path from 'node:path' +import { readFileSync, existsSync } from 'fs' +import fs from 'fs/promises' +import axios from 'axios' + +const pkgPath = path.resolve(process.cwd(), 'package.json') +const pkgJson = JSON.parse(readFileSync(pkgPath)) +const componentPath = pkgJson.name +const componentName = componentPath + .replace('x_component_', '') + .split('_') + .join('.') + +const devConfig = { + dev: { + cliShortcuts: true, + }, + server: { + host: '0.0.0.0', + port: 7979, + }, + output: { + manifest: true, + }, +} + +let proxyTarget = '' +const configPath = path.resolve(process.cwd(), 'o2.config.json') +const o2config = existsSync(configPath) + ? JSON.parse(readFileSync(configPath)) + : {} +if (o2config && o2config.devServer) { + const server = o2config.devServer + proxyTarget = `${server.https ? 'https' : 'http'}://${server.host}${!server.port || server.port === '80' || server.port === '443' ? '' : `:${server.port}`}` +} + +if (proxyTarget) { + let domain = '' + const getDomainPlugin = () => ({ + name: 'configure-server', + setup(api) { + api.onBeforeStartDevServer(({ server }) => { + server.middlewares.use((req, res, next) => { + const referer = req.headers['referer'] + if (referer) { + const matches = referer.match(/(?<=:\/\/)([^\/\r\n:]+)/) + if (matches && matches[0] !== domain) { + domain = matches[0] + } + } + next() + }) + }) + }, + }) + + const o = { + target: proxyTarget, + changeOrigin: true, + } + Object.defineProperty(o, 'cookieDomainRewrite', { + configurable: true, + enumerable: true, + get() { + return domain + }, + }) + + devConfig.server.proxy = { + '/o2_core': { target: proxyTarget, changeOrigin: true }, + '/o2_lib': { target: proxyTarget, changeOrigin: true }, + '/x_desktop': { target: proxyTarget, changeOrigin: true }, + '/x_component': { target: proxyTarget, changeOrigin: true }, + '/o2api': { target: proxyTarget, changeOrigin: true }, + '/x_app_center/ws': { target: proxyTarget, changeOrigin: true, ws: true }, + '/x_': o, + } + + const getRunComponentFunStr = () => { + return ` + var runComponent = function(name, res){ + o2.xApplication = o2.xApplication || {}; + var names = name.split("."); + var o = o2.xApplication; + names.forEach(function(n){ + o = o[n] = o[n] || {}; + }); + o.loading = new Promise(function(resolve){ + o2.loadAll(res, {evalScripts:true, url: true, type:"module"}, function(){ resolve(); }); + }) + } + ` + } + + let host = proxyTarget + const parseContentPlugin = () => ({ + name: 'parse-content', + setup(api) { + api.onBeforeStartDevServer(({ server, environments }) => { + server.middlewares.use((req, res, next) => { + + let js, css, jsTags, cssTags + try { + js = environments.web.manifest.entries.index.initial?.js || [ + '/static/js/index.js', + ] + jsTags = js + .map(item => ``) + .join('\n') + css = environments.web.manifest.entries.index.initial?.css || [ + '/static/css/index.css', + ] + cssTags = css + .map(item => ``) + .join('\n') + } catch (error) { + console.error('Failed to get manifest:', error) + return next() + } + if ( + req.url.startsWith('/x_desktop/app.html') || + (req.url.startsWith('/x_desktop/index.html') && + req.url.includes(componentName)) + ) { + const htmlUrl = new URL(req.url, host) + axios + .get(htmlUrl.toString()) + .then(html => { + const htmlContent = html.data + const headIndex = htmlContent.indexOf('
') + const newHtmlContent = + htmlContent.slice(0, headIndex + 6) + + jsTags + + cssTags + + htmlContent.slice(headIndex + 6) + + res.setHeader('Content-Type', 'text/html') + res.end(newHtmlContent, 'utf8') + }) + .catch(error => { + res.statusCode = 500 + res.end(`get remote html file error: ${error.message}`) + }) + } else if (req.url.match(`/${componentPath}/lp/*`)) { + let toUrl = path + .basename(req._parsedUrl.pathname) + .replace(/min\./, '') + toUrl = path.resolve(process.cwd(), 'public', './lp/' + toUrl) + fs.readFile(toUrl).then( + data => { + res.setHeader( + 'Content-Type', + 'application/javascript; charset=UTF-8' + ) + res.end(data, 'utf8') + }, + () => { + res.end('') + } + ) + } else if (req.url.match(`/${componentPath}/Main*`)) { + const script = ` + ${getRunComponentFunStr()} + runComponent("${componentName}", { + js: ['/static/js/index.js'], + }); + ` + res.setHeader( + 'Content-Type', + 'application/javascript; charset=UTF-8' + ) + res.end(script, 'utf8') + } else if (req.url === `/${componentPath}/$Main/default/style.css`) { + const toUrl = path.resolve( + process.cwd(), + 'public', + '$Main/default/style.css' + ) + fs.readFile(toUrl).then( + data => { + res.setHeader('Content-Type', 'text/css; charset=UTF-8') + res.end(data, 'utf8') + }, + () => { + res.end('') + } + ) + } else { + next() + } + }) + }) + }, + }) + devConfig.plugins = [getDomainPlugin(), parseContentPlugin()] +} + +export default { + ...devConfig, + html: { + meta: { + charset: { charset: 'utf-8' }, + 'http-equiv': { + 'http-equiv': 'refresh', + content: + `0;url=/x_desktop/index.html?app=${componentName}&default=false&debugger`, + }, + }, + }, +} diff --git a/template/react/rsbuild/rsbuild.prod.config.mjs b/template/react/rsbuild/rsbuild.prod.config.mjs new file mode 100644 index 0000000..f0eb9fc --- /dev/null +++ b/template/react/rsbuild/rsbuild.prod.config.mjs @@ -0,0 +1,164 @@ +import { readFileSync, existsSync, statSync, readdirSync } from 'fs' +import path from 'node:path' +import fs from 'fs/promises' +import UglifyJS from 'uglify-js' +import { pluginUmd } from '@rsbuild/plugin-umd' + +const pkgPath = path.resolve(process.cwd(), 'package.json') +const pkgJson = JSON.parse(readFileSync(pkgPath)) +const componentPath = pkgJson.name +const componentName = componentPath + .replace('x_component_', '') + .split('_') + .join('.') + +const getOutDir = () => { + switch (process.env.npm_lifecycle_event) { + case 'o2-build': + return `../../../target/o2server/servers/webServer/${componentPath}` + case 'o2-deploy': + return `../../dest/${componentPath}` + default: + return `dist/${componentPath}` + } +} +const outDir = getOutDir() + +function getAllFiles(dir, files_ = [], nest = false) { + const files = readdirSync(dir) + for (const i in files) { + const name = path.join(dir, files[i]) + if (existsSync(name)) { + if (statSync(name).isDirectory()) { + if (nest) getAllFiles(name, files_, nest) + } else { + files_.push(name) + } + } + } + return files_ +} + +function includeMain(relativePath, extname, nest) { + const dir = path.resolve(`${process.cwd()}`, `${outDir}/${relativePath}/`) + if (existsSync(dir)) { + return getAllFiles(dir, [], nest) + .filter(file => { + return path.extname(file) === extname + }) + .map(file => { + return `'../${componentPath}/${relativePath}${file.replace(dir, '').replace(/\\/g, '/')}'`.replace( + /\/\//g, + '/' + ) + }) + } else { + return [] + } +} + +function findLp(relativePath, extname, nest) { + const dir = path.resolve(`${process.cwd()}`, `${outDir}/${relativePath}/`) + if (existsSync(dir)) { + return getAllFiles(dir, [], nest) + .filter(file => { + return path.extname(file) === extname && !file.endsWith('.min.js') + }) + .map(filePath => { + return filePath + }) + } else { + return [] + } +} + +function generateMiniLP() { + const lps = findLp('lp', '.js', false) + lps.forEach(lpPath => { + const o = path.parse(lpPath) + + const toPath = path.resolve(`${o.dir}`, `${o.name}.min${o.ext}`) + fs.readFile(lpPath).then( + data => { + const miniContent = UglifyJS.minify(data.toString()).code + fs.writeFile(toPath, miniContent, 'utf8').catch(err => { + console.error('Failed to write file lp.min.js:', err) + }) + }, + err => { + console.error('Failed to read file lp.min.js:', err) + } + ) + }) +} + +function generateMain() { + let mainFileContent = `o2.component("${componentName}", {\n` + + const maincsss = includeMain('$Main/css', '.css', false) + const csss = includeMain('', '.css', false).concat(maincsss) + if (csss.length) mainFileContent += ` css: [${csss.join(', ')}],\n` + + const jss = includeMain('$Main/js', '.js', false) + if (jss.length) mainFileContent += ` js: [${jss.join(', ')}],\n` + + mainFileContent += `});` + + const filePath = path.resolve(`${process.cwd()}`, outDir, `Main.js`) + fs.writeFile(filePath, mainFileContent, 'utf8').catch(err => { + console.error('Failed to generate Main.js:', err) + }) + + const miniFileContent = UglifyJS.minify(mainFileContent).code + const minFilePath = path.resolve(`${process.cwd()}`, outDir, `Main.min.js`) + fs.writeFile(minFilePath, miniFileContent, 'utf8').catch(err => { + console.error('Failed to generate Main.min.js:', err) + }) +} + +const generateMainPlugin = () => ({ + name: 'generate-file', + setup(api) { + api.onCloseBuild(() => { + generateMain() + generateMiniLP() + }) + }, +}) + +const prodConfig = { + plugins: [ + pluginUmd({ + name: `${componentName}`, + }), + generateMainPlugin(), + ], + source: { + define: { + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), + }, + }, + }, + output: { + emitAssets: true, + assetPrefix: `../${componentPath}/`, + distPath: { + root: outDir, + js: '$Main/js', + jsAsync: '$Main/js/async', + css: '$Main/css', + cssAsync: '$Main/css/async', + svg: '$Main/svg', + font: '$Main/font', + wasm: '$Main/wasm', + image: '$Main/image', + media: '$Main/media', + assets: '$Main/assets', + }, + cleanDistPath: true, + sourceMap: true, + }, +} + +export default prodConfig diff --git a/template/react/rsbuild/src/App.css b/template/react/rsbuild/src/App.css new file mode 100644 index 0000000..40cb49f --- /dev/null +++ b/template/react/rsbuild/src/App.css @@ -0,0 +1,23 @@ +.appRoot { + margin: 0; + color: #fff; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + background-image: linear-gradient(to bottom, #020917, #101725); + display: flex; + min-height: 100vh; + line-height: 1.1; + text-align: center; + flex-direction: column; + justify-content: center; +} + +.appRoot h1 { + font-size: 3.6rem; + font-weight: 700; +} + +.appRoot p { + font-size: 1.2rem; + font-weight: 400; + opacity: 0.5; +} diff --git a/template/react/rsbuild/src/App.jsx b/template/react/rsbuild/src/App.jsx new file mode 100644 index 0000000..d5cd26d --- /dev/null +++ b/template/react/rsbuild/src/App.jsx @@ -0,0 +1,12 @@ +import './App.css'; + +const App = () => { + return ( +Start building amazing things with Rsbuild.
+Start building amazing things with Rsbuild.
+