阅读关于 @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的规则感到有些困惑,或者想进一步提高工作效率的人们,不妨尝试自己开发工具!