2 votes

Regrouper une application NestJS + TypeORM (avec webpack)

J'ai récemment dû réfléchir à une méthode de déploiement pour un nouveau logiciel écrit avec :

  • NestJS 6 / Express
  • TypeORM 0.2
  • TypeScript est utilisé

Le logiciel sera déployé sur plus de 160 serveurs, répartis dans toute l'Europe, et certains d'entre eux ont de très mauvaises connexions Internet.

J'ai fait des recherches et beaucoup de Les gens conseillent explicitement contre regroupement. L'argument principal est que l'extension native échouera avec des bundlers tels que webpack ou rollup (Spoiler : c'est vrai, mais il y a une solution). A mon avis, c'est en grande partie dû au fait que les gens ne s'y intéressent pas : l'auteur de node-pre-gyp utilisé presque les mêmes mots pour ce cas d'utilisation . En général, on m'a dit d'utiliser soit yarn install ou synchroniser les node_modules/ dossier .

Le projet est nouveau, mais la node_modules/ est déjà supérieur à 480 Mo. En utilisant XZ avec une compression maximale, j'ai obtenu une archive de 20 Mo. C'est encore beaucoup trop volumineux pour moi, et cela me semble être un énorme gaspillage de ressources.

J'ai également consulté les questions-réponses suivantes :

Il existe également des questions-réponses distinctes pour TypeORM, mais toutes semblent nécessiter l'installation de ts-node ou typescript :

2voto

Adrien Clerc Points 571

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X