diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..291b1fe --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + open-pull-requests-limit: 10 + ignore: + # 忽略通过 BOM 管理的 Vert.x 依赖 + # 这些依赖的版本通过 vertx-dependencies BOM 统一管理 + # 应该通过更新 pom.xml 中的 vertx.version 属性来更新这些依赖 + - dependency-name: "io.vertx:vertx-web" + - dependency-name: "io.vertx:vertx-codegen" + - dependency-name: "io.vertx:vertx-config" + - dependency-name: "io.vertx:vertx-config-yaml" + - dependency-name: "io.vertx:vertx-service-proxy" + - dependency-name: "io.vertx:vertx-web-proxy" + - dependency-name: "io.vertx:vertx-web-client" + diff --git a/core/src/main/java/cn/qaiu/vx/core/verticle/ReverseProxyVerticle.java b/core/src/main/java/cn/qaiu/vx/core/verticle/ReverseProxyVerticle.java index aa95d3b..0395bd3 100644 --- a/core/src/main/java/cn/qaiu/vx/core/verticle/ReverseProxyVerticle.java +++ b/core/src/main/java/cn/qaiu/vx/core/verticle/ReverseProxyVerticle.java @@ -128,7 +128,9 @@ public class ReverseProxyVerticle extends AbstractVerticle { } private HttpServer getHttpsServer(JsonObject proxyConf) { - HttpServerOptions httpServerOptions = new HttpServerOptions(); + HttpServerOptions httpServerOptions = new HttpServerOptions() + .setCompressionSupported(true); + if (proxyConf.containsKey("ssl")) { JsonObject sslConfig = proxyConf.getJsonObject("ssl"); @@ -182,6 +184,7 @@ public class ReverseProxyVerticle extends AbstractVerticle { } else { staticHandler = StaticHandler.create(); } + if (staticConf.containsKey("directory-listing")) { staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing")); } else if (staticConf.containsKey("index")) { diff --git a/web-front/babel.config.js b/web-front/babel.config.js index 5dc91c5..04d6d70 100644 --- a/web-front/babel.config.js +++ b/web-front/babel.config.js @@ -3,6 +3,7 @@ module.exports = { '@vue/cli-plugin-babel/preset' ], plugins: [ - '@vue/babel-plugin-transform-vue-jsx' + '@vue/babel-plugin-transform-vue-jsx', + '@babel/plugin-transform-class-static-block' ] } diff --git a/web-front/package.json b/web-front/package.json index 8a963dd..3c13dea 100644 --- a/web-front/package.json +++ b/web-front/package.json @@ -5,7 +5,8 @@ "scripts": { "serve": "vue-cli-service serve", "dev": "vue-cli-service serve", - "build": "vue-cli-service build", + "build": "vue-cli-service build && node scripts/compress-vs.js", + "build:no-compress": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { @@ -28,6 +29,7 @@ "@babel/core": "^7.26.0", "@babel/eslint-parser": "^7.25.9", "@babel/plugin-transform-class-properties": "^7.26.0", + "@babel/plugin-transform-class-static-block": "^7.26.0", "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", "@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-eslint": "~5.0.8", @@ -35,7 +37,8 @@ "compression-webpack-plugin": "^11.1.0", "eslint": "^9.0.0", "eslint-plugin-vue": "^9.30.0", - "filemanager-webpack-plugin": "8.0.0" + "filemanager-webpack-plugin": "8.0.0", + "monaco-editor-webpack-plugin": "^7.1.1" }, "eslintConfig": { "root": true, diff --git a/web-front/scripts/compress-vs.js b/web-front/scripts/compress-vs.js new file mode 100755 index 0000000..46d57c4 --- /dev/null +++ b/web-front/scripts/compress-vs.js @@ -0,0 +1,124 @@ +#!/usr/bin/env node + +const path = require("path"); +const fs = require("fs"); +const zlib = require("zlib"); +const { promisify } = require("util"); + +const gzip = promisify(zlib.gzip); +const readdir = promisify(fs.readdir); +const stat = promisify(fs.stat); +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); + +// 递归压缩目录下的所有文件 +async function compressDirectory(dirPath, threshold = 1024) { + if (!fs.existsSync(dirPath)) { + console.warn(`目录不存在: ${dirPath}`); + return; + } + + const files = await readdir(dirPath, { withFileTypes: true }); + let compressedCount = 0; + let totalOriginalSize = 0; + let totalCompressedSize = 0; + + for (const file of files) { + const filePath = path.join(dirPath, file.name); + + if (file.isDirectory()) { + await compressDirectory(filePath, threshold); + } else if (file.isFile()) { + const stats = await stat(filePath); + // 只压缩超过阈值且不是已压缩的文件 + if (stats.size > threshold && !filePath.endsWith('.gz') && !filePath.endsWith('.map')) { + try { + const content = await readFile(filePath); + const compressed = await gzip(content); + await writeFile(filePath + '.gz', compressed); + compressedCount++; + totalOriginalSize += stats.size; + totalCompressedSize += compressed.length; + console.log(`✓ ${file.name} (${(stats.size / 1024).toFixed(2)}KB -> ${(compressed.length / 1024).toFixed(2)}KB)`); + } catch (error) { + console.warn(`⚠ 压缩失败: ${filePath}`, error.message); + } + } + } + } + + if (compressedCount > 0) { + console.log(`\n压缩完成: ${compressedCount} 个文件`); + console.log(`原始大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`); + console.log(`压缩后大小: ${(totalCompressedSize / 1024 / 1024).toFixed(2)}MB`); + console.log(`压缩率: ${((1 - totalCompressedSize / totalOriginalSize) * 100).toFixed(1)}%`); + } +} + +// 删除未使用的 worker 文件 +function deleteUnusedWorkers() { + const jsDir = path.join(__dirname, '../nfd-front/js'); + const workers = ['editor.worker.js', 'editor.worker.js.gz', 'json.worker.js', 'json.worker.js.gz', 'ts.worker.js', 'ts.worker.js.gz']; + + let deletedCount = 0; + for (const worker of workers) { + const filePath = path.join(jsDir, worker); + if (fs.existsSync(filePath)) { + try { + fs.unlinkSync(filePath); + deletedCount++; + console.log(`✓ 已删除未使用的文件: ${worker}`); + } catch (error) { + console.warn(`⚠ 删除失败: ${worker}`, error.message); + } + } + } + + if (deletedCount > 0) { + console.log(`\n已删除 ${deletedCount} 个未使用的 worker 文件\n`); + } +} + +// 复制到 webroot +function copyToWebroot() { + const source = path.join(__dirname, '../nfd-front'); + const dest = path.join(__dirname, '../../webroot/nfd-front'); + + // 使用 FileManagerPlugin 的方式,这里用简单的复制 + const { execSync } = require('child_process'); + try { + // 删除目标目录 + if (fs.existsSync(dest)) { + execSync(`rm -rf "${dest}"`, { stdio: 'inherit' }); + } + // 复制整个目录 + execSync(`cp -R "${source}" "${dest}"`, { stdio: 'inherit' }); + console.log('\n✓ 已复制到 webroot'); + } catch (error) { + console.error('\n✗ 复制到 webroot 失败:', error.message); + process.exit(1); + } +} + +// 主函数 +async function main() { + // 先删除未使用的 worker 文件 + deleteUnusedWorkers(); + + // 然后压缩 vs 目录 + const vsPath = path.join(__dirname, '../nfd-front/js/vs'); + console.log('开始压缩 vs 目录下的文件...\n'); + try { + await compressDirectory(vsPath, 1024); // 只压缩超过1KB的文件 + console.log('\n✓ vs 目录压缩完成'); + } catch (error) { + console.error('\n✗ vs 目录压缩失败:', error); + process.exit(1); + } + + // 最后复制到 webroot + copyToWebroot(); +} + +main(); + diff --git a/web-front/src/components/MonacoEditor.vue b/web-front/src/components/MonacoEditor.vue index 6a5dbbb..f226053 100644 --- a/web-front/src/components/MonacoEditor.vue +++ b/web-front/src/components/MonacoEditor.vue @@ -94,13 +94,18 @@ export default { return; } - // 配置Monaco Editor使用国内CDN (npmmirror) - // npmmirror的路径格式: https://registry.npmmirror.com/包名/版本号/files/文件路径 - loader.config({ - paths: { - vs: 'https://registry.npmmirror.com/monaco-editor/0.55.1/files/min/vs' - } - }); + // 配置Monaco Editor使用本地打包的文件,而不是CDN + if (loader.config) { + const vsPath = process.env.NODE_ENV === 'production' + ? './js/vs' // 生产环境使用相对路径 + : '/js/vs'; // 开发环境使用绝对路径 + + loader.config({ + paths: { + vs: vsPath + } + }); + } // 初始化Monaco Editor monaco = await loader.init(); diff --git a/web-front/src/views/Playground.vue b/web-front/src/views/Playground.vue index 2640d76..54ef926 100644 --- a/web-front/src/views/Playground.vue +++ b/web-front/src/views/Playground.vue @@ -1062,12 +1062,18 @@ function parseById(shareLinkInfo, http, logger) { return; } - // 配置Monaco Editor使用国内CDN (npmmirror) - loader.config({ - paths: { - vs: 'https://registry.npmmirror.com/monaco-editor/0.55.1/files/min/vs' - } - }); + // 配置Monaco Editor使用本地打包的文件,而不是CDN + if (loader.config) { + const vsPath = process.env.NODE_ENV === 'production' + ? './js/vs' // 生产环境使用相对路径 + : '/js/vs'; // 开发环境使用绝对路径 + + loader.config({ + paths: { + vs: vsPath + } + }); + } const monaco = await loader.init(); if (monaco) { diff --git a/web-front/vue.config.js b/web-front/vue.config.js index 56a9722..83a2513 100644 --- a/web-front/vue.config.js +++ b/web-front/vue.config.js @@ -4,8 +4,10 @@ const path = require("path"); function resolve(dir) { return path.join(__dirname, dir) } + const CompressionPlugin = require('compression-webpack-plugin'); -const FileManagerPlugin = require('filemanager-webpack-plugin') +const FileManagerPlugin = require('filemanager-webpack-plugin'); +const MonacoEditorPlugin = require('monaco-editor-webpack-plugin'); module.exports = { productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度 @@ -43,7 +45,7 @@ module.exports = { '@': resolve('src') } }, - // Monaco Editor配置 - 使用国内CDN + // Monaco Editor配置 - 使用本地打包 module: { rules: [ { @@ -53,9 +55,18 @@ module.exports = { ] }, plugins: [ + new MonacoEditorPlugin({ + languages: ['javascript', 'typescript', 'json'], + features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'], + publicPath: process.env.NODE_ENV === 'production' ? './' : '/', + // Worker 文件输出路径 + filename: 'js/[name].worker.js' + }), new CompressionPlugin({ test: /\.js$|\.html$|\.css/, // 匹配文件 - threshold: 10240 // 对超过10k文件压缩 + threshold: 10240, // 对超过10k文件压缩 + // 排除 js 目录下的 worker 文件(Monaco Editor 使用 vs/assets 下的) + exclude: /js\/.*\.worker\.js$/ }), new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例 events: { @@ -70,7 +81,11 @@ module.exports = { { source: '../webroot/nfd-front/view/.gitignore', options: { force: true } }, ], copy: [ - { source: './nfd-front', destination: '../webroot/nfd-front' } + // 复制 Monaco Editor 的 vs 目录到 js/vs + { + source: './node_modules/monaco-editor/min/vs', + destination: './nfd-front/js/vs' + } ], archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录 {