odac 0.9.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/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
## 🌍 Environment Variables
|
|
2
|
+
|
|
3
|
+
CandyPack supports environment variables through `.env` files, making it easy to manage sensitive data and environment-specific settings.
|
|
4
|
+
|
|
5
|
+
### Creating a .env File
|
|
6
|
+
|
|
7
|
+
Create a `.env` file in your website's root directory (same location as `config.json`):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# .env
|
|
11
|
+
|
|
12
|
+
# Application
|
|
13
|
+
NODE_ENV=production
|
|
14
|
+
DEBUG=false
|
|
15
|
+
APP_URL=https://myapp.com
|
|
16
|
+
|
|
17
|
+
# Database
|
|
18
|
+
MYSQL_HOST=localhost
|
|
19
|
+
MYSQL_USER=root
|
|
20
|
+
MYSQL_PASSWORD=super_secret_123
|
|
21
|
+
MYSQL_DATABASE=myapp
|
|
22
|
+
|
|
23
|
+
# API Keys
|
|
24
|
+
STRIPE_SECRET_KEY=sk_live_xxxxx
|
|
25
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
|
|
26
|
+
API_KEY=your_api_key_here
|
|
27
|
+
|
|
28
|
+
# Mail
|
|
29
|
+
MAIL_FROM=noreply@myapp.com
|
|
30
|
+
SMTP_HOST=smtp.gmail.com
|
|
31
|
+
SMTP_PORT=587
|
|
32
|
+
SMTP_USER=user@gmail.com
|
|
33
|
+
SMTP_PASSWORD=app_password
|
|
34
|
+
|
|
35
|
+
# Features
|
|
36
|
+
FEATURE_BETA=true
|
|
37
|
+
MAINTENANCE_MODE=false
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Using in config.json
|
|
41
|
+
|
|
42
|
+
Reference environment variables using `${VARIABLE_NAME}` syntax:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mysql": {
|
|
47
|
+
"host": "${MYSQL_HOST}",
|
|
48
|
+
"user": "${MYSQL_USER}",
|
|
49
|
+
"password": "${MYSQL_PASSWORD}",
|
|
50
|
+
"database": "${MYSQL_DATABASE}"
|
|
51
|
+
},
|
|
52
|
+
"api": {
|
|
53
|
+
"stripe": {
|
|
54
|
+
"key": "${STRIPE_SECRET_KEY}",
|
|
55
|
+
"webhook": "${STRIPE_WEBHOOK_SECRET}"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"mail": {
|
|
59
|
+
"from": "${MAIL_FROM}"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Accessing in Controllers
|
|
65
|
+
|
|
66
|
+
You can access environment variables in your controllers in three ways:
|
|
67
|
+
|
|
68
|
+
#### 1. Using Candy.env() (Recommended)
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
module.exports = function() {
|
|
72
|
+
const apiKey = Candy.env('API_KEY')
|
|
73
|
+
const debug = Candy.env('DEBUG', 'false')
|
|
74
|
+
const port = Candy.env('PORT', '3000')
|
|
75
|
+
|
|
76
|
+
if (debug === 'true') {
|
|
77
|
+
console.log('Debug mode enabled')
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The second parameter is the default value if the variable is not set.
|
|
83
|
+
|
|
84
|
+
#### 2. Using process.env
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
module.exports = function() {
|
|
88
|
+
const nodeEnv = process.env.NODE_ENV
|
|
89
|
+
const debug = process.env.DEBUG === 'true'
|
|
90
|
+
const apiKey = process.env.API_KEY
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### 3. From Candy.Config (if defined in config.json)
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
module.exports = function() {
|
|
98
|
+
const dbHost = Candy.Config.mysql.host
|
|
99
|
+
const apiKey = Candy.Config.api.stripe.key
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Practical Examples
|
|
104
|
+
|
|
105
|
+
#### Feature Flags
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// controller/home.js
|
|
109
|
+
module.exports = function() {
|
|
110
|
+
const betaEnabled = Candy.env('FEATURE_BETA', 'false') === 'true'
|
|
111
|
+
const maintenance = Candy.env('MAINTENANCE_MODE', 'false') === 'true'
|
|
112
|
+
|
|
113
|
+
if (maintenance) {
|
|
114
|
+
return Candy.abort(503)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Candy.set('betaEnabled', betaEnabled)
|
|
118
|
+
return Candy.View.render('home')
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### API Integration
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// controller/payment.js
|
|
126
|
+
module.exports = async function() {
|
|
127
|
+
const stripeKey = Candy.env('STRIPE_SECRET_KEY')
|
|
128
|
+
const webhookSecret = Candy.env('STRIPE_WEBHOOK_SECRET')
|
|
129
|
+
|
|
130
|
+
const stripe = require('stripe')(stripeKey)
|
|
131
|
+
|
|
132
|
+
// Process payment...
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Mail Configuration
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// controller/contact.js
|
|
140
|
+
module.exports = async function() {
|
|
141
|
+
const mail = Candy.Mail()
|
|
142
|
+
|
|
143
|
+
mail.from(Candy.env('MAIL_FROM', 'noreply@example.com'))
|
|
144
|
+
mail.to(Candy.request('email'))
|
|
145
|
+
mail.subject('Thank you for contacting us')
|
|
146
|
+
mail.html('<h1>We received your message!</h1>')
|
|
147
|
+
|
|
148
|
+
await mail.send()
|
|
149
|
+
|
|
150
|
+
return Candy.return({ success: true })
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Security Best Practices
|
|
155
|
+
|
|
156
|
+
#### 1. Add .env to .gitignore
|
|
157
|
+
|
|
158
|
+
The `.env` file should never be committed to version control:
|
|
159
|
+
|
|
160
|
+
```gitignore
|
|
161
|
+
# .gitignore
|
|
162
|
+
.env
|
|
163
|
+
.env.local
|
|
164
|
+
.env.*.local
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### 2. Create .env.example
|
|
168
|
+
|
|
169
|
+
Provide a template for other developers:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# .env.example
|
|
173
|
+
|
|
174
|
+
# Database
|
|
175
|
+
MYSQL_HOST=localhost
|
|
176
|
+
MYSQL_USER=root
|
|
177
|
+
MYSQL_PASSWORD=your_password_here
|
|
178
|
+
MYSQL_DATABASE=myapp
|
|
179
|
+
|
|
180
|
+
# API Keys
|
|
181
|
+
API_KEY=your_api_key_here
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Commit `.env.example` to git, but not `.env`.
|
|
185
|
+
|
|
186
|
+
#### 3. Different Environments
|
|
187
|
+
|
|
188
|
+
Use different `.env` files for different environments:
|
|
189
|
+
|
|
190
|
+
**Development (.env):**
|
|
191
|
+
```bash
|
|
192
|
+
NODE_ENV=development
|
|
193
|
+
DEBUG=true
|
|
194
|
+
MYSQL_HOST=localhost
|
|
195
|
+
MYSQL_PASSWORD=dev123
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Production (.env):**
|
|
199
|
+
```bash
|
|
200
|
+
NODE_ENV=production
|
|
201
|
+
DEBUG=false
|
|
202
|
+
MYSQL_HOST=production.db.com
|
|
203
|
+
MYSQL_PASSWORD=super_secure_production_password
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Comments and Formatting
|
|
207
|
+
|
|
208
|
+
The `.env` file supports:
|
|
209
|
+
|
|
210
|
+
- **Comments:** Lines starting with `#`
|
|
211
|
+
- **Quotes:** Values can be wrapped in single or double quotes
|
|
212
|
+
- **Spaces:** Spaces around `=` are trimmed
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# This is a comment
|
|
216
|
+
API_KEY=simple_value
|
|
217
|
+
DB_PASSWORD="password with spaces"
|
|
218
|
+
MAIL_FROM='noreply@example.com'
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Important Notes
|
|
222
|
+
|
|
223
|
+
- Environment variables are loaded when the application starts
|
|
224
|
+
- Changes to `.env` require restarting the application
|
|
225
|
+
- The `.env` file is **optional** - you can use direct values in `config.json` if preferred
|
|
226
|
+
- Variables defined in `.env` are available throughout your entire application
|
|
227
|
+
- If a variable is not found, `Candy.env()` returns the default value or `undefined`
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Early Hints (HTTP 103)
|
|
2
|
+
|
|
3
|
+
Early Hints is a performance optimization feature that allows the server to send preliminary HTTP headers to the browser before the final response is ready. This enables browsers to start preloading critical resources (CSS, JavaScript, fonts) while the server is still processing the request.
|
|
4
|
+
|
|
5
|
+
## Zero-Config Operation
|
|
6
|
+
|
|
7
|
+
Early Hints works automatically without any configuration. The framework:
|
|
8
|
+
|
|
9
|
+
1. **Detects** critical resources from your HTML files at startup
|
|
10
|
+
2. **Caches** resource information for fast access
|
|
11
|
+
3. **Sends** Early Hints on subsequent requests automatically
|
|
12
|
+
4. **Optimizes** page load performance transparently
|
|
13
|
+
|
|
14
|
+
You don't need to do anything - it just works!
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
### Automatic Resource Detection
|
|
19
|
+
|
|
20
|
+
When your application starts, CandyPack scans your `view/` and `skeleton/` directories and builds a manifest of critical resources:
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- skeleton/main.html -->
|
|
24
|
+
<html>
|
|
25
|
+
<head>
|
|
26
|
+
<link rel="stylesheet" href="/css/main.css">
|
|
27
|
+
<script src="/js/app.js"></script>
|
|
28
|
+
</head>
|
|
29
|
+
<body>...</body>
|
|
30
|
+
</html>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The framework automatically detects:
|
|
34
|
+
- ✅ CSS files (`<link rel="stylesheet">`)
|
|
35
|
+
- ✅ Blocking JavaScript (`<script src="...">` without `defer` or `async`)
|
|
36
|
+
- ✅ Web fonts (`<link href="...woff2">`)
|
|
37
|
+
|
|
38
|
+
### Request Flow
|
|
39
|
+
|
|
40
|
+
**First Request:**
|
|
41
|
+
```
|
|
42
|
+
1. Browser requests /
|
|
43
|
+
2. Controller sets view: skeleton('main')
|
|
44
|
+
3. Framework checks manifest for 'skeleton/main'
|
|
45
|
+
4. Sends 103 Early Hints with CSS/JS links
|
|
46
|
+
5. Browser starts downloading resources
|
|
47
|
+
6. Server processes request (database queries, etc.)
|
|
48
|
+
7. Sends 200 OK with HTML
|
|
49
|
+
8. Resources already loaded!
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Subsequent Requests:**
|
|
53
|
+
Same flow - hints are sent from the manifest immediately.
|
|
54
|
+
|
|
55
|
+
## Configuration (Optional)
|
|
56
|
+
|
|
57
|
+
While Early Hints works automatically, you can customize it in `config.json`:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"earlyHints": {
|
|
62
|
+
"enabled": true,
|
|
63
|
+
"auto": true,
|
|
64
|
+
"maxResources": 5
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Options
|
|
70
|
+
|
|
71
|
+
#### `enabled` (boolean, default: `true`)
|
|
72
|
+
Enable or disable Early Hints globally.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"earlyHints": {
|
|
77
|
+
"enabled": false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### `auto` (boolean, default: `true`)
|
|
83
|
+
Enable automatic resource detection and hint generation.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"earlyHints": {
|
|
88
|
+
"auto": false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `maxResources` (number, default: `5`)
|
|
94
|
+
Maximum number of resources to include in Early Hints. Limiting this prevents overwhelming the browser with too many preload hints.
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"earlyHints": {
|
|
99
|
+
"maxResources": 3
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## What Gets Preloaded?
|
|
105
|
+
|
|
106
|
+
The framework intelligently selects only **critical resources** from the `<head>` section:
|
|
107
|
+
|
|
108
|
+
### ✅ Included
|
|
109
|
+
- CSS files: `<link rel="stylesheet" href="/css/main.css">`
|
|
110
|
+
- Blocking JavaScript: `<script src="/js/app.js"></script>`
|
|
111
|
+
- Web fonts: `<link href="/fonts/main.woff2" as="font">` (supports `.woff`, `.woff2`, `.ttf`, `.otf`, `.eot`)
|
|
112
|
+
|
|
113
|
+
### ❌ Excluded
|
|
114
|
+
- Deferred scripts: `<script src="/js/app.js" defer></script>`
|
|
115
|
+
- Async scripts: `<script src="/js/app.js" async></script>`
|
|
116
|
+
- Deferred CSS: `<link rel="stylesheet" href="/css/non-critical.css" defer>`
|
|
117
|
+
- Resources in `<body>` (not critical for initial render)
|
|
118
|
+
- Images (handled by browser's own optimization)
|
|
119
|
+
|
|
120
|
+
## Excluding Resources from Early Hints
|
|
121
|
+
|
|
122
|
+
If you want to prevent specific resources from being preloaded, use the `defer` attribute:
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<head>
|
|
126
|
+
<!-- Critical CSS - will be preloaded -->
|
|
127
|
+
<link rel="stylesheet" href="/css/critical.css">
|
|
128
|
+
|
|
129
|
+
<!-- Non-critical CSS - will NOT be preloaded -->
|
|
130
|
+
<link rel="stylesheet" href="/css/non-critical.css" defer>
|
|
131
|
+
|
|
132
|
+
<!-- Critical JS - will be preloaded -->
|
|
133
|
+
<script src="/js/app.js"></script>
|
|
134
|
+
|
|
135
|
+
<!-- Analytics JS - will NOT be preloaded -->
|
|
136
|
+
<script src="/js/analytics.js" defer></script>
|
|
137
|
+
</head>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `defer` attribute works consistently for both CSS and JavaScript:
|
|
141
|
+
- **For JS**: Browser's native defer behavior (execute after DOM is ready)
|
|
142
|
+
- **For CSS**: CandyPack-specific (exclude from Early Hints, but still loads normally)
|
|
143
|
+
|
|
144
|
+
This is useful for:
|
|
145
|
+
- Non-critical styles (animations, print styles)
|
|
146
|
+
- Analytics and tracking scripts
|
|
147
|
+
- Third-party widgets
|
|
148
|
+
- Below-the-fold content styles
|
|
149
|
+
|
|
150
|
+
## Performance Impact
|
|
151
|
+
|
|
152
|
+
Early Hints is most effective when:
|
|
153
|
+
- Server-side processing takes time (database queries, API calls)
|
|
154
|
+
- You have critical CSS/JS files in `<head>`
|
|
155
|
+
- Users are on slower connections
|
|
156
|
+
|
|
157
|
+
### Example Improvement
|
|
158
|
+
|
|
159
|
+
**Without Early Hints:**
|
|
160
|
+
```
|
|
161
|
+
Server processing: 500ms
|
|
162
|
+
Resource download: 200ms
|
|
163
|
+
Total: 700ms
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**With Early Hints:**
|
|
167
|
+
```
|
|
168
|
+
Server processing: 500ms (resources downloading in parallel)
|
|
169
|
+
Total: 500ms (200ms saved!)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Browser Support
|
|
173
|
+
|
|
174
|
+
- Chrome 103+
|
|
175
|
+
- Edge 103+
|
|
176
|
+
- Firefox 103+
|
|
177
|
+
- Safari (partial support)
|
|
178
|
+
|
|
179
|
+
Older browsers simply ignore the `103` response and wait for the `200 OK` - no breaking changes!
|
|
180
|
+
|
|
181
|
+
## Technical Details
|
|
182
|
+
|
|
183
|
+
### Manifest System
|
|
184
|
+
|
|
185
|
+
At startup, the framework builds an in-memory manifest:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
{
|
|
189
|
+
'view/header/home': [
|
|
190
|
+
{href: '/css/header.css', as: 'style'},
|
|
191
|
+
{href: '/js/header.js', as: 'script'}
|
|
192
|
+
],
|
|
193
|
+
'skeleton/main': [
|
|
194
|
+
{href: '/css/main.css', as: 'style'},
|
|
195
|
+
{href: '/js/app.js', as: 'script'}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Proxy Integration
|
|
201
|
+
|
|
202
|
+
CandyPack's architecture uses a proxy layer. Early Hints are:
|
|
203
|
+
1. Generated in the framework
|
|
204
|
+
2. Sent via `X-Candy-Early-Hints` header to proxy
|
|
205
|
+
3. Forwarded to client as `103 Early Hints` by proxy
|
|
206
|
+
|
|
207
|
+
This ensures Early Hints work correctly in the multi-domain hosting environment.
|
|
208
|
+
|
|
209
|
+
### Node.js Requirements
|
|
210
|
+
|
|
211
|
+
- Requires Node.js 18+ for `response.writeEarlyHints()` API
|
|
212
|
+
- Automatically disabled on older Node.js versions
|
|
213
|
+
- No errors or warnings - graceful degradation
|
|
214
|
+
|
|
215
|
+
## Best Practices
|
|
216
|
+
|
|
217
|
+
### 1. Keep Critical Resources in `<head>`
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<!-- Good: Critical CSS in head -->
|
|
221
|
+
<head>
|
|
222
|
+
<link rel="stylesheet" href="/css/critical.css">
|
|
223
|
+
</head>
|
|
224
|
+
|
|
225
|
+
<!-- Bad: CSS in body -->
|
|
226
|
+
<body>
|
|
227
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
228
|
+
</body>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 2. Use `defer` for Non-Critical Scripts
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<!-- Good: Non-critical scripts deferred -->
|
|
235
|
+
<script src="/js/analytics.js" defer></script>
|
|
236
|
+
|
|
237
|
+
<!-- Bad: Blocking script for non-critical feature -->
|
|
238
|
+
<script src="/js/analytics.js"></script>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3. Minimize Critical Resources
|
|
242
|
+
|
|
243
|
+
Aim for 3-5 critical resources maximum. Combine CSS/JS files if needed:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<!-- Good: Combined critical CSS -->
|
|
247
|
+
<link rel="stylesheet" href="/css/critical.css">
|
|
248
|
+
|
|
249
|
+
<!-- Bad: Too many separate files -->
|
|
250
|
+
<link rel="stylesheet" href="/css/reset.css">
|
|
251
|
+
<link rel="stylesheet" href="/css/typography.css">
|
|
252
|
+
<link rel="stylesheet" href="/css/layout.css">
|
|
253
|
+
<link rel="stylesheet" href="/css/components.css">
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Or use `defer` for non-critical resources:
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<!-- Good: Critical resources only -->
|
|
260
|
+
<link rel="stylesheet" href="/css/critical.css">
|
|
261
|
+
<link rel="stylesheet" href="/css/animations.css" defer>
|
|
262
|
+
<link rel="stylesheet" href="/css/print.css" defer>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 4. Preload Fonts
|
|
266
|
+
|
|
267
|
+
If using custom fonts, include them in `<head>`. All common font formats are supported:
|
|
268
|
+
|
|
269
|
+
```html
|
|
270
|
+
<!-- WOFF2 (recommended, best compression) -->
|
|
271
|
+
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
|
|
272
|
+
|
|
273
|
+
<!-- WOFF (fallback for older browsers) -->
|
|
274
|
+
<link rel="preload" href="/fonts/main.woff" as="font" type="font/woff" crossorigin>
|
|
275
|
+
|
|
276
|
+
<!-- TrueType/OpenType -->
|
|
277
|
+
<link rel="preload" href="/fonts/main.ttf" as="font" type="font/ttf" crossorigin>
|
|
278
|
+
<link rel="preload" href="/fonts/main.otf" as="font" type="font/otf" crossorigin>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Troubleshooting
|
|
282
|
+
|
|
283
|
+
### Early Hints Not Working?
|
|
284
|
+
|
|
285
|
+
**Check Node.js version:**
|
|
286
|
+
```bash
|
|
287
|
+
node --version # Should be 18.0.0 or higher
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Verify configuration:**
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"earlyHints": {
|
|
294
|
+
"enabled": true // Make sure it's not disabled
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Check browser DevTools:**
|
|
300
|
+
1. Open Network tab
|
|
301
|
+
2. Look for `103 Early Hints` status
|
|
302
|
+
3. Check response headers for `Link:` headers
|
|
303
|
+
|
|
304
|
+
### No Resources Detected?
|
|
305
|
+
|
|
306
|
+
Make sure resources are in `<head>`:
|
|
307
|
+
```html
|
|
308
|
+
<!-- This will be detected -->
|
|
309
|
+
<head>
|
|
310
|
+
<link rel="stylesheet" href="/css/main.css">
|
|
311
|
+
</head>
|
|
312
|
+
|
|
313
|
+
<!-- This will NOT be detected -->
|
|
314
|
+
<body>
|
|
315
|
+
<link rel="stylesheet" href="/css/main.css">
|
|
316
|
+
</body>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Too Many Resources?
|
|
320
|
+
|
|
321
|
+
Reduce `maxResources` in config:
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"earlyHints": {
|
|
325
|
+
"maxResources": 3
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Disabling Early Hints
|
|
331
|
+
|
|
332
|
+
Removing the `earlyHints` configuration section from your `config.json` is equivalent to using the default settings, which has the feature enabled. To truly disable Early Hints, you must explicitly set `enabled: false` in your configuration:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"earlyHints": {
|
|
337
|
+
"enabled": false
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Summary
|
|
343
|
+
|
|
344
|
+
Early Hints is a powerful performance optimization that:
|
|
345
|
+
- ✅ Works automatically (zero-config)
|
|
346
|
+
- ✅ Detects critical resources intelligently
|
|
347
|
+
- ✅ Improves page load times
|
|
348
|
+
- ✅ Requires no code changes
|
|
349
|
+
- ✅ Degrades gracefully on older browsers
|
|
350
|
+
- ✅ Can be customized if needed
|
|
351
|
+
|
|
352
|
+
Just build your app normally, and Early Hints will optimize it for you!
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## 📄 Basic Page Routes
|
|
2
|
+
|
|
3
|
+
#### `page(path, controller)`
|
|
4
|
+
This is the most common method. It maps a URL path to a controller that is expected to render a standard HTML page. It handles `GET` requests.
|
|
5
|
+
|
|
6
|
+
- `path`: The URL path to match (e.g., `/about`).
|
|
7
|
+
- `controller`: The name of the controller file.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// When a user visits yoursite.com/
|
|
11
|
+
Candy.Route.page('/', 'index');
|
|
12
|
+
|
|
13
|
+
// When a user visits yoursite.com/contact
|
|
14
|
+
Candy.Route.page('/contact', 'contact-form');
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Page Identifier:** The controller filename becomes the page identifier in the frontend. For example, `'contact-form'` becomes accessible as `Candy.page()` returning `"contact-form"`. This allows you to run page-specific JavaScript:
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
// Frontend
|
|
21
|
+
Candy.action({
|
|
22
|
+
page: {
|
|
23
|
+
'contact-form': function() {
|
|
24
|
+
console.log('Contact form page loaded')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## ⚡ Controller-less View Routes
|
|
2
|
+
|
|
3
|
+
For simple pages that don't require complex logic in a controller, you can render a view directly from your route file by passing a view configuration object as the second parameter.
|
|
4
|
+
|
|
5
|
+
#### `page(path, { ... })`
|
|
6
|
+
This defines a page and immediately tells it which view components to render.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
Candy.Route.page("/users", {
|
|
10
|
+
skeleton: "dashboard",
|
|
11
|
+
header: "dashboard.main",
|
|
12
|
+
sidebar: "dashboard.main",
|
|
13
|
+
footer: "dashboard.main",
|
|
14
|
+
content: "users"
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
This example tells CandyPack to render the `/users` page by assembling a view from multiple parts, likely using a main `dashboard` skeleton and filling it with different content blocks.
|
|
18
|
+
|
|
19
|
+
**Page Identifier:** When using view objects, the page identifier (accessible via `Candy.page()` in frontend) is automatically set to the `content` or `all` value. In this example, the page identifier would be `"users"`, allowing you to run page-specific JavaScript:
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
// Frontend
|
|
23
|
+
Candy.action({
|
|
24
|
+
page: {
|
|
25
|
+
users: function() {
|
|
26
|
+
console.log('Users page loaded')
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### `auth.page(path, { ... })`
|
|
33
|
+
Similar to `page()`, but requires authentication. Only authenticated users can access this route.
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// Only authenticated users can see the dashboard
|
|
37
|
+
Candy.Route.auth.page('/', {
|
|
38
|
+
skeleton: 'main',
|
|
39
|
+
content: 'dashboard'
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See [Authentication-Aware Routes](04-authentication-aware-routes.md) for more details.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## 📦 API and Data Routes
|
|
2
|
+
|
|
3
|
+
#### `get(path, controller, options)`
|
|
4
|
+
Defines a route that responds to `GET` requests. This is ideal for API endpoints that return data (like JSON).
|
|
5
|
+
|
|
6
|
+
- `options`: By default, CandyPack protects routes from CSRF attacks by checking for a token. For a public API or stateless endpoint, you must disable this by passing `{ token: false }`. If you don't, the server will expect a token and will not return a response if one isn't provided.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
// An API endpoint at GET /api/users/123
|
|
10
|
+
// We disable the token check as this is a public API.
|
|
11
|
+
Candy.Route.get('/api/users/{id}', 'api/users.get', { token: false });
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
#### `post(path, controller, options)`
|
|
15
|
+
Defines a route that responds to `POST` requests, typically used for form submissions. The `{ token: false }` option works here as well, but should be used with caution as POST routes are primary targets for CSRF attacks.
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// A form that posts data to /login
|
|
19
|
+
Candy.Route.post('/login', 'auth.login');
|
|
20
|
+
```
|