@beforesemicolon/site-builder 0.32.0 → 0.34.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/.netlify/netlify.toml +28 -0
- package/README.md +396 -82
- package/dist/cjs/build-templates.js +11 -1
- package/dist/cjs/default-parse-options.js +1 -1
- package/dist/cjs/parse-component.js +1 -1
- package/dist/cjs/parse-template.js +1 -1
- package/dist/cjs/parse-widget.js +1 -1
- package/dist/cjs/types.js +1 -1
- package/dist/cjs/utils/flatten-object.js +1 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +3 -3
- package/dist/esm/build-templates.js +11 -1
- package/dist/esm/default-parse-options.js +1 -1
- package/dist/esm/parse-component.js +1 -1
- package/dist/esm/parse-template.js +1 -1
- package/dist/esm/parse-widget.js +1 -1
- package/dist/esm/types.js +1 -1
- package/dist/esm/utils/flatten-object.js +1 -0
- package/dist/types/parse-widget.d.ts +2 -2
- package/dist/types/types.d.ts +39 -11
- package/dist/types/utils/flatten-object.d.ts +2 -0
- package/dist/types/utils/input-definitions-to-object.d.ts +1 -1
- package/package.json +1 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
plugins = []
|
|
2
|
+
headers = []
|
|
3
|
+
redirects = []
|
|
4
|
+
|
|
5
|
+
[functions]
|
|
6
|
+
|
|
7
|
+
[functions."*"]
|
|
8
|
+
|
|
9
|
+
[build]
|
|
10
|
+
publish = "/Users/ecorreia/Sites/@beforesemicolon/site-builder/public"
|
|
11
|
+
publishOrigin = "ui"
|
|
12
|
+
commandOrigin = "ui"
|
|
13
|
+
base = "/Users/ecorreia/Sites/@beforesemicolon/site-builder"
|
|
14
|
+
command = "npm run build"
|
|
15
|
+
|
|
16
|
+
[build.environment]
|
|
17
|
+
|
|
18
|
+
[build.processing]
|
|
19
|
+
|
|
20
|
+
[build.processing.css]
|
|
21
|
+
|
|
22
|
+
[build.processing.html]
|
|
23
|
+
|
|
24
|
+
[build.processing.images]
|
|
25
|
+
|
|
26
|
+
[build.processing.js]
|
|
27
|
+
|
|
28
|
+
[build.services]
|
package/README.md
CHANGED
|
@@ -1,133 +1,447 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🚀 BeforeSemicolon Site Builder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/%40beforesemicolon%2Fsite-builder)
|
|
4
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
5
|
+
[](https://github.com/beforesemicolon/site-builder/actions)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
[](https://github.com/beforesemicolon/markup/blob/main/docs/index.md)
|
|
7
|
-
[](https://www.npmjs.com/package/@beforesemicolon/markup)
|
|
8
|
-

|
|
9
|
-
[](https://github.com/beforesemicolon/html/actions/workflows/test.yml)
|
|
7
|
+
## The Next-Generation Website Builder for Developers
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
**BeforeSemicolon Site Builder** is a revolutionary JSON-based static site generator that puts developers in complete control. Unlike traditional website builders that lock you into proprietary systems, our builder gives you the power of code with the simplicity of configuration.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
### 🎯 Why Choose BeforeSemicolon Site Builder?
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
- `state`: A simple state tracking API that allows you to define reactive data as you wish;
|
|
17
|
-
- `effect`: A straight forward way to define things that need to happen when certain states change;
|
|
13
|
+
**🆚 Traditional Website Builders vs. BeforeSemicolon**
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
| Feature | Traditional Builders | BeforeSemicolon Site Builder |
|
|
16
|
+
| ------------------------ | ----------------------- | ---------------------------- |
|
|
17
|
+
| **Data Format** | Proprietary/Database | Standard JSON files |
|
|
18
|
+
| **Version Control** | ❌ Limited/None | ✅ Full Git integration |
|
|
19
|
+
| **Customization** | ❌ Template limitations | ✅ Unlimited flexibility |
|
|
20
|
+
| **Performance** | ❌ Bloated output | ✅ Optimized static files |
|
|
21
|
+
| **Portability** | ❌ Platform locked | ✅ Framework agnostic |
|
|
22
|
+
| **Developer Experience** | ❌ GUI only | ✅ Code + Visual |
|
|
23
|
+
| **SEO Control** | ❌ Limited | ✅ Complete control |
|
|
24
|
+
| **Build Pipeline** | ❌ No control | ✅ Custom build process |
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## 🌟 Key Features
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
### 📄 **JSON-First Architecture**
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
Define your entire website structure using simple, readable JSON files. No proprietary formats, no vendor lock-in. Templates, widgets, and components are all described in JSON, supporting inheritance, metadata, localization, and more.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
let count = 0
|
|
32
|
+
### 🧩 **Dynamic Widget System**
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
const p = document.createElement('p')
|
|
32
|
-
p.textContent = `count: ${count}`
|
|
34
|
+
Create reusable, interactive components with state management and custom logic. Widgets can be defined with inputs, state, and a render function or static content. Widgets support localization, environment variables, and can compose other components.
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
btn.type = 'button'
|
|
36
|
-
btn.textContent = 'count up'
|
|
36
|
+
### 🏗️ **Template Inheritance & Metadata**
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
btn.addEventListener('onclick', () => {
|
|
40
|
-
count += 1
|
|
41
|
-
p.textContent = `count: ${count}`
|
|
38
|
+
Templates can extend other templates, allowing for DRY and maintainable site structures. Each template supports rich metadata (SEO, social, custom), scripts, stylesheets, links, fonts, favicons, manifest, CSP, preload, and structured data.
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
### 🗂️ **Component Library**
|
|
41
|
+
|
|
42
|
+
Build a library of reusable UI components, each with their own styles and content. Components can be referenced and rendered inside widgets and templates.
|
|
43
|
+
|
|
44
|
+
### 🌍 **Localization Support**
|
|
45
|
+
|
|
46
|
+
Localization files (JSON) are automatically loaded and flattened for use in templates and widgets, enabling multi-language sites out of the box.
|
|
47
|
+
|
|
48
|
+
### ⚡ **Lightning-Fast Build Process**
|
|
49
|
+
|
|
50
|
+
The build script processes templates, widgets, components, assets, and localization files, outputting a fully static, minified site. HTML and CSS are minified, and assets/data are copied to the output directory. Supports production and development modes.
|
|
51
|
+
|
|
52
|
+
### 🛠️ **Highly Configurable & Extensible**
|
|
53
|
+
|
|
54
|
+
Custom fetchers for widgets/templates, custom components, asset origins, and more. Caching and environment options are available for advanced use cases.
|
|
55
|
+
|
|
56
|
+
### 🧑💻 **TypeScript Native**
|
|
57
|
+
|
|
58
|
+
Strong TypeScript types for templates, widgets, components, styles, and scripts. Input definitions support a wide range of types (text, html, markdown, media, options, groups, lists, etc.).
|
|
59
|
+
|
|
60
|
+
## 🚀 Quick Start
|
|
61
|
+
|
|
62
|
+
### Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install @beforesemicolon/site-builder
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Basic Usage
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { buildTemplates, parseTemplate } from '@beforesemicolon/site-builder'
|
|
72
|
+
|
|
73
|
+
// Build entire site
|
|
74
|
+
await buildTemplates({
|
|
75
|
+
srcDir: './src',
|
|
76
|
+
publicDir: './dist',
|
|
77
|
+
prod: true,
|
|
46
78
|
})
|
|
47
79
|
|
|
48
|
-
|
|
80
|
+
// Parse individual template
|
|
81
|
+
const template = {
|
|
82
|
+
id: 'home',
|
|
83
|
+
name: 'Home Page',
|
|
84
|
+
route: '/',
|
|
85
|
+
type: 'page',
|
|
86
|
+
title: 'Welcome to My Site',
|
|
87
|
+
description: 'An amazing website built with BeforeSemicolon',
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
name: 'h1',
|
|
91
|
+
children: ['Welcome to {{title}}'],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const html = await parseTemplate(template)
|
|
49
97
|
```
|
|
50
98
|
|
|
51
|
-
|
|
99
|
+
## 📁 Project Structure
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
my-website/
|
|
103
|
+
├── src/
|
|
104
|
+
│ ├── templates/ # Page templates (JSON)
|
|
105
|
+
│ │ ├── home.json
|
|
106
|
+
│ │ ├── about.json
|
|
107
|
+
│ │ └── base.json
|
|
108
|
+
│ ├── widgets/ # Dynamic widgets (JS)
|
|
109
|
+
│ │ ├── navigation.js
|
|
110
|
+
│ │ └── contact-form.js
|
|
111
|
+
│ ├── components/ # Static components (JSON)
|
|
112
|
+
│ │ ├── hero.json
|
|
113
|
+
│ │ └── footer.json
|
|
114
|
+
│ ├── stylesheets/ # CSS or other stylesheet files
|
|
115
|
+
│ │ └── main.css
|
|
116
|
+
│ ├── scripts/ # JavaScript files for the site
|
|
117
|
+
│ │ └── app.js
|
|
118
|
+
│ ├── assets/ # Static asset/media files (fonts, icons, logo, favicons, etc.)
|
|
119
|
+
│ │ ├── fonts/
|
|
120
|
+
│ │ ├── icons/
|
|
121
|
+
│ │ ├── logo/
|
|
122
|
+
│ │ ├── videos/
|
|
123
|
+
│ │ ├── audios/
|
|
124
|
+
│ │ ├── images/
|
|
125
|
+
│ │ └── favicons/
|
|
126
|
+
│ │ └── manifest.json
|
|
127
|
+
│ │ └── _redirects
|
|
128
|
+
│ │ └── robots.txt
|
|
129
|
+
│ │ └── sitemap.xml
|
|
130
|
+
│ ├── data/ # Static data files (JSON, CSV, etc.)
|
|
131
|
+
│ │ └── products.json
|
|
132
|
+
│ └── locales/ # Localization files (e.g., en.json, fr.json)
|
|
133
|
+
│ ├── en.json
|
|
134
|
+
│ └── fr.json
|
|
135
|
+
└── public/ # Generated site
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 📝 Templates
|
|
139
|
+
|
|
140
|
+
Templates are the backbone of your site. They define page structure, metadata, and content.
|
|
141
|
+
|
|
142
|
+
### Base Template
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"id": "home",
|
|
147
|
+
"name": "Home Page",
|
|
148
|
+
"route": "/",
|
|
149
|
+
"type": "base",
|
|
150
|
+
"lang": "en",
|
|
151
|
+
"title": "My Awesome Website",
|
|
152
|
+
"domain": "https://mysite.com",
|
|
153
|
+
"description": "A site built with BeforeSemicolon Site Builder",
|
|
154
|
+
"metadata": {
|
|
155
|
+
"keywords": ["website", "builder", "static"],
|
|
156
|
+
"themeColor": "#ffffff"
|
|
157
|
+
},
|
|
158
|
+
"stylesheets": ["main.css"],
|
|
159
|
+
"scripts": ["/app.js"],
|
|
160
|
+
"content": [
|
|
161
|
+
{
|
|
162
|
+
"name": "header",
|
|
163
|
+
"class": "site-header",
|
|
164
|
+
"children": [
|
|
165
|
+
{
|
|
166
|
+
"name": "h1",
|
|
167
|
+
"children": ["{{title}}"]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Template Inheritance
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"id": "about",
|
|
180
|
+
"extends": "base",
|
|
181
|
+
"type": "page",
|
|
182
|
+
"title": "About Us",
|
|
183
|
+
"route": "/about",
|
|
184
|
+
"content": [
|
|
185
|
+
{
|
|
186
|
+
"name": "main",
|
|
187
|
+
"children": [
|
|
188
|
+
{
|
|
189
|
+
"name": "h1",
|
|
190
|
+
"children": ["About {{title}}"]
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 🧩 Widgets
|
|
199
|
+
|
|
200
|
+
Widgets are dynamic, reusable components with logic and state.
|
|
52
201
|
|
|
53
202
|
```javascript
|
|
54
|
-
//
|
|
55
|
-
|
|
203
|
+
// widgets/counter.js
|
|
204
|
+
export default {
|
|
205
|
+
id: 'counter',
|
|
206
|
+
name: 'Counter Widget',
|
|
207
|
+
inputs: [
|
|
208
|
+
{
|
|
209
|
+
type: 'number',
|
|
210
|
+
name: 'initialValue',
|
|
211
|
+
value: 0,
|
|
212
|
+
label: 'Initial Value',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
render: ({ initialValue, env }) => {
|
|
216
|
+
return `
|
|
217
|
+
<div class="counter" data-initial="${initialValue}">
|
|
218
|
+
<button onclick="decrement()">-</button>
|
|
219
|
+
<span class="count">${initialValue}</span>
|
|
220
|
+
<button onclick="increment()">+</button>
|
|
221
|
+
</div>
|
|
222
|
+
`
|
|
223
|
+
},
|
|
224
|
+
scripts: ['widgets/counter-behavior.js'],
|
|
225
|
+
}
|
|
226
|
+
```
|
|
56
227
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
228
|
+
### Using Widgets in Templates
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"content": [
|
|
233
|
+
{
|
|
234
|
+
"name": "widget",
|
|
235
|
+
"id": "counter",
|
|
236
|
+
"initialValue": 10
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
"widgetsData": {
|
|
240
|
+
"counter": {
|
|
241
|
+
"initialValue": 5,
|
|
242
|
+
"theme": "dark"
|
|
243
|
+
}
|
|
61
244
|
}
|
|
62
|
-
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
63
247
|
|
|
64
|
-
|
|
65
|
-
|
|
248
|
+
## 🎨 Components
|
|
249
|
+
|
|
250
|
+
Components are static, reusable UI elements.
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"id": "hero",
|
|
255
|
+
"stylesheets": ["components/hero.css"],
|
|
256
|
+
"content": [
|
|
257
|
+
{
|
|
258
|
+
"name": "section",
|
|
259
|
+
"class": "hero",
|
|
260
|
+
"children": [
|
|
261
|
+
{
|
|
262
|
+
"name": "h1",
|
|
263
|
+
"class": "hero-title",
|
|
264
|
+
"children": ["{{title}}"]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"name": "p",
|
|
268
|
+
"class": "hero-subtitle",
|
|
269
|
+
"children": ["{{subtitle}}"]
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
]
|
|
66
274
|
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## 🎯 Advanced Features
|
|
278
|
+
|
|
279
|
+
### Dynamic Content Rendering
|
|
67
280
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
281
|
+
```typescript
|
|
282
|
+
import { parseWidget } from '@beforesemicolon/site-builder'
|
|
283
|
+
|
|
284
|
+
const dynamicContent = await parseWidget({
|
|
285
|
+
node: {
|
|
286
|
+
name: 'widget',
|
|
287
|
+
id: 'product-list',
|
|
288
|
+
category: 'electronics',
|
|
289
|
+
},
|
|
290
|
+
data: { products: await fetchProducts() },
|
|
291
|
+
})
|
|
73
292
|
```
|
|
74
293
|
|
|
75
|
-
###
|
|
294
|
+
### Custom Styling
|
|
76
295
|
|
|
77
|
-
|
|
296
|
+
```typescript
|
|
297
|
+
import { parseStyle } from '@beforesemicolon/site-builder'
|
|
78
298
|
|
|
79
|
-
|
|
80
|
-
|
|
299
|
+
const styles = parseStyle({
|
|
300
|
+
'.hero': {
|
|
301
|
+
backgroundColor: '#333',
|
|
302
|
+
color: 'white',
|
|
303
|
+
padding: '2rem',
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
```
|
|
81
307
|
|
|
82
|
-
|
|
308
|
+
### Build Optimization
|
|
83
309
|
|
|
84
|
-
|
|
310
|
+
```typescript
|
|
311
|
+
await buildTemplates({
|
|
312
|
+
srcDir: './src',
|
|
313
|
+
publicDir: './dist',
|
|
314
|
+
prod: true, // Enables minification and optimization
|
|
315
|
+
})
|
|
316
|
+
```
|
|
85
317
|
|
|
86
|
-
|
|
318
|
+
## 🔧 Configuration
|
|
87
319
|
|
|
88
|
-
|
|
320
|
+
### Parse Options
|
|
89
321
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
322
|
+
```typescript
|
|
323
|
+
const options = {
|
|
324
|
+
useCache: true,
|
|
325
|
+
prod: false,
|
|
326
|
+
assetsOrigin: 'https://cdn.mysite.com',
|
|
327
|
+
components: componentLibrary,
|
|
328
|
+
locales: localizationFiles,
|
|
329
|
+
fetchWidget: async (id) => await loadWidget(id),
|
|
330
|
+
fetchTemplate: async (id) => await loadTemplate(id),
|
|
331
|
+
}
|
|
93
332
|
|
|
94
|
-
|
|
333
|
+
const html = await parseTemplate(template, options)
|
|
334
|
+
```
|
|
95
335
|
|
|
336
|
+
### Input Definitions
|
|
337
|
+
|
|
338
|
+
Create rich form inputs for your widgets:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const inputs = [
|
|
342
|
+
{
|
|
343
|
+
type: 'text',
|
|
344
|
+
name: 'title',
|
|
345
|
+
label: 'Page Title',
|
|
346
|
+
value: 'Default Title',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: 'options',
|
|
350
|
+
name: 'theme',
|
|
351
|
+
label: 'Color Theme',
|
|
352
|
+
definitions: [
|
|
353
|
+
{ name: 'light', value: 'light' },
|
|
354
|
+
{ name: 'dark', value: 'dark' },
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
type: 'group',
|
|
359
|
+
name: 'seo',
|
|
360
|
+
label: 'SEO Settings',
|
|
361
|
+
definitions: [
|
|
362
|
+
{ type: 'text', name: 'description' },
|
|
363
|
+
{ type: 'image', name: 'ogImage' },
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
]
|
|
96
367
|
```
|
|
97
|
-
|
|
368
|
+
|
|
369
|
+
## 🛠️ Development Workflow
|
|
370
|
+
|
|
371
|
+
### Development Server
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
npm run docs:watch # Start development server with hot reload
|
|
98
375
|
```
|
|
99
376
|
|
|
100
|
-
|
|
377
|
+
### Building for Production
|
|
101
378
|
|
|
379
|
+
```bash
|
|
380
|
+
npm run build # Build optimized production bundle
|
|
381
|
+
npm run test # Run test suite
|
|
382
|
+
npm run lint # Check code quality
|
|
102
383
|
```
|
|
103
|
-
|
|
384
|
+
|
|
385
|
+
### Testing
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npm test # Run all tests
|
|
389
|
+
npm run test:watch # Watch mode for development
|
|
104
390
|
```
|
|
105
391
|
|
|
106
|
-
## Use
|
|
392
|
+
## 🌟 Use Cases
|
|
107
393
|
|
|
108
|
-
|
|
394
|
+
### 🏢 **Business Websites**
|
|
109
395
|
|
|
110
|
-
|
|
111
|
-
<!doctype html>
|
|
112
|
-
<html lang="en">
|
|
113
|
-
<head>
|
|
114
|
-
<!-- Grab the latest version -->
|
|
115
|
-
<script src="https://unpkg.com/@beforesemicolon/markup/dist/client.js"></script>
|
|
396
|
+
Create professional business sites with dynamic contact forms, product catalogs, and CMS integration.
|
|
116
397
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
398
|
+
### 📝 **Blogs & Content Sites**
|
|
399
|
+
|
|
400
|
+
Build SEO-optimized blogs with markdown support, tagging systems, and social sharing.
|
|
401
|
+
|
|
402
|
+
### 🛍️ **E-commerce**
|
|
403
|
+
|
|
404
|
+
Develop fast, conversion-optimized online stores with dynamic product listings and checkout flows.
|
|
405
|
+
|
|
406
|
+
### 📱 **Landing Pages**
|
|
407
|
+
|
|
408
|
+
Craft high-converting landing pages with A/B testing capabilities and analytics integration.
|
|
409
|
+
|
|
410
|
+
### 🎨 **Portfolios**
|
|
411
|
+
|
|
412
|
+
Showcase your work with customizable galleries, case studies, and contact forms.
|
|
122
413
|
|
|
123
|
-
|
|
414
|
+
## 🚀 Performance Benefits
|
|
124
415
|
|
|
125
|
-
|
|
126
|
-
|
|
416
|
+
- **Static Generation**: Pre-rendered HTML for maximum speed
|
|
417
|
+
- **Code Splitting**: Automatic optimization of JavaScript bundles
|
|
418
|
+
- **Asset Optimization**: Minified CSS/JS and optimized images
|
|
419
|
+
- **SEO Ready**: Server-side rendering with complete meta control
|
|
420
|
+
- **CDN Friendly**: Deploy anywhere with zero configuration
|
|
127
421
|
|
|
128
|
-
|
|
129
|
-
const { html, state, effect } = BFS.MARKUP
|
|
422
|
+
## 🤝 Contributing
|
|
130
423
|
|
|
131
|
-
|
|
132
|
-
|
|
424
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
git clone https://github.com/beforesemicolon/site-builder.git
|
|
428
|
+
cd site-builder
|
|
429
|
+
npm install
|
|
430
|
+
npm run test
|
|
133
431
|
```
|
|
432
|
+
|
|
433
|
+
## 📄 License
|
|
434
|
+
|
|
435
|
+
BSD-3-Clause License - see the [LICENSE](LICENSE) file for details.
|
|
436
|
+
|
|
437
|
+
## 💝 Support
|
|
438
|
+
|
|
439
|
+
- ⭐ Star this repository
|
|
440
|
+
- 🐛 [Report issues](https://github.com/beforesemicolon/site-builder/issues)
|
|
441
|
+
- 💖 [Sponsor the project](https://github.com/sponsors/beforesemicolon)
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
**Built with ❤️ by [BeforeSemicolon](https://beforesemicolon.com)**
|
|
446
|
+
|
|
447
|
+
_"The future of web development is declarative, portable, and developer-first."_
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var oe=Object.create;var x=Object.defineProperty;var ie=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var re=Object.getPrototypeOf,ce=Object.prototype.hasOwnProperty;var b=(t,n)=>x(t,"name",{value:n,configurable:!0});var le=(t,n)=>{for(var i in n)x(t,i,{get:n[i],enumerable:!0})},z=(t,n,i,g)=>{if(n&&typeof n=="object"||typeof n=="function")for(let p of ae(n))!ce.call(t,p)&&p!==i&&x(t,p,{get:()=>n[p],enumerable:!(g=ie(n,p))||g.enumerable});return t};var N=(t,n,i)=>(i=t!=null?oe(re(t)):{},z(n||!t||!t.__esModule?x(i,"default",{value:t,enumerable:!0}):i,t)),me=t=>z(x({},"__esModule",{value:!0}),t);var we={};le(we,{buildTemplates:()=>ue});module.exports=me(we);var e=N(require("path"),1),m=N(require("fs"),1),H=N(require("esbuild"),1),M=require("./utils/merge-objects.js"),Q=require("./parse-template.js"),V=N(require("clean-css"),1),Y=require("./types.js"),Z=require("html-minifier"),D=require("./utils/flatten-object.js");const pe=new V.default,{writeFile:T,readFile:C,readdir:P,mkdir:j,cp:w,rm:fe}=m.default.promises;async function de(t,n){try{const i=e.default.resolve(t,"templates"),g=e.default.resolve(t,"widgets"),p=await P(i),y=[];let v=null;for(const c of p)if(c.endsWith(".json")){const f=e.default.join(i,c),F=await C(f,"utf-8"),d=JSON.parse(F);c==="base.json"?v=d:y.push({id:d.id,name:d.name||d.id,url:`/${d.id}.html`,template:c})}const O=await P(g),h=[];for(const c of O)if(c.endsWith(".js")){const f=e.default.basename(c,".js");h.push({id:f,name:f.charAt(0).toUpperCase()+f.slice(1).replace(/-/g," "),file:c})}const W={meta:v,pages:y,widgets:h},$=e.default.join(n,"config.json");await T($,JSON.stringify(W,null,2)),console.log("Generated config.json with",y.length,"pages and",h.length,"widgets")}catch(i){throw console.error("Error generating admin config:",i),i}}b(de,"generateAdminConfig");const ue=b(async({publicDir:t,srcDir:n,prod:i=!0})=>{console.log("Building templates:",{publicDir:t,srcDir:n,prod:i});const g=process.cwd(),p=e.default.join(g,"_redirects"),y=e.default.join(t,"_redirects"),v=e.default.resolve(t,"scripts"),O=e.default.resolve(t,"stylesheets"),h=e.default.resolve(n,"assets"),W=e.default.resolve(n,"admin"),$=e.default.resolve(n,"widgets"),c=e.default.resolve(t,"admin","widgets"),f=e.default.resolve(t,"assets"),F=e.default.resolve(n,"data"),d=e.default.resolve(t,"data"),B=e.default.resolve(n,"assets","robots.txt"),ee=e.default.join(t,"robots.txt"),E=e.default.resolve(n,"assets","sitemap.xml");if(m.default.existsSync(t)&&await fe(t,{recursive:!0,force:!0}),await j(t,{recursive:!0}),await j(v,{recursive:!0}),await j(O,{recursive:!0}),m.default.existsSync(p)&&await w(p,y),m.default.existsSync(h)&&await w(h,f,{recursive:!0}),m.default.existsSync(F)&&await w(F,d,{recursive:!0}),m.default.existsSync(W)){const s=e.default.resolve(t,"admin");await j(s,{recursive:!0}),await w(W,s,{recursive:!0}),await de(n,s),i||(await j(c,{recursive:!0}),await w($,c,{recursive:!0}))}const te=await P(e.default.resolve(n,"templates")),J=e.default.resolve(n,"locales"),k={};if(m.default.existsSync(J)){const s=await P(J);for(const o of s)if(o.endsWith(".json")){const l=JSON.parse(await C(e.default.join(J,o),"utf8")),r=e.default.basename(o,".json");k[r]=(0,D.flattenObject)(l)}}const L=await Promise.all(te.filter(s=>s.endsWith(".json")).map(async s=>{const o=e.default.basename(s);return{dir:e.default.dirname(s),name:o.replace(".json",""),content:JSON.parse(await C(e.default.resolve(n,"templates",s),"utf8"))}})),_=L.reduce((s,o)=>({...s,[o.name]:o}),{}),A=L.filter(({content:s})=>s.type===Y.TemplateType.Page&&s.excluded!==!0),R=e.default.resolve(n,"components");let G={};if(m.default.existsSync(R)){const s=await P(R),o=await Promise.all(s.filter(l=>l.endsWith(".json")).map(async l=>{const r=JSON.parse(await C(e.default.join(R,l),"utf8"));return[r.id,r]}));G=Object.fromEntries(o)}const I=b(async s=>{let o=typeof s=="string"?s:"";s&&typeof s=="object"&&"src"in s&&(o=String(s.src)),!o.startsWith("http")&&o.endsWith(".js")&&await H.default.build({entryPoints:[e.default.resolve(n,"scripts",o)],minify:!0,outfile:e.default.join(v,o)})},"handleScript"),K=b(async s=>{let o=typeof s=="string"?s:"";if(s&&typeof s=="object"&&"href"in s&&(o=String(s.href)),!o.startsWith("http")&&o.endsWith(".css")){const l=await C(e.default.resolve(n,"stylesheets",o),"utf8");await T(e.default.join(O,o),pe.minify(l).styles,"utf-8")}},"handleStylesheet"),X=[];for(const{name:s,dir:o,content:l}of A){let r=l;if(r.extends){const a=_[r.extends];r=(0,M.mergeObjects)(a.content,r)}const q=l.domain||"",U=l.route||"";if(U){const a=q?`${q.replace(/\/$/,"")}/${U.replace(/^\//,"")}`:`/${U.replace(/^\//,"")}`;X.push(a)}await j(e.default.join(t,o),{recursive:!0});for(let a=0;a<(r.scripts?.length??0);a++)await I(r.scripts[a]);for(let a=0;a<(r.stylesheets?.length??0);a++)await K(r.stylesheets[a]);const se=await(0,Q.parseTemplate)(r,{prod:i,components:G,locales:k,fetchTemplate:a=>_[a].content,fetchWidget:async a=>{const S=(await import(e.default.resolve($,`${a}.js`))).default;for(let u=0;u<(S.scripts?.length??0);u++)await I(S.scripts[u]);for(let u=0;u<(S.stylesheets?.length??0);u++)await K(S.stylesheets[u]);return S}}),ne=(0,Z.minify)(se,{collapseWhitespace:!0,removeComments:!0,minifyCSS:!0,minifyJS:!0});await T(e.default.join(t,o,`${s}.html`),ne)}if(m.default.existsSync(B))await w(B,e.default.join(t,"robots.txt"));else{const o=`User-agent: *
|
|
2
|
+
Disallow:
|
|
3
|
+
Sitemap: ${A[0]?.content.domain?`${A[0].content.domain.replace(/\/$/,"")}/sitemap.xml`:"/sitemap.xml"}
|
|
4
|
+
`;await T(ee,o,"utf-8")}if(m.default.existsSync(E))await w(E,e.default.join(t,"sitemap.xml"));else{const s=`<?xml version="1.0" encoding="UTF-8"?>
|
|
5
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
6
|
+
`+X.map(o=>` <url>
|
|
7
|
+
<loc>${o}</loc>
|
|
8
|
+
</url>`).join(`
|
|
9
|
+
`)+`
|
|
10
|
+
</urlset>
|
|
11
|
+
`;await T(e.default.join(t,"sitemap.xml"),s,"utf-8")}},"buildTemplates");0&&(module.exports={buildTemplates});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var r=(s,e)=>{for(var n in e)o(s,n,{get:e[n],enumerable:!0})},i=(s,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of l(e))!c.call(s,t)&&t!==n&&o(s,t,{get:()=>e[t],enumerable:!(a=p(e,t))||a.enumerable});return s};var f=s=>i(o({},"__esModule",{value:!0}),s);var m={};r(m,{defaultOptions:()=>u});module.exports=f(m);const u={fetchWidget:async()=>null,fetchTemplate:async()=>null,useCache:!0,locales:{},components:{},prod:!1,assetsOrigin:"/"};0&&(module.exports={defaultOptions});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var o=Object.defineProperty;var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var p=(e,t)=>o(e,"name",{value:t,configurable:!0});var h=(e,t)=>{for(var i in t)o(e,i,{get:t[i],enumerable:!0})},y=(e,t,i,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of g(t))!C.call(e,r)&&r!==i&&o(e,r,{get:()=>t[r],enumerable:!(n=u(t,r))||n.enumerable});return e};var $=e=>y(o({},"__esModule",{value:!0}),e);var M={};h(M,{parseComponent:()=>b});module.exports=$(M);var a=require("./utils/replace-string-value.js"),f=require("./utils/attributes-to-string.js");const b=p((e,t={},i=new Map)=>{if(!e||!e.content)return"";const{content:n="",stylesheets:r,id:s}=e;return r&&i.set(`comp-${s}`,r),typeof n=="string"?n?(0,a.replaceStringValue)(n,t):"":c(n,t,i)},"parseComponent");function c(e,t={},i=new Map){return e.map(n=>{if(typeof n=="string")return(0,a.replaceStringValue)(n,t);const{name:r,children:s=[],...l}=n,m=(0,f.attributeToString)(l,t);return`<${r}`+(m?` ${m}`:"")+`>${c(s,t,i)}</${r}>`}).join("")}p(c,"parseContent");0&&(module.exports={parseComponent});
|