create-vulajs-app 1.1.0
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/bin/index.js +42 -0
- package/package.json +8 -0
- package/template/app/api/counter.sever.comp.ts +7 -0
- package/template/app/api/hello.ts +3 -0
- package/template/app/page.tsx +38 -0
- package/template/index.html +11 -0
- package/template/package.json +21 -0
- package/template/rspack.config.js +65 -0
- package/template/src/globals.css +11 -0
- package/template/src/index.tsx +50 -0
- package/template/src/vula.ts +25 -0
- package/template/tsconfig.json +23 -0
- package/template/vulaism.config.yaml +48 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const projectName = process.argv[2];
|
|
6
|
+
|
|
7
|
+
if (!projectName) {
|
|
8
|
+
console.error('\x1b[31mPlease specify the project directory:\x1b[0m');
|
|
9
|
+
console.error(' npx create-vulajs-app <project-directory>');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(targetDir)) {
|
|
16
|
+
console.error(`\x1b[31mDirectory ${projectName} already exists.\x1b[0m`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const templateDir = path.join(__dirname, '../template');
|
|
21
|
+
|
|
22
|
+
console.log(`\x1b[36mCreating a new Vula.js app in ${targetDir}...\x1b[0m`);
|
|
23
|
+
|
|
24
|
+
// Copy template files
|
|
25
|
+
fs.cpSync(templateDir, targetDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Update package.json name
|
|
28
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
29
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
30
|
+
packageJson.name = projectName;
|
|
31
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
32
|
+
|
|
33
|
+
console.log('\x1b[32mSuccess! Created', projectName, '\x1b[0m');
|
|
34
|
+
console.log('Inside that directory, you can run several commands:\n');
|
|
35
|
+
console.log(' \x1b[36mnpm install\x1b[0m');
|
|
36
|
+
console.log(' Installs dependencies.\n');
|
|
37
|
+
console.log(' \x1b[36mnpm run dev\x1b[0m');
|
|
38
|
+
console.log(' Starts the development server.\n');
|
|
39
|
+
console.log('We suggest that you begin by typing:\n');
|
|
40
|
+
console.log(` \x1b[36mcd ${projectName}\x1b[0m`);
|
|
41
|
+
console.log(' \x1b[36mnpm install\x1b[0m');
|
|
42
|
+
console.log(' \x1b[36mnpm run dev\x1b[0m');
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '2rem', backgroundColor: '#0f172a', color: '#f8fafc', fontFamily: 'system-ui, sans-serif' }}>
|
|
6
|
+
|
|
7
|
+
{/* Vula Logo / Hero */}
|
|
8
|
+
<div style={{ marginBottom: '2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
9
|
+
<div style={{ width: '100px', height: '100px', borderRadius: '24px', background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '1.5rem', boxShadow: '0 10px 25px -5px rgba(139, 92, 246, 0.5)' }}>
|
|
10
|
+
<span style={{ fontSize: '3rem', fontWeight: 'bold', color: 'white' }}>V</span>
|
|
11
|
+
</div>
|
|
12
|
+
<h1 style={{ fontSize: '3.5rem', fontWeight: '800', margin: '0', background: '-webkit-linear-gradient(0deg, #60a5fa, #c084fc)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
|
|
13
|
+
Vula.js
|
|
14
|
+
</h1>
|
|
15
|
+
<h2 style={{ fontSize: '1.5rem', fontWeight: '500', color: '#94a3b8', marginTop: '0.5rem', margin: '0' }}>
|
|
16
|
+
Swavoti/vulajs
|
|
17
|
+
</h2>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
{/* Introduction */}
|
|
21
|
+
<p style={{ fontSize: '1.25rem', color: '#cbd5e1', textAlign: 'center', maxWidth: '600px', lineHeight: '1.6', marginBottom: '3rem' }}>
|
|
22
|
+
The blazingly fast, minimal file-based React framework powered by Rspack.
|
|
23
|
+
Experience instant hot module replacement, zero-config SSR, and beautiful out-of-the-box defaults.
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
{/* Call to Actions */}
|
|
27
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', justifyContent: 'center' }}>
|
|
28
|
+
<a href="https://github.com/nguyenblacks/vulajs" target="_blank" rel="noreferrer" style={{ padding: '1rem 2rem', backgroundColor: '#3b82f6', borderRadius: '9999px', textDecoration: 'none', color: '#ffffff', fontWeight: '600', fontSize: '1.125rem', transition: 'background-color 0.2s', boxShadow: '0 4px 6px -1px rgba(59, 130, 246, 0.4)' }}>
|
|
29
|
+
View Docs on GitHub
|
|
30
|
+
</a>
|
|
31
|
+
<a href="/api/hello" style={{ padding: '1rem 2rem', backgroundColor: 'transparent', border: '2px solid #475569', borderRadius: '9999px', textDecoration: 'none', color: '#f8fafc', fontWeight: '600', fontSize: '1.125rem', transition: 'border-color 0.2s' }}>
|
|
32
|
+
Test API Route
|
|
33
|
+
</a>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vulajs-template",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "vula dev",
|
|
6
|
+
"build": "vula build"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"react": "^19.2.6",
|
|
10
|
+
"react-dom": "^19.2.6",
|
|
11
|
+
"@swavoti/vula": "latest",
|
|
12
|
+
"@vula/video-opti": "latest"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/react": "^19.0.0",
|
|
16
|
+
"@types/react-dom": "^19.0.0",
|
|
17
|
+
"babel-loader": "^9.1.3",
|
|
18
|
+
"@babel/core": "^7.23.0",
|
|
19
|
+
"babel-plugin-react-compiler": "latest"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { defineConfig } = require('@rspack/cli');
|
|
3
|
+
const { ReactRefreshRspackPlugin } = require('@rspack/plugin-react-refresh');
|
|
4
|
+
const rspack = require('@rspack/core');
|
|
5
|
+
|
|
6
|
+
module.exports = defineConfig({
|
|
7
|
+
entry: {
|
|
8
|
+
main: './src/index.tsx',
|
|
9
|
+
},
|
|
10
|
+
resolve: {
|
|
11
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
12
|
+
alias: {
|
|
13
|
+
'@app': path.resolve(__dirname, 'app'),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
module: {
|
|
17
|
+
rules: [
|
|
18
|
+
{
|
|
19
|
+
test: /\.(j|t)sx?$/,
|
|
20
|
+
exclude: [/[\\/]node_modules[\\/]/],
|
|
21
|
+
use: [
|
|
22
|
+
{
|
|
23
|
+
loader: 'builtin:swc-loader',
|
|
24
|
+
options: {
|
|
25
|
+
jsc: {
|
|
26
|
+
parser: { syntax: 'typescript', tsx: true },
|
|
27
|
+
transform: { react: { runtime: 'automatic', refresh: true } },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
loader: 'babel-loader',
|
|
33
|
+
options: {
|
|
34
|
+
plugins: [['babel-plugin-react-compiler', { target: '19' }]],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
type: 'javascript/auto',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
test: /\.css$/,
|
|
42
|
+
type: 'css',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
plugins: [
|
|
47
|
+
new ReactRefreshRspackPlugin(),
|
|
48
|
+
new rspack.HtmlRspackPlugin({ template: './index.html' }),
|
|
49
|
+
],
|
|
50
|
+
devServer: {
|
|
51
|
+
port: 3000,
|
|
52
|
+
hot: true,
|
|
53
|
+
historyApiFallback: true,
|
|
54
|
+
static: { directory: path.join(__dirname, 'public') },
|
|
55
|
+
},
|
|
56
|
+
output: {
|
|
57
|
+
clean: true,
|
|
58
|
+
path: path.resolve(__dirname, 'dist'),
|
|
59
|
+
},
|
|
60
|
+
optimization: {
|
|
61
|
+
splitChunks: {
|
|
62
|
+
chunks: 'all',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { Suspense, lazy } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { match } from 'path-to-regexp';
|
|
4
|
+
import './globals.css';
|
|
5
|
+
|
|
6
|
+
// Route manifest generated by the Vula CLI
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import manifest from '../.vula-routes.json';
|
|
9
|
+
|
|
10
|
+
function Router() {
|
|
11
|
+
const pathname = window.location.pathname;
|
|
12
|
+
|
|
13
|
+
let matchedRoute: any = null;
|
|
14
|
+
let matchedParams: Record<string, string> = {};
|
|
15
|
+
|
|
16
|
+
for (const route of manifest.routes) {
|
|
17
|
+
const fn = match(route.path, { decode: decodeURIComponent });
|
|
18
|
+
const result = fn(pathname);
|
|
19
|
+
if (result) {
|
|
20
|
+
matchedRoute = route;
|
|
21
|
+
matchedParams = result.params as Record<string, string>;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!matchedRoute) {
|
|
27
|
+
return (
|
|
28
|
+
<div style={{ padding: '4rem', textAlign: 'center', fontFamily: 'system-ui' }}>
|
|
29
|
+
<h1 style={{ fontSize: '3rem', marginBottom: '1rem' }}>404</h1>
|
|
30
|
+
<p>Page not found.</p>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const relativePath = matchedRoute.componentPath.split('app')[1];
|
|
36
|
+
const Component = lazy(() => import(`@app${relativePath}`));
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Suspense fallback={<div style={{ padding: '2rem', textAlign: 'center' }}>Loading...</div>}>
|
|
40
|
+
<Component params={matchedParams} />
|
|
41
|
+
</Suspense>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
|
46
|
+
root.render(
|
|
47
|
+
<React.StrictMode>
|
|
48
|
+
<Router />
|
|
49
|
+
</React.StrictMode>
|
|
50
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class Vula {
|
|
2
|
+
static async server(name: string, ...args: any[]) {
|
|
3
|
+
const res = await fetch('/_vula/server-component', {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
body: JSON.stringify({ name, args }),
|
|
7
|
+
});
|
|
8
|
+
if (!res.ok) {
|
|
9
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
10
|
+
throw new Error(err.error || `Server component "${name}" failed`);
|
|
11
|
+
}
|
|
12
|
+
const { result } = await res.json();
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static async api(path: string, options: RequestInit = {}) {
|
|
17
|
+
const url = path.startsWith('/') ? path : `/api/${path}`;
|
|
18
|
+
const res = await fetch(url, options);
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
21
|
+
throw new Error(err.error || `API ${path} returned ${res.status}`);
|
|
22
|
+
}
|
|
23
|
+
return res.json();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"jsx": "preserve",
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@app/*": ["app/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["src", "app"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# vulaism.config.yaml
|
|
2
|
+
# Vula.js project configuration file.
|
|
3
|
+
# All options are optional - defaults will apply when omitted.
|
|
4
|
+
|
|
5
|
+
app:
|
|
6
|
+
name: "My Vula App"
|
|
7
|
+
# Path to favicon/icon, relative to project root (from assets/ folder)
|
|
8
|
+
icon: "assets/icon.png"
|
|
9
|
+
# Default HTML language attribute
|
|
10
|
+
lang: "en"
|
|
11
|
+
|
|
12
|
+
optimization:
|
|
13
|
+
# Enable text optimizations (minification hints, compression headers)
|
|
14
|
+
text-opt: true
|
|
15
|
+
# Preload critical fonts via <link rel="preload">
|
|
16
|
+
font-preload: true
|
|
17
|
+
# CSS font-display strategy: swap | block | fallback | optional
|
|
18
|
+
font-display: "swap"
|
|
19
|
+
# Remove unused glyphs from fonts in production builds
|
|
20
|
+
font-subset: true
|
|
21
|
+
# Enable image compression hints during build
|
|
22
|
+
image-compression: true
|
|
23
|
+
# Minify injected HTML tags
|
|
24
|
+
minify-html: false
|
|
25
|
+
|
|
26
|
+
security:
|
|
27
|
+
# Enable CSRF origin protection middleware
|
|
28
|
+
csrf: true
|
|
29
|
+
# Whitelist origins for CSRF (empty = allow all in dev)
|
|
30
|
+
allowed-origins: []
|
|
31
|
+
# Inject Content-Security-Policy headers
|
|
32
|
+
content-security-policy: false
|
|
33
|
+
|
|
34
|
+
proxy:
|
|
35
|
+
# Enable the built-in HTTP proxy
|
|
36
|
+
enabled: false
|
|
37
|
+
# Target URL to proxy requests to
|
|
38
|
+
target: "http://localhost:8080"
|
|
39
|
+
# URL prefix to trigger the proxy
|
|
40
|
+
prefix: "/proxy"
|
|
41
|
+
|
|
42
|
+
server:
|
|
43
|
+
# Port to run the dev server on
|
|
44
|
+
port: 3000
|
|
45
|
+
# Enable gzip compression for responses
|
|
46
|
+
compression: true
|
|
47
|
+
# Trust X-Forwarded-* headers (set true when behind a reverse proxy)
|
|
48
|
+
trust-proxy: false
|