基于ElementUI封装公司内部使用UI库(快速上手)

4,784 阅读8分钟

一 前言

二 nvm的使用(非必看)

elementUI源码使用的node版本14,如果与公司其它项目的node不同,建议安装nvm切换不同版本node,开发对应项目。

nvm在该项目中使用到的基本命令:

  • nvm install v14.17.6 安装该版本的node
  • nvm ls 查看已下载
  • nvm use v14.17.6 切换node版本

三 快速开始-启动开发文档

  • npm run bootstrap安装相关依赖
  • npm run dev 第一次启动组件的开发文档。

本地链接地址一般为:http://localhost:8085

四 快速开始-创建新组件

1.在package.json中新增脚本命令(linux系统可以直接用make脚本命令)

 "scripts": {
    "new": "node build/bin/new.js"
 }

2.运行脚本命令 npm run new <component-name> [中文名] 例如:npm run new newUi 新的UI组件

1.new一个新的组件发生了什么?

首先,我们来看看build/bin/new.js这个文件。

'use strict';

console.log();
// 监听node进程,即将退出时触发exit事件。
process.on('exit', () => {
  console.log();
});

// 脚本命令第一个参数未填写时报错,process.argv返回一个数组,[node,脚本文件名,...参数]。
if (!process.argv[2]) {
  console.error('[组件名]必填 - Please enter new component name');
  process.exit(1);
}

// 实现文件路径的处理和操作,join、resolve、normalize等
const path = require('path');
// 允许您以编程方式操作计算机上的文件
const fs = require('fs');
// 在Node.js中将内容保存到文件中fileSave('文件路径').write('内容')
const fileSave = require('file-save');
// 转换成驼峰命名
const uppercamelcase = require('uppercamelcase');
// 组件英文名
const componentname = process.argv[2];
// 组件中文名
const chineseName = process.argv[3] || componentname;
// 转换成驼峰命名
const ComponentName = uppercamelcase(componentname);
// 解析路径,返回新增组件在package文件夹中的路径
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
// Files[0]:index.js组件文件模板
// Files[1]:main.vue组件文件模板
// Files[2]、Files[3]、Files[4]、Files[5]:*.md四种语言文档模板
// Files[6] 生成单元测试模板
// Files[7] 生成组件样式模板
// Files[8] ts模板
// 原El改为Ss,后续都有,区分elment原有组件。
const Files = [
  {
    filename: 'index.js',
    content: `import ${ComponentName} from './src/main';

/* istanbul ignore next */
${ComponentName}.install = function(Vue) {
  Vue.component(${ComponentName}.name, ${ComponentName});
};

export default ${ComponentName};`
  },
  {
    filename: 'src/main.vue',
    content: `<template>
  <div class="ss-${componentname}"></div>
</template>

<script>
export default {
  name: 'Ss${ComponentName}'
};
</script>`
  },
  {
    filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
    content: `## ${ComponentName} ${chineseName}`
  },
  {
    filename: path.join('../../examples/docs/en-US', `${componentname}.md`),
    content: `## ${ComponentName}`
  },
  {
    filename: path.join('../../examples/docs/es', `${componentname}.md`),
    content: `## ${ComponentName}`
  },
  {
    filename: path.join('../../examples/docs/fr-FR', `${componentname}.md`),
    content: `## ${ComponentName}`
  },
  {
    filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),
    content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';

describe('${ComponentName}', () => {
  let vm;
  afterEach(() => {
    destroyVM(vm);
  });

  it('create', () => {
    vm = createTest(${ComponentName}, true);
    expect(vm.$el).to.exist;
  });
});
`
  },
  {
    filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),
    content: `@import "mixins/mixins";
@import "common/var";

@include s(${componentname}) {
}`
  },
  {
    filename: path.join('../../types', `${componentname}.d.ts`),
    content: `import { ElementUIComponent } from './component'

/** ${ComponentName} Component */
export declare class Ss${ComponentName} extends ElementUIComponent {
}`
  }
];

// 判断组件是否存在。
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {
  console.error(`${componentname} 已存在.`);
  process.exit(1);
}

// components.json组件目录添加新增组件路径
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json'))
  .write(JSON.stringify(componentsFile, null, '  '), 'utf8')
  .end('\n');

// index.scss中写入-导入新增样式的代码
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath)
  .write(sassImportText, 'utf8')
  .end('\n');

// element-ui.d.ts中写入引入和导出新增ts文件代码,原El改为Ss
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');

let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends Ss${ComponentName} {}`;

const index = elementTsText.indexOf('export') - 1;
const importString = `import { Ss${ComponentName} } from './${componentname}'`;

elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);

fileSave(elementTsPath)
  .write(elementTsText, 'utf8')
  .end('\n');

// 遍历新增文件
Files.forEach(file => {
  fileSave(path.join(PackagePath, file.filename))
    .write(file.content, 'utf8')
    .end('\n');
});

// 添加到 nav.config.json中的‘shunsh’(数组最后一个元素,新增的分组名,后续新增组件都会放入这里)分组里,文档菜单。
const navConfigFile = require('../../examples/nav.config.json');

Object.keys(navConfigFile).forEach(lang => {
  let groups = navConfigFile[lang][4].groups;
  groups[groups.length - 1].list.push({
    path: `/${componentname}`,
    title: lang === 'zh-CN' && componentname !== chineseName
      ? `${ComponentName} ${chineseName}`
      : ComponentName
  });
});

fileSave(path.join(__dirname, '../../examples/nav.config.json'))
  .write(JSON.stringify(navConfigFile, null, '  '), 'utf8')
  .end('\n');

console.log('DONE!');


2.示例:

01.运行命令,自定义一个新组件npm run new BaseView 基础盒子
02.packages文件部分变化

用 Vue.use 方法调用插件时,会自动调用 install 函数,注册各种指令、组件,挂载全局方法。 01.png

02.png

这里lib文件夹是样式相关文件编译后的结果。 03.png

03.examples文件部分变化

这里就是存放开发文档项目的文件夹了。

12.png

04.png

运行npm run dev,查看开发环境下的文档

05.png

04.单元测试用例npm run test

06.png

05.ts相关文件

目前我们的项目还没使用到ts,主要还是给使用了ts的项目使用的。

07.png

web-type.json文件,用于在代码编辑器中提供自动补全、代码提示等功能。

  • name:表示组件或函数的名称,用于在代码编辑器中进行匹配和提示。
  • description:对组件或函数的简短描述,提供给开发者参考。
  • doc-url:链接到文档页面,提供更详细的文档和示例。

在使用代码编辑器时,当你输入组件或函数的名称时,编辑器会根据web-types.json文件中的信息,自动显示相关的提示信息,如参数类型、返回值类型、描述等。同时,点击链接,还可以跳转到文档页面,查看更详细的文档和示例。

08.png

五 目录结构

简单介绍主要的一些文件及文件夹

image.png

  • build:构建打包的相关配置
  • examples:开发文档项目的文件夹
  • lib:打包后项目文件夹
  • packages:组件源码
  • src:入口文件/工具函数/指令等
  • test:单元测试文件
  • types:类型声明文件
  • components.json:组件目录
  • Makefile:Makefile是一个定义了一系列规则来编译和链接程序的文件。(句兮君是在window系统下开发的就不用它了,linux的童鞋可以尝试下。)
  • package.json:版本、入口、脚本、依赖等关键信息。

其它的文件就是一些次要的更新日志,常见问题描述,github,balel相关配置文件等等,自己私下了解。

六 packages.json里的脚本命令

 "scripts": {
        "bootstrap": "yarn || npm i",
        "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
        "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
        "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
        "build:umd": "node build/bin/build-locale.js",
        "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
        "deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",
        "deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",
        "dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",
        "dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
        "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
        "dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
        "i18n": "node build/bin/i18n.js",
        "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
        "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js",
        "test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
        "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
        "new": "node build/bin/new.js"
    },

1.npm run dist发生了什么?

01.npm run clean

清除 lib、子目录的lib、以及测试日志。

02.npm run build:file

。。。待补充,后面有时间补充,不影响开发。(建议先看看webpack源码,了解编译原理)

最终的打包后的文件在根目录的lib中。

七 思想:

1.为什么是这样的结构?为了实现编译后的代码,能让开发人员可以有选择的使用完整引入或着按需引入(组件的js文件、组件css样式文件、可复用工具函数等等)

2.新增组件的方式,全程自动化,减少了搬砖的工作量。

八 发版前准备

先看看package.json这个文件。

{
    "name": "自定义组件名-ui", npm包项目名,组件库的名字
    "version": "0.0.1-test",组件库版本号,每次发版都必须不同。
    "description": "A Component Library for Vue.js.",组件库描述
    "main": "lib/element-ui.common.js",组件库的主要入口文件
    "files": [
        "lib",
        "src",
        "packages",
        "types",
        "web-types.json"
    ],组件库的文件列表
    "typings": "types/index.d.ts",组件库类型定义文件
    "scripts": {
        "bootstrap": "yarn || npm i",
        "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
        "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
        "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
        "build:umd": "node build/bin/build-locale.js",
        "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
        "deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",
        "deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",
        "dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",
        "dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
        "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
        "dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
        "i18n": "node build/bin/i18n.js",
        "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
        "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js",
        "test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
        "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
        "new": "node build/bin/new.js"
    },
    "faas": [
        {
            "domain": "element",
            "public": "temp_web/element"
        },
        {
            "domain": "element-theme",
            "public": "examples/element-ui",
            "build": [
                "yarn",
                "npm run deploy:build"
            ]
        }
    ],组件库的函数即服务配置列表
    "repository": {
        "type": "git",
        "url": "git@github.com:ElemeFE/element.git"
    },组件库git仓库信息,自定义地址
    "homepage": "http://element.eleme.io",组件库开发文档地址,根据公司部署地址更改
    "keywords": [
        "eleme",
        "shunsh",
        "vue",
        "components"
    ],组件库关键词列表
    "license": "MIT",组件库许可证
    "bugs": {
        "url": "https://github.com/ElemeFE/element/issues"
    },bug反馈地址
    "unpkg": "lib/index.js",unpkg地址
    "style": "lib/theme-chalk/index.css",组件库样式文件地址
    "web-types": "./web-types.json",web类型定义文件地址,配合ts使用,需要自己手动添加。
    "dependencies": {
        "async-validator": "~1.8.1",
        "babel-helper-vue-jsx-merge-props": "^2.0.0",
        "deepmerge": "^1.2.0",
        "normalize-wheel": "^1.0.1",
        "resize-observer-polyfill": "^1.5.0",
        "throttle-debounce": "^1.0.1"
    },
    "peerDependencies": {
        "vue": "^2.5.17"
    },组件库依赖列表
    "devDependencies": {
        "@vue/component-compiler-utils": "^2.6.0",
        "algoliasearch": "^3.24.5",
        "babel-cli": "^6.26.0",
        "babel-core": "^6.26.3",
        "babel-loader": "^7.1.5",
        "babel-plugin-add-module-exports": "^0.2.1",
        "babel-plugin-istanbul": "^4.1.1",
        "babel-plugin-module-resolver": "^2.2.0",
        "babel-plugin-syntax-jsx": "^6.18.0",
        "babel-plugin-transform-vue-jsx": "^3.7.0",
        "babel-preset-env": "^1.7.0",
        "babel-preset-stage-2": "^6.24.1",
        "babel-regenerator-runtime": "^6.5.0",
        "chai": "^4.2.0",
        "chokidar": "^1.7.0",
        "copy-webpack-plugin": "^5.0.0",
        "coveralls": "^3.0.3",
        "cp-cli": "^1.0.2",
        "cross-env": "^3.1.3",
        "css-loader": "^2.1.0",
        "es6-promise": "^4.0.5",
        "eslint": "4.18.2",
        "eslint-config-elemefe": "0.1.1",
        "eslint-loader": "^2.0.0",
        "eslint-plugin-html": "^4.0.1",
        "eslint-plugin-json": "^1.2.0",
        "file-loader": "^1.1.11",
        "file-save": "^0.2.0",
        "gulp": "^4.0.0",
        "gulp-autoprefixer": "^6.0.0",
        "gulp-cssmin": "^0.2.0",
        "gulp-sass": "^4.0.2",
        "highlight.js": "^9.3.0",
        "html-webpack-plugin": "^3.2.0",
        "json-loader": "^0.5.7",
        "json-templater": "^1.0.4",
        "karma": "^4.0.1",
        "karma-chrome-launcher": "^2.2.0",
        "karma-coverage": "^1.1.2",
        "karma-mocha": "^1.3.0",
        "karma-sinon-chai": "^2.0.2",
        "karma-sourcemap-loader": "^0.3.7",
        "karma-spec-reporter": "^0.0.32",
        "karma-webpack": "^3.0.5",
        "launch-editor-middleware": "^2.3.0",
        "markdown-it": "^8.4.1",
        "markdown-it-anchor": "^5.0.2",
        "markdown-it-chain": "^1.3.0",
        "markdown-it-container": "^2.0.0",
        "mini-css-extract-plugin": "^0.4.1",
        "mocha": "^6.0.2",
        "node-sass": "^4.11.0",
        "optimize-css-assets-webpack-plugin": "^5.0.1",
        "postcss": "^7.0.14",
        "progress-bar-webpack-plugin": "^1.11.0",
        "rimraf": "^2.5.4",
        "sass-loader": "^7.1.0",
        "select-version-cli": "^0.0.2",
        "sinon": "^7.2.7",
        "sinon-chai": "^3.3.0",
        "style-loader": "^0.23.1",
        "transliteration": "^1.1.11",
        "uglifyjs-webpack-plugin": "^2.1.1",
        "uppercamelcase": "^1.1.0",
        "url-loader": "^1.0.1",
        "vue": "2.5.21",
        "vue-loader": "^15.7.0",
        "vue-router": "^3.0.1",
        "vue-template-compiler": "2.5.21",
        "vue-template-es2015-compiler": "^1.6.0",
        "webpack": "^4.14.0",
        "webpack-cli": "^3.0.8",
        "webpack-dev-server": "^3.1.11",
        "webpack-node-externals": "^1.7.2"
    }
}

再看看README.md这个文件。

这个文件主要是用于npm上readme内容的展示,根据需要,更改相关内容。 11.png 10.png

九 最后

npm publish(也可以调整脚本命令npm run pub的涉及到的相关脚本的内容,然后使用这个脚本命令发版)

这样,我们的项目就可以在npm上找到啦!