boxwood 1.1.0 → 2.0.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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node test:*)",
5
+ "Bash(mkdir:*)",
6
+ "Bash(npm test:*)"
7
+ ],
8
+ "deny": []
9
+ }
10
+ }
package/README.md CHANGED
@@ -3,24 +3,71 @@
3
3
  [![npm](https://img.shields.io/npm/v/boxwood.svg)](https://www.npmjs.com/package/boxwood)
4
4
  [![build](https://github.com/buxlabs/boxwood/workflows/build/badge.svg)](https://github.com/buxlabs/boxwood/actions)
5
5
 
6
- > Server side templating engine written in JavaScript
6
+ > It's just JavaScript™ - A template engine that gets out of your way
7
7
 
8
- [boxwood](https://github.com/buxlabs/boxwood) was created to achieve the following design goals:
8
+ ## Why Boxwood?
9
9
 
10
- 1. templates can be split into components
11
- 2. css is hashed per component
12
- 3. css is automatically minified
13
- 4. critical css is inlined
14
- 5. templates can import other dependencies
15
- 6. inline images or svgs
16
- 7. i18n support
17
- 8. server side
18
- 9. good for seo
19
- 10. small (1 file, 890 LOC~)
20
- 11. easy to start, familiar syntax
21
- 12. easy to test
10
+ Unlike traditional template engines, Boxwood templates are **just JavaScript functions**. No new syntax to learn, no parsing overhead, and full access to the JavaScript ecosystem.
22
11
 
23
- The template starts with a standard js file, which builds a tree of nodes, that get rendered to html.
12
+ ```javascript
13
+ // This is your template - just a function that returns HTML nodes
14
+ const HomePage = ({ posts }) => {
15
+ return Div([
16
+ H1("Blog"),
17
+ posts.map(post => Article([
18
+ H2(post.title),
19
+ P(post.summary)
20
+ ]))
21
+ ])
22
+ }
23
+ ```
24
+
25
+ ## Key Advantages
26
+
27
+ ### Zero Learning Curve
28
+ If you know JavaScript, you already know Boxwood. Use `map`, `filter`, `if/else`, and all standard JS features naturally.
29
+
30
+ ### IDE Support
31
+ Get autocomplete, refactoring, and go-to-definition out of the box. Your templates are just code, so your editor understands them.
32
+
33
+ ### True Composition
34
+ Components are functions. Compose them like functions. No slots, no special APIs - just parameters and return values.
35
+
36
+ ### Performance
37
+ No template parsing at runtime. Templates are already JavaScript functions, eliminating parsing overhead.
38
+
39
+ ### Security Helpers
40
+ - Automatic HTML escaping by default
41
+ - Basic sanitization for loaded SVG/HTML files
42
+ - Path traversal protection for file operations
43
+ - Remember: security is ultimately your responsibility
44
+
45
+ ### Integrated CSS Management
46
+ - Automatic CSS scoping with hash-based class names
47
+ - CSS-in-JS with zero runtime
48
+ - Critical CSS inlining
49
+ - Automatic minification
50
+
51
+ ### Built-in i18n Support
52
+ First-class internationalization support with a simple, component-friendly API for multi-language applications.
53
+
54
+ ### Asset Handling
55
+ - Inline images as base64
56
+ - SVG loading with automatic sanitization
57
+ - JSON data loading
58
+ - Raw HTML imports with XSS protection
59
+
60
+ ### SEO Friendly
61
+ - Pure server-side rendering - search engines see fully rendered HTML
62
+ - Lightning fast pages with inlined critical CSS
63
+ - Minimal payload size improves Core Web Vitals scores
64
+ - No client-side hydration delays
65
+
66
+ ### Minimal Footprint
67
+ Single file implementation (~950 lines). No complex build process or heavy dependencies.
68
+
69
+ ### Testable by Design
70
+ Templates are pure functions - easy to unit test with any testing framework.
24
71
 
25
72
  ## Table of Contents
26
73
 
@@ -37,122 +84,117 @@ The template starts with a standard js file, which builds a tree of nodes, that
37
84
 
38
85
  ## Usage
39
86
 
40
- ```js
41
- const { compile } = require("boxwood")
42
- const { join } = require("path")
43
- // ...
44
- const path = join(__dirname, "index.js")
45
- const { template } = compile(path)
46
- // ...
47
- const html = template({ foo: "bar" })
48
- console.log(html)
49
- ```
50
-
51
- You can use [express-boxwood](https://www.npmjs.com/package/express-boxwood) for [express](https://www.npmjs.com/package/express).
52
-
53
- ## Syntax
87
+ Create a template file:
54
88
 
55
89
  ```js
56
- // example/index.js
57
- const layout = require("./layout")
58
- const banner = require("./banner")
90
+ // templates/greeting.js
91
+ const { Div, H1, P } = require("boxwood")
59
92
 
60
- module.exports = () => {
61
- return layout({ language: "en" }, [
62
- banner({
63
- title: "Hello, world!",
64
- description: "Lorem ipsum dolor sit amet",
65
- }),
93
+ module.exports = ({ name, message }) => {
94
+ return Div([
95
+ H1(`Hello, ${name}!`),
96
+ P(message)
66
97
  ])
67
98
  }
68
99
  ```
69
100
 
70
- ```js
71
- // example/layout/index.js
72
- const { component, css, doctype, html, body } = require("boxwood")
73
- const head = require("./head")
74
-
75
- const styles = css.load(__dirname)
76
-
77
- module.exports = component(
78
- ({ language }, children) => {
79
- return [
80
- doctype(),
81
- html({ lang: language }, [
82
- head(),
83
- body({ className: styles.layout }, children),
84
- ]),
85
- ]
86
- },
87
- { styles }
88
- )
89
- ```
101
+ Compile and render it:
90
102
 
91
103
  ```js
92
- // example/layout/head/index.js
93
- const { head, title } = require("boxwood")
104
+ // app.js
105
+ const { compile } = require("boxwood")
94
106
 
95
- module.exports = () => {
96
- return head([title("example")])
97
- }
107
+ const { template } = compile("./templates/greeting.js")
108
+ const html = template({
109
+ name: "World",
110
+ message: "Welcome to Boxwood"
111
+ })
112
+
113
+ console.log(html)
114
+ // <div><h1>Hello, World!</h1><p>Welcome to Boxwood</p></div>
98
115
  ```
99
116
 
117
+ For Express apps, use [express-boxwood](https://www.npmjs.com/package/express-boxwood).
118
+
119
+ ## Features
120
+
121
+ ### Components with CSS
122
+
100
123
  ```js
101
- // example/banner/index.js
102
- const { component, css, h1, p, section } = require("boxwood")
103
-
104
- const styles = css.load(__dirname)
105
-
106
- module.exports = component(
107
- ({ title, description }) => {
108
- return section({ className: styles.banner }, [
109
- h1(title),
110
- description && p(description),
111
- ])
112
- },
113
- { styles }
114
- )
124
+ // button.js
125
+ const { component, css, Button: ButtonTag } = require("boxwood")
126
+
127
+ const styles = css`
128
+ .button {
129
+ padding: 8px 16px;
130
+ background: blue;
131
+ color: white;
132
+ }
133
+ .secondary {
134
+ background: gray;
135
+ }
136
+ `
137
+
138
+ const Button = ({ variant, children }) => {
139
+ return ButtonTag({
140
+ // className accepts arrays - falsy values are automatically filtered
141
+ className: [styles.button, variant === 'secondary' && styles.secondary]
142
+ }, children)
143
+ }
144
+
145
+ module.exports = component(Button, { styles })
115
146
  ```
116
147
 
117
- ```js
118
- // example/banner/index.test.js
119
- const test = require("node:test")
120
- const assert = require("node:assert")
121
- const { compile } = require("boxwood")
148
+ ### Internationalization
122
149
 
123
- test("banner renders a title", async () => {
124
- const { template } = compile(__dirname)
125
- const html = template({ title: "foo" })
126
- assert(html.includes("<h1>foo</h1>"))
127
- })
150
+ ```js
151
+ // welcome.js
152
+ const { component, i18n, H1, P } = require("boxwood")
153
+
154
+ const Welcome = ({ translate, username }) => {
155
+ return [
156
+ H1(translate("greeting").replace("{name}", username)),
157
+ P(translate("intro"))
158
+ ]
159
+ }
128
160
 
129
- test("banner renders an optional description", async () => {
130
- const { template } = compile(__dirname)
131
- const html = template({ title: "foo", description: "bar" })
132
- assert(html.includes("<h1>foo</h1>"))
133
- assert(html.includes("<p>bar</p>"))
161
+ module.exports = component(Welcome, {
162
+ i18n: i18n.load(__dirname)
134
163
  })
135
164
  ```
136
165
 
137
- You can check the `test` dir for more examples.
166
+ ### Asset Loading
138
167
 
139
- ## Security
168
+ ```js
169
+ const { Img, Svg } = require("boxwood")
140
170
 
141
- By default, boxwood sanitizes all HTML, SVG and i18n content loaded via its API to protect against basic XSS attacks.
171
+ // Load and inline images
172
+ const Logo = Img.load("./assets/logo.png")
142
173
 
143
- Disabling sanitization ({ sanitize: false }) is only safe for trusted, developer-controlled files. Never use it with user-generated or untrusted content.
174
+ // Load and sanitize SVGs
175
+ const Icon = Svg.load("./assets/icon.svg")
144
176
 
145
- All file access is restricted to the project directory and symlinks are not allowed by default to prevent path traversal attacks.
177
+ module.exports = () => {
178
+ return [Logo(), Icon]
179
+ }
180
+ ```
181
+
182
+ Additional examples are available in the `test` directory.
183
+
184
+ ## Security
146
185
 
147
- That said, the library is pretty small so please review it and suggest improvements if you have any.
186
+ Boxwood provides basic security features:
148
187
 
149
- ## Maintainers
188
+ - HTML content is escaped by default
189
+ - Loaded SVG and HTML files are sanitized
190
+ - File access is restricted to the project directory
191
+ - Symlinks are blocked to prevent directory traversal
150
192
 
151
- [@emilos](https://github.com/emilos)
193
+ The `sanitize: false` option should only be used with trusted content. Security remains the developer's responsibility.
152
194
 
153
195
  ## Contributing
154
196
 
155
- All contributions are highly appreciated. Please feel free to open new issues and send PRs.
197
+ Issues and pull requests are welcome. The codebase is intentionally small and focused.
156
198
 
157
199
  ## License
158
200
 
@@ -0,0 +1,49 @@
1
+ // Example of using boxwood with TypeScript
2
+ import { Div, H1, Button, Form, Input, component, classes } from 'boxwood';
3
+
4
+ // Define typed component props
5
+ interface UserCardProps {
6
+ name: string;
7
+ email: string;
8
+ isActive: boolean;
9
+ }
10
+
11
+ // Create a typed component
12
+ const UserCard = component<UserCardProps>(
13
+ ({ name, email, isActive }, children) => {
14
+ return Div({
15
+ className: classes('user-card', { active: isActive })
16
+ }, [
17
+ H1({}, name),
18
+ Div({ className: 'email' }, email),
19
+ children
20
+ ]);
21
+ }
22
+ );
23
+
24
+ // Use the component with type checking
25
+ const app = Div({ id: 'app' }, [
26
+ UserCard({
27
+ name: 'John Doe',
28
+ email: 'john@example.com',
29
+ isActive: true
30
+ },
31
+ Button({ onclick: () => alert('Hello!') }, 'Click me')
32
+ ),
33
+
34
+ Form({ method: 'post' }, [
35
+ Input({
36
+ type: 'email',
37
+ name: 'email',
38
+ required: true,
39
+ placeholder: 'Enter email'
40
+ }),
41
+ Button({ type: 'submit' }, 'Submit')
42
+ ])
43
+ ]);
44
+
45
+ // TypeScript will provide:
46
+ // - Autocomplete for all element attributes
47
+ // - Type checking for attribute values
48
+ // - Error highlighting for invalid props
49
+ // - IntelliSense documentation