从 ElementUI 源码的构建流程来看前端 UI 库设计

前端森林 2020-11-13 09:54:03
源码 elementUI 构建 流程 来看


image

引言

由于业务需要,近期团队要搞一套自己的UI组件库,框架方面还是Vue。而业界已经有比较成熟的一些UI库了,比如ElementUIAntDesignVant等。

结合框架Vue,我们选择在ElementUI基础上进行改造。但造轮子绝非易事,首先需要先去了解它整个但构建流程、目录设计等。

本文通过分析ElementUI完整的构建流程,最后给出搭建一个完备的组件库需要做的一些工作,希望对于想了解ElementUI源码或者也有搭建UI组件库需求的你,可以提供一些帮助!

我们先来看下ElementUI的源码的目录结构。

目录结构解析

  • github:存放了Element UI贡献指南、issuePR模板
  • build:存放了打包相关的配置文件
  • examples:组件相关示例 demo
  • packages:组件源码
  • src:存放入口文件和一些工具辅助函数
  • test:单元测试相关文件,这也是一个优秀的开源项目必备的
  • types:类型声明文件

说完文件目录,剩下还有几个文件(常见的.babelrc.eslintc这里就不展开说明了),在业务代码中是不常见的:
image

  • .travis.yml:持续集成(CI)的配置文件
  • CHANGELOG:更新日志,这里Element UI提供了四种不同语言的,也是很贴心了
  • components.json:标明了组件的文件路径,方便 webpack 打包时获取组件的文件路径。
  • FAQ.md:ElementUI 开发者对常见问题的解答。
  • LICENSE:开源许可证,Element UI使用的是MIT协议
  • Makefile:Makefile 是一个适用于 C/C++ 的工具,在拥有 make 环境的目录下, 如果存在一个 Makefile 文件。 那么输入 make 命令将会执行 Makefile 文件中的某个目标命令。

深入了解构建流程前,我们先来看下ElementUI 源码的几个比较主要的文件目录,这对于后面研究ElementUI的完整流程是有帮助的。

package.json

通常我们去看一个大型项目都是从package.json文件开始看起的,这里面包含了项目的版本、入口、脚本、依赖等关键信息。

我这里拿出了几个关键字段,一一的去分析、解释他的含义。

main

项目的入口文件

import Element from 'element-ui' 时候引入的就是 main中的文件

lib/element-ui.common.jscommonjs规范,而lib/index.jsumd规范,这个我在后面的打包模块会详细说明。

files

指定npm publish发包时需要包含的文件/目录。

typings

TypeScript入口文件。

home

项目的线上地址

unpkg

当你把一个包发布到npm上时,它同时应该也可以在unpkg上获取到。也就是说,你的代码既可能在NodeJs环境也可能在浏览器环境执行。为此你需要用umd格式打包,lib/index.jsumd规范,由webpack.conf.js生成。

style

声明样式入口文件,这里是lib/theme-chalk/index.css,后面也会详细说明。

scripts

开发、测试、生产构建,打包、部署,测试用例等相关脚本。scripts算是package.json中最重要的部分了,下面我会一一对其中的重要指令进行说明。
image

bootstrap

"bootstrap": "yarn || npm i"

安装依赖, 官方推荐优先选用yarn(吐槽一句:我刚开始没看明白,想着bootstrap不是之前用过的那个 ui 库吗 ,后来看了下,原来bootstrap翻译过来是引导程序的意思,这样看看也就大概理解了 )

build:file

该指令主要用来自动化生成一些文件。

"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js"

这条指令较长,我们拆开来看:

build/bin/iconInit.js

解析icon.scss,把所有的icon的名字放在icon.json里面 最后挂在Vue原型上的$icon上。

最后通过遍历icon.json,得到了官网的这种效果:
image

build/bin/build-entry.js

根据components.json文件,生成src/index.js文件,核心就是json-templater/string插件的使用。

我们先来看下src/index.js文件,他对应的是项目的入口文件,最上面有这样一句:

/* Automatically generated by './build/bin/build-entry.js' */

也就是src/index.js文件是由build/bin/build-entry.js脚本自动构建的。我们来看下源码:

// 根据components.json生成src/index.js文件
// 引入所有组件的依赖关系
var Components = require("../../components.json");
var fs = require("fs");
// https://www.npmjs.com/package/json-templater 可以让string与变量结合 输出一些内容
var render = require("json-templater/string");
// https://github.com/SamVerschueren/uppercamelcase 转化为驼峰 foo-bar >> FooBar
var uppercamelcase = require("uppercamelcase");
var path = require("path");
// os.EOL属性是一个常量,返回当前操作系统的换行符(Windows系统是\r\n,其他系统是\n)
var endOfLine = require("os").EOL;
// 生成文件的名字和路径
var OUTPUT_PATH = path.join(__dirname, "../../src/index.js");
var IMPORT_TEMPLATE =
"import {{name}} from '../packages/{{package}}/index.js';";
var INSTALL_COMPONENT_TEMPLATE = " {{name}}";
// var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */
// ...
// 获取所有组件的名字,存放在数组中
var ComponentNames = Object.keys(Components);
var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];
ComponentNames.forEach((name) => {
var componentName = uppercamelcase(name);
includeComponentTemplate.push(
render(IMPORT_TEMPLATE, {
name: componentName,
package: name,
})
);
if (
[
"Loading",
"MessageBox",
"Notification",
"Message",
"InfiniteScroll",
].indexOf(componentName) === -1
) {
installTemplate.push(
render(INSTALL_COMPONENT_TEMPLATE, {
name: componentName,
component: name,
})
);
}
if (componentName !== "Loading") listTemplate.push(` ${componentName}`);
});
var template = render(MAIN_TEMPLATE, {
include: includeComponentTemplate.join(endOfLine),
install: installTemplate.join("," + endOfLine),
version: process.env.VERSION || require("../../package.json").version,
list: listTemplate.join("," + endOfLine),
});
// 结果输出到src/index.js中
fs.writeFileSync(OUTPUT_PATH, template);
console.log("[build entry] DONE:", OUTPUT_PATH);

其实就是上面说的,根据components.json,生成src/index.js文件。

build/bin/i18n.js

根据 examples/i18n/page.json 和模版,生成不同语言的 demo,也就是官网 demo 展示国际化的处理。

ElementUI官网的国际化依据的模版是examples/pages/template,根据不同的语言,分别生成不同的文件:
image
这里面都是.tpl文件,每个文件对应一个模版,而且每个tpl文件又都是符合SFC规范的Vue文件。

我们随便打开一个文件:

export default {
data() {
return {
lang: this.$route.meta.lang,
navsData: [
{
path: "/design",
name: "<%= 1 >",
},
{
path: "/nav",
name: "<%= 2 >",
},
],
};
},
};

里面都有数字标示了需要国际化处理的地方。

首页所有国际化相关的字段对应关系存储在examples/i18n/page.json中:
image

最终官网展示出来的就是经过上面国际化处理后的页面:
image
支持切换不同语言。

绕了一圈,回到主题:build/bin/i18n.js帮我们做了什么呢?

我们思考一个问题:首页的展示是如何做到根据不同语言,生成不同的vue文件呢?

这就是build/bin/i18n.js帮我们做的事情。

来看下对应的源码:

"use strict";
var fs = require("fs");
var path = require("path");
var langConfig = require("../../examples/i18n/page.json");
langConfig.forEach((lang) => {
try {
fs.statSync(path.resolve(__dirname, `../../examples/pages/${lang.lang}`));
} catch (e) {
fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${lang.lang}`));
}
Object.keys(lang.pages).forEach((page) => {
var templatePath = path.resolve(
__dirname,
`../../examples/pages/template/${page}.tpl`
);
var outputPath = path.resolve(
__dirname,
`../../examples/pages/${lang.lang}/${page}.vue`
);
var content = fs.readFileSync(templatePath, "utf8");
var pairs = lang.pages[page];
Object.keys(pairs).forEach((key) => {
content = content.replace(
new RegExp(`<%=\\s*${key}\\s*>`, "g"),
pairs[key]
);
});
fs.writeFileSync(outputPath, content);
});
});

处理流程也很简单:遍历examples/i18n/page.json,根据不同的数据结构把tpl文件的标志位,通过正则匹配出来,并替换成自己预先设定好的字段。

这样官网首页的国际化就完成了。

build/bin/version.js

根据package.json中的version,生成examples/versions.json,对应就是完整的版本列表

build:theme

处理样式相关。

"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/bin/gen-cssfile

这一步是根据components.json,生成package/theme-chalk/index.scss文件,把所有组件的样式都导入到index.scss

其实是做了一个自动化导入操作,后面每次新增组件,就不用手动去引入新增组件的样式了。

gulp build --gulpfile packages/theme-chalk/gulpfile.js

我们都知道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),
});

对应两种引入方式,Element在打包时对应的也有两种方案。

具体如下:将packages/theme-chalk下的所有scss文件编译为css,当你需要全局引入时,就去引入index.scss文件;当你按需引入时,引入对应的组件scss文件即可。

这其中有一点,我们需要思考下:如何把packages/theme-chalk下的所有scss文件编译为css

在平时的开发中,我们打包、压缩之类的工作往往都会交给webpack去处理,但是,针对上面这个问题,我们如果采用gulp基于工作流去处理会更加方便。

gulp相关的处理就在packages/theme-chalk/gulpfile.js中:

"use strict";
const { series, src, dest } = require("gulp");
const sass = require("gulp-sass"); // 编译gulp工具
const autoprefixer = require("gulp-autoprefixer"); // 添加厂商前缀
const cssmin = require("gulp-cssmin"); // 压缩css
function compile() {
return src("./src/*.scss") // src下的所有scss文件
.pipe(sass.sync()) // 把scss文件编译成css
.pipe(
autoprefixer({
// 基于目标浏览器版本,添加厂商前缀
browsers: ["ie > 9", "last 2 versions"],
cascade: false,
})
)
.pipe(cssmin()) // 压缩css
.pipe(dest("./lib")); // 输出到lib下
}
function copyfont() {
return src("./src/fonts/**") // 读取src/fonts下的所有文件
.pipe(cssmin())
.pipe(dest("./lib/fonts")); // 输出到lib/fonts下
}
exports.build = series(compile, copyfont);

经过处理,最终就会打包出对应的样式文件

cp-cli packages/theme-chalk/lib lib/theme-chalk
cp-cli 是一个跨平台的 copy工具,和 CopyWebpackPlugin类似

这里就是复制文件到lib/theme-chalk下。

上面提到过多次components.json,下面就来了解下。

components.json

这个文件其实就是记录了组件的路径,在自动化生成文件以及入口时会用到:

{
"pagination": "./packages/pagination/index.js",
"dialog": "./packages/dialog/index.js",
"autocomplete": "./packages/autocomplete/index.js",
// ...
"avatar": "./packages/avatar/index.js",
"drawer": "./packages/drawer/index.js",
"popconfirm": "./packages/popconfirm/index.js"
}

packages

存放着组件库的源码和组件样式文件。

这里以Alert组件为例做下说明:

Alert 文件夹

image
这里main.vue对应就是组件源码,而index.js就是入口文件:

import Alert from "./src/main";
/* istanbul ignore next */
Alert.install = function (Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;

引入组件,然后为组件提供install方法,让Vue可以通过Vue.use(Alert)去使用。

关于 install可以看 官方文档

packages/theme-chalk

这里面存放的就是所有组件相关的样式,上面也已经做过说明了,里面有index.scss(用于全局引入时导出所有组件样式)和其他每个组件对应的scss文件(用于按需引入时导出对应的组件样式)

src

说了半天,终于绕到了src文件夹。

上面的packages文件夹是分开去处理每个组件,而src的作用就是把所有的组件做一个统一处理,同时包含自定义指令、项目整体入口、组件国际化、组件 mixins、动画的封装和公共方法。
image
我们主要来看下入口文件,也就是src/index.js

/* Automatically generated by './build/bin/build-entry.js' */
// 导入了packages下的所有组件
import Pagination from "../packages/pagination/index.js";
import Dialog from "../packages/dialog/index.js";
import Autocomplete from "../packages/autocomplete/index.js";
// ...
const components = [
Pagination,
Dialog,
Autocomplete,
// ...
];
// 提供了install方法,帮我们挂载了一些组件与变量
const install = function (Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
// 把所有的组件注册到Vue上面
components.forEach((component) => {
Vue.component(component.name, component);
});
Vue.use(InfiniteScroll);
Vue.use(Loading.directive);
Vue.prototype.$ELEMENT = {
size: opts.size || "",
zIndex: opts.zIndex || 2000,
};
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
/* istanbul ignore if */
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
// 导出版本号、install方法(插件)、以及一些功能比如国际化功能
export default {
version: "2.13.2",
locale: locale.use,
i18n: locale.i18n,
install,
Pagination,
Dialog,
Autocomplete,
// ...
};

文件开头的:

/* Automatically generated by './build/bin/build-entry.js' */

其实在上面的scriptsbuild/bin/build-entry.js中我们已经提到过:src/index.js是由build-entry脚本自动生成的。

这个文件主要做下以下事情:

  • 导入了 packages 下的所有组件
  • 对外暴露了install方法,把所有的组件注册到Vue上面,并在Vue原型上挂载了一些全局变量和方法
  • 最终将install方法、变量、方法导出

examples

存放了 ElementUI的组件示例。
image
其实从目录结构,我们不难看出这是一个完整独立的Vue项目。主要用于官方文档的展示:
image
这里我们主要关注下docs文件夹:
image
Element官网支持 4 种语言,docs一共有 4 个文件夹,每个文件夹里面的内容基本是一样的。

我们可以看到里面全部都是md文档,而每一个md文档,分别对应着官网组件的展示页面。

其实现在各大主流组件库文档都是用采用 md编写。

我们上面大致了解了源码的几个主要文件目录,但是都比较分散。下面我们从构建指令到新建组件、打包流程、发布组件完整的看一下构建流程。

构建流程梳理

构建指令(Makefile)

平时我们都习惯将项目常用的脚本放在package.json中的scripts中。但ElementUI还使用了Makefile文件(由于文件内容较多,这里就选取了几个做下说明):

.PHONY: dist test
default: help
# build all theme
build-theme:
npm run build:theme
install:
npm install
install-cn:
npm install --registry=http://registry.npm.taobao.org
dev:
npm run dev
play:
npm run dev:play
new:
node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
dist: install
npm run dist
deploy:
@npm run deploy
pub:
npm run pub
test:
npm run test:watch
// Tip:
// make new <component-name> [中文]
// 1、将新建组件添加到components.json
// 2、添加到index.scss
// 3、添加到element-ui.d.ts
// 4、创建package
// 5、添加到nav.config.json

我是第一次见,所以就去Google下,网上对Makefile对定义大概是这样:

Makefile 是一个适用于 C/C++ 的工具,较早作为工程化工具出现在 UNIX 系统中, 通过 make 命令来执行一系列的编译和连接操作。在拥有 make 环境的目录下, 如果存在一个 Makefile 文件。 那么输入 make 命令将会执行 Makefile 文件中的某个目标命令。

这里我以make install为例简要说明下执行流程:

  • 执行 make 命令, 在该目录下找到 Makefile 文件。
  • 找到 Makefile 文件中对应命令行参数的 install 目标。这里的目标就是 npm install

构建入口文件

我们看下scripts中的dev指令:

"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",

首先npm run bootstrap是用来安装依赖的。

npm run build:file在前面也有提到,主要用来自动化生成一些文件。主要是node build/bin/build-entry.js,用于生成Element的入口js:先是读取根目录的components.json,这个json文件维护着Element所有的组件路径映射关系,键为组件名,值为组件源码的入口文件;然后遍历键值,将所有组件进行import,对外暴露install方法,把所有import的组件通过Vue.component(name, component)方式注册为全局组件,并且把一些弹窗类的组件挂载到Vue的原型链上(这个在上面介绍scripts相关脚本时有详细说明)。

在生成了入口文件的src/index.js之后就会运行webpack-dev-server

webpack-dev-server --config build/webpack.demo.js

这个前面也提过,用于跑Element官网的基础配置。

新建组件

上面我们提到了,Element中还用了makefile为我们编写了一些额外的脚本。

这里重点说一下 make new <component-name> [中文] 这个命令。

当运行这个命令的时候,其实运行的是 node build/bin/new.js

build/bin/new.js比较简单,备注也很清晰,它帮我们做了下面几件事:

1、新建的组件添加到components.json

2、在packages/theme-chalk/src下新建对应到组件scss文件,并添加到packages/theme-chalk/src/index.scss

3、添加到 element-ui.d.ts,也就是对应的类型声明文件

4、创建package(我们上面有提到组件相关的源码都在package目录下存放)

5、添加到nav.config.json(也就是官网组件左侧的菜单)

打包流程分析

ElementUI打包执行的脚本是:

"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",

下面我们一一来进行分析:

npm run clean(清理文件)

"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",

删除之前打包生成文件。

npm run build:file(生成入口文件)

根据components.json生成入口文件src/index.js,以及i18n相关文件。这个在上面已经做过分析,这里就不再展开进行说明。

npm run lint(代码检查)

"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",

项目eslint检测,这也是现在项目必备的。

文件打包相关

webpack --config build/webpack.conf.js &&
webpack --config build/webpack.common.js &&
webpack --config build/webpack.component.js
build/webpack.conf.js

生成umd格式的js文件(index.js)

build/webpack.common.js

生成commonjs格式的js文件(element-ui.common.js),require时默认加载的是这个文件。

build/webpack.component.js

components.json为入口,将每一个组件打包生成一个文件,用于按需加载。

npm run build:utils(转译工具方法)

"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",

src目录下的除了index.js入口文件外的其他文件通过babel转译,然后移动到lib文件夹下。

npm run build:umd(语言包)

"build:umd": "node build/bin/build-locale.js",

生成umd模块的语言包。

npm run build:theme(生成样式文件)

"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",

根据components.json,生成package/theme-chalk/index.scss。用gulp构建工具,编译scss、压缩、输出csslib目录。

最后用一张图来描述上述整个打包流程:
image

发布流程

打包完成,紧跟着就是代码的发布了。Element中发布主要是用shell脚本实现的。

Element发布一共涉及三个部分:

1、git 发布

2、npm 发布

3、官网发布

发布对应的脚本是:

"pub":
"npm run bootstrap &&
sh build/git-release.sh &&
sh build/release.sh &&
node build/bin/gen-indices.js &&
sh build/deploy-faas.sh",

sh build/git-release.sh(代码冲突检测)

运行 git-release.sh 进行git冲突的检测,这里主要是检测dev分支是否冲突,因为Element是在dev分支进行开发的。

#!/usr/bin/env sh
# 切换至dev分支
git checkout dev
# 检测本地和暂存区是否还有未提交的文件
if test -n "$(git status --porcelain)"; then
echo 'Unclean working tree. Commit or stash changes first.' >&2;
exit 128;
fi
# 检测本地分支是否有误
if ! git fetch --quiet 2>/dev/null; then
echo 'There was a problem fetching your branch. Run `git fetch` to see more...' >&2;
exit 128;
fi
# 检测本地分支是否落后远程分支
if test "0" != "$(git rev-list --count --left-only @'{u}'...HEAD)"; then
echo 'Remote history differ. Please pull changes.' >&2;
exit 128;
fi
# 通过以上检查,表示代码无冲突
echo 'No conflicts.' >&2;

发布 npm && 官网更新

dev分支代码检测没有冲突,接下来就会执行release.sh脚本,合并dev分支到master、更新版本号、推送代码到远程仓库并发布到npm(npm publish)。

官网更新大致就是:将静态资源生成到examples/element-ui目录下,然后放到gh-pages分支,这样就能通过github pages的方式访问。

到这里ElementUI的完整构建流程就分析完了。

ui 组件库搭建指北

通过对ElementUI源码文件和构建流程的分析,下面我们可以总结一下搭建一个完备的 ui 组件库都需要做什么工作。

目录结构

目录结构对于大型项目是尤其重要的,合理清晰的结构对于后期的开发和扩展都是很有意义的。ui组件库的目录结构,我感觉ElementUI的就很不错:

|-- Element
|-- .babelrc // babel相关配置
|-- .eslintignore
|-- .eslintrc // eslint相关配置
|-- .gitattributes
|-- .gitignore
|-- .travis.yml // ci配置
|-- CHANGELOG.en-US.md
|-- CHANGELOG.es.md
|-- CHANGELOG.fr-FR.md
|-- CHANGELOG.zh-CN.md // 版本改动说明
|-- FAQ.md // 常见问题QA
|-- LICENSE // 版权协议相关
|-- Makefile // 脚本集合(工程化编译)
|-- README.md // 项目说明文档
|-- components.json // 组件配置文件
|-- element_logo.svg
|-- package.json
|-- yarn.lock
|-- .github // 贡献者、issue、PR模版
| |-- CONTRIBUTING.en-US.md
| |-- CONTRIBUTING.es.md
| |-- CONTRIBUTING.fr-FR.md
| |-- CONTRIBUTING.zh-CN.md
| |-- ISSUE_TEMPLATE.md
| |-- PULL_REQUEST_TEMPLATE.md
| |-- stale.yml
|-- build // 打包
|-- examples // 示例代码
|-- packages // 组件源码
|-- src // 入口文件以及各种辅助文件
|-- test // 单元测试文件
|-- types // 类型声明

组件开发

参考大多数 UI 组件库的做法,可以将 examples 下的示例代码组织起来并暴露一个入口,使用 webpack 配置一个 dev-server,后续对组件的调试、运行都在此 dev-server 下进行。

单元测试

UI 组件作为高度抽象的基础公共组件,编写单元测试是很有必要的。合格的单元测试也是一个成熟的开源项目必备的。

打包

对于打包后的文件,统一放在 lib 目录下,同时记得要在 .gitignore 中加上 lib 目录,避免将打包结果提交到代码库中。

同时针对引入方式的不同,要提供全局引入(UMD)和按需加载两种形式的包。

文档

组件库的文档一般都是对外可访问的,因此需要部署到服务器上,同时也需具备本地预览的功能。

发布

组件库的某个版本完成开发工作后,需要将包发布到 npm 上。发布流程:

  • 执行测试用例
  • 打包构建
  • 更新版本号
  • npm 包发布
  • 打 tag
  • 自动化部署

维护

发布后需要日常维护之前老版本,一般需要注意一下几点:

  • issue(bug 修复)
  • pull request(代码 pr)
  • CHANGELOG.md(版本改动记录)
  • CONTRIBUTING.md(项目贡献者及规范)

参考

️ 爱心三连击

1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号前端森林,定期为你推送新鲜干货好文。

3.特殊阶段,带好口罩,做好个人防护。
image

版权声明
本文为[前端森林]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000038155023

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple