在庫中使用schematics——ng add與ng update

Yasg發表於2020-12-21

起步

當建立 Angular 庫時,你可以為同時為它打包進一組原理圖,並把它與 Angular CLI 整合在一起。藉助原理圖,使用者可以用 ng add 來安裝你這個庫的初始版本,可以用 ng generate 來建立你在庫中定義的一些工件,可以用 ng update 來調整他們的專案,以支援你在庫的新版本中引入的重大變更。

建立一個angular庫

ng new demo --create-application=false
ng g library my-lib

可見如下目錄結構

├── node_modules/                 
├── projects                           
│   └── my-lib        
│       ├── src               
│       │   ├── lib
│       │   ├── public-api.ts
│       │   └── test.ts
│       ├── karma.conf.js
│       ├── ng-package.json       
│       ├── package.json
│       ├── README.md
│       ├── tsconfig.lib.json
│       ├── tsconfig.lib.prod.json
│       ├── tsconfig.spec.json
│       └── tslint.json
├── README.md
├── package.json
├── .editorconfig
├── angular.json
├── tsconfig.base.json
├── tslint.json
└── tsconfig.json

新增projects/my-lib/schematics/collection.json檔案

{
  "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "ng-add": {
      "description": "Add my library to the project.",
      "factory": "./ng-add/index#ngAdd"
    }
  }
}

修改projects/my-lib/package.json檔案

{
  "name": "my-lib",
  "version": "0.0.1",
  "schematics": "./schematics/collection.json",
}

新增projects/my-lib/schematics/ng-add/index.ts檔案

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { strings } from '@angular-devkit/core';

// Just return the tree
export function ngAdd(options: any): Rule {
  return (tree: Tree, context: SchematicContext) => {
    context.addTask(new NodePackageInstallTask());
     // 如果不是 Angular 專案則丟擲錯誤
    const workspaceConfig = tree.read('angular.json');
    if (!workspaceConfig) {
      throw new SchematicsException('Not an Angular CLI workspace');
    }

     const templateSource = apply(url('./files'), [
      applyTemplates({
        ...strings
      }),
      move(normalize('')),
    ]);

    return chain([mergeWith(templateSource)]);
  };
}

新增projects/my-lib/schematics/ng-add/files 檔案

└── files                           
      └──index.ts

index.ts內容如下

// #docregion template
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class <%= classify(name) %>Service {
  constructor(private http: HttpClient) { }
}

定義依賴型別,在projects/my-lib/package.json中新增

"ng-add": {
  "save": "devDependencies"
}

修改projects/my-lib/tsconfig.schematics.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "lib": [
      "es2018",
      "dom"
    ],
    "declaration": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "noEmitOnError": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noUnusedParameters": true,
    "noUnusedLocals": true,
    "rootDir": "schematics",
    "outDir": "../../dist/my-lib/schematics",
    "skipDefaultLibCheck": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strictNullChecks": true,
    "target": "es6",
    "types": [
      "jasmine",
      "node"
    ]
  },
  "include": [
    "schematics/**/*"
  ],
  "exclude": [
    "schematics/*/files/**/*"
  ]
}
屬性 說明
rootDir 指出在你的 schematics/ 資料夾中包含要編譯的輸入檔案。
outDir 對映到了庫的輸出目錄下。預設情況下,這是工作空間根目錄下的 dist/my-lib 資料夾

修改projects/my-lib/package.json

{
  "name": "my-lib",
  "version": "0.0.1",
  "scripts": {
    "build": "../../node_modules/.bin/tsc -p tsconfig.schematics.json",
    "copy:files": "cp --parents -p -r schematics/*/files/** ../../dist/my-lib/",
    "copy:collection": "cp schematics/collection.json ../../dist/my-lib/schematics/collection.json",
    "postbuild":"npm run copy:files && npm run copy:collection"
  },
  "peerDependencies": {
    "@angular/common": "^7.2.0",
    "@angular/core": "^7.2.0"
  },
  "schematics": "./schematics/collection.json",
  "ng-add": {
    "save": "devDependencies"
  }
}
屬性 說明
build 指令碼使用自定義的 tsconfig.schematics.json 檔案來編譯你的原理圖。
copy *語句將已編譯的原理圖檔案複製到庫的輸出目錄下的正確位置,以保持目錄的結構。
postbuild 指令碼會在 build 指令碼完成後複製原理圖檔案。

修改根目錄下的package.json,在scripts下加入

 "build": "ng build my-lib --prod && cd ./projects/my-lib && npm run build",
 "publish": "cd ./dist/my-lib && npm publish"

補充:
建立 schema.d.ts,定義的各個選項的值
一般的,可以手動建立 schema.d.ts,如本生成 component 的原理圖,它的 schema.json 中屬性只有一個必填的 name,那麼編寫的 schema.d.ts 內容就如下:

export interface Schema {
    name: string;
}

實際上,這個檔案可以使用指令生成,在 schema.json 的同級目錄下,開啟終端輸入指令,如下:

npx -p dtsgenerator dtsgen schema.json -o schema.d.ts

開始釋出

npm publish

試一試ng add吧!

使用schematics修改專案

如何使用schematics修改專案中的package.json

方法

說明

tree.overwrite() 重新編排package.json
tree.read() 讀取檔案內容

修改projects/my-lib/schematics/ng-add/index.ts

    //讀取angular.json的檔案內容
    const workspaceConfig = tree.read('angular.json');
    // 如果不是 Angular 專案則丟擲錯誤
   if (!workspaceConfig) {
      throw new SchematicsException('Not an Angular CLI workspace');
    }
    // 讀取package.json的檔案內容
    const workspaceJson = tree.read('package.json');
    let json=JSON.parse(workspaceJson)
    json.script.hello="print('123')"
    tree.overwrite('./package.json',JSON.stringify(json))
    

在node_modules新增引入與注入項

方法 說明
addImportToModule(source: ts.SourceFile, modulePath: string, classifiedName: string, importPath: string): Change[] 新增引入到module
ts.createSourFile 建立資原始檔

import * as ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript';
import { addImportToModule } from '@schematics/angular/utility/ast-utils';

......

  const modulePath = `/app.module.ts`;
  const sourText = _tree.read(modulePath).toString('utf-8');
  const sourceFile =  ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
  const importPath = '@fortawesome/angular-fontawesome';
  const moduleName = 'FontAwesomeModule';  //檔名為駝峰命名
  const declarationChanges = addImportToModule(sourceFile, modulePath, moduleName, importPath);
  const declarationRecorder = _tree.beginUpdate(modulePath);
  for (const change of declarationChanges) {
    if (change instanceof InsertChange) {
        declarationRecorder.insertLeft(change.pos, change.toAdd);
    }
  }
 _tree.commitUpdate(declarationRecorder);

更新檔案

方法 說明
tree.beginUpdate() 更新內容
   const htmlPath = `./app/src/app.component.html`;
   const htmlStr = `\n\n`;
   const modulePath = `/app.module.ts`;
   const sourText = _tree.read(htmlPath).toString('utf-8');
   const htmlSourceFile =  ts.createSourceFile(htmlPath, sourceText, ts.ScriptTarget.Latest, true);
   const htmlRecorder = _tree.beginUpdate(htmlPath);
   htmlRecorder.insertLeft(htmlSourceFile.end, htmlStr);
   _tree.commitUpdate(htmlRecorder);

package.json依賴項安裝

方法 說明
addPackageToPackageJson 新增依賴支援
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';

......

const dependencies = [{ name: '@fortawesome/fontawesome-svg-core', version: '~1.2.25' }],
addPackageToPackageJson(
    _tree,
    dependency.name, //依賴包名
    dependency.version,//版本
);

ng Update使用

在collection.json 同級目錄,新建 migration.json 檔案,並寫入以下內容:

{
    "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
    "schematics": {
        "migration002": {
            "version": "0.0.2",
            "description": "更新angular-schematics-tutorial到0.0.2版本時執行的更新",
            "factory": "./ng-update/index#update"
        }
    }
}

在 package.json 中宣告 ug-update 配置
在 package.json 中,新增以下專案:

  "ng-update": {
    "migrations": "./schematics/migration.json"
  },

其作用,就是在執行 ng update 時,能夠找到對應的配置檔案
在projects/my-lib/schematics/ 目錄下新建index.ts

import { Rule, Tree, SchematicContext, SchematicsException } from '@angular-devkit/schematics';
import { buildDefaultPath } from '@schematics/angular/utility/project';
import * as ts from 'typescript';

export function update(): Rule {
    return (_tree: Tree, _context: SchematicContext) => {

        // 解析angular專案
        const workspaceConfigBuffer = _tree.read('angular.json');
        if (!workspaceConfigBuffer) {
            throw new SchematicsException('Not an Angular CLI workspace');
        }
        return tree.create('index.md','我更新了')
      }
    
}


完結撒花!!

相關文章