Angular 如何构建和运行
Posted
技术标签:
【中文标题】Angular 如何构建和运行【英文标题】:How Angular builds and runs 【发布时间】:2018-08-03 04:55:32 【问题描述】:只想了解 Angular 如何在幕后构建和运行?
以下是我目前所理解的。想知道我是否遗漏了什么。
Angular 的构建方式
使用 TypeScript 编写 Angular 应用程序后,我们使用 Angular CLI 命令构建应用程序。
ng build
命令将应用程序编译到输出目录中,构建工件将存储在dist/
目录中。
内部流程
1. Angular CLI 运行 Webpack 以构建和捆绑所有 javascript 和 CSS 代码。
2. Webpack 依次调用 TypeScript 加载器,该加载器获取 Angular 项目中的所有 .ts
文件,然后将它们转换为 JavaScript,即转换为浏览器可以理解的 .js
文件。
This 帖子说 Angular 有两个编译器:
查看编译器
模块编译器
关于构建的问题
调用构建过程的顺序是什么?
Angular CLI 首先调用用 TypeScript 编写的 Angular 内置编译器 => 然后调用 TypeScript Transpiler => 然后调用 Webpack 打包并存储在 dist/
目录中。
Angular 的运行方式
构建完成后,我们应用程序的所有组件、服务、模块等都被转换为JavaScript .js
文件,用于在浏览器中运行 Angular 应用程序。
Angular Docs中的声明
-
1234563 987654332@标签。
Angular 在用户浏览应用程序时创建、更新和销毁组件。
跑步问题
虽然main.ts
在上面的语句中用于解释引导过程,但Angular应用程序不是使用JavaScript .js
文件引导或启动的吗?
上面所有的语句不都是使用 JavaScript .js
文件在运行时完成的吗?
有谁知道所有部分是如何深入结合在一起的?
【问题讨论】:
【参考方案1】:Angular 是如何构建的?
Angular CLI
调用Webpack
,当Webpack
遇到.ts
文件时,它会将其传递给TypeScript
编译器,该编译器有一个输出转换器,用于编译Angular
模板
所以构建顺序是:
Angular CLI
=> Webpack
=> TypeScript
编译器 => TypeScript
编译器在编译时调用Angular
编译器。
Angular 如何运行?
Angular
使用Javascript
文件引导和运行。
实际上,引导过程是运行时的,发生在打开浏览器之前。这将我们带到下一个问题。
如果引导过程发生在Javascript
文件中,那么为什么Angular
Docs 使用main.ts
TypeScript 文件来解释引导过程?
Angular
Docs 只讨论.ts
文件,因为那是源文件。
这是简短的回答。如果有人能深入回答,不胜感激。
感谢 @Toxicable 在聊天中回答我的问题。
【讨论】:
【参考方案2】:(当我说 Angular 时,我的意思是 Angular 2+,如果我提到 Angular 1,我会明确地说 angular-js)。
前奏:令人困惑
Angular,也许更准确地说是 angular-cli,已经将构建过程中涉及的 Javascript 中的许多趋势工具合并在一起。这确实会导致一些混乱。
为了进一步混淆,在 angular-js 中经常使用术语compile
来指代获取模板的伪 html 并将其转换为 DOM 元素的过程。这是编译器所做的一部分,但只是较小的部分之一。
首先,不需要使用 TypeScript、angular-cli 或 Webpack 来运行 Angular。回答你的问题。我们应该看一个简单的问题:“什么是 Angular?”
Angular:它有什么作用?
这部分可能会引起争议,我们拭目以待。 Angular 提供的服务的核心是一种依赖注入机制,它可以跨 Javascript、HTML 和 CSS 工作。 您可以单独编写所有的小片段,并且在每个小片段中都遵循 Angular 的规则用于参考其他部分。然后 Angular 以某种方式完全编织了它。
要(稍微)更具体一些:
模板允许将 HTML 连接到 Javascript 组件中。这允许用户在 DOM 本身上的输入(例如单击按钮)输入到 Javascript 组件中,还允许 Javascript 组件中的变量控制 DOM 中的结构和值。 Javascript 类(包括 Javascript 组件)需要能够访问它们所依赖的其他 Javascript 类的实例(例如经典依赖注入)。 BookListComponent 需要 BookListService 的实例,而 BookListService 可能需要 BookListPolicy 或类似的实例。这些类中的每一个都有不同的生命周期(例如,服务通常是单例,组件通常不是单例),Angular 必须管理所有这些生命周期、组件的创建以及依赖项的连接。 CSS 规则需要以仅适用于 DOM 子集的方式加载(组件的样式是该组件的本地样式)。需要注意的重要一点是 Angular 不负责 Javascript 文件如何引用其他 Javascript 文件(例如 import
关键字)。这由 Webpack 负责。
编译器做了什么?
既然您知道 Angular 是做什么的,我们就可以谈谈编译器的作用。我会避免过于技术性,主要是因为我无知。但是,在依赖注入系统中,您通常必须使用某种元数据来表达您的依赖关系(例如,一个类如何表示 I can be injected
、My lifetime is blah
或 You can think of me as a Component type of instance
)。在 Java 中,Spring 最初是使用 XML 文件来实现的。 Java 后来采用了注解,它们已经成为表达元数据的首选方式。 C# 使用属性来表达元数据。
Javascript 没有一个很好的机制来公开这个内置的元数据。 angular-js 做了一个尝试,还不错,但是有很多规则不容易检查,有点混乱。使用 Angular 有两种支持的方式来指定元数据。您可以编写纯 Javascript 并手动指定元数据,有点类似于 angular-js,只需继续遵循规则并编写额外的样板代码。或者,您可以切换到 TypeScript,因为它恰好具有用于表达元数据的装饰器(那些 @
符号)。
所以这里是我们最终可以使用编译器的地方。编译器的工作是获取元数据并创建作为您的应用程序的工作系统。您专注于所有部分和所有元数据,编译器构建一个大型互连应用程序。
编译器是怎么做的?
编译器有两种工作方式,运行时和提前。从这里开始,我假设您使用的是 TypeScript:
运行时: 当 typescript 编译器运行时,它会获取所有装饰器信息并将其推送到附加到装饰类、方法和字段的 Javascript 代码中。在您的index.html
中,您引用了调用bootstrap
方法的main.js
。该方法传递给您的***模块。
bootstrap 方法会启动运行时编译器并为其提供对该***模块的引用。然后运行时编译器开始抓取该模块、该模块引用的所有服务、组件等,以及所有相关元数据,并构建您的应用程序。
AOT: Angular 提供了一种在构建时完成大部分工作的机制,而不是在运行时完成所有工作。这几乎总是使用a webpack plugin 完成(这一定是最流行但最不为人知的 npm 包之一)。它在 typescript 编译运行后运行,因此它看到的输入与运行时编译器基本相同。 AOT 编译器像运行时编译器一样构建您的应用程序,然后将其保存回 Javascript。这里的优势不仅在于您可以节省编译本身所需的 CPU 时间,还可以让您减小应用程序的大小。
具体答案
Angular CLI 首先调用 Angular 内置编译器编写的 Typescript => 然后调用 Typescript Transpiler => 然后调用 Webpack 打包并存储在 dist/ 目录中。
没有。 Angular CLI 调用 Webpack(Angular CLI 的真正服务是配置 webpack。当您运行 ng build
时,它只不过是启动 Webpack 的代理)。 Webpack 首先调用 Typescript 编译器,然后调用 Angular 编译器(假设为 AOT),同时打包你的代码。
尽管上面的语句中使用 main.ts 来解释引导程序 过程,角度应用程序不是使用 Javascript 引导或启动的吗 .js 文件?
我不完全确定你在这里问什么。 main.ts
将被转换成 Javascript。该 Javascript 将包含对 bootstrap
的调用,这是 Angular 的入口点。当bootstrap
完成后,您将运行完整的 Angular 应用程序。
这篇文章说 Angular 有两个编译器:
查看编译器
模块编译器
老实说,我只是在这里声称无知。我认为在我们的水平上,我们可以将其全部视为一个大型编译器。
有谁知道所有部分是如何深入结合在一起的?
我希望以上满足这一点。
别@我:Angular 不仅仅是依赖注入
当然。它执行路由、视图构建、更改检测和各种其他事情。编译器确实会生成用于视图构建和更改检测的 Javascript。当我说这只是依赖注入时,我撒了谎。但是,依赖注入是核心,足以驱动其余的答案。
我们应该称它为编译器吗?
它可能会进行大量的解析和词法分析,因此肯定会生成大量代码,因此您可以将其称为编译器。
另一方面,它并没有真正将您的代码翻译成不同的表示形式。取而代之的是,它采用了一堆不同的代码并将它们编织成更大系统的可消耗部分。然后引导过程(在编译之后,如果需要)获取这些部分并将它们插入到 Angular 核心中。
【讨论】:
感谢您的详细解答。在接受您的回答之前,我对您的声明The compiler does actually generate
Javascript` 对视图构建和更改检测有疑问。` 这不是谎言。这就是编译器所做的不是吗?并且 Angular 进行依赖注入。
是的,对不起。我所指的谎言是“Angular 提供的服务的核心是依赖注入机制”,因为虽然 Angular 做到了这一点,但它并不是它所做的一切,甚至不是编译器所做的全部。
如果 Angular 被抽象为一种具有组件、指令、服务等特性的新“语言”。它可以称为编译器。将 Angular 语言编译成原始 js 和 html。【参考方案3】:
所以如果引导过程发生在 Javascript 文件中,那么为什么 Angular Docs 使用 main.ts TypeScript 文件来解释引导过程 ?
这是 ng build
发出的 main.ts 的转换 .js 版本的一部分,它还没有被丑化和缩小,你希望初学者如何理解这段代码?看起来是不是很复杂?
Object(__WEBPACK_IMPORTED_MODULE_1__angular_platform_browser_dynamic__["a" /* platformBrowserDynamic */])().bootstrapModule(__WEBPACK_IMPORTED_MODULE_2__app_app_module__["a" /* AppModule */])
.catch(function (err) return console.log(err); );
使用 ng build --prod --build-optimizer
丑化和缩小代码以优化它,生成的包是紧凑的并且是位不可读的格式。
webpackJsonp([1],0:function(t,e,n)t.exports=n("cDNt"),"1j/l":function(t,e,n)"use strict";n.d(e,"a",function()return r);var r=Array.isArray||function(t)return t&&"number"==typeof t.length,"2kLc
而 main.ts 文件是人类可读且清晰的,这就是为什么 angular.io 使用 main.ts 来解释 Angular 应用程序的引导过程。Angular: Why TypeScript? 除此之外,如果您是这样一个伟大框架的作者为了使您的框架流行和用户友好,您会使用什么方法?您不会寻求清晰简洁的解释而不是复杂的解释吗?我同意 angular.io 文档缺乏深入的解释,而且它不是很好,但据我所知,他们正在努力让它变得更好。
【讨论】:
【参考方案4】:让我从头开始。
在我的应用程序中,我直接从Webpack
运行应用程序。
要构建和运行应用程序,我们使用 webpack --progress 和 webpack-dev-server --inline 命令,该命令已写在package.json
中,如下所示
"scripts":
"serve": "webpack-dev-server --inline ",
"build": "webpack --progress"
当我们运行webpack --progress
命令时,它开始读取webpack.config.js
文件,并在该文件中找到如下入口点。
module.exports =
devtool: 'source-map',
entry: './src/main.ts',
output:
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
,
module:
loaders: [
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader'],
exclude: [/\.(spec|e2e)\.ts$/]
,
/* Embed files. */
test: /\.(html|css)$/,
loader: 'raw-loader',
exclude: /\.async\.(html|css)$/
,
/* Async loading. */
test: /\.async\.(html|css)$/,
loaders: ['file?name=[name].[hash].[ext]', 'extract']
,
]
,
resolve:
extensions: ['.ts', '.js']
,
plugins: [
new HtmlWebpackPlugin(
template: './src/index.html'
)
]
然后它读取所有Typescript
文件并根据tsconfig.json
文件中声明的规则进行编译,然后将其转换为相应的.js
文件和映射文件。
如果它在没有任何编译错误的情况下运行,它将创建具有我们在 Webpack
输出部分中声明的名称的 bundle.js
文件。
现在让我解释一下为什么我们使用加载器。
awesome-typescript-loader, angular2-template-loader 我们使用这些加载器在 tsconfig.json
文件中声明的基础上编译 Typescript
文件,然后 angular2-template-loader 搜索templateUrl
和 styleUrls
声明位于 Angular 2 组件元数据中,并用相应的 require 语句替换路径。
resolve:
extensions: ['.ts', '.js']
我们使用上述解析部分告诉Webpack
将Typescript
转换为JavaScript
文件
plugins: [
new HtmlWebpackPlugin(
template: './src/index.html'
)
]
Plugins 部分用于注入第三方框架或文件。
在我的代码中,我使用它来注入目标文件夹的index.html
。
devtool: 'source-map',
上面一行用于在浏览器中查看Typescript
文件并对其进行调试,主要用于开发人员代码。
loader: 'raw-loader'
上面的raw-loader
用于加载.html
和.css
文件,并与Typescript
文件捆绑在一起。
最后,当我们运行 webpack-dev-server --inline 时,它会创建自己的服务器并以web-pack.config.js
文件中提到的路径启动应用程序,其中我们提到了目标文件夹和入口点。
在Angular
2 中大多数应用程序的入口点是main.ts
,我们提到了初始引导模块,例如(app.module),该模块包含完整的应用程序信息,例如所有指令、服务、模块、组件和路由实现整个应用程序。
注意:
许多人怀疑为什么index.html
只启动应用程序,即使他们没有提到任何地方。
答案是当Webpack
serve 命令运行时,它会创建自己的服务,如果您没有提及任何默认页面,默认情况下它会加载index.html
。
我希望所提供的信息对某些人有所帮助。
【讨论】:
感谢您尝试解释,如果您能以更清晰的顺序方式解释会更好。所以你不使用Angular CLI
来构建Angular
应用程序并直接使用Webpack
如何?【参考方案5】:
这个答案可能会迟到,但最近有一个关于这个话题的非常好的讨论,它从初学者的角度开始并深入。与其试图用我的话来总结或指出这个线程中的错误信息,我只会通过Kara Erickson: How Angular works 链接视频。
她是 Angular 框架的技术主管,并且在以下方面做了非常好的演示:
Angular 框架的主要部分是什么 编译器是如何工作的,它会产生什么 什么是“组件定义” 什么是应用引导程序,它是如何工作的【讨论】:
感谢您的贡献:),感谢。 主要关注om运行时进程,只简单涉及编译策略/进程。正好与 OP 的需求“相反”。【参考方案6】:Angular 9+ 使用 AOT(提前编译),这意味着它采用分散在各种文件中的所有位,即组件(.ts + .html + .css)、模块(.ts)并构建浏览器可理解的 JavaScript运行时由浏览器下载并执行。
在 Angular 9 之前,它是 JIT(即时编译),代码按照浏览器的要求进行编译。
详情见:Angular AOT Documentaiton
【讨论】:
以上是关于Angular 如何构建和运行的主要内容,如果未能解决你的问题,请参考以下文章
如何在生产构建设置和启用 AOT 的情况下运行 angular cli ng 测试