随着前端框架的诞生,也会随之呈现一些组件库,不便日常业务开发。明天就聊聊 angular4 组件库开发流程。
下图是 button 组件的根底文件。
nk-button.component.ts 为该组件的外围文件,看看代码:
import {Component, Renderer2, ElementRef, AfterContentInit, ViewEncapsulation, Input} from '@angular/core';@Component({ selector: '[nk-button]', templateUrl: './nk-button.component.html', encapsulation: ViewEncapsulation.None, styleUrls: ['./nk-button.component.scss']})export class NkButtonComponent implements AfterContentInit { _el: HTMLElement; _prefixCls = 'ky-btn'; _type: string; _size: string; _shape: string; _classList: Array<string> = []; @Input() get nkType() { return this._type; } set nkType(value) { this._type = value; this._setClass(); } @Input() get nkSize() { return this._size; } set nkSize(value: string) { this._size = value; this._setClass(); } @Input() get nkShape() { return this._shape; } set nkShape(value: string) { this._shape = value; this._setClass(); } constructor(private _elementRef: ElementRef, private _renderer: Renderer2) { this._el = this._elementRef.nativeElement; this._renderer.addClass(this._el, this._prefixCls); } ngAfterContentInit() { } /** *设置class属性 */ _setClass(): void { this._classList = [ this.nkType && `${this._prefixCls}-${this.nkType}`, this.nkSize && `${this._prefixCls}-${this.nkSize}`, this.nkShape && `${this._prefixCls}-${this.nkShape}` ].filter(item => { return item; }); this._classList.forEach(_className => { this._renderer.addClass(this._el, _className); }); }}
针对外围概念 ElementRef、Renderer2、ViewEncapsulation 做简要阐明:
ElementRef
在应用层间接操作 DOM,就会造成应用层与渲染层之间强耦合,通过 ElementRef 咱们就能够封装不同平台下视图层中的 native 元素 (在浏览器环境中,native 元素通常是指 DOM 元素),最初借助于 Angular 提供的弱小的依赖注入个性,咱们就能够轻松地拜访到 native 元素。
参考链接
Renderer2
渲染器是 Angular 为咱们提供的一种内置服务,用于执行 UI 渲染操作。在浏览器中,渲染是将模型映射到视图的过程。模型的值能够是 JavaScript 中的原始数据类型、对象、数组或其它的数据对象。然而视图能够是页面中的段落、表单、按钮等其余元素,这些页面元素外部应用 DOM 来示意。
参考链接
ViewEncapsulation
ViewEncapsulation 容许设置三个可选的值:
- ViewEncapsulation.Emulated - 无 Shadow DOM,然而通过 Angular 提供的款式包装机制来封装组件,使得组件的款式不受内部影响。这是 Angular 的默认设置。
- ViewEncapsulation.Native - 应用原生的 Shadow DOM 个性
- ViewEncapsulation.None - 无 Shadow DOM,并且也无款式包装
参考链接
button 组件创立思路:
- 针对 button 咱们只需批改其款式,因而在这里创立属性指令
- 提供属性接口
- 依据其传入的属性值动静渲染 DOM
至此,最简略的 button 就开发完结。
模块打包流程
合并 html、css 到 component 文件
let fs = require("fs");let pathUtil = require("path");let sass = require("node-sass");let filePath = pathUtil.join(__dirname, "src", "temp_components");let fileArray = [];function fildFile(path) { if (fs.statSync(path).isFile()) { if (/\.component.ts/.test(path)) { fileArray[0] = path; } if (/\.html$/.test(path)) { fileArray[1] = readFile(path); } if (/\.component.scss$/.test(path)) { fileArray[2] = path; } } else if (fs.statSync(path).isDirectory()) { let paths = fs.readdirSync(path); if (fileArray.length === 3) { writeFile(fileArray); fileArray = []; } paths.forEach((p) => { fildFile(pathUtil.join(path, p)); }); }}function readFile(file) { return fs.readFileSync(file);}function writeFile(fileArray) { let file = fileArray[0]; let content = fileArray[1]; let scssPath = fileArray[2]; mergeContent(file, content, scssPath).then((result) => { if (!result) return; fs.writeFile(file, result, function (err) { if (err) console.error(err); console.log("file merge success!"); }); });}/** * 转换scss * @param path * @returns {Promise} */function processScss(path) { return new Promise((resolve, reject) => { sass.render( { file: path, }, (err, result) => { if (!err) { resolve(result.css.toString()); } else { reject(err); } } ); });}function mergeContent(file, content, scssPath) { let componentContent = readFile(file); let htmlRegex = /(templateUrl *:\s*[\"|\'])(.*[\"|\']\,?)/g; let scssRegex = /(styleUrls *:\s*)(\[.*\]\,?)/g; let newContent = ""; if (htmlRegex.test(componentContent) && scssRegex.test(componentContent)) { let contentArray = componentContent.toString().split(htmlRegex); contentArray[1] = "template:`"; contentArray[2] = content + "`,"; contentArray.forEach((con) => { newContent += con; }); contentArray = newContent.toString().split(scssRegex); return new Promise((resolve, reject) => { processScss(scssPath).then( (result) => { newContent = ""; contentArray[1] = "styles:[`"; contentArray[2] = result + "`],"; contentArray.forEach((con) => { newContent += con; }); resolve(newContent); }, (err) => { reject(err); } ); }); }}fildFile(filePath);
ts 编译(tsconfig-aot.json)
{ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./publish/src", "baseUrl": "./", "declaration": true, "importHelpers": true, "module": "es2015", "sourceMap": false, "target": "es2015", "types": [ "node" ] }, "files": [ "./src/temp_components/ng-kylin.module.ts" ], "angularCompilerOptions": { "annotateForClosureCompiler": true, "strictMetadataEmit": true, "flatModuleOutFile": "index.js", "flatModuleId": "ng-kylin", "skipTemplateCodegen": true }}
rollup 打包 (rollup-config.js)
import resolve from "rollup-plugin-node-resolve";import replace from "rollup-plugin-replace";const format = process.env.ROLLUP_FORMAT || "es";let globals = { "@angular/animations": "ng.animations", "@angular/cdk": "ng.cdk", "@angular/core": "ng.core", "@angular/common": "ng.common", "@angular/compiler": "ng.compiler", "@angular/forms": "ng.forms", "@angular/platform-browser": "ng.platformBrowser", moment: "moment", "moment/locale/zh-cn": null, "rxjs/BehaviorSubject": "Rx", "rxjs/Observable": "Rx", "rxjs/Subject": "Rx", "rxjs/Subscription": "Rx", "rxjs/observable/fromPromise": "Rx.Observable", "rxjs/observable/forkJoin": "Rx.Observable", "rxjs/observable/fromEvent": "Rx.Observable", "rxjs/observable/merge": "Rx.Observable", "rxjs/observable/of": "Rx.Observable", "rxjs/operator/auditTime": "Rx.Observable.prototype", "rxjs/operator/catch": "Rx.Observable.prototype", "rxjs/operator/debounceTime": "Rx.Observable.prototype", "rxjs/operator/distinctUntilChanged": "Rx.Observable.prototype", "rxjs/operator/do": "Rx.Observable.prototype", "rxjs/operator/filter": "Rx.Observable.prototype", "rxjs/operator/finally": "Rx.Observable.prototype", "rxjs/operator/first": "Rx.Observable.prototype", "rxjs/operator/map": "Rx.Observable.prototype", "rxjs/operator/pluck": "Rx.Observable.prototype", "rxjs/operator/startWith": "Rx.Observable.prototype", "rxjs/operator/switchMap": "Rx.Observable.prototype", "rxjs/operator/takeUntil": "Rx.Observable.prototype", "rxjs/operator/throttleTime": "Rx.Observable.prototype",};if (format === "es") { globals = Object.assign(globals, { tslib: "tslib", });}let input;let file;switch (format) { case "es": input = "./publish/src/index.js"; file = "./publish/esm15/index.js"; break; case "umd": input = "./publish/esm5/index.js"; file = "./publish/bundles/ng-kylin.umd.js"; break; default: throw new Error(`format ${format} is not supported`);}export default { input, output: { file, format, }, exports: "named", name: "ngKylin", plugins: [replace({ "import * as moment": "import moment" }), resolve()], external: Object.keys(globals), globals,};
shell 脚本定义执行流程(build.sh)
#!/usr/bin/env bashrm -rf ./publishcp -r src/app/components src/temp_componentsnode ./html.merge.jsecho 'Generating entry file using Angular compiler'$(npm bin)/ngc -p tsconfig-aot.jsonrm -rf src/temp_componentsecho 'Bundling to es module'export ROLLUP_FORMAT=es$(npm bin)/rollup -c rollup-config.jsrm -rf publish/src/*.jsrm -rf publish/src/**/*.jssed -e "s/from '.\//from '.\/src\//g" publish/src/index.d.ts > publish/index.d.tssed -e "s/\":\".\//\":\".\/src\//g" publish/src/index.metadata.json > publish/index.metadata.jsonrm publish/src/index.d.ts publish/src/index.metadata.jsonecho 'Transpiling es module to es5'$(npm bin)/tsc --allowJs --importHelpers --target es5 --module es2015 --outDir publish/esm5 publish/esm15/index.jsecho 'Bundling to umd module'export ROLLUP_FORMAT=umd$(npm bin)/rollup -c rollup-config.jsecho 'Minifying umd module'$(npm bin)/uglifyjs publish/bundles/ng-kylin.umd.js --output publish/bundles/ng-kylin.umd.min.jsecho 'Copying package.json'cp package.json publish/package.json
至此,我的项目打包完结。