Angular 应用打包后生成的 main.js

在我们运行一个 Angular 应用的时候,Chrome 开发者工具 Network 面板里会显示加载的文件和相关资源。其中一个重要的文件就是 main.js。这个文件对应用正常工作至关重要。

main.js 文件通常是 TypeScript 代码经过编译并打包后的结果。在 Angular 应用中,这个文件包含了应用的启动代码和一些核心逻辑,负责引导和初始化整个应用。为了更高效地进行说明,我们可以从以下几个方面来深入探讨 main.js 的内容和使用场景:这是从架构、内容、优化和实际实例出发的。

Angular 应用架构

在一个典型的 Angular 项目中,文件组织结构通常如下:

/src
  /app
    app.module.ts
    app.component.ts
    ...
  main.ts
  index.html
  ...
angular.json

main.ts 文件是 Angular 应用的入口文件之一。该文件引导了应用,调用了 Angular 的核心 API 来启动应用。最终在打包的时候,main.ts 和其他文件会被打包成 main.js,在浏览器中加载和执行。

main.js 的内容

main.js 包含了多种内容和逻辑,下面是其主要内容和职责:

  1. 引导模块 (Bootstrap Module)

    main.ts 中,我们通常会看到如下代码:

    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app/app.module';
    
    platformBrowserDynamic().bootstrapModule(AppModule)
      .catch(err => console.error(err));
    

    这段代码导入 AppModule 并使用 platformBrowserDynamic 帮助引导应用。在 main.js 中,这段代码会被转换为 JavaScript,进行引导操作。

  2. polyfills

    Angular 应用可以在不同的浏览器上运行,因此需要一些 polyfills 来保证兼容性。polyfills 的一些代码也会被包含在 main.js 中,确保应用在各种浏览器上正常运行。

  3. 编译后代码和依赖

    TypeScript 代码会被编译为 JavaScript,同时应用的所有依赖,包括 RxJS、Angular 核心库等,也会被打包在 main.js 中。举个具体的例子:

    import { of } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    const observable = of(1, 2, 3).pipe(
      map(x => x * 2)
    );
    
    observable.subscribe(value => console.log(value));
    

    这段代码在打包后会变成:

    "use strict";
    (self["webpackChunkmy_app"] = self["webpackChunkmy_app"] || []).push([["main"],{
    
    /***/ "./src/main.ts":
    /*!***********************!*\
      !*** ./src/main.ts ***!
      \***********************/
    (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
    
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! rxjs */ "./node_modules/rxjs/dist/esm5/index.js");
    /* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! rxjs/operators */ "./node_modules/rxjs/dist/esm5/operators/index.js");
    
    
    var observable = (0,rxjs__WEBPACK_IMPORTED_MODULE_0__.of)(1, 2, 3).pipe((0,rxjs_operators__WEBPACK_IMPORTED_MODULE_1__.map)(function(x) {
      return x * 2;
    }));
    observable.subscribe(function(value) {
      return console.log(value);
    });
    
    })
    
    },0]);
    

    这段代码就是 main.ts 文件中示例的编译和打包后的结果。

优化和压缩

为了提高性能,生产环境中的 main.js 文件会经过优化和压缩。例如,Angular CLI 会进行如下操作:

  • 树摇优化 (Tree Shaking)

    仅将实际使用到的代码打包到 main.js 中,通过删除未使用的模块来减少文件的大小。这种方法依赖于 ES6 模块的静态结构。

  • 代码分离 (Code Splitting)

    将代码分离为多个块(chunks),通过按需加载来减少初始加载时间。main.js 可能会只加载核心模块,而非核心部分则会在需要时异步加载。

  • 最小化 (Minification)

    使用工具如 Terser 来删除多余的空格、注释,并缩短变量名以减小文件大小。

  • 懒加载 (Lazy Loading)

    使用 Angular 路由的懒加载特性来按需加载模块,这样可以显著减少 main.js 的初始文件大小。

    例如,在 app-routing.module.ts 文件中,我们可以这样配置懒加载:

    const routes: Routes = [
      {
        path: 'feature',
        loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
      }
    ];
    

    这样只有在用户访问 feature 路由时,才会加载 FeatureModule

使用场景和实例

为了更加直观地描述,下面举一个具体的 Angular 应用实例:

假设我们有一个待办事项列表 (To-Do List) 应用,其中 main.ts 文件如下:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

AppModule 代码如下:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { TodoListComponent } from './todo-list/todo-list.component';

@NgModule({
  declarations: [
    AppComponent,
    TodoListComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

AppComponent 代码如下:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h1>To-Do List</h1>
             <app-todo-list></app-todo-list>`
})
export class AppComponent { }

TodoListComponent 代码如下:

import { Component } from '@angular/core';

@Component({
  selector: 'app-todo-list',
  template: `<ul>
               <li *ngFor="let todo of todos">{{ todo }}</li>
             </ul>`
})
export class TodoListComponent {
  todos = ['Buy milk', 'Do laundry', 'Finish project'];
}

在这种结构下,当我们运行应用时,main.ts 会引导 AppModuleAppModule 又包含了 AppComponentTodoListComponent。这些组件最终会被编译,打包在 main.js 中。

如果在 Network 面板中观察 main.js,我们会发现它包含了上述所有代码的编译后结果。这成为应用启动的核心文件。

深入理解 RxJS 编程

在 Angular 中,RxJS 是一个非常重要的库,用于处理异步编程逻辑。在 main.js 中,RxJS 的各类操作符和 Observable 也会被打包使用。通过 RxJS,我们可以实现复杂的异步数据流处理。例如:

import { Component, OnInit } from '@angular/core';
import { of } from 'rxjs';
import { delay, map, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos">{{ todo }}</li>
    </ul>
  `
})
export class TodoListComponent implements OnInit {
  todos: string[] = [];

  ngOnInit() {
    this.loadTodos().subscribe(todos => this.todos = todos);
  }

  loadTodos() {
    return of(['Buy milk', 'Do laundry', 'Finish project']).pipe(
      delay(2000), // 模拟异步请求
      map(todos => todos.map(todo => todo + ' - updated'))
    );
  }
}

在这个示例中,loadTodos 方法使用了 RxJS 的 ofdelaymap 操作符,模拟了一个异步请求并更新待办事项。这些 RxJS 代码在编译和打包后,也会成为 main.js 的一部分。

性能优化实例

为了进一步优化应用,我们可以开启 Angular 的生产构建模式并启用惰性加载。例如:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

angular.json 文件中,我们可以配置生产构建:

"configurations": {
  "production": {
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.prod.ts"
      }
    ],
    "optimization": true,
    "outputHashing": "all",
    "sourceMap": false,
    ...
  }
}

当我们运行 ng build --prod 时,Angular CLI 会进行优化构建,可能会生成优化后的 main.js,其大小可能大大减小。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容