【问题标题】:How do I actually deploy an Angular 2 + Typescript + systemjs app?我如何实际部署 Angular 2 + Typescript + systemjs 应用程序?
【发布时间】:2026-01-26 07:10:01
【问题描述】:

在 angular.io 上有一个使用 typescript 和 systemjs 的快速入门教程。 现在我已经运行了那个小应用程序,我将如何创建可部署的东西?我找不到任何关于它的信息。

我是否需要任何额外的工具,System.config 中的任何其他设置?

(我知道我可以使用 webpack 并创建单个 bundle.js,但我想使用教程中使用的 systemjs)

有人可以用这个设置分享他们的构建过程吗(Angular 2、TypeScript、systemjs)

【问题讨论】:

标签: typescript angular systemjs


【解决方案1】:

这一层要理解的关键是,使用下面的配置,不能直接concat编译好的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 文件将包含匿名模块。匿名模块是一个使用 System.register 但没有模块名称作为第一个参数的 JS 文件。这是在将 systemjs 配置为模块管理器时 typescript 编译器默认生成的内容。

因此,要将所有模块放入一个 JS 文件中,您需要在 TypeScript 编译器配置中利用 outFile 属性。

您可以在 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 任务中使用了以下依赖项:

  • gulp-concat
  • gulp-html-替换
  • gulp 打字稿
  • gulp-丑化

以下是一个示例,因此可以进行调整。

  • 创建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'); 必须在正文末尾完成,以等待您的所有应用组件从 app.min.js 文件中注册。

我不在这里描述处理 CSS 和 HTML 缩小的方法。

【讨论】:

  • 你能用一个例子创建一个github repo吗?
  • 我按照您的指示进行操作,gulp 看起来一切正常。但是,当我在浏览器中运行该应用程序时,我收到此控制台日志错误:“system.src.js:1625 Uncaught TypeError: Multiple anonymous System.register calls in the same module file.”任何想法这意味着什么以及如何解决它?
  • @AngularM:你有 outFile 参数吗?这是您错误的关键;-)
  • 我的 gulp 文件和 tsconfig 中有它
  • 可以看看我提交的github项目吗?请参阅我上面的评论。你发现你的代码有什么不同吗?
【解决方案2】:

你可以使用angular2-cli构建命令

ng build -prod

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

通过ng build -prodng serve -prod 使用-prod 标志创建的构建将所有依赖项捆绑到一个单个文件,并利用tree-shaking 技术。

更新

这个答案是angular2在rc4时提交的

我在 angular-cli beta21 和 angular2 ^2.1.0 上再次尝试过,它按预期工作

这个答案需要使用 angular-cli 初始化应用程序 你可以使用

ng new myApp

或者在现有的上

ng init

2018 年 8 月 6 日更新

对于 Angular 6,语法不同。

ng build --prod --build-optimizer

查看documentation

【讨论】:

  • 这要求您的应用采用 angular-cli 自以为是的结构。
  • @Amr ElAdawy 仅供参考 angular-cli 已移至 webpack。这个问题与 SystemJS 有关。 ng build 不适合我。
  • @ShahriarHasanSayeed 你是指我提交答案的时间还是你尝试的时间?
  • @AmrElAdawy,您能否为实际工作的模块添加版本。自 7 月以来,Angular2 发生了很大变化。
  • 将《英雄之旅》教程转换为 cli 版本很简单。只需使用 cli 生成一个新项目,然后将教程文件复制过来。
【解决方案3】:

您可以使用带有GulpSystemJS-Builder 的SystemJS 在Typescript 中构建一个Angular 2 (2.0.0-rc.1) 项目。

以下是如何构建、捆绑和缩小运行 2.0.0-rc.1(full sourcelive example)的《英雄之旅》的简化版本。

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

【讨论】:

  • 能否请您具体说明,如何运行 SystemJs 和 Gulp?
  • @JanDrozen 在与 gulpfile 相同的位置,您可以运行 gulp &lt;taskname&gt;,其中“taskname”是调用 SystemJS 构建器的任务的名称,在我上面的示例中是 bundle:app。在那个 Gulp 任务中,您可以使用“systemjs-builder”npm 模块来指定您的系统配置和输出文件。
  • @steely:谢谢!奇迹般有效。期望默认目标 - 缺少 uglify() 方法(或者我缺少某些东西)。你能解释一下最后不清楚的部分吗?
  • @Steely 请指导如何使用较新版本的 angular2?
  • @Steely.你能否提供运行 angular2 quickstart-app 所需的最新 angular2 构建文件的最终链接(在 github 上)?
【解决方案4】:

这是 Angular 2 的 MEA2N 样板:https://github.com/simonxca/mean2-boilerplate

这是一个简单的样板文件,它使用tsc 将东西放在一起。 (实际上使用grunt-ts,其核心只是tsc 命令。)不需要Wekpack 等。

无论你是否使用 grunt,思路都是:

  • 将您的应用程序写入名为ts/ 的文件夹中(例如:public/ts/
  • 使用tscts/ 文件夹的目录结构镜像到js/ 文件夹中,并仅引用index.htmljs/ 文件夹中的文件。

要让 grunt-ts 工作(应该有一个普通 tsc、Gulp 等的等效命令),您的 tsconfig.json 中有一个名为 "outDir": "../js" 的属性,并参考它在你的gruntfile.js 中:

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

然后运行grunt ts,这会将您的应用程序放入public/ts/ 并将其镜像到public/js/

那里。超级容易理解。不是最好的方法,但却是一个很好的入门方法。

【讨论】:

    【解决方案5】:

    我发现为 systemJs 捆绑 angular rc1 的最简单方法是使用 gulpsystemjs-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);
            });
    });
    

    正如 cmets 中所指出的,systemJs 目前在使用 moduleId: module.id 捆绑组件时存在问题

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

    目前的建议(角度 2 rc1)似乎是使用显式路径,即moduleId: '/app/path/'

    【讨论】:

    • 这看起来很有希望,但是当我在 @Component 装饰器中使用外部模板的相对路径时它会失败。 bundle.js 尝试将路径解析为绝对路径,即使它们是相对的,也会导致 404 错误(请参阅 *.com/questions/37497635/…)。你是怎么处理的?
    • 您是否将moduleId 设置为相对路径?
    • 不确定我是否理解。我在@Component中有moduleId: module.id
    • 这与将完整路径放在templateUrl 下具有相同的缺点,并且首先破坏了拥有moduleId 的目的。我正在尝试按照推荐使用相对路径 (angular.io/docs/ts/latest/cookbook/…)
    • 您可以通过自己明确设置路径来获得更多运气,例如moduleId: '/app/components/home/'
    【解决方案6】:

    在 Angular.io 网站的 Advanced/Deployment 部分下,建议最简单的部署方式是“将开发环境复制到服务器”。

    1. 浏览以下部分:最简单的部署。最终的项目文件显示在代码部分中。请注意,它已经设置了从 web 加载 npm 包文件的代码(而不是从本地 npm_modules 文件夹)。

    2. 确保它正在您的本地计算机上运行 (npm start)。然后在项目文件夹下,将“/src”子文件夹下的所有内容复制到您设置的 S3 存储桶中。您可以使用拖放进行复制,在此过程中,您可以选择文件的权限设置,确保它们对“每个人”都“可读”。

    3. 在存储桶“属性”选项卡下,查找“静态网站托管”面板,选中“使用此存储桶托管网站”选项,并为索引文档和错误文档指定“index.html”。

    4. 点击静态网站Endpoint,你的项目运行良好!

    【讨论】: