Adr*_*erc 14 bundling-and-minification typeorm nestjs
我最近必须考虑一个新软件的部署方法,该软件是用以下代码编写的:
该软件将部署在160多台服务器上,分布在欧洲各地,其中一些服务器的互联网连接非常糟糕。
我做了一些研究,很多人明确建议 反对捆绑。主要论点是,原生扩展将因像webpack或 这样的捆绑器而失败rollup(剧透:这是真的,但有一个解决方案)。在我看来,这很大程度上是因为人们不关心这一点:作者对这个用例node-pre-gyp使用了几乎相同的词语。因此,通常情况下,我被告知要使用yarn install或同步node_modules/文件夹。
该项目是新的,但node_modules/文件夹已经超过 480 MB。使用 XZ 进行最大压缩后,我得到了 20 MB 的存档。这对我来说仍然太大了,而且似乎是对资源的巨大浪费。
我还看了以下问答:
node_modules/开箱即用。IgnorePlugins显得太过分了。TypeORM 还有一些单独的问答,但所有这些似乎都需要安装ts-nodeor typescript:
Adr*_*erc 17
现在TypeORM 在 0.3.0 版本中ormconfig.json已被严重弃用,数据源定义是一个单独的 JS(甚至 TS)文件,必须由 Webpack 捆绑。
这很棘手,因为这将是一个动态 require(),因此 Webpack 不知道如何在 TypeORM 代码中处理它。提示是library入口点定义中的关键,没有名称。您可以转到上面的代码,但这里是摘录:
entry: {\n main: \'./src/main.ts\',\n console: "./src/console.ts",\n \'data-source\': {\n import: \'./src/data-source.ts\',\n library: {\n type: \'commonjs2\'\n }\n },\n typeorm: \'./node_modules/typeorm/cli.js\',\n },\nRun Code Online (Sandbox Code Playgroud)\n原始响应是使用 NestJS 6 完成的,该版本使用 Webpack 4。\n由于 NestJS 8 使用 Webpack 5,因此可以使用块分割,并提供更好的解决方案。
\n我还集成了webpack-merge的使用,使其只有一个配置文件。\n这只会更改您在阅读时看到的 Webpack 配置。
\n我设法找到了一个很好的解决方案,使用以下工具生成 2.7 MB 的独立 RPM:
\nwebpack具有特殊配置webpack, 来分发生成的文件。该软件是一个 API 服务器,使用 PostgreSQL 进行持久化。用户通常使用外部服务器进行身份验证,但我们可以拥有本地(紧急)用户,因此我们用于bcrypt存储和检查密码。
我必须坚持:我的解决方案不适用于本机扩展。幸运的是,流行的bcrypt可以替换为纯 JS 实现,并且最流行的 postgresql 包既可以使用编译的也可以使用纯 JS。
如果想与本机扩展捆绑,您可以尝试使用ncc。他们设法为依赖包实现了一个解决方案node-pre-gyp,该解决方案在一些初步测试中对我有用。当然,编译的扩展应该与您的目标平台相匹配,就像编译的东西一样。
我个人选择是webpack因为 NestJS在它的build命令中支持这个。这只是webpack编译器的一个传递,但它似乎调整了一些路径,所以它更容易一些。
那么,如何实现这一目标呢?webpack可以将所有内容捆绑在一个文件中,但在这个用例中,我需要其中三个:
由于每个捆绑都需要不同的 options\xe2\x80\xa6 我使用了 3 个webpack文件。这是布局:
webpack.config.js\nwebpack\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 migrations.config.js\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 typeorm-cli.config.js\nRun Code Online (Sandbox Code Playgroud)\n所有这些文件都基于ZenSoftware 善意提供的相同模板。主要区别是我从 切换IgnorePlugin到 ,externals因为它更易于阅读,并且完全适合用例。
webpack.config.js\nwebpack\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 migrations.config.js\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 typeorm-cli.config.js\nRun Code Online (Sandbox Code Playgroud)\nTypeORM 的配置文件更加冗长,因为我们需要显式使用 TypeScript。幸运的是,他们在常见问题解答中对此提供了一些建议。然而,捆绑迁移工具还需要两个技巧:
\nshebang-loader(5年后仍然按原样工作!)webpack不替换require对动态配置文件的调用,用于从 JSON 或env文件加载配置。我在这个 QA 的指导下,最终构建了自己的包。// webpack.config.js\nconst { NODE_ENV = \'production\' } = process.env;\n\nconsole.log(`-- Webpack <${NODE_ENV}> build --`);\n\nmodule.exports = {\n target: \'node\',\n mode: NODE_ENV,\n externals: [\n // Here are listed all optional dependencies of NestJS,\n // that are not installed and not required by my project\n {\n \'fastify-swagger\': \'commonjs2 fastify-swagger\',\n \'aws-sdk\': \'commonjs2 aws-sdk\',\n \'@nestjs/websockets/socket-module\': \'commonjs2 @nestjs/websockets/socket-module\',\n \'@nestjs/microservices/microservices-module\': \'commonjs2 @nestjs/microservices/microservices-module\',\n \n // I\'ll skip pg-native in the production deployement, and use the pure JS implementation\n \'pg-native\': \'commonjs2 pg-native\'\n }\n ],\n optimization: {\n // Minimization doesn\'t work with @Module annotation\n minimize: false,\n }\n};\nRun Code Online (Sandbox Code Playgroud)\n// webpack/typeorm-cli.config.js\n\nconst path = require(\'path\');\n// TypeScript compilation option\nconst TsconfigPathsPlugin = require(\'tsconfig-paths-webpack-plugin\');\n// Don\'t try to replace require calls to dynamic files\nconst IgnoreDynamicRequire = require(\'webpack-ignore-dynamic-require\');\n\nconst { NODE_ENV = \'production\' } = process.env;\n\nconsole.log(`-- Webpack <${NODE_ENV}> build for TypeORM CLI --`);\n\nmodule.exports = {\n target: \'node\',\n mode: NODE_ENV,\n entry: \'./node_modules/typeorm/cli.js\',\n output: {\n // Remember that this file is in a subdirectory, so the output should be in the dist/\n // directory of the project root\n path: path.resolve(__dirname, \'../dist\'),\n filename: \'migration.js\',\n },\n resolve: {\n extensions: [\'.ts\', \'.js\'],\n // Use the same configuration as NestJS\n plugins: [new TsconfigPathsPlugin({ configFile: \'./tsconfig.build.json\' })],\n },\n module: {\n rules: [\n { test: /\\.ts$/, loader: \'ts-loader\' },\n // Skip the shebang of typeorm/cli.js\n { test: /\\.[tj]s$/i, loader: \'shebang-loader\' }\n ],\n },\n externals: [\n {\n // I\'ll skip pg-native in the production deployement, and use the pure JS implementation\n \'pg-native\': \'commonjs2 pg-native\'\n }\n ],\n plugins: [\n // Let NodeJS handle are requires that can\'t be resolved at build time\n new IgnoreDynamicRequire()\n ]\n};\nRun Code Online (Sandbox Code Playgroud)\n自从 Nest-cli 迁移到 webpack 5 以来,现在提供了一个有趣的功能:节点目标的块分割。
\n我也很不高兴用相同的逻辑管理多个文件,所以我决定使用webpack-merge只有一个配置文件。
\n您必须yarn add -D webpack-merge并具备以下条件webpack.config.js
// webpack/migrations.config.js\n\nconst glob = require(\'glob\');\nconst path = require(\'path\');\n// TypeScript compilation option\nconst TsconfigPathsPlugin = require(\'tsconfig-paths-webpack-plugin\');\n// Minimization option\nconst TerserPlugin = require(\'terser-webpack-plugin\');\n\nconst { NODE_ENV = \'production\' } = process.env;\n\nconsole.log(`-- Webpack <${NODE_ENV}> build for migrations scripts --`);\n\nmodule.exports = {\n target: \'node\',\n mode: NODE_ENV,\n // Dynamically generate a `{ [name]: sourceFileName }` map for the `entry` option\n // change `src/db/migrations` to the relative path to your migration folder\n entry: glob.sync(path.resolve(\'src/migration/*.ts\')).reduce((entries, filename) => {\n const migrationName = path.basename(filename, \'.ts\');\n return Object.assign({}, entries, {\n [migrationName]: filename,\n });\n }, {}),\n resolve: {\n // assuming all your migration files are written in TypeScript\n extensions: [\'.ts\'],\n // Use the same configuration as NestJS\n plugins: [new TsconfigPathsPlugin({ configFile: \'./tsconfig.build.json\' })],\n },\n module: {\n rules: [\n { test: /\\.ts$/, loader: \'ts-loader\' }\n ]\n },\n output: {\n // Remember that this file is in a subdirectory, so the output should be in the dist/\n // directory of the project root\n path: __dirname + \'/../dist/migration\',\n // this is important - we want UMD (Universal Module Definition) for migration files.\n libraryTarget: \'umd\',\n filename: \'[name].js\',\n },\n optimization: {\n minimizer: [\n // Migrations rely on class and function names, so keep them.\n new TerserPlugin({\n terserOptions: {\n mangle: true, // Note `mangle.properties` is `false` by default.\n keep_classnames: true,\n keep_fnames: true,\n }\n })\n ],\n },\n};\nRun Code Online (Sandbox Code Playgroud)\n使用此文件,可以从当前命令中选择 webpack 配置:bundle:main将选择主入口点的配置。
您还会注意到 main:main和中现在有多个入口点console。前者用于主应用程序,后者用于 CLI 助手。但它们都共享相同(且大量)的代码,并且 Webpack 5 能够通过splitChunkssection 来做到这一点。这在 Webpack 4 中可用,但不适用于node目标。
最后,当您保留类和函数名称时,即使使用装饰器(使用反射),一些优化现在也可以完全发挥作用。
\n捆绑包更小,代码共享,package.json更清晰,每个人都高兴。
由于我们现在必须使用 JS 数据源,因此必须可以从 TypeORM CLI 中发现它。
\n\nconst TYPEORM_CONFIG = {\n entry: {\n \'data-source\': {\n // Are you ready? You must provide a data source as TypeORM cli >= 0.3\n // But this will be a dynamic require(), so Webpack can\'t know how to handle it\n // in TypeORM code. Instead, export this data source as a library.\n // BUT, TypeORM expect it to be at the top level instead of a module variable, so\n // we MUST remove the library name to make webpack export each variable from data source into the module\n import: \'./src/data-source.ts\',\n library: {\n type: \'commonjs2\'\n }\n },\n typeorm: \'./node_modules/typeorm/cli.js\'\n },\n externals: [\n {\n \'pg-native\': \'commonjs2 pg-native\',\n }\n ],\n plugins: [\n new IgnoreDynamicRequire(),\n ],\n module: {\n rules: [\n { test: /\\.[tj]s$/i, loader: \'shebang-loader\' }\n ],\n },\n}\nRun Code Online (Sandbox Code Playgroud)\n之后,为了简化构建过程,我在以下位置添加了一些目标package.json:
// webpack.config.js\nconst { merge } = require("webpack-merge")\nconst path = require(\'path\')\nconst glob = require(\'glob\')\nconst TsconfigPathsPlugin = require(\'tsconfig-paths-webpack-plugin\')\nconst TerserPlugin = require(\'terser-webpack-plugin\')\nconst MomentLocalesPlugin = require(\'moment-locales-webpack-plugin\')\nconst IgnoreDynamicRequire = require(\'webpack-ignore-dynamic-require\')\n\nconst { NODE_ENV = \'production\', ENTRY, npm_lifecycle_event: lifecycle } = process.env\n\n// Build platform don\'t support ?? and ?. operators\nconst entry = ENTRY || (lifecycle && lifecycle.match(/bundle:(?<entry>\\w+)/).groups["entry"])\n\nif (entry === undefined) {\n throw new Error("ENTRY must be defined")\n}\n\nconsole.log(`-- Webpack <${NODE_ENV}> build for <${entry}> --`);\n\nconst BASE_CONFIG = {\n target: \'node\',\n mode: NODE_ENV,\n resolve: {\n extensions: [\'.ts\', \'.js\'],\n plugins: [new TsconfigPathsPlugin({ configFile: \'./tsconfig.build.json\' })],\n },\n module: {\n rules: [\n { test: /\\.ts$/, loader: \'ts-loader\' }\n ]\n },\n output: {\n path: path.resolve(__dirname, \'dist/\'),\n filename: \'[name].js\',\n },\n}\n\nconst MIGRATION_CONFIG = {\n // Dynamically generate a `{ [name]: sourceFileName }` map for the `entry` option\n // change `src/db/migrations` to the relative path to your migration folder\n entry: glob.sync(path.resolve(\'src/migration/*.ts\')).reduce((entries, filename) => {\n const migrationName = path.basename(filename, \'.ts\')\n return Object.assign({}, entries, {\n [migrationName]: filename,\n })\n }, {}),\n output: {\n path: path.resolve(__dirname, \'dist/migration\'),\n // this is important - we want UMD (Universal Module Definition) for migration files.\n libraryTarget: \'umd\',\n filename: \'[name].js\',\n },\n optimization: {\n minimizer: [\n new TerserPlugin({\n terserOptions: {\n mangle: true, // Note `mangle.properties` is `false` by default.\n keep_classnames: true,\n keep_fnames: true,\n }\n })\n ],\n }\n}\n\nconst TYPEORM_CONFIG = {\n entry: {\n typeorm: \'./node_modules/typeorm/cli.js\'\n },\n externals: [\n {\n \'pg-native\': \'commonjs2 pg-native\',\n }\n ],\n plugins: [\n new IgnoreDynamicRequire(),\n ],\n module: {\n rules: [\n { test: /\\.[tj]s$/i, loader: \'shebang-loader\' }\n ],\n },\n}\n\nconst MAIN_AND_CONSOLE_CONFIG = {\n entry: {\n main: \'./src/main.ts\',\n console: "./src/console.ts"\n },\n externals: [\n {\n \'pg-native\': \'commonjs2 pg-native\',\n \'fastify-swagger\': \'commonjs2 fastify-swagger\',\n \'@nestjs/microservices/microservices-module\': \'commonjs2 @nestjs/microservices/microservices-module\',\n \'@nestjs/websockets/socket-module\': \'commonjs2 @nestjs/websockets/socket-module\',\n // This one is a must have to generate the swagger document, but we remove it in production\n \'swagger-ui-express\': \'commonjs2 swagger-ui-express\',\n \'aws-sdk\': \'commonjs2 aws-sdk\',\n }\n ],\n plugins: [\n // We don\'t need moment locale\n new MomentLocalesPlugin()\n ],\n optimization: {\n // Full minization doesn\'t work with @Module annotation\n minimizer: [\n new TerserPlugin({\n terserOptions: {\n mangle: true, // Note `mangle.properties` is `false` by default.\n keep_classnames: true,\n keep_fnames: true,\n }\n })\n ],\n splitChunks: {\n cacheGroups: {\n commons: {\n test: /[\\\\/]node_modules[\\\\/]/,\n name: \'vendors\',\n chunks: \'all\'\n }\n }\n }\n }\n}\n\nconst withPlugins = (config) => (runtimeConfig) => ({\n ...config,\n plugins: [\n ...runtimeConfig.plugins,\n ...(config.plugins || [])\n ]\n})\n\nconst config = entry === "migrations" ? merge(BASE_CONFIG, MIGRATION_CONFIG)\n : entry === "typeorm" ? merge(BASE_CONFIG, TYPEORM_CONFIG)\n : entry === "main" ? merge(BASE_CONFIG, MAIN_AND_CONSOLE_CONFIG)\n : undefined\n\n\nmodule.exports = withPlugins(config)\n\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 你就快完成了。您可以调用yarn bundle,输出将在dist/目录中构建。我没有设法删除生成的一些 TypeScript 定义文件,但这不是真正的问题。
最后一步是编写 RPM 规范文件:
\n%build\nmkdir yarncache\nexport YARN_CACHE_FOLDER=yarncache\n\n# Setting to avoid node-gype trying to download headers\nexport npm_config_nodedir=/opt/rh/rh-nodejs10/root/usr/\n\n%{_yarnbin} install --offline --non-interactive --frozen-lockfile\n%{_yarnbin} bundle\n\nrm -r yarncache/\n\n%install\ninstall -D -m644 dist/main.js $RPM_BUILD_ROOT%{app_path}/main.js\n\ninstall -D -m644 dist/migration.js $RPM_BUILD_ROOT%{app_path}/migration.js\n# Migration path have to be changed, let\'s hack it.\nsed -ie \'s/src\\/migration\\/\\*\\.ts/migration\\/*.js/\' ormconfig.json\ninstall -D -m644 ormconfig.json $RPM_BUILD_ROOT%{app_path}/ormconfig.json\nfind dist/migration -name \'*.js\' -execdir install -D -m644 "{}" "$RPM_BUILD_ROOT%{app_path}/migration/{}" \\;\nRun Code Online (Sandbox Code Playgroud)\n如果您将 TypeORM >= 0.3 与 JS 数据源一起使用,则不必执行 sed 行,因为 DataSource 可以接受生产和开发源。但是,ormconfig.json 必须替换为data-source.js。
systemd 服务文件可以告诉你如何启动它。目标平台是CentOS7,所以我必须使用软件集合中的NodeJS 10。您可以调整 NodeJS 二进制文件的路径。
\n\nconst TYPEORM_CONFIG = {\n entry: {\n \'data-source\': {\n // Are you ready? You must provide a data source as TypeORM cli >= 0.3\n // But this will be a dynamic require(), so Webpack can\'t know how to handle it\n // in TypeORM code. Instead, export this data source as a library.\n // BUT, TypeORM expect it to be at the top level instead of a module variable, so\n // we MUST remove the library name to make webpack export each variable from data source into the module\n import: \'./src/data-source.ts\',\n library: {\n type: \'commonjs2\'\n }\n },\n typeorm: \'./node_modules/typeorm/cli.js\'\n },\n externals: [\n {\n \'pg-native\': \'commonjs2 pg-native\',\n }\n ],\n plugins: [\n new IgnoreDynamicRequire(),\n ],\n module: {\n rules: [\n { test: /\\.[tj]s$/i, loader: \'shebang-loader\' }\n ],\n },\n}\nRun Code Online (Sandbox Code Playgroud)\n如果您使用 TypeORM JS 数据源,则必须添加-f data-source.js到ExecStartPre.
最终统计:
\n.production.env用于主应用程序和ormconfig.json迁移)| 归档时间: |
|
| 查看次数: |
8704 次 |
| 最近记录: |