J'ai réussi à trouver une bonne solution, qui génère un RPM autonome de 2.7 MB avec les outils suivants :
webpack
avec configuration spéciale
- RPM, en utilisant
webpack
afin de distribuer les fichiers générés.
Le logiciel est un serveur API, utilisant PostgreSQL pour la persistance. Les utilisateurs sont généralement authentifiés à l'aide d'un serveur externe, mais nous pouvons avoir des utilisateurs locaux (d'urgence). bcrypt
pour stocker et vérifier les mots de passe.
J'insiste : ma solution ne fonctionne pas avec les extensions natives . Heureusement, le populaire bcrypt
peut être remplacé par un mise en œuvre purement JS et le package postgresql le plus populaire est capable d'utiliser à la fois du JS compilé et du JS pur.
Si vous souhaitez utiliser l'extension native, vous pouvez essayer d'utiliser l'option ncc . Ils ont réussi à mettre en œuvre une solution pour node-pre-gyp
paquets dépendants qui a fonctionné pour moi lors de quelques tests préliminaires. Bien sûr, les extensions compilées doivent correspondre à votre plateforme cible, comme toujours pour les choses compilées.
J'ai personnellement choisi webpack
parce que NestJS soutenir ce projet dans ses build
commande . Il s'agit simplement d'un transfert vers le webpack
mais il semble ajuster certains chemins, donc c'était un peu plus facile.
Comment y parvenir ? webpack
peut tout regrouper dans un seul fichier, mais dans ce cas d'utilisation, j'en ai besoin de trois :
- Le programme principal
- En Outil CLI de migration TypeORM
- Le TypeORM migration scripts, parce qu'ils ne peuvent pas être fournis avec l'outil, car il s'appuie sur le nom de fichier.
Et comme chaque regroupement nécessite des options différentes j'ai utilisé 3 webpack
dossiers. Voici la présentation :
webpack.config.js
webpack
migrations.config.js
typeorm-cli.config.js
Tous ces dossiers étaient basés sur le même modèle aimablement fourni par ZenSoftware . La principale différence est que je suis passé de IgnorePlugin
a externals
parce que c'est plus simple à lire et que cela correspond parfaitement au cas d'utilisation.
// 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,
}
};
Les fichiers de configuration pour TypeORM sont plus verbeux, car nous devons expliciter l'utilisation de TypeScript. Heureusement, ils ont quelques des conseils à ce sujet dans leur FAQ . Cependant, l'intégration de l'outil de migration a nécessité deux autres piratages :
- Ignorer le shebang au début du fichier. Facilement résolu avec
shebang-loader
(qui fonctionne toujours en l'état après 5 ans !)
-
La force webpack
ne pas remplacer require
appeler à fichier de configuration dynamique utilisé pour charger la configuration à partir de JSON ou de env
des dossiers. J'ai été guidé par cette AQ et enfin construire mon propre paquet .
// 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, {
});
}, {}),
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 kee them.
new TerserPlugin({
terserOptions: {
mangle: true, // Note mangle.properties
is false
by default.
keep_classnames: true,
keep_fnames: true,
}
})
],
},
};
Ensuite, pour simplifier le processus de construction, j'ai ajouté quelques cibles dans le fichier 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"
},
}
Et vous avez presque terminé. Vous pouvez appeler yarn bundle
et la sortie sera construite dans le répertoire dist/
répertoire. Je n'ai pas réussi à supprimer certains fichiers de définition TypeScript qui ont été générés, mais ce n'était pas un réel problème.
La dernière étape consistait à rédiger le fichier de spécification du 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/{}" \;
Le fichier de service systemd peut vous indiquer comment le lancer. La plateforme cible est CentOS7, je dois donc utiliser NodeJS 10 dans les collections de logiciels . Vous pouvez adapter le chemin à votre binaire 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
Statistiques finales :
- La construction a duré 3 minutes 30 secondes sur une machine virtuelle à double cœur.
- La taille du RPM est de 2,70 Mo, autonome, avec 3 fichiers JavaScript et 2 fichiers de configuration (
.production.env
pour l'application principale et ormconfig.json
pour les migrations)