@x-withu/page-withu 1.0.0 → 1.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/README.md +0 -2
- package/bin/page-withu.js +212 -75
- package/docs/deploy.md +10 -11
- package/docs/develop.md +12 -15
- package/docs/setup.md +2 -2
- package/package.json +2 -9
- package/template/config.js +1 -1
- package/template/src/app.vue +4 -4
- package/template/vite.config.js +77 -29
- package/template/package.json +0 -25
package/README.md
CHANGED
package/bin/page-withu.js
CHANGED
|
@@ -1,115 +1,252 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import {
|
|
4
|
-
import fs from 'fs
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import { constants as fsConstants } from 'node:fs';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
5
8
|
import path from 'node:path';
|
|
6
9
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import ora from 'ora';
|
|
9
10
|
|
|
10
|
-
const pkg = await fs.
|
|
11
|
+
const pkg = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
11
12
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
15
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
16
|
+
const templateDir = path.join(packageRoot, 'template');
|
|
17
|
+
const viteConfigPath = path.join(templateDir, 'vite.config.js');
|
|
18
|
+
const runtimeDependencies = {
|
|
19
|
+
'@traptitech/markdown-it-katex': '^3.6.0',
|
|
20
|
+
'@vitejs/plugin-vue': '^5.2.3',
|
|
21
|
+
'gray-matter': '^4.0.3',
|
|
22
|
+
'highlight.js': '^11.11.1',
|
|
23
|
+
'katex': '^0.16.45',
|
|
24
|
+
'markdown-it': '^14.1.0',
|
|
25
|
+
'markdown-it-emoji': '^3.0.0',
|
|
26
|
+
'markdown-it-footnote': '^4.0.0',
|
|
27
|
+
'mermaid': '^11.15.0',
|
|
28
|
+
'vite': '^6.3.1',
|
|
29
|
+
'vue': '^3.5.13',
|
|
30
|
+
};
|
|
31
|
+
const runtimeKey = createHash('sha256').update(JSON.stringify(runtimeDependencies)).digest('hex').slice(0, 12);
|
|
32
|
+
const runtimeDir = path.join(homedir(), '.page-withu', 'runtime', `${pkg.version}-${runtimeKey}`);
|
|
33
|
+
|
|
34
|
+
const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
|
|
35
|
+
const green = (text) => `\x1b[32m${text}\x1b[0m`;
|
|
36
|
+
const red = (text) => `\x1b[31m${text}\x1b[0m`;
|
|
37
|
+
const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
|
|
38
|
+
|
|
39
|
+
function npmCommand() {
|
|
40
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function exists(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(filePath, fsConstants.F_OK);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function run(command, args, options = {}) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const child = spawn(command, args, { stdio: 'inherit', ...options });
|
|
55
|
+
child.on('error', reject);
|
|
56
|
+
child.on('exit', (code, signal) => {
|
|
57
|
+
if (signal) {
|
|
58
|
+
process.kill(process.pid, signal);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (code === 0) resolve();
|
|
62
|
+
else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function ensureRuntime() {
|
|
68
|
+
const marker = path.join(runtimeDir, '.ready');
|
|
69
|
+
if (await exists(marker)) return runtimeDir;
|
|
70
|
+
|
|
71
|
+
process.stdout.write('Preparing Page With U runtime...\n');
|
|
72
|
+
try {
|
|
73
|
+
await fs.mkdir(runtimeDir, { recursive: true });
|
|
74
|
+
await fs.writeFile(path.join(runtimeDir, 'package.json'), `${JSON.stringify({
|
|
75
|
+
private: true,
|
|
76
|
+
type: 'module',
|
|
77
|
+
dependencies: runtimeDependencies,
|
|
78
|
+
allowScripts: {
|
|
79
|
+
esbuild: true,
|
|
80
|
+
},
|
|
81
|
+
}, null, 2)}\n`);
|
|
82
|
+
await run(npmCommand(), ['install', '--fund=false', '--audit=false'], {
|
|
83
|
+
cwd: runtimeDir,
|
|
84
|
+
stdio: 'ignore',
|
|
85
|
+
});
|
|
86
|
+
await fs.writeFile(marker, new Date().toISOString());
|
|
87
|
+
process.stdout.write(`${green('Page With U runtime is ready.')}\n`);
|
|
88
|
+
return runtimeDir;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
process.stderr.write(`${red('Failed to prepare Page With U runtime.')}\n`);
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveRuntimeFile(runtimeRoot, specifier) {
|
|
96
|
+
const runtimeRequire = createRequire(path.join(runtimeRoot, 'package.json'));
|
|
97
|
+
return runtimeRequire.resolve(specifier);
|
|
98
|
+
}
|
|
16
99
|
|
|
17
100
|
async function detectProject(projectDir) {
|
|
18
|
-
const required = ['
|
|
101
|
+
const required = ['config.js', 'content/index.md', 'content/domains.md'];
|
|
19
102
|
const missing = [];
|
|
20
103
|
|
|
21
104
|
for (const item of required) {
|
|
22
|
-
if (!await
|
|
105
|
+
if (!await exists(path.join(projectDir, item))) missing.push(item);
|
|
23
106
|
}
|
|
24
107
|
|
|
25
108
|
return missing;
|
|
26
109
|
}
|
|
27
110
|
|
|
28
|
-
function shouldCopyTemplateItem(source) {
|
|
29
|
-
return path.relative(templateDir, source) !== '.gitignore';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function normalizePackageName(name) {
|
|
33
|
-
return name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+|-+$/g, '') || 'page-withu-site';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
111
|
async function createProject(projectName = 'my-homepage') {
|
|
37
112
|
const targetDir = projectName.trim() || 'my-homepage';
|
|
38
113
|
const fullPath = path.resolve(process.cwd(), targetDir);
|
|
39
114
|
|
|
40
|
-
if (await
|
|
41
|
-
|
|
115
|
+
if (await exists(fullPath)) {
|
|
116
|
+
process.stderr.write(red(`\nError: Directory ${targetDir} already exists.\n`));
|
|
42
117
|
process.exit(1);
|
|
43
118
|
}
|
|
44
119
|
|
|
45
|
-
|
|
120
|
+
process.stdout.write('Creating project...\n');
|
|
46
121
|
|
|
47
122
|
try {
|
|
48
|
-
await fs.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
console.log('\nNext steps:');
|
|
59
|
-
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
60
|
-
console.log(chalk.cyan(' npm install'));
|
|
61
|
-
console.log(chalk.cyan(' page-withu serve'));
|
|
62
|
-
console.log(chalk.cyan(' page-withu build\n'));
|
|
123
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
124
|
+
await fs.copyFile(path.join(templateDir, 'config.js'), path.join(fullPath, 'config.js'));
|
|
125
|
+
await fs.cp(path.join(templateDir, 'content'), path.join(fullPath, 'content'), { recursive: true });
|
|
126
|
+
await fs.mkdir(path.join(fullPath, 'content/blog'), { recursive: true });
|
|
127
|
+
|
|
128
|
+
process.stdout.write(`${green(`Successfully created PageWithU project in ${targetDir}!`)}\n`);
|
|
129
|
+
process.stdout.write('\nNext steps:\n');
|
|
130
|
+
process.stdout.write(cyan(` cd ${targetDir}\n`));
|
|
131
|
+
process.stdout.write(cyan(' page-withu serve\n'));
|
|
132
|
+
process.stdout.write(cyan(' page-withu build\n\n'));
|
|
63
133
|
} catch (err) {
|
|
64
|
-
|
|
134
|
+
process.stderr.write(`${red('Failed to create project.')}\n`);
|
|
65
135
|
console.error(err);
|
|
66
136
|
process.exit(1);
|
|
67
137
|
}
|
|
68
138
|
}
|
|
69
139
|
|
|
70
|
-
async function
|
|
71
|
-
const
|
|
140
|
+
async function runVite(command, args) {
|
|
141
|
+
const projectRoot = process.cwd();
|
|
142
|
+
const missing = await detectProject(projectRoot);
|
|
72
143
|
if (missing.length) {
|
|
73
|
-
|
|
74
|
-
|
|
144
|
+
process.stderr.write(`${red('Current directory does not look like a PageWithU project.')}\n`);
|
|
145
|
+
process.stderr.write(`${yellow(`Missing: ${missing.join(', ')}`)}\n`);
|
|
75
146
|
process.exit(1);
|
|
76
147
|
}
|
|
77
148
|
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
149
|
+
const runtimeRoot = await ensureRuntime();
|
|
150
|
+
const vitePackageDir = path.dirname(resolveRuntimeFile(runtimeRoot, 'vite/package.json'));
|
|
151
|
+
const viteBin = path.join(vitePackageDir, 'bin/vite.js');
|
|
152
|
+
const viteArgs = command === 'serve'
|
|
153
|
+
? ['--config', viteConfigPath, ...args]
|
|
154
|
+
: ['build', '--config', viteConfigPath, ...args];
|
|
83
155
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
156
|
+
try {
|
|
157
|
+
await run(process.execPath, [viteBin, ...viteArgs], {
|
|
158
|
+
cwd: projectRoot,
|
|
159
|
+
env: {
|
|
160
|
+
...process.env,
|
|
161
|
+
NODE_PATH: path.join(runtimeRoot, 'node_modules'),
|
|
162
|
+
PAGE_WITHU_PROJECT_ROOT: projectRoot,
|
|
163
|
+
PAGE_WITHU_RUNTIME_ROOT: runtimeRoot,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(err.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function printMainHelp() {
|
|
173
|
+
console.log(`Usage: page-withu [options] [command]
|
|
174
|
+
|
|
175
|
+
CLI to scaffold, preview, and build a lightweight personal homepage
|
|
176
|
+
|
|
177
|
+
Options:
|
|
178
|
+
-V, --version output the version number
|
|
179
|
+
-h, --help display help for command
|
|
180
|
+
|
|
181
|
+
Commands:
|
|
182
|
+
new [project-name] create a new PageWithU project with the default content and config
|
|
183
|
+
serve [args...] start local preview for the current PageWithU project
|
|
184
|
+
build [args...] build dist assets for the current PageWithU project
|
|
185
|
+
help [command] display help for command`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function printCommandHelp(command) {
|
|
189
|
+
if (command === 'new') {
|
|
190
|
+
console.log(`Usage: page-withu new [options] [project-name]
|
|
191
|
+
|
|
192
|
+
create a new PageWithU project with the default content and config
|
|
193
|
+
|
|
194
|
+
Options:
|
|
195
|
+
-h, --help display help for command`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (command === 'serve') {
|
|
199
|
+
console.log(`Usage: page-withu serve [options] [args...]
|
|
200
|
+
|
|
201
|
+
start local preview for the current PageWithU project
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
-h, --help display help for command`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (command === 'build') {
|
|
208
|
+
console.log(`Usage: page-withu build [options] [args...]
|
|
209
|
+
|
|
210
|
+
build dist assets for the current PageWithU project
|
|
211
|
+
|
|
212
|
+
Options:
|
|
213
|
+
-h, --help display help for command`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
printMainHelp();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function main(argv) {
|
|
220
|
+
const [command, ...args] = argv;
|
|
221
|
+
|
|
222
|
+
if (!command || command === '--help' || command === '-h') {
|
|
223
|
+
printMainHelp();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (command === '--version' || command === '-V') {
|
|
227
|
+
console.log(pkg.version);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (command === 'help') {
|
|
231
|
+
printCommandHelp(args[0]);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
235
|
+
printCommandHelp(command);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (command === 'new') {
|
|
239
|
+
await createProject(args[0]);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (command === 'serve' || command === 'build') {
|
|
243
|
+
await runVite(command, args);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
process.stderr.write(`Unknown command: ${command}\n\n`);
|
|
248
|
+
printMainHelp();
|
|
249
|
+
process.exit(1);
|
|
91
250
|
}
|
|
92
251
|
|
|
93
|
-
|
|
94
|
-
.name('page-withu')
|
|
95
|
-
.description('CLI to scaffold, preview, and build a lightweight personal homepage')
|
|
96
|
-
.version(pkg.version);
|
|
97
|
-
|
|
98
|
-
program
|
|
99
|
-
.command('new [project-name]')
|
|
100
|
-
.description('create a new PageWithU project with the default template config')
|
|
101
|
-
.action(createProject);
|
|
102
|
-
|
|
103
|
-
program
|
|
104
|
-
.command('serve [args...]')
|
|
105
|
-
.description('start local preview for the current PageWithU project')
|
|
106
|
-
.allowUnknownOption(true)
|
|
107
|
-
.action((args = []) => runProjectScript('dev', args));
|
|
108
|
-
|
|
109
|
-
program
|
|
110
|
-
.command('build [args...]')
|
|
111
|
-
.description('build dist assets for the current PageWithU project')
|
|
112
|
-
.allowUnknownOption(true)
|
|
113
|
-
.action((args = []) => runProjectScript('build', args));
|
|
114
|
-
|
|
115
|
-
program.parse();
|
|
252
|
+
await main(process.argv.slice(2));
|
package/docs/deploy.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
构建生产版本:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
page-withu build
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
构建产物会输出到:
|
|
@@ -28,10 +28,10 @@ blog/<slug>.html
|
|
|
28
28
|
|
|
29
29
|
这样可以直接部署到 GitHub Pages、Cloudflare Pages、OSS 等对象存储上。
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
预览生产版本可以在构建后用任意静态服务器打开 `dist/`,例如:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
34
|
+
npx serve dist
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
如果觉得没什么问题,就可以把这些文件上传到服务器进行部署了。
|
|
@@ -75,9 +75,8 @@ jobs:
|
|
|
75
75
|
- uses: actions/setup-node@v4
|
|
76
76
|
with:
|
|
77
77
|
node-version: 20
|
|
78
|
-
|
|
79
|
-
- run:
|
|
80
|
-
- run: npm run build
|
|
78
|
+
- run: npm install -g @x-withu/page-withu
|
|
79
|
+
- run: page-withu build
|
|
81
80
|
- uses: actions/upload-pages-artifact@v3
|
|
82
81
|
with:
|
|
83
82
|
path: dist
|
|
@@ -104,7 +103,7 @@ Settings → Pages → Build and deployment → Source → GitHub Actions
|
|
|
104
103
|
如果仓库名不是用于根域名的 `用户名.github.io`,通常需要给构建命令设置子路径。例如仓库名是 `my-homepage`:
|
|
105
104
|
|
|
106
105
|
```yaml
|
|
107
|
-
- run: BASE_PATH=/my-homepage/
|
|
106
|
+
- run: BASE_PATH=/my-homepage/ page-withu build
|
|
108
107
|
```
|
|
109
108
|
|
|
110
109
|
### 方式二:手动上传 dist
|
|
@@ -112,7 +111,7 @@ Settings → Pages → Build and deployment → Source → GitHub Actions
|
|
|
112
111
|
如果不想使用 GitHub Actions,也可以本地构建后手动上传 `dist/`。
|
|
113
112
|
|
|
114
113
|
```bash
|
|
115
|
-
|
|
114
|
+
page-withu build
|
|
116
115
|
```
|
|
117
116
|
|
|
118
117
|
然后把 `dist/` 内容上传到用于 GitHub Pages 的发布分支或目录。
|
|
@@ -132,7 +131,7 @@ Cloudflare Pages 适合托管静态站点,并且可以自动连接 GitHub 仓
|
|
|
132
131
|
|
|
133
132
|
```text
|
|
134
133
|
Framework preset: None 或 Vue
|
|
135
|
-
Build command: npm
|
|
134
|
+
Build command: npm install -g @x-withu/page-withu && page-withu build
|
|
136
135
|
Build output directory: dist
|
|
137
136
|
Root directory: /
|
|
138
137
|
```
|
|
@@ -177,7 +176,7 @@ Root directory: /
|
|
|
177
176
|
本地构建:
|
|
178
177
|
|
|
179
178
|
```bash
|
|
180
|
-
|
|
179
|
+
page-withu build
|
|
181
180
|
```
|
|
182
181
|
|
|
183
182
|
然后把 `dist/` 中的所有文件上传到 Bucket 根目录。
|
|
@@ -201,7 +200,7 @@ ossutil cp -r dist/ oss://your-bucket-name/ --update
|
|
|
201
200
|
每次更新内容后重新执行:
|
|
202
201
|
|
|
203
202
|
```bash
|
|
204
|
-
|
|
203
|
+
page-withu build
|
|
205
204
|
ossutil cp -r dist/ oss://your-bucket-name/ --update
|
|
206
205
|
```
|
|
207
206
|
|
package/docs/develop.md
CHANGED
|
@@ -9,7 +9,7 @@ Page With U 的目标是保持轻量、清晰、容易定制。开发时建议
|
|
|
9
9
|
- 优先修改已有文件,不要为了小功能引入复杂架构。
|
|
10
10
|
- 模板默认配置要适合大多数用户开箱即用。
|
|
11
11
|
- 文档中的用户流程要始终和 CLI 行为保持一致。
|
|
12
|
-
- 修改 `template/` 后,需要用模板本身或新生成项目执行一次 `
|
|
12
|
+
- 修改 `template/` 后,需要用模板本身或新生成项目执行一次 `page-withu build`。
|
|
13
13
|
- 不要引入大型 UI 框架,样式优先维护在 `template/src/styles/main.css`。
|
|
14
14
|
- Markdown 能力优先在 `template/vite.config.js` 中集中处理,避免分散到多个组件。
|
|
15
15
|
|
|
@@ -21,12 +21,11 @@ page-withu/
|
|
|
21
21
|
│ └── page-withu.js # CLI 入口
|
|
22
22
|
├── docs/ # 使用、部署、开发文档
|
|
23
23
|
├── template/
|
|
24
|
-
│ ├── config.js #
|
|
25
|
-
│ ├── content/ #
|
|
26
|
-
│ ├── src/ # Vue
|
|
27
|
-
│
|
|
28
|
-
|
|
29
|
-
├── package.json # page-withu CLI 自身配置
|
|
24
|
+
│ ├── config.js # 新项目默认配置
|
|
25
|
+
│ ├── content/ # 新项目默认内容
|
|
26
|
+
│ ├── src/ # 包内 Vue 运行时源码
|
|
27
|
+
│ └── vite.config.js # 包内 Markdown 渲染与静态路由生成
|
|
28
|
+
├── package.json # page-withu CLI 与运行时依赖配置
|
|
30
29
|
└── README.md
|
|
31
30
|
```
|
|
32
31
|
|
|
@@ -46,9 +45,9 @@ bin/page-withu.js
|
|
|
46
45
|
|
|
47
46
|
当前 CLI 主要负责:
|
|
48
47
|
|
|
49
|
-
1. `new
|
|
50
|
-
2. `serve
|
|
51
|
-
3. `build
|
|
48
|
+
1. `new`:复制默认 `config.js` 和 `content/` 到目标目录。
|
|
49
|
+
2. `serve`:用当前安装的包内运行时,在当前主页项目中启动本地预览。
|
|
50
|
+
3. `build`:用当前安装的包内运行时,在当前主页项目中构建 `dist` 产物。
|
|
52
51
|
|
|
53
52
|
本地调试 CLI:
|
|
54
53
|
|
|
@@ -82,11 +81,10 @@ mkdir -p /tmp/page-withu-test
|
|
|
82
81
|
cd /tmp/page-withu-test
|
|
83
82
|
node /path/to/page-withu/bin/page-withu.js new demo-site
|
|
84
83
|
cd demo-site
|
|
85
|
-
npm install
|
|
86
84
|
node /path/to/page-withu/bin/page-withu.js build
|
|
87
85
|
```
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
如果构建成功,说明内容型项目可以通过包内运行时正常生成。
|
|
90
88
|
|
|
91
89
|
## 开发 Template
|
|
92
90
|
|
|
@@ -100,8 +98,7 @@ template/
|
|
|
100
98
|
|
|
101
99
|
```bash
|
|
102
100
|
cd template
|
|
103
|
-
|
|
104
|
-
npm run dev
|
|
101
|
+
node ../bin/page-withu.js serve
|
|
105
102
|
```
|
|
106
103
|
|
|
107
104
|
常见开发位置:
|
|
@@ -125,7 +122,7 @@ npm run dev
|
|
|
125
122
|
|
|
126
123
|
```bash
|
|
127
124
|
cd template
|
|
128
|
-
|
|
125
|
+
node ../bin/page-withu.js build
|
|
129
126
|
```
|
|
130
127
|
|
|
131
128
|
如果改动影响脚手架生成后的项目,还需要用 CLI 重新生成一个临时项目并构建。
|
package/docs/setup.md
CHANGED
|
@@ -94,8 +94,8 @@ page-withu build
|
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
- `page-withu new my-homepage`:新建主页项目,所有站点配置都保持模板默认值。
|
|
97
|
-
- `page-withu serve
|
|
98
|
-
- `page-withu build
|
|
97
|
+
- `page-withu serve`:使用当前安装的 Page With U 运行时启动本地预览。
|
|
98
|
+
- `page-withu build`:使用当前安装的 Page With U 运行时构建 `dist` 产物。
|
|
99
99
|
|
|
100
100
|
创建完成后,直接编辑 `config.js`、`content/index.md`、`content/domains.md` 和 `content/blog/` 即可定制网站。
|
|
101
101
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-withu/page-withu",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A lightweight, elegant personal homepage generator.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"template/content/",
|
|
17
17
|
"template/src/",
|
|
18
18
|
"template/index.html",
|
|
19
|
-
"template/package.json",
|
|
20
19
|
"template/vite.config.js",
|
|
21
20
|
"docs/",
|
|
22
21
|
"README.md"
|
|
@@ -32,11 +31,5 @@
|
|
|
32
31
|
"vite"
|
|
33
32
|
],
|
|
34
33
|
"author": "Explorer-Dong",
|
|
35
|
-
"license": "MIT"
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"chalk": "^5.3.0",
|
|
38
|
-
"commander": "^12.1.0",
|
|
39
|
-
"fs-extra": "^11.2.0",
|
|
40
|
-
"ora": "^8.0.1"
|
|
41
|
-
}
|
|
34
|
+
"license": "MIT"
|
|
42
35
|
}
|
package/template/config.js
CHANGED
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
author: "Your Name",
|
|
6
6
|
year: new Date().getFullYear(),
|
|
7
7
|
footerLinks: [
|
|
8
|
-
{ label: 'GitHub', url: 'https://github.com' },
|
|
8
|
+
{ label: 'GitHub', url: 'https://github.com/Explorer-Dong/page-withu' },
|
|
9
9
|
{ label: 'Email', url: 'mailto:youremail@example.com' }
|
|
10
10
|
],
|
|
11
11
|
pagination: {
|
package/template/src/app.vue
CHANGED
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
|
|
55
55
|
<script setup>
|
|
56
56
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
57
|
-
import userConfig from '
|
|
57
|
+
import userConfig from '@page-withu/user-config'
|
|
58
58
|
import ThemeToggle from './components/theme_toggle.vue'
|
|
59
59
|
import SiteCard from './components/site_card.vue'
|
|
60
60
|
import BlogList from './components/blog-list.vue'
|
|
61
61
|
import BlogDetail from './components/blog-detail.vue'
|
|
62
|
-
import { frontmatter as domainsFrontmatter } from '
|
|
63
|
-
import { sections as aboutSections } from '
|
|
62
|
+
import { frontmatter as domainsFrontmatter } from '@page-withu/user-content/domains.md'
|
|
63
|
+
import { sections as aboutSections } from '@page-withu/user-content/index.md'
|
|
64
64
|
import defaultFavicon from './assets/bulb.svg'
|
|
65
65
|
|
|
66
|
-
const blogModules = import.meta.glob('
|
|
66
|
+
const blogModules = import.meta.glob('@page-withu/user-content/blog/*.md', { eager: true })
|
|
67
67
|
const validPages = ['about', 'domains', 'blog']
|
|
68
68
|
const basePath = import.meta.env.BASE_URL.replace(/\/$/, '')
|
|
69
69
|
const navItems = [
|
package/template/vite.config.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import mk from '@traptitech/markdown-it-katex'
|
|
5
|
-
import footnote from 'markdown-it-footnote'
|
|
6
|
-
import { full as emoji } from 'markdown-it-emoji'
|
|
7
|
-
import matter from 'gray-matter'
|
|
8
|
-
import hljs from 'highlight.js'
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
9
4
|
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
import
|
|
12
|
-
|
|
5
|
+
import { dirname, join, resolve } from 'node:path'
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const runtimeRoot = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const projectRoot = resolve(process.env.PAGE_WITHU_PROJECT_ROOT || process.cwd())
|
|
10
|
+
const packageRuntimeRoot = process.env.PAGE_WITHU_RUNTIME_ROOT
|
|
11
|
+
const dependencyRoot = packageRuntimeRoot ? resolve(packageRuntimeRoot, 'node_modules') : resolve(runtimeRoot, 'node_modules')
|
|
12
|
+
const runtimeRequire = packageRuntimeRoot ? createRequire(resolve(packageRuntimeRoot, 'package.json')) : createRequire(import.meta.url)
|
|
13
|
+
const loadRuntimeModule = async (specifier) => import(pathToFileURL(runtimeRequire.resolve(specifier)).href)
|
|
14
|
+
const runtimeModuleAlias = Object.fromEntries([
|
|
15
|
+
'vue',
|
|
16
|
+
'katex/dist/katex.min.css',
|
|
17
|
+
'highlight.js/styles/github.css',
|
|
18
|
+
'mermaid',
|
|
19
|
+
].map((specifier) => [specifier, runtimeRequire.resolve(specifier)]))
|
|
20
|
+
const userConfigFile = resolve(projectRoot, 'config.js')
|
|
21
|
+
const userContentDir = resolve(projectRoot, 'content')
|
|
22
|
+
const cacheKey = createHash('sha256').update(projectRoot).digest('hex').slice(0, 12)
|
|
23
|
+
const cacheDir = resolve(tmpdir(), `page-withu-${cacheKey}`)
|
|
13
24
|
const basePath = process.env.BASE_PATH || '/'
|
|
14
25
|
|
|
15
26
|
function slugify(text) {
|
|
@@ -65,7 +76,7 @@ function callouts(md) {
|
|
|
65
76
|
})
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
function createMarkdownRenderer() {
|
|
79
|
+
function createMarkdownRenderer({ MarkdownIt, mk, footnote, emoji, hljs }) {
|
|
69
80
|
const toc = []
|
|
70
81
|
let md
|
|
71
82
|
md = new MarkdownIt({
|
|
@@ -119,8 +130,9 @@ function renderMarkdown(md, content) {
|
|
|
119
130
|
return { html, toc: env.toc }
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
function markdown() {
|
|
123
|
-
const { md } = createMarkdownRenderer()
|
|
133
|
+
function markdown(deps) {
|
|
134
|
+
const { md } = createMarkdownRenderer(deps)
|
|
135
|
+
const { matter } = deps
|
|
124
136
|
return {
|
|
125
137
|
name: 'vite-plugin-markdown',
|
|
126
138
|
transform(code, id) {
|
|
@@ -147,14 +159,14 @@ function markdown() {
|
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
function staticHtmlRoutes() {
|
|
162
|
+
function staticHtmlRoutes(userConfig) {
|
|
151
163
|
return {
|
|
152
164
|
name: 'static-html-routes',
|
|
153
165
|
apply: 'build',
|
|
154
166
|
closeBundle() {
|
|
155
|
-
const dist = join(
|
|
167
|
+
const dist = join(projectRoot, 'dist')
|
|
156
168
|
const index = readFileSync(join(dist, 'index.html'), 'utf8')
|
|
157
|
-
const blogDir = join(
|
|
169
|
+
const blogDir = join(userContentDir, 'blog')
|
|
158
170
|
let blogPosts = []
|
|
159
171
|
try {
|
|
160
172
|
blogPosts = readdirSync(blogDir)
|
|
@@ -164,7 +176,8 @@ function staticHtmlRoutes() {
|
|
|
164
176
|
if (e.code !== 'ENOENT') throw e
|
|
165
177
|
}
|
|
166
178
|
|
|
167
|
-
const
|
|
179
|
+
const pageSize = userConfig.pagination?.pageSize || 5
|
|
180
|
+
const totalPages = Math.max(1, Math.ceil(blogPosts.length / pageSize))
|
|
168
181
|
const routes = ['domains.html', 'blog.html']
|
|
169
182
|
for (let page = 2; page <= totalPages; page += 1) routes.push(`blog/page/${page}.html`)
|
|
170
183
|
for (const slug of blogPosts) routes.push(`blog/${slug}.html`)
|
|
@@ -178,14 +191,49 @@ function staticHtmlRoutes() {
|
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
const { defineConfig } = await loadRuntimeModule('vite')
|
|
195
|
+
|
|
196
|
+
export default defineConfig(async () => {
|
|
197
|
+
const [vueModule, MarkdownItModule, mkModule, footnoteModule, emojiModule, matterModule, hljsModule] = await Promise.all([
|
|
198
|
+
loadRuntimeModule('@vitejs/plugin-vue'),
|
|
199
|
+
loadRuntimeModule('markdown-it'),
|
|
200
|
+
loadRuntimeModule('@traptitech/markdown-it-katex'),
|
|
201
|
+
loadRuntimeModule('markdown-it-footnote'),
|
|
202
|
+
loadRuntimeModule('markdown-it-emoji'),
|
|
203
|
+
loadRuntimeModule('gray-matter'),
|
|
204
|
+
loadRuntimeModule('highlight.js'),
|
|
205
|
+
])
|
|
206
|
+
const deps = {
|
|
207
|
+
MarkdownIt: MarkdownItModule.default,
|
|
208
|
+
mk: mkModule.default,
|
|
209
|
+
footnote: footnoteModule.default,
|
|
210
|
+
emoji: emojiModule.full,
|
|
211
|
+
matter: matterModule.default,
|
|
212
|
+
hljs: hljsModule.default,
|
|
213
|
+
}
|
|
214
|
+
const userConfig = (await import(`${pathToFileURL(userConfigFile).href}?t=${Date.now()}`)).default
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
root: runtimeRoot,
|
|
218
|
+
base: basePath,
|
|
219
|
+
cacheDir,
|
|
220
|
+
resolve: {
|
|
221
|
+
alias: {
|
|
222
|
+
...runtimeModuleAlias,
|
|
223
|
+
'@page-withu/user-config': userConfigFile,
|
|
224
|
+
'@page-withu/user-content': userContentDir,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
plugins: [vueModule.default(), markdown(deps), staticHtmlRoutes(userConfig)],
|
|
228
|
+
server: {
|
|
229
|
+
port: 5500,
|
|
230
|
+
fs: {
|
|
231
|
+
allow: [runtimeRoot, projectRoot, dependencyRoot],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
build: {
|
|
235
|
+
outDir: resolve(projectRoot, 'dist'),
|
|
236
|
+
emptyOutDir: true,
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
})
|
package/template/package.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "your-homepage-name",
|
|
3
|
-
"private": true,
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "vite",
|
|
7
|
-
"build": "vite build",
|
|
8
|
-
"preview": "vite preview"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@traptitech/markdown-it-katex": "^3.6.0",
|
|
12
|
-
"highlight.js": "^11.11.1",
|
|
13
|
-
"katex": "^0.16.45",
|
|
14
|
-
"markdown-it-emoji": "^3.0.0",
|
|
15
|
-
"markdown-it-footnote": "^4.0.0",
|
|
16
|
-
"mermaid": "^11.15.0",
|
|
17
|
-
"vue": "^3.5.13"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@vitejs/plugin-vue": "^5.2.3",
|
|
21
|
-
"gray-matter": "^4.0.3",
|
|
22
|
-
"markdown-it": "^14.1.0",
|
|
23
|
-
"vite": "^6.3.1"
|
|
24
|
-
}
|
|
25
|
-
}
|