Comment déployer réellement une application Angular 2 + Typescript + systemjs?

103

Il existe un didacticiel de démarrage rapide sur angular.io qui utilise du typecript et systemjs. Maintenant que j'ai ce miniapp en cours d'exécution, comment pourrais-je créer quelque chose de déployable? Je n'ai trouvé aucune information à ce sujet.

Ai-je besoin d'outils supplémentaires, de paramètres supplémentaires dans System.config?

(Je sais que je pourrais utiliser webpack et créer un seul bundle.js, mais j'aimerais utiliser systemjs tel qu'il est utilisé dans le tutoriel)

Quelqu'un pourrait-il partager son processus de construction avec cette configuration (Angular 2, TypeScript, systemjs)

Brian G. Bell
la source
Voici ma recette pour créer une application ng2 pour le déploiement à l'aide de JSPM: stackoverflow.com/a/34616199/3532945
brando
2
réponse simple ng build -prod stackoverflow.com/a/38421680/5079380
Amr ElAdawy

Réponses:

66

La chose clé à comprendre à ce niveau est qu'en utilisant la configuration suivante, vous ne pouvez pas concaténer directement les fichiers JS compilés.

Dans la configuration du compilateur TypeScript:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": false,
    "stripInternal": true,
    "module": "system",
    "moduleResolution": "node",
    "noEmitOnError": false,
    "rootDir": ".",
    "inlineSourceMap": true,
    "inlineSources": true,
    "target": "es5"
  },
  "exclude": [
    "node_modules"
  ]
}

Dans le HTML

System.config({
  packages: {
    app: {
      defaultExtension: 'js',
      format: 'register'
    }
  }
});

En fait, ces fichiers JS contiendront des modules anonymes. Un module anonyme est un fichier JS qui utilise System.registermais sans le nom du module comme premier paramètre. C'est ce que le compilateur dactylographié génère par défaut lorsque systemjs est configuré comme gestionnaire de module.

Ainsi, pour avoir tous vos modules dans un seul fichier JS, vous devez exploiter la outFilepropriété dans la configuration de votre compilateur TypeScript.

Vous pouvez utiliser ce qui suit à l'intérieur de gulp pour ce faire:

const gulp = require('gulp');
const ts = require('gulp-typescript');

var tsProject = ts.createProject('tsconfig.json', {
  typescript: require('typescript'),
  outFile: 'app.js'
});

gulp.task('tscompile', function () {
  var tsResult = gulp.src('./app/**/*.ts')
                     .pipe(ts(tsProject));

  return tsResult.js.pipe(gulp.dest('./dist'));
});

Cela pourrait être combiné avec un autre traitement:

  • pour uglifier les choses les fichiers TypeScript compilés
  • pour créer un app.jsfichier
  • pour créer un vendor.jsfichier pour des bibliothèques tierces
  • pour créer un boot.jsfichier pour importer le module qui amorce l'application. Ce fichier doit être inclus à la fin de la page (lorsque toute la page est chargée).
  • mettre à jour le index.htmlpour prendre en compte ces deux fichiers

Les dépendances suivantes sont utilisées dans les tâches gulp:

  • gulp-concat
  • gulp-html-replace
  • gulp-typescript
  • gulp-uglify

Ce qui suit est un exemple afin qu'il puisse être adapté.

  • Créer un app.min.jsfichier

    gulp.task('app-bundle', function () {
      var tsProject = ts.createProject('tsconfig.json', {
        typescript: require('typescript'),
        outFile: 'app.js'
      });
    
      var tsResult = gulp.src('app/**/*.ts')
                       .pipe(ts(tsProject));
    
      return tsResult.js.pipe(concat('app.min.js'))
                    .pipe(uglify())
                    .pipe(gulp.dest('./dist'));
    });
    
  • Créer un vendors.min.jsfichier

    gulp.task('vendor-bundle', function() {
      gulp.src([
        'node_modules/es6-shim/es6-shim.min.js',
        'node_modules/systemjs/dist/system-polyfills.js',
        'node_modules/angular2/bundles/angular2-polyfills.js',
        'node_modules/systemjs/dist/system.src.js',
        'node_modules/rxjs/bundles/Rx.js',
        'node_modules/angular2/bundles/angular2.dev.js',
        'node_modules/angular2/bundles/http.dev.js'
      ])
      .pipe(concat('vendors.min.js'))
      .pipe(uglify())
      .pipe(gulp.dest('./dist'));
    });
    
  • Créer un boot.min.jsfichier

    gulp.task('boot-bundle', function() {
      gulp.src('config.prod.js')
        .pipe(concat('boot.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./dist'));
     });
    

    Le config.prod.jscontient simplement les éléments suivants:

     System.import('boot')
        .then(null, console.error.bind(console));
    
  • Mettre à jour le index.htmlfichier

    gulp.task('html', function() {
      gulp.src('index.html')
        .pipe(htmlreplace({
          'vendor': 'vendors.min.js',
          'app': 'app.min.js',
          'boot': 'boot.min.js'
        }))
        .pipe(gulp.dest('dist'));
    });
    

    Le index.htmlressemble à ce qui suit:

    <html>
      <head>
        <!-- Some CSS -->
    
        <!-- build:vendor -->
        <script src="node_modules/es6-shim/es6-shim.min.js"></script>
        <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
        <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
        <script src="node_modules/systemjs/dist/system.src.js"></script>
        <script src="node_modules/rxjs/bundles/Rx.js"></script>
        <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
        <script src="node_modules/angular2/bundles/http.dev.js"></script>
        <!-- endbuild -->
    
        <!-- build:app -->
        <script src="config.js"></script>
        <!-- endbuild -->
      </head>
    
      <body>
        <my-app>Loading...</my-app>
    
        <!-- build:boot -->
        <!-- endbuild -->
      </body>
    </html>
    

Notez que le System.import('boot');doit être effectué à la fin du corps pour attendre que tous les composants de votre application soient enregistrés à partir du app.min.jsfichier.

Je ne décris pas ici la manière de gérer la minification CSS et HTML.

Thierry Templier
la source
1
pouvez-vous créer un dépôt github avec un exemple s'il vous plaît?
jdelobel
J'ai suivi vos instructions et tout va bien en ce qui concerne la gorgée. Cependant, lorsque j'exécute l'application dans le navigateur, j'obtiens cette erreur de journal de console: "system.src.js: 1625 Uncaught TypeError: plusieurs appels anonymes System.register dans le même fichier de module." Des idées sur ce que cela signifie et comment y remédier?
AngularM
@AngularM: avez-vous le paramètre outFile? C'est la clé de votre erreur ;-)
Thierry Templier
Je l'ai dans mon fichier gulp et tsconfig
AngularM
Pourriez-vous jeter un œil au projet github que j'ai soumis? Voir mon commentaire ci-dessus. Voyez-vous des différences avec votre code?
Thierry Templier
28

Vous pouvez utiliser la commande de construction angular2-cli

ng build -prod

https://github.com/angular/angular-cli/wiki/build#bundling

Les builds créés avec l' indicateur -prod via ng build -prodou ng serve -prodregroupent toutes les dépendances dans un seul fichier et utilisent des techniques d' arborescence .

Mettre à jour

Cette réponse a été soumise lorsque angular2 était dans rc4

J'avais essayé à nouveau sur angular-cli beta21 et angular2 ^ 2.1.0 et cela fonctionne comme prévu

Cette réponse nécessite l'initialisation de l'application avec angular-cli que vous pouvez utiliser

ng new myApp

Ou sur un existant

ng init

Mise à jour 08/06/2018

Pour angulaire 6, la syntaxe est différente.

ng build --prod --build-optimizer

Consultez la documentation

Amr ElAdawy
la source
8
Cela nécessite que votre application soit structurée selon la structure d'opinion d'angular-cli.
Michael Pell
2
@Amr ElAdawy FYI angular-cli est passé au webpack. Cette question est liée à SystemJS. ng build ne fonctionne pas pour moi.
Shahriar Hasan Sayeed
@ShahriarHasanSayeed Faites-vous référence à l'heure à laquelle j'ai soumis la réponse ou à l'heure à laquelle vous l'avez essayée?
Amr ElAdawy
@AmrElAdawy, pouvez-vous ajouter les versions des modules où cela fonctionne réellement. Angular2 a beaucoup changé depuis juillet.
ppovoski
2
Il est trivial de convertir le didacticiel Tour of Heroes en version cli. Générez simplement un nouveau projet en utilisant cli, puis copiez les fichiers du didacticiel.
Rosdi Kasim
12

Vous pouvez créer un projet Angular 2 (2.0.0-rc.1) dans Typescript en utilisant SystemJS avec Gulp et SystemJS-Builder .

Vous trouverez ci-dessous une version simplifiée de la création, du regroupement et de la réduction de Tour of Heroes exécutant 2.0.0-rc.1 ( source complète , exemple en direct ).

gulpfile.js

var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var concat = require('gulp-concat');
var typescript = require('gulp-typescript');
var systemjsBuilder = require('systemjs-builder');

// Compile TypeScript app to JS
gulp.task('compile:ts', function () {
  return gulp
    .src([
        "src/**/*.ts",
        "typings/*.d.ts"
    ])
    .pipe(sourcemaps.init())
    .pipe(typescript({
        "module": "system",
        "moduleResolution": "node",
        "outDir": "app",
        "target": "ES5"
    }))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('app'));
});

// Generate systemjs-based bundle (app/app.js)
gulp.task('bundle:app', function() {
  var builder = new systemjsBuilder('public', './system.config.js');
  return builder.buildStatic('app', 'app/app.js');
});

// Copy and bundle dependencies into one file (vendor/vendors.js)
// system.config.js can also bundled for convenience
gulp.task('bundle:vendor', function () {
    return gulp.src([
        'node_modules/jquery/dist/jquery.min.js',
        'node_modules/bootstrap/dist/js/bootstrap.min.js',
        'node_modules/es6-shim/es6-shim.min.js',
        'node_modules/es6-promise/dist/es6-promise.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system-polyfills.js',
        'node_modules/systemjs/dist/system.src.js',
      ])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('vendor'));
});

// Copy dependencies loaded through SystemJS into dir from node_modules
gulp.task('copy:vendor', function () {
  gulp.src(['node_modules/rxjs/**/*'])
    .pipe(gulp.dest('public/lib/js/rxjs'));

  gulp.src(['node_modules/angular2-in-memory-web-api/**/*'])
    .pipe(gulp.dest('public/lib/js/angular2-in-memory-web-api'));
  
  return gulp.src(['node_modules/@angular/**/*'])
    .pipe(gulp.dest('public/lib/js/@angular'));
});

gulp.task('vendor', ['bundle:vendor', 'copy:vendor']);
gulp.task('app', ['compile:ts', 'bundle:app']);

// Bundle dependencies and app into one file (app.bundle.js)
gulp.task('bundle', ['vendor', 'app'], function () {
    return gulp.src([
        'app/app.js',
        'vendor/vendors.js'
        ])
    .pipe(concat('app.bundle.js'))
    .pipe(uglify())
    .pipe(gulp.dest('./app'));
});

gulp.task('default', ['bundle']);

system.config.js

var map = {
  'app':                                'app',
  'rxjs':                               'vendor/rxjs',
  'zonejs':                             'vendor/zone.js',
  'reflect-metadata':                   'vendor/reflect-metadata',
  '@angular':                           'vendor/@angular'
};

var packages = {
  'app':                                { main: 'main', defaultExtension: 'js' },
  'rxjs':                               { defaultExtension: 'js' },
  'zonejs':                             { main: 'zone', defaultExtension: 'js' },
  'reflect-metadata':                   { main: 'Reflect', defaultExtension: 'js' }
};

var packageNames = [
  '@angular/common',
  '@angular/compiler',
  '@angular/core',
  '@angular/http',
  '@angular/platform-browser',
  '@angular/platform-browser-dynamic',
  '@angular/router',
  '@angular/router-deprecated',
  '@angular/testing',
  '@angular/upgrade',
];

packageNames.forEach(function(pkgName) {
  packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});

System.config({
  map: map,
  packages: packages
});

Steely
la source
2
Pourriez-vous s'il vous plaît préciser, comment exécuter SystemJs et Gulp?
Jan Drozen
@JanDrozen Dans le même emplacement que votre gulpfile, vous pouvez exécuter gulp <taskname>où "taskname" est le nom de la tâche qui appelle le générateur SystemJS, dans mon exemple ci-dessus bundle:app. Dans cette tâche Gulp, vous pouvez utiliser le module npm 'systemjs-builder' pour spécifier votre configuration système et votre tenue.
Steely
@ steely: Merci! Fonctionne comme un charme. Attendez-vous à la cible par défaut - la méthode uglify () est manquante (ou il me manque quelque chose). Pouvez-vous m'expliquer cette dernière partie peu claire?
Jan Drozen
@Steely pourriez-vous s'il vous plaît guider comment le faire avec la nouvelle version d'angular2?
micronyks
@ Steely.Pouvez-vous fournir un lien final (sur github) des derniers fichiers de construction angular2 qui sont nécessaires pour exécuter angular2 quickstart-app?
micronyks
1

Voici mon passe-partout MEA2N pour Angular 2: https://github.com/simonxca/mean2-boilerplate

C'est un simple passe-partout qui sert tscà assembler les choses. (Utilise en fait grunt-ts , qui à la base n'est que la tsccommande.) Aucun Wekpack, etc. n'est nécessaire.

Que vous utilisiez ou non grunt, l'idée est:

  • écrire votre application dans un dossier appelé ts/( par exemple: public/ts/)
  • utilisez tscpour refléter la structure de ts/répertoires de votre dossier dans un js/dossier et référencer uniquement les fichiers du js/dossier de votre index.html.

Pour faire fonctionner grunt-ts (il devrait y avoir une commande équivalente pour plain tsc, Gulp, etc.), vous avez une propriété dans votre tsconfig.jsonappelé "outDir": "../js", et la référencez dans votre gruntfile.jsavec:

grunt.initConfig({
  ts: {
    source: {tsconfig: 'app/ts/tsconfig.json'}
  },
  ...
});

Ensuite, exécutez grunt ts, ce qui prendra votre application public/ts/et la reflétera public/js/.

Là. Super facile à comprendre. Pas la meilleure approche, mais une bonne pour commencer.

Simonxca
la source
1

Le moyen le plus simple que j'ai trouvé pour regrouper angular rc1 pour systemJs est d'utiliser gulpet systemjs-builder:

gulp.task('bundle', function () {
    var path = require('path');
    var Builder = require('systemjs-builder');

    var builder = new Builder('/node_modules');

    return builder.bundle([
        '@angular/**/*.js'
        ], 
        'wwwroot/bundle.js', 
        { minify: false, sourceMaps: false })
        .then(function () {
            console.log('Build complete');
        })
        .catch(function (err) {
            console.log('Build error');
            console.log(err);
        });
});

Comme indiqué dans les commentaires, systemJs rencontre actuellement des problèmes lors du regroupement de composants à l'aide de moduleId: module.id

https://github.com/angular/angular/issues/6131

La recommandation actuelle (angulaire 2 rc1) semble être d'utiliser des chemins explicites ie moduleId: '/app/path/'

Paul
la source
Cela semble prometteur mais cela échoue lorsque j'utilise des chemins relatifs vers des modèles externes dans le @Componentdécorateur. bundle.jsessaie de résoudre les chemins comme absolus même s'ils sont relatifs, provoquant des erreurs 404 (voir stackoverflow.com/questions/37497635/… ). Comment avez-vous géré cela?
BeetleJuice
définissez-vous moduleIdle chemin relatif?
paul
Pas sûr que je comprenne. J'ai moduleId: module.iddans@Component
BeetleJuice
Cela présente les mêmes inconvénients que de mettre en place le chemin complet templateUrlet va à l'encontre de l'objectif d'avoir moduleIden premier lieu. J'essaie d'utiliser les chemins relatifs comme recommandé ( angular.io/docs/ts/latest/cookbook/… )
BeetleJuice
vous pouvez avoir plus de chance en définissant explicitement le chemin vous-même, par exemplemoduleId: '/app/components/home/'
paul
0

Sous le site Web Angular.io, dans la section Avancé / Déploiement, il est recommandé que le moyen le plus simple de déployer soit de «copier l'environnement de développement sur le serveur».

  1. passez par la section sous: Déploiement le plus simple possible. Les fichiers de projet finaux sont affichés dans la section code. Notez qu'il configure déjà le code pour charger les fichiers de package npm à partir du Web (au lieu du dossier local npm_modules).

  2. assurez-vous qu'il fonctionne sur votre ordinateur local (démarrage npm). Ensuite, sous le dossier du projet, copiez tout ce qui se trouve sous le sous-dossier '/ src' dans le compartiment S3 que vous avez configuré. Vous pouvez utiliser le glisser-déposer pour copier, pendant ce processus, vous avez la possibilité de sélectionner le paramètre d'autorisation pour les fichiers, assurez-vous de les rendre `` lisibles '' par `` tout le monde ''.

  3. sous l'onglet «Propriétés» du compartiment, recherchez le panneau «Hébergement de site Web statique», cochez l'option «Utiliser ce compartiment pour héberger le site Web» et spécifiez «index.html» à la fois pour le document d'index et le document d'erreur.

  4. cliquez sur le site statique Endpoint, votre projet tourne bien!

Joseph Wu
la source