{"id":26387083,"url":"https://github.com/chenlong-io/angular-webpack","last_synced_at":"2025-03-17T08:29:22.376Z","repository":{"id":126915892,"uuid":"510182974","full_name":"chenlong-io/angular-webpack","owner":"chenlong-io","description":"Angualr项目有效优化：使用 webpack DLL、路由级 lazyload、组件级 lazyload、状态管理及共享","archived":false,"fork":false,"pushed_at":"2024-09-29T06:47:00.000Z","size":800,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-16T05:00:45.752Z","etag":null,"topics":["angular","webpack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chenlong-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-07-04T02:09:02.000Z","updated_at":"2024-04-22T06:44:12.000Z","dependencies_parsed_at":"2023-11-24T10:45:42.957Z","dependency_job_id":null,"html_url":"https://github.com/chenlong-io/angular-webpack","commit_stats":null,"previous_names":["chenlong-io/angular-webpack"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenlong-io%2Fangular-webpack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenlong-io%2Fangular-webpack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenlong-io%2Fangular-webpack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenlong-io%2Fangular-webpack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chenlong-io","download_url":"https://codeload.github.com/chenlong-io/angular-webpack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243999381,"owners_count":20381337,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","webpack"],"created_at":"2025-03-17T08:29:21.443Z","updated_at":"2025-03-17T08:29:22.338Z","avatar_url":"https://github.com/chenlong-io.png","language":"TypeScript","readme":"该库中主要对 Angular 项目做了一些优化：\n\n- 页面级代码分割\n- 组件级代码分割\n- 使用 webpack DLL\n- 使用 service 配合 rxjs 做状态管理及状态共享\n- 按需加载模块和组件\n\n### 页面级代码分割\n\n开发中经常一个 `module` 有很多组件，目前 Angular 的路由使用代码分割只支持以 module 为单位，这会有一个问题：一个 `module` 可能包含多个页面，当用户只访问`页面A`时，打包出来的 module 代码却包含没有被访问的其他页面。\n\n理想情况是让用户在访问每个路由时只 lazyLoad 当前页面的代码，而非整个 module 的代码，避免造成用户等待时间加长的体验问题、流量问题等。\n\n好消息是这个方案是可以实现的：Angular 内部支持动态组件的功能，可以通过该功能配合 `import()`方法达到组件级别的代码分割。\n\n但是！！！！\n\n虽然可以将组件进行懒加载，**但组件被懒加载时，其代码中并不包含所使用的外部引用的组件库代码，被引用的组件库代码全都在当前模块中**，这样会造成当前模块越来越大，最好把这些 UI 组件也分到对应的页面里，**否则打包出来的`main.js`越来越大**。\n\n**最好的解决方案就是：将每个路由对应的页面进行模块化**。也就是一个页面就是一个`module`，每个页面所使用的 UI 库组件可以在当前 `module` 里进行 `import`，再配合路由的 `loadChild` 和使用 `import()` 做代码分割，就能达到页面级的 `lazyLoad`\n\n![image-20220721143344437](https://tva1.sinaimg.cn/large/e6c9d24egy1h4ejd7aux7j20is07gq3d.jpg)\n\n上图中在 `dashboard`作为一个大模块，其中包含两个页面：`monitor`、`welcome`\n\n![image-20220721143604663](https://tva1.sinaimg.cn/large/e6c9d24egy1h4ejffny21j20hg0ewwfe.jpg)\n\n展开后 monitor 和 welcome 两个页面都模块化，这样就可以达到页面级代码分割。（具体的看 github 里的代码吧）\n\n### 组件级代码分割\n\n核心代码：\n\n```typescript\nimport { ChangeDetectionStrategy, Component, ComponentFactoryResolver, OnInit, ViewChild, ViewContainerRef } from \"@angular/core\";\n\n@Component({\n  selector: \"app-welcome\",\n  template: `\n    \u003cng-template #dynamicContent\u003e\u003c/ng-template\u003e\n    \u003cbutton (click)=\"loadComponent()\"\u003elazyLoad component\u003c/button\u003e\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class WelcomeComponent implements OnInit {\n  @ViewChild(\"dynamicContent\", { read: ViewContainerRef })\n  dyncomp!: ViewContainerRef;\n\n  constructor(private cfr: ComponentFactoryResolver) {}\n\n  async loadComponent() {\n    // lazyLoad组件\n    const { Comp1Component } = await import(/* webpackChunkName: \"comp1\" */ \"./comp1/comp1.component\");\n\n    // 将组件载入到ng-template中\n    const loadedComponent = this.dyncomp.createComponent(this.cfr.resolveComponentFactory(Comp1Component));\n  }\n}\n```\n\n### 使用 webpack + DLL\n\n前端优化点之一就是使用 webpack 的 DLL 来把 Angular 相关的库比如 rxjs、router 等缓存起来，因为这些库几乎不会跟着业务版本迭代去变化，除非公司要求升级 Angular 才会动它们。\n\n这些库每次都会被打包，每次打包后 hash 可能都会变，不仅让打包时间变长，而且用户要从新从远程拉取，导致页面等待时间长用户体验差。\n\n可以把这些库集中到一个 js 文件中，而且不会每次打包都变更这个 js 文件，这样打包时只打包业务代码，缩小打包时间，用户除了第一次访问时要拉取 dll ，以后再访问会直接从缓存里拉取，减少页面加载时间，提升用户体验。\n\n#### Angular 使用 webpack\n\nAngular 默认的配置对我们来说是黑盒子，要拓展 webpack 配置，要安装`@angular-builders/custom-webpack`，然后在 `angular.json`里配置：\n\n```ts\n ......\n    \"architect\": {\n        ......\n        \"build\": {\n            // 配置 builder 方式\n            \"builder\": \"@angular-builders/custom-webpack:browser\",\n            \"options\": {\n                // 配置要拓展的 webpack config\n                \"customWebpackConfig\": {\n                    \"path\": \"./webpack.extra.config.js\"\n                },\n                  //打包出来的 DLL 文件，需要在 angular.json 里引用\n                \"scripts\": [\n                  {\n                    \"input\": \"./dll/vendor.dll.js\",\n                    \"inject\": true,\n                    \"bundleName\": \"vendor_library\"\n                  }\n                ]\n            }\n        },\n        \"serve\": {\n            // 配置 builder 方式\n            \"builder\": \"@angular-builders/custom-webpack:dev-server\"\n        }\n    }\n```\n\n### 状态管理及共享\n\n使用 `service` 注入，配合 rxjs 做状态管理和共享\n\n先封装一个 `Store` 基类\n\n```ts\nimport { BehaviorSubject, Observable } from \"rxjs\";\nimport { distinctUntilChanged, map } from \"rxjs/operators\";\n\nexport class StateService\u003cT\u003e {\n  private state$: BehaviorSubject\u003cT\u003e;\n\n  get state(): T {\n    return this.state$.getValue();\n  }\n\n  constructor(initialState: T) {\n    this.state$ = new BehaviorSubject\u003cT\u003e(initialState);\n  }\n\n  select\u003cK\u003e(mapFn: (state: T) =\u003e K): Observable\u003cK\u003e {\n    return this.state$.asObservable().pipe(\n      map((state: T) =\u003e mapFn(state)),\n      distinctUntilChanged()\n    );\n  }\n\n  setState(newState: Partial\u003cT\u003e) {\n    this.state$.next({ ...this.state, ...newState });\n  }\n}\n```\n\n在继承该基类\n\n```ts\nimport { Injectable } from \"@angular/core\";\nimport { Observable } from \"rxjs\";\nimport { StateService } from \"@store\";\n\nconst initialState = {\n  title: \"here\",\n  message: \"hello\",\n  count: 0,\n};\n\n@Injectable()\nexport class PageStoreService extends StateService\u003cany\u003e {\n  store$: Observable\u003cany\u003e = this.select((state) =\u003e {\n    return state;\n  });\n\n  constructor() {\n    super(initialState);\n    this.store$.subscribe((state) =\u003e {\n      console.log(\"listening--\u003e\", state);\n    });\n  }\n\n  setStore(newState: any) {\n    this.setState(newState);\n  }\n}\n```\n\n在需要的地方进行依赖注入，比如`PageStoreService`是页面级的，所以在页面对应的`module`里引入\n\n```ts\n@NgModule({\n  declarations: [WelcomeComponent],\n  imports: [RouterModule.forChild(routes), CommonModule],\n  providers: [PageStoreService],\n})\nexport class WelcomeModule {}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenlong-io%2Fangular-webpack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchenlong-io%2Fangular-webpack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenlong-io%2Fangular-webpack/lists"}