0%

Node.js 视频下载工具开发总结

速览

  • 项目地址: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 层支持总进度条 + 单任务进度条;

如有建议或使用问题,欢迎留言交流!