首页 Angular Angular 学习之路 24 – 引导

Angular 学习之路 24 – 引导

0 0

前面我们已经完成了一个简单的演示项目,大致了解了 Angular 的开发流程。现在,我们要回过头来,看看 Angular 程序是如何从代码显示到浏览器中的。

引导就是把 Angular 程序初始化并且加载到浏览器显示的过程。

之前我们直接使用ng serve命令运行 Angular 程序,看不到编译后的结果。为了探究 Angular 的引导过程,我们需要使用ng build命令,把 Angular 项目编译打包。默认情况下,项目根目录会生成一个 dist 目录,即最终发布用的 Angular 程序。在接下来的内容中,我们都会使用 dist 目录生成的编译之后的文件。

总体来说,Angular 程序的引导过程大致分为一下几个步骤:

  1. HTML 入口点被加载,也就是 index.html 被加载
  2. JavaScript 包被加载,通常就是 Angular 本身、第三方依赖以及程序本身
  3. 程序自己的包被执行
  4. main.ts 作为入口点,需要加载 Angular,并且触发根模块的引导
  5. Angular 加载根模块,渲染整个应用程序

下面我们一步一步看,整个引导过程是怎样的。

首先,index.html 被浏览器下载到本地并且开始解析。经过编译的 index.html 内容一般如下:

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>TodomvcApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<style>body,html{margin:0;padding:0}body{-moz-osx-font-smoothing:grayscale}body{font:14px Helvetica Neue,Helvetica,Arial,sans-serif;line-height:1.4em;background:#f5f5f5;color:#111;min-width:230px;max-width:550px;margin:0 auto;-webkit-font-smoothing:antialiased;font-weight:300}</style><link rel="stylesheet" href="styles.4624dd4d7b2e8e969209.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.4624dd4d7b2e8e969209.css"></noscript></head>
<body>
  <app-root></app-root>
<script src="runtime.d5b1b50f788b857b859e.js" defer></script><script src="polyfills.4e2bd010f12e9f62f018.js" defer></script><script src="main.34c6a9a09f2b4bf274ee.js" defer></script>

</body></html>

注意这个文件和 src 目录下的 index.html 已经有所不同:Angular 编译之后会把生成的 js、css 等文件插入到 index.html 相应的位置。

HTML 文件的<body>标签必须包含一个根组件,作为组件的入口点;在这个例子中,就是<app-root>。只要 Angular 被正确加载并且运行,就可以识别到这个组件,然后使用组件对应的模板更新 DOM。

至此,Angular 应用程序就已经启动完毕了。下面,我们更深入一些。

回顾一下,当我们直接打开 src 目录下的 index.html 时,并不会看到任何 JS 文件,而前面所说的,直接打开编译之后的 index.html,则可以看到一堆 JS 文件的加载。这些文件都是在构建过程中被自动插入的。当我们使用 Angular CLI 构建时,会首先读取 angular.json 中的项目配置信息,然后基于此构建整个项目。

Angular CLI 底层使用 Webpack。这是一个模块打包器。简单来说,Webpack 在自己的大量插件的帮助下,把源代码文件和各种资源文件转换成浏览器可以执行的 JavaScript 代码包。这些代码包包含了项目执行所需的所有代码、各种依赖。当我们运行ng build命令之后,打开 dist 文件夹可以看到类似下面的文件:

其中,文件名中间的随机字符串部分是由 Webpack 自动生成的。

前面我们已经讨论过 index.html,接下来看看其它几个文件:

  • main.js:项目本身代码以及所有import的代码
  • vendor.js:第三方依赖库代码,如果没有第三方依赖,则没有这个文件
  • polyfills.js:允许新特性在旧平台运行的兼容性代码
  • runtime.js:Webpack 在运行时加载代码的工具类

这些文件都是由 Webpack 构建生成,并且自动插入到 index.html 中,正如前面我们所看到的那样。

另外,如果有懒加载模块,这里同样会生成对应的文件。这点我们会在后面的章节详细介绍。

这些文件都会被插入到 index.html,因此浏览器会把这些文件全部下载下来。但是,仅仅下载文件,并不能把项目运行起来。真正让项目运行起来,还需要执行这些文件所包含的代码。接下来,我们要了解,这部分代码是如何被执行的。

我们所关心的是 main.js,因为按照上面的说明,这里实际包含了项目本身的代码。但 Angular 究竟执行 main.js 的哪部分代码呢?这实际是在 angular.json 中配置的:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "cli": {
    "analytics": false
  },
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "todomvc-app": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/todomvc-app",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "todomvc-app:build:production"
            },
            "development": {
              "browserTarget": "todomvc-app:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "todomvc-app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  },
  "defaultProject": "todomvc-app"
}

注意这里的第 25 行,指定了 main.js 被加载之后,应该执行的代码文件,默认就是 main.ts。正是根据这一配置,Angular CLI 保证在代码包文件加载之后,Webpack 会立即加载并执行对应的模块。

现在,我们已经知道,main.ts 就是 Angular 项目的入口点。那么,接下来,我们来看看 main.ts 究竟干了些什么。

如果没有做任何修改,main.ts 应该是这样的:

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

首先,我们看到,这里有一些import语句。这部分语句在把 TypeScript 编译成 JavaScript 之后依然会保留。运行时,这部分import语句就会被 Webpack 加载并执行。只有这些导入语句执行完毕之后,剩下的代码才会被执行。

接下来,如果是生产环境,也就是environment.production被设置为true,会开启 Angular 的生产模式。这一步是为了提高性能。

最后,也是最重要的一步,

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

虽然只有一行代码,但这一行干了很多事情。platformBrowserDynamic()是一个模块,用于在 Web 浏览器环境中加载 Angular 上下文。这个模块会加载框架的必须元素和配置。通过不同的模块,Angular 可以在不同环境中运行。

因此,platformBrowserDynamic()用于加载 Angular。一旦完成,第二部分,bootstrapModule()通知 Angular 去引导指定的模块。默认情况下,所有的 Angular 项目都包含至少一个名为AppModule的模块。到这一步,Angular 会接管整个项目,加载组件、服务、指令、管道等等,所有可以在AppModule中导入、引用、声明的一切。如果一切正常的话,Angular 会找能够匹配app-root选择器的组件。通常这个组件就是AppComponent。一旦 Angular 找到了这个组件,就会把它渲染出来,同时更新 DOM。

至此,Angular 程序就在浏览器运行起来,整个引导过程结束。

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2