前言
前一阵子搞了个nest项目,当我开发完一个功能,打算部署到服务器进行测试时,发现它跑不起来,报了一大堆错缺少了很多依赖包。
我几乎找遍了全网的解决方案,他们的答案齐刷刷只有一个:nest在打包时,不会将依赖打包进去,需要在服务器上clone项目,安装依赖。
这个答案不是我想要的,我的项目没有到达devOps的级别,在服务器上安装node_modules
太占用资源了,不是我想要的。幸运的是,经过一番研究后,我终于解决了这个问题,本文就跟大家分享下我的实现思路与方案,欢迎各位感兴趣的开发者阅读本文。
场景概述
我们继续用文章“使用NestJS搭建服务端应用”所创建的项目,以此为基础来描述这个问题,我们打开package.json文件,执行里面的build
命令。
{
"scripts": {
"build": "nest build",
}
}
一眨眼的功夫,它就打包好了,在你的项目根目录下会多出一个dist
文件夹,如下如所示,这就是它所打包出来的文件。
紧接着,我们把dist目录上传到服务器,用node来执行其目录下的main.js文件,上传文件至服务器后,我发现整个文件夹竟然只有18KB,我当时惊呆了,心想js这么牛的吗!开发出来的服务端应用包体积居然这么小,同样的功能使用Java实现,打包出来的jar包都50MB起步了!
当我在服务器上运行时,我傻眼了,程序报错跑不起来🌚,这玩意儿不经夸啊。
定位问题
我怀着忐忑的心情打开dist的目录下的文件后,发现它只是简单的把ts编译成了js,并没有打包任何依赖包进去,他所有的依赖包都是从node_modules
中引的。我们的服务器上是没有这些依赖包的,所以他就报错了。
在搜索引擎上找了下解决方案,千篇一律的要在服务器上clone项目,然后在服务器上安装庞大的node_modules,没有适合我的。
跟几个人交流后,他们说node项目本来就是这样啊,都是在服务器上安装依赖包的,这让我想起了好多年前看到的一个图,用在此处极为合适。
当然如果你的项目很大是devops规模级别的,那你完全可以忽略我的看法,不用再继续往下看了,完全可以上docker,对node_modules进行统一管理。加快项目的打包速度。
解决方案
我是一个追求完美的人,这么庞大的一个开源库,设计者一定不会这么傻吧,这种低级问题应该早就考虑到了才对,既然网上找不到方案,那我就读一下它的源码吧。
皇天不负有心人,当我在查阅nest-cli源码的打包模块时,在@nestjs/cli/actions/build.action.js
文件中发现了它有个配置变量webpack
。
随后,我在nest的官方文档中,在nest-build章节找到了这个配置项的相关内容,发现他可以在打包命令后面添加--webpack
参数来生成单文件main.js。
于是,我添加了这个参数,运行打包命令后,单文件是生成了,但是依赖文件依然没打包进去。出现这种情况那就只有一种可能了:nest-cli在打包时排除屏蔽了依赖包。
顺藤摸瓜,我在@nestjs/cli/lib/compiler/defaults/webpack-defaults.js
发现了猫腻,如下图所示:
- 它使用
webpack-node-externals
插件屏蔽了依赖的打包。
实现代码
经过上面的分析,我们定位到了问题所在,既然它默认屏蔽了依赖的打包,那我们就自己创建一个webpack.config.js
文件,忽略掉externals
以及一些nest提供的插件,这个问题就完美解决了,实现代码如下所示:
- 将
externals
属性置为空,就忽略掉了默认的webpack-node-externals
插件 - 使用IgnorePlugin忽略掉了nest中的一些无用依赖包
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const webpack = require("webpack");
// fork-ts-checker-webpack-plugin需要单独安装
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
module.exports = {
entry: "./src/main",
target: "node",
// 置为空即可忽略webpack-node-externals插件
externals: {},
// ts文件的处理
module: {
rules: [
{
test: /\.ts?$/,
use: {
loader: "ts-loader",
options: { transpileOnly: true }
},
exclude: /node_modules/
}
]
},
// 打包后的文件名称以及位置
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist")
},
resolve: {
extensions: [".js", ".ts", ".json"]
},
plugins: [
// 需要进行忽略的插件
new webpack.IgnorePlugin({
checkResource(resource) {
const lazyImports = [
"@nestjs/microservices",
"@nestjs/microservices/microservices-module",
"@nestjs/websockets/socket-module",
"cache-manager",
"class-validator",
"class-transformer"
];
if (!lazyImports.includes(resource)) {
return false;
}
try {
require.resolve(resource, {
paths: [process.cwd()]
});
} catch (err) {
return true;
}
return false;
}
}),
new ForkTsCheckerWebpackPlugin()
]
};
⚠️注意:上述webpack配置文件要求
package.json
中webpack的版本号为^5.11.0"
,还需要安装fork-ts-checker-webpack-plugin
依赖包到devDependencies中。
最后,我们修改打包命令为:
{
"scripts": {
"build": "nest build --webpack --webpackPath=./webpack.config.js",
}
}
执行上述命令后,我们发现依赖包已经打入main.js了,文件体积也上升到了3.6mb。
最后,我们用node来运行这个js文件,也没有了报错,顺利的跑起来了。
我们在拿postman来测试下接口能否正常访问,如下所示,也都可以正常访问。
小tips:在服务器上运行node项目时,通常会使用
pm2
来执行。对此感兴趣的开发者,请自行了解。
示例代码
本文中所列举的完整代码请移步:
写在最后
至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于神奇的程序员公众号,未经许可禁止转载💌
评论区