איך אני באמת מפרסם אפליקציה של Angular 2 + Typescript + systemjs?


יש מדריך התחלה מהירה באתר angular.io שמשתמש ב-Typescript ו-SystemJS. עכשיו כששידרגתי את המיני-אפליקציה הזו, איך אני יכול ליצור משהו שניתן לפרוס? לא הצלחתי למצוא שום מידע על זה אפילו.

האם אני צריך כלים נוספים כלשהם, הגדרות נוספות ב-System.config?

(אני יודע שאני יכול להשתמש ב-webpack וליצור קובץ bundle.js יחיד, אבל אני רוצה להשתמש ב-systemjs כפי שמשתמשים בו במדריך)

מישהו יכול לשתף את תהליך הבנייה שלו עם ההגדרה הזו (Angular 2, TypeScript, systemjs)

תשובה #1

הדבר החשוב להבין ברמה זו הוא שבעזרת התצורה הבאה, לא ניתן לצרף ישירות קבצי JS מקומפלים.

בתצורת קומפיילר 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"
  ]
}

ב-HTML

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

למעשה, קבצי JS אלו יכילו מודולים אנונימיים. מודול אנונימי הוא קובץ JS שמשתמש ב-System.register אך ללא שם המודול כפרמטר ראשון. זה מה שהקומפיילר של TypeScript מייצר כברירת מחדל כאשר SystemJS מוגדר כמנהל מודולים.

אז כדי לכלול את כל המודולים שלך בקובץ JS אחד, אתה צריך להשתמש בתכונה outFile בתוך קובץ התצורה של מהדר TypeScript שלך.

אתה יכול להשתמש בדבר הבא בתוך gulp כדי לעשות זאת:

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

זה יכול להיות משולב עם קצת עיבוד נוסף:

  • לפגום בדברים בקבצי TypeScript המונגשים
  • ליצור קובץ app.js
  • ליצור קובץ vendor.js עבור ספריות צד שלישי
  • ליצור קובץ boot.js לייבוא המודול שמאתחל את היישום. קובץ זה חייב להיות כלול בסוף הדף (כאשר כל הדף נטען).
  • לעדכן את index.html כדי לקחת בחשבון שני קבצים אלו

התלויות הבאות משמשות במשימות הגלפ:

  • גלפּ-קונקט
  • gulp-html-replace
  • גאלפ-טייפסקריפט
  • גלופ-אגליף

להלן דוגמה כך שניתן להתאים אותה.

  • צור קובץ app.min.js

    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'));
    });
    
  • צור קובץ vendors.min.js

    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'));
    });
    
  • צור קובץ boot.min.js

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

    הקובץ config.prod.js מכיל פשוט את הדברים הבאים:

     System.import('boot')
        .then(null, console.error.bind(console));
    
  • עדכן את הקובץ index.html

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

    הקובץ index.html נראה כך:

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

שים לב שה-System.import('boot'); חייב להתבצע בסוף ה-body כדי להמתין שכל רכיבי האפליקציה שלך יירשמו מהקובץ app.min.js.

אני לא מתאר כאן את הדרך לטפל במיניפיקציה של CSS ו-HTML.

תשובה #2

אתה יכול להשתמש בפקודה angular2-cli build

ng build -prod

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

בניינים שנוצרו עם הדגל -prod דרך ng build -prod או ng serve -prod אוספים את כל התלויות ל-קובץ יחיד, ומשתמשים בטכניקות tree-shaking.

עדכון

תשובה זו נשלחה כאשר Angular2 היה ב-rc4

ניסיתי את זה שוב על angular-cli beta21 ו-angular2 ^2.1.0 וזה עובד כפי שציפיתי

תשובה זו מחייבת אתחול של האפליקציה עם angular-cli שניתן להשתמש בו

ng new myApp

או על אחד קיים

ng init

עדכון 08/06/2018

ל-angular 6 התחביר שונה.

ng build --prod --build-optimizer

בדוק את התיעוד

תשובה #3

אתה יכול לבנות פרויקט Angular 2 (2.0.0-rc.1) ב-Typescript תוך שימוש ב-SystemJS עם Gulp ו-SystemJS-Builder.

להלן גרסה מפושטת של איך לבנות, לארוז ולמזער את Tour of Heroes שרץ ב־2.0.0-rc.1 (קוד מלא, דוגמה חיה).

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