Skip to content

Commit 51c8109

Browse files
committed
implemented router' URL matching logic in multiple (non-JS) languages for server-side processing
1 parent 82e24e7 commit 51c8109

16 files changed

+2535
-0
lines changed

polyglot-utils/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Language-specific files to ignore
2+
__pycache__/
3+
*.pyc
4+
.venv/
5+
.ruby-version

polyglot-utils/README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Preact ISO URL Pattern Matching - Polyglot Utils
2+
3+
Multi-language implementations of URL pattern matching utilities for building bespoke server setups that need to preload JS/CSS resources or handle early 404 responses.
4+
5+
## Use Case
6+
7+
This utility is designed for server languages that **cannot do SSR/prerendering** but still want to provide better experiences. It enables servers to:
8+
9+
- 🚀 **Add preload head tags** for JS,CSS before serving HTML
10+
-**Return early 404 pages** for unmatched routes
11+
- 🎨 **Generate dynamic titles** based on route parameters
12+
- 📦 **Optimize resource loading** without full SSR capability
13+
14+
## How can I implement preloading of JS, CSS?
15+
16+
Typical implementation flow:
17+
18+
1. **Build-time Setup:**
19+
- Write your routes as an array in a JS file
20+
- Create a build script that exports route patterns and entry files to a `.json` file
21+
- Configure your frontend build tool to output a `manifest` file mapping entry files to final fingerprinted/hashed output JS/CSS files and dependencies
22+
23+
2. **Server-time Processing:**
24+
- Load the JSON route file when a request comes in
25+
- Match the requested URL against each route pattern until you find a match
26+
- Once matched, you have the source entry `.jsx` file
27+
- Load the build manifest file to find which JS chunk contains that code and its dependency files
28+
- Generate `<link rel="preload">` tags for each dependency (JS, CSS, images, icons)
29+
- Inject those head tags into the HTML before serving
30+
31+
3. **Result:**
32+
- Browsers start downloading critical resources immediately
33+
- Faster page loads without full SSR complexity
34+
- Early 404s for invalid routes
35+
36+
### Example - preloading of JS, CSS
37+
38+
Here's how you might integrate this into a server setup:
39+
40+
### 1. Route Configuration (routes.json)
41+
```json
42+
[
43+
{
44+
"path": "/users/:userId/posts",
45+
"component": "pages/UserPosts.jsx",
46+
"title": "Posts by :userId"
47+
},
48+
{
49+
"path": "/products/:category/:id",
50+
"component": "pages/Product.jsx",
51+
"title": "Product :id"
52+
}
53+
]
54+
```
55+
56+
### 2. Build Manifest (manifest.json)
57+
```json
58+
{
59+
"pages/UserPosts.jsx": {
60+
"file": "assets/UserPosts-abc123.js",
61+
"css": ["assets/UserPosts-def456.css"],
62+
"imports": ["chunks/shared-ghi789.js"]
63+
}
64+
}
65+
```
66+
67+
### 3. Server Implementation
68+
```python
69+
# Python example
70+
import json
71+
72+
routes = json.load(open('routes.json'))
73+
manifest = json.load(open('manifest.json'))
74+
75+
def handle_request(url_path):
76+
for route in routes:
77+
matches = preact_iso_url_pattern_match(url_path, route['path'])
78+
if matches:
79+
# Generate preload tags
80+
component = route['component']
81+
entry_info = manifest[component]
82+
83+
preload_tags = []
84+
for js_file in [entry_info['file']] + entry_info.get('imports', []):
85+
preload_tags.append(f'<link rel="modulepreload" crossorigin href="{js_file}">')
86+
87+
for css_file in entry_info.get('css', []):
88+
preload_tags.append(f'<link rel="stylesheet" crossorigin href="{css_file}">')
89+
# Generate dynamic title
90+
title = route['title']
91+
for param, value in matches['params'].items():
92+
title = title.replace(f':{param}', value)
93+
94+
return {
95+
'preload_tags': preload_tags,
96+
'title': title,
97+
'params': matches['params']
98+
}
99+
100+
# No match found - return early 404
101+
return None
102+
```
103+
104+
This approach gives you the performance benefits of resource preloading without the complexity of full server-side rendering!
105+
106+
## Available Languages
107+
108+
| Language | Implementation | Tests | Status |
109+
|----------|----------------|-------|---------|
110+
| **Go** | `go/preact_iso_url_pattern.go` | `go/preact_iso_url_pattern_test.go` | ✅ 51/51 tests |
111+
| **Python** | `python/preact_iso_url_pattern.py` | `python/test_preact_iso_url_pattern.py` | ✅ 51/51 tests |
112+
| **Ruby** | `ruby/preact-iso-url-pattern.rb` | `ruby/test_preact_iso_url_pattern.rb` | ✅ 51/51 tests |
113+
| **PHP** | `php/preact-iso-url-pattern.php` | `php/test_preact_iso_url_pattern.php` | ✅ 51/51 tests |
114+
115+
## Running Tests
116+
117+
```bash
118+
# Run all tests across all languages
119+
./run_tests.sh
120+
121+
# Or run individual language tests
122+
cd go && go test -v
123+
cd python && python3 test_preact_iso_url_pattern.py
124+
cd ruby && ruby test_preact_iso_url_pattern.rb
125+
cd php && php test_preact_iso_url_pattern.php
126+
```
127+
128+
## Language-Specific Documentation
129+
130+
Each language implementation has detailed documentation with usage examples and API references:
131+
132+
- **[Go](go/README.md)** - Static typing with struct returns
133+
- **[Python](python/README.md)** - Dictionary-based with optional typing
134+
- **[Ruby](ruby/README.md)** - Hash-based with flexible syntax
135+
- **[PHP](php/README.md)** - Mixed array/object approach for JSON compatibility
136+

polyglot-utils/go/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Go Implementation
2+
3+
URL pattern matching utility for Go servers.
4+
5+
## Setup
6+
7+
Code tested on Go 1.24.x.
8+
9+
```sh
10+
# If using in a project, initialize go module
11+
go mod init myproject
12+
# No third party dependencies needed. Just run the tests or use the function directly
13+
```
14+
15+
## Running Tests
16+
17+
```sh
18+
go test -v
19+
```
20+
21+
## Usage
22+
23+
```go
24+
package main
25+
26+
import "fmt"
27+
28+
func main() {
29+
matches := preactIsoUrlPatternMatch("/users/test%40example.com/posts", "/users/:userId/posts", nil)
30+
if matches != nil {
31+
fmt.Printf("User ID: %s\n", matches.Params["userId"]) // Output: test@example.com
32+
}
33+
}
34+
```
35+
36+
## Function Signature
37+
38+
```go
39+
func preactIsoUrlPatternMatch(url, route string, matches *Matches) *Matches
40+
```
41+
42+
### Parameters
43+
44+
- `url` (string): The URL path to match
45+
- `route` (string): The route pattern with parameters
46+
- `matches` (*Matches): Optional pre-existing matches to extend
47+
48+
### Return Value
49+
50+
Returns a `*Matches` struct on success, or `nil` if no match:
51+
52+
```go
53+
type Matches struct {
54+
Params map[string]string
55+
Rest string
56+
}
57+
```
58+
59+
## Route Patterns
60+
61+
| Pattern | Description | Example |
62+
|---------|-------------|---------|
63+
| `/users/:id` | Named parameter | `{id: "123"}` |
64+
| `/users/:id?` | Optional parameter | `{id: ""}` |
65+
| `/files/:path+` | Required rest parameter | `{path: "docs/readme.txt"}` |
66+
| `/static/:path*` | Optional rest parameter | `{path: "css/main.css"}` |
67+
| `/static/*` | Anonymous wildcard | `{Rest: "/images/logo.png"}` |

polyglot-utils/go/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module myproject
2+
3+
go 1.13
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Run program: go run preact-iso-url-pattern.go
2+
3+
package main
4+
5+
import (
6+
// "fmt"
7+
"net/url"
8+
"regexp"
9+
"strings"
10+
)
11+
12+
type Matches struct {
13+
Params map[string]string `json:"params"`
14+
Rest string `json:"rest,omitempty"`
15+
}
16+
17+
func preactIsoUrlPatternMatch(urlStr, route string, matches *Matches) *Matches {
18+
if matches == nil {
19+
matches = &Matches{
20+
Params: make(map[string]string),
21+
}
22+
}
23+
urlParts := filterEmpty(strings.Split(urlStr, "/"))
24+
routeParts := filterEmpty(strings.Split(route, "/"))
25+
26+
for i := 0; i < max(len(urlParts), len(routeParts)); i++ {
27+
var m, param, flag string
28+
if i < len(routeParts) {
29+
re := regexp.MustCompile(`^(:?)(.*?)([+*?]?)$`)
30+
matches := re.FindStringSubmatch(routeParts[i])
31+
if len(matches) > 3 {
32+
m, param, flag = matches[1], matches[2], matches[3]
33+
}
34+
}
35+
36+
var val string
37+
if i < len(urlParts) {
38+
val = urlParts[i]
39+
}
40+
41+
// segment match:
42+
if m == "" && param == val {
43+
continue
44+
}
45+
46+
// /foo/* match
47+
if m == "" && val != "" && flag == "*" {
48+
matches.Rest = "/" + strings.Join(urlParts[i:], "/")
49+
break
50+
}
51+
52+
// segment mismatch / missing required field:
53+
if m == "" || (val == "" && flag != "?" && flag != "*") {
54+
return nil
55+
}
56+
57+
rest := flag == "+" || flag == "*"
58+
59+
// rest (+/*) match:
60+
if rest {
61+
decodedParts := make([]string, len(urlParts[i:]))
62+
for j, part := range urlParts[i:] {
63+
decoded, err := url.QueryUnescape(part)
64+
if err != nil {
65+
decoded = part // fallback to original if decode fails
66+
}
67+
decodedParts[j] = decoded
68+
}
69+
val = strings.Join(decodedParts, "/")
70+
} else if val != "" {
71+
// normal/optional field: decode val (like JavaScript does)
72+
decoded, err := url.QueryUnescape(val)
73+
if err != nil {
74+
decoded = urlParts[i]
75+
}
76+
val = decoded
77+
}
78+
79+
matches.Params[param] = val
80+
81+
if rest {
82+
break
83+
}
84+
}
85+
86+
return matches
87+
}
88+
89+
func filterEmpty(s []string) []string {
90+
var result []string
91+
for _, str := range s {
92+
if str != "" {
93+
result = append(result, str)
94+
}
95+
}
96+
return result
97+
}
98+
99+
func max(a, b int) int {
100+
if a > b {
101+
return a
102+
}
103+
return b
104+
}
105+
106+
// Example usage:
107+
// func main() {
108+
// params := &Matches{Params: make(map[string]string)}
109+
// fmt.Println(preactIsoUrlPatternMatch("/foo/bar%20baz", "/foo/:param", params))
110+
//
111+
// params := &Matches{Params: make(map[string]string)}
112+
// fmt.Println(preactIsoUrlPatternMatch("/foo/bar/baz", "/foo/*"))
113+
//
114+
// params := &Matches{Params: make(map[string]string)}
115+
// fmt.Println(preactIsoUrlPatternMatch("/foo", "/foo/:param?"))
116+
//
117+
// params := &Matches{Params: make(map[string]string)}
118+
// fmt.Println(preactIsoUrlPatternMatch("/foo/bar", "/bar/:param"))
119+
//
120+
// params := &Matches{Params: make(map[string]string)}
121+
// fmt.Println(preactIsoUrlPatternMatch("/users/test%40example.com/posts", "/users/:userId/posts"))
122+
// }

0 commit comments

Comments
 (0)