介绍Angular 工程化,构建对象,编译器

介绍Angular 工程化,构建对象,编译器

这篇文章从宏观的角度认识Angular 的工程(project),构建对象(build target),编译器(compiler)。包括如下内容

  • 工程文件的主要构成
  • 构建对象的配置
  • 编译器的步骤与职责

Angular的工程(project)

一般而言,Angular的工作目录结构如下:

my-app |----      tsconfig.json      angular.json      package.json      projects      |-----            project1            project2      src      |-----            xxxx        

特指angular.json文件。下面看看这个工程文件的主要几个部分。先列出其中使用的github 链接。

  • 工程文件Schema 也就是$schema字段指定的值。用来描述Angular 工作空间的多项目的结构。其实就是当前angular.json 的约束。
  • configSchema这个Schema就是控制当我们工程里面的组件的风格,样式的类型scss/css 等,同时也会控制ng new 自动产生的组件的风格。

简单样例如下。

{   $schema: ./node_modules/@angular/cli/lib/config/schema.json,//schema   version: 1, // 你的程序的版本   newProjectRoot: projects, // 表示如果使用ng new 创建一个新的project,它的根目录在projects下面,可以任意指定   projects:{     project1:{},  // 第一个project     project2:{}   // 第二个project   },   defaultProject:project1,  // 默认是那个project   schematics:{}, // configSchema, 链接如上,控制组件风格的。   cli:{analytics: false} // 这个可以忽略,例如这个选项就是决定是否跟Angular Team 分析你项目的使用分析。 } 

我们再次深入看看某个project 的配置信息。

还是看下面的这个简单的例子,下面的配置是上面配置中project1的值。 先列出其中用到的一些链接

  • 这个JSON的schema,注意:不同的项目类型(projectType),(例如:application, library等),参考github里面不同的文件夹内的schema文件。
{   root:xxx,  // 表示这个project 的根目录。   sourcrRoot:xxx/xxx, // 这个表示这个project 的src 的目录。注意跟上面的区分, src 里面放置了main.ts app.module 等。   projectType:application,// 项目的类型,application  library 等。   prefix:,// 组件selector 的前缀。   schematics:{}, // 与上一节中的configSchema 一样。控制组件风格的。   architect:{},  // 这个是核心,会定义这个工程有哪些编译模式。下节细谈. } 

再来看看architect里面的配置信息

architect里面定义了各种各样的构建对象(build target),那么什么是build target 呢?最直接的认识,ng build,ng serve,这两个命令里面的build,serve就是构建对象。我们可以把它们理解成环境变量+构建工具的组合。
首先,Angular 保留了这些构建对象

{   architect: {   build: {},   serve: {},   e2e : {},   test: {},   lint: {},   extract-i18n: {},   server: {},   app-shell: {},   mybuildTarget:{}, //  这个是自定义的构建对象,任意名字。   } } 

我们也可以自己定义任意名字的构建对象,然后我们可以在package.json 中这么使用它 ng run mybuildTarget(这个我没有实验过),不过大部分情况下,我们只会用保留build Target。那么每一个build Target 拥有三个重要的字段 builder,options,configurations

  • builder 这个里面是清单,给两个例子

    • {builder:@angular-devkit/build-angular:browser} 这个构建对象代表编译生产库的网页应用
    • {builder:@angular-devkit/build-angular:dev-server} 这个构建对象代表本地开发的网页应用
    • {builder:@angular-devkit/build-angular:ng-packagr} 这个构建对象代表编译共享库
  • options 描述与对象的project 工程相关的属性,例如下面的例子

{   options:{// 配置选项       outputPath:'dist/xxx',       index:'xxx/src/index.html',       main:xxx/src/main.ts,       polyfills:'xxx/src/polyfill.ts',       tsConfig:'xxx/src/tsconfig.app.json',       assets:[assetPath1,assetPath2,...], // {glob:xxx,input:xxx,output:xxx},需要复制到dist目录的assets       styles:[stylePath1,stylePath2,...],// {glob:xxx,input:xxx,output:xxx},需要添加到index.html的样式文件,       scripts:[script1,script2,xxx],// 需要添加到index.html的js 文件。     } } 
  • configurations 则表示这个构建对象有哪些环境配置选项,例如Dev, Production, UAT, QA 等,它的内容与options类似,用于复写options 的内容。给个例子
{   configurations:{  // 典型的用法是 `ng build -c=production/development/qa/uat`       production:{},  // 生成环境的编译配置       development:{},  // 本地开发环境的配置       qa:{},   // 测试环境的配置       uat:{},  // uat测试环境的配置       demo:{         fileReplacements:[           {replace:'xxx/environment.ts','with':'xxx/environment.prod.ts'}  // 编译前替换文件         ],         //options 里面大部分的配置,这个地方都可以替换。       }     } } 

那么将上面的配置信息用起来就是

  • ng run buildTarget -c=configurationName或者99%的使用场景是
  • ng build -c=configurationName 使用configurationName来编译build 编译对象,也就是编译应用。
  • ng serve -c=configurationName 使用configurationName来编译serve 编译对象,也就是本地开发环境。

编译IVY

Angular 编译器模型与步骤

Create Programe(TS) ---> Analysis(Angular) ---> Resolve(Angular) ---> Type Checking(TS) ---> Emit (TS)
TS: TSC中的步骤

  • Create Programe, 从tsconfig.json 出发 递归解析文件,import modules,除了ts本身的,angular 一些文件导入,ngFactoryResolve 等。
  • Analysis 递归查找文件树中的所有类,找到Angular相关的类,组件等。并且以类为单元,尝试做一些独立的解析。不会去做类之间操作的事情,这一步可以理解为对类做一些元数据的解析生成。Isolate,这些类的解析完成后,下一步会利用他们做成软件结构的解析。
  • Resolve 这个阶段是从一个组件树的角度去理解依赖关系,做一些优化的策略
  • Type Checking 这一步会去检查有没有类型错误,以及Component 表达式中有没有类型错误。
  • Emit 这一步是最昂贵的一步,将typescript 转换成js. 产生Angular Component 类的产生js 代码。

编译器的模型(Model)与功能(Feature)

功能(Feature)

  • NgModule Scopes, Scope 中声明template 中组件查找的作用域 (decleration, exports)
  • Partial evaluation, import 中的内容可以是原生的Array, funtion等,对于一些其他的例如使用到document.body.scrollwitdh 的属性,可以在编译的时候跳过,等等
  • Template type-checking, 解析模板中的类型。

AOT(Ahead-Of-Time Compiler)

首先使用这个模式的好处与原因

  • 快速的渲染加载,由于Angular的框架代码已经是编译好摇树过的js,浏览器中直接执行,无需再编译
  • 更少的异步请求,IVY编译器通过inline的方式,集成了html template + css,减少了请求数量
  • 更小的框架包体积,IVY在编译阶段摇树的方式去除了不用的代码
  • 更早的检测到模板中的错误
  • 更好的安全性

Angular 提供了两者编译器 JIT(Just In Time) vs AOT(Ahead Of Time)

  • JIT: 在浏览器中的runtime中编译你的应用。Angular8 之前默认的编译器, 也叫ViewEngine

  • AOT: 在编译打包阶段编译应用, Angular9 开始的默认编译器,也叫IVY。
    Angular.json中的aot:true可以控制编译器的选择。

  • AOT 友好的代码

    • factoryfunction 必须是一个有名方法,不能是一个匿名的或者箭头函数
    • 模板中的变量不能是私有变量
    • 模板中的函数调用的前面必须要匹配
  • code Analysis (AOT collector)

    • AOT collector所认识的是js的一个子集. 也就是说在decorator(例如:@Component, @NgModule等) 中所以写的表达式就是js的一个子集。
    • 这个子集Expression syntax limitations
    • 不能使用箭头函数,必须使用exporeted named function.
    • 代码折叠(Code Folding),编译器引用exported symbols. 然而collector 可以执行部分表达式,将结果记录到meta.json中。这个过程叫做fold,也就是代码折叠。
  • code generation/Resolve

    • 编译器只能支持某些类的创建,支持核心的decorator,支持返回表达式的宏函数(静态函数或者函数)的调用
    • Metadata rewriting, 对于provider 里面的语义对象,编译器有特殊的逻辑,能够使用箭头函数,没有导出的对象,只能是某些特殊写法
  • Template type checking
    它是通过typescript 来实现模板里面的语法类型检测的。