@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 CHANGED
@@ -44,9 +44,7 @@ npm install -g @x-withu/page-withu
44
44
  # 创建项目,默认配置会写入 config.js,后续可自行编辑
45
45
  page-withu new my-homepage
46
46
 
47
- # 安装依赖
48
47
  cd my-homepage
49
- npm install
50
48
 
51
49
  # 本地预览
52
50
  page-withu serve
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 { Command } from 'commander';
4
- import fs from 'fs-extra';
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.readJson(new URL('../package.json', import.meta.url));
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 templateDir = path.resolve(__dirname, '../template');
15
- const program = new Command();
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 = ['package.json', 'config.js', 'content', 'src', 'vite.config.js'];
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 fs.pathExists(path.join(projectDir, item))) missing.push(item);
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 fs.pathExists(fullPath)) {
41
- console.error(chalk.red(`\nError: Directory ${targetDir} already exists.`));
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
- const spinner = ora('Creating project...').start();
120
+ process.stdout.write('Creating project...\n');
46
121
 
47
122
  try {
48
- await fs.copy(templateDir, fullPath, { filter: shouldCopyTemplateItem });
49
-
50
- const pkgPath = path.join(fullPath, 'package.json');
51
- const projectPkg = await fs.readJson(pkgPath);
52
- projectPkg.name = normalizePackageName(path.basename(targetDir));
53
- await fs.writeJson(pkgPath, projectPkg, { spaces: 2 });
54
-
55
- await fs.ensureDir(path.join(fullPath, 'content/blog'));
56
-
57
- spinner.succeed(chalk.green(`Successfully created PageWithU project in ${targetDir}!`));
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
- spinner.fail(chalk.red('Failed to create project.'));
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 runProjectScript(script, args) {
71
- const missing = await detectProject(process.cwd());
140
+ async function runVite(command, args) {
141
+ const projectRoot = process.cwd();
142
+ const missing = await detectProject(projectRoot);
72
143
  if (missing.length) {
73
- console.error(chalk.red('Current directory does not look like a PageWithU project.'));
74
- console.error(chalk.yellow(`Missing: ${missing.join(', ')}`));
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 npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
79
- const child = spawn(npmCommand, ['run', script, '--', ...args], {
80
- cwd: process.cwd(),
81
- stdio: 'inherit',
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
- child.on('exit', (code, signal) => {
85
- if (signal) {
86
- process.kill(process.pid, signal);
87
- return;
88
- }
89
- process.exit(code ?? 1);
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
- program
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
- npm run build
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
- npm run preview
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
- cache: npm
79
- - run: npm ci
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/ npm run build
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
- npm run build
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 run build
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
- npm run build
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
- npm run build
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/` 后,需要用模板本身或新生成项目执行一次 `npm run build`。
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
- ├── vite.config.js # Markdown 渲染与静态路由生成
28
- │ └── package.json # 生成项目后的应用依赖
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`:复制 `template/` 到目标目录,保留默认配置。
50
- 2. `serve`:在当前主页项目中启动本地预览。
51
- 3. `build`:在当前主页项目中构建 `dist` 产物。
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
- npm install
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
- npm run build
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`:在当前项目目录启动本地预览,对应模板项目的 `npm run dev`。
98
- - `page-withu build`:在当前项目目录构建 `dist` 产物,对应模板项目的 `npm run 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.0.0",
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
  }
@@ -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: {
@@ -54,16 +54,16 @@
54
54
 
55
55
  <script setup>
56
56
  import { ref, computed, onMounted, onUnmounted } from 'vue'
57
- import userConfig from '../config.js'
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 '../content/domains.md'
63
- import { sections as aboutSections } from '../content/index.md'
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('../content/blog/*.md', { eager: true })
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 = [
@@ -1,15 +1,26 @@
1
- import { defineConfig } from 'vite'
2
- import vue from '@vitejs/plugin-vue'
3
- import MarkdownIt from 'markdown-it'
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 userConfig from './config.js'
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(process.cwd(), 'dist')
167
+ const dist = join(projectRoot, 'dist')
156
168
  const index = readFileSync(join(dist, 'index.html'), 'utf8')
157
- const blogDir = join(process.cwd(), 'content', 'blog')
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 totalPages = Math.max(1, Math.ceil(blogPosts.length / userConfig.pagination.pageSize))
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
- export default defineConfig({
182
- base: basePath,
183
- plugins: [vue(), markdown(), staticHtmlRoutes()],
184
- server: {
185
- port: 5500,
186
- },
187
- build: {
188
- outDir: 'dist',
189
- emptyOutDir: true,
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
+ })
@@ -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
- }