Nest系列(八)一路坎坷,我实现了最简便的方式打包部署nestjs+prisma应用

5,350 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

最近学习Nestjs的时候做了一个项目,整合了Typescript+Prisma+MySQL,开发时跑的好好的,没有任何问题,后来想打包部署试一试,中途遇到到了很多问题,也在网上查阅了很多资料,我试了很多办法,包括pkg打包(还没有成功)、docker容器部署等,可行的还是有几个,但是一直不是我要的,不过经过不懈的研究努力,我终于还是达到了自己的目的!

前情提要,需要大家使用过prisma,知道怎么在nestjs中集成prisma,默认的集成非常简单,可以查看官网说明。Prisma | NestJS - A progressive Node.js framework

一、坎坷旅途

1. 普通的build不会打包依赖

这里我参阅了掘金另一位大佬的文章,具体里面的细节文章讲的很清楚,想了解学习的可前往juejin.cn/post/706572…

简单来说,使用nest build命令进行打包,默认是不会将依赖打包进去的,需要将整个项目拷贝到服务器上才行,如果环境不一样,可能需要重新安装依赖,在服务器上安装node_modules真是太占用资源了!根据前面大佬的文章,我开始研究了webpack模式打包,按照大佬的步骤一步一步走,

nest build通过webpack-node-externals插件默认屏蔽了依赖的打包,我们手动配置externals为空,让node_modules依赖也参加打包,配置webpack.config.js文件如下(直接拷贝的大佬配置),

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const webpack = require('webpack');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

console.log('start build');
module.exports = {
  mode: 'production',
  entry: './src/main',
  target: 'node',
  externals: {},
  module: {
    rules: [
      {
        test: /.ts?$/,
        use: {
          loader: 'ts-loader',
          options: { transpileOnly: true },
        },
        exclude: /node_modules/,
      },
    ],
  },
  output: {
    filename: '[name].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',
          '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(),
  ],
};

修改package.json中的scripts代码,增加webpack打包模式,即下面的"build-wp":

"scripts": {
  "build": "nest build",
  "build-wp": "nest build --webpack --webpackPath=./webpack.config.js",
},

而后执行

➜ npm run build-wp

当我欣喜等待的时候,报错了,大致信息是Can't resolve 'class-transformer/storage' in......:

经过查阅github的相关issue,找到了解决方案:github.com/nestjs/mapp…,其实也很简单,就是将'class-transformer/storage'也加入到IgnorePlugin的lazyImports中去,这也是无用的依赖包,如果你在打包过程中遇到同样的问题,可以照此执行,将相关依赖放入到lazyImports中:

new webpack.IgnorePlugin({
      checkResource(resource) {
        const lazyImports = [
          //... ...
          'class-transformer/storage',
        ];
        ......
    }),

这样做完后打包成功,没有任何问题。

可以看到打包后的文件3.4M大小,已经包含了依赖

接下来就是运行了

2. 项目目录下直接使用node运行main.js

在项目目录下执行node dist/main.js

可以发现执行完全正常,我觉得大工告成了!

3. 拷贝到其他目录单独执行

不过我还是想简单测试一下,我把dist目录拷贝到了别的地方,直接进入dist目录,执行同样的操作

node main.js

报错又来了!我在写文章的时候,我的webstorm突然崩溃了起不起来,把我气得!换vscode继续撸吧,我想把当时的错误都重现一遍。

可以看出,缺少了prisma.shcema文件,原来还需要这个文件呀!这简单,我直接把该文件拷贝到dist目录中,继续执行!

终端教会我做人!在经历了将近仅10s的一顿乱码操作后,终于爆出了错误信息!

赶紧上网查了一下,原来prisma在生成client的时候是需要指定平台的,比如报错中就是我没有指定"darwin",不过还好报错信息比较友好,一眼就知道大概需要做什么了!提示信息如下:

To solve this problem, add the platform "darwin" to the "binaryTargets" attribute in the "generator" block in the "schema.prisma" file:
generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native"]
}

Then run "prisma generate" for your changes to take effect.
Read more about deploying Prisma Client: https://pris.ly/d/client-generator
    at DefaultLibraryLoader.getLibQueryEnginePath (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:909505)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async DefaultLibraryLoader.loadLibrary (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:906429)
    at async LibraryEngine.loadEngine (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:915306)
    at async LibraryEngine.instantiateLibrary (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:914257)
    at async LibraryEngine.getConfig (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:918644)
    at async a._getActiveProvider (/Users/lijing/coder/node/nestjs/scoring-system/dist/main.js:2:1008726) {
  clientVersion: '4.6.1',
  errorCode: undefined
}

直接根据提示查阅官网www.prisma.io/docs/concep…

根据官网描述,如果只是在本地环境进行使用,可以不指定,或者简单指定binaryTargets为"native",很不幸,我经过测试指定为"native"还是不行,会出现一样的问题。官网还说,可以一次性指定多个,我把windows,mac,linux全部都涵盖进去了

generator client {
  provider = "prisma-client-js"
  binaryTargets = ["darwin","windows","linux-musl"]
}

这里需要注意,每次修改完prisma.schema文件后需要重新执行一次prisma generate命令来生成新的Client!这个client默认是放在node_modues/.prisma/client中,并在node_modules/@prisma/client进行了导出,所以实际上我们不用更改已经写好的代码(代码中我们都是从@prisma/client这里引入PrismaClient)。

我在想,这回总该没问题了吧!!然后显示再次打了我的脸!

提示信息说我已经加入了多个平台的生成器配置,但是还是出现了问题!!让我去官方github提issue!!!这把我搞懵逼了,我不信这个邪,我在想这么简单的打包操作怎么会有这么多坑啊!我决定再研究尝试一下!

问题还是出在prisma,我再次查看了generator相关的信息,我想试试自定义导出位置,会不会是因为node_modules打包后,没有将我们的client导出出来,我做了如下配置,主要是增加了output,自定义导出路径:

generator client {
  provider = "prisma-client-js"
  output = "../src/generated/client"
  binaryTargets = ["darwin","windows","linux-musl"]
}

而后执行

➜ prisma generate

可以看出新生成的client已经在src目录下生成了

此时我们需要修改自己的代码,修改PrismaClient的引入位置

再次打包,并进入dist目录执行main.js!

➜ node main.js  

我的个乖乖,终端又开始一顿乱码输出!

不过这次的问题不再是Prisma generator的问题了!这是环境变量的配置问题,我感觉到希望就在眼前了!我把url这不使用env,直接写死在里面

datasource db {
  provider = "mysql"
  url      = "mysql://root:xxxxxx(这里是密码)@localhost:3306/nestjs"
}

最后真是让我喜极而泣啊!终于成功了!打包成功!

至此,打包完成!

二、总结

  • nestjs默认的build打包不会打包依赖,只是简单将ts转为js放到dist目录,想要实现依赖打包需要配置webpack打包方式,并一定注意IgnorePlugin中的配置,当遇到Can't resolve ...的问题时,可以尝试添加到lazyImports中。
  • PrismaClient客户端的生成依赖于schema.prisma文件中的generator配置,一定要记得要配置自定义导出及目标平台。
  • schema.prisma中的数据库参数配置不要使用env的方式,直接配置实际值就行。

最后交付的文件如下,不到5M的大小,部署时直接运行node main.js即可

三、其它补充

打包的问题困扰了我好几天,因为一直无果,我考虑了其他方式,这里只做一个简单的介绍

1. docker容器部署

Dockerfile如下(文件就在项目根目录下):

FROM node:lts-alpine
WORKDIR /app
COPY package.json .
RUN npm config set registry https://registry.npm.taobao.org
RUN npm install
COPY . .
COPY dist ./dist
EXPOSE 7777
CMD ["node", "dist/main.js"]

可以看到生成镜像的体积在1.5个G左右,非常庞大!运行是没问题的,相当于把整个项目copy过去了,还得加上底层的linux核心!

2. pkg打包

需要先安装pkg

npm install pkg -g

可以看到,入口文件我使用的是build后的main.js,因为我如果使用源代码下的src/main.ts,打包会报错

估计是没法pkg没法直接打包,使用src/main.ts作为入口时会出现一下信息

使用dist/mian.js这种方式打包有exe产生,打包出来的文件大概200多M,但是放到windows下运行依然会出错。

报错如下:

这个问题在网上搜了一下,没有发现解决方案,希望有大佬可以解疑答惑。

至此,nestjs + prisma + mysql的打包部署已经讲完了,相比来说,我更喜欢使用webpack的方式进行打包,打包出来的文件只有5M不到,只要有node环境,直接使用node运行main.js文件,就完成了部署,非常简单方便!希望本篇文章能帮到有同样疑惑的你,如果有所帮助,记得给个关注和好评,谢谢!