Angular2 too many file requests on load

2020-01-24 11:11发布

问题:

I'm making a website using Angular2 and I'm having what i suppose is an issue. On the first load of my angular page, SystemJS is making more than 500 hundred requests to retrieve every Angular2 file in angular2/src directory. In total, the first load downloads more than 4MB and it takes more than 14 seconds to start.

My index.html does the following scripts includes:

<script src="libs/angular2/bundles/angular2-polyfills.js"></script>
<script src="libs/systemjs/dist/system.src.js"></script>
<script src="libs/rxjs/bundles/Rx.js"></script>
<script src="libs/angular2/bundles/angular2.min.js"></script>
<script src="libs/angular2/bundles/http.dev.js"></script>
<script src="libs/jquery/jquery.js"></script>
<script src="libs/lodash/lodash.js"></script>
<script src="libs/bootstrap/js/bootstrap.js"></script>

And my systemJs initialization code looks like this:

    <script>
      System.config({
        defaultJSExtensions: true,
        paths: {
          '*': 'libs/*',
          'app/*': 'app/*'
        },
        packageConfigPaths: ['libs/*/package.json'],
        packages: {
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));

    </script>

My public folder has the following structure:

.
├── img
├── styles
├── app
├── libs
|   └── angular2
|   └── systemjs
|   └── rxjs
|   └── jquery
|   └── lodash
|   └── bootstrap
└── index.html

A couple of screenshots of some of the js files that are being requested:

Is there a way to avoid all of those requests?

回答1:

I had the exact same problem, was actually looking at this post for an answer. Here is what I did to solve the problem.

  1. Modify your project to use webpack. Follow this short tutorial: Angular2 QuickStart SystemJS To Webpack
  2. This method will give you a single javascript file however it is quite large (my project file was over 5MB) and needs to be minified. To do this I installed webpack globaly:npm install webpack -g. Once installed, run webpack -p from your apps root directory. This brought my file size down to about 700KB

From 20 seconds and 350 requests down to 3 seconds and 7 requests.



回答2:

I see you already have a response, which is good of course. BUT for those who want to use systemjs (like I also do), and not go to webpack, you can still bundle the files. However, it does involve using another tool also (I use gulp). So... you would have the folowing systemjs config (not in the html, but in a separate file - let's call it "system.config.js"):

(function(global) {

    // map tells the System loader where to look for things
    var map = {
        'app':                        'dist/app', // this is where your transpiled files live
        'rxjs':                       'node_modules/rxjs',
        'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api', // this is something new since angular2 rc.0, don't know what it does
        '@angular':                   'node_modules/@angular'
    };

    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        'app':                        { main: 'boot.js',  defaultExtension: 'js' },
        'rxjs':                       { defaultExtension: 'js' },
        'angular2-in-memory-web-api': { defaultExtension: 'js' }
    };

    var packageNames = [
        '@angular/common',
        '@angular/compiler',
        '@angular/core',
        '@angular/http',
        '@angular/platform-browser',
        '@angular/platform-browser-dynamic',
        //'@angular/router', // I still use "router-deprecated", haven't yet modified my code to use the new router that came with rc.0
        '@angular/router-deprecated',
        '@angular/http',
        '@angular/testing',
        '@angular/upgrade'
    ];

    // add package entries for angular packages in the form '@angular/common': { main: 'index.js', defaultExtension: 'js' }
    packageNames.forEach(function(pkgName) {
        packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
    });

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

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

    System.config(config);
})(this);

Then, in your gulpfile.js you would build a bundle like this (using the info from system.config.js and tsconfig.json files):

var gulp = require('gulp'),
    path = require('path'),
    Builder = require('systemjs-builder'),
    ts = require('gulp-typescript'),
    sourcemaps  = require('gulp-sourcemaps');

var tsProject = ts.createProject('tsconfig.json');

var appDev = 'dev/app'; // where your ts files are, whatever the folder structure in this folder, it will be recreated in the below 'dist/app' folder
var appProd = 'dist/app';

/** first transpile your ts files */
gulp.task('ts', () => {
    return gulp.src(appDev + '/**/*.ts')
        .pipe(sourcemaps.init({
            loadMaps: true
        }))
        .pipe(ts(tsProject))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest(appProd));
});

/** then bundle */
gulp.task('bundle', function() {
    // optional constructor options
    // sets the baseURL and loads the configuration file
    var builder = new Builder('', 'dist/system.config.js');

    /*
       the parameters of the below buildStatic() method are:
           - your transcompiled application boot file (the one wich would contain the bootstrap(MyApp, [PROVIDERS]) function - in my case 'dist/app/boot.js'
           - the output (file into which it would output the bundled code)
           - options {}
    */
    return builder
        .buildStatic(appProd + '/boot.js', appProd + '/bundle.js', { minify: true, sourceMaps: true})
        .then(function() {
            console.log('Build complete');
        })
        .catch(function(err) {
            console.log('Build error');
            console.log(err);
        });
});

/** this runs the above in order. uses gulp4 */
gulp.task('build', gulp.series(['ts', 'bundle']));

So, when running "gulp build", you will get the "bundle.js" file with everything you need. Sure, you also need a few more packages for this gulp bundle task to work:

npm install --save-dev github:gulpjs/gulp#4.0 gulp-typescript gulp-sourcemaps path systemjs-builder

Also, make sure that in your tsconfig.json you have "module":"commonjs". Here is my tsconfig.json which is used in my 'ts' gulp task:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "removeComments": false,
        "noImplicitAny": false
    },
    "exclude": [
        "node_modules",
        "typings/main",
        "typings/main.d.ts"
    ]
}

Then, in your html file you only need to include this:

<!-- Polyfill(s) for older browsers -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script>

<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="dist/app/bundle.js"></script>

And that's it... I got from 600 requests, 4mb in about 5 seconds... to 20 requests, 1.4mb in 1.6 seconds (local development machine). But these 20 requests ~1.4mb in 1.6 seconds also include some other js and css that the admin theme came with plus a few html templates that get required on the first load, I prefer to use external templates - templateUrl: '', instead of inline ones, written in my component.ts file. Sure, for an app that would have millions of users, this still wouldn't be enough. Also server-side rendering for initial load and cache system should be implemented, I actually managed to do that with angular universal, but on Angular2 beta (took about 200-240 milliseconds to load the initial render of the same admin app that above takes 1.6 seconds - I know: WOW!). Now it's incompatible since Angular2 RC came out, but I'm sure the guys doing universal will get it up to speed soon, specially since ng-conf is coming up. Plus, they're also planing to make Angular Universal for PHP, ASP and a few other - right now it's only for Nodejs.

Edit: Actually, I've just found out that on NG-CONF they said Angular Universal already supports ASP (but it doesn't support Angular2 > beta.15 :)) ... but let's give them some time, RC just came out a few days ago



回答3:

I think that your question is related to this one:

  • My angular 2 app takes a long time to load for first time users, I need help to speed it up

To have something ready for production (and speed it up), you need to package it.

I mean transpiling all files into JavaScript ones and concat them the same way Angular2 does for example. This way you will have several modules contained into a single JS file. This way you will reduce the number of HTTP calls to load your application code into the browser.



回答4:

I found a simple solution, using browserify & uglifyjs on mgechev's angular2-seed repository

Here's my version:

pacakge.json:

{
...
  "scripts": {
      "build_prod": "npm run clean && npm run browserify",
      "clean": "del /S/Q public\\dist",
      "browserify": "browserify -s main  public/YourMainModule.js > public/dist/bundle.js && npm run minify",
      "minify": "uglifyjs public/dist/bundle.js --screw-ie8 --compress --mangle --output public/dist/bundle.min.js"
    },
...
  "devDependencies": {
      "browserify": "^13.0.1",    
      "typescript": "^1.9.0-dev.20160625-1.0",
      "typings": "1.0.4",
      "uglifyjs": "^2.4.10"
    }
}
  1. Build your project.
  2. Run: npm run build_prod It'll create bundle.js & bundle.min.js under public\dist directory.
  3. Edit your index.html file: Instead of running System.import('YourMainModule')... , add <script src="/dist/bundle.min.js"></script>


回答5:

On the first load of my angular page, systemjs is making more than 500 hundred requests to retrieve every angular2 file in angular2/src directory. In total, the first load downloads more than 4mb and it takes more than 14s to start.

The SystemJs workflows are fairly new and don't have enough research in them for best deployment.

Suggest going back to commonjs + webpack. More : https://basarat.gitbooks.io/typescript/content/docs/quick/browser.html

Here is an example : https://github.com/AngularClass/angular2-webpack-starter



回答6:

@FreeBird72 Your answer is awesome.

If you want to use SystemJS for development and speed up the production server like I do. Check this out.

NOTE: Only import the components that you use, DO NOT import from the whole package.

Eg: If you want to use Modal from ng2-bootstrap.

import {MODAL_DIRECTIVES} from "ng2-bootstrap/components/modal";

Instead of:

import {MODAL_DIRECTIVES} from "ng2-bootstrap/ng2-bootstrap";

This will import the modal component instead of the whole ng2-bootstrap

Then follow the answer from @FreeBird72

Add this package.json

{
  ...
  "scripts": {
    ...
    "prod": "npm run tsc && npm run browserify",
    "browserify": "browserify -s main  dist/main.js > dist/bundle.js && npm run minify",
    "minify": "uglifyjs dist/bundle.js --screw-ie8 --compress --mangle --output dist/bundle.min.js",
    ...
  },
  "devDependencies": {
    ...
    "browserify": "^13.0.1",    
    "uglifyjs": "^2.4.10",
    ...
  }
  ...
}

Then you can npm run tsc on development and npm run prod on production server Also remove System.import(.... from your index.html and change it to <script src="/dist/bundle.min.js"></script>



回答7:

If you want to stick with SystemJS, you can bundle your app with JSPM. I've had good success with this so far, using JSPM's bundle-sfx command to make single JS files for Angular 2 apps.

There's some useful information in this Gist, and there's a seed project.



回答8:

I am using AG2 RC version While using MrCroft's solution with systemjs-builder, i was hitting a lot of issues like: error TS2304: Cannot find name 'Map' error TS2304: Cannot find name 'Promise'...

After many tries, i added: ///<reference path="../../typings/index.d.ts" /> into my boot.ts and now I got my bundle file compiled.



回答9:

The Angular command line interface now supports bundling (with tree-shaking to strip out unused code from imports), minification, and ahead-of-time template compilation, which not only hugely minimises the number of requests made, but also makes the bundle very small. It uses WebPack underneath.

It's incredibly easy to make production builds with it:

ng build --prod --aot

https://github.com/angular/angular-cli