最近在用nest做一些工具,开发阶段挺好用,等到打包时遇到了各种各样的问题,在这里简单总结下。
一、nest build
如果使用nest build进行打包,你将会得到一份和原目录相同的目录结构输出。
为什么?
因为NestJS CLI仅做如下处理:
- 读取配置文件:读取nest-cli.json等配置文件,确定构建选项和输出目录。
- TypeScript编译
- 部分文件处理:复制静态资源,处理路径映射
- 优化操作:代码压缩 以及生成source map
可以看到,NestJS CLI 并没有像webpack一样,将不同文件打包成一个,而是保留了原有目录结构。
二、关于.map文件
上面我们提到,会生成.map文件。有些同学可能会有疑问,.map 不是在浏览器中使用的么?服务端项目为什么还需要.map文件?这就要讲到.map文件的使用场景:
-
错误调试与堆栈跟踪
当服务端运行时出现错误,Node.js会生成错误堆栈信息。如果没有Source map,错误信息会指向编译后的JavaScript代码的行号,而不是原始的TypeScript代码。这使得定位和修复问题变得非常困难。
-
性能分析
在进行性能分析时,source map可以帮助开发者识别哪些原始TypeScript代码段在运行时消耗了最多资源。这对于优化关键路径和提高应用性能至关重要。
- 开发体验优化
在开发环境中,使用source map可以获得更好的开发体验。例如,当使用Node.js的调试器或日志记录时,可以直接看到和引用原始的TypeScript代码,而不是编译后的JavaScript代码。
- 日志记录增强
在日志系统中,可以通过source map将日志中记录的代码位置转换回原始TypeScript文件位置,使日志信息更有意义和可操作。
三、为什么不将依赖包打包进最终结果中?
nest项目打包,建议采用分离依赖的方式,即打包后单独安装依赖。
优势:
-
镜像体积更小(如果采用docker方式的话):只包含编译后的代码
-
缓存友好:Docker构建时可以分层缓存
-
部署灵活:可以在目标环境安装特定版本的依赖
-
标准实践:符合Node.js生态的常见做法
为什么不推荐打包依赖进dist?
-
体积臃肿:node_modules会非常大
-
路径问题:可能出现模块解析错误
-
平台兼容性:native依赖可能不兼容目标平台
webpack打包示例:
js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: './src/main.ts',// 入口问啊进
target: 'node',
mode: process.env.NODE_ENV || 'production',
output: {
path: path.resolve(__dirname, 'dist'), //输出目录
filename: 'bundle.js',// 输出结果
},
resolve: {
extensions: ['.ts', '.js'],// 支持的文件扩展名
plugins: [
new TsconfigPathsPlugin({// 支持tsconfig路径映射
configFile: './tsconfig.json',
}),
],
},
module: {
rules: [
{
test: /\.ts$/, // 处理TypeScript文件
use: {
loader: 'ts-loader',
options: {
transpileOnly: true, // 只编译,不做类型检查
},
},
exclude: /node_modules/, //排除 node_modules
},
],
},
externals: [nodeExternals()], // 排除所有 node_modules 依赖;不打包 [node_modules](),运行时从外部加载,这就是为什么你需要在部署环境安装依赖的原因
plugins: [
new CleanWebpackPlugin(), // 清理dist目录
new ForkTsCheckerWebpackPlugin(),// 在单独进程中进行类型检查
new CopyPlugin({ //将静态文件复制到dist目录
patterns: [
{
from: path.resolve(__dirname, 'public'),
to: path.resolve(__dirname, 'dist/public'),
},
{
from: path.resolve(__dirname, 'package.json'),
to: path.resolve(__dirname, 'dist/package.json'),
},
{
from: path.resolve(__dirname, '.env'),
to: path.resolve(__dirname, 'dist'),
},
],
}),
],
optimization: {
minimize: process.env.NODE_ENV === 'production',
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production',// 生产环境移除 console
},
},
}),
],
},
devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'eval-cheap-module-source-map',
};
四、为什么静态文件没有打包到最终的boundle.js中
- 在Node.js应用中,静态文件和配置文件通常保持独立,便于在运行时直接读取或修改,而不是打包进最终结果中
- webpack默认只会打包通过import或require引入的JavaScript/TypeScript模块。
- .env问啊进包含环境变量,通常需要在运行时读取,保持其独立文件形式更灵活;public目录下的文件通常是静态资源,需要通过路径访问。
- 如果需要使用,可以在webpack打包时,将.env文件等复制到dist目录下。
五、fetch api
如果你在代码中使用了fetch api,开发环境没有问题,但是打包后将报错fetch is not defined。因为Node18以下版本中,默认不包含浏览器的原生fetch API,如需使用,需要引入node-fetch。18以上就可以正常使用了。