The introduction

In our business, we always choose some open source common component libraries and embed them into the business system by simple reference, so as to improve development efficiency and unify the overall style. Our focus is often on how it is used, what attributes and methods a component exposes, and in what context certain attributes and methods are used.

But in fact, understanding the construction process and composition structure of a common component helps us to use components more flexibly in business, and also provides a theoretical basis for companies to develop their own common component library.

Today, I want to talk about the source code of ElememtUI and analyze its construction process and composition structure. Finally, summarize some of the work required for a complete component library.

Directory structure parsing

Directory structure is very important for large projects, reasonable and clear directory structure is very meaningful for the later development and expansion. The directory structure of ElementUI is:

  • .github

Store the Element UI contribution guide, Issue, and PR templates

  • build

Store configuration files related to packaging

  • examples

Store component-related sample demos

  • packages

Component source

  • src

Store entry files and some tool helper functions

  • test

Store unit test related files

  • types

Type declaration file

The rest is just a few files, two of the more important ones that relate to the build:

Components. json: indicates the file path of the component, which is easy to get when Webpack packs.

Makefile: Makefile is a tool for C/C++. If there is a Makefile in a directory that has a make environment. Typing make will execute one of the target commands in the Makefile.

Package. json: Usually when configuring a large project, you start with a package.json file, which contains key information about the project’s version, entry, script, dependencies, and so on.

packege.json

There are several key fields that we must set when building a component library project, and their configuration items are analyzed and explained below.

(1) Scripts: Scripts are the most important object in package.json, which contains scripts related to development, testing, production construction, packaging, deployment, test cases and so on. Script commands can be provided by scaffolding or customized to the needs of the project. The script commands used in Element are combed through the build process.

(2) main: the target entry file, the actual reference address when the external library is introduced. For example, the Element UI directive import Element from ‘element-ui’ introduces the file address configured in main. Lib/ element-ui.common.js is the CommonJS specification, while lib/index.js is the UMD specification. Lib is the file that is generated when the packaging command is run.

(3) Version: the version of the project. When releasing the project, control the version update and change. The same version cannot be released twice.

(4) files: Specify the files/directories that need to be included when publishing the package. Some files or directories are not necessary to be uploaded, such as examples.

(5) Style: Declare the style entry file because Element UI’s JS and CSS files are packaged separately.

packages

Contains component library source code and component style files.

  • Alert component

Main. vue is the component source and index.js is the entry file

The entry file provides the install method for components to add global components. Make Vue available via vue.use (Alert).

  • /theme-chalk

This is where all component-related styles are stored, named BEM, indexed. SCSS is responsible for referencing all component styles for global references, and individual component SCSS are used to import the corresponding component styles everywhere on demand.

The rest of the style file is a collection of utility functions, common styles, font libraries, and so on.

src

The packages folder above deals with each component separately, while SRC’s role is to unify all components in a single way, including custom directives, project entry, component internationalization, component mixins, animation encapsulation, and common methods.

Let’s analyze the entry file index.js

/* Automatically generated by ‘./build/bin/build-entry.js’ */

The hint index.js is automatically generated by the build-entry script.

examples

It can be considered as a separate Vues project, mainly used to display official documents. The Element UI supports four languages, so there are four folders under Docs, each corresponding to a language-related component example.

There are all MD documents in docs, and each MD document corresponds to the display page of the components on the official website. The MD document needs to be converted into a VUE file using the Loader when it is packaged, and then embedded into the display location.

The build process

The script command

Scripts in the scripts property. Develop, test, produce build, package, deploy, test case and other related scripts. The beauty of Element’s UI is its automated generation of scripts.

  • “bootstrap”: “yarn || npm i”

The bootstrap program stands for bootstrap. Yarn is preferred when installing dependencies.

  • The build:file directive is used to automate the generation of files.

build/bin/iconInit.js

Parse icon. SCSS, place all icon names in icon.json and finally hang $icon on Vue prototype.

build/bin/build-entry.js

SRC /index.js is automatically generated from components. Json, which is the entry file for the project.

build/bin/i18n.js

Using examples/i18n/page.json and templates to generate the home page in different languages, that is, the home page of the official website to show the processing of internationalization.

The templates used for internationalization in ElementUI’s official website are examples/ Pages/Template, which generate different files for different languages. There are.TPL files, each of which corresponds to a template, and each TPL file is a Vue file conforming to the SFC specification. They all have numbers that indicate where they need to be processed internationally. All internationalization-related field mappings on the home page are stored in examples/i18n/page.json.

I18n. js is responsible for the internationalization of the official website home page, through examples/i18n/page.json, according to different data structures to the TPL file flag bits, through the regular matching, and replaced with their own pre-set fields.

build/bin/version.js

Json to generate examples/versions.json, corresponding to the complete version list

  • Build :theme handles style dependencies

build/bin/gen-cssfile

This step is based on components. Json, generate package/theme-chalk/index.scss file, import all component styles into index.scss.

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

Compile all SCSS files under Packages/Theme-chalk to CSS, import index. CSS file when global import is needed; To import components on demand, import the corresponding CSS file.

Based on these requirements, Element decided it would be more convenient to use GULp to package style files based on workflow. Gulp-related processing is provided in Packages /theme-chalk/gulpfile.js.

cp-cli packages/theme-chalk/lib lib/theme-chalk

Cp – CLI is a cross-platform copy tool, similar to CopyWebpackPlugin. Copy the lib files under Packages /theme-chalk into the theme-chalk folder under the lib package file.

Build the instruction Makefile

It is common practice to put common scripts for projects in scripts in package.json. But ElementUI also uses makefiles. Run the make command to find the Makefile in that directory. Locate the corresponding command line parameter target in the Makefile.

  • Make new component-name [英 文]

Execute the build/bin/new.js file, which is all the files associated with building a component.

The development process

For example, when we start developing a test button component called button-test, we first make new to create a new component.

It will perform:

1. Add the new component to components.json

2. Create the SCSS file corresponding to the component under Packages /theme-chalk/ SRC and add it to packages/theme-chalk/ SRC /index.scss

3. Add to elder-ui.d.ts, the corresponding type declaration file

4. Create package (we mentioned above that source code related to components is stored in package directory)

5. Add it to the routing file nav.config.json (the menu on the left of the component).

Yarn Run Dev then starts the project on the development environment

NPM Run Bootstrap and NPM Run Build: File mentioned earlier, install dependencies first, then automate the generation of some files. Then run webpack-dev-server and start the runtime using the configuration in build/webpack.demo.js.

We can take a look at how md files are handled in webpack.demo.js. Element has developed an MD-loader of its own.

Packaging process

npm run clean

Clean up the previous package files.

npm run build:file

npm run lint

Code esLint checks, involving a series of files.

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

The configuration parameters used for webpack packaging are basically similar except for entry and Output.

Build /webpack.conf.js generates umD js files (index.js)

Build/webpack.common.js generates the commonJS file (element-ui.common.js) that is loaded by default when require.

Build/webpack.com ponent. Js to components. Json as the entrance, entrance packaging, packaged each component to generate a file, to load on demand.

npm run build:utils

Translate a single copy of all tool methods, transfer all SRC files except the index.js entry file through Babel and move them to lib.

Why do they need to be translated separately? Element considers that developers who are familiar with its source code can use these handy tools without downloading other packages.

like

const date = require('element-ui/lib/utils/date')
date.format(new Date.'HH:mm:ss')
Copy the code

npm run build:umd

Generate a language package for the UMD module

npm run build:theme

Generate style files

Release process PUB

1. Git release

2. NPM release

3. Official website release

sh build/git-release.sh

Code collision detection

Run git-releage.sh to check for git conflicts, mainly in the dev branch, where Element is developed.

Release NPM && official website update

The dev branch code detects no conflicts, and the relex.sh script is executed to merge the dev branch to master, update the version number, push the code to the remote repository, and publish to NPM (NPM publish).

The official website update is basically: generate static resources in the examples/ element-UI directory and put them in the GH-Pages branch so that they can be accessed through Github Pages.

Some supplement

md-loader

Loader is used to convert the module source code. Md-loader converts markdown files into Vue files.

In build/ Md-loader, the five files are responsible for:

  • Index.js entry file

  • Config.js Markdown -it configuration file

  • Containments.js render adds custom output configuration

  • Fence. js Modifies the fence rendering strategy

  • Util. Js Some functions that handle parsing MD data

You can start by looking at an MD demo component, wrapped in the code section

:::demo descprition
code
:::
Copy the code

In, this set of code has two functions, one is shown as an example, one is as a sample code. Put it all in the Demo-Block component.

 <div class="source">
   <slot name="source"></slot>
 </div>
 <div class="meta" ref="meta">
  <div class="description" v-if="$slots.default">
    <slot></slot>
  </div>
  <div class="highlight">
    <slot name="highlight"></slot>
  </div>
 </div>
Copy the code

In config.js, markdown-it-anchor is used to add anchor points to the page header and generate hyperlinks for the title, which can be quickly jumped.

Then use the methods exposed in config.js first in index.js to generate a new MD file.

const md = require('./config');

module.exports = function(source) {
  const content = md.render(source) 
  / /...
}
Copy the code

Then, in order to convert the MD file into a vUE file

<template>
</template>

<script>
export default{}</script>
Copy the code

Given that there are many similar demos in an MD, and the output is bound to be a VUE object, these demos must be packaged as components.

Create components for each demo using render functions.

The output script of the final source looks like:

<script>
export default {
  name: 'component-doc'.components: {
    component1: (function() {*render1* })(),
    component2: (function() {*render2* })(),
    component3: (function() {*render3* })(),
  }
}
</script>
Copy the code

The method of rendering the demo as a functional component is extracted from the template and script in the util. Js genInlineComponentText function, and compiled into Render Functioon through the plug-in.

Template is converted into the render function (as a configuration item) using vue-template-compiler, and the single-file component is compiled using the @vue/component-compiler-utils plug-in.

Compiled compiled. Code for each demo results in the following:

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h     $createElement = $createElement
  return _c(
    "div".// An HTML tag name
    [/ /... ] // The child node array is actually built from $createElement)}var staticRenderFns = []
render._withStripped = true
Copy the code

At this point, we also need to consider how to differentiate and name all the components of the demo. Each demo needs to be tagged, and a DemoBlock component is formed by wrapping the DemoBlock tag around each demo using the markdown-it-container plugin in containments.js.

There are three things in this Demo-block:

${description}  //(1) Description is the beginning of demo. Please return to demo.md to check<! --element-demo: ${content}:element-demo-->//(2) Before and after the display component is the tag, which will be found in index.js
${content} // (3) this thing will render overlay in fence.js file, modify its tag content
Copy the code

Finally, assemble all the demo components in index.js, like this

 let content = 'Description1 // Description Component1 // show Description2 componentCode2 // show code Description2
 output.push(content.slice(0))
 
 script = ``;
 
 return `
    <template>
      <section class="content element-doc">
        ${output.join(' ')}
      </section>
    </template>
    ${script}
  `
Copy the code

According to the need to load

Multi-entry packaging and code conversion plug-ins are used to achieve on-demand loading of JS logic files and CSS style files.

Multi-entry packaging is covered in the packaging process above, followed by easy component introduction in the business using transcoding tools. Importing a component requires importing both its logic and style code. The conversion tool is designed to simplify the reference process, and it does the conversion automatically at compile time. Suppose a component XXX is packaged and its style is in the path of componets/lib/ XXX. The code before and after the conversion tool is converted is as follows:

/ / before the conversion
import { xxx } from 'componets // import XXX form 'componets/lib/xxx' import 'componets/lib/xxx/xxx.css'
Copy the code

The AST (Abstract syntax tree) of Babel is used in the above conversion process. First, the @babel/ Parser module parses the code into an abstract syntax tree, and the @babel/ Traverse module is used to traverse the syntax tree to find relevant syntax nodes before conversion. Then, the @babel/types module is used to convert it into the syntax of the transformed component that is referenced separately. Finally, the @babel/ Generator module is used to generate new code strings from the modified abstract syntax tree, so as to complete the transformation of the syntax of the component loaded on demand while carrying out the forward compatibility of ES syntax.

Element’s official recommendation is to use the babel-plugin-Component plugin, which is configured in. Babelrc

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [["component",
      {
        "libraryName": "element-ui".// the js reference path
        "styleLibraryName": "theme-chalk" // CSS reference path}}]]Copy the code