- Static compiler with STRC pattern (Static Template Resolution with
Compartmentalized Layers)
- Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
}
- File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
.scss
- CLI tools: strata dev, strata build, strata g (generators)
- create-strata scaffolding CLI with Pokemon API example
- Dev server with WebSocket HMR (Hot Module Replacement)
- Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
LICENSE
182 lines
4.3 KiB
Go
182 lines
4.3 KiB
Go
package generator
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// InjectedScript represents a script file to be injected
|
|
type InjectedScript struct {
|
|
Name string
|
|
Content string
|
|
Position string // "head" or "body"
|
|
Priority int // Lower = earlier injection
|
|
}
|
|
|
|
// ScriptInjector handles the injectedscripts/ directory
|
|
type ScriptInjector struct {
|
|
scriptsDir string
|
|
scripts []InjectedScript
|
|
}
|
|
|
|
// NewScriptInjector creates a new script injector
|
|
func NewScriptInjector(projectDir string) *ScriptInjector {
|
|
return &ScriptInjector{
|
|
scriptsDir: filepath.Join(projectDir, "src", "injectedscripts"),
|
|
scripts: make([]InjectedScript, 0),
|
|
}
|
|
}
|
|
|
|
// LoadScripts loads all scripts from injectedscripts/ directory
|
|
func (si *ScriptInjector) LoadScripts() error {
|
|
if _, err := os.Stat(si.scriptsDir); os.IsNotExist(err) {
|
|
return nil // No injectedscripts directory, that's fine
|
|
}
|
|
|
|
entries, err := os.ReadDir(si.scriptsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := entry.Name()
|
|
if !strings.HasSuffix(name, ".js") {
|
|
continue
|
|
}
|
|
|
|
content, err := os.ReadFile(filepath.Join(si.scriptsDir, name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
script := si.parseScript(name, string(content))
|
|
si.scripts = append(si.scripts, script)
|
|
}
|
|
|
|
// Sort by priority
|
|
sort.Slice(si.scripts, func(i, j int) bool {
|
|
return si.scripts[i].Priority < si.scripts[j].Priority
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseScript parses a script file and extracts metadata from comments
|
|
func (si *ScriptInjector) parseScript(name string, content string) InjectedScript {
|
|
script := InjectedScript{
|
|
Name: strings.TrimSuffix(name, ".js"),
|
|
Content: content,
|
|
Position: "head", // default to head
|
|
Priority: 100, // default priority
|
|
}
|
|
|
|
// Parse special comments at the top
|
|
// /* @position: body */
|
|
// /* @priority: 10 */
|
|
lines := strings.Split(content, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "/*") && !strings.HasPrefix(line, "//") {
|
|
break
|
|
}
|
|
|
|
if strings.Contains(line, "@position:") {
|
|
if strings.Contains(line, "body") {
|
|
script.Position = "body"
|
|
} else {
|
|
script.Position = "head"
|
|
}
|
|
}
|
|
|
|
if strings.Contains(line, "@priority:") {
|
|
// Extract priority number
|
|
parts := strings.Split(line, "@priority:")
|
|
if len(parts) > 1 {
|
|
priority := strings.TrimSpace(parts[1])
|
|
priority = strings.TrimSuffix(priority, "*/")
|
|
priority = strings.TrimSpace(priority)
|
|
var p int
|
|
if _, err := parseIntFromString(priority, &p); err == nil {
|
|
script.Priority = p
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return script
|
|
}
|
|
|
|
// GetHeadScripts returns scripts for <head>
|
|
func (si *ScriptInjector) GetHeadScripts() []InjectedScript {
|
|
var result []InjectedScript
|
|
for _, s := range si.scripts {
|
|
if s.Position == "head" {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetBodyScripts returns scripts for end of <body>
|
|
func (si *ScriptInjector) GetBodyScripts() []InjectedScript {
|
|
var result []InjectedScript
|
|
for _, s := range si.scripts {
|
|
if s.Position == "body" {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// InjectIntoHTML injects scripts into HTML content
|
|
func (si *ScriptInjector) InjectIntoHTML(html string) string {
|
|
// Inject head scripts before </head>
|
|
headScripts := si.GetHeadScripts()
|
|
if len(headScripts) > 0 {
|
|
var headContent strings.Builder
|
|
for _, s := range headScripts {
|
|
headContent.WriteString("<!-- Injected: ")
|
|
headContent.WriteString(s.Name)
|
|
headContent.WriteString(" -->\n")
|
|
headContent.WriteString(s.Content)
|
|
headContent.WriteString("\n")
|
|
}
|
|
html = strings.Replace(html, "</head>", headContent.String()+"</head>", 1)
|
|
}
|
|
|
|
// Inject body scripts before </body>
|
|
bodyScripts := si.GetBodyScripts()
|
|
if len(bodyScripts) > 0 {
|
|
var bodyContent strings.Builder
|
|
for _, s := range bodyScripts {
|
|
bodyContent.WriteString("<!-- Injected: ")
|
|
bodyContent.WriteString(s.Name)
|
|
bodyContent.WriteString(" -->\n")
|
|
bodyContent.WriteString(s.Content)
|
|
bodyContent.WriteString("\n")
|
|
}
|
|
html = strings.Replace(html, "</body>", bodyContent.String()+"</body>", 1)
|
|
}
|
|
|
|
return html
|
|
}
|
|
|
|
func parseIntFromString(s string, target *int) (int, error) {
|
|
var n int
|
|
for _, c := range s {
|
|
if c >= '0' && c <= '9' {
|
|
n = n*10 + int(c-'0')
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
*target = n
|
|
return n, nil
|
|
}
|