xfmpress 0.1.0 → 0.1.1
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/CHANGELOG.md +42 -0
- package/INSTALL.md +111 -103
- package/INSTALL_CN.md +298 -0
- package/README.md +99 -95
- package/README_CN.md +187 -0
- package/bin/xfm.js +1 -1
- package/lib/cli/scaffold.js +1 -1
- package/package.json +6 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.1] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- English `README.md` is now the default document shown on the npm page;
|
|
12
|
+
the Chinese version ships as `README_CN.md`, and both link to each other.
|
|
13
|
+
- Documentation split into English/Chinese pairs: `README` / `README_CN`,
|
|
14
|
+
`INSTALL` / `INSTALL_CN`, `PROGRAM` / `PROGRAM_CN`.
|
|
15
|
+
- Dev server port corrected to `4173` in all docs and scaffolds
|
|
16
|
+
(previously documented inconsistently as `4000` / `4321`).
|
|
17
|
+
- Package description rewritten in English to match the npm-facing README.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Docs referenced the wrong install target (`npm i -D xfm`); the installable
|
|
22
|
+
package is `xfmpress` while the CLI command remains `xfm`.
|
|
23
|
+
- Configuration examples now consistently use the `site: {}` shape with
|
|
24
|
+
a top-level `outDir`.
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-11-02
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Initial release: zero-dependency static site generator with a shared
|
|
31
|
+
render pipeline used by both the main thread and worker threads.
|
|
32
|
+
- Multi-threaded page rendering with content-hashed incremental builds.
|
|
33
|
+
- Nunjucks-style template engine compiled to JavaScript functions at build
|
|
34
|
+
time, with optional-chaining guards generated at compile time.
|
|
35
|
+
- Plugin system with `markdown`, `transformHtml`, `extendContext`,
|
|
36
|
+
`afterBuild`, `configureServer` and `finish` hooks.
|
|
37
|
+
- Built-in `default` and `docs` themes, plus `xfm create` scaffolding.
|
|
38
|
+
- Dev server with SSE live reload and configuration hot reload.
|
|
39
|
+
- `xfm new`, `xfm build`, `xfm dev`, `xfm clean` commands.
|
|
40
|
+
|
|
41
|
+
[0.1.1]: https://github.com/xfm0797/xfm/compare/v0.1.0...v0.1.1
|
|
42
|
+
[0.1.0]: https://github.com/xfm0797/xfm/releases/tag/v0.1.0
|
package/INSTALL.md
CHANGED
|
@@ -1,81 +1,83 @@
|
|
|
1
|
-
# xfm
|
|
1
|
+
# xfm — Installation & Usage
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> Assumes a Node 22 environment. From zero to a running site takes about a minute.
|
|
4
|
+
> Repository: `https://github.com/xfm0797/xfm`
|
|
5
|
+
|
|
6
|
+
[English](./INSTALL.md) | [简体中文](./INSTALL_CN.md)
|
|
5
7
|
|
|
6
8
|
---
|
|
7
9
|
|
|
8
|
-
##
|
|
10
|
+
## 1. Prerequisites
|
|
9
11
|
|
|
10
|
-
|
|
|
12
|
+
| Item | Requirement | Check |
|
|
11
13
|
| --- | --- | --- |
|
|
12
|
-
| Node.js | **18
|
|
13
|
-
| npm |
|
|
14
|
+
| Node.js | **18 or newer** (22 LTS recommended) | `node -v` |
|
|
15
|
+
| npm | Ships with Node (>= 9 recommended) | `npm -v` |
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
|
-
node -v #
|
|
17
|
-
npm -v #
|
|
18
|
+
node -v # expect v22.x
|
|
19
|
+
npm -v # expect 10.x or 11.x
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
Node
|
|
22
|
+
An outdated Node version triggers `EBADENGINE`. Switch to 22 with [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm):
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
nvm install 22 && nvm use 22
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
xfm
|
|
28
|
+
xfm has **zero runtime dependencies**, so installing it pulls no third-party packages — which also means no dependency-tree conflicts and no `npm audit` warnings.
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
30
|
-
##
|
|
32
|
+
## 2. Install (pick one of three methods)
|
|
31
33
|
|
|
32
|
-
###
|
|
34
|
+
### Option A: Global install — use `xfm` like you use `hexo`
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
37
|
npm install -g xfmpress
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
Verify:
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
43
|
xfm --help
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
If you see the `⚡ xfm v0.1.0` banner, you are good to go.
|
|
45
47
|
|
|
46
|
-
>
|
|
48
|
+
> **About the name**: the npm package is **`xfmpress`**, the command is **`xfm`** (`npm i -g xfmpress`, then call `xfm`). It is a Static Site Generator, sharing the `xfm` brand with the `hexo-theme-xfm` family. The project was originally called `bolt`; `bolt`/`bolt-ssg` were taken, and `xmssg` and `xfm` were both rejected by npm's "too similar to an existing package" check (against `xss`, then against `xo`/`xml`/`htm` and friends), so `xfmpress` has been the name since 0.1.0.
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
**Hit `EACCES: permission denied`?** There are two proper fixes; do not use `sudo npm i -g`:
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
|
-
#
|
|
53
|
+
# Fix 1 (recommended): move the global prefix into your home directory
|
|
52
54
|
npm config set prefix ~/.npm-global
|
|
53
|
-
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc #
|
|
55
|
+
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc # use ~/.bashrc for bash
|
|
54
56
|
source ~/.zshrc
|
|
55
57
|
|
|
56
|
-
#
|
|
58
|
+
# Fix 2: manage Node with nvm (global packages already live in your home directory)
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
###
|
|
61
|
+
### Option B: Run without installing — just want to try it
|
|
60
62
|
|
|
61
63
|
```bash
|
|
62
64
|
npx xfmpress --help
|
|
63
65
|
npx xfmpress create my-blog --template=blog
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
`npx`
|
|
68
|
+
`npx` downloads into a temporary cache and runs from there, leaving no global command behind. Good for a quick look or a one-off build in CI.
|
|
67
69
|
|
|
68
|
-
###
|
|
70
|
+
### Option C: Install into a project — best for teams
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
Add xfm as a dev dependency so everyone gets the **exact same** version after `npm install`:
|
|
71
73
|
|
|
72
74
|
```bash
|
|
73
75
|
mkdir my-blog && cd my-blog
|
|
74
76
|
npm init -y
|
|
75
|
-
npm i -D
|
|
77
|
+
npm i -D xfmpress
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
Then add scripts to `package.json`:
|
|
79
81
|
|
|
80
82
|
```json
|
|
81
83
|
{
|
|
@@ -88,201 +90,207 @@ npm i -D xfm
|
|
|
88
90
|
}
|
|
89
91
|
```
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
From then on use `npm run dev` / `npm run build`; for a one-off command use `npx xfmpress info`.
|
|
92
94
|
|
|
93
|
-
>
|
|
95
|
+
> The downside of Option C: you cannot run `xfm create` in an empty directory, because there is no `node_modules` yet. Scaffold the site with Option A or B first, then install it locally.
|
|
94
96
|
|
|
95
97
|
---
|
|
96
98
|
|
|
97
|
-
##
|
|
99
|
+
## 3. Quick start
|
|
98
100
|
|
|
99
|
-
### 1.
|
|
101
|
+
### 1. Create a site
|
|
100
102
|
|
|
101
103
|
```bash
|
|
102
104
|
xfm create my-blog --template=blog
|
|
103
105
|
cd my-blog
|
|
104
106
|
```
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
Available templates:
|
|
107
109
|
|
|
108
|
-
| `--template` |
|
|
110
|
+
| `--template` | Look | Good for |
|
|
109
111
|
| --- | --- | --- |
|
|
110
|
-
| `blog
|
|
111
|
-
| `docs` |
|
|
112
|
+
| `blog` (default) | Blog: post list on the home page, post pages, right-hand TOC | Personal blogs, tech notes |
|
|
113
|
+
| `docs` | Documentation: grouped left sidebar + body | Product docs, knowledge bases |
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
You can also set the site title:
|
|
114
116
|
|
|
115
117
|
```bash
|
|
116
|
-
xfm create my-blog --template=docs --title="
|
|
118
|
+
xfm create my-blog --template=docs --title="My Knowledge Base"
|
|
117
119
|
```
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
Generated structure:
|
|
120
122
|
|
|
121
123
|
```
|
|
122
124
|
my-blog/
|
|
123
|
-
├── xfm.config.mjs #
|
|
124
|
-
├── content/
|
|
125
|
+
├── xfm.config.mjs # site config (sensible defaults, edit as needed)
|
|
126
|
+
├── content/ # all Markdown sources
|
|
125
127
|
│ ├── index.md
|
|
126
128
|
│ ├── about.md
|
|
127
129
|
│ └── posts/
|
|
128
130
|
│ └── hello-world.md
|
|
129
|
-
├── plugins/
|
|
130
|
-
├── public/
|
|
131
|
+
├── plugins/ # your own plugins
|
|
132
|
+
├── public/ # static files copied verbatim to the output root
|
|
131
133
|
└── README.md
|
|
132
134
|
```
|
|
133
135
|
|
|
134
|
-
### 2.
|
|
136
|
+
### 2. Local development
|
|
135
137
|
|
|
136
138
|
```bash
|
|
137
139
|
xfm dev
|
|
138
140
|
```
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
Open `http://127.0.0.1:4173/`. Edit Markdown under `content/` or templates under `themes/`, save, and the browser **refreshes itself** (SSE push, no browser extension needed).
|
|
141
143
|
|
|
142
144
|
```bash
|
|
143
|
-
xfm dev --port=5000 #
|
|
145
|
+
xfm dev --port=5000 # different port
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
### 3.
|
|
148
|
+
### 3. Write a post
|
|
147
149
|
|
|
148
150
|
```bash
|
|
149
|
-
xfm new "
|
|
150
|
-
# ✓
|
|
151
|
+
xfm new "My First Post"
|
|
152
|
+
# ✓ created content/posts/my-first-post.md (the title is slugified into the filename)
|
|
151
153
|
```
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
Titles with spaces or non-ASCII characters are handled automatically:
|
|
154
156
|
|
|
155
157
|
```bash
|
|
156
158
|
xfm new "Hello, World!" # → content/posts/hello-world.md
|
|
157
|
-
xfm new "
|
|
158
|
-
xfm new "
|
|
159
|
+
xfm new "Deploy notes" --slug=deploy-notes # → explicit filename
|
|
160
|
+
xfm new "About" --dir=pages # → under content/pages/
|
|
159
161
|
```
|
|
160
162
|
|
|
161
|
-
>
|
|
163
|
+
> **Note**: posts created by `xfm new` are **publishable by default** and show up on the next `xfm build`. To write a work in progress, use `xfm new --draft` (writes `draft: true`); such posts never appear in production builds. Set `draft` to `false` or delete the line when you are ready to publish.
|
|
162
164
|
|
|
163
|
-
### 4.
|
|
165
|
+
### 4. Production build
|
|
164
166
|
|
|
165
167
|
```bash
|
|
166
168
|
xfm build
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
Output goes to `dist/`, fully static — drop it on Nginx, object storage, Vercel, Netlify or GitHub Pages.
|
|
170
172
|
|
|
171
173
|
```
|
|
172
|
-
✓
|
|
173
|
-
✓
|
|
174
|
-
✓
|
|
175
|
-
|
|
174
|
+
✓ pages 5
|
|
175
|
+
✓ extra files 4 (sitemap / rss, etc.)
|
|
176
|
+
✓ output dist
|
|
177
|
+
done in 30 ms
|
|
176
178
|
```
|
|
177
179
|
|
|
178
|
-
|
|
180
|
+
Rebuilds use the incremental cache and only re-render changed pages:
|
|
179
181
|
|
|
180
182
|
```bash
|
|
181
|
-
xfm build --dev #
|
|
183
|
+
xfm build --dev # enable the incremental cache in dev mode too
|
|
182
184
|
```
|
|
183
185
|
|
|
184
|
-
### 5.
|
|
186
|
+
### 5. Other commands
|
|
185
187
|
|
|
186
188
|
```bash
|
|
187
|
-
xfm info #
|
|
188
|
-
xfm clean #
|
|
189
|
-
xfm --help #
|
|
189
|
+
xfm info # show the resolved config (theme, content dir, plugins, Node version)
|
|
190
|
+
xfm clean # wipe dist/ and the incremental cache
|
|
191
|
+
xfm --help # every command and option
|
|
190
192
|
```
|
|
191
193
|
|
|
192
194
|
---
|
|
193
195
|
|
|
194
|
-
##
|
|
196
|
+
## 4. Writing Markdown
|
|
195
197
|
|
|
196
|
-
|
|
198
|
+
Content is plain Markdown — no proprietary syntax. Common extensions:
|
|
197
199
|
|
|
198
200
|
````markdown
|
|
199
201
|
---
|
|
200
|
-
title:
|
|
202
|
+
title: Post title
|
|
201
203
|
date: 2024-06-01
|
|
202
|
-
tags: [
|
|
203
|
-
description:
|
|
204
|
+
tags: [performance, node]
|
|
205
|
+
description: Used for SEO and list summaries
|
|
204
206
|
layout: post.html
|
|
205
207
|
draft: false
|
|
206
208
|
---
|
|
207
209
|
|
|
208
|
-
##
|
|
210
|
+
## H2 headings land in the right-hand TOC automatically
|
|
209
211
|
|
|
210
|
-
|
|
212
|
+
Normal paragraphs support **bold**, *italic*, `inline code`, ~~strikethrough~~, [links](/about/).
|
|
211
213
|
|
|
212
|
-
|
|
|
214
|
+
| Syntax | Notes |
|
|
213
215
|
| --- | --- |
|
|
214
|
-
|
|
|
216
|
+
| Tables | Standard GFM |
|
|
215
217
|
|
|
216
|
-
- [x]
|
|
217
|
-
- [ ]
|
|
218
|
+
- [x] Task lists work
|
|
219
|
+
- [ ] Unfinished item
|
|
218
220
|
|
|
219
|
-
::: tip
|
|
220
|
-
|
|
221
|
+
::: tip Callout
|
|
222
|
+
Four kinds: `tip` / `info` / `warning` / `danger`.
|
|
221
223
|
:::
|
|
222
224
|
|
|
223
225
|
```js
|
|
224
|
-
const ok = true; //
|
|
226
|
+
const ok = true; // code blocks are highlighted automatically
|
|
225
227
|
```
|
|
226
228
|
````
|
|
227
229
|
|
|
228
|
-
|
|
230
|
+
Highlighting supports: `js / javascript / jsx / ts / tsx / java / c / cpp / csharp / go / rust / swift / kotlin / scala / php / python / bash / sh / json / yaml / html / xml / svg / vue / css / scss / markdown`, case-insensitive and alias-aware; unknown languages degrade to plain text instead of failing. Fences can carry a title: `` ```js app.js ``.
|
|
229
231
|
|
|
230
232
|
---
|
|
231
233
|
|
|
232
|
-
##
|
|
234
|
+
## 5. Configuration
|
|
233
235
|
|
|
234
|
-
`xfm.config.mjs`
|
|
236
|
+
`xfm.config.mjs` is a plain ESM module — export an object and you are done. The dev server picks up changes automatically:
|
|
235
237
|
|
|
236
238
|
```js
|
|
237
239
|
export default {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
site: {
|
|
241
|
+
title: 'My Blog',
|
|
242
|
+
description: 'Notes on tech and life',
|
|
243
|
+
url: 'https://example.com', // absolute links for sitemap / rss
|
|
244
|
+
lang: 'en',
|
|
245
|
+
base: '/',
|
|
246
|
+
nav: [{ title: 'Home', url: '/' }],
|
|
247
|
+
},
|
|
242
248
|
contentDir: 'content',
|
|
243
|
-
|
|
249
|
+
outDir: 'dist',
|
|
250
|
+
theme: 'default', // built-in: default | docs; or a local path
|
|
244
251
|
plugins: [
|
|
245
252
|
'sitemap',
|
|
246
253
|
'rss',
|
|
247
|
-
'./plugins/my-plugin.js', //
|
|
254
|
+
'./plugins/my-plugin.js', // local plugin
|
|
248
255
|
],
|
|
249
|
-
workers: 0, // 0 =
|
|
256
|
+
workers: 0, // 0 = auto; number of render workers
|
|
257
|
+
server: { port: 4173 }, // dev port
|
|
250
258
|
};
|
|
251
259
|
```
|
|
252
260
|
|
|
253
|
-
|
|
261
|
+
Every field has a default, so the minimal config is an empty object. Full reference: `README.md` (English) / `README_CN.md` (Chinese).
|
|
254
262
|
|
|
255
263
|
---
|
|
256
264
|
|
|
257
|
-
##
|
|
265
|
+
## 6. FAQ
|
|
258
266
|
|
|
259
267
|
**`xfm: command not found`**
|
|
260
|
-
|
|
268
|
+
The global npm `bin` directory is not on your `PATH`. Run `npm config get prefix` and add its `bin` subdirectory to `PATH`. With nvm, opening a fresh terminal usually fixes it.
|
|
261
269
|
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
**`EACCES: permission denied` during install**
|
|
271
|
+
See the global-prefix fix under Option A. Do not use `sudo`.
|
|
264
272
|
|
|
265
|
-
**`node -v`
|
|
266
|
-
|
|
273
|
+
**`node -v` is below 18**
|
|
274
|
+
Upgrade Node, then reinstall: `npm i -g xfmpress --force`.
|
|
267
275
|
|
|
268
|
-
**`xfm new`
|
|
269
|
-
|
|
276
|
+
**`xfm new` created a post but it is not on the site**
|
|
277
|
+
Most likely `draft: true`. Change it to `false` and run `xfm build` again.
|
|
270
278
|
|
|
271
|
-
**Windows
|
|
272
|
-
|
|
279
|
+
**On Windows, `xfm` is "not recognized as an internal or external command"**
|
|
280
|
+
Reopen your terminal in Git Bash or PowerShell. If it still fails, check that `%APPDATA%\npm` is on your `Path`.
|
|
273
281
|
|
|
274
|
-
|
|
282
|
+
**Upgrading**
|
|
275
283
|
```bash
|
|
276
|
-
npm i -g xfmpress@latest #
|
|
277
|
-
npm i -D
|
|
284
|
+
npm i -g xfmpress@latest # global
|
|
285
|
+
npm i -D xfmpress@latest # in a project
|
|
278
286
|
```
|
|
279
287
|
|
|
280
288
|
---
|
|
281
289
|
|
|
282
|
-
##
|
|
290
|
+
## 7. One-liner (copy & paste)
|
|
283
291
|
|
|
284
292
|
```bash
|
|
285
|
-
#
|
|
293
|
+
# install globally + scaffold a blog + start the dev server
|
|
286
294
|
npm i -g xfmpress \
|
|
287
295
|
&& xfm create my-blog --template=blog \
|
|
288
296
|
&& cd my-blog \
|
package/INSTALL_CN.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# xfm 安装与使用说明
|
|
2
|
+
|
|
3
|
+
> 面向 Node 22 环境,从零到跑起来大约 1 分钟。
|
|
4
|
+
> 项目地址:`https://github.com/xfm0797/xfm`
|
|
5
|
+
|
|
6
|
+
[English](./INSTALL.md) | 简体中文
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 一、先决条件
|
|
11
|
+
|
|
12
|
+
| 项目 | 要求 | 检查命令 |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Node.js | **18 或更高**(推荐 22 LTS) | `node -v` |
|
|
15
|
+
| npm | 随 Node 附带(推荐 ≥ 9) | `npm -v` |
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node -v # 期望 v22.x
|
|
19
|
+
npm -v # 期望 10.x 或 11.x
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Node 版本过旧会报 `EBADENGINE`,用 [nvm](https://github.com/nvm-sh/nvm) 或 [fnm](https://github.com/Schniz/fnm) 切到 22 即可:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
nvm install 22 && nvm use 22
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
xfm 是**零运行时依赖**的,安装时不会拉取任何第三方包,也因此不会出现依赖树冲突、`npm audit` 报警。
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 二、安装(三种方式,按需选一种)
|
|
33
|
+
|
|
34
|
+
### 方式 A:全局安装 —— 和 Hexo 一样用 `xfm` 命令
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g xfmpress
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
验证:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
xfm --help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
看到 `⚡ xfm v0.1.0` 的版本横幅即安装成功。
|
|
47
|
+
|
|
48
|
+
> **命名说明**:npm 包名是 **`xfmpress`**,命令是 **`xfm`**(`npm i -g xfmpress` 之后用 `xfm` 调用)。定位是 Static Site Generator(静态站点生成器),与同系列的 `hexo-theme-xfm` 共用品牌。项目早期叫 `bolt`,`bolt`/`bolt-ssg` 已被占用,`xmssg` 与 `xfm` 又分别被 npm 的"名字过于相似"校验拦下(撞 `xss`、以及 `xo`/`xml`/`htm` 等一批短包名),0.1.0 起定为 `xfmpress`。
|
|
49
|
+
|
|
50
|
+
**权限报错怎么办**(`EACCES: permission denied`)?有两种正规解法,不要用 `sudo npm i -g`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 解法 1(推荐):把全局前缀改到用户目录
|
|
54
|
+
npm config set prefix ~/.npm-global
|
|
55
|
+
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc # bash 用户改成 ~/.bashrc
|
|
56
|
+
source ~/.zshrc
|
|
57
|
+
|
|
58
|
+
# 解法 2:用 nvm 管理 Node(全局包天然在用户目录,无需额外配置)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 方式 B:免安装运行 —— 只想先试试
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx xfmpress --help
|
|
65
|
+
npx xfmpress create my-blog --template=blog
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`npx` 会自动下载到临时缓存并执行,不在系统里留下全局命令。适合快速体验或 CI 里一次性构建。
|
|
69
|
+
|
|
70
|
+
### 方式 C:项目内安装 —— 团队协作推荐
|
|
71
|
+
|
|
72
|
+
把 xfm 作为开发依赖写进项目,任何人 `npm install` 后即可获得**完全一致**的版本:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
mkdir my-blog && cd my-blog
|
|
76
|
+
npm init -y
|
|
77
|
+
npm i -D xfmpress
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
然后在 `package.json` 里加脚本:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"scripts": {
|
|
85
|
+
"dev": "xfm dev",
|
|
86
|
+
"build": "xfm build",
|
|
87
|
+
"new": "xfm new",
|
|
88
|
+
"clean": "xfm clean"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
之后用 `npm run dev` / `npm run build` 即可;临时调用可用 `npx xfmpress info`。
|
|
94
|
+
|
|
95
|
+
> 方式 C 的缺点是不能在空目录里用 `xfm create` 生成脚手架(那时还没有 `node_modules`)。建议先用方式 A 或 B 生成站点,再进目录装成本地依赖。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 三、快速上手
|
|
100
|
+
|
|
101
|
+
### 1. 创建站点
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
xfm create my-blog --template=blog
|
|
105
|
+
cd my-blog
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
可选模板:
|
|
109
|
+
|
|
110
|
+
| `--template` | 外观 | 适合 |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `blog`(默认) | 博客:首页列表 + 文章页 + 右侧目录 | 个人博客、技术随笔 |
|
|
113
|
+
| `docs` | 文档:左侧分组侧栏 + 正文 | 产品文档、知识库 |
|
|
114
|
+
|
|
115
|
+
也能指定站名:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
xfm create my-blog --template=docs --title="我的知识库"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
生成的结构:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
my-blog/
|
|
125
|
+
├── xfm.config.mjs # 站点配置(有合理默认值,可按需改)
|
|
126
|
+
├── content/ # 所有 Markdown 源文件
|
|
127
|
+
│ ├── index.md
|
|
128
|
+
│ ├── about.md
|
|
129
|
+
│ └── posts/
|
|
130
|
+
│ └── hello-world.md
|
|
131
|
+
├── plugins/ # 你自己的插件
|
|
132
|
+
├── public/ # 原样拷贝到产物根目录的静态文件
|
|
133
|
+
└── README.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2. 本地开发
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
xfm dev
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
打开 `http://127.0.0.1:4173/`。改 `content/` 里的 Markdown 或 `themes/` 里的模板,保存后浏览器**自动刷新**(SSE 推送,无需装浏览器插件)。
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
xfm dev --port=5000 # 换端口
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. 写文章
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
xfm new "我的第一篇文章"
|
|
152
|
+
# ✓ 已创建 content/posts/我的第一篇文章.md (文件名由标题自动转为 slug)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
带空格或多语言的标题会自动处理文件名:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
xfm new "Hello, World!" # → content/posts/hello-world.md
|
|
159
|
+
xfm new "部署笔记" --slug=deploy-notes # → 手动指定文件名
|
|
160
|
+
xfm new "关于" --dir=pages # → 放到 content/pages/ 下
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **注意**:`xfm new` 生成的文章**默认可发布**,`xfm build` 立刻就能看到。想先写半成品,用 `xfm new --draft`(会写入 `draft: true`),这类文章不会出现在生产构建里;要发布时把 `draft` 改成 `false` 或删掉该行即可。
|
|
164
|
+
|
|
165
|
+
### 4. 生产构建
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
xfm build
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
产物在 `dist/`,纯静态,扔到 Nginx / 对象存储 / Vercel / Netlify / GitHub Pages 都能跑。
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
✓ 页面 5 个
|
|
175
|
+
✓ 附加产物 4 个(sitemap / rss 等)
|
|
176
|
+
✓ 输出目录 dist
|
|
177
|
+
耗时 30 ms
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
重构建会走增量缓存,只重新渲染改动过的页面:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
xfm build --dev # 开发模式下也启用增量缓存
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 5. 其他命令
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
xfm info # 查看当前站点解析后的配置(主题、内容目录、插件、Node 版本)
|
|
190
|
+
xfm clean # 清空 dist/ 与增量缓存
|
|
191
|
+
xfm --help # 全部命令与选项
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 四、写 Markdown
|
|
197
|
+
|
|
198
|
+
内容就是标准 Markdown,没有私有语法。常用增强:
|
|
199
|
+
|
|
200
|
+
````markdown
|
|
201
|
+
---
|
|
202
|
+
title: 文章标题
|
|
203
|
+
date: 2024-06-01
|
|
204
|
+
tags: [性能, Node]
|
|
205
|
+
description: 用于 SEO 与列表摘要
|
|
206
|
+
layout: post.html
|
|
207
|
+
draft: false
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 二级标题会自动进右侧目录
|
|
211
|
+
|
|
212
|
+
普通段落支持 **加粗**、*斜体*、`行内代码`、~~删除线~~、[链接](/about/)。
|
|
213
|
+
|
|
214
|
+
| 语法 | 说明 |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| 表格 | 标准 GFM |
|
|
217
|
+
|
|
218
|
+
- [x] 任务列表已支持
|
|
219
|
+
- [ ] 未完成项
|
|
220
|
+
|
|
221
|
+
::: tip 提示容器
|
|
222
|
+
支持 `tip` / `info` / `warning` / `danger` 四种。
|
|
223
|
+
:::
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
const ok = true; // 代码块自动语法高亮
|
|
227
|
+
```
|
|
228
|
+
````
|
|
229
|
+
|
|
230
|
+
代码高亮支持:`js / javascript / jsx / ts / tsx / java / c / cpp / csharp / go / rust / swift / kotlin / scala / php / python / bash / sh / json / yaml / html / xml / svg / vue / css / scss / markdown`,大小写与常见别名都能识别;未知语言降级为纯文本,不会报错。围栏可加标题:`` ```js app.js ``。
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 五、配置
|
|
235
|
+
|
|
236
|
+
`xfm.config.mjs` 是一个普通 ESM 模块,导出对象即可,改动后 dev 服务器会自动重启:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
export default {
|
|
240
|
+
site: {
|
|
241
|
+
title: '我的博客',
|
|
242
|
+
description: '记录技术与生活',
|
|
243
|
+
url: 'https://example.com', // 用于 sitemap / rss 的绝对链接
|
|
244
|
+
lang: 'zh-CN',
|
|
245
|
+
base: '/',
|
|
246
|
+
nav: [{ title: '首页', url: '/' }],
|
|
247
|
+
},
|
|
248
|
+
contentDir: 'content',
|
|
249
|
+
outDir: 'dist',
|
|
250
|
+
theme: 'default', // 内置主题:default | docs;也可填本地路径
|
|
251
|
+
plugins: [
|
|
252
|
+
'sitemap',
|
|
253
|
+
'rss',
|
|
254
|
+
'./plugins/my-plugin.js', // 本地插件
|
|
255
|
+
],
|
|
256
|
+
workers: 0, // 0 = 自动;渲染进程数
|
|
257
|
+
server: { port: 4173 }, // dev 端口
|
|
258
|
+
};
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
所有字段都有默认值,最小配置就是一个空对象。完整字段见 `README_CN.md`(中文)/ `README.md`(English)。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 六、常见问题
|
|
266
|
+
|
|
267
|
+
**`xfm: command not found`**
|
|
268
|
+
全局 npm 的 `bin` 目录不在 `PATH` 里。执行 `npm bin -g`(旧版)或 `npm config get prefix`,把其下的 `bin` 加进 `PATH`。用 nvm 的话重开一个终端通常就能解决。
|
|
269
|
+
|
|
270
|
+
**安装时报 `EACCES: permission denied`**
|
|
271
|
+
见「方式 A」里的全局前缀改法,不要用 `sudo`。
|
|
272
|
+
|
|
273
|
+
**`node -v` 低于 18**
|
|
274
|
+
升级 Node 后重装:`npm i -g xfmpress --force`。
|
|
275
|
+
|
|
276
|
+
**`xfm new` 建了文章但页面上没有**
|
|
277
|
+
大概率是 `draft: true`。改成 `false` 后重新 `xfm build`。
|
|
278
|
+
|
|
279
|
+
**Windows 下 `xfm` 提示不是内部或外部命令**
|
|
280
|
+
用 Git Bash 或 PowerShell 重开终端;仍不行则检查 `%APPDATA%\npm` 是否在 `Path` 中。
|
|
281
|
+
|
|
282
|
+
**想升级**
|
|
283
|
+
```bash
|
|
284
|
+
npm i -g xfmpress@latest # 全局
|
|
285
|
+
npm i -D xfmpress@latest # 项目内
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 七、一键脚本(复制即用)
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# 全局安装 + 创建博客 + 本地预览
|
|
294
|
+
npm i -g xfmpress \
|
|
295
|
+
&& xfm create my-blog --template=blog \
|
|
296
|
+
&& cd my-blog \
|
|
297
|
+
&& xfm dev
|
|
298
|
+
```
|
package/README.md
CHANGED
|
@@ -1,140 +1,144 @@
|
|
|
1
1
|
# ⚡ xfm
|
|
2
2
|
|
|
3
|
-
**A zero-dependency static site generator for Node.js.**
|
|
3
|
+
**A zero-dependency static site generator for Node.js.** Zero runtime dependencies, multi-threaded rendering, Nunjucks-style templates, drop-in themes and plugins.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[English](./README.md) | [简体中文](./README_CN.md)
|
|
6
|
+
|
|
7
|
+
## Install & Quick Start
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm i -g xfmpress #
|
|
9
|
-
xfm create my-blog # 30
|
|
10
|
-
cd my-blog && xfm dev #
|
|
11
|
-
xfm build #
|
|
10
|
+
npm i -g xfmpress # install globally (Node >= 18); the command is xfm
|
|
11
|
+
xfm create my-blog # scaffold a site in 30 seconds
|
|
12
|
+
cd my-blog && xfm dev # instant hot reload
|
|
13
|
+
xfm build # production build
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
16
|
+
> **Package name `xfmpress`, command `xfm`** (`npm i -g xfmpress`, then call `xfm`).
|
|
17
|
+
> This is a **static site generator**: it compresses Markdown into plain static HTML.
|
|
18
|
+
> Shares the `xfm` brand with [hexo-theme-xfm](https://github.com/xfm0797/hexo-theme-xfm).
|
|
19
|
+
> All three install methods, permission troubleshooting and the FAQ live in **[INSTALL.md](./INSTALL.md)**.
|
|
17
20
|
|
|
18
|
-
##
|
|
21
|
+
## Performance
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
Benchmark: 32-core machine, Node 22, 1,000 Chinese documents (~5 MB of HTML output). Reproduce with `npm run bench`.
|
|
21
24
|
|
|
22
|
-
|
|
|
25
|
+
| Metric | Value |
|
|
23
26
|
| --- | --- |
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
27
|
+
| Full cold build | **≈0.38–0.42 s** (≈2,400–2,700 pages/s) |
|
|
28
|
+
| Render phase with warm cache | **1 ms** (1,001 pages) |
|
|
29
|
+
| Dev hot rebuild of a single page | **2–7 ms** |
|
|
30
|
+
| Runtime footprint | plain static HTML + CSS, **zero JavaScript** |
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
Per-stage timing (1,000 pages):
|
|
30
33
|
|
|
31
|
-
|
|
|
34
|
+
| Stage | Time | How |
|
|
32
35
|
| --- | --- | --- |
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
36
|
+
| Scan & parse | ~128 ms | Parallel directory walk + parallel source reads; each file is read once |
|
|
37
|
+
| Hashing | ~9 ms | Reuses the already-read source text, no second IO |
|
|
38
|
+
| Render | ~91 ms | Worker pool reuses threads; site context is serialized once |
|
|
39
|
+
| Write | ~132 ms | One `mkdir` per directory, two-phase parallel writes |
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
Four design decisions carry the performance:
|
|
39
42
|
|
|
40
|
-
1.
|
|
41
|
-
2.
|
|
42
|
-
3. **
|
|
43
|
-
4.
|
|
43
|
+
1. **Line-scanning Markdown engine**: advances line by line at block level plus a single inline regex pass, no AST. Ships CJK and English anchors, tables, task lists (with continuation lines and nesting), syntax highlighting and `::: callout` containers.
|
|
44
|
+
2. **Templates compiled to functions**: Nunjucks-style syntax is compiled into plain JS functions at build time, so rendering is string concatenation; undefined variables resolve safely to `undefined`.
|
|
45
|
+
3. **Worker pool**: threads and template compile caches are reused for the whole run, and the site context is serialized only once. Concurrency is calibrated by *minimum work per thread* — 2–4 threads measured fastest; forcing 8 is actually slower.
|
|
46
|
+
4. **Incremental builds**: a page is reused from cache when `source hash + theme fingerprint` are unchanged.
|
|
44
47
|
|
|
45
|
-
>
|
|
46
|
-
>
|
|
48
|
+
> More workers is not better: spinning up a worker and cloning the context costs ~30–60 ms each,
|
|
49
|
+
> which eats the parallel gain on small workloads. `pickConcurrency()` calibrates from measurements
|
|
50
|
+
> instead of blindly matching CPU core count.
|
|
47
51
|
|
|
48
|
-
##
|
|
52
|
+
## Features
|
|
49
53
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
54
|
+
- **Themes**: `{% extend %}` / `{% block %}` template inheritance, `{% include %}` partials, CSS-variable light/dark themes. Ships `default` (blog) and `docs` (sidebar documentation).
|
|
55
|
+
- **Plugins**: five hooks (`extendContext` / `markdown` / `transformHtml` / `afterBuild` / `configureServer`); drop a file into `plugins/` and it loads itself. Ships sitemap, RSS and reading-stats.
|
|
56
|
+
- **Content**: YAML front-matter, drafts, tag/category/series taxonomies, automatic prev/next, `weight` ordering, and relative `.md` links resolved automatically.
|
|
57
|
+
- **Developer experience**: `xfm dev` builds in memory with SSE hot reload (keeps your scroll position), hot-swaps CSS separately, and lists available routes on 404.
|
|
58
|
+
- **Easy to migrate**: template syntax follows Nunjucks/Liquid conventions; config supports `.mjs/.js/.json`.
|
|
55
59
|
|
|
56
|
-
##
|
|
60
|
+
## Install
|
|
57
61
|
|
|
58
62
|
```bash
|
|
59
|
-
npm i -g xfmpress #
|
|
60
|
-
#
|
|
63
|
+
npm i -g xfmpress # then use the xfm command (Node >= 18)
|
|
64
|
+
# or try without installing: npx xfmpress --help
|
|
61
65
|
```
|
|
62
66
|
|
|
63
|
-
>
|
|
64
|
-
>
|
|
67
|
+
> The package is `xfmpress`, the command is `xfm` — install once, use anywhere.
|
|
68
|
+
> Full install guide, permission troubleshooting and three install methods: **[INSTALL.md](./INSTALL.md)**.
|
|
65
69
|
|
|
66
|
-
##
|
|
70
|
+
## Commands
|
|
67
71
|
|
|
68
|
-
|
|
|
72
|
+
| Command | Description |
|
|
69
73
|
| --- | --- |
|
|
70
|
-
| `xfm create [
|
|
71
|
-
| `xfm dev [
|
|
72
|
-
| `xfm build [
|
|
73
|
-
| `xfm new "
|
|
74
|
-
| `xfm clean` / `xfm info` |
|
|
74
|
+
| `xfm create [dir]` | Scaffold a site (`--template=blog\|docs`, `--title=`) |
|
|
75
|
+
| `xfm dev [dir]` | Dev server (`--port=`) |
|
|
76
|
+
| `xfm build [dir]` | Production build (`--dev` enables incremental cache) |
|
|
77
|
+
| `xfm new "Title"` | New post (`--dir=posts --slug=xxx --draft`) |
|
|
78
|
+
| `xfm clean` / `xfm info` | Clean output / show config |
|
|
75
79
|
|
|
76
|
-
##
|
|
80
|
+
## Configuration (`xfm.config.mjs`)
|
|
77
81
|
|
|
78
82
|
```js
|
|
79
83
|
export default {
|
|
80
84
|
site: {
|
|
81
85
|
title: 'XFM Notes',
|
|
82
|
-
description: '
|
|
83
|
-
lang: '
|
|
86
|
+
description: 'Notes on static sites and the frontend',
|
|
87
|
+
lang: 'en',
|
|
84
88
|
base: '/',
|
|
85
|
-
url: 'https://example.com', //
|
|
86
|
-
nav: [{ title: '
|
|
89
|
+
url: 'https://example.com', // used by sitemap/RSS
|
|
90
|
+
nav: [{ title: 'Posts', url: '/' }],
|
|
87
91
|
},
|
|
88
|
-
theme: 'default', //
|
|
89
|
-
plugins: ['sitemap', 'rss'], //
|
|
92
|
+
theme: 'default', // theme name or path
|
|
93
|
+
plugins: ['sitemap', 'rss'], // built-in names or paths
|
|
90
94
|
defaults: { layout: 'post.html' },
|
|
91
|
-
workers: 0, // worker
|
|
92
|
-
server: { port:
|
|
95
|
+
workers: 0, // worker count; 0 = auto-calibrated
|
|
96
|
+
server: { port: 4173 },
|
|
93
97
|
};
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
##
|
|
100
|
+
## Writing (`content/`)
|
|
97
101
|
|
|
98
102
|
```markdown
|
|
99
103
|
---
|
|
100
|
-
title:
|
|
104
|
+
title: Post title
|
|
101
105
|
date: 2026-10-28 09:00:00
|
|
102
|
-
tags: [
|
|
103
|
-
description:
|
|
106
|
+
tags: [nodejs, performance]
|
|
107
|
+
description: Summary (used by list pages and RSS)
|
|
104
108
|
draft: false
|
|
105
109
|
---
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
Body supports the common GFM syntax, plus:
|
|
108
112
|
|
|
109
|
-
::: tip
|
|
110
|
-
|
|
113
|
+
::: tip Callout
|
|
114
|
+
A highlighted box with a title.
|
|
111
115
|
:::
|
|
112
116
|
|
|
113
117
|
\`\`\`js
|
|
114
|
-
const ok = true; //
|
|
118
|
+
const ok = true; // built-in syntax highlighting
|
|
115
119
|
\`\`\`
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
Fence languages (aliases normalized, case-insensitive): `js / javascript / jsx / ts / tsx / java / c / cpp / csharp / go / golang / rust / rs / swift / kotlin / scala / php / python / py / bash / sh / shell / json / yaml / yml / html / xml / svg / vue / css / scss / markdown / md / text`. Unknown languages fall back to escaped plain text instead of failing.
|
|
118
122
|
|
|
119
|
-
|
|
123
|
+
A fence can carry a title on its first line: \`\`\`js app.js (meta such as `{1,3-5}` is ignored).
|
|
120
124
|
```
|
|
121
125
|
|
|
122
|
-
`group:
|
|
126
|
+
In the `docs` theme, `group: Getting Started` plus `weight: 1` builds the sidebar navigation.
|
|
123
127
|
|
|
124
|
-
##
|
|
128
|
+
## Hook semantics
|
|
125
129
|
|
|
126
|
-
|
|
|
130
|
+
| Hook | Arguments | Return value |
|
|
127
131
|
| --- | --- | --- |
|
|
128
|
-
| `extendContext(ctx)` |
|
|
129
|
-
| `markdown(src, page)` |
|
|
130
|
-
| `transformHtml(html, ctx)` |
|
|
131
|
-
| `afterBuild(site)` |
|
|
132
|
-
| `configureServer(server)` / `finish(site)` |
|
|
132
|
+
| `extendContext(ctx)` | Site context | Replaces the context (merged into `site.*`) |
|
|
133
|
+
| `markdown(src, page)` | Current source | Replaces the source |
|
|
134
|
+
| `transformHtml(html, ctx)` | Current HTML | Replaces the HTML |
|
|
135
|
+
| `afterBuild(site)` | Site instance (`site.documents[].html` is **body** HTML, without layout) | Returned `[{path, contents}]` are written out |
|
|
136
|
+
| `configureServer(server)` / `finish(site)` | Site / server | Ignored |
|
|
133
137
|
|
|
134
|
-
##
|
|
138
|
+
## Plugin example
|
|
135
139
|
|
|
136
140
|
```js
|
|
137
|
-
// plugins/search-index.js
|
|
141
|
+
// plugins/search-index.js — drop into plugins/ and it runs
|
|
138
142
|
module.exports = () => ({
|
|
139
143
|
name: 'search-index',
|
|
140
144
|
afterBuild(site) {
|
|
@@ -147,39 +151,39 @@ module.exports = () => ({
|
|
|
147
151
|
});
|
|
148
152
|
```
|
|
149
153
|
|
|
150
|
-
##
|
|
154
|
+
## Theme structure
|
|
151
155
|
|
|
152
156
|
```
|
|
153
157
|
themes/my-theme/
|
|
154
|
-
├── layouts/ # base.html
|
|
155
|
-
├── partials/ #
|
|
156
|
-
└── assets/ # style.css
|
|
158
|
+
├── layouts/ # base.html (skeleton), index/post/page.html (extend base)
|
|
159
|
+
├── partials/ # reusable fragments (toc, cards, ...)
|
|
160
|
+
└── assets/ # style.css and friends, copied to /assets/ on build
|
|
157
161
|
```
|
|
158
162
|
|
|
159
|
-
|
|
163
|
+
Template context: `site.*` (site config and aggregated data), `page.*` (current page metadata), `content` (body HTML), `headings` (table of contents).
|
|
160
164
|
|
|
161
|
-
##
|
|
165
|
+
## Project layout
|
|
162
166
|
|
|
163
167
|
```
|
|
164
168
|
xfm/
|
|
165
|
-
├── bin/xfm.js # CLI
|
|
169
|
+
├── bin/xfm.js # CLI (build/dev/new/create/clean/info)
|
|
166
170
|
├── lib/engine/ # markdown / template / render / frontmatter / theme / plugins / site / worker / server / config
|
|
167
|
-
├── lib/plugins/ #
|
|
168
|
-
├── lib/cli/scaffold.js # create
|
|
169
|
-
├── themes/default/ #
|
|
170
|
-
├── themes/docs/ #
|
|
171
|
-
├── examples/blog/ #
|
|
172
|
-
├── bench/bench.js #
|
|
173
|
-
└── test/run.js # 72
|
|
171
|
+
├── lib/plugins/ # built-in plugins (sitemap / rss / reading-stats)
|
|
172
|
+
├── lib/cli/scaffold.js # create scaffolding
|
|
173
|
+
├── themes/default/ # blog theme (light/dark)
|
|
174
|
+
├── themes/docs/ # docs theme (sidebar navigation)
|
|
175
|
+
├── examples/blog/ # buildable example site
|
|
176
|
+
├── bench/bench.js # benchmark (per-stage timings)
|
|
177
|
+
└── test/run.js # 72 tests (zero dependencies)
|
|
174
178
|
```
|
|
175
179
|
|
|
176
|
-
##
|
|
180
|
+
## Local development
|
|
177
181
|
|
|
178
182
|
```bash
|
|
179
|
-
npm run test # 72
|
|
180
|
-
npm run bench #
|
|
181
|
-
npm run build:example #
|
|
182
|
-
npm run dev #
|
|
183
|
+
npm run test # 72 tests
|
|
184
|
+
npm run bench # 1,000-page benchmark (per-stage timings)
|
|
185
|
+
npm run build:example # build the example site
|
|
186
|
+
npm run dev # dev-server the example site
|
|
183
187
|
```
|
|
184
188
|
|
|
185
189
|
MIT License
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# ⚡ xfm
|
|
2
|
+
|
|
3
|
+
**A zero-dependency static site generator for Node.js.** 极致性能的静态站点生成器 —— 零运行时依赖、多线程渲染、Nunjucks 风格模板、即插即用的主题与插件。
|
|
4
|
+
|
|
5
|
+
[English](./README.md) | 简体中文
|
|
6
|
+
|
|
7
|
+
## 安装与上手
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g xfmpress # 全局安装(Node ≥ 18),装完命令是 xfm
|
|
11
|
+
xfm create my-blog # 30 秒创建站点
|
|
12
|
+
cd my-blog && xfm dev # 毫秒级热更新
|
|
13
|
+
xfm build # 生产构建
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> **包名 `xfmpress`,命令 `xfm`**(`npm i -g xfmpress` → 用 `xfm` 调用)。定位是 **Static Site Generator(静态站点生成器)**:把 Markdown 压成纯静态 HTML。
|
|
17
|
+
> 与同系列的 [hexo-theme-xfm](https://github.com/xfm0797/hexo-theme-xfm) 共用 `xfm` 品牌。
|
|
18
|
+
> 完整的三种安装方式、权限排错与 FAQ 见 **[INSTALL_CN.md](./INSTALL_CN.md)**。
|
|
19
|
+
|
|
20
|
+
## 性能
|
|
21
|
+
|
|
22
|
+
压测环境:32 核 Node 22,1000 篇中文文档(约 5MB HTML 产出)。运行 `npm run bench` 可复现。
|
|
23
|
+
|
|
24
|
+
| 指标 | 数值 |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| 全量冷构建 | **≈0.38–0.42 s**(≈2400–2700 页/秒) |
|
|
27
|
+
| 增量缓存命中后渲染阶段 | **1 ms**(1001 页) |
|
|
28
|
+
| dev 热更新单页重建 | **2–7 ms** |
|
|
29
|
+
| 产物运行时 | 纯静态 HTML + CSS,**零 JavaScript 依赖** |
|
|
30
|
+
|
|
31
|
+
分阶段耗时(1000 页):
|
|
32
|
+
|
|
33
|
+
| 阶段 | 耗时 | 优化手段 |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| 扫描与解析 | ~128 ms | 目录并行遍历 + 源文件并行读取,raw 只读一次 |
|
|
36
|
+
| 哈希 | ~9 ms | 复用已读入的源文本,无二次 IO |
|
|
37
|
+
| 渲染 | ~91 ms | worker 池复用线程,上下文只传一次 |
|
|
38
|
+
| 写盘 | ~132 ms | 同目录只 mkdir 一次,两阶段并行写 |
|
|
39
|
+
|
|
40
|
+
性能来自四层设计:
|
|
41
|
+
|
|
42
|
+
1. **线性扫描的 Markdown 引擎**:块级逐行推进 + 一次性行内正则,不构造 AST;内置中英文锚点、表格、任务列表(含续行与嵌套)、代码高亮、`::: callout` 容器。
|
|
43
|
+
2. **模板编译成函数**:Nunjucks 风格语法在构建期编译为 JS 函数,渲染只做字符串拼接,未定义变量安全返回 undefined。
|
|
44
|
+
3. **worker 池**:线程与模板编译缓存全程复用,站点上下文只序列化一次;并发度按"每线程最少工作量"标定(实测 2–4 个最优,堆到 8 个反而更慢)。
|
|
45
|
+
4. **增量构建**:`源文件哈希 + 主题指纹` 判定,未变页面直接复用缓存 HTML。
|
|
46
|
+
|
|
47
|
+
> 并发度不是越多越好:worker 的启动与上下文克隆成本约 30–60 ms/个,
|
|
48
|
+
> 在小任务量下会吃掉并行收益。`pickConcurrency()` 依据实测数据标定,而非按 CPU 核数堆线程。
|
|
49
|
+
|
|
50
|
+
## 特性
|
|
51
|
+
|
|
52
|
+
- **主题**:`{% extend %}` / `{% block %}` 模板继承、`{% include %}` 片段、CSS 变量深浅色主题;内置 `default`(博客)与 `docs`(侧栏文档)两套主题。
|
|
53
|
+
- **插件**:五个钩子(`extendContext` / `markdown` / `transformHtml` / `afterBuild` / `configureServer`),放进 `plugins/` 目录自动加载;内置 sitemap、RSS、阅读统计三个插件。
|
|
54
|
+
- **内容**:YAML front-matter、草稿、标签/分类/系列聚合、自动 prev/next、按 weight 排序、`.md` 相对链接自动解析。
|
|
55
|
+
- **开发体验**:`xfm dev` 内存构建 + SSE 热更新(保持滚动位置)、CSS 单独热替换、404 页列出可用路由。
|
|
56
|
+
- **易迁移**:模板语法兼容 Nunjucks/Liquid 习惯,配置文件支持 `.mjs/.js/.json`。
|
|
57
|
+
|
|
58
|
+
## 安装
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm i -g xfmpress # 全局安装,然后直接使用 xfm 命令(Node ≥ 18)
|
|
62
|
+
# 或免安装试用:npx xfmpress --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> 包名是 `xfmpress`,命令是 `xfm`,装一次即可全局使用。
|
|
66
|
+
> 完整的安装、权限排错与三种安装方式见 **[INSTALL_CN.md](./INSTALL_CN.md)**。
|
|
67
|
+
|
|
68
|
+
## 命令
|
|
69
|
+
|
|
70
|
+
| 命令 | 说明 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `xfm create [目录]` | 创建站点(`--template=blog\|docs`,`--title=`) |
|
|
73
|
+
| `xfm dev [目录]` | 开发服务器(`--port=`) |
|
|
74
|
+
| `xfm build [目录]` | 生产构建(`--dev` 启用增量缓存) |
|
|
75
|
+
| `xfm new "标题"` | 新建文章(`--dir=posts --slug=xxx`) |
|
|
76
|
+
| `xfm clean` / `xfm info` | 清理产物 / 查看配置 |
|
|
77
|
+
|
|
78
|
+
## 配置示例(xfm.config.mjs)
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
export default {
|
|
82
|
+
site: {
|
|
83
|
+
title: 'XFM Notes',
|
|
84
|
+
description: '关于静态站点与前端的笔记',
|
|
85
|
+
lang: 'zh-CN',
|
|
86
|
+
base: '/',
|
|
87
|
+
url: 'https://example.com', // 供 sitemap/RSS 使用
|
|
88
|
+
nav: [{ title: '文章', url: '/' }],
|
|
89
|
+
},
|
|
90
|
+
theme: 'default', // 主题名或路径
|
|
91
|
+
plugins: ['sitemap', 'rss'], // 内置插件,也可写路径
|
|
92
|
+
defaults: { layout: 'post.html' },
|
|
93
|
+
workers: 0, // worker 并发数;0=自动按任务量标定
|
|
94
|
+
server: { port: 4173 },
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 写作(content/ 目录)
|
|
99
|
+
|
|
100
|
+
```markdown
|
|
101
|
+
---
|
|
102
|
+
title: 文章标题
|
|
103
|
+
date: 2026-10-28 09:00:00
|
|
104
|
+
tags: [Node.js, 性能]
|
|
105
|
+
description: 摘要(列表页与 RSS 使用)
|
|
106
|
+
draft: false
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
正文支持 GFM 全部常用语法,另加:
|
|
110
|
+
|
|
111
|
+
::: tip 提示容器
|
|
112
|
+
带标题的高亮提示框。
|
|
113
|
+
:::
|
|
114
|
+
|
|
115
|
+
\`\`\`js
|
|
116
|
+
const ok = true; // 内置语法高亮
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
代码围栏语言标记(含别名归一,大小写不敏感):`js / javascript / jsx / ts / tsx / java / c / cpp / csharp / go / golang / rust / rs / swift / kotlin / scala / php / python / py / bash / sh / shell / json / yaml / yml / html / xml / svg / vue / css / scss / markdown / md / text`。未知语言降级为纯转义文本,不会报错。
|
|
120
|
+
|
|
121
|
+
围栏首行可附加标题:\`\`\`js app.js(元信息如 `{1,3-5}` 会被忽略)。
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`group: 使用指南` + `weight: 1` 字段可在 docs 主题生成侧栏导航。
|
|
125
|
+
|
|
126
|
+
## 钩子语义
|
|
127
|
+
|
|
128
|
+
| 钩子 | 参数 | 返回值处理 |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| `extendContext(ctx)` | 站点上下文 | 替换上下文(返回值写入 `site.*`) |
|
|
131
|
+
| `markdown(src, page)` | 当前源码 | 替换源码 |
|
|
132
|
+
| `transformHtml(html, ctx)` | 当前 HTML | 替换 HTML |
|
|
133
|
+
| `afterBuild(site)` | 站点实例(`site.documents[].html` 为**正文** HTML,不含布局) | 返回的 `[{path, contents}]` 被汇总写出 |
|
|
134
|
+
| `configureServer(server)` / `finish(site)` | 站点/服务器 | 忽略 |
|
|
135
|
+
|
|
136
|
+
## 插件示例
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// plugins/search-index.js —— 放进 plugins/ 目录即生效
|
|
140
|
+
module.exports = () => ({
|
|
141
|
+
name: 'search-index',
|
|
142
|
+
afterBuild(site) {
|
|
143
|
+
const index = site.documents.map(d => ({
|
|
144
|
+
title: d.title, url: d.url,
|
|
145
|
+
text: d.html.replace(/<[^>]+>/g, ' ').slice(0, 800),
|
|
146
|
+
}));
|
|
147
|
+
return [{ path: 'search.json', contents: JSON.stringify(index) }];
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 主题结构
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
themes/my-theme/
|
|
156
|
+
├── layouts/ # base.html(骨架)、index/post/page.html(继承 base)
|
|
157
|
+
├── partials/ # 可复用片段(toc、卡片等)
|
|
158
|
+
└── assets/ # style.css 等,构建时复制到站点 /assets/
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
模板上下文:`site.*`(站点配置与聚合数据)、`page.*`(当前页元数据)、`content`(正文 HTML)、`headings`(目录)。
|
|
162
|
+
|
|
163
|
+
## 目录
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
xfm/
|
|
167
|
+
├── bin/xfm.js # CLI(build/dev/new/create/clean/info)
|
|
168
|
+
├── lib/engine/ # markdown / template / render / frontmatter / theme / plugins / site / worker / server / config
|
|
169
|
+
├── lib/plugins/ # 内置插件(sitemap / rss / reading-stats)
|
|
170
|
+
├── lib/cli/scaffold.js # create 脚手架
|
|
171
|
+
├── themes/default/ # 博客主题(深浅色)
|
|
172
|
+
├── themes/docs/ # 文档主题(侧栏导航)
|
|
173
|
+
├── examples/blog/ # 可构建的示例站点
|
|
174
|
+
├── bench/bench.js # 基准测试(分阶段耗时)
|
|
175
|
+
└── test/run.js # 72 项测试(零依赖)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 本地开发
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm run test # 72 项测试
|
|
182
|
+
npm run bench # 1000 页基准(分阶段耗时)
|
|
183
|
+
npm run build:example # 构建示例站点
|
|
184
|
+
npm run dev # 开发示例站点
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
MIT License
|
package/bin/xfm.js
CHANGED
package/lib/cli/scaffold.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xfmpress",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Zero-dependency static site generator for Node.js —
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Zero-dependency static site generator for Node.js — multi-threaded rendering, Nunjucks-style templates, drop-in themes and plugins",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xfm": "bin/xfm.js"
|
|
7
7
|
},
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"bin",
|
|
15
15
|
"lib",
|
|
16
16
|
"themes",
|
|
17
|
-
"INSTALL.md"
|
|
17
|
+
"INSTALL.md",
|
|
18
|
+
"README_CN.md",
|
|
19
|
+
"INSTALL_CN.md",
|
|
20
|
+
"CHANGELOG.md"
|
|
18
21
|
],
|
|
19
22
|
"scripts": {
|
|
20
23
|
"test": "node test/run.js",
|