Jak właściwie wdrożyć aplikację Angular 2 + Typescript + systemjs?

103

Na angular.io znajduje się samouczek szybkiego startu, który używa maszynopisu i systemjs. Skoro mam już uruchomioną miniaplikację, jak powinienem zająć się tworzeniem czegoś do wdrożenia? Nie mogłem znaleźć żadnych informacji na ten temat.

Czy potrzebuję dodatkowych narzędzi, dodatkowych ustawień w System.config?

(Wiem, że mógłbym użyć webpacka i utworzyć pojedynczy plik bundle.js, ale chciałbym użyć systemjs, ponieważ jest używany w samouczku)

Czy ktoś mógłby udostępnić proces kompilacji w tej konfiguracji (Angular 2, TypeScript, systemjs)

Brian G. Bell
źródło
Oto mój przepis na zbudowanie aplikacji ng2 do wdrożenia przy użyciu JSPM: stackoverflow.com/a/34616199/3532945
brando
2
prosta odpowiedź na kompilację -prod stackoverflow.com/a/38421680/5079380
Amr ElAdawy

Odpowiedzi:

66

Kluczową rzeczą do zrozumienia na tym poziomie jest to, że używając następującej konfiguracji, nie można bezpośrednio łączyć skompilowanych plików JS.

W konfiguracji kompilatora 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"
  ]
}

W HTML

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

W rzeczywistości te pliki JS będą zawierać anonimowe moduły. Moduł anonimowy to plik JS, który używaSystem.register nazwy modułu jako pierwszego parametru, ale bez nazwy. To jest to, co kompilator TypeScript generuje domyślnie, gdy systemjs jest skonfigurowany jako menedżer modułów.

Aby mieć wszystkie swoje moduły w jednym pliku JS, musisz wykorzystać rozszerzenie outFile właściwość w konfiguracji kompilatora TypeScript.

Aby to zrobić, możesz użyć następującego wewnętrznego łyku:

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'));
});

Można to połączyć z innym przetwarzaniem:

  • aby uglifikować rzeczy skompilowane pliki TypeScript
  • aby utworzyć app.jsplik
  • aby utworzyć vendor.jsplik dla bibliotek innych firm
  • stworzyć boot.js plik w celu zaimportowania modułu ładującego aplikację. Ten plik musi znajdować się na końcu strony (gdy cała strona jest załadowana).
  • aby zaktualizować, index.htmlaby uwzględnić te dwa pliki

W zadaniach łykowych używane są następujące zależności:

  • gulp-concat
  • gulp-html-replace
  • maszynopis
  • gulp-uglify

Poniżej znajduje się próbka, która może zostać dostosowana.

  • Utwórz app.min.jsplik

    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'));
    });
  • Utwórz vendors.min.jsplik

    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'));
    });
  • Utwórz boot.min.jsplik

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

    config.prod.jsProstu zawiera następujące elementy:

     System.import('boot')
        .then(null, console.error.bind(console));
  • Zaktualizuj index.htmlplik

    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'));
    });

    Do index.htmlwygląda następująco:

    <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>

Zwróć uwagę, że System.import('boot');należy wykonać na końcu treści, aby poczekać na zarejestrowanie wszystkich składników aplikacji z plikuapp.min.js pliku.

Nie opisuję tutaj sposobu obsługi minifikacji CSS i HTML.

Thierry Templier
źródło
1
czy możesz utworzyć repozytorium na githubie z przykładem?
jdelobel
Postępowałem zgodnie z twoimi instrukcjami i wszystko wygląda dobrze, jeśli chodzi o łyk. Jednak po uruchomieniu aplikacji w przeglądarce pojawia się następujący błąd dziennika konsoli: „system.src.js: 1625 Uncaught TypeError: Wiele anonimowych wywołań System.register w tym samym pliku modułu”. Jakieś pomysły, co to oznacza i jak to naprawić?
AngularM
@AngularM: czy masz parametr outFile? To jest klucz do twojego błędu ;-)
Thierry Templier
Mam to w swoim pliku gulp i tsconfig
AngularM
Czy możesz rzucić okiem na zgłoszony przeze mnie projekt na githubie? Zobacz mój komentarz powyżej. Czy widzisz jakieś różnice w swoim kodzie?
Thierry Templier
28

Możesz użyć polecenia kompilacji angular2-cli

ng build -prod

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

Kompilacje utworzone z flagą -prod za pośrednictwem ng build -prodlub ng serve -prodpakują wszystkie zależności w jeden plik i wykorzystują techniki wstrząsania drzewem .

Aktualizacja

Ta odpowiedź została przesłana, gdy angular2 był w rc4

Wypróbowałem to ponownie na angular-cli beta21 i angular2 ^ 2.1.0 i działa zgodnie z oczekiwaniami

Ta odpowiedź wymaga zainicjowania aplikacji za pomocą angular-cli, którego możesz użyć

ng new myApp

Lub na już istniejącym

ng init

Aktualizacja 08.06.2018

W przypadku kątowej 6 składnia jest inna.

ng build --prod --build-optimizer

Sprawdź dokumentację

Amr ElAdawy
źródło
8
Wymaga to, aby Twoja aplikacja była zorganizowana w upartą strukturę angular-cli.
Michael Pell,
2
@Amr ElAdawy FYI angular-cli przeniósł się do webpacka. To pytanie dotyczy SystemJS. Kompilacja nie działa dla mnie.
Shahriar Hasan Sayeed
@ShahriarHasanSayeed Czy odnosisz się do czasu, kiedy przesłałem odpowiedź, czy też do czasu, kiedy ją wypróbowałeś?
Amr ElAdawy
@AmrElAdawy, czy możesz dodać wersje dla modułów, w których to faktycznie działa. Angular2 zmienił się nieco od lipca.
ppovoski
2
Konwersja samouczka Tour of Heroes do wersji CLI jest banalna. Po prostu wygeneruj nowy projekt za pomocą cli, a następnie skopiuj pliki samouczka.
Rosdi Kasim,
12

Możesz zbudować projekt Angular 2 (2.0.0-rc.1) w Typescript używając SystemJS with Gulp i SystemJS-Builder .

Poniżej znajduje się uproszczona wersja tego, jak budować, pakować i minimalizować Tour of Heroes w wersji 2.0.0-rc.1 ( pełne źródło , przykład na żywo ).

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
});

Stalowy
źródło
2
Czy mógłbyś określić, jak uruchomić SystemJs i Gulp?
Jan Drozen
@JanDrozen W tym samym miejscu, w którym znajduje się plik gulpfile, możesz uruchomić, gulp <taskname>gdzie „nazwa_zadania” jest nazwą zadania, które wywołuje program budujący SystemJS, w moim powyższym przykładzie tak jest bundle:app. W tym zadaniu Gulp możesz użyć modułu npm „systemjs-builder”, aby określić konfigurację systemu i plik wyjściowy.
Steely
@ steely: Dzięki! Działa jak marzenie. Spodziewaj się domyślnego celu - brakuje metody uglify () (lub czegoś mi brakuje). Czy możesz mi wyjaśnić tę ostatnią niejasną część?
Jan Drozen
@Steely, czy mógłbyś poprowadzić, jak to zrobić w nowszej wersji angular2?
micronyks
@ Steely. Czy możesz podać końcowe łącze (na github) do najnowszych plików kompilacji angular2, które są wymagane do uruchomienia aplikacji angular2 quickstart?
micronyks
1

Oto mój boilerplate MEA2N dla Angular 2: https://github.com/simonxca/mean2-boilerplate

To prosty szablon, który służy tscdo łączenia rzeczy. (Właściwie używa grunt-ts , co w tscgruncie rzeczy jest tylko poleceniem). Nie jest potrzebny Wekpack itp.

Niezależnie od tego, czy używasz pomruku, czy nie, idea jest taka:

  • napisz swoją aplikację w folderze o nazwie ts/(przykład:public/ts/ :)
  • użyj, tscaby odzwierciedlić strukturę katalogów twojego ts/folderu w js/folderze i po prostu odwołać się do plików w js/folderze w twoim index.html.

Aby uruchomić grunt-ts (powinno być równoważne polecenie dla zwykłego tsc, Gulp itp.), Masz właściwość w tsconfig.jsonwywołaniu "outDir": "../js"i odwołujesz się do niej w gruntfile.js:

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

Następnie uruchom grunt ts, co przeniesie twoją aplikację public/ts/i dubluje ją public/js/.

Tam. Bardzo łatwe do zrozumienia. Nie jest to najlepsze podejście, ale na początek dobre.

Simonxca
źródło
1

Najłatwiejszym sposobem, w jaki znalazłem połączenie kątowego rc1 dla systemJs, jest użycie gulpi 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);
        });
});

Jak wskazano w komentarzach, systemJs ma obecnie problemy z pakowaniem komponentów przy użyciu moduleId: module.id

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

Wydaje się, że obecne zalecenie (kąt 2 rc1) zakłada użycie jawnych ścieżek, tj moduleId: '/app/path/'

Paweł
źródło
Wydaje się to obiecujące, ale kończy się niepowodzeniem, gdy używam ścieżek względnych do zewnętrznych szablonów w @Componentdekoratorze. bundle.jspróbuje rozwiązać ścieżki jako bezwzględne, mimo że są względne, powodując błędy 404 (patrz stackoverflow.com/questions/37497635/… ). Jak sobie z tym poradziłeś?
BeetleJuice
czy ustawiasz moduleIdsię na ścieżkę względną?
paul
Nie wiem, czy rozumiem. Mam moduleId: module.idw@Component
BeetleJuice
Ma to te same wady, co porzucenie pełnej ścieżki templateUrli jest sprzeczne z celem posiadania moduleIdna pierwszym miejscu. Próbuję używać ścieżek względnych zgodnie z zaleceniami ( angular.io/docs/ts/latest/cookbook/… )
BeetleJuice
możesz mieć więcej szczęścia, samemu wyznaczając ścieżkę, np.moduleId: '/app/components/home/'
Paweł
0

W witrynie Angular.io, w sekcji Zaawansowane / Wdrażanie, zaleca się, aby najprostszym sposobem wdrożenia było „skopiowanie środowiska programistycznego na serwer”.

  1. przejdź przez sekcję pod: Najprostsze możliwe wdrożenie. Ostateczne pliki projektu są wyświetlane w sekcji kodu. Zauważ, że już skonfigurował kod do ładowania plików pakietów npm z sieci WWW (zamiast z lokalnego folderu npm_modules).

  2. upewnij się, że jest uruchomiony na komputerze lokalnym (npm start). Następnie w folderze projektu skopiuj wszystko z podfolderu „/ src” do skonfigurowanego wiadra S3. Możesz użyć przeciągania i upuszczania do kopiowania, podczas tego procesu masz możliwość wyboru ustawienia uprawnień dla plików, upewnij się, że są one „czytelne” dla „wszystkich”.

  3. na karcie „Właściwości” zasobnika poszukaj panelu „Statyczny hosting witryny”, zaznacz opcję „Użyj tego zasobnika do hostowania witryny” i określ „index.html” zarówno w dokumencie Indeks, jak i w dokumencie błędu.

  4. kliknij statyczną stronę internetową Endpoint, twój projekt będzie działał!

Joseph Wu
źródło