速览
-
项目地址:mai_ke/tools
-
核心依赖:npm i esbuild pkg
esbuild
:将源码及依赖打包成单一 JS 文件
pkg
:将 JS 文件打包为可执行程序(如 .exe
)
-
package.json
脚本配置:
1 2 3 4 5 6
| { "scripts": { "start": "node src/index.js", "build": "esbuild src/index.js --bundle --platform=node --outfile=dist/app.js && pkg dist/app.js --targets node16-win-x64 --output dist/app.exe && echo dist/app.exe" } }
|
技术要点拆解
一、打包策略
- 使用
pkg
将 Node.js 项目打包为 Windows 可执行程序;
- 对于
import
语法支持有限,推荐先用 esbuild
打包成单一 CommonJS 文件;
pkg
无法打包动态 require()
,必须预处理;
- 示例命令:
1
| pkg src/index.js -t node16-win-x64 -o dist/app.exe
|
二、ES6 模块兼容处理
-
Webpack 配置复杂,未采用;
-
使用 esbuild
简单快速,推荐用法:
1
| esbuild src/index.js --bundle --platform=node --outfile=dist/app.js
|
--bundle
:将依赖打入同一文件;
--platform=node
:打包为 Node 环境可执行;
--outfile
:输出文件路径。
三、视频下载与进度条展示
文件下载核心逻辑位于 utils/download_file
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| export async function download_file(url, output_path) { try { if (process.platform === 'win32') { output_path = output_path.replace(/\//g, '\\') } fs.access(output_path) console.log(`文件已存在,跳过下载: ${output_path}`) return } catch { }
const outputDir = path.dirname(output_path) await fs.promises.mkdir(outputDir, { recursive: true })
console.log(`正在下载: "${output_path}",按 Ctrl+C 可中断`)
const { data, headers } = await axios({ url, method: 'GET', responseType: 'stream' })
const totalLength = parseInt(headers['content-length'], 10) console.log(`文件大小: ${(totalLength / 1024 / 1024).toFixed(2)} MB`)
const progressBar = new ProgressBar('下载进度 [:bar] :percent :etas', { width: 40, complete: '=', incomplete: ' ', total: totalLength })
const writer = createWriteStream(output_path) data.on('data', chunk => progressBar.tick(chunk.length)) data.pipe(writer)
return new Promise((resolve, reject) => { writer.on('finish', resolve) writer.on('error', reject) }) }
|
四、Axios 配置 query 参数
通过拦截器自动将 query
字段序列化并拼接到 URL 上:
1 2 3 4 5 6 7 8 9 10
| axios.interceptors.request.use( (config) => { if (config.query) { const params = new URLSearchParams(config.query).toString() config.url += (config.url.includes('?') ? '&' : '?') + params } return config }, (error) => Promise.reject(error) )
|
五、文件名合法化处理
防止文件名中包含非法字符导致保存失败:
1 2 3 4
| export function format_file_name(name) { const invalidChars = /[<>:"/\\|?*\x00-\x1F]/g return name.replace(invalidChars, '_') }
|
六、数据展示与调试
通过子进程方式将数据输出至 VS Code 编辑器中:
1 2 3 4 5 6 7 8 9 10 11 12
| export const open_vscode_with_data = (data) => { data = try_parse_json(data) if (typeof data !== 'string') { data = JSON.stringify(data, null, 2) }
const child = spawn('code', ['-'], { stdio: ['pipe', 'inherit', 'inherit'] }) child.stdin.write(data) child.stdin.end() }
|
后续功能展望
✅ 数据展示优化
- 根据内容长度自动切换输出方式(控制台或编辑器);
- 自动检测系统中是否安装 VS Code / Vim / Sublime,并优先选择可用编辑器;
✅ 下载逻辑增强
- 支持并发下载;
- 添加下载重试、断点续传机制;
- UI 层支持总进度条 + 单任务进度条;
如有建议或使用问题,欢迎留言交流!