@flun/html-template 4.2.2 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/compile.js CHANGED
@@ -5,47 +5,30 @@
5
5
  * 1. 递归目录复制工具(copyDir)
6
6
  * 2. 路由文件处理及入口文件生成
7
7
  * - 路由检测(checkUserRoutesExist)
8
- * - 入口文件生成(generateServerEntry)
8
+ * - 入口文件生成(generateServerEntry)—— 支持沿用开发配置(端口/HTTPS/证书/host
9
9
  * - 依赖管理(mergeDependencies → 返回完整 package.json)
10
10
  * 3. 编译模板所有文件(compile)
11
11
  * 4. 批量编译主流程(compileAllTemplates)
12
12
  * 5. 导出接口与执行编译
13
13
  *
14
- * 核心功能:
15
- * - 完整的模板编译流水线:模板替换→包含处理→变量替换→文件输出
16
- * - 智能路由检测与入口生成:自动创建可运行的服务端环境
17
- * - 资源打包优化:确保路由文件在静态资源前生成
18
- * - 生产环境就绪:自动生成Express服务器和依赖配置
19
- *
20
- * 特殊机制:
21
- * - 编译模式标识:控制包含文件的收集逻辑
22
- * - 路由功能检测:扫描用户功能文件中的setupRoutes函数
23
- * - 模块缓存清理:确保路由加载时使用最新代码
24
- * - Express版本管理:优先使用模板依赖,默认^5.2.1
14
+ * 核心改进:
15
+ * - 复用 parseServerConfig 读取开发阶段配置(端口、HTTPS、证书、主机名)
16
+ * - 生成的 server.js 严格沿用这些配置,不擅自改变默认行为
17
+ * - 若启用 HTTPS 则自动复制证书文件到输出目录/certs
25
18
  */
26
19
  import {
27
- path, fsPromises, CWD, getAvailableTemplates, validateTemplateFile, renderTemplate, processIncludes, processVariables,
28
- setCompilationMode, getIncludedFiles, loadUserFeatures, findEntryFile, templatesDir, staticDir, customizeDir, defaultPort
20
+ path, fsPromises, CWD, getAvailableTemplates, parseServerConfig, validateTemplateFile, renderTemplate, processIncludes,
21
+ processVariables, setCompilationMode, getIncludedFiles, loadUserFeatures, findEntryFile, templatesDir, staticDir, customizeDir
29
22
  } from './services/templateService.js';
30
23
  import PK from './package.json' with { type: 'json' };
31
24
  import util from 'util';
32
25
  import { exec } from 'child_process';
33
26
  import { fileURLToPath, pathToFileURL } from 'url';
34
27
 
35
- let cachedPages = []; // 缓存模板列表
36
- const execPromise = util.promisify(exec),
28
+ let cachedPages = [];
29
+ const __filename = fileURLToPath(import.meta.url), __dirname = path.dirname(__filename), execPromise = util.promisify(exec),
37
30
 
38
- // ==================== 1.递归目录复制工具 ====================
39
- /**
40
- * 目录结构克隆工具(含错误抑制)
41
- * @param {string} src - 源目录路径
42
- * @param {string} destDir - 目标目录路径
43
- *
44
- * 特性:
45
- * - 自动创建目标目录结构
46
- * - 跳过不存在的源目录(不报错)
47
- * - 保留子目录结构递归复制
48
- */
31
+ // ==================== 1. 递归目录复制工具 ====================
49
32
  copyDir = async (src, destDir) => {
50
33
  try {
51
34
  await fsPromises.mkdir(destDir, { recursive: true });
@@ -60,12 +43,7 @@ const execPromise = util.promisify(exec),
60
43
  }
61
44
  },
62
45
 
63
- // ==================== 2.路由文件处理及入口文件生成 ====================
64
-
65
- /**
66
- * 检测用户是否定义路由功能(兼容默认导出与具名导出)
67
- * @returns {Promise<boolean>} 是否存在有效路由
68
- */
46
+ // ==================== 2. 路由文件处理及入口文件生成 ====================
69
47
  checkUserRoutesExist = async () => {
70
48
  try {
71
49
  const featuresDir = path.join(CWD, customizeDir);
@@ -77,9 +55,7 @@ const execPromise = util.promisify(exec),
77
55
  moduleUrl.search = 'update=' + Date.now();
78
56
  const mod = await import(moduleUrl.href), feature = mod.default?.setupRoutes ? mod.default : mod;
79
57
  if (typeof feature.setupRoutes === 'function') return true;
80
- } catch (e) {
81
- console.warn(`⚠️ 检查路由文件 ${file} 失败:`, e.message);
82
- }
58
+ } catch (e) { /* ignore */ }
83
59
  }
84
60
  return false;
85
61
  } catch {
@@ -87,30 +63,23 @@ const execPromise = util.promisify(exec),
87
63
  }
88
64
  },
89
65
 
90
- /**
91
- * 合并用户项目依赖与模板工具依赖,生成完整的 package.json 内容
92
- * @param {boolean} hasUserRoutes - 是否存在用户自定义路由
93
- * @returns {Promise<string>} 格式化后的 package.json 字符串
94
- */
95
- mergeDependencies = async hasUserRoutes => {
66
+ mergeDependencies = async (hasUserRoutes) => {
96
67
  let basePkg = {}, userDeps = {}, mergedDeps = {};
97
68
  const userPkgPath = path.join(CWD, 'package.json');
98
69
  try {
99
70
  const userPkgRaw = await fsPromises.readFile(userPkgPath, 'utf8'), userPkg = JSON.parse(userPkgRaw);
100
- basePkg.author = userPkg.author || '', basePkg.license = userPkg.license || 'ISC';
71
+ basePkg.author = userPkg.author || '';
72
+ basePkg.license = userPkg.license || 'ISC';
101
73
  userDeps = userPkg.dependencies || {};
102
74
  } catch (err) { }
103
-
104
75
  if (hasUserRoutes) {
105
76
  const templateDeps = PK.dependencies || {}, excludeList = ['chokidar', 'socket.io', '@flun/html-template'];
106
77
  mergedDeps = { ...templateDeps, ...userDeps };
107
78
  for (const pkg of excludeList) delete mergedDeps[pkg];
108
79
  }
109
80
  if (!mergedDeps.express) mergedDeps.express = '^5.2.1';
110
-
111
81
  const finalPkg = {
112
- name: 'dist-server', version: '1.0.0',
113
- ...basePkg,
82
+ name: 'dist-server', version: '1.0.0', ...basePkg,
114
83
  type: 'module', main: 'server.js',
115
84
  scripts: { dev: 'node server.js' },
116
85
  dependencies: mergedDeps,
@@ -119,11 +88,7 @@ const execPromise = util.promisify(exec),
119
88
  return JSON.stringify(finalPkg, null, 2);
120
89
  },
121
90
 
122
- /**
123
- * 在目标目录中执行 npm install
124
- * @param {string} targetDir - 目标目录
125
- */
126
- installDependencies = async targetDir => {
91
+ installDependencies = async (targetDir) => {
127
92
  console.log('📦 正在安装项目依赖,请稍候...');
128
93
  try {
129
94
  const { stdout, stderr } = await execPromise('npm install', { cwd: targetDir });
@@ -137,133 +102,159 @@ const execPromise = util.promisify(exec),
137
102
  },
138
103
 
139
104
  /**
140
- * 生成服务端入口文件内容(ESM 格式)
105
+ * 生成服务端入口文件内容(ESM 格式),沿用开发配置
141
106
  * @param {boolean} hasUserRoutes - 是否存在用户自定义路由
142
- * @param {string} entryFile - 入口文件名(如 index.html)
143
- * @returns {Promise<string>} server.js 文件内容
107
+ * @param {string} entryFile - 入口文件名
108
+ * @param {object} buildConfig - 编译配置(port, host, httpsEnabled, httpsKeyPath, httpsCertPath)
109
+ * @returns {string} server.js 文件内容
144
110
  */
145
- generateServerEntry = async (hasUserRoutes, entryFile) => {
146
- const imports = `import express from 'express';
111
+ generateServerEntry = async (hasUserRoutes, entryFile, buildConfig) => {
112
+ const { port, host, httpsEnabled, httpsKeyPath, httpsCertPath } = buildConfig,
113
+
114
+ // 基础导入
115
+ baseImports = `import express from 'express';
147
116
  import path from 'path';
148
117
  import { fileURLToPath, pathToFileURL } from 'url';
149
- const __filename = fileURLToPath(import.meta.url), __dirname = path.dirname(__filename),
150
- app = express(),port = process.env.PORT || ${defaultPort}`,
118
+ import fs from 'fs';
119
+ import http from 'http';
120
+ import https from 'https';
121
+ import { corsMiddleware, trustProxySetting } from './middleware.js';`,
122
+
123
+ // 变量声明
124
+ declarations = `const __filename = fileURLToPath(import.meta.url),
125
+ __dirname = path.dirname(__filename), app = express(), port = ${port}, host = ${JSON.stringify(host)};
126
+ let server, protocol = 'http';`,
127
+
128
+ // 公共中间件
151
129
  corsAndSecurity = `
152
- app.use((req, res, next) => {
153
- res.setHeader('Access-Control-Allow-Origin', '*');
154
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD');
155
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin, X-CSRF-Token');
156
- res.setHeader('Access-Control-Expose-Headers', 'Content-Length, Content-Range');
157
- res.setHeader('Access-Control-Max-Age', '86400');
158
- res.setHeader('X-Content-Type-Options', 'nosniff');
159
- res.setHeader('X-Frame-Options', 'SAMEORIGIN');
160
- res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
161
- if (req.method === 'OPTIONS') {
162
- res.setHeader('Content-Length', '0');
163
- return res.status(204).end();
164
- }
165
- next();
166
- }), app.set('trust proxy', false);`,
167
- staticMiddleware = `app.use('/static', express.static(path.join(__dirname, '${staticDir}')));
130
+ app.use(corsMiddleware);
131
+ app.set('trust proxy', trustProxySetting);`,
132
+
133
+ // 静态文件服务
134
+ staticMiddleware = `
135
+ app.use('/static', express.static(path.join(__dirname, '${staticDir}')));
168
136
  app.use(express.static(path.join(__dirname, '${templatesDir}')));`,
169
- defaultRootRoute = `app.get('/', (req, res) => res.redirect('/${entryFile}'));`;
170
137
 
171
- // ----- 有用户路由时的动态加载服务器 -----
138
+ // 默认根路由
139
+ defaultRootRoute = `app.get('/', (req, res) => res.redirect('/${entryFile}'));`,
140
+ httpServerCreation = ` server = http.createServer(app), protocol = 'http';`;
141
+
142
+
143
+ // 服务器创建代码(仅赋值,不再重复声明)
144
+ let serverCreationCode;
145
+ if (httpsEnabled && httpsKeyPath && httpsCertPath) {
146
+ const safeKeyPath = JSON.stringify(httpsKeyPath), safeCertPath = JSON.stringify(httpsCertPath);
147
+ serverCreationCode = `
148
+ try {
149
+ const options = {
150
+ key: fs.readFileSync(${safeKeyPath}), cert: fs.readFileSync(${safeCertPath})
151
+ };
152
+ server = https.createServer(options, app);
153
+ protocol = 'https';
154
+ console.log(\`🔒 使用 HTTPS,证书路径: ${safeKeyPath}, ${safeCertPath}\`);
155
+ } catch (err) {
156
+ console.error('HTTPS 证书加载失败,降级为 HTTP:', err.message);
157
+ ${httpServerCreation}
158
+ }`;
159
+ } else {
160
+ const warning = httpsEnabled ? `console.warn('⚠️ HTTPS 已启用但未提供证书路径,降级为 HTTP 启动');` : '';
161
+ serverCreationCode = `
162
+ ${httpServerCreation}
163
+ ${warning}`;
164
+ }
165
+
166
+ // 有用户路由时
172
167
  if (hasUserRoutes) {
173
- return `
174
- import fs from 'fs';
175
- ${imports}, allRoutes = [];
176
-
177
- // 拦截 app 方法,收集路由
178
- const wrapAppMethods = (app) => {
179
- const methodsToWrap = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'], originals = {};
180
- methodsToWrap.forEach(method => {
181
- originals[method] = app[method].bind(app);
182
- app[method] = function(routePath, ...handlers) {
183
- allRoutes.push({ method: method.toUpperCase(), path: routePath });
184
- return originals[method](routePath, ...handlers);
185
- };
186
- });
187
- },
188
- // 打印已注册路由
189
- printRoutes = () => {
190
- if (allRoutes.length) {
191
- console.log(' 🗺️ 注册路由:');
192
- allRoutes.forEach(r => console.log(\` \${r.method.padEnd(6)} \${r.path}\`));
193
- } else console.log(' ℹ️ 未找到任何路由');
194
- },
195
- // 动态加载用户自定义路由
196
- loadUserRoutes = async () => {
197
- const featuresDir = path.join(__dirname, '${customizeDir}');
198
- if (!fs.existsSync(featuresDir)) return console.log(\` ℹ️ \${featuresDir}目录不存在,跳过路由加载\`);
199
-
200
- const routeFiles = fs.readdirSync(featuresDir).filter(file => file.endsWith('.js'));
201
- for (const file of routeFiles) {
202
- try {
203
- // 使用带时间戳的查询参数避免模块缓存,确保每次获取最新内容
204
- const modulePath = path.join(featuresDir, file), moduleUrl = pathToFileURL(modulePath);
205
- moduleUrl.search = 'update=' + Date.now();
206
- const feature = await import(moduleUrl.href);
207
- if (typeof feature.default?.setupRoutes === 'function')
208
- feature.default.setupRoutes(app), console.log(\` ✅ 路由加载文件: \${file}\`);
209
- else if (typeof feature?.setupRoutes === 'function')
210
- feature.setupRoutes(app), console.log(\` ✅ 路由加载文件: \${file}\`);
211
- } catch (e) {
212
- console.error(\` \${file}文件未检测到路由\`, e.message);
213
- }
214
- }
215
- };
216
-
217
- wrapAppMethods(app);
218
- ${corsAndSecurity}
219
-
220
- // 启动流程:先加载路由,再注册静态资源与默认路由
221
- const start = async () => {
222
- await loadUserRoutes();
223
- ${staticMiddleware}
224
- if (!allRoutes.some(r => r.method === 'GET' && r.path === '/')) ${defaultRootRoute}
225
- app.listen(port, () => {
226
- console.log(\`\\n🚀 服务已启动: http://localhost:\${port}\`);
227
- console.log('📡 路由监控:');
228
- printRoutes();
229
- });
230
- };
231
- start();`.trim();
168
+ return `${baseImports}
169
+ ${declarations}
170
+
171
+ let allRoutes = [];
172
+ const wrapAppMethods = (app) => {
173
+ const methodsToWrap = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'];
174
+ const originals = {};
175
+ methodsToWrap.forEach(method => {
176
+ originals[method] = app[method].bind(app);
177
+ app[method] = function(routePath, ...handlers) {
178
+ allRoutes.push({ method: method.toUpperCase(), path: routePath });
179
+ return originals[method](routePath, ...handlers);
180
+ };
181
+ });
182
+ };
183
+ wrapAppMethods(app);
184
+
185
+ const printRoutes = () => {
186
+ if (allRoutes.length) {
187
+ console.log(\` 🗺️ 检测到 \${ allRoutes.length } 条注册路由\`);
188
+ // allRoutes.forEach(r => console.log(\` \${r.method.padEnd(6)} \${r.path}\`));
189
+ } else {
190
+ console.log(' ℹ️ 未找到任何路由');
191
+ }
192
+ };
193
+
194
+ const loadUserRoutes = async () => {
195
+ const featuresDir = path.join(__dirname, '${customizeDir}');
196
+ if (!fs.existsSync(featuresDir)) {
197
+ return console.log(\` ℹ️ \${featuresDir} 目录不存在,跳过路由加载\`);
198
+ }
199
+ const routeFiles = fs.readdirSync(featuresDir).filter(file => file.endsWith('.js'));
200
+ for (const file of routeFiles) {
201
+ try {
202
+ const modulePath = path.join(featuresDir, file);
203
+ const moduleUrl = pathToFileURL(modulePath);
204
+ moduleUrl.search = 'update=' + Date.now();
205
+ const feature = await import(moduleUrl.href);
206
+ if (typeof feature.default?.setupRoutes === 'function') {
207
+ feature.default.setupRoutes(app);
208
+ console.log(\` ✅ 路由加载文件: \${file}\`);
209
+ } else if (typeof feature?.setupRoutes === 'function') {
210
+ feature.setupRoutes(app);
211
+ console.log(\` ✅ 路由加载文件: \${file}\`);
212
+ }
213
+ } catch (e) {
214
+ console.error(\` ❌ \${file} 加载失败:\`, e.message);
215
+ }
216
+ }
217
+ };
218
+
219
+ ${corsAndSecurity}
220
+
221
+ const start = async () => {
222
+ await loadUserRoutes();
223
+ ${staticMiddleware}
224
+ if (!allRoutes.some(r => r.method === 'GET' && r.path === '/')) ${defaultRootRoute}
225
+
226
+ ${serverCreationCode}
227
+ server.listen(port, host, () => {
228
+ console.log(\`\\n🚀 服务已启动: \${protocol}://\${host}:\${port}\`);
229
+ console.log('📡 路由监控:');
230
+ printRoutes();
231
+ });
232
+ };
233
+ start();`;
232
234
  }
233
235
 
234
- // ----- 无用户路由时的纯静态服务器 -----
235
- return `
236
- ${imports};
237
-
238
- ${corsAndSecurity}
239
- ${staticMiddleware}
240
- ${defaultRootRoute}
241
- app.listen(port, () => {
242
- console.log(\`\\n🚀 静态服务器已启动: http://localhost:\${port}\`);
243
- console.log('📁 当前仅提供静态文件服务(未检测到用户路由)');
244
- });`.trim();
236
+ // 无用户路由(纯静态服务器)
237
+ return `${baseImports}
238
+ ${declarations}
239
+ ${corsAndSecurity}
240
+ ${staticMiddleware}
241
+ ${defaultRootRoute}
242
+ ${serverCreationCode}
243
+ server.listen(port, host, () => {
244
+ console.log(\`\\n🚀 静态服务器已启动: \${protocol}://\${host}:\${port}\`);
245
+ console.log('📁 当前仅提供静态文件服务(未检测到用户路由)');
246
+ });`;
245
247
  },
246
248
 
247
- // ==================== 3.编译模板文件 ====================
248
- /**
249
- * @param {string[]} cachedPages - 所有待编译文件(相对于 templatesDir 的路径)
250
- * @param {string} outputDir - 输出根目录(例如 'dist')
251
- *
252
- * 处理阶段:
253
- * 1. 展平编译(模板继承,包含指令解析,变量占位符替换)
254
- * 2. 获取所有包含文件并跳过
255
- * 3. 文件输出到 outputDir/templatesDir/ 下,保持原相对路径结构
256
- */
249
+ // ==================== 3. 编译模板文件 ====================
257
250
  compile = async (cachedPages, outputDir) => {
258
251
  for (const templateFile of cachedPages) {
259
252
  try {
260
253
  let rendered = await renderTemplate(templateFile);
261
254
  rendered = await processIncludes(rendered, templateFile);
262
255
  rendered = processVariables(rendered, { currentUrl: `/${templateFile}`, query: {} });
263
-
264
- const includedFiles = getIncludedFiles(); // 获取所有包含文件
265
- if (includedFiles.has(templateFile)) continue; // 跳过被包含的文件
266
-
256
+ const includedFiles = getIncludedFiles();
257
+ if (includedFiles.has(templateFile)) continue;
267
258
  const outputPath = path.join(CWD, outputDir, templatesDir, templateFile);
268
259
  await fsPromises.mkdir(path.dirname(outputPath), { recursive: true });
269
260
  await fsPromises.writeFile(outputPath, rendered);
@@ -274,76 +265,92 @@ const execPromise = util.promisify(exec),
274
265
  }
275
266
  };
276
267
 
277
- // ==================== 4.批量编译主流程 ====================
268
+ // ==================== 4. 批量编译主流程 ====================
278
269
  /**
279
- * 全量模板编译与打包
280
- * >查看定义:@see {@link compileAllTemplates}
281
- * @param {string|Object} [options] - 配置项,可以是字符串(输出目录)或对象(支持 outputDir 字段)
270
+ * 全量模板编译与打包,沿用开发阶段配置(端口、HTTPS、证书、主机名)
271
+ * @param {string|Object} [options] - 配置项,可以是字符串(输出目录)或对象(支持 outputDir 字段)
282
272
  * @param {string} [options.outputDir='dist'] - 自定义打包输出目录
283
- *
284
- * 核心流程:
285
- * 1. 初始化编译环境(模式标识->缓存清理->验证模板->获取编译文件)
286
- * 2. 预加载用户自定义变量
287
- * 3. 创建打包目录,异步编译所有模板文件
288
- * 4. 路由检测,根据有无路由准备不同的依赖对象,生成入口文件内容、原子写入文件
289
- * 5. 复制资源、自动安装依赖、恢复非编译模式
290
- *
291
- * 特殊处理:
292
- * - 通过编译模式切换包含文件收集行为
293
- * - 自动过滤片段文件避免重复输出
294
- * - 有路由时合并用户依赖,无路由时仅包含 express
295
- * - 自动安装依赖确保运行环境完整
296
273
  */
297
274
  const compileAllTemplates = async (options = {}) => {
298
275
  if (typeof options === 'string') options = { outputDir: options };
299
276
  const outputDir = options.outputDir || 'dist';
300
277
 
301
278
  try {
302
- // 1.设置编译模式并清空包含文件记录
279
+ // 1. 读取开发阶段配置(与 dev-server 行为完全一致)
280
+ let defaults = {};
281
+ const configFile = path.join(CWD, '.dev-config.json');
282
+ try {
283
+ const content = await fsPromises.readFile(configFile, 'utf8');
284
+ defaults = JSON.parse(content);
285
+ console.log('📋 已读取上次开发配置作为默认值');
286
+ } catch (err) {
287
+ if (err.code !== 'ENOENT') console.warn('⚠️ 读取开发配置失败:', err.message);
288
+ }
289
+
290
+ const { port, host, httpsEnabled, httpsKeyPath, httpsCertPath } = parseServerConfig({}, defaults);
291
+ console.log(`🔧 沿用开发配置: 端口=${port}, 主机=${host}, HTTPS=${httpsEnabled}`);
292
+
293
+ // 2. 设置编译模式并清空包含文件记录
303
294
  setCompilationMode(true), cachedPages = await getAvailableTemplates();
304
- for (const file of cachedPages) await validateTemplateFile(file); // 模板验证
295
+ for (const file of cachedPages) await validateTemplateFile(file);
305
296
 
306
- // 2.加载用户自定义功能(编译模式)
297
+ // 3. 加载用户自定义功能(编译模式)
307
298
  await loadUserFeatures(null, true), console.log(`ℹ️ 变量已从${customizeDir}目录加载`);
308
299
 
309
- // 3.创建打包目录
300
+ // 4. 创建打包目录
310
301
  await fsPromises.rm(outputDir, { recursive: true, force: true });
311
302
  await fsPromises.mkdir(outputDir, { recursive: true }), console.log(`📁 已创建输出目录: ${outputDir}`);
303
+
304
+ // 5. 编译模板文件
312
305
  await compile(cachedPages, outputDir), console.log(`\n🎉 编译文件完成!`);
313
306
 
314
- // 4. 检测是否存在用户路由,生成package.json内容,获取入口文件生成 server.js 内容,并原子写入磁盘
307
+ // 6. 检测路由、生成 package.json server.js
315
308
  const hasUserRoutes = await checkUserRoutesExist(), pkgContent = await mergeDependencies(hasUserRoutes),
316
- entryFile = await findEntryFile(cachedPages), serverContent = await generateServerEntry(hasUserRoutes, entryFile);
309
+ entryFile = await findEntryFile(cachedPages), buildConfig = { port, host, httpsEnabled, httpsKeyPath, httpsCertPath };
310
+ // 如果启用了 HTTPS 且证书路径存在;
311
+ if (httpsEnabled && httpsKeyPath && httpsCertPath) {
312
+ console.log(` 证书路径: ${httpsKeyPath}, ${httpsCertPath}`);
313
+ console.warn(' 证书文件将使用原路径,如果部署时证书路径发生变化,请自行调整 server.js 中的证书路径;');
314
+ }
317
315
 
316
+ const serverContent = await generateServerEntry(hasUserRoutes, entryFile, buildConfig);
318
317
  await Promise.all([
319
318
  fsPromises.writeFile(path.join(outputDir, 'server.js'), serverContent),
320
319
  fsPromises.writeFile(path.join(outputDir, 'package.json'), pkgContent)
321
320
  ]);
322
321
 
323
- // 5. 复制静态资源与用户功能目录
322
+ // 7. 复制静态资源与用户功能目录
324
323
  await copyDir(staticDir, path.join(outputDir, staticDir));
325
324
  await copyDir(customizeDir, path.join(outputDir, customizeDir));
325
+ const middlewareSrc = path.join(__dirname, 'services', 'middleware.js'),
326
+ middlewareDest = path.join(outputDir, 'middleware.js');
327
+ await fsPromises.copyFile(middlewareSrc, middlewareDest);
328
+ console.log('✅ 公共中间件已复制到输出目录/middleware.js');
326
329
  try {
327
330
  await fsPromises.copyFile(path.join(CWD, '.env'), path.join(outputDir, '.env'));
328
331
  } catch (err) {
329
332
  if (err.code !== 'ENOENT') console.error(`⚠️ 复制 .env 文件失败: ${err.message}`);
330
333
  }
331
- console.log('✅ 资源打包完成'), await installDependencies(outputDir);
334
+ console.log('✅ 资源打包完成');
332
335
 
333
- if (hasUserRoutes) console.log('\n🚀 检测到自定义路由,已创建完整服务端入口文件');
336
+ // 8. 安装依赖
337
+ await installDependencies(outputDir);
338
+
339
+ if (hasUserRoutes) console.log('\n🚀 检测到自定义路由,已创建完整服务端入口文件');
334
340
  else console.log('\n📄 已生成静态文件服务器(无用户路由)');
335
341
 
336
- console.log(`👉 启动服务器命令: cd ${outputDir} && node server.js`), setCompilationMode(false); // 设置编译模式为假
342
+ console.log(`👉 启动服务器命令: cd ${outputDir} && node server.js`);
343
+ setCompilationMode(false);
337
344
  } catch (error) {
338
345
  console.error('❌ 编译流程出错:', error.message);
339
346
  setCompilationMode(false);
340
347
  }
341
348
  };
342
349
 
343
- // ==================== 5.导出接口与执行编译 ====================
350
+ // ==================== 5. 导出接口与执行编译 ====================
344
351
  export { compileAllTemplates };
345
352
 
346
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
353
+ if (process.argv[1] === __filename) {
347
354
  const customDir = process.argv[2];
348
355
  compileAllTemplates(customDir);
349
356
  }
@@ -1,25 +1,25 @@
1
- /**注入热重载脚本到 HTML
2
- * > 查看定义:@see {@link injectScript}
3
- * @param {string} html - 原始 HTML 内容
4
- * @returns {string} 注入热重载脚本后的 HTML 内容
5
- */
6
- const injectScript = html => {
7
- if (/hot-reload-socket|socket\.io\.js/.test(html)) return html; // 避免重复注入
8
- const socketScript = `
9
- <script src="/socket.io/socket.io.js"></script>
10
- <script>
11
- (function() {
12
- var socket = io();
13
- socket.on('hot-reload', delay => {
14
- console.log('[热重载] 检测到文件更改,' + delay + '毫秒后重新加载页面...');
15
- setTimeout(() => window.location.reload(), delay);
16
- });
17
- })();
18
- </script>
19
- `;
1
+ import fs from 'fs';
2
+ import path from 'path';
20
3
 
21
- if (html.includes('</body>')) return html.replace('</body>', `${socketScript}</body>`);
22
- return html + socketScript;
23
- };
4
+ const isProduction = fs.existsSync(path.join(process.cwd(), 'middleware.js')),
5
+ injectScript = html => {
6
+ if (isProduction) return html; // 生产模式(存在 middleware.js)不注入热重载脚本
7
+ if (/hot-reload-socket|socket\.io\.js/.test(html)) return html; // 已经注入过了
8
+ const socketScript = `
9
+ <script src="/socket.io/socket.io.js"></script>
10
+ <script>
11
+ (function() {
12
+ var socket = io();
13
+ socket.on('hot-reload', delay => {
14
+ console.log('[热重载] 检测到文件更改,' + delay + '毫秒后重新加载页面...');
15
+ setTimeout(() => window.location.reload(), delay);
16
+ });
17
+ })();
18
+ </script>
19
+ `;
20
+
21
+ if (html.includes('</body>')) return html.replace('</body>', `${socketScript}</body>`);
22
+ return html + socketScript;
23
+ };
24
24
 
25
25
  export { injectScript };