阅读 618

基于NX开发Angular项目

项目依赖的安装

Node和NPM的安装

​ 网上教程较多,可以自行搜索,下面给出2个链接:

mac的安装

pc的安装

NX的安装

​ 全局安装NX CLI

npm install -g @nrwl/schematics
复制代码

​ 全局安装Angular CLI

npm install -g @angular/cli
复制代码

官网教程

ng-packagr的安装

​ 在项目中安装

> npm i ng-packagr --save-dev
复制代码

github

创建一个workspace项目

首先通过nx命令行建立一个workspace

> create-nx-workspace example
复制代码

​ 该过程中会自动安装所需的各项依赖,请确保通过nx命令建立workspace,避免因使用webpack或ng-cli创建项目,带来各项依赖的额外安装。

当nx workspace建立之后,我们需要在example的app中建立对应的项目

> ng generate app myapp --routing
复制代码

​ 其中myapp是项目名称,—routing是自动增加路由依赖参数,由于项目会使用到路由,因此我们在建立项目时带上这个参数。如果忘记带上routing参数,也可以后续手动添加,不影响实际开发。

有些时候当nx或一些依赖的版本发生更新时,会出现一些预料之外的错误

Error: Cannot find module '~/example/node_modules/prettier/bin/prettier.js'
复制代码

此时可以参考官网issue,此类问题一般是由于依赖更新导致的,更新一下依赖就可以解决类似问题

> npm install prettier@1.10.1 -D
复制代码

​ 当项目建立完成之后,我们可以看到在apps目录下建立了myapp这个目录,以及完整的代码结构。同时我们在vscode中会发现.angular-cli.json被更新了,其中被加入myapp的项目配置,一般情况下不需要去修改这些配置。

{
      "name": "myapp",
      "root": "apps/myapp/src",
      "outDir": "dist/apps/myapp",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "../../../test.js",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "../../../tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
复制代码

建立完myapp之后,我们可以先运行一下实例,看看是否能够跑通

> ng serve -a=myapp
复制代码

​ 键入命令行之后,我们能够通过http://localhost:4200/地址来进行查看,4200是ng serve的默认端口,也可以通过 -port=端口号 进行修改。-a=myapp是指定运行myapp,如果有其他项目也是用相同的方式来开启。当我们愉快的看见浏览器中的NX标志后,意味着我们的项目已经跑通了。

为APP加点料

建立为myapp服务的lib

​ 在没有workspace概念之前,一般的开发过程是先在app中实施,而后再将完成的代码拆分到另一个库,并且打成NPM包。这个方法及其难以维护,即使通过git的submodule来做处理依然不是非常的流畅。而现在NX为我们提供了一种快捷的实现方式。

>ng g lib myapp-feature --routing
复制代码

​ 几乎和创建app同样的方式,我们在libs目录下创建了名为myapp-feature的lib,因为需要路由,我们把路由也带上了。观察一下.angular-cli.json,同样,新的lib也被更新了。

    {
      "name": "myapp-feature",
      "root": "libs/myapp-feature/src",
      "test": "../../../test.js",
      "appRoot": ""
    }
复制代码

​ 接下来就是在myapp中引用lib,NX为我们提供了一个方便的别名引用方式。

import { MyappFeatureModule } from '@ebiz-example/myapp-feature';
复制代码

​ 我们在app.module.ts下加入引用,其中ebiz-example是我们在package.json的name配置项的值,myapp-feature是libs下的目录。在app和lib的module下的constructor里分别添加console,然后运行app,我们可以发现lib已经被顺利引入。

为myapp添加基于ngrx的state manage

​ 对于Component间的通讯,我们需要寻找一个state manage的方案,而继承了redux思路的ngrx是个非常不错的选择,很幸运的,NX为我们集成了快速添加ngrx的方式。

> ng g ngrx root -m=apps/myapp/src/app/app.module.ts --root
复制代码

​ 命令完成后,我们会发现在app目录下多出了+state目录,因为使用了-m所以在app.module.ts文件中为我们加入了相应的代码,—root则为我们指定了使用forRoot方式。

import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { rootReducer } from './+state/root.reducer';
import { rootInitialState } from './+state/root.init';
import { RootEffects } from './+state/root.effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';
import { StoreRouterConnectingModule } from '@ngrx/router-store';

@NgModule({
  imports: [
    StoreModule.forRoot({ root: rootReducer }, { initialState: { root: rootInitialState } }),
    EffectsModule.forRoot([RootEffects]),
    !environment.production ? StoreDevtoolsModule.instrument() : [],
    StoreRouterConnectingModule
  ],
})

复制代码

​ 其中不仅有@ngrx/store和@ngrx/effects,Devtools也为我们准备好了,接下来启动一下服务器,打开Chrome的Redux Devtools,我们可以看到ngrx已经顺利的启动了。

​ 除了app之外,lib也会有state manage的需求,我们一样可以通过ng-cli来进行添加,我们先为lib添加一个module,并为module添加state。

> ng g module subfeature -a=myapp-feature —routing
> ng g component subfeature -a=myapp-feature
> ng g ngrx userinfo -m=libs/myapp-feature/src/subfeature/subfeature.module.ts
复制代码

​ 我们顺利的添加了module和component,如果需要自动注入myapp-feature,也可以加上-m来做到。在添加了ngrx后,同样的+state被加入了代码,其中在subfeature.module.ts有个细微的差别是需要注意的。

@NgModule({
  imports: [
    StoreModule.forFeature('userinfo', userinfoReducer, { initialState: userinfoInitialState }),
    EffectsModule.forFeature([UserinfoEffects])
  ]
})
复制代码

​ 在子module中,我们应该使用forFeature,只有在根module中我们才会使用—root。

​ 将subfeature注入myapp-feature再次启动服务,我们打开Redux Devtools,可以确认userinfo已经被加入到state tree。

为分享Lib做准备

将开发成熟的Lib交付出去

​ 当项目开发到一定阶段,很多时候,我们会希望将libs目录下的功能模组分享给其他的团队来使用,如果是希望将代码分享出去,比较建议的方式是使用git的submodule,具体命令可以参考help。

> git submodule --help
复制代码

将代码封包并进行管理

​ 有的时候我们并不希望其他团队来修改我们的代码,因此将编制完成的Lib打包成NPM,提供给对方进行依赖是一个比较好的方案。

​ 对于Private Lib,一般我们不会提交给Public repo,因此你需要自己搭建一个NPM repo,比如Nexus。当Nexus安装完后,我们还需要对package.json进行设置,主要是2个配置项。

  "publishConfig": {
    "registry": "http://private/repository/ebiz-npm-private/"
  },
复制代码
  "private": false,
复制代码

​ 这2项配置确保了你的npm包是可以发布的,并被正确发布到Private NPM repo。

使用ng-packagr来进行打包

​ 如果是一个es的函数库,我们可以很愉快的直接使用npm publish命令,如果是一个ts的函数库,我们也可以先执行一个tsc进行编译,然后愉快的npm publish。

​ 然而作为一个angular的lib,特别是带有各种Component的lib,这个过程就会麻烦许多。我们都知道Component的templateUrl和styleUrls都是以字符来获取相应的资源文件,而这在打包时会显然会领我们变得不那么愉快,因此需要进行额外处理,比如用gulp预处理代码,而后打包。

​ 幸运的是,我们还能使用ng-packagr来进行打包,首先我们来新建ng-package.json文件。

{
  "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
  "lib": {
    "entryFile": "public_api.ts",
    "externals": {
      "primeng/primeng": "primeng",
      "primeng/components/common/messageservice":
        "primeng/components/common/messageservice",
      "rxjs": "Rx",
      "@nrwl/nx": "@nrwl/nx",
      "@ngrx":"@ngrx",
      "@ngrx/store":"@ngrx/store",
      "@ngrx/store-devtools":"@ngrx/store-devtools",
      "@ngrx/effects":"@ngrx/effects"
    }
  }
}
复制代码

​ 这里定义了ng-packagr的schema,以及一些外部引用时希望能正确认知的第三方库。有一个比较关键的是entryFile,这个配置项是我们设置的一个入口,为此我们新建了一个public_api.ts文件。

	export * from "./libs/myapp-feature";
复制代码

​ 这里我们可以指定将一个或多个lib打入一个NPM包中,所以你希望打包什么就在这个文件进行定义吧。

​ 在这2个文件定义完成之后,我们可以执行打包命令了,先将这个命令写进package.json中,方便运行。

"scripts": {
    "packagr": "ng-packagr -p ng-package.json",
    }
复制代码

​ 执行npm run packagr,有的时候一些引用可能会查找错误,我们需要显式的引用这些依赖。

BUILD ERROR
Error at /Users/aaronjin/eBizprise/ebiz-example/.ng_pkg_build/ebiz-example/ts/libs/myapp-feature/src/subfeature/+state/userinfo.effects.ts:11:3: Public property 'loadData' of exported class has or is using name 'Observable' from external module "/Users/aaronjin/eBizprise/ebiz-example/node_modules/rxjs/Observable" but cannot be named.
复制代码

​ 比如这样的错误,就需要去添加引用。

import { Observable } from 'rxjs/Observable';
复制代码

​ 有些可以通过ng-package.json的externals配置来解决,基本上ng-packagr在打包时会碰到的问题,都是类似第三方的加载不能判别。

发布到Nexus

​ 如果代码没有问题,你可以在项目下找到dist目录了,这个就是我们打包出来的代码了。

​ 代码的命名是根据package.json里的name来设置的,我们不需要去修改它,查看dist目录下的package.json。

    "name": "ebiz-example",
    "version": "0.0.0",
    "license": "MIT",
    "publishConfig": {
        "registry": "http://dkh01.ebizprise.com/repository/ebiz-npm-private/"
    },
复制代码

​ 我们将ebiz-example修改为@ebiz-ex/feature,以区分线上和本地项目,并且将version修改为0.0.1,,当然也是可以在pre-publish用npm version patch来提升版本。

​ 然后我们执行npm publish dist,返回了如下信息,我们就会发现NPM已经发布成功了。

+ @ebiz-ex/feature@0.0.1
复制代码

通过NPM引用打包好的lib

​ 使用Private lib之前,我们要切换到Nexus服务器,平时则可以使用标准官网地址来加快访问速度

> npm config set registry  http://private/repository/ebiz-npm-all/
或
> npm config set registry  http://registry.npmjs.org
复制代码

​ 使用npm命令将已经打包好的lib包引入项目。

> npm i @ebiz-ex/feature --save
复制代码

​ 将app.module.ts中的引用进行修改。

import { MyappFeatureModule } from '@ebiz-ex/feature';
复制代码

​ 再次运行ng server,我们发现使用打包后的NPM包和我们使用在nx项目中的lib包是一致的。

通过文件引用打包好的lib

​ 有些时候我们没有Private repo,那么能不能用打包好的NPM哪?答案是肯定的,把dist目录打包成tgz,然后放在项目中,添加package.json的引用。

        "@ebiz-ex/feature": "file:.feature-0.0.1.tgz",
复制代码

​ 将app.module.ts中的引用进行修改。

import { MyappFeatureModule } from '@ebiz-ex/feature';
复制代码

​ 再次运行ng server,我们发现使用打包后的NPM包和我们使用在nx项目中的lib包是一致的。

是否使用这套方案

Mono Repo or Muti Repo

​ 两个方案的优劣不需要再做过多阐述,这里只想说明NX是一个基于Mono Repo的Workspace方案,如果项目更偏向Muti Repo,则完全不要考虑NX。

​ NX带来最大的优势是统一管理,易于部署,并且不需要为不同的App或Lib反复同步NPM包,并且可以较为方便的将代码拆分开。如果这些能直击痛点,那么不要犹豫的选择NX吧。

NX的侵入性

​ NX集成了较多的功能,有些或许是未必需要用到的,比如不使用@ngrx或者不想使用@ngrx/effects,这些都是需要考虑的范畴。不过NX最大的问题在于对ng-cli的侵入,我们查看package.json,可以发现使用了file的调用方式。

"@angular/cli": "file:.angular_cli.tgz",
复制代码

​ 而这一系列的侵入导致了另一个问题,当Lib处于项目下时,可以正常的使用AOT进行编译,而当我们用ng-packagr进行NPM打包之后再引入,进行AOT就会出现问题。

ERROR in ./apps/myapp/src/main.ts
Module not found: Error: Can't resolve './app/app.module.ngfactory' in '/Users/aaronjin/eBizprise/ebiz-example/apps/myapp/src'
resolve './app/app.module.ngfactory' in '/Users/aaronjin/eBizprise/ebiz-example/apps/myapp/src'
复制代码

​ 这个问题是由于ng、ts的版本冲突引起,具体的解决方案仍未找到,因此使用这套方案仍需谨慎。

ng-packagr的不足

​ 这个打包方案除了版本冲突的可能,仍有一些其他的不足,比如使用ng router的loadchildren异步机制,就会出现这样的元数据丢失情况。

No NgModule metadata found for 'MyappFeatureModule'.
复制代码

github地址

NX-packagr

关注下面的标签,发现更多相似文章
评论