Skip to content

Commit

Permalink
feat(ng:schematic): support ng add & ng generate (NG-ZORRO#1430)
Browse files Browse the repository at this point in the history
  • Loading branch information
hsuanxyz authored and vthinkxie committed Jun 6, 2018
1 parent 858d0dc commit a60ed89
Show file tree
Hide file tree
Showing 35 changed files with 2,022 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ publish/
integration/**/lib/
integration/**/*.ngfactory.ts
integration/**/*.ngsummary.json

schematics/demo
schematics/utils/custom-theme.ts
schematics/utils/lib-versions.ts

# dependencies
node_modules
Expand Down
10 changes: 10 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ sed -e "s/from '.\//from '.\/src\//g" publish/src/index.d.ts > publish/antd.d.ts
sed -e "s/\":\".\//\":\".\/src\//g" publish/src/index.metadata.json > publish/antd.metadata.json
rm publish/src/index.d.ts publish/src/index.metadata.json

echo 'Generate schematics by demos'
npm run schematic:demo

echo 'Building schematics'
node ./schematics_script/set-version.js
node ./schematics_script/set-theme.js
npm run schematic:demo
npm run schematic:build
rm -rf schematics/demo

echo 'Copying package.json'
cp package.json publish/package.json

Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"site:start": "node site_scripts/generate-site init && node site_scripts/generateColorLess && ng serve --port 0 --open",
"site:init": "node site_scripts/generate-site init && node site_scripts/generateColorLess",
"site": "node site_scripts/generate-site",
"schematic:demo": "node schematics_script/demo2schematics",
"schematic:tsc": "tsc -p schematics/tsconfig.json",
"schematic:build": "npm run schematic:tsc && node schematics_script/copy-resources",
"ng": "ng",
"start": "ng serve -p 0",
"build": "node site_scripts/generate-site init && ng build",
Expand All @@ -25,6 +28,7 @@
"module": "./esm5/antd.js",
"es2015": "./esm2015/antd.js",
"typings": "./antd.d.ts",
"schematics": "./schematics/collection.json",
"keywords": [
"ant",
"design",
Expand Down Expand Up @@ -61,6 +65,9 @@
"zone.js": "^0.8.26",
"@angular/compiler-cli": "^6.0.0",
"@angular-devkit/build-angular": "~0.6.0",
"@angular-devkit/core": "^0.6.0",
"@angular-devkit/schematics": "^0.6.0",
"@schematics/angular": "^0.6.0",
"typescript": "~2.7.2",
"@angular/cli": "~6.0.0",
"@angular/language-service": "^6.0.0",
Expand Down Expand Up @@ -96,7 +103,9 @@
"@stackblitz/sdk": "^1.1.1",
"codecov": "^3.0.0",
"ngx-infinite-scroll": "^6.0.0",
"less-plugin-clean-css": "^1.5.1"
"less-plugin-clean-css": "^1.5.1",
"fs-extra": "^6.0.1",
"parse5": "^4.0.0"
},
"peerDependencies": {
"@angular/animations": "^6.0.0",
Expand Down
45 changes: 45 additions & 0 deletions schematics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ng-zorro-antd Schematics

## Schematics

### ng-add

添加 ng-zorro-antd 与它的依赖,并根据需要自动配置。

- 添加 ng-zorro-antd 到 `package.json`
- 替换 `app.component.html` 引导内容
- 在根模块导入必要的模块
- 进行国际化配置
- 将用于自定义的 `theme.less` 或编译后的 css 导入 `angular.json`

```bash
$ ng add ng-zorro-antd [--locale=zh-CN] [--theme] [--skipPackageJson]
```

## 开发

### 脚本

- `npm run schematic:build` 编译到 publish 文件夹
- `npm run schematic:demo` 从 demo 生成 schematics
- `node ./schematics_script/set-version.js` 从 package.json 设置版本号
- `node ./schematics_script/set-theme.js` 从 site_scripts/_site/src/theme.less 设置自定义样式内容

### 开发

只有首次运行才需要以下步骤。

1. 运行 `npm run generate` 生成 `publish` 文件夹
2. `cd publish && npm link`
3. `ng new schematic-debug`
4. `cd schematic-debug && npm link ng-zorro-antd`

调试

1. `cd schematic-debug`
1. `git checkout . && git clean -fd`
1. `ng g ng-zorro-antd:[schematics]`

发布

原有发布流程不变,但是 `schematics/utils/custom-theme.ts``schematics/utils/lib-versions.ts` 内容为动态生成,不提交到版本管理。
10 changes: 10 additions & 0 deletions schematics/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "add NG-ZORRO",
"factory": "./ng-add",
"schema": "./ng-add/schema.json"
}
}
}
176 changes: 176 additions & 0 deletions schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { chain, noop, Rule, SchematicsException, SchematicContext, Tree } from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import * as ts from 'typescript';
import { addModuleImportToRootModule, getSourceFile } from '../utils/ast';
import { createCustomTheme } from '../utils/custom-theme';
import { addSymbolToNgModuleMetadata, findNodes, insertAfterLastOccurrence } from '../utils/devkit-utils/ast-utils';
import { InsertChange } from '../utils/devkit-utils/change';
import { getProjectFromWorkspace, getWorkspace, Project, Workspace } from '../utils/devkit-utils/config';
import { getAppModulePath } from '../utils/devkit-utils/ng-ast-utils';
import { insertImport } from '../utils/devkit-utils/route-utils';
import { zorroVersion } from '../utils/lib-versions';
import { addPackageToPackageJson } from '../utils/package';
import { Schema } from './schema';

export default function (options: Schema): Rule {
return chain([
options && options.skipPackageJson ? noop() : addZorroToPackageJson(),
options && options.theme ? downgradeLess() : noop(),
setBootstrapPage(),
addThemeToAppStyles(options),
addModulesToAppModule(options),
addI18n(options),
(options && !options.skipPackageJson) || (options && !options.theme) ? installNodeDeps() : noop()
]);
}

/** 添加 i18n 配置, 取决于 options.locale */
function addI18n(options: Schema): (host: Tree) => Tree {
return function (host: Tree): Tree {
const workspace = getWorkspace(host);
const project = getProjectFromWorkspace(workspace, options.project);
const modulePath = getAppModulePath(host, project.architect.build.options.main);
const moduleSource = getSourceFile(host, modulePath);
const locale = options.locale;
const localePrefix = locale.split('_')[0];

if (!moduleSource) {
throw new SchematicsException(`Module not found: ${modulePath}`);
}

if (!locale) {
throw new SchematicsException(`Invalid locale-symbol`);
}

const allImports = findNodes(moduleSource, ts.SyntaxKind.ImportDeclaration);

const changes = [
insertImport(moduleSource, modulePath, 'NZ_I18N', 'ng-zorro-antd'),
insertImport(moduleSource, modulePath, locale, 'ng-zorro-antd'),
insertImport(moduleSource, modulePath, 'registerLocaleData', '@angular/common'),
insertImport(moduleSource, modulePath, localePrefix, `@angular/common/locales/${localePrefix}`, true),
...addSymbolToNgModuleMetadata(moduleSource, modulePath, 'providers', `{ provide: NZ_I18N, useValue: ${locale} }`, null),
insertAfterLastOccurrence(allImports, `\n\nregisterLocaleData(${localePrefix});`, modulePath, 0)
];

const recorder = host.beginUpdate(modulePath);
changes.forEach((change) => {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
});

host.commitUpdate(recorder);
return host;
};
}

/** 降级 less */
function downgradeLess(): (host: Tree) => Tree {
return (host: Tree) => {
addPackageToPackageJson(host, 'dependencies', 'less', '^2.7.3');
return host;
};
}

/** 添加 ng-zorro-antd 到 package.json 的 dependencies */
function addZorroToPackageJson(): (host: Tree) => Tree {
return (host: Tree) => {
addPackageToPackageJson(host, 'dependencies', 'ng-zorro-antd', zorroVersion);
return host;
};
}

/** 添加 BrowserAnimationsModule FormsModule HttpClientModule NgZorroAntdModule 到 app.module */
function addModulesToAppModule(options: Schema): (host: Tree) => Tree {
return (host: Tree) => {
const workspace = getWorkspace(host);
const project = getProjectFromWorkspace(workspace, options.project);

addModuleImportToRootModule(host, 'BrowserAnimationsModule', '@angular/platform-browser/animations', project);
addModuleImportToRootModule(host, 'FormsModule', '@angular/forms', project);
addModuleImportToRootModule(host, 'HttpClientModule', '@angular/common/http', project);
addModuleImportToRootModule(host, 'NgZorroAntdModule.forRoot()', 'ng-zorro-antd', project);

return host;
};
}

/** 添加样式配置 */
export function addThemeToAppStyles(options: Schema): (host: Tree) => Tree {
return function (host: Tree): Tree {
const workspace = getWorkspace(host);
const project = getProjectFromWorkspace(workspace, options.project);
if (options.theme) {
insertCustomTheme(project, host, workspace);
} else {
insertCompiledTheme(project, host, workspace);
}
return host;
};
}

/** 将预设样式写入 theme.less,并添加到 angular.json */
function insertCustomTheme(project: Project, host: Tree, workspace: Workspace): void {
const themePath = 'src/theme.less';
host.create(themePath, createCustomTheme());
if (project.architect) {
addStyleToTarget(project.architect.build, host, themePath, workspace);
addStyleToTarget(project.architect.test, host, themePath, workspace);
} else {
throw new SchematicsException(`${project.name} does not have an architect configuration`);
}
}

/** 设置引导页面到 app.component.ts */
function setBootstrapPage(): (host: Tree) => Tree {
return (host: Tree) => {
host.overwrite('src/app/app.component.html', `<a href="https://github.com/NG-ZORRO/ng-zorro-antd" target="_blank" style="display: flex;align-items: center;justify-content: center;height: 100%;width: 100%;">
<img height="400" src="https://img.alicdn.com/tfs/TB1MGSRv21TBuNjy0FjXXajyXXa-89-131.svg">
</a>
`);
return host;
};

}

/** 安装依赖 */
function installNodeDeps(): (host: Tree, context: SchematicContext) => void {
return (host: Tree, context: SchematicContext) => {
context.addTask(new NodePackageInstallTask());
};
}

/** 将编译的 css 添加到 angular.json */
function insertCompiledTheme(project: Project, host: Tree, workspace: Workspace): void {
const themePath = `node_modules/ng-zorro-antd/src/ng-zorro-antd.min.css`;

if (project.architect) {
addStyleToTarget(project.architect.build, host, themePath, workspace);
addStyleToTarget(project.architect.test, host, themePath, workspace);
} else {
throw new SchematicsException(`${project.name} does not have an architect configuration`);
}
}

/** Adds a style entry to the given target. */
function addStyleToTarget(target: any, host: Tree, asset: string, workspace: Workspace): void {
const styleEntry = asset;

// We can't assume that any of these properties are defined, so safely add them as we go
// if necessary.
if (!target.options) {
target.options = { styles: [ styleEntry ] };
} else if (!target.options.styles) {
target.options.styles = [ styleEntry ];
} else {
const existingStyles = target.options.styles.map(s => typeof s === 'string' ? s : s.input);
const hasGivenTheme = existingStyles.find(s => s.includes(asset));

if (!hasGivenTheme) {
target.options.styles.splice(0, 0, styleEntry);
}
}

host.overwrite('angular.json', JSON.stringify(workspace, null, 2));
}
25 changes: 25 additions & 0 deletions schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/schema",
"id": "ngAdd",
"title": "add NG-ZORRO",
"type": "object",
"properties": {
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add ng-zorro dependencies to package.json (e.g., --skipPackageJson)"
},
"locale": {
"type": "string",
"default": "zh_CN",
"enum": ["ar_EG","bg_BG","ca_ES","cs_CZ","de_DE","el_GR","en_GB","en_US","es_ES","et_EE","fa_IR","fi_FI","fr_BE","fr_FR","is_IS","it_IT","ja_JP","ko_KR","nb_NO","nl_BE","nl_NL","pl_PL","pt_BR","pt_PT","sk_SK","sr_RS","sv_SE","th_TH","tr_TR","ru_RU","uk_UA","vi_VN","zh_CN","zh_TW"],
"description": "add locale code to module (e.g., --locale=en_US)"
},
"theme": {
"type": "boolean",
"default": false,
"description": "add theme.less"
}
},
"required": []
}
12 changes: 12 additions & 0 deletions schematics/ng-add/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

export interface Schema {
/** Whether to skip package.json install. */
skipPackageJson: boolean;

theme: boolean;

/** Name of the project to target. */
project?: string;

locale: 'ar_EG' | 'bg_BG' | 'ca_ES' | 'cs_CZ' | 'de_DE' | 'el_GR' | 'en_GB' | 'en_US' | 'es_ES' | 'et_EE' | 'fa_IR' | 'fi_FI' | 'fr_BE' | 'fr_FR' | 'is_IS' | 'it_IT' | 'ja_JP' | 'ko_KR' | 'nb_NO' | 'nl_BE' | 'nl_NL' | 'pl_PL' | 'pt_BR' | 'pt_PT' | 'sk_SK' | 'sr_RS' | 'sv_SE' | 'th_TH' | 'tr_TR' | 'ru_RU' | 'uk_UA' | 'vi_VN' | 'zh_CN' | 'zh_TW';
}
25 changes: 25 additions & 0 deletions schematics/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"lib": ["es2017", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../publish/schematics",
"noEmitOnError": false,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"target": "es6",
"types": [
"jasmine",
"node"
]
},
"include": [
"**/*"
],
"exclude": [
"**/*.component.ts",
"**/*spec*",
"template/**/*"
]
}
Loading

0 comments on commit a60ed89

Please sign in to comment.