@stacksjs/sanitizer 0.2.5 → 0.2.8
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 +33 -318
- package/dist/index.d.ts +2 -1
- package/dist/presets.d.ts +1 -1
- package/dist/sanitizer.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,356 +5,71 @@
|
|
|
5
5
|
[](http://commitizen.github.io/cz-cli/)
|
|
6
6
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# @stacksjs/sanitizer
|
|
9
9
|
|
|
10
|
-
A
|
|
10
|
+
A fast, native Bun-powered HTML sanitizer with DOMPurify-like features. Protection against XSS and malicious content.
|
|
11
11
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
- **Vue-like SFC** - `<script>`, `<template>`, `<style>` structure
|
|
15
|
-
- **Auto-imported Components** - Use `<Card />` directly, no imports needed
|
|
16
|
-
- **Two-way Binding** - `x-model` and `x-text` for reactive forms
|
|
17
|
-
- **Blade Directives** - `@if`, `@foreach`, `@layout`, `@section`
|
|
18
|
-
- **Props & Slots** - Pass data and content to components
|
|
19
|
-
- **200K+ Icons** - Built-in Iconify integration
|
|
20
|
-
- **Custom Directives** - Extend with your own directives
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
12
|
+
## Installation
|
|
23
13
|
|
|
24
14
|
```bash
|
|
25
|
-
bun add
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
```toml
|
|
29
|
-
# bunfig.toml
|
|
30
|
-
preload = ["bun-plugin-stx"]
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Single File Components
|
|
34
|
-
|
|
35
|
-
STX components use a Vue-like structure:
|
|
36
|
-
|
|
37
|
-
```html
|
|
38
|
-
<!-- components/Greeting.stx -->
|
|
39
|
-
<script server>
|
|
40
|
-
// Server-side only - used for SSR, stripped from output
|
|
41
|
-
const name = props.name || 'World'
|
|
42
|
-
const time = new Date().toLocaleTimeString()
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
<template>
|
|
46
|
-
<div class="greeting">
|
|
47
|
-
<h1>Hello, {{ name }}!</h1>
|
|
48
|
-
<p>Current time: {{ time }}</p>
|
|
49
|
-
<slot />
|
|
50
|
-
</div>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<style>
|
|
54
|
-
.greeting {
|
|
55
|
-
padding: 2rem;
|
|
56
|
-
background: #f5f5f5;
|
|
57
|
-
}
|
|
58
|
-
</style>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Script Types
|
|
62
|
-
|
|
63
|
-
| Type | Behavior |
|
|
64
|
-
|------|----------|
|
|
65
|
-
| `<script server>` | SSR only - extracted for variables, stripped from output |
|
|
66
|
-
| `<script client>` | Client only - preserved for browser, skips server evaluation |
|
|
67
|
-
| `<script>` | Both - runs on server AND preserved for client |
|
|
68
|
-
|
|
69
|
-
## Components
|
|
70
|
-
|
|
71
|
-
Components in `components/` are auto-imported using PascalCase:
|
|
72
|
-
|
|
73
|
-
```html
|
|
74
|
-
<!-- pages/home.stx -->
|
|
75
|
-
<Header />
|
|
76
|
-
|
|
77
|
-
<main>
|
|
78
|
-
<UserCard name="John" role="Admin" />
|
|
79
|
-
<Card title="Welcome">
|
|
80
|
-
<p>This goes into the slot!</p>
|
|
81
|
-
</Card>
|
|
82
|
-
</main>
|
|
83
|
-
|
|
84
|
-
<Footer />
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Props
|
|
88
|
-
|
|
89
|
-
Pass data to components via attributes:
|
|
90
|
-
|
|
91
|
-
```html
|
|
92
|
-
<!-- String prop -->
|
|
93
|
-
<Card title="Hello" />
|
|
94
|
-
|
|
95
|
-
<!-- Expression binding with : -->
|
|
96
|
-
<Card :count="items.length" :active="isActive" />
|
|
97
|
-
|
|
98
|
-
<!-- Mustache interpolation -->
|
|
99
|
-
<Card title="{{ userName }}" />
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Access props in components:
|
|
103
|
-
|
|
104
|
-
```html
|
|
105
|
-
<script server>
|
|
106
|
-
const title = props.title || 'Default'
|
|
107
|
-
const count = props.count || 0
|
|
108
|
-
</script>
|
|
109
|
-
|
|
110
|
-
<template>
|
|
111
|
-
<h1>{{ title }}</h1>
|
|
112
|
-
<p>Count: {{ count }}</p>
|
|
113
|
-
</template>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Slots
|
|
117
|
-
|
|
118
|
-
Use `<slot />` to inject content:
|
|
119
|
-
|
|
120
|
-
```html
|
|
121
|
-
<!-- components/Card.stx -->
|
|
122
|
-
<template>
|
|
123
|
-
<div class="card">
|
|
124
|
-
<h2>{{ props.title }}</h2>
|
|
125
|
-
<slot />
|
|
126
|
-
</div>
|
|
127
|
-
</template>
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
```html
|
|
131
|
-
<!-- Usage -->
|
|
132
|
-
<Card title="News">
|
|
133
|
-
<p>This content appears in the slot!</p>
|
|
134
|
-
</Card>
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Explicit Imports
|
|
138
|
-
|
|
139
|
-
For components outside `components/`, use `@import`:
|
|
140
|
-
|
|
141
|
-
```html
|
|
142
|
-
@import('layouts/Sidebar')
|
|
143
|
-
@import('shared/Button', 'shared/Modal')
|
|
144
|
-
|
|
145
|
-
<Sidebar />
|
|
146
|
-
<Button label="Click me" />
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Layouts
|
|
150
|
-
|
|
151
|
-
Wrap pages with common structure using `@layout`:
|
|
152
|
-
|
|
153
|
-
```html
|
|
154
|
-
<!-- layouts/default.stx -->
|
|
155
|
-
<!DOCTYPE html>
|
|
156
|
-
<html>
|
|
157
|
-
<head>
|
|
158
|
-
<title>{{ title || 'My App' }}</title>
|
|
159
|
-
</head>
|
|
160
|
-
<body>
|
|
161
|
-
<Header />
|
|
162
|
-
<main>
|
|
163
|
-
@yield('content')
|
|
164
|
-
</main>
|
|
165
|
-
<Footer />
|
|
166
|
-
</body>
|
|
167
|
-
</html>
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
```html
|
|
171
|
-
<!-- pages/about.stx -->
|
|
172
|
-
@layout('default')
|
|
173
|
-
|
|
174
|
-
@section('content')
|
|
175
|
-
<h1>About Us</h1>
|
|
176
|
-
<p>Welcome to our site.</p>
|
|
177
|
-
@endsection
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Two-Way Binding (x-element)
|
|
181
|
-
|
|
182
|
-
For reactive forms, use x-element directives:
|
|
183
|
-
|
|
184
|
-
```html
|
|
185
|
-
<div x-data="{ message: '', count: 0 }">
|
|
186
|
-
<!-- Two-way binding -->
|
|
187
|
-
<input x-model="message" placeholder="Type here..." />
|
|
188
|
-
|
|
189
|
-
<!-- Reactive display -->
|
|
190
|
-
<p>You typed: <span x-text="message"></span></p>
|
|
191
|
-
|
|
192
|
-
<!-- Event handling -->
|
|
193
|
-
<button @click="count++">Increment</button>
|
|
194
|
-
<button @click="count--">Decrement</button>
|
|
195
|
-
<span x-text="count"></span>
|
|
196
|
-
</div>
|
|
15
|
+
bun add @stacksjs/sanitizer
|
|
197
16
|
```
|
|
198
17
|
|
|
199
|
-
|
|
200
|
-
|-----------|---------|
|
|
201
|
-
| `x-data` | Define reactive scope |
|
|
202
|
-
| `x-model` | Two-way binding for inputs |
|
|
203
|
-
| `x-text` | Reactive text content |
|
|
204
|
-
| `@click` | Event handling |
|
|
205
|
-
|
|
206
|
-
## Template Directives
|
|
207
|
-
|
|
208
|
-
### Conditionals
|
|
209
|
-
|
|
210
|
-
```html
|
|
211
|
-
@if (user.isAdmin)
|
|
212
|
-
<AdminPanel />
|
|
213
|
-
@elseif (user.isEditor)
|
|
214
|
-
<EditorTools />
|
|
215
|
-
@else
|
|
216
|
-
<UserView />
|
|
217
|
-
@endif
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Loops
|
|
221
|
-
|
|
222
|
-
```html
|
|
223
|
-
@foreach (items as item)
|
|
224
|
-
<li>{{ item.name }}</li>
|
|
225
|
-
@endforeach
|
|
226
|
-
|
|
227
|
-
@for (let i = 0; i < 5; i++)
|
|
228
|
-
<li>Item {{ i }}</li>
|
|
229
|
-
@endfor
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### Auth Guards
|
|
233
|
-
|
|
234
|
-
```html
|
|
235
|
-
@auth
|
|
236
|
-
<p>Welcome back, {{ user.name }}!</p>
|
|
237
|
-
@endauth
|
|
238
|
-
|
|
239
|
-
@guest
|
|
240
|
-
<a href="/login">Please log in</a>
|
|
241
|
-
@endguest
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Output
|
|
245
|
-
|
|
246
|
-
```html
|
|
247
|
-
<!-- Escaped (safe) -->
|
|
248
|
-
{{ userInput }}
|
|
249
|
-
|
|
250
|
-
<!-- Raw HTML (trusted content only) -->
|
|
251
|
-
{!! trustedHtml !!}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Custom Directives
|
|
255
|
-
|
|
256
|
-
Register custom directives in your build:
|
|
18
|
+
## Usage
|
|
257
19
|
|
|
258
20
|
```typescript
|
|
259
|
-
import {
|
|
260
|
-
|
|
261
|
-
const uppercase: CustomDirective = {
|
|
262
|
-
name: 'uppercase',
|
|
263
|
-
handler: (content, params) => params[0]?.toUpperCase() || content.toUpperCase()
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const wrap: CustomDirective = {
|
|
267
|
-
name: 'wrap',
|
|
268
|
-
hasEndTag: true,
|
|
269
|
-
handler: (content, params) => `<div class="${params[0] || 'wrapper'}">${content}</div>`
|
|
270
|
-
}
|
|
21
|
+
import { sanitize } from '@stacksjs/sanitizer'
|
|
271
22
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
customDirectives: [uppercase, wrap]
|
|
276
|
-
})]
|
|
277
|
-
})
|
|
23
|
+
// Basic sanitization
|
|
24
|
+
const clean = sanitize('<script>alert("xss")</script><p>Hello</p>')
|
|
25
|
+
// => '<p>Hello</p>'
|
|
278
26
|
```
|
|
279
27
|
|
|
280
|
-
|
|
281
|
-
<!-- Usage -->
|
|
282
|
-
<p>@uppercase('hello world')</p>
|
|
28
|
+
### Presets
|
|
283
29
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
@endwrap
|
|
287
|
-
```
|
|
30
|
+
```typescript
|
|
31
|
+
import { sanitize, strict, basic, relaxed, markdown } from '@stacksjs/sanitizer'
|
|
288
32
|
|
|
289
|
-
|
|
33
|
+
// Strict - minimal tags allowed
|
|
34
|
+
sanitize(html, strict)
|
|
290
35
|
|
|
291
|
-
|
|
36
|
+
// Basic - common formatting tags
|
|
37
|
+
sanitize(html, basic)
|
|
292
38
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<SearchIcon size="20" color="#333" />
|
|
296
|
-
```
|
|
39
|
+
// Relaxed - most HTML allowed
|
|
40
|
+
sanitize(html, relaxed)
|
|
297
41
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
bun stx iconify generate material-symbols
|
|
42
|
+
// Markdown - optimized for markdown output
|
|
43
|
+
sanitize(html, markdown)
|
|
301
44
|
```
|
|
302
45
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
```html
|
|
306
|
-
<!-- components/TodoApp.stx -->
|
|
307
|
-
<script server>
|
|
308
|
-
const title = props.title || 'My Todos'
|
|
309
|
-
</script>
|
|
46
|
+
### Utilities
|
|
310
47
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<h1>{{ title }}</h1>
|
|
48
|
+
```typescript
|
|
49
|
+
import { isSafe, escape, stripTags } from '@stacksjs/sanitizer'
|
|
314
50
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
<button type="submit">Add</button>
|
|
318
|
-
</form>
|
|
51
|
+
// Check if HTML is safe
|
|
52
|
+
isSafe('<p>Hello</p>') // true
|
|
319
53
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
@foreach (initialTodos as todo)
|
|
323
|
-
<li>{{ todo.text }}</li>
|
|
324
|
-
@endforeach
|
|
325
|
-
</ul>
|
|
326
|
-
@endif
|
|
327
|
-
</div>
|
|
328
|
-
</template>
|
|
54
|
+
// Escape HTML entities
|
|
55
|
+
escape('<script>') // '<script>'
|
|
329
56
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
max-width: 400px;
|
|
333
|
-
margin: 0 auto;
|
|
334
|
-
}
|
|
335
|
-
</style>
|
|
57
|
+
// Strip all HTML tags
|
|
58
|
+
stripTags('<p>Hello <b>world</b></p>') // 'Hello world'
|
|
336
59
|
```
|
|
337
60
|
|
|
338
61
|
## Documentation
|
|
339
62
|
|
|
340
63
|
- [Full Documentation](https://stx.sh)
|
|
341
|
-
- [Syntax Highlighting Guide](./docs/STX_SYNTAX_HIGHLIGHTING.md)
|
|
342
|
-
- [Examples](./examples/)
|
|
343
|
-
|
|
344
|
-
## Testing
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
bun test
|
|
348
|
-
```
|
|
349
64
|
|
|
350
65
|
## License
|
|
351
66
|
|
|
352
67
|
MIT
|
|
353
68
|
|
|
354
69
|
<!-- Badges -->
|
|
355
|
-
[npm-version-src]: https://img.shields.io/npm/v/@stacksjs/
|
|
356
|
-
[npm-version-href]: https://npmjs.com/package/@stacksjs/
|
|
357
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/@stacksjs/
|
|
358
|
-
[npm-downloads-href]: https://npmjs.com/package/@stacksjs/
|
|
70
|
+
[npm-version-src]: https://img.shields.io/npm/v/@stacksjs/sanitizer?style=flat-square
|
|
71
|
+
[npm-version-href]: https://npmjs.com/package/@stacksjs/sanitizer
|
|
72
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@stacksjs/sanitizer?style=flat-square
|
|
73
|
+
[npm-downloads-href]: https://npmjs.com/package/@stacksjs/sanitizer
|
|
359
74
|
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/stx/ci.yml?style=flat-square&branch=main
|
|
360
75
|
[github-actions-href]: https://github.com/stacksjs/stx/actions?query=workflow%3Aci
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { sanitize } from './sanitizer';
|
|
|
2
2
|
export * from './presets';
|
|
3
3
|
export { basic, getPreset, markdown, relaxed, strict } from './presets';
|
|
4
4
|
export * from './sanitizer';
|
|
5
|
+
// Re-export for convenience
|
|
5
6
|
export { escape, isSafe, sanitize, sanitizeWithInfo, stripTags } from './sanitizer';
|
|
6
7
|
export * from './types';
|
|
7
|
-
export default sanitize;
|
|
8
|
+
export default sanitize;
|
package/dist/presets.d.ts
CHANGED
package/dist/sanitizer.d.ts
CHANGED
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/sanitizer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"description": "A fast, native Bun-powered HTML sanitizer with DOMPurify-like features. Protection against XSS and malicious content.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.org>",
|
|
7
7
|
"license": "MIT",
|