我如何实际部署 Angular 2 + Typescript + systemjs 应用程序?

Posted

技术标签:

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

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

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

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

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

【问题讨论】:

这是我使用 JSPM 构建用于部署的 ng2 应用程序的秘诀:***.com/a/34616199/3532945 简单回答 ng build -prod ***.com/a/38421680/5079380 【参考方案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】:

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

以下是如何构建、捆绑和缩小运行 2.0.0-rc.1(full source、live 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 部分下,建议最简单的部署方式是“将开发环境复制到服务器”。

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

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

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

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

【讨论】:

以上是关于我如何实际部署 Angular 2 + Typescript + systemjs 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何部署我的 Angular 2 + Typescript + Webpack 应用程序

Typings 与 @types NPM 范围

使用 @types/angular 作为全局

在 Angular 5 中,如何导入没有 @types 的 NPM 模块

Angular 1.x 与 TypeScript 2.x、@types 和 SystemJS - 使用全局类型

如何避免 @types 软件包引入的问题,补丁级别增加会引入重大更改?