本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前面两篇文章怎么规划一款营销低代码可视化海报渠道、低代码海报渠道的修改器难点分析别离从海报渠道的全体架构和修改器难点两部分对乔巴海报搭建渠道做了讲解,信任看完这两篇,咱们关于渠道的主体功用和完成也有了大概的了解。今日这一篇,咱们会深入到组件库环节,比较传统的element-ui
、ant-design
,低代码渠道的组件库往往受众很小(一般都是为自身的渠道服务),规划时考虑的点也完全不同。
本篇文章会从组件库初始化、文字组件规划、图片组件规划、资料组件规划、组件库打包、组件库发布几个末节依次展开阐明。
组件库初始化
首先是组件库的初始化,因为项目自身是根据vue
的,所以这儿直接运用vue create xxx
来创立项目即可。
在我之前的文章从 Element UI 源码的构建流程来看前端 UI 库规划中有提过ElementUI
在运用时有两种引进办法:
- 大局引进
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
- 按需引进
import Vue from 'vue';
import { Pagination, Dropdown } from 'element-ui';
import App from './App.vue';
Vue.use(Pagination)
Vue.use(Dropdown)
new Vue({
el: '#app',
render: h => h(App)
});
这两种引进办法都用到了Vue.use
,这便是Vue的插件体系。
来简略了解一下:
经过vue官网,咱们知道插件一般用来为 Vue 增加大局功用。插件的功用规模没有严格的约束——一般有下面几种:
- 增加大局办法或许 property。
- 增加大局资源:指令/过滤器/过渡等。
- 经过大局混入来增加一些组件选项。
- 增加 Vue 实例办法,经过把它们增加到
Vue.prototype
上完成。 - 一个库,供给自己的 API,一起供给上面提到的一个或多个功用。
Vue.js 的插件应该暴露一个install
办法。这个办法的第一个参数是Vue
结构器,第二个参数是一个可选的选项目标。
了解了插件体系,结合咱们组件库的两种引进办法,咱们来对最开端的项目进行改造。
首先是按需引进,也便是单个组件导入而且作为插件运用:
import { CImage } from choba-lego-components
app.use(CImage)
这种需求每个组件新建一个文件夹,而且创立一个独自的index.ts
文件。
import { App } from "vue";
import CImage from "./CImage.vue";
CImage.install = (app: App) => {
app.component(CImage.name, CImage);
};
export default CImage;
组件规划为了插件,并具有install
办法。
终究还需求在大局进口文件导出:
import CImage from "./components/CImage";
export { CImage };
其次是大局引进这种办法,这种需求在大局进口文件index.ts
中将一切组件导入,放到一个数组中,相同创立install
办法,循环调用app.component
办法,最后默认导出install
函数:
import { App } from "vue";
import CText from "./components/CText";
import CImage from "./components/CImage";
import CShape from "./components/CShape";
const components = [CText, CImage, CShape];
const install = (app: App) => {
components.forEach((component) => {
app.component(component.name, component);
});
};
export default {
install,
};
改造完的项目目录结构为:
编写组件代码
现在乐高渠道的组件库还比较轻量,只要文字组件、图片组件和资料组件,比较一些成熟的组件库,如ant design
,还会划分为通用组件、布局组件、导航组件、数据录入组件、数据展现组件、反应型组件和其他。又或许像element ui
,组件划分为基础组件、表单组件、数据呈现组件、告知类组件、导航类组件和其他。
就现在来说,乐高渠道运用的配套组件库更倾向于纯展现类(这与海报的展现方法有关),会带一些轻交互。
所以组件规划维度其实是很简略的,更多处理是在款式维度。
经过上一篇文章低代码海报渠道的修改器难点分析咱们知道文字、图片、资料组件有很多通用特点:actionType
、url
、width
、height
、padding
、position
、border
、opacity
等,一起文字组件也具有自身的一些特有特点:font
、color
、textAlign
、textDecoration
,图片组件具有:imageSrc
,资料组件具有:backgroundColor
。
能够看到每一个组件都被分为了两大类:
- 款式特点
- 其他特点
关于款式特点,规划时又有两种计划:
- 直接在外部运用时传入一个
css
目标 - 每一个款式特点别离传入
在平常的业务开发中,我其实用第一种计划更多一些,但如果是在现在的组件库场景,显得就不是那么的合适。
之所以这么说是因为在上面分析的props
中,除了款式特点外还有一些是非款式特点(现在是点击相关),那么第一种计划的话就会多一层结构,而第二种别离传入的办法则是咱们现已把纯款式特点挑选后的成果了。
这儿拿其间的文字组件终究的代码来做进一步阐明:
<template>
<component
:is="tag"
:style="styleProps"
class="c-text-component"
@click.prevent="handleClick"
>
{{ text }}
</component>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import handleStylePick from "../handleStylePick";
import handleComponentClick from "../handleComponentClick";
import {
componentsDefaultProps,
convertToComponentProps,
isEditingProp,
} from "../../defaultProps";
const extraProps = {
tag: {
type: String,
default: "p",
},
...isEditingProp,
};
const defaultProps = convertToComponentProps(
componentsDefaultProps["c-text"].props,
extraProps
);
export default defineComponent({
name: "c-text",
props: {
tag: {
type: String,
default: "div",
},
...defaultProps,
},
setup(props) {
const styleProps = handleStylePick(props);
const handleClick = handleComponentClick(props);
return {
styleProps,
handleClick,
};
},
});
</script>
<style scoped>
h2.c-text-component,
p.c-text-component {
margin-bottom: 0;
}
button.c-text-component {
padding: 5px 10px;
cursor: pointer;
}
.c-text-component {
box-sizing: border-box;
white-space: pre-wrap;
}
</style>
首先经过handleStylePick
拿到纯款式特点
,然后经过handleComponentClick
拿到上面提到的其他特点
。这儿的handleStylePick
和handleComponentClick
是三个组件都会用到的通用办法。
handleStylePick
import { pick, without } from "lodash-es";
import { computed } from "vue";
import { textDefaultProps } from "../defaultProps";
export const defaultStyles = without(
Object.keys(textDefaultProps),
"actionType",
"url",
"text"
);
const handleStylePick = (props: any, pickStyles = defaultStyles) => {
return computed(() => pick(props, pickStyles));
};
export default handleStylePick;
handleComponentClick
const handleComponentClick = (props: any) => {
const handleClick = () => {
if (props.actionType && props.url && !props.isEditing) {
window.location.href = props.url;
}
};
return handleClick;
};
export default handleComponentClick;
至于本地组件库开发和调试能够运用
npm link
组件增加测验用例
咱们相同以文本组件为例来进行阐明。单元测验的意图是为了尽或许发布前用test case
的办法去测验组件功用的完整性和正确性,关于运用方来说也更具说服力。
针对文本组件,我写了三个case:
- CText组件能够正常烘托,包括特点
- 当有actionType和url特点时,点击CText组件能够正常跳转
- 当正在修改状况时,点击CText组件,即使具有actionType和url特点也不应该触发跳转
import { shallowMount } from "@vue/test-utils";
import CText from "../../src/components/CText";
import { textDefaultProps } from "../../src/defaultProps";
describe("CText.vue", () => {
const { location } = window;
beforeEach(() => {
Object.defineProperty(window, "location", {
writable: true,
value: { href: "" },
});
});
afterEach(() => {
window.location = location;
});
it("CText组件能够正常烘托,包括特点", () => {
const msg = "test";
const props = {
...textDefaultProps,
text: msg,
};
const wrapper = shallowMount(CText, { props });
expect(wrapper.text()).toBe(msg);
expect(wrapper.element.tagName).toBe("P");
const style = wrapper.attributes().style;
expect(style.includes("font-size")).toBeTruthy();
expect(style.includes("actionType")).toBeFalsy();
});
it("当有actionType和url特点时,点击CText组件能够正常跳转", async () => {
const props = {
...textDefaultProps,
actionType: "url",
url: "http://cosen95.cn/",
tag: "h2",
};
const wrapper = shallowMount(CText, { props });
expect(wrapper.element.tagName).toBe("H2");
await wrapper.trigger("click");
expect(window.location.href).toBe("http://cosen95.cn/");
});
it("当正在修改状况时,点击CText组件,即使具有actionType和url特点也不应该触发跳转", async () => {
const props = {
...textDefaultProps,
actionType: "url",
url: "http://cosen95.cn/",
tag: "h2",
isEditing: true,
};
const wrapper = shallowMount(CText, { props });
await wrapper.trigger("click");
expect(window.location.href).not.toBe("http://cosen95.cn/");
});
});
履行测验用例:
组件库打包
提到打包,必不可少的第一步便是打包东西的选择,以现在市面最为常用的两种打包东西来说:
webpack
我信任做前端的同学咱们都用过,那么为什么有些场景还要运用rollup
呢?这儿我简略对webpack
和rollup
做一个比较:
整体来说webpack
和rollup
在不同场景下,都能发挥自身优势效果。webpack
关于代码分割和静态资源导入有着“先天优势”,而且支撑热模块替换(HMR
),而rollup
并不支撑。
所以当开发应用时能够优先选择webpack
,可是rollup
关于代码的Tree-shaking
和ES6
模块有着算法优势上的支撑,若你项目只需求打包出一个简略的bundle
包,并是根据ES6
模块开发的,能够考虑运用rollup
。
其实webpack
从2.0
开端就现已支撑Tree-shaking
,并在运用babel-loader
的情况下还能够支撑es6 module
的打包。实际上,rollup
现已在渐渐地失去了当初的优势了。可是它并没有被扔掉,反而因其简略的API
、运用办法被许多库开发者喜爱,如React
、Vue
等,都是运用rollup
作为构建东西的。
显然经过一番对比后,rollup
作为组件库的打包计划是最合适不过的。
打包计划确认了,下一个要思考的问题便是应该打包什么类型的文件?
咱们能够先去看下现在Element UI
和Ant Design
别离都支撑什么样的装置办法:
能够看到,根本都一起支撑npm下载或许浏览器直接引进的办法,那么对应的终究打包文件便是ES Module
和UMD
格局。
到这儿,咱们就能够开端编写rollup
配置文件了 – rollup.config.js
:
import vue from "rollup-plugin-vue";
import css from "rollup-plugin-css-only";
import typescript from "rollup-plugin-typescript2";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import { name } from "../package.json";
const file = (type) => `dist/${name}.${type}.js`;
const overrides = {
compilerOptions: { declaration: true },
exclude: ["tests/**/*.ts", "tests/**/*.tsx"],
};
export { name, file };
export default {
input: "src/index.ts",
output: {
name,
file: file("esm"),
format: "es",
},
plugins: [
nodeResolve(),
typescript({ tsconfigOverride: overrides }),
vue(),
css({ output: "choba-lego-components.css" }),
],
external: ["vue", "lodash-es"],
};
这儿面用到的几个插件的含义为:
-
rollup-plugin-vue
:处理vue文件 -
rollup-plugin-css-only
:独自打包css文件 -
rollup-plugin-typescript2
:处理ts文件 -
@rollup/plugin-node-resolve
:rollup 无法辨认node_modules
中的包,协助 rollup 查找外部模块,然后导入
这儿主要还是以介绍项目中的rollup plugin为主,关于rollup更具体的可参阅我之前的文章: 一文带你快速上手Rollup
这儿着重说一下rollup-plugin-typescript2
,上面也提到了,组件库需求供给类型声明文件,也便是.d.ts
,那么就需求在配置时增加tsconfigOverride
:
const overrides = {
compilerOptions: { declaration: true },
exclude: ["tests/**/*.ts", "tests/**/*.tsx"],
};
这样在打包成果中就会包括对应的类型声明文件了。
有些场景下,尽管咱们运用了@rollup/plugin-node-resolve
插件,但或许咱们仍然想要某些库坚持外部引用状况,这时咱们就需求运用external
特点,来告知rollup.js
哪些是外部的类库。
这儿的vue
和lodash-es
都能够放到external
中。
有了这个基础的文件后,针对ES Module
和UMD
还要有独自的配置文件:
rollup.esm.config.js
import basicConfig, { name, file } from "./rollup.config";
export default {
...basicConfig,
output: {
name,
file: file("esm"),
format: "es",
},
};
rollup.umd.config.js
import basicConfig, { file } from "./rollup.config";
export default {
...basicConfig,
output: {
name: "ChobaLegoComponents",
file: file("umd"),
format: "umd",
globals: {
vue: "Vue",
"lodash-es": "_",
},
exports: "named",
},
};
中心差异在于format
,然后umd
的还要配置一下globals
(来供给大局变量名称)和exports
。
配置完成,来看下打包成果:
组件库发布
最后一步便是将组件库发布到npm
了。
第一步肯定是要调整package.json
为契合npm publish
的条件了。有以下几个字段要做相应调整:
-
name
:包的仅有标识,不能和其他包重名 -
version
:包版别号 -
private
:如果需求发布到npm的话,需求放开约束,也便是设置为false
-
main
:指定程序的主进口文件(这儿是dist/choba-lego-components.umd.js
) -
module
:指向的应该是一个根据 ES6 模块规范的运用ES5语法书写的模块 -
files
:用于描述你npm publish
后推送到npm
服务器的文件列表,如果指定文件夹,则文件夹内的一切内容都会包括进来
除了上面这些外还有像keywords
、homepage
这些不是特别重要的我就不赘述了。
下面我要补充说一下上面没讲到的dependencies
、devDependencies
和peerDependencies
。
dependencies
指定了项目运转所依靠的模块,开发环境和出产环境的依靠模块都能够配置到这儿。
有一些包有或许你只是在开发环境中用到,例如用于检测代码规范的eslint
或许是用于进行测验的jest
,用户运用你的包时即使不装置这些依靠也能够正常运转,反而装置他们会耗费更多的时间和资源,所以你能够把这些依靠增加到devDependencies
中,这些依靠照样会在你本地进行npm install
时被装置和办理,可是不会被装置到出产环境。
peerDependencies
用于指定你正在开发的模块所依靠的版别以及用户装置的依靠包版别的兼容性。这个或许有点难理解。就以咱们这个组件库为例,组件库依靠指定版别的vue
,那么就要保证组件库运用方的环境也一定要有对应版别的vue
依靠。这个时分,就能够把vue
配置到peerDependencies
中。
聊完这些,在命令行履行npm login
进行npm的登录:
然后能够运用npm whoami
验证是否已登录成功:
登录成功后,修改package.json
中的版别号,然后履行npm publish
:
出现上图的效果,就标明现已发布成功了。
这儿还有一个点在于,能够运用npm scripts
的钩子(prepublishOnly
),在publish
前做一些操作,比如对组件做lint
和test
,都经往后才真实履行publish
操作。
去npm官网看下:
1.0.5
便是刚刚发上去的版别。
看完了这些,去组件库运用的地方看一下:
现已正常烘托了。
到这儿,组件库从初始化、开发、增加测验用例、打包、运用方引进这一系列流程就现已梳理结束了~