文章摘要
最近由于项目需要,我选择用 Nestjs 做一个后端项目。 以下是主要技术栈 NestJS + Graphql : Documentation | NestJS - A progressive Node.js framework Typescript : TypeScript: JavaScript With Syntax For Types. (typescriptlang.org) Prism…
最近由于项目需要,我选择用 Nestjs 做一个后端项目。 以下是主要技术栈 NestJS + Graphql : Documentation | NestJS - A progressive Node.js framework Typescript : TypeScript: JavaScript With Syntax For Types. (typescriptlang.org) Prisma : Prisma | Next-generation ORM for Node.js TypeScript 我的开发环境: Windows 11 23H2 欢迎使用 Windows (microsoft.com) Node v16.18.1: Index of /dist/v16.18.1/ (nodejs.org) pnpm v8.10.3 : Fast, disk space efficient package manager | pnpm MYSQL 8.0 : MySQL orm 框架没用 typeorm ,是想尝尝新的工具库。 网络上有很多打包 nest 的博文,但是和我用类似技术栈( prisma )还用 pkg 打包的貌似很少。因为这个框架的特殊性,给打包的过程中也带来了很多依赖加载的问题,在查询文档的过程中我还看到有使用 electron 的同志也遇到了类似的依赖加载问题。 这篇问题记录在记录遇到的问题的同时分享一下三种打包方式,希望给使用类似技术栈的读者能带来帮助。 webpack webpack打包时, 我参考了这篇文章的相关内容: Nest系列(八)一路坎坷,我实现了最简便的方式打包部署nestjs+prisma应用 - 掘金 他的基础配置如下: 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(), ], }; 不出意外的话要出意外了, 以下是部分报错内容 ERROR in ./node_modules/@nestjs/apollo/dist/drivers/apollo-base.driver.js 100:155-190 Module not found: Error: Can't resolve '@as-integrations/fastify' in 'E:\VSCode Proj\NavigationSite\Backend\node_modules\@nestjs\apollo\dist\drivers @ ./node_modules/@nestjs/apollo/dist/drivers/apollo-gateway.driver.js 10:29-60 @ ./node_modules/@nestjs/apollo/dist/drivers/index.js 5:21-55 @ ./node_modules/@nestjs/apollo/dist/index.js 5:21-41 @ ./src/app.module.ts 20:17-42 @ ./src/main.ts 4:21-44 可以看出,都是类似于某某依赖无法正常导入,我将报错出现过的依赖全部添加进 webpack.IgnorePlugin 插件里的 lazyImports 后再生成,就能够正常生成了。 大概有这些依赖: const lazyImports = '@nestjs/websockets/socket-module', '@nestjs/microservices', '@nestjs/microservices/microservices-module', 'ts-morph', '@as-integrations/fastify', '@apollo/gateway', '@apollo/subgraph', '@apollo/subgraph/package.json', '@apollo/subgraph/dist/directives', 'cache-manager', 'class-validator', 'class-transformer', 'class-transformer/storage', ]; 但是运行时仍然报错: Error: Schema must contain uniquely named types but contains multiple types named "s". at new Bt (E:\VSCode Proj\Backend\dist\main.js:16:2190263) at r.create (E:\VSCode Proj\Backend\dist\main.js:16:36799) at p.generateSchema (E:\VSCode Proj\Backend\dist\main.js:10:827) at p.build (E:\VSCode Proj\Backend\dist\main.js:10:601) at h.generateSchema (E:\VSCode Proj\Backend\dist\main.js:10:5265) at u.generateSchema (E:\VSCode Proj\Backend\dist\main.js:2:312804) at r.onModuleInit (E:\VSCode Proj\Backend\dist\main.js:16:2227) at async t.callModuleInitHook (E:\VSCode Proj\Backend\dist\main.js:2:178229) at async _.callInitHook (E:\VSCode Proj\Backend\dist\main.js:2:244363) at async _.init (E:\VSCode Proj\Backend\dist\main.js:2:248580) 根据 schema 大概推测是是 graphql 报的错,由于 webpack 会压缩 js ,所以会改变 graphql 的 resolver 和相关类实现,而在我的项目中,使用了 nest 的 GraphQLModule 来动态生成 graphql 的相关 schema ,而动态生成的过程中,会使用类的原名,这就导致了错误,因为压缩过程中会有许多同名类,也就导致多个相同名字的 schema 生成,上述报错就是说 schema 需要类型名唯一 我是使用以下方式加载的 GraphQLModule GraphQLModule.forRoot ApolloDriverConfig ({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), }), 分析出了错误,解决方法也就清晰了,修改 webpack 的相关配置,让 TerserPlugin 在压缩 js 时,保持类名和函数名就行了 optimization: { minimize: true, minimizer: new TerserPlugin({ terserOptions: { keep_classnames: true, keep_fnames: true, }, }), ], }, 打包完成后,复制到其他文件夹运行试试,不出意外的话又会有新的错误 Nest] 31708 - 2023/11/15 17:11:31 ERROR ExceptionHandler] Prisma Client could not find its `schema.prisma`. This is likely caused by a bundling step, which leads to `schema.prisma` not being copied near the resulting bundle. We would appreciate if you could take the time to share some information with us. Please help us by answering a few questions: https://pris.ly/bundler-investigation-error 大概就是缺少文件,把运行时会用到的文件也拷贝到这个文件夹,主要有这两个文件 .env : 环境配置 schema.prisma : prisma库的配置文件 复制到目录后还是报错: PrismaClientInitializationError: Prisma Client could not locate the Query Engine for runtime "windows". Generators (Reference) | Prisma Docs 查阅文档可知,这是由于 prisma 会根据目标平台(开发环境)生成一个二进制文件,这个二进制文件是 prisma 与数据库交互的必备组件,如果没有修改 schema.prisma 配置文件里 generator client 配置的 output 属性的话,这个二进制文件的默认路径在 node_modules/.prisma/client/ 文件夹下,如果修改了,则在 output 目录下,后缀为 .node , windows 环境下这个文件是 query_engine-windows.dll.node 把这个文件也复制到目录中再运行试试 node main.js ………… Nest] 57292 - 2023/11/15 17:51:05 LOG InstanceLoader] GraphQLSchemaBuilderModule dependencies initialized +1ms Nest] 57292 - 2023/11/15 17:51:05 LOG InstanceLoader] GraphQLModule dependencies initialized +0ms Nest] 57292 - 2023/11/15 17:51:05 LOG RoutesResolver] AppController {/}: +6ms Nest] 57292 - 2023/11/15 17:51:05 LOG RouterExplorer] Mapped {/, GET} route +4ms Nest] 57292 - 2023/11/15 17:51:05 LOG GraphQLModule] Mapped {/graphql, POST} route +99ms Nest] 57292 - 2023/11/15 17:51:05 LOG NestApplication] Nest application successfully started +11ms Ok,启动成功,打包过后大概有这些文件(windows环境) │ .env │ main.js │ query_engine-windows.dll.node │ schema.prisma 只要移植这些文件到一个拥有 node 环境的 windows 电脑上就能运行。( node 版本最好一致) 如果要移植到 linux ,根据上述 prisma 的文档,修改 schema.prisma 配置文件里 generator client 配置的 binaryTargets 属性为指定架构的 linux 就行,具体填写的值,请参考文档。之后使用 webpack 打包完,像上面一样把二进制文件也复制打包到目标计算机,就能够正常运行了,得益于 nodejs 的跨平台特性啊。 webpack配置微调 上面的 webpack 打包时没有任何输出,且得手动复制 prisma 的相关依赖文件,下面是我添加了进度条插件和复制插件后的 webpack 配置,需要自取 webpack.config.js /* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path'); const webpack = require('webpack'); const WebpackBar = require('webpackbar'); let progressPlugin = new WebpackBar({ color: '#85d', // 默认green,进度条颜色支持HEX basic: true, // 默认true,启用一个简单的日志报告器 profile: false, // 默认false,启用探查器。 }); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); var CopyWebpackPlugin = require('copy-webpack-plugin'); const resolve = (dir) = path.resolve(__dirname, dir); console.log('start build'); module.exports = { mode: 'production', entry: './src/main', devtool: 'source-map', stats: { errorDetails: true, }, 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: { alias: { '@': resolve('src'), }, extensions: ['.js', '.ts', '.json'], }, plugins: [ progressPlugin, new CopyWebpackPlugin({ patterns: [ '.env', 'prisma/schema.prisma', 'node_modules/.prisma/client/query_engine-windows.dll.node', ], }), new webpack.IgnorePlugin({ checkResource(resource) { const lazyImports = [ '@nestjs/websockets/socket-module', '@nestjs/microservices', '@nestjs/microservices/microservices-module', 'ts-morph', '@as-integrations/fastify', '@apollo/gateway', '@apollo/subgraph', '@apollo/subgraph/package.json', '@apollo/subgraph/dist/directives', 'cache-manager', 'class-validator', 'class-transformer', 'class-transformer/storage', ]; if (!lazyImports.includes(resource)) { return false; } try { require.resolve(resource, { paths: [process.cwd()], }); } catch (err) { return true; } return false; }, }), new ForkTsCheckerWebpackPlugin(), ], optimization: { minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { keep_classnames: true, keep_fnames: true, }, }), ], }, }; docker docker打包没什么阻力, 主要是docker镜像的大小有点超出我的预料, 足足500M, 压缩完也还有100M+., 所以放弃了使用docker部署的念头。 Dockerfile如下 Dockerfile FROM node:16.18.1-alpine # as build-stage WORKDIR /app RUN corepack enable RUN corepack prepare pnpm@8.10.2 --activate RUN npm config set registry https://registry.npmmirror.com COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm prisma generate RUN pnpm build EXPOSE 3000 CMD ["node", "dist/main.js"] 运行以下指令生成镜像,然后运行镜像 docker build -t backend . docker run -p 3000:3000 镜像ID 由于我的服务器环境中, mysql 运行在宿主机上, 所以要将 mysql 的连接地址从 localhost 改为 host.docker.internal , 使得 docker 容器能够正常访问到宿主机的 mysql 数据库。 docker 打包成功,正常运行,但是镜像文件过于庞大,还是选择使用 webpack 打包后直接在 node 环境运行 pkg打包 使用nest编译, pkg打包 (失败) pkg打包流程如下 pnpm build # 实际调用的是nest build pkg .\dist\main.js -t node16-win-x64 .\main.exe 运行时, 会报以下错误 PrismaClientInitializationError: Unable to require(`C:\snapshot\Backend\node_modules\.prisma\client\query_engine-windows.dll.node`). The Prisma engines do not seem to be compatible with your system. Please refer to the documentation about Prisma's system requirements: https://pris.ly/d/system-requirements Details: UNEXPECTED-15 at fn.loadLibrary (C:\snapshot\Backend\node_modules\@prisma\client\runtime\library.js:112:10067) at processTicksAndRejections (node:internal/process/task_queues:96:5) at process.runNextTicks as _tickCallback] (node:internal/process/task_queues:65:3) at Function.runMain (pkg/prelude/bootstrap.js:1980:13) at node:internal/main/run_main_module:17:47 at async mr.loadEngine (C:\snapshot\Backend\node_modules\@prisma\client\runtime\library.js:114:447) at async mr.instantiateLibrary (C:\snapshot\Backend\node_modules\@prisma\client\runtime\library.js:113:1762) { clientVersion: '5.5.2', errorCode: undefined } prisma官方有类似解决方案: Solve ENOENT package error with vercel/pkg | Prisma Docs 根据解决方案,在package.json文件中添加了如下配置 "pkg": { "assets": "node_modules/.prisma/client/*.node", "prisma/schema.prisma", ".env" ], "targets": "node16-win-x64" ], "outputPath": "bin" } 再次输入 pkg . 指令生成后运行仍然报相同错误, 应该是该二进制文件没有被正确打包, 即使手动复制 node_modules 的相关文件夹也一样报错, 具体原因我暂时没有找到相关解决方案,此打包方式尝试失败。 类似问题相关github issue讨论页面: Cannot access engine inside vercel/pkg · Issue #8449 · prisma/prisma (github.com) 使用webpack先打包, 在用pkg生成 分析了一下,觉得 nestjs 编译时只是转译 typescript 为 javascript ,并没有包含依赖,所以使用 nestjs 编译再用 pkg 打包会失败, pkg 也没有正确的包含二进制文件,使得程序运行时无法正确加载依赖而崩溃 所以我又尝试了先使用上面的 webpack 打包,然后再使用 pkg 打包的方式,试验是否能够解决这个问题。 结果是我直接在开发目录运行能够运行成功,拷贝到其他目录时就会运行失败,说明在开发目录能够加载到相关依赖,而迁移后不行。 然后我打开打包后的 main.js 查找相关加载代码,找到了以下这段(已格式化): it = T(57147); if ( ((nt.dirname = __dirname), !it.existsSync(rt.join(__dirname, "schema.prisma"))) ) { const c = "node_modules/.prisma/client", ".prisma/client"], p = c.find((c) = it.existsSync(rt.join(process.cwd(), c, "schema.prisma")) ) ?? c 0]; (nt.dirname = rt.join(process.cwd(), p)), (nt.isBundled = !0); } 因为有 process.cwd() 动态获取程序运行目录,所以我初步判断这就是动态加载 prisma 二进制文件引擎和 schema 文件的代码。 尝试在程序目录下创建 .prisma/client 文件,并拷入二进制文件和 schema.prisma 文件 再运行,成功了! node main.js ………… Nest] 26416 - 2023-11-15 18:50:27 LOG InstanceLoader] GraphQLSchemaBuilderModule dependencies initialized +0ms Nest] 26416 - 2023-11-15 18:50:27 LOG InstanceLoader] GraphQLModule dependencies initialized +0ms Nest] 26416 - 2023-11-15 18:50:27 LOG RoutesResolver] AppController {/}: +2ms Nest] 26416 - 2023-11-15 18:50:27 LOG RouterExplorer] Mapped {/, GET} route +1ms Nest] 26416 - 2023-11-15 18:50:27 LOG GraphQLModule] Mapped {/graphql, POST} route +56ms Nest] 26416 - 2023-11-15 18:50:27 LOG NestApplication] Nest application successfully started +36ms 目录结构如下: │ main.exe │ ├─ .prisma │ └─client │ query_engine-windows.dll.node │ schema.prisma 不过这样的结构总感觉不太好看。 深入一点,生成单文件 仔细观察上一节我找到的js代码 if ( ((nt.dirname = __dirname), !it.existsSync(rt.join(__dirname, "schema.prisma"))) ) 这个 if 判断是先判断了 path.join(__dirname, "schema.prisma") 文件不存在后,才去程序目录下 "node_modules/.prisma/client", ".prisma/client" 的两个文件夹下找 schema 文件。 一般情况下 __dirname 与 process.cwd() 代表的都是运行的 js 所在的文件目录地址,但在 pkg 打包后,情况有所改变,以下是 pkg 打包前后的文件地址对应关系 value with node packaged comments __filename /project/app.js /snapshot/project/app.js __dirname /project /snapshot/project process.cwd() /project /deploy suppose the app is called ... process.execPath /usr/bin/nodejs /deploy/app-x64 app-x64 and run in /deploy process.argv[0] /usr/bin/nodejs /deploy/app-x64 process.argv[1] /project/app.js /snapshot/project/app.js process.pkg.entrypoint undefined /snapshot/project/app.js process.pkg.defaultEntrypoint undefined /snapshot/project/app.js require.main.filename /project/app.js /snapshot/project/app.js 只需要关注第二行和第三行, 从中可知 process.cwd() 运行时还是保持程序运行目录,但是 __dirname 会变成打包时,打包的 js 文件所在的目录(相对路径)。 而上面的 if 使用的是 path.join(__dirname, "schema.prisma") 来合成 schema 所在地址,知道这些,解决方案也就大概知道啦,那就是把依赖和 webpack 打包生成的dist放在同一目录下。 刚才 配置 webpack 的时候我就用 CopyPlugin 自动复制了 prisma 相关依赖到最终的 dist 目录,这里我只需要把 pkg 的 assets 选项也进行一些变更,在编译生成运行,就能验证这个方案是否生效了。 修改前的配置: "pkg": { "assets": "node_modules/.prisma/client/query_engine-windows.dll.node", "prisma/schema.prisma", "dist/.env" ], "targets": "node16-win-x64" ], "outputPath": "bin" }, 修改后的配置: "pkg": { "assets": "dist/query_engine-windows.dll.node", "dist/schema.prisma", "dist/.env" ], "targets": "node16-win-x64" ], "outputPath": "bin" }, 输入 pkg . 打包 然后测试运行 PrismaClientInitializationError: error: Environment variable not found: DATABASE_URL. 缺少 env 文件,拷贝后再运行,成功运行。 到这里其实就可以结束了。pkg打包后,只有可执行文件和env变量文件。 不过还没有实现单可执行文件的目标。要是想要生成的文件,可以动态修改运行变量,可以显式的保留 env 文件。 但如果想要继续将env也打包进可执行文件内,咱们接着往下走。 看样子加载 env 的逻辑还得微调,加载 env 我用的 dotenv 库,而这个库默认会在 process.cwd() 目录下找 env 文件,以下是 dotenv 加载的部分源码 let dotenvPath = path.resolve(process.cwd(), '.env') if (options options.path options.path.length 0) { dotenvPath = options.path } 所以我们只需要在 dotenv 加载 env 的时候判断一下文件是否存在,不存在就使用 path.join(__dirname, ".env") 目录就能够修复这个小问题。而且不会对开发测试有影响。( vscode 会甚至会自动将env文件里的变量添加进 process.env ,而不用手动加载) 在加载 env 的地方做出如下修改 const envPath = fs.existsSync(path.resolve(process.cwd(), '.env')) ? path.resolve(process.cwd(), '.env') : path.resolve(__dirname, '.env'); env.config({ path: envPath }); 打包编译运行,成功。至此,成功实现 pkg 打包成单文件运行后端。就是这体积。。。 100M+,当然打包的时候加上 --compress Brotli 参数能压缩到50M+,也还是可以接受的。 总结 文章写的比较匆忙,若有错误请加以指正。 查阅文档也许解决不了所有问题,但能解决大部分问题。查阅文档的同时配合源码的阅读那就更加快了问题的解决速度,事半功倍。所以遇事不决看文档和源码,祝各位在编码的路上不忘初心,越走越远。咱们顶峰见!最近由于项目需要,我选择用Nestjs做一个后端项目。
以下是主要技术栈
我的开发环境:
- Windows 11 23H2 欢迎使用 Windows (microsoft.com)
- Node v16.18.1: Index of /dist/v16.18.1/ (nodejs.org)
- pnpm v8.10.3 : Fast, disk space efficient package manager | pnpm
- MYSQL 8.0 : MySQL
orm框架没用typeorm,是想尝尝新的工具库。
网络上有很多打包nest的博文,但是和我用类似技术栈(prisma)还用pkg打包的貌似很少。因为这个框架的特殊性,给打包的过程中也带来了很多依赖加载的问题,在查询文档的过程中我还看到有使用electron的同志也遇到了类似的依赖加载问题。
这篇问题记录在记录遇到的问题的同时分享一下三种打包方式,希望给使用类似技术栈的读者能带来帮助。
webpack
webpack打包时, 我参考了这篇文章的相关内容: Nest系列(八)一路坎坷,我实现了最简便的方式打包部署nestjs+prisma应用 - 掘金
他的基础配置如下:
不出意外的话要出意外了, 以下是部分报错内容
可以看出,都是类似于某某依赖无法正常导入,我将报错出现过的依赖全部添加进webpack.IgnorePlugin插件里的lazyImports后再生成,就能够正常生成了。
大概有这些依赖:
但是运行时仍然报错:
根据schema大概推测是是graphql报的错,由于webpack会压缩js,所以会改变graphql的resolver和相关类实现,而在我的项目中,使用了nest的GraphQLModule来动态生成graphql的相关schema,而动态生成的过程中,会使用类的原名,这就导致了错误,因为压缩过程中会有许多同名类,也就导致多个相同名字的schema生成,上述报错就是说schema需要类型名唯一
我是使用以下方式加载的GraphQLModule
分析出了错误,解决方法也就清晰了,修改webpack的相关配置,让TerserPlugin在压缩js时,保持类名和函数名就行了
打包完成后,复制到其他文件夹运行试试,不出意外的话又会有新的错误
大概就是缺少文件,把运行时会用到的文件也拷贝到这个文件夹,主要有这两个文件
- .env : 环境配置
- schema.prisma : prisma库的配置文件
复制到目录后还是报错:
查阅文档可知,这是由于prisma会根据目标平台(开发环境)生成一个二进制文件,这个二进制文件是prisma与数据库交互的必备组件,如果没有修改schema.prisma配置文件里generator client配置的output属性的话,这个二进制文件的默认路径在node_modules/.prisma/client/文件夹下,如果修改了,则在output目录下,后缀为.node, windows环境下这个文件是query_engine-windows.dll.node
把这个文件也复制到目录中再运行试试
Ok,启动成功,打包过后大概有这些文件(windows环境)
只要移植这些文件到一个拥有node环境的windows电脑上就能运行。(node版本最好一致)
如果要移植到linux,根据上述prisma的文档,修改schema.prisma配置文件里generator client配置的binaryTargets属性为指定架构的linux就行,具体填写的值,请参考文档。之后使用webpack打包完,像上面一样把二进制文件也复制打包到目标计算机,就能够正常运行了,得益于nodejs的跨平台特性啊。
webpack配置微调
上面的webpack打包时没有任何输出,且得手动复制prisma的相关依赖文件,下面是我添加了进度条插件和复制插件后的webpack配置,需要自取
docker
docker打包没什么阻力, 主要是docker镜像的大小有点超出我的预料, 足足500M, 压缩完也还有100M+., 所以放弃了使用docker部署的念头。
Dockerfile如下
运行以下指令生成镜像,然后运行镜像
由于我的服务器环境中,mysql运行在宿主机上, 所以要将mysql的连接地址从localhost改为host.docker.internal, 使得docker容器能够正常访问到宿主机的mysql数据库。
docker打包成功,正常运行,但是镜像文件过于庞大,还是选择使用webpack打包后直接在node环境运行
pkg打包
使用nest编译, pkg打包 (失败)
pkg打包流程如下
运行时, 会报以下错误
prisma官方有类似解决方案: Solve ENOENT package error with vercel/pkg | Prisma Docs
根据解决方案,在package.json文件中添加了如下配置
再次输入pkg .指令生成后运行仍然报相同错误, 应该是该二进制文件没有被正确打包, 即使手动复制node_modules的相关文件夹也一样报错, 具体原因我暂时没有找到相关解决方案,此打包方式尝试失败。
类似问题相关github issue讨论页面: Cannot access engine inside vercel/pkg · Issue #8449 · prisma/prisma (github.com)
使用webpack先打包, 在用pkg生成
分析了一下,觉得nestjs编译时只是转译typescript为javascript,并没有包含依赖,所以使用nestjs编译再用pkg打包会失败,pkg也没有正确的包含二进制文件,使得程序运行时无法正确加载依赖而崩溃
所以我又尝试了先使用上面的webpack打包,然后再使用pkg打包的方式,试验是否能够解决这个问题。
结果是我直接在开发目录运行能够运行成功,拷贝到其他目录时就会运行失败,说明在开发目录能够加载到相关依赖,而迁移后不行。
然后我打开打包后的main.js查找相关加载代码,找到了以下这段(已格式化):
因为有process.cwd()动态获取程序运行目录,所以我初步判断这就是动态加载prisma二进制文件引擎和schema文件的代码。
尝试在程序目录下创建.prisma/client文件,并拷入二进制文件和schema.prisma文件
再运行,成功了!
目录结构如下:
不过这样的结构总感觉不太好看。
深入一点,生成单文件
仔细观察上一节我找到的js代码
这个if判断是先判断了path.join(__dirname, "schema.prisma")文件不存在后,才去程序目录下"node_modules/.prisma/client", ".prisma/client"的两个文件夹下找schema文件。
一般情况下__dirname与process.cwd()代表的都是运行的js所在的文件目录地址,但在pkg打包后,情况有所改变,以下是pkg打包前后的文件地址对应关系
| value | with node | packaged | comments |
|---|---|---|---|
| __filename | /project/app.js | /snapshot/project/app.js | |
| __dirname | /project | /snapshot/project | |
| process.cwd() | /project | /deploy | suppose the app is called ... |
| process.execPath | /usr/bin/nodejs | /deploy/app-x64 | app-x64 and run in /deploy |
| process.argv[0] | /usr/bin/nodejs | /deploy/app-x64 | |
| process.argv[1] | /project/app.js | /snapshot/project/app.js | |
| process.pkg.entrypoint | undefined | /snapshot/project/app.js | |
| process.pkg.defaultEntrypoint | undefined | /snapshot/project/app.js | |
| require.main.filename | /project/app.js | /snapshot/project/app.js |
只需要关注第二行和第三行, 从中可知process.cwd()运行时还是保持程序运行目录,但是__dirname会变成打包时,打包的js文件所在的目录(相对路径)。
而上面的if使用的是path.join(__dirname, "schema.prisma")来合成schema所在地址,知道这些,解决方案也就大概知道啦,那就是把依赖和webpack打包生成的dist放在同一目录下。
刚才配置webpack的时候我就用CopyPlugin自动复制了prisma相关依赖到最终的dist目录,这里我只需要把pkg的assets选项也进行一些变更,在编译生成运行,就能验证这个方案是否生效了。
修改前的配置:
修改后的配置:
输入pkg .打包
然后测试运行
缺少env文件,拷贝后再运行,成功运行。
到这里其实就可以结束了。pkg打包后,只有可执行文件和env变量文件。
不过还没有实现单可执行文件的目标。要是想要生成的文件,可以动态修改运行变量,可以显式的保留env文件。
但如果想要继续将env也打包进可执行文件内,咱们接着往下走。
看样子加载env的逻辑还得微调,加载env我用的dotenv库,而这个库默认会在process.cwd()目录下找env文件,以下是dotenv加载的部分源码
所以我们只需要在dotenv加载env的时候判断一下文件是否存在,不存在就使用
path.join(__dirname, ".env")目录就能够修复这个小问题。而且不会对开发测试有影响。(vscode会甚至会自动将env文件里的变量添加进process.env,而不用手动加载)
在加载env的地方做出如下修改
打包编译运行,成功。至此,成功实现pkg打包成单文件运行后端。就是这体积。。。
100M+,当然打包的时候加上--compress Brotli参数能压缩到50M+,也还是可以接受的。
总结
文章写的比较匆忙,若有错误请加以指正。
查阅文档也许解决不了所有问题,但能解决大部分问题。查阅文档的同时配合源码的阅读那就更加快了问题的解决速度,事半功倍。所以遇事不决看文档和源码,祝各位在编码的路上不忘初心,越走越远。咱们顶峰见!
本文采用 CC BY-NC-SA 4.0 协议进行授权,如无注明均为原创,转载请注明出处。
标题:[Nest + Prisma + Graphql] 三种方式打包部署nestjs应用时的问题记录作者:九仞之行链接:http://dev.styunlen.cn/archives/post-1656.html
