一 前言
- window系统下开发
- elementUI源码地址
二 nvm的使用(非必看)
elementUI源码使用的node版本14,如果与公司其它项目的node不同,建议安装nvm切换不同版本node,开发对应项目。
nvm在该项目中使用到的基本命令:
nvm install v14.17.6安装该版本的nodenvm 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 函数,注册各种指令、组件,挂载全局方法。
这里lib文件夹是样式相关文件编译后的结果。
03.examples文件部分变化
这里就是存放开发文档项目的文件夹了。
运行npm run dev,查看开发环境下的文档
04.单元测试用例npm run test
05.ts相关文件
目前我们的项目还没使用到ts,主要还是给使用了ts的项目使用的。
web-type.json文件,用于在代码编辑器中提供自动补全、代码提示等功能。
- name:表示组件或函数的名称,用于在代码编辑器中进行匹配和提示。
- description:对组件或函数的简短描述,提供给开发者参考。
- doc-url:链接到文档页面,提供更详细的文档和示例。
在使用代码编辑器时,当你输入组件或函数的名称时,编辑器会根据web-types.json文件中的信息,自动显示相关的提示信息,如参数类型、返回值类型、描述等。同时,点击链接,还可以跳转到文档页面,查看更详细的文档和示例。
五 目录结构
简单介绍主要的一些文件及文件夹
- 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内容的展示,根据需要,更改相关内容。
九 最后
npm publish(也可以调整脚本命令npm run pub的涉及到的相关脚本的内容,然后使用这个脚本命令发版)
这样,我们的项目就可以在npm上找到啦!