阅读关于 @capacitor/angular 的 ng add 示例(有附加说明)

有一個叫做Capacitor的函式庫,它可以將靜態網站包裝成可在AppStore或Google Play上分發的手機應用程式。這個概念類似於Cordova的繼任者,可以用來建立HTML5混合應用程式或外殼應用程式。

由于该电容器提供对ng add的支持,该电容器称为@capacitor/angular包,让我们来阅读一下它的代码。

ionic-team/capacitor-angular-toolkit的源代码可以在这个链接中找到:https://github.com/ionic-team/capacitor-angular-toolkit/blob/master/schematics/add/index.ts

代码阅读

构成

首先,我们查看 package.json 文件(摘录)来了解 @capacitor/angular 软件包是由什么构成的。

{
..中略..
  "peerDependencies": {
    "@angular-devkit/core": "^8.0.0",
    "@angular-devkit/schematics": "^8.0.0",
    "rxjs": "~6.4.0",
    "typescript": "~3.4.3"
  },
..中略..
  "schematics": "./schematics/collection.json"
}

我在使用rxjs和typescript的同时,还使用了@angular-devkit。Angular提供了@angular-devkit和schematics作为开发包,用于扩展Angular。@angular-devkit/core是其核心文件,@angular-devkit/schematics可用于扩展Angular CLI的功能等。除了扩展ng add以外,我们还可以使用ng generate来添加任意模板。

然后,使用 schematics 键,指定了描述该套件 schematics 内容的 collection.json,因此接下来我们会查看这个文件。

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
  "ng-add": {
      "description": "Add Capacitor to your project",
      "factory": "./add",
      "schema": "./add/schema.json"
    },
  "cap-init": {
      "description": "Run Cap init",
      "factory": "./cap-init",
      "schema": "./cap-init/schema.json"
    }
  }
}

$scheme 键表示了此集合是根据什么进行描述的(用于 IDE 的自动补全等)。并且定义了两个 schematic。当执行 ng add 时,ng-add 将自动执行,并且 cap-init 将在 ng-add 执行期间通过 RunSchematicTask 执行。现在,让我们看看 ng-add 具体执行了哪些操作。

处理

在这个代码中,有大约100行的代码,其中包含许多函数,而实际执行的部分就是这一段。

export default function ngAdd(options: CapAddOptions): Rule {
  return async (host: Tree) => {
    const workspace = await getWorkspace(host);

    if (!options.project) {
      options.project = workspace.extensions.defaultProject as string;
    }

    const projectTree = workspace.projects.get(options.project);

    if (projectTree.extensions['projectType'] !== 'application') {
      throw new SchematicsException(
        `Capacitor Add requires a project type of "application".`
      );
    }

    const packageMgm = getPackageManager(projectTree.root);
    const distTarget = projectTree.targets.get('build').options[
      'outputPath'
    ] as string;
    const sourcePath = projectTree.sourceRoot;

    return chain([
      addCapacitorToPackageJson(),
      addCapPluginsToAppComponent(sourcePath),
      capInit(options.project, packageMgm, distTarget),
    ]);
  };
}

getWorkspace()提供了多种方法来获取Angular的工作区(在angular.json文件中进行了配置),它是在@schematics/angular/utility/workspace中实现的。

首先获取workspace,如果workspace中没有项目,则添加默认项目(对于Ionic来说,是app。这是在angular.json文件中设置的值)。

如果 projectType 不是 application,就会返回错误,提示无法使用 Capacitor。如果没问题,会从 getPackageManager 方法中获取包,并从项目中获取构建的 outputPath。

然后,我正在执行 addCapacitorToPackageJson() 方法(将 @capacitor/core 添加到 dependencies,将 @capacitor/cli 添加到 devDependencies),以及 addCapPluginsToAppComponent 方法(在 app.component.ts 中添加 import { Plugins } from ‘@capacitor/core’),一起使用 Capacitor CLI 执行 npx cap init 的 capInit 方法。

尝试制作一个更漂亮的包装设计

我不知道 ng add 是如何实现的,但大概了解了。那么,在 @capacitor/angular 的基础上,我们来创建一个自定义的 Prettier 包吧。

首先,将 @capacitor/angular 进行 Fork。将其包名修改为 prettier-angular-toolkit。

1. 修改包名等内容

修改package.json的信息。与从零开始创建相比,通过阅读代码并复制其他OSS包进行修改会更加稳定,这对我来说很受欢迎。通过自己来实现,还能更深入地理解原始代码。

2. 删除不必要的文件夹

由于本次不使用schematics/cap-init,将其删除。

3. 包装的更改

更改要安装的软件包。 在schematics / add / index.ts中,将要安装的软件包从Capacitor相关更改为Prettier。由于此次没有依赖关系,因此删除该行,将devDependencies替换为下面的代码。

function addFormatterToPackageJson(): Rule {
  return (host: Tree) => {
    addPackageToPackageJson(
      host,
      'devDependencies',
      'prettier',
      'latest'
    );
    addPackageToPackageJson(
      host,
      'devDependencies',
      'lint-staged',
      'latest'
    );
    addPackageToPackageJson(
      host,
      'devDependencies',
      '@kaizenplatform/prettier-config',
      'latest'
    );
    return host;
  };
}

addPackageToPackageJson 是 IonicTeam 开发的一个自定义库,非常通用且方便。
lint-staged 用于将 Prettier 限定在 Git 已暂存的文件范围内(因为文件数量较多时格式化会花费较长时间)。
@kaizenplatform/prettier-config 用作 Prettier 的格式化配置(如果有更好的格式化方法,请在注释中告知)。

另外,还需要改变 addCapPluginsToAppComponent 方法,并将其作为 addPrettierConfig 方法,用于创建 prettier.config.js。该方法会判断主机的 projectRoot 中是否存在 prettier.config.js,如果不存在,则添加一个简单的方法来添加它。

function addPrettierConfig(projectRoot: string): Rule {
  return (host: Tree) => {
    const sourcePath = `${projectRoot}/prettier.config.js`;
    if (!host.exists(sourcePath)) {
      host.create(sourcePath, 'module.exports =require(\'@kaizenplatform/prettier-config\');')
    }
    return host;
  };
}

需要更新Package.json,并设置任务和lint-staged。由于该方法不存在,所以需要自行创建。将其添加到schematics/utils/package.ts文件中。

/**
 * Adds a method to the package.json
 */
export function addMethodToPackageJson(host: Tree, scriptsName: string, method: string) {
  if (host.exists('package.json')) {
    const sourceText = host.read('package.json')!.toString('utf-8');
    const json = JSON.parse(sourceText);
    if (!json['scripts']) {
      json['scripts'] = {};
    }

    if (!json['scripts'][scriptsName]) {
      json['scripts'][scriptsName] = method;
    }

    host.overwrite('package.json', JSON.stringify(json, null, 2));
  }

  return host;
}

/**
 * Adds a method to the package.json
 */
export function addKeyToPackageJson(host: Tree, key: string, method: string | object) {
  if (host.exists('package.json')) {
    const sourceText = host.read('package.json')!.toString('utf-8');
    const json = JSON.parse(sourceText);

    if (!json[key]) {
      json[key] = method;
    }

    host.overwrite('package.json', JSON.stringify(json, null, 2));
  }

  return host;
}

虽然这两种方法看起来相似,但都是用于向package.json中追加内容的方法。前者用于添加原始脚本,而后者用于添加原始键。现在,让我们继续利用addFormatterToPackageJson()方法。

addScriptsToPackageJson(
      host,
      'lint-staged',
      'lint-staged'
    );
    addScriptsToPackageJson(
      host,
      'formatter',
      'prettier --parser typescript --write \"./**/*.ts\" &&  prettier --parser html --write \"./**/*.html\"'
    );
    addKeyToPackageJson(
      host,
      'pre-commit',
      [
        'lint-staged',
      ]
    );
    addKeyToPackageJson(
      host,
      'lint-staged',
      {
        '*.ts': [
          'prettier --parser typescript --write',
          'git add',
        ],
        '*.html': [
          'prettier --parser html --write',
          'git add',
        ],
      }
    );

由于尚未执行npm install,因此最后让我们进行npm install。我们将使用spawn直接执行。

function doNpmInstall(): Rule {
  return (host: Tree) => {
    return new Observable<Tree>(subscriber => {
      const child = spawn('npm', ['install'], { stdio: 'inherit' });
      child.on('error', error => {
        subscriber.error(error);
      });
      child.on('close', () => {
        subscriber.next(host);
        subscriber.complete();
      });
      return () => {
        child.kill();
        return host;
      };
    });
  };
}

可以实现。让我们通过 npm publish 进行发布。现在,@rdlabo/ng-add-formatter 可以使用了。真简单。

总结

Angular为开发者提供各种支持,通过@schematics和@angular-devkit等包,方便开发者以Angular为基础进行自定义工具和开发流程的优化。
如果对Angular的规则感到有些困惑,或者想进一步提高工作效率的人们,不妨尝试自己开发工具!

广告
将在 10 秒后关闭
bannerAds