我最近不得不考虑一个新的软件的部署方法,它是用以下代码编写的:
该软件将部署在160多台服务器上,分布在整个欧洲,其中一些服务器的互联网连接非常糟糕。
我做了一些研究,很多人明确建议不要捆绑销售。主要的论点是本机扩展将失败与捆绑器,如webpack
或rollup
(剧透:这是真的,但有一个解决方案)。在我看来,这在很大程度上是由于人们不关心这个事实:node-pre-gyp
的作者在这个用例中使用了几乎相同的词。所以通常,我被告知要么使用yarn安装
,要么同步node_modules/
文件夹。
该项目是新的,但节点\u模块/
文件夹已超过480 MB。使用最大压缩的XZ给了我20 MB的存档。这对我来说还是太大了,而且似乎是对资源的巨大浪费。
我还看了下面的Q
node\u模块的webpack/
还有一些单独的问题
最初的响应是用NestJS 6完成的,它使用了Webpack 4。由于NestJS 8使用Webpack 5,块分割是可用的,并提供了一个更好的解决方案。
我还集成了webpack合并的使用,使其只有一个配置文件。这只会更改阅读时看到的网页包配置。
我设法找到了一个很好的解决方案,通过以下工具生成了2.7 MB的自包含RPM:
具有特殊配置的网页包
webpack
,以分发生成的文件
该软件是一个API服务器,使用PostgreSQL进行持久化。用户通常使用外部服务器进行身份验证,但我们可以使用本地(紧急)用户,因此我们使用bcrypt
存储和检查密码。
我必须坚持:我的解决方案不适用于本机扩展。幸运的是,流行的bcrypt
可以用纯JS实现代替,最流行的postgresql包可以同时使用编译的或纯JS。
如果想要绑定本机扩展,可以尝试使用ncc。他们设法为节点前gyp
依赖包实现了一个解决方案,在一些初步测试中对我有效。当然,编译后的扩展应该与您的目标平台相匹配,就像编译后的东西一样。
我个人选择webpack
是因为NestJS在它的build
命令中支持这个。这仅仅是一个通向webpack
编译器的通道,但是它似乎调整了一些路径,所以有点容易。
那么,如何做到这一点呢webpack
可以将所有内容捆绑在一个文件中,但在本用例中,我需要三个:
因为每个捆绑需要不同的选择...我用了3个webpack
文件。这里是布局:
webpack.config.js
webpack
├── migrations.config.js
└── typeorm-cli.config.js
所有这些文件都基于ZenSoftware善意提供的相同模板。主要的区别是我从IgnorePlugin
切换到externals
,因为这更易于阅读,并且非常适合用例。
// webpack.config.js
const { NODE_ENV = 'production' } = process.env;
console.log(`-- Webpack <${NODE_ENV}> build --`);
module.exports = {
target: 'node',
mode: NODE_ENV,
externals: [
// Here are listed all optional dependencies of NestJS,
// that are not installed and not required by my project
{
'fastify-swagger': 'commonjs2 fastify-swagger',
'aws-sdk': 'commonjs2 aws-sdk',
'@nestjs/websockets/socket-module': 'commonjs2 @nestjs/websockets/socket-module',
'@nestjs/microservices/microservices-module': 'commonjs2 @nestjs/microservices/microservices-module',
// I'll skip pg-native in the production deployement, and use the pure JS implementation
'pg-native': 'commonjs2 pg-native'
}
],
optimization: {
// Minimization doesn't work with @Module annotation
minimize: false,
}
};
TypeForm的配置文件更详细,因为我们需要明确使用TypeScript。幸运的是,他们在FAQ中对此有一些建议。然而,捆绑迁移工具还需要两个技巧:
shebang loader轻松解决(5年后仍能正常工作!)
// webpack/typeorm-cli.config.js
const path = require('path');
// TypeScript compilation option
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
// Don't try to replace require calls to dynamic files
const IgnoreDynamicRequire = require('webpack-ignore-dynamic-require');
const { NODE_ENV = 'production' } = process.env;
console.log(`-- Webpack <${NODE_ENV}> build for TypeORM CLI --`);
module.exports = {
target: 'node',
mode: NODE_ENV,
entry: './node_modules/typeorm/cli.js',
output: {
// Remember that this file is in a subdirectory, so the output should be in the dist/
// directory of the project root
path: path.resolve(__dirname, '../dist'),
filename: 'migration.js',
},
resolve: {
extensions: ['.ts', '.js'],
// Use the same configuration as NestJS
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.build.json' })],
},
module: {
rules: [
{ test: /\.ts$/, loader: 'ts-loader' },
// Skip the shebang of typeorm/cli.js
{ test: /\.[tj]s$/i, loader: 'shebang-loader' }
],
},
externals: [
{
// I'll skip pg-native in the production deployement, and use the pure JS implementation
'pg-native': 'commonjs2 pg-native'
}
],
plugins: [
// Let NodeJS handle are requires that can't be resolved at build time
new IgnoreDynamicRequire()
]
};
// webpack/migrations.config.js
const glob = require('glob');
const path = require('path');
// TypeScript compilation option
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
// Minimization option
const TerserPlugin = require('terser-webpack-plugin');
const { NODE_ENV = 'production' } = process.env;
console.log(`-- Webpack <${NODE_ENV}> build for migrations scripts --`);
module.exports = {
target: 'node',
mode: NODE_ENV,
// Dynamically generate a `{ [name]: sourceFileName }` map for the `entry` option
// change `src/db/migrations` to the relative path to your migration folder
entry: glob.sync(path.resolve('src/migration/*.ts')).reduce((entries, filename) => {
const migrationName = path.basename(filename, '.ts');
return Object.assign({}, entries, {
[migrationName]: filename,
});
}, {}),
resolve: {
// assuming all your migration files are written in TypeScript
extensions: ['.ts'],
// Use the same configuration as NestJS
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.build.json' })],
},
module: {
rules: [
{ test: /\.ts$/, loader: 'ts-loader' }
]
},
output: {
// Remember that this file is in a subdirectory, so the output should be in the dist/
// directory of the project root
path: __dirname + '/../dist/migration',
// this is important - we want UMD (Universal Module Definition) for migration files.
libraryTarget: 'umd',
filename: '[name].js',
},
optimization: {
minimizer: [
// Migrations rely on class and function names, so keep them.
new TerserPlugin({
terserOptions: {
mangle: true, // Note `mangle.properties` is `false` by default.
keep_classnames: true,
keep_fnames: true,
}
})
],
},
};
自从nest-cli移到webpack 5后,现在有了一个有趣的特性:节点目标的块分割。
我也对用相同的逻辑管理多个文件感到不安,所以我决定使用webpack merge来只有一个配置文件。
您必须
添加-D网页包合并
,并拥有以下网页包。配置。js
// webpack.config.js
const { merge } = require("webpack-merge")
const path = require('path')
const glob = require('glob')
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const MomentLocalesPlugin = require('moment-locales-webpack-plugin')
const IgnoreDynamicRequire = require('webpack-ignore-dynamic-require')
const { NODE_ENV = 'production', ENTRY, npm_lifecycle_event: lifecycle } = process.env
// Build platform don't support ?? and ?. operators
const entry = ENTRY || (lifecycle && lifecycle.match(/bundle:(?<entry>\w+)/).groups["entry"])
if (entry === undefined) {
throw new Error("ENTRY must be defined")
}
console.log(`-- Webpack <${NODE_ENV}> build for <${entry}> --`);
const BASE_CONFIG = {
target: 'node',
mode: NODE_ENV,
resolve: {
extensions: ['.ts', '.js'],
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.build.json' })],
},
module: {
rules: [
{ test: /\.ts$/, loader: 'ts-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist/'),
filename: '[name].js',
},
}
const MIGRATION_CONFIG = {
// Dynamically generate a `{ [name]: sourceFileName }` map for the `entry` option
// change `src/db/migrations` to the relative path to your migration folder
entry: glob.sync(path.resolve('src/migration/*.ts')).reduce((entries, filename) => {
const migrationName = path.basename(filename, '.ts')
return Object.assign({}, entries, {
[migrationName]: filename,
})
}, {}),
output: {
path: path.resolve(__dirname, 'dist/migration'),
// this is important - we want UMD (Universal Module Definition) for migration files.
libraryTarget: 'umd',
filename: '[name].js',
},
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
mangle: true, // Note `mangle.properties` is `false` by default.
keep_classnames: true,
keep_fnames: true,
}
})
],
}
}
const TYPEORM_CONFIG = {
entry: {
typeorm: './node_modules/typeorm/cli.js'
},
externals: [
{
'pg-native': 'commonjs2 pg-native',
}
],
plugins: [
new IgnoreDynamicRequire(),
],
module: {
rules: [
{ test: /\.[tj]s$/i, loader: 'shebang-loader' }
],
},
}
const MAIN_AND_CONSOLE_CONFIG = {
entry: {
main: './src/main.ts',
console: "./src/console.ts"
},
externals: [
{
'pg-native': 'commonjs2 pg-native',
'fastify-swagger': 'commonjs2 fastify-swagger',
'@nestjs/microservices/microservices-module': 'commonjs2 @nestjs/microservices/microservices-module',
'@nestjs/websockets/socket-module': 'commonjs2 @nestjs/websockets/socket-module',
// This one is a must have to generate the swagger document, but we remove it in production
'swagger-ui-express': 'commonjs2 swagger-ui-express',
'aws-sdk': 'commonjs2 aws-sdk',
}
],
plugins: [
// We don't need moment locale
new MomentLocalesPlugin()
],
optimization: {
// Full minization doesn't work with @Module annotation
minimizer: [
new TerserPlugin({
terserOptions: {
mangle: true, // Note `mangle.properties` is `false` by default.
keep_classnames: true,
keep_fnames: true,
}
})
],
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
const withPlugins = (config) => (runtimeConfig) => ({
...config,
plugins: [
...runtimeConfig.plugins,
...(config.plugins || [])
]
})
const config = entry === "migrations" ? merge(BASE_CONFIG, MIGRATION_CONFIG)
: entry === "typeorm" ? merge(BASE_CONFIG, TYPEORM_CONFIG)
: entry === "main" ? merge(BASE_CONFIG, MAIN_AND_CONSOLE_CONFIG)
: undefined
module.exports = withPlugins(config)
使用此文件,可从当前命令中选择网页包配置:
bundle:main
将选择主入口点的配置。
您还会注意到,在main中现在有多个入口点:
main
和控制台
。前者用于主应用程序,后者用于CLI助手。但是他们都共享相同的(并且巨大的)代码量,Webpack 5能够通过spitChunks
部分做到这一点。这在Webpack 4中可用,但不适用于节点目标。
最后,当您保留类和函数名时,一些优化现在甚至可以与decorator(使用反射)一起完全工作。
捆绑更小,代码共享,
包。json更清晰,每个人都很开心。
之后,为了简化构建过程,我在
package.json
中添加了一些目标:
{
"scripts": {
"bundle:application": "nest build --webpack",
"bundle:migrations": "nest build --webpack --webpackPath webpack/typeorm-cli.config.js && nest build --webpack --webpackPath webpack/migrations.config.js",
"bundle": "yarn bundle:application && yarn bundle:migrations"
},
}
最后一步是编写RPM规范文件:
%build
mkdir yarncache
export YARN_CACHE_FOLDER=yarncache
# Setting to avoid node-gype trying to download headers
export npm_config_nodedir=/opt/rh/rh-nodejs10/root/usr/
%{_yarnbin} install --offline --non-interactive --frozen-lockfile
%{_yarnbin} bundle
rm -r yarncache/
%install
install -D -m644 dist/main.js $RPM_BUILD_ROOT%{app_path}/main.js
install -D -m644 dist/migration.js $RPM_BUILD_ROOT%{app_path}/migration.js
# Migration path have to be changed, let's hack it.
sed -ie 's/src\/migration\/\*\.ts/migration\/*.js/' ormconfig.json
install -D -m644 ormconfig.json $RPM_BUILD_ROOT%{app_path}/ormconfig.json
find dist/migration -name '*.js' -execdir install -D -m644 "{}" "$RPM_BUILD_ROOT%{app_path}/migration/{}" \;
而且System d服务文件可以告诉您如何启动它。目标平台是CentOS7,所以我必须使用软件集合中的NodeJS 10。您可以将路径调整为NodeJS二进制文件。
[Unit]
Description=NestJS Server
After=network.target
[Service]
Type=simple
User=nestjs
Environment=SCLNAME=rh-nodejs10
ExecStartPre=/usr/bin/scl enable $SCLNAME -- /usr/bin/env node migration migration:run
ExecStart=/usr/bin/scl enable $SCLNAME -- /usr/bin/env node main
WorkingDirectory=/export/myapplication
Restart=on-failure
# Hardening
PrivateTmp=true
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=read-only
[Install]
WantedBy=multi-user.target
最后统计数字:
在双核虚拟机上构建时间为3分30秒。
- RPM大小为2.70 MB,独立包含,包含3个JavaScript文件和2个配置文件(
。production.env
用于主应用程序,ormconfig.json
用于迁移)
我用的是edu。seis理学学士。启动4J插件,使用gradle构建脚本构建可分发的应用程序。我正试图用一个捆绑的JRE来制作这些。 这是格雷德尔的剧本 令人沮丧的是,这创建了一个运行的应用程序(由gradle任务createExe创建的exe),但显然没有捆绑在/旁边的JRE,大概是因为它运行是因为它返回到使用系统jre,这使得测试变得困难。如果我把一个故意损坏的jre放在 /jre/它似乎仍然
我创建了一个依赖于OpenJDK 11和JavaFX的HelloWorldJava小应用程序。该应用程序打包在jar文件中,只有在我的系统上单独安装Java11和JavaFX时才能运行。 现在,我想将我的jar转换成一个自包含的Java应用程序,其中包括JavaFX和一个功能齐全的Java运行时环境。这将允许我在不安装OpenJDK 11的情况下运行我的应用程序(这会带来技术障碍,例如正确设置路径
我刚刚启动了一个新的应用程序,但当我点击欢迎登船页面上的“关于您的应用程序的环境”链接时,会出现这个错误。 启动应用程序时出错当Pow试图运行时,您的Rack应用程序引发了异常。 Bundler::,但找不到任何源代码 我的应用正在运行: 轨道3.2。6 Ruby 1.9。3p194 Rubygems 1.8。24 RVM 1.14。5 战俘0.4。0 我发现了类似的问题,问题在于如何让乘客安静下
null 支持与平台无关的应用程序图标 支持对JAR的自动更新 运行my.jar时对JRE参数的支持 Linux支持(.deb或.rpm)
问题内容: 我在使用browserify时遇到了一些麻烦。 目标 我正在尝试使用Backbone构建基本的TodoMVC单页应用程序,而不仅仅是在我的标签堆中,而是尝试将它们全部与browserify捆绑在一起。 到目前为止,这就是我要做的。 lib /模型/todo.js lib /收藏/todo.js lib / app.js 要构建我的捆绑包,我正在使用 最后,我很简单 问题 当我打开控制台
我有以下结构: 我不知道如何构建一切,以便: 在本地运行服务器,以便调试 制作生产tar(例如使用应用程序插件) 生产应如下所示: ktor作为主服务器 ktor应用程序有API 所有反应JS/超文本标记语言文件也由ktor提供 换句话说,我想从单个Web应用程序提供所有服务。 我知道我可以制作两个独立的服务——一个用于反应,一个用于应用编程接口。但是我想把它捆绑到一个应用程序中。 地方发展要快。