使用 UMD 捆绑包捆绑 Angular 2 个应用程序(不构建供应商捆绑包)

Bundle Angular 2 app using UMD bundles (not building vendor bundle)

我目前正在将我的 Angular 2 应用程序与 WebPack 捆绑在一起。我们仍在快速循环,因此我们不想在构建和应用程序加载过程中增加延迟,而是希望包含很少更改的 Angular 2 UMD CDN 准备好的捆绑包,例如:

<script src="https://npmcdn.com/@angular/core@2.0.0-rc.3/bundles/core.umd.min.js"></script>
<script src="https://npmcdn.com/@angular/common@2.0.0-rc.3/bundles/common.umd.min.js"></script>
<script src="https://npmcdn.com/@angular/compiler@2.0.0-rc.3/bundles/compiler.umd.min.js"></script>

如何构建不依赖于唯一重新打包的应用程序包 Angular 2 包?

我目前正在针对 RC3 进行构建。如上所述,我的流程目前是 WebPack,但如果这样更容易,我会转向另一个工具集。

做更多的研究,我想我被 WebPack 的 "Loader" 术语误导了。我必须使用模块加载器,但 WebPack 似乎没有适合此功能的模块加载器。

要分配 UMD 包模块名称空间(并连接依赖项),它们不能加载到脚本标签中。相反,必须使用给定的 this 上下文对它们进行评估,以充当模块引用。这意味着即使我希望它们全部同步加载,我仍然必须配置其他类似 SystemJS 的东西来通过网络加载它们,所以它们的上下文是 controlled/wrapped.

这个 接近我要找的东西。它使用 Angular 2 个 UMD 包,但不使用 RxJs 包,尽管如果我想要整个 RxJs 库,这看起来很容易改变。

到目前为止,我一直在尝试实现相同的结果,但没有成功。

作为第一次尝试,我设法让 webpack 使用 resolve 配置加载预构建的 UMD 包,如下例所示:

var webpack = require('webpack');

module.exports = {
  entry: './src/main',
  output: {
    filename: 'bundle.js',
    path: 'bundles/app',
  },

  resolve: {
    extensions: ['', '.ts', '.js'],
    alias: {
        '@angular/common' : '@angular/common/common.umd.js',
        '@angular/compiler' : '@angular/compiler/compiler.umd.js',
        '@angular/core' : '@angular/core/core.umd.js',
        '@angular/http' : '@angular/http/http.umd.js',
        '@angular/platform-browser' : '@angular/platform-browser/platform-browser.umd.js',
        '@angular/platform-browser-dynamic' : '@angular/platform-browser-dynamic/platform-browser-dynamic.umd.js',
        '@angular/router' : '@angular/router/router.umd.js',
        '@angular/router-deprecated' : '@angular/router-deprecated/router-deprecated.umd.js',
        '@angular/upgrade' : '@angular/upgrade/upgrade.umd.js',
        'rxjs/Observable' : 'rxjs/bundles/Rx.umd.min.js',
        'rxjs/add/operator/map' : 'rxjs/bundles/Rx.umd.min.js',
        'rxjs/add/observable/fromEvent' : 'rxjs/bundles/Rx.umd.min.js',
        rxjs : 'rxjs/bundles/Rx.umd.min.js'
    }
  },

  module: {
    loaders: [
      {test: /\.ts$/, exclude: /node_modules/, loader: 'ts'},
    ],
    noParse: [/@angular/, /rxjs/]
  },

};

这里的关键配置是noParseresolve/alias。我告诉 webpack 在发现 @angular/common 等需求时加载 UMD 预构建包。

此配置确实使 webpack 包含 UMD 文件,而不是处理每个 angular 和 rxjs 文件。我的目标是缩短构建时间。但是,就我而言,我的应用程序在运行时失败了。我想我使用的库在其 src 文件夹中需要 angular 文件,这导致它失败了。我会尝试此设置以检查它是否适用于您的场景。

作为第二次尝试,我尝试了一个名为 Webpack DLL 的功能,如 http://engineering.invisionapp.com/post/optimizing-webpack/ 中所述。它创建了一个可以跨构建重复使用的包,从而避免每次都重新捆绑 angular2。不幸的是,在这种情况下,我的应用程序因依赖注入异常而失败。

我确实使用 SystemJS 而不是 Webpack 加载了 UMD 包,设置如下

(function(global) {

  var paths = {
    'jquery' : '../node_modules/jquery/dist/jquery.js',
    'bootstrap' : '../node_modules/bootstrap/dist/js/bootstrap.js',
    //'rxjs/*' : '../node_modules/rxjs/bundles/Rx.umd.min.js', //FAILS with router 3.0
  }

  var map = {
    'app':                        'app', // 'dist',
    'angular2-in-memory-web-api': '../node_modules/angular2-in-memory-web-api',
    '@angular':                   '../node_modules/@angular',
    'lodash':                     '../node_modules/lodash',
    'rxjs':                       '../node_modules/rxjs'    
  };

  var packages = {
    'app':                        { main: 'main.js',  defaultExtension: 'js' },
    'angular2-in-memory-web-api': { defaultExtension: 'js' },
    'lodash':                     { main: 'lodash.js', defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
  };

  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  ngPackageNames.forEach(function(pkgName) {
    packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
  });

  var config = {
    map: map,
    packages: packages,
    paths : paths,
  };

  // filterSystemConfig - index.html's chance to modify config before we register it.
  if (global.filterSystemConfig) { global.filterSystemConfig(config); }

  System.config(config);

})(this);

这是官方 Angular2 文档提出的添加 path 部分的设置。它曾经能够将 RxJS 作为单个 UMD 包加载,但随着最近引入的路由器 3.0,此设置失败,路由器代码出现异常。我不得不再次恢复并加载 RxJS 作为单独的请求。

我的问题中不止一种方法可行。由于 Angular 中的缺陷,有些不会,有些目前不会。这是我目前使用的方法:


WebPack + require.js

angular2-webpack-config.js

var config = {
    entry: {
        app: inputFile
    },
    externals: [
        /^@angular\//,
        /^rxjs\//
    ],
    output: {
        libraryTarget: "amd",
        path: __dirname,
        filename: './' + outputName
    },
    plugins: [
        new require('webpack').optimize.UglifyJsPlugin()
    ]
};

我只告诉它什么是externals以及什么伪标准机制会在运行时通过libraryTarget(AMD/RequireJS、CommonJs/node、UMD)加载它们。我的设置只是导致外部库引用被包裹在 define().

请注意,我没有对 WebPack 中的路径执行任何操作。在我的软件中,node_modules 文件夹中的任何内容在我的软件和第三方模块内部都有类似的引用机制。我的代码和第三方库都希望在 rxjs 处找到 RxJS(例如,而不是 ../rxjs 或 'node_modules/rxjs`)。在运行时,两者都需要映射,但由于我们不允许 WebPack 进入第三方模块(我们使用预构建的 UMD),WebPack 不是进行映射的地方。它只会映射我的代码。相反,我们应该在我们的运行时加载器中这样做:

index.htm

<script src="https://npmcdn.com/core-js/client/shim.min.js"></script>
<script src="https://npmcdn.com/zone.js@0.6.12/dist/zone.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reflect-metadata/0.1.3/Reflect.min.js"></script>
<script>
    var require = (function(){
        var versions = {
            'router-deprecated': '@@2.0.0-rc.2',
            'forms': '@@0.1.1',
            'angular': '@@2.0.0-rc.4',
            'rxjs': '@@5.0.0-beta.10'
        }

        var paths = {
            'rxjs': "https://npmcdn.com/rxjs" + versions.rxjs + "/bundles/Rx.umd.min"
        };
        [
            'core',
            'http',
            'common',
            'compiler',
            'platform-browser',
            'router-deprecated',
            'platform-browser-dynamic'
        ].forEach(function (submodule) {
            var module = '@@angular/' + submodule
            paths[module] = 'https://npmcdn.com/' + module + (versions[submodule] || versions.angular) + '/bundles/' + submodule + '.umd.min';
        });

        var rxmap = {};
        [
            'Rx',
            'Observable',
            'Subject',
            'observable/PromiseObservable',
            'operator/toPromise'
        ].forEach(function (submodule) {
            rxmap['rxjs/' + submodule] = 'rxjs';
        })

        return {
            paths: paths,
            map: {
                '*': rxmap
            }
        };
    })();
</script>
<script data-main="../assets/compiled/a2.webpack.js" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js"></script>

此外,因为如果您使用的是 WebPack 和 UMD,您可能会关心生成的文件大小和时间。此子应用程序的 Angular 2 构建过程从大约 24 秒减少到 1 秒。它的 publish changed-size 从超过 2MB 变成了大约 100k。

这里是缓存依赖项的线载大小以供参考。奇怪的是,它们目前在 UMD 版本中 比集成的 WebPack 修剪包的线路大小增加

 KB
27.5 shim
 6.8 zone
 8.0 require
 3.3 platform-browser-dynamic
36.8 http
 8.7 core
20.8 common
16.5 router
98.5 compiler
27.9 platform-browser
39.0 Rx

显然我的 public 网站加载时间在更新后大幅减少(从大约 10-20 秒减少到 1 秒),但这些数字因连接而异。