What functionality does a component library need?

Recently received a request to develop a document for your company’s component library. When I first received the demand, I was confused. However, careful analysis reveals that a UI library consists of three main parts:

  • Component: A library of components that can be compiled to be loaded in ways that are not available to modules
  • Usage document: You can view different parameter configurations of components
  • Component example: Easy for users to use, but also to find bugs during development

But how do you develop such a component library? Do you have to develop a separate project for “usage documentation” and “samples”? Of course, this is not desirable, because every time we add a new component, we have to go to the “Use documentation” and “sample” projects to write the corresponding functionality, and such code is not easy to maintain. Is it possible that we write components in a component directory, using documentation and samples, because after all, the people who develop components are also the people who know the components best, and the documentation and samples should also be written and maintained by the people who develop the components.

Directory structure of the Vant component library

So I went online and looked up some open source component libraries. When I saw the ant- UI, I was deeply attracted by its beautiful page design. So took a look at its source code structure, really good appearance also has an interesting soul.

Project Structure:

project

├ ─ SRCComponent source code

│ ├ ─ the buttonButton component source code

│ └ ─ dialog# Dialog component source code



├ ─ docsStatic document directory

│ ├ ─ home. The md# Document home page

│ └ ─ changelog. The md# update log



├ ─ Babel. Config. Js# Babel configuration file

├ ─ vant. Config. Js# Vant Cli configuration file

├ ─ pacakge. Json

└ ─ README. Md

Copy the code

Component structure:

button

├ ─ demo# sample directory

│ └ ─ index. Vue# Component example

├ ─ index. Vue# component source code

└ ─ README. Md# Component documentation

Copy the code

I was impressed by the clarity of the structure, but underneath the gorgeous exterior, a lot of transformation work was done.

Vant – the role of the cli

The Vant component library is mainly compiled and packaged by their in-house developed Vant-CLI. Ant-cli builds differently for development and production environments.

  • Development environment: The launch service is used to display UI library documentation for users to develop components, write component documentation, and sample
  • Production environment: Compile component libraries for different module loading methods

The construction of the Vant-CLI development environment

The Launch service is used to display the UI library documentation for developing components, writing component documentation and examples

The document is divided into three parts:

  • The UI library template
  • Component document
  • Components in the sample

The UI library template

The template style is defined in the ant- CLI scaffolding and can be found in the source @ant \cli\site\desktop\ Components \index.vue

Van - doc component

<div class="van-doc">

    <doc-header

      :lang="lang"

      :config="config"

      :versions="versions"

      :lang-configs="langConfigs"

      @switch-version="$emit('switch-version', $event)"

    />


    <doc-nav :lang="lang" :nav-config="config.nav" />

    <doc-container :has-simulator="!!!!! simulator">

      <doc-content>

// Component document slot

        <slot />

      </doc-content>

    </doc-container>

// Sample emulator

    <doc-simulator v-if="simulator" :src="simulator" />

</div>





<van-doc

      :lang="lang"

      :config="config"

      :versions="versions"

      :simulator="simulator"

      :lang-configs="langConfigs"

    >


// Display different component documents through different routes

    <router-view />

</van-doc>

Copy the code

Component document

The slot part is dynamically imported from outside the component via
, so we can switch different routes to different “component document.md “, but how can the MD document be imported through the vUE component?

  1. Import all “MD” files through the entry file
  2. Convert “MD” files to “VUE” files
How do I import all the “component document.md” files through the entry file?

VantCliSitePlugin was added to the WebPack configuration when the VantCliSitePlugin was built during the development environment.

plugins: [

    new vant_cli_site_plugin_1.VantCliSitePlugin(),

].

Copy the code

Two things are done in the genSiteEntry method in this plug-in

async function genSiteEntry({

.

    gen_site_mobile_shared_1.genSiteMobileShared();  // Generate entry files that import all "component document.md"

    gen_site_desktop_shared_1.genSiteDesktopShared();  // Generate import files for all "example.vue" entries

.

}

Copy the code

GenSiteDesktopShared generates the entry file code as follows

"import config from 'C:/Users/hongjunjie/Desktop/vant/vant-demo2/vant.config';

import Home from 'C:/Users/hongjunjie/Desktop/vant/vant-demo2/docs/home.md';

import Quickstart from 'C:/Users/hongjunjie/Desktop/vant/vant-demo2/docs/quickstart.md';

import DemoButton from 'C:/Users/hongjunjie/Desktop/vant/vant-demo2/src/demo-button/README.md';

import DemoUtils from 'C:/Users/hongjunjie/Desktop/vant/vant-demo2/src/demo-utils/README.md';



export { config };

export const documents = {

  Home,

  Quickstart,

  DemoButton,

  DemoUtils

};

Export const packageVersion = '1.0.0';

"


Copy the code
How to convert “MD” file to “VUE” file?

This time only introduced “component document.md” file, when will be converted to “vue” file, this scenario we naturally think of loader, just as “vue-loader” can parse “vue” file, can be found in webpack configuration

{

    test/\.md$/.

    use: [CACHE_LOADER, 'vue-loader'.'@vant/markdown-loader'].

},

@vant/markdown-loader can help us to"md"into"vue"file

Copy the code

Components in the sample

For better demonstration, the component sample should run separately on the phone, so it should be separated into a separate page. In the UI library document, load it as an iframe.

// Module component

<div :class="['van-doc-simulator', { 'van-doc-simulator-fixed': isFixed }]">

    <iframe ref="iframe" :src="src" :style="simulatorStyle" frameborder="0" />

  </div>

Copy the code

“SRC” is a separate page address passed in from the outside, so webPack builds with a separate package for the demo section. Webpack multi-entry file configuration, as follows

entry: {

    'site-desktop': [path_1.join(__dirname, '.. /.. /site/desktop/main.js')].

    'site-mobile': [path_1.join(__dirname, '.. /.. /site/mobile/main.js')].

},

output: {

    chunkFilename'[name].js'.

},

plugins: [

    new html_webpack_plugin_1.default({

        title,

        logo: siteConfig.logo,

        description: siteConfig.description,

        chunks: ['chunks'.'site-desktop'].

        template: path_1.join(__dirname, '.. /.. /site/desktop/index.html'),

        filename'index.html'.

        baiduAnalytics,

    }),

    new html_webpack_plugin_1.default({

        title,

        logo: siteConfig.logo,

        description: siteConfig.description,

        chunks: ['chunks'.'site-mobile'].

        template: path_1.join(__dirname, '.. /.. /site/mobile/index.html'),

        filename'mobile.html'.

        baiduAnalytics,

    }),

].

Copy the code

Because the genSiteDesktopShared method in VantCliSitePlugin above generates and introduces all the “componentexample.vue” entry files, it dynamically configures the route by traversing the component example to switch to different routes in the simulator to display the component example.

import { demos, config } from 'site-mobile-shared';

names.forEach(name= > {

    const component = decamelize(name);

.

      routes.push({

        name,

        path` /${component}`.

        component: demos[name],

        meta: {

          name,

        },

      });

  });

Copy the code

The construction of a production environment for ant- CLI

Compile component libraries for different module loading methods

This paper mainly analyzes the editing results of ES module and performs the following tasks in sequence during the construction of production environment

const tasks = [

    {

        text'Build ESModule Outputs'.

        task: buildEs,

    },

    {

        text'Build Commonjs Outputs'.

        task: buildLib,

    },

    {

        text'Build Style Entry'.

        task: buildStyleEntry,

    },

    {

        text'Build Package Entry'.

        task: buildPacakgeEntry,

    },

    {

        text'Build Packed Outputs'.

        task: buildPackages,

    },

];



async function buildEs({

    common_1.setModuleEnv('esmodule');

    // Copy the components written in the SRC directory to the es folder

    await fs_extra_1.copy(constant_1.SRC_DIR, constant_1.ES_DIR);

    await compileDir(constant_1.ES_DIR);

}



async function compileDir(dir{

    const files = fs_extra_1.readdirSync(dir);

    await Promise.all(files.map(filename= > {

        const filePath = path_1.join(dir, filename);

        // Delete the Demo and Test folders

        if (common_1.isDemoDir(filePath) || common_1.isTestDir(filePath)) {

            return fs_extra_1.remove(filePath);

        }

        // Recursively process subfolders

        if (common_1.isDir(filePath)) {

            return compileDir(filePath);

        }

        // Compile different files

        return compileFile(filePath);

    }));

}



async function compileFile(filePath{

    // Compile vue file: process vue file into js file, template=>render

    if (common_1.isSfc(filePath)) {

        return compile_sfc_1.compileSfc(filePath);

    }

    // Compile the js file

    if (common_1.isScript(filePath)) {

        return compile_js_1.compileJs(filePath);

    }

    / / compile CSS | less | SCSS document

    if (common_1.isStyle(filePath)) {

        return compile_style_1.compileStyle(filePath);

    }

    // Delete unnecessary files, such as md

    return fs_extra_1.remove(filePath);

}

Copy the code

The compiled components are as follows

button

├ ─ index. JsThe compiled JS file of the component

├ ─ index. The CSSComponent compiled CSS file

├ ─ but lessThe CSS file before the component is compiled can be LESS or SCSS

└ ─ styleImport styles on demand

├ ─ index. Js# Introduce compiled styles as needed

└ ─ less. Js# Introduce uncompiled styles on demand that can be used for theme customization

Copy the code

Once all components are compiled, there is still a main entry file missing to export all components

async function buildPacakgeEntry({

.

    const esEntryFile = path_1.join(constant_1.ES_DIR, 'index.js');

Generate the main entry file and export all components

    gen_package_entry_1.genPackageEntry({

        outputPath: esEntryFile,

        pathResolver(path) = > `. /${path_1.relative(constant_1.SRC_DIR, path)}`.

    });

.

}

Copy the code
function genPackageEntry(options{

    const names = common_1.getComponents();

    const version = process.env.PACKAGE_VERSION || constant_1.getPackageJson().version;

    const components = names.map(common_1.pascalize);

    // Main entry file code

    const content = `${genImports(names, options)}

        const version = '${version}';

        function install(Vue) {

// Register all components globally

        components.forEach(item => {

            if (item.install) {

            Vue.use(item);

            } else if (item.name) {

            Vue.component(item.name, item);

            }

        });

        }



if (typeof window ! == 'undefined' && window.Vue) {

        install(window.Vue);

        }



        export {

            install,

            version,

            ${genExports(components)}// Export all components

        };



        export default {

        install,

        version

} `


    common_1.smartOutputFile(options.outputPath, content);

}

Copy the code

Finally, the directory structure is built

project

├ ─ esThe code in the # es directory follows the ESModule specification

├ ─ buttonA directory of compiled code for the # button component

├ ─ dialogDirectory of compiled code for the # dialog component

└ ─ index. Js# Support tree Shaking by introducing entry to all components

Copy the code

conclusion

Unified component management entrance, including components, documentation, examples, for the environment does not need to build files for different purposes, the construction process is closed, we only care about the directory structure of components, documents, examples, easy maintenance and expansion.

Thanks to the open source work of the Vant team, we not only reduce the time spent reinventing the wheel, but also learn more valuable coding ideas and specifications. Finally, thank you for watching.