泵车专卖 發表於 2025-3-3 11:50:00

Angular CLI 源码分析

<blockquote>
<p>🔥 重磅推荐!这份文档 + 课程《Angular CLI 源码分析》= 王炸组合 💥 双倍知识,双倍快乐,带你起飞~ 🚀 (ง •_•)ง</p>
</blockquote>
<p>准备:</p>
<ol>
<li>安装 Node.js https://nodejs.org/;</li>
<li>安装 VS Code https://code.visualstudio.com/;</li>
<li>创建文件夹 angular-cli-source-learn;</li>
<li>安装 Angular CLI <code>npm install @angular/cli</code>https://www.npmjs.com/package/@angular/cli;</li>
</ol>
<blockquote>
<p>开发时一般全局安装 <code>npm install -g @angular/cli</code>,为了方便研究源码,使用本地安装。https://angular.dev/installation</p>
</blockquote>
<p>环境:</p>
<ol>
<li>Node.js 22.13.1;</li>
<li>Angular CLI 19.1.5。</li>
</ol>
<h2 id="一ng---help-源码分析">一、ng --help 源码分析</h2>
<p><strong>程序入口</strong></p>
<p><code>angular-cli-learn\node_modules\@angular\cli\bin\ng.js</code></p>
<p><strong>重要文件</strong></p>
<ol>
<li>node_modules@angular\cli\bin\ng.js</li>
<li>node_modules@angular\cli\bin\bootstrap.js</li>
<li>node_modules@angular\cli\lib\init.js</li>
<li>node_modules@angular\cli\lib\cli\index.js</li>
<li>node_modules@angular\cli\src\command-builder\command-runner.js</li>
<li>node_modules@angular\cli\src\command-builder\utilities\command.js</li>
<li>node_modules@angular\cli\src\commands\build\cli.js</li>
</ol>
<p><strong>.vscode/launch.json</strong></p>
<pre><code class="language-json">{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
      "&lt;node_internals&gt;/**"
      ],
      "program": "${workspaceFolder}\\node_modules\\@angular\\cli\\bin\\ng.js",
      "args": ["--help"]
    }
]
}
</code></pre>
<p><strong>逗号运算符</strong></p>
<p>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Comma_operator</p>
<p><strong>可选链运算符</strong></p>
<p>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining</p>
<p><strong>Yargs</strong></p>
<p>https://www.npmjs.com/package/yargs</p>
<p><code>yargs-demo.js</code></p>
<pre><code class="language-javascript">const yargs = require('yargs')

// const argv = yargs.parse()
// console.log(process.argv);
// console.log(argv);

yargs
.command({
    command: 'add',
    describe: '添加笔记',
    handler: function () {
      console.log('正在添加笔记……');
    }
})
.command({
    command: 'edit &lt;file&gt;',
    describe: '编辑笔记',
    handler: function (argv) {
      console.log('正在编辑笔记…… ' + argv.file);
    }
})
.scriptName('note')
.epilogue('欢迎使用笔记命令行工具。')
.parse()
</code></pre>
<p><code>node .\demo.js create student --name=zhouhuajian --age=18</code></p>
<p><code>ng --help --json-help</code></p>
<h2 id="二ng-new-源码分析">二、ng new 源码分析</h2>
<p><strong>重要文件</strong></p>
<ol>
<li>node_modules@angular\cli\src\command-builder\utilities\command.js</li>
<li>node_modules@angular\cli\src\command-builder\schematics-command-module.js</li>
<li>node_modules@angular\cli\src\command-builder\command-module.js</li>
<li>node_modules@angular-devkit\schematics\src\workflow\base.js</li>
<li>node_modules@schematics\angular\ng-new\index.js</li>
<li>node_modules@schematics\angular\workspace\index.js</li>
<li>node_modules@schematics\angular\application\index.js</li>
<li>node_modules@angular-devkit\schematics\tasks\package-manager\executor.js</li>
<li>angular-app2\node_modules@angular-devkit\schematics\tasks\repo-init\executor.js</li>
</ol>
<p><strong>创建工作区/项目</strong></p>
<p><code>ng new angular-app</code></p>
<blockquote>
<p>ng new                   Creates a new Angular workspace.</p>
</blockquote>
<p><strong>.vscode/launch.json</strong></p>
<pre><code class="language-json">{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
      "&lt;node_internals&gt;/**"
      ],
      "program": "${workspaceFolder}\\node_modules\\@angular\\cli\\bin\\ng.js",
      // "args": ["--help"]
      "args": ["new", "angular-app2"]
    }
]
}
</code></pre>
<p><strong>条件断点</strong></p>
<p>node_modules@angular\cli\src\command-builder\utilities\command.js</p>
<p>cmd.command == 'new '</p>
<p><strong>Angular Schematics</strong></p>
<p>https://www.npmjs.com/package/@angular-devkit/schematics</p>
<p>https://www.npmjs.com/package/@angular-devkit/schematics-cli</p>
<p>https://angular.dev/tools/cli/schematics</p>
<p>https://angular.dev/tools/cli/schematics-authoring</p>
<p><code>npm install -g @angular-devkit/schematics-cli</code></p>
<blockquote>
<p>本地安装,方便研究<code>npm install @angular-devkit/schematics-cli</code></p>
</blockquote>
<pre><code class="language-bash">schematics blank --name hello-world
cd hello-world
schematics blank --name hello-angular
# 修改项目名为 schematics-demo
cd ../schematics-demo
schematics blank --name hello-both
</code></pre>
<pre><code class="language-typescript">// schematics-demo\src\hello-world\index.ts
export function helloWorld(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) =&gt; {
    tree.create('hello-world', '123')
    return tree;
};
}
// schematics-demo\src\hello-angular\index.ts
export function helloAngular(_options: any): Rule {
return mergeWith(url('./files'));
}
// schematics-demo\src\hello-both\index.ts
export function helloBoth(_options: any): Rule {
// return chain()

// /hello-world.txt
// /hello-angular.txt
return chain([
    mergeWith(apply(empty(), ))
])
}
</code></pre>
<pre><code class="language-bash">npm run build
schematics .:hello-world
schematics .:hello-angular
# 删除 已生成的 两个文件
schematics .:hello-both
cd ..
schematics ./schematics-demo:hello-both
</code></pre>
<h2 id="三ng-generate-源码分析">三、ng generate 源码分析</h2>
<p><strong>重要文件</strong></p>
<ol>
<li>node_modules@angular\cli\lib\init.js</li>
<li>angular-app\node_modules@angular\cli\lib\cli\index.js</li>
<li>angular-app\node_modules@angular\cli\src\commands\generate\cli.js</li>
<li>angular-app\node_modules@schematics\angular\component\index.js</li>
<li>angular-app\node_modules@schematics\angular\component\files_<em>name@dasherize@if-flat</em>__<em>name@dasherize</em>_.<strong>type@dasherize</strong>.ts.template</li>
</ol>
<p><strong>.vscode/launch.json</strong></p>
<pre><code class="language-json">{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
      "&lt;node_internals&gt;/**"
      ],
      "program": "${workspaceFolder}\\node_modules\\@angular\\cli\\bin\\ng.js",
      "args": ["generate", "component", "home"],
      "cwd": "${workspaceFolder}\\angular-app"
    }
]
}
</code></pre>
<p><strong>Schematic</strong></p>
<p>schematics-demo\src\hello-somebody</p>
<pre><code class="language-plain">// 创建 hello-somebody
cd .\schematics-demo
schematics blank --name hello-somebody
// 运行 hello-somebody
cd ..
schematics ./schematics-demo:hello-somebody --name XiaoMing--flat --debug false

// schema.json
{
"properties": {
    "name": {
      "type": "string"
    },
    "flat": {
      "type": "boolean"
    }
}
}

// files/__name@dasherize@if-flat__/__name__.txt.template
&lt;%= name %&gt;
&lt;% if (name=='XiaoMing') {%&gt;Hello&lt;%}%&gt;
</code></pre>
<pre><code class="language-typescript">// index.ts
import { Rule, apply, applyTemplates, mergeWith, url, strings, move } from '@angular-devkit/schematics';

export function helloSomebody(_options: any): Rule {
// /__name@dasherize@if-flat__/__name__.txt.template
// 虚拟的树
// name: XiaoMing
// /__name__/__name__.txt.template
console.log(_options);

// /xiao-ming/XiaoMing.txt
// C:\Users\zhouhuajian\Desktop\angular-cli-source-learn/hello/somebody/XiaoMing.txt
let source = apply(url('./files'), [
    applyTemplates({
      ..._options,
      ...strings,
      'if-flat': (dirname: string) =&gt; (_options.flat ? '' : dirname)
    }),
    move("hello/somebody")
])
return mergeWith(source)
}
</code></pre>
<h2 id="四ng-build-源码分析">四、ng build 源码分析</h2>
<p><strong>重要文件</strong></p>
<ol>
<li>angular-app\node_modules@angular\cli\src\command-builder\architect-command-module.js</li>
<li>angular-app\node_modules@angular\cli\src\command-builder\architect-base-command-module.js</li>
<li>angular-app\node_modules@angular\build\src\builders\application\index.js</li>
<li>angular-app\node_modules@angular\build\src\builders\application\build-action.js</li>
<li>angular-app\node_modules@angular\build\src\builders\application\execute-build.js</li>
<li>angular-app\node_modules@angular\build\src\tools\esbuild\bundler-context.js</li>
</ol>
<p><strong>.vscode/launch.json</strong></p>
<pre><code class="language-json">{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
      "&lt;node_internals&gt;/**"
      ],
      "program": "${workspaceFolder}\\node_modules\\@angular\\cli\\bin\\ng.js",
      "args": ["build"],
      "cwd": "${workspaceFolder}\\angular-app"
    }
]
}
</code></pre>
<p><strong>Architect</strong></p>
<p>https://angular.dev/reference/configs/workspace-config#configuring-cli-builders</p>
<p><strong>esbuild 打包器</strong></p>
<p>https://esbuild.github.io/</p>
<p>angular-app\esbuild-demo\esbuild-demo.js</p>
<pre><code class="language-javascript">const esbuild = require('esbuild')

esbuild.build({
entryPoints: {
    main: "./src/main.ts"
},
outdir: "./dist",
bundle: true,
minifyIdentifiers: true,
minifySyntax: true,
minifyWhitespace: true,
entryNames: '-',
write: false
}).then(buildResult =&gt; {
console.log(buildResult);
console.log(buildResult.outputFiles.text);
})
</code></pre>
<p>angular-app\esbuild-demo\src\main.ts</p>
<p>angular-app\esbuild-demo\src\utils.ts</p>
<pre><code class="language-typescript">// main.ts
require('./utils').sayHello()
console.log(123);

// utils.ts
exports.sayHello = () =&gt; console.log("hello");
</code></pre>
<h2 id="五ng-serve-源码分析">五、ng serve 源码分析</h2>
<p><strong>重要文件</strong></p>
<ol>
<li>angular-app\node_modules@angular\cli\src\command-builder\architect-command-module.js</li>
<li>angular-app\node_modules@angular\cli\src\command-builder\architect-base-command-module.js</li>
<li>angular-app\node_modules@angular-devkit\build-angular\src\builders\dev-server\index.js</li>
<li>angular-app\node_modules@angular-devkit\build-angular\src\builders\dev-server\builder.js</li>
<li>angular-app\node_modules@angular\build\src\builders\dev-server\vite-server.js</li>
<li>angular-app\node_modules@angular\build\src\tools\vite\plugins\setup-middlewares-plugin.js</li>
<li>angular-app\node_modules@angular\build\src\tools\vite\middlewares\html-fallback-middleware.js</li>
<li>angular-app\node_modules@angular\build\src\tools\vite\middlewares\index-html-middleware.js</li>
</ol>
<p><strong>.vscode/launch.json</strong></p>
<pre><code class="language-json">{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": [
      "&lt;node_internals&gt;/**"
      ],
      "program": "${workspaceFolder}\\node_modules\\@angular\\cli\\bin\\ng.js",
      "args": ["serve"],
      "cwd": "${workspaceFolder}\\angular-app"
    }
]
}
</code></pre>
<p><strong>Vite 构建工具</strong></p>
<p>angular-app\vite-demo\vite-demo.js</p>
<pre><code class="language-typescript">import { createServer } from "vite";
const indexHtml = `&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
This is a home page in memory.
&lt;/body&gt;
&lt;/html&gt;`
const server = await createServer({
server: {
    host: 'localhost',
    port: 3000
},
root: './src',
plugins: [{
    name: 'plugin-demo',
    configureServer: async (server) =&gt; {
      // request response
      server.middlewares.use((req, res, next) =&gt; {
      if (req.url == '/') {
          req.url = '/index.html'
      }
      next()
      })
      server.middlewares.use((req, res, next) =&gt; {
      console.log(req.url) // http://localhost:3000/
      // if (req.url == '/index.html' || req.url == '/') {
      if (req.url == '/index.html') {
          res.statusCode = 200
          res.end(indexHtml)
      } else {
          res.statusCode = 404
          res.end()
      }
      // console.log(req.method)
      // res.write("Hello")
      // res.write(" World")
      // res.end("!")
      })

      return async () =&gt; {
      server.middlewares.use((req, res, next) =&gt; {

      })
      console.log(server.middlewares.stack);

      }
    }
}]
})
await server.listen() // http://localhost:3000/index.html
server.printUrls()
server.bindCLIShortcuts({
print: true
})
</code></pre>
<p>angular-app\vite-demo\src\index.html</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    This is a home page.
&lt;/body&gt;
&lt;/html&gt;
</code></pre><br><br>
来源:https://www.cnblogs.com/huajianketang/p/18747904
頁: [1]
查看完整版本: Angular CLI 源码分析