fusee-framework 1.6.0 → 1.7.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/README.md +29 -13
- package/app/benchmark/benchmark.js +633 -0
- package/app/benchmark/index.html +102 -0
- package/app/benchmark/styles.css +249 -0
- package/app/components/Counter.js +17 -7
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +29 -62
- package/app/main.ts +72 -63
- package/app/pages/404.js +16 -0
- package/app/pages/[...all].js +35 -0
- package/app/pages/[...all].ts +35 -0
- package/app/pages/_layout.js +26 -0
- package/app/pages/_layout.ts +26 -0
- package/app/pages/about.js +23 -0
- package/app/pages/about.ts +29 -0
- package/app/pages/composables.js +158 -0
- package/app/pages/composables.ts +158 -0
- package/app/pages/dynamic.js +40 -0
- package/app/pages/dynamic.ts +40 -0
- package/app/pages/index.js +117 -0
- package/app/pages/index.ts +78 -0
- package/app/pages/login.ts +21 -0
- package/app/pages/objects/[id].js +56 -0
- package/app/pages/objects/[id].ts +56 -0
- package/app/pages/posts/[category]/[slug].js +56 -0
- package/app/pages/posts/[category]/[slug].ts +56 -0
- package/app/pages/router-example.js +56 -0
- package/app/pages/router-example.ts +56 -0
- package/app/pages/test-for.js +65 -0
- package/app/pages/test-for.ts +80 -0
- package/app/pages/users/[id].js +24 -0
- package/app/pages/users/[id].ts +46 -0
- package/app/pages/users/index.js +22 -0
- package/app/pages/users/index.ts +29 -0
- package/app/pages/users.js +43 -0
- package/app/pages/users.ts +78 -0
- package/app/routes/About.js +1 -1
- package/app/routes/About.ts +1 -1
- package/app/routes/AuthRoutes.ts +41 -0
- package/app/routes/Home.js +9 -0
- package/app/routes/NotFound.js +16 -0
- package/app/routes/NotFound.ts +16 -0
- package/app/routes/RouterExample.js +56 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/auto-imports.d.ts +1 -0
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/bin/create-fusee.exe +0 -0
- package/bin/index.js +38 -0
- package/create-fusee-go/cmd/generate.go +72 -0
- package/create-fusee-go/cmd/init.go +38 -0
- package/create-fusee-go/cmd/root.go +121 -0
- package/create-fusee-go/go.mod +10 -0
- package/create-fusee-go/go.sum +10 -0
- package/create-fusee-go/internal/assets/assets.go +78 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
- package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
- package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
- package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
- package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
- package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
- package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
- package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
- package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
- package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
- package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
- package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
- package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
- package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
- package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
- package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
- package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
- package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
- package/create-fusee-go/main.go +7 -0
- package/framework/auto-import-preset.js +1 -1
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +265 -260
- package/framework/core/directives.js +875 -786
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +33 -14
- package/framework/core/signal.js +412 -403
- package/framework/index.js +10 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +462 -80
- package/framework/types/event-delegation.d.ts +0 -58
- package/framework/types/router.d.ts +32 -15
- package/framework/types/signal.d.ts +1 -0
- package/index.html +638 -626
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
|
Binary file
|
|
Binary file
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
function getBinaryName() {
|
|
10
|
+
const platform = process.platform;
|
|
11
|
+
const arch = process.arch;
|
|
12
|
+
|
|
13
|
+
if (platform === 'win32') return 'create-fusee-win.exe';
|
|
14
|
+
if (platform === 'darwin') {
|
|
15
|
+
return arch === 'arm64' ? 'create-fusee-mac-arm64' : 'create-fusee-mac-intel';
|
|
16
|
+
}
|
|
17
|
+
if (platform === 'linux') return 'create-fusee-linux';
|
|
18
|
+
|
|
19
|
+
return 'create-fusee-linux'; // Default fallback
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const binName = getBinaryName();
|
|
23
|
+
const binPath = path.join(__dirname, binName);
|
|
24
|
+
|
|
25
|
+
// Fallback for local development if specific named binary isn't there
|
|
26
|
+
const finalPath = fs.existsSync(binPath) ? binPath : path.join(__dirname, 'create-fusee.exe');
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(finalPath)) {
|
|
29
|
+
console.error(`\n❌ Error: Fusée binary not found for your platform (${process.platform})`);
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const child = spawn(finalPath, args, { stdio: 'inherit' });
|
|
35
|
+
|
|
36
|
+
child.on('exit', (code) => {
|
|
37
|
+
process.exit(code);
|
|
38
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"create-fusee/internal/assets"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var genCmd = &cobra.Command{
|
|
14
|
+
Use: "generate [type] [name]",
|
|
15
|
+
Aliases: []string{"g"},
|
|
16
|
+
Short: "Generate a new page or component",
|
|
17
|
+
Long: `Generate a new Fusée resource.
|
|
18
|
+
Types available: page (p), component (c)`,
|
|
19
|
+
Args: cobra.ExactArgs(2),
|
|
20
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
21
|
+
genType := strings.ToLower(args[0])
|
|
22
|
+
name := strings.Title(args[1])
|
|
23
|
+
|
|
24
|
+
// Check if we are in a Fusée project
|
|
25
|
+
if _, err := os.Stat("framework"); os.IsNotExist(err) {
|
|
26
|
+
fmt.Println("❌ Error: This command must be run from the root of a Fusée project.")
|
|
27
|
+
os.Exit(1)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ext := "js"
|
|
31
|
+
if _, err := os.Stat("tsconfig.json"); err == nil {
|
|
32
|
+
ext = "ts"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
config := assets.Config{
|
|
36
|
+
ProjectName: name,
|
|
37
|
+
IsTS: ext == "ts",
|
|
38
|
+
Ext: ext,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var dest string
|
|
42
|
+
var tmpl string
|
|
43
|
+
|
|
44
|
+
switch genType {
|
|
45
|
+
case "page", "p":
|
|
46
|
+
dest = filepath.Join("app/pages", args[1]+"."+ext)
|
|
47
|
+
tmpl = "templates/page.tmpl"
|
|
48
|
+
case "component", "c":
|
|
49
|
+
dest = filepath.Join("app/components", args[1]+"."+ext)
|
|
50
|
+
tmpl = "templates/component.tmpl"
|
|
51
|
+
default:
|
|
52
|
+
fmt.Printf("❌ Error: Unknown type '%s'. Use 'page' or 'component'.\n", genType)
|
|
53
|
+
os.Exit(1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if _, err := os.Stat(dest); err == nil {
|
|
57
|
+
fmt.Printf("❌ Error: File '%s' already exists.\n", dest)
|
|
58
|
+
os.Exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if err := assets.WriteTemplate(tmpl, dest, config); err != nil {
|
|
62
|
+
fmt.Printf("❌ Error: Could not generate %s: %v\n", genType, err)
|
|
63
|
+
os.Exit(1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fmt.Printf("✅ %s created: %s\n", strings.Title(genType), dest)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func init() {
|
|
71
|
+
rootCmd.AddCommand(genCmd)
|
|
72
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bufio"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/spf13/cobra"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var initCmd = &cobra.Command{
|
|
14
|
+
Use: "init [project-name]",
|
|
15
|
+
Short: "Initialize a new Fusée project",
|
|
16
|
+
Args: cobra.MaximumNArgs(1),
|
|
17
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
+
projectName := "my-fusee-app"
|
|
19
|
+
if len(args) > 0 {
|
|
20
|
+
projectName = args[0]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
isTS := IsTSFlag
|
|
24
|
+
if !cmd.Flags().Changed("ts") {
|
|
25
|
+
reader := bufio.NewReader(os.Stdin)
|
|
26
|
+
fmt.Printf("🧬 Select language template [JavaScript (js) / TypeScript (ts)] (default: js): ")
|
|
27
|
+
langInput, _ := reader.ReadString('\n')
|
|
28
|
+
isTS = strings.TrimSpace(strings.ToLower(langInput)) == "ts"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
runInitWithParams(projectName, isTS)
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func init() {
|
|
36
|
+
initCmd.Flags().BoolVarP(&IsTSFlag, "ts", "t", false, "Use TypeScript template")
|
|
37
|
+
rootCmd.AddCommand(initCmd)
|
|
38
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bufio"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"create-fusee/internal/assets"
|
|
11
|
+
"github.com/spf13/cobra"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
var IsTSFlag bool
|
|
15
|
+
|
|
16
|
+
var rootCmd = &cobra.Command{
|
|
17
|
+
Use: "fusee",
|
|
18
|
+
Short: "Fusée is a high-performance reactive framework CLI",
|
|
19
|
+
Long: `Fusée CLI helps you scaffold new projects, generate components,
|
|
20
|
+
and manage your Fusée application development workflow.`,
|
|
21
|
+
Args: cobra.MaximumNArgs(1),
|
|
22
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
23
|
+
if len(args) > 0 {
|
|
24
|
+
isTS := IsTSFlag
|
|
25
|
+
if !cmd.Flags().Changed("ts") {
|
|
26
|
+
reader := bufio.NewReader(os.Stdin)
|
|
27
|
+
fmt.Printf("🧬 Select language template [JavaScript (js) / TypeScript (ts)] (default: js): ")
|
|
28
|
+
langInput, _ := reader.ReadString('\n')
|
|
29
|
+
isTS = strings.TrimSpace(strings.ToLower(langInput)) == "ts"
|
|
30
|
+
}
|
|
31
|
+
runInitWithParams(args[0], isTS)
|
|
32
|
+
} else {
|
|
33
|
+
cmd.Help()
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func Execute() {
|
|
39
|
+
if err := rootCmd.Execute(); err != nil {
|
|
40
|
+
fmt.Println(err)
|
|
41
|
+
os.Exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func init() {
|
|
46
|
+
rootCmd.Flags().BoolVarP(&IsTSFlag, "ts", "t", false, "Use TypeScript template")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func runInitWithParams(projectName string, isTS bool) {
|
|
50
|
+
if strings.ContainsAny(projectName, " !@#$%^&*()") {
|
|
51
|
+
fmt.Printf("❌ Error: Project name '%s' contains invalid characters.\n", projectName)
|
|
52
|
+
os.Exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
projectPath, err := filepath.Abs(projectName)
|
|
56
|
+
if err != nil {
|
|
57
|
+
fmt.Printf("❌ Error: Could not determine absolute path: %v\n", err)
|
|
58
|
+
os.Exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if _, err := os.Stat(projectPath); !os.IsNotExist(err) && projectName != "." {
|
|
62
|
+
fmt.Printf("❌ Error: Directory '%s' already exists.\n", projectName)
|
|
63
|
+
os.Exit(1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ext := "js"
|
|
67
|
+
if isTS {
|
|
68
|
+
ext = "ts"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fmt.Printf("\n🚀 Scaffolding a new project in: %s...\n", projectPath)
|
|
72
|
+
|
|
73
|
+
config := assets.Config{
|
|
74
|
+
ProjectName: filepath.Base(projectName),
|
|
75
|
+
IsTS: isTS,
|
|
76
|
+
Ext: ext,
|
|
77
|
+
BtnStyle: "background:#ff3333; color:white; border:none; width:50px; height:50px; border-radius:15px; cursor:pointer; font-size:1.5rem; transition: 0.2s;",
|
|
78
|
+
}
|
|
79
|
+
if projectName == "." {
|
|
80
|
+
config.ProjectName = "fusee-project"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
dirs := []string{"app/components", "app/pages", "app/routes", "framework"}
|
|
84
|
+
for _, d := range dirs {
|
|
85
|
+
if err := os.MkdirAll(filepath.Join(projectPath, d), 0755); err != nil {
|
|
86
|
+
fmt.Printf("❌ Error: Could not create directory %s: %v\n", d, err)
|
|
87
|
+
os.Exit(1)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fmt.Println("📦 Injecting Fusée Core Engine...")
|
|
92
|
+
if err := assets.CopyEmbeddedDir("embed/framework", filepath.Join(projectPath, "framework"), isTS); err != nil {
|
|
93
|
+
fmt.Printf("❌ Error: Could not inject framework: %v\n", err)
|
|
94
|
+
os.Exit(1)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
files := map[string]string{
|
|
98
|
+
"templates/package.json.tmpl": "package.json",
|
|
99
|
+
"templates/vite.config.js.tmpl": "vite.config.js",
|
|
100
|
+
"templates/index.html.tmpl": "index.html",
|
|
101
|
+
"templates/main.tmpl": "app/main." + ext,
|
|
102
|
+
"templates/components/Loading.tmpl": "app/components/Loading." + ext,
|
|
103
|
+
"templates/pages/layout.tmpl": "app/pages/_layout." + ext,
|
|
104
|
+
"templates/pages/index.tmpl": "app/pages/index." + ext,
|
|
105
|
+
"templates/pages/about.tmpl": "app/pages/about." + ext,
|
|
106
|
+
"templates/Counter.tmpl": "app/components/Counter." + ext,
|
|
107
|
+
}
|
|
108
|
+
if isTS {
|
|
109
|
+
files["templates/tsconfig.json.tmpl"] = "tsconfig.json"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for src, dest := range files {
|
|
113
|
+
if err := assets.WriteTemplate(src, filepath.Join(projectPath, dest), config); err != nil {
|
|
114
|
+
fmt.Printf("❌ Error: Could not write file %s: %v\n", dest, err)
|
|
115
|
+
os.Exit(1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fmt.Println("\n✅ Fusée Project Ready!")
|
|
120
|
+
fmt.Printf("👉 Run: cd %s && npm install && npm run dev\n\n", projectName)
|
|
121
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
2
|
+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
3
|
+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
4
|
+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
5
|
+
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
|
6
|
+
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
|
7
|
+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
8
|
+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
9
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
10
|
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
package assets
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"embed"
|
|
5
|
+
"io/fs"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"text/template"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
//go:embed all:templates all:embed/framework
|
|
13
|
+
var EmbeddedFiles embed.FS
|
|
14
|
+
|
|
15
|
+
type Config struct {
|
|
16
|
+
ProjectName string
|
|
17
|
+
IsTS bool
|
|
18
|
+
Ext string
|
|
19
|
+
BtnStyle string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func CopyEmbeddedDir(srcDir, destDir string, keepTypes bool) error {
|
|
23
|
+
return fs.WalkDir(EmbeddedFiles, srcDir, func(path string, d fs.DirEntry, err error) error {
|
|
24
|
+
if err != nil {
|
|
25
|
+
return err
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
relPath := strings.TrimPrefix(path, srcDir)
|
|
29
|
+
relPath = strings.TrimPrefix(relPath, "/")
|
|
30
|
+
|
|
31
|
+
if relPath == "" {
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if !keepTypes && (strings.HasSuffix(relPath, ".d.ts") || strings.HasPrefix(relPath, "types")) {
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
parts := strings.Split(relPath, "/")
|
|
40
|
+
for _, part := range parts {
|
|
41
|
+
if part == "bin" || part == "node_modules" || part == ".git" || part == "dist" || part == "__tests__" {
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
destPath := filepath.Join(destDir, filepath.FromSlash(relPath))
|
|
47
|
+
|
|
48
|
+
if d.IsDir() {
|
|
49
|
+
return os.MkdirAll(destPath, 0755)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
data, err := EmbeddedFiles.ReadFile(path)
|
|
53
|
+
if err != nil {
|
|
54
|
+
return err
|
|
55
|
+
}
|
|
56
|
+
return os.WriteFile(destPath, data, 0644)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func WriteTemplate(tmplPath, destPath string, config Config) error {
|
|
61
|
+
data, err := EmbeddedFiles.ReadFile(tmplPath)
|
|
62
|
+
if err != nil {
|
|
63
|
+
return err
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tmpl, err := template.New(tmplPath).Parse(string(data))
|
|
67
|
+
if err != nil {
|
|
68
|
+
return err
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
f, err := os.Create(destPath)
|
|
72
|
+
if err != nil {
|
|
73
|
+
return err
|
|
74
|
+
}
|
|
75
|
+
defer f.Close()
|
|
76
|
+
|
|
77
|
+
return tmpl.Execute(f, config)
|
|
78
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { effect, batch } from './signal.js'
|
|
2
|
+
import { processDirectives } from './directives.js'
|
|
3
|
+
import { parseSlots } from './component.js'
|
|
4
|
+
import {
|
|
5
|
+
evaluateExpression,
|
|
6
|
+
parseInterpolation,
|
|
7
|
+
sanitizeAttr,
|
|
8
|
+
MUSTACHE_RE
|
|
9
|
+
} from './evaluator.js'
|
|
10
|
+
|
|
11
|
+
export function mountTemplate(template, container, context, components) {
|
|
12
|
+
const effects = []
|
|
13
|
+
let processed = template.replace(/(<[a-zA-Z0-9-]+\s[^>]*?)\s:([a-zA-Z][a-zA-Z0-9-]*)=/g, '$1 data-bind-$2=')
|
|
14
|
+
|
|
15
|
+
for (const name of Object.keys(components)) {
|
|
16
|
+
const reWithSlot = new RegExp(
|
|
17
|
+
`\\{\\{\\s*${name}(\\s[^}]*?|\\s*)\\}\\}([\\s\\S]*?)\\{\\{\\s*\\/${name}\\s*\\}\\}`,
|
|
18
|
+
'g'
|
|
19
|
+
)
|
|
20
|
+
const reNoSlot = new RegExp(`\\{\\{\\s*${name}(\\s+.*?|\\s*)\\}\\}`, 'g')
|
|
21
|
+
|
|
22
|
+
processed = processed.replace(reWithSlot, (match, propsStr, slotContent) => {
|
|
23
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
24
|
+
const encodedSlot = encodeURIComponent(slotContent.trim())
|
|
25
|
+
return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
processed = processed.replace(reNoSlot, (match, propsStr) => {
|
|
29
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
30
|
+
return `<div data-component="${name}"${attrs}></div>`
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
container.innerHTML = processed
|
|
35
|
+
compileNode(container, context, components, effects)
|
|
36
|
+
|
|
37
|
+
return { effects }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseComponentAttrs(propsStr) {
|
|
41
|
+
let attrs = ''
|
|
42
|
+
if (!propsStr || !propsStr.trim()) return attrs
|
|
43
|
+
|
|
44
|
+
const attrRegex = /(@?:?[a-zA-Z0-9-]+)="([^"]*)"/g
|
|
45
|
+
let attrMatch
|
|
46
|
+
while ((attrMatch = attrRegex.exec(propsStr)) !== null) {
|
|
47
|
+
const attrName = attrMatch[1]
|
|
48
|
+
const attrValue = attrMatch[2]
|
|
49
|
+
|
|
50
|
+
if (attrName.startsWith('@')) {
|
|
51
|
+
attrs += ` data-on-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
52
|
+
} else if (attrName.startsWith(':')) {
|
|
53
|
+
attrs += ` data-bind-prop-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
54
|
+
} else {
|
|
55
|
+
attrs += ` data-prop-${attrName.toLowerCase()}="${attrValue}"`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return attrs
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function compileNode(node, context, components, effects) {
|
|
62
|
+
if (node.nodeType === 1 && node.hasAttribute('f-once')) {
|
|
63
|
+
node.removeAttribute('f-once')
|
|
64
|
+
const temporaryEffects = []
|
|
65
|
+
|
|
66
|
+
processDirectives(node, context, components, temporaryEffects)
|
|
67
|
+
processBindingAttrs(node, context, temporaryEffects)
|
|
68
|
+
processMustaches(node, context, temporaryEffects)
|
|
69
|
+
bindComponents(node, components, context, temporaryEffects)
|
|
70
|
+
|
|
71
|
+
let child = node.firstChild
|
|
72
|
+
while (child) {
|
|
73
|
+
compileOnce(child, context, components, temporaryEffects)
|
|
74
|
+
child = child.nextSibling
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (let i = temporaryEffects.length - 1; i >= 0; i--) {
|
|
78
|
+
const cleanup = temporaryEffects[i]
|
|
79
|
+
if (typeof cleanup === 'function') cleanup()
|
|
80
|
+
}
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (node.nodeType === 1) {
|
|
85
|
+
if (processDirectives(node, context, components, effects)) return
|
|
86
|
+
processBindingAttrs(node, context, effects)
|
|
87
|
+
bindComponents(node, components, context, effects)
|
|
88
|
+
} else if (node.nodeType === 3) {
|
|
89
|
+
processMustaches(node, context, effects)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let child = node.firstChild
|
|
93
|
+
while (child) {
|
|
94
|
+
compileNode(child, context, components, effects)
|
|
95
|
+
child = child.nextSibling
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function compileOnce(node, context, components, effects) {
|
|
100
|
+
if (node.nodeType === 1) {
|
|
101
|
+
if (processDirectives(node, context, components, effects)) return
|
|
102
|
+
processBindingAttrs(node, context, effects)
|
|
103
|
+
bindComponents(node, components, context, effects)
|
|
104
|
+
} else if (node.nodeType === 3) {
|
|
105
|
+
processMustaches(node, context, effects)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let child = node.firstChild
|
|
109
|
+
while (child) {
|
|
110
|
+
compileOnce(child, context, components, effects)
|
|
111
|
+
child = child.nextSibling
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function processMustaches(node, context, effects) {
|
|
116
|
+
if (node.nodeType !== 3) return
|
|
117
|
+
const text = node.textContent
|
|
118
|
+
if (!MUSTACHE_RE.test(text)) return
|
|
119
|
+
|
|
120
|
+
const parts = parseInterpolation(text)
|
|
121
|
+
const fragment = document.createDocumentFragment()
|
|
122
|
+
|
|
123
|
+
for (const part of parts) {
|
|
124
|
+
if (part.type === 'static') {
|
|
125
|
+
fragment.appendChild(document.createTextNode(part.value))
|
|
126
|
+
} else {
|
|
127
|
+
const reactiveNode = document.createTextNode('')
|
|
128
|
+
fragment.appendChild(reactiveNode)
|
|
129
|
+
const key = part.key
|
|
130
|
+
const e = effect(() => {
|
|
131
|
+
const resolved = evaluateExpression(key, context)
|
|
132
|
+
reactiveNode.textContent = String(resolved ?? '')
|
|
133
|
+
})
|
|
134
|
+
effects.push(e)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
node.parentNode.replaceChild(fragment, node)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function processBindingAttrs(el, context, effects) {
|
|
141
|
+
if (el.nodeType !== 1) return
|
|
142
|
+
for (const attr of [...el.attributes]) {
|
|
143
|
+
if (attr.name.startsWith('data-bind-') && !attr.name.startsWith('data-bind-prop-')) {
|
|
144
|
+
const realAttr = attr.name.slice(10)
|
|
145
|
+
const expr = attr.value
|
|
146
|
+
el.removeAttribute(attr.name)
|
|
147
|
+
|
|
148
|
+
const e = effect(() => {
|
|
149
|
+
let resolved = evaluateExpression(expr, context)
|
|
150
|
+
if (realAttr === 'class') {
|
|
151
|
+
if (!el._staticClass) el._staticClass = el.getAttribute('class') || ''
|
|
152
|
+
let dynamicClass = ''
|
|
153
|
+
if (Array.isArray(resolved)) dynamicClass = resolved.filter(Boolean).join(' ')
|
|
154
|
+
else if (resolved !== null && typeof resolved === 'object') {
|
|
155
|
+
dynamicClass = Object.entries(resolved).filter(([_, v]) => !!v).map(([k]) => k).join(' ')
|
|
156
|
+
} else dynamicClass = String(resolved ?? '')
|
|
157
|
+
el.className = (el._staticClass + ' ' + dynamicClass).trim()
|
|
158
|
+
} else if (realAttr === 'style') {
|
|
159
|
+
if (resolved !== null && typeof resolved === 'object') {
|
|
160
|
+
for (const k in resolved) el.style[k] = resolved[k]
|
|
161
|
+
} else el.style.cssText = String(resolved ?? '')
|
|
162
|
+
} else {
|
|
163
|
+
if (typeof resolved === 'boolean') {
|
|
164
|
+
resolved ? el.setAttribute(realAttr, '') : el.removeAttribute(realAttr)
|
|
165
|
+
} else el.setAttribute(realAttr, sanitizeAttr(realAttr, String(resolved)))
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
effects.push(e)
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (MUSTACHE_RE.test(attr.value)) {
|
|
173
|
+
const attrName = attr.name
|
|
174
|
+
const parts = parseInterpolation(attr.value)
|
|
175
|
+
el.removeAttribute(attrName)
|
|
176
|
+
const e = effect(() => {
|
|
177
|
+
let result = ''
|
|
178
|
+
for (const part of parts) {
|
|
179
|
+
result += part.type === 'static' ? part.value : String(evaluateExpression(part.key, context) ?? '')
|
|
180
|
+
}
|
|
181
|
+
el.setAttribute(attrName, sanitizeAttr(attrName, result))
|
|
182
|
+
})
|
|
183
|
+
effects.push(e)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function bindComponents(el, components, context, effects) {
|
|
189
|
+
if (el.nodeType !== 1) return
|
|
190
|
+
const name = el.getAttribute('data-component')
|
|
191
|
+
if (!name) return
|
|
192
|
+
|
|
193
|
+
el.removeAttribute('data-component')
|
|
194
|
+
|
|
195
|
+
const ComponentFn = components[name]
|
|
196
|
+
if (!ComponentFn) return
|
|
197
|
+
|
|
198
|
+
const props = {}
|
|
199
|
+
const listeners = {}
|
|
200
|
+
|
|
201
|
+
for (const attr of [...el.attributes]) {
|
|
202
|
+
const attrName = attr.name.toLowerCase()
|
|
203
|
+
|
|
204
|
+
if (attrName.startsWith('data-prop-')) {
|
|
205
|
+
props[attrName.slice(10)] = attr.value
|
|
206
|
+
|
|
207
|
+
} else if (attrName.startsWith('data-bind-prop-')) {
|
|
208
|
+
const propName = attrName.slice(15)
|
|
209
|
+
const expression = attr.value.trim()
|
|
210
|
+
|
|
211
|
+
if (expression in context) {
|
|
212
|
+
Object.defineProperty(props, propName, {
|
|
213
|
+
get() {
|
|
214
|
+
const val = context[expression]
|
|
215
|
+
return typeof val === 'function' && val.isSignal ? val() : val
|
|
216
|
+
},
|
|
217
|
+
enumerable: true,
|
|
218
|
+
configurable: true
|
|
219
|
+
})
|
|
220
|
+
} else {
|
|
221
|
+
Object.defineProperty(props, propName, {
|
|
222
|
+
get() { return evaluateExpression(expression, context) },
|
|
223
|
+
enumerable: true,
|
|
224
|
+
configurable: true
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
} else if (attrName.startsWith('data-on-')) {
|
|
229
|
+
const eventName = attrName.slice(8)
|
|
230
|
+
const handlerExpr = attr.value.trim()
|
|
231
|
+
|
|
232
|
+
listeners[eventName] = (...args) => {
|
|
233
|
+
const handler = context[handlerExpr]
|
|
234
|
+
if (typeof handler === 'function') {
|
|
235
|
+
handler(...args)
|
|
236
|
+
} else {
|
|
237
|
+
evaluateExpression(handlerExpr, context)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const rawSlot = el.getAttribute('data-slot')
|
|
244
|
+
const slots = rawSlot ? parseSlots(decodeURIComponent(rawSlot)) : {}
|
|
245
|
+
el.removeAttribute('data-slot')
|
|
246
|
+
|
|
247
|
+
const childComponent = ComponentFn(props, { listeners, slots, parent: context._instance })
|
|
248
|
+
childComponent.render(el)
|
|
249
|
+
|
|
250
|
+
if (effects) {
|
|
251
|
+
effects.push(() => childComponent.unmount())
|
|
252
|
+
}
|
|
253
|
+
}
|