使用Angular CLI时的6个最佳实践和专业技巧

使用Angular CLI开发angular应用程序是一种很是愉快的体验!Angular团队为咱们提供了使人惊叹的CLI,它支持了任何重要项目开箱即用所需的大部分东西。javascript

规范化的项目结构与全面的测试能力(包括单元测试和端到端测试),脚手架,支持使用特定的环境配置去构建产品。这在构建每个新项目时候节约了大量时间。感谢Angular团队!?css

虽然Angular CLI的工做的很好,但咱们能够利用一些潜在的配置和最佳实践使咱们的项目更好!前端

咱们将要学习什么?

  1. 具备Core(核心),Shared(共享),lazy-loaded Feature modules(延迟加载功能模块)体系结构的最佳实践
  2. 为app和environments文件夹使用别名来支持更干净的导入
  3. 为何和如何使用Sass ,Angular Material
  4. 如何组织好的产品构建方式
  5. 如何向PhantomJS说再见以及使用Headless Chrome来替代(测试)
  6. 如何发布咱们的项目经过自动生成更新日志和正确的版本号

1. 关于项目的的文件结构

好的, 咱们使用Angular CLI生成了新的项目,可是如今呢?咱们应该继续在一些随机文件夹生成咱们的服务和组件吗?咱们应该如何组织咱们的项目?java

一个好的指导原则应该是遵循将应用程序分红至少三个不一样的模块: Core(核心模块), Shared(共享模块) 和 Feature(功能模块) (不过咱们可能须要更多的功能模块。?).git

核心模块

全部服务都应该在核心模块实现。典型的例子好比认证服务或用户服务。让咱们看个例子。github

/* 3rd party libraries */
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

/* our own custom services  */
import { SomeSingletonService } from './some-singleton/some-singleton.service';

@NgModule({
  imports: [
    /* 3rd party libraries */
    CommonModule,
    HttpClientModule,
  ],
  declarations: [],
  providers: [
    /* our own custom services  */
    SomeSingletonService
  ]
})
export class CoreModule {
  /* make sure CoreModule is imported only by one NgModule the AppModule */
  constructor (
    @Optional() @SkipSelf() parentModule: CoreModule
  ) {
    if (parentModule) {
      throw new Error('CoreModule is already loaded. Import only in AppModule');
    }
  }
}

共享模块

全部的“dumb(哑)”组件和管道都应该在这里实现。这些组件不能从核心模块或其余特性模块的构造函数中的导入和注入服务。它们应该使用组件的模板中的属性接收全部数据。这一切都归结到这一事实,SharedModule(共享模块)没有任何依赖于咱们的应用程序的其他部分。web

这也是导入和从新导入Angular Material 组件库的完美场所。chrome

/* 3rd party libraries */
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule  } from '@angular/forms';
import { MdButtonModule } from '@angular/material';

/* our own custom components */
import { SomeCustomComponent } from './some-custom/some-custom.component';

@NgModule({
  imports: [
    /* angular stuff */
    CommonModule,
    FormsModule,

    /* 3rd party components */
    MdButtonModule,
  ],
  declarations: [
    SomeCustomComponent
  ],
  exports: [
    /* angular stuff */
    CommonModule,
    FormsModule,

    /* 3rd party components */
    MdButtonModule,

    /* our own custom components */
    SomeCustomComponent
  ]
})
export class SharedModule { }

如何使用Angular CLI编写项目结构

咱们能够在建立新项目后当即生成核心和共享模块。这样,咱们就能够从一开始就准备生成额外的组件和服务。npm

运行命令ng generate module core生成核心模块。而后在core文件夹建立index.ts文件,再从新导出CoreModule。咱们将从新导出额外的公共服务,这些服务在整个开发过程当中均可以使用。 json

完成后,咱们能够对shared module(共享模块)执行一样的操做。

功能模块

咱们将为应用程序的每个独立特性建立多个功能模块。Feature modules(功能模块)应该只能从CoreModule导入服务。若是功能模块A须要从功能模块B导入服务,能够考虑将该服务迁移到CoreModule。

在某些状况下,须要只是某些功能模块共享的服务,将它们移动到核心是没有意义的。在这种状况下,咱们能够建立特殊的共享功能模块,如本文后面所述。

经验法则是: 咱们建立的功能模块尽可能不依赖其余功能模块,仅仅服务由CoreModule提供,组件由SharedModule提供

这将保持咱们的代码干净,易于维护和扩展的新功能。它还减小了重构所需的工做量。若是遵循得当,咱们将确信对一个功能的更改不会影响或破坏咱们的应用程序的其他部分。

延迟加载

咱们应该尽量延迟加载咱们的功能模块。理论上,只有一个功能模块应该在应用程序启动时同步加载以显示初始内容。每一个其余功能模块应该在用户触发导航后缓慢加载。

2. app 和 environments 的别名使用

咱们的app 和 environments文件夹使用别名将使咱们可以实现干净的导入,在咱们的应用程序这将是一致的。

假设,但一般状况。咱们正在研究一个组件,它位于功能模块A中的三个文件夹深处,咱们但愿从核心模块中导入位于两个文件夹深处的服务。这将致使导入声明看起来有点像import { SomeService } from '../../../core/subpackage1/subpackage2/some.service'

绝对不是最干净的导入申明…

更糟糕的是,每当咱们想改变这两个文件中任何一个的位置时,咱们的导入语句都会中断。相比之下,更短的导入申明import { SomeService } from "@app/core"。看起来更好,不是吗?

可以使用别名必须添加URL地址和路径属性,咱们tsconfig.json文件像这样…

{
  "compilerOptions": {
    "...": "reduced for brevity",
    
    "baseUrl": "src",
    "paths": {
      "@app/*": ["app/*"],
      "@env/*": ["environments/*"]
    }
  }
}

咱们还添加了@env别名,以便可以从import { environment } from "@env/environment"中使用同一个导入申明轻松地从应用程序的任何地方访问环境变量。它将为全部指定的environments工做,由于它将根据传递给ng build命令的--env标志自动解析正确的环境配置文件。

经过咱们的路径,咱们如今能够像这样导入environment 和 services …

/* 3rd party libraries */
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

/* globally accessible app code (in every feature module) */
import { SomeSingletonService } from '@app/core';
import { environment } from '@env/environment';

/* localy accessible feature module code, always use relative path */
import { ExampleService } from './example.service';

@Component({
  /* ... */
})
export class ExampleComponent implements OnInit {
  constructor(
    private someSingletonService: SomeSingletonService,
    private exampleService: ExampleService
  ) {}
}

在上面的例子中,你也许注意到咱们直接导入服务SomeSingletonService是经过@app/core,代替了@app/core/some-package/some-singleton.service。这得感谢核心模块的入口文件index.ts从新导出了每个公共实体。咱们在每个包(文件夹)都建立了一个文件index.ts,它看起来像这样...

export * from './core.module';
export * from './auth/auth.service';
export * from './user/user.service';
export * from './some-singleton-service/some-singleton.service';

在大多数的应用程序中,特殊功能模块的组件和服务的一般只须要访问服务的 CoreModule 和组件的SharedModule。有时这可能不足以解决特定的业务状况,咱们还须要某种“shared feature module(共享功能模块)”,它为其余功能模块的有限子集提供功能。

在这种状况下,咱们将获得相似的东西import { SomeService } from '@app/shared-feature';。和核心模块同样,共享功能模块使用别名@app进行访问。

图片描述

3. 使用SASS

SASS是一个CSS的预处理器,支持有趣的东西,像variables(尽管CSS将要实现变量),functions,mixin等等。

SASS在Angular Material Components官方库的多主题中进行有效的使用。项目中默认选择使用SASS能够假设是安全的。

为了使用SASS咱们在使用Angular CLI生成咱们的项目时候必须用命令ng new command --style scss 。这就设置了大部分必须的配置。默认状况下不添加stylePreprocessorOptions includePaths,咱们能够本身设置成根目录 "./" 和 可选的 "./themes"。

{
  "apps": [
    {
      "...": "reduced for brevity",
      
      "stylePreprocessorOptions": {
        "includePaths": ["./", "./themes"]
      }
    }
  ]
}

这有助于咱们的编辑器找到导入标志,而且经过Angular Material的变量和工具函数的代码完成来加强开发人员的体验。

When theming Angular Material apps it’s a good practice to extract theme definitions into separate themes folder, one theme per file.

4. 应用产品构建

Angular CLI声称的项目只提供了一个很是简单开箱即用的构建脚本ng build。为了生成产品级的工件,咱们必须本身作一些定制。

咱们在package.json中添加脚本"build:prod": "ng build --target production --build-optimizer --vendor-chunk"

Target Production

这是一个标志能使代码压缩,以及还有不少默认的有用的构建标志。使用以下:

  • --environment prod —使用 environment.prod.ts 文件设置环境变量
  • --aot —预编译,提早编译. 这将在将来的Angular CLI是默认设置,可是如今咱们必须手动启动。
  • --output-hashing all — 将生成的文件的散列内容添加到文件名中,以方便浏览器缓存破坏(文件内容的任何更改都会致使不一样的哈希值,所以浏览器被迫加载新版本的文件)
  • --extract-css true — 将全部的css提取到到单独的样式表文件
  • --sourcemaps false — 禁用source maps的生成
  • --named-chunks false — 禁用使用可读的名字,用数字替代

Other useful flags

  • --build-optimizer — 新的功能,致使更小的捆绑,但更长的构建时间,因此慎用!(未来也应该默认启用)
  • --vendor-chunk — 将全部第三方(库)代码提取到单独的块中

官方文档检查其余有用的配置项,也许在我的项目中用得上。

5. Phantom JS 死了! Headless Chrome 永存!

PhantomJS是一个很是著名的无头的浏览器。这是事实上的解决方案,在CI服务器和许多开发机器上运行前端测试。

虽然有好的,这是现代ECMAScript支持滞后。更重要的是,它是不规范的行为,在许多状况下致使头痛,当测试经过本地没有问题,但他们仍然在CI环境出现问题。

幸运的是,咱们再也不须要处理它了!

图片描述

就像官方文档说的同样…

Headless Chrome 是在Chrome 59上运行。这是在Headless环境下运行Chrome浏览器的一种方式。本质上,运行没有Chrome的Chrome!它将Chrome和闪烁渲染引擎提供的全部现代Web平台特性带到命令行。

很棒,那咱们在Angular CLI上使用它呢?

咱们在package.json上添加脚本"test": "ng test --browser ChromeHeadless --single-run""watch": "ng test --browser ChromeHeadless"

很简单,对吧!

6. 使用标准的提交信息 & 自动生成更新日志

对咱们感兴趣的项目的新特性和bug修复有一个快速概述老是很棒的。

让咱们为用户提供一样的便利!

手动写更改日志将是极其繁琐,并且容易出错的任务,因此它最好是自动化的过程。有不少可用的工具能够作这项工做,可是让咱们看下standard-version

这个工具根据Conventional Commits specification把全部提交自动生成和更新changelog.md文件,而且正确地肯定咱们项目的新版本。

常规提交定义了强制类型、可选(范围):其次是提交消息。还能够添加可选的正文和页脚,它们都由空行分隔。让咱们来看看ngx-model库全部提交信息的一个实践例子。

fix(dependency): multiple versions of rxjs in single project (TS90010)

BREAKING CHANGE: rxjs is now peerDependency instead of dependency

closes #1

标准版本将正确地撞击(bump)项目的主要版本,由于在提交主体中存在着BREAKING CHANGE关键字。

生成的 CHANGELOG.md 文件将像这个样子….

图片描述

看起来很好!那么咱们如何在咱们的项目中使用它呢?

首先咱们须要经过命令npm install -D standard-version安装到devDependencies ,而后添加脚本"release": "standard-version"到package.json文件中。

咱们也能够经过git pushnpm publish使整个过程自动化。本例子中使用脚本"release": "standard-version && git push — follow-tags origin master && npm publish"

注意咱们使用&&连接命令是平台相关的,只能在基于Unix的系统上(所以也对Windows Cygwin,gitbash,或新的win10)。

附赠 Use resource root (Intellij IDEA, Webstorm only)

IntelliJ IDEA永远不会找到全部默认状况下(带有错误的标记和残废的红色代码)的路径。幸运的是,解决方案很是简单。只需选择SRC文件夹并将其标记为Sources Root。

图片描述

很棒! 你终于读完了它!

我但愿你能找到一些有用的技巧和最佳实践!请支持这篇文章,以便向更多的受众传播这些建议!

参考资源