preface

It’s been a long time since the Nuggets have been bubbling, but there’s a reason for that, too, having surgery in March and being out on medical leave for three months. After the operation, I lay down all the time. Because I hurt my spine, I started to stand up and exercise in April under the protection of a brace.

Before the accident, the company was preparing to build an internal UI library, so we studied some open source UI library schemes, here is a brief summary and share.

What do the individual component libraries do?

They usually use webPack or rollup packaging to generate an entry JS file, which is usually used when you don’t need to import component libraries on demand.

For example, the iView.js file in the dist directory of the iView component library.

import ViewUI from 'view-design';

// Introduces whole js files that may contain code for components you don't need
Vue.use(ViewUI);
Copy the code

Or the index.js file in the lib directory of the RSuite component library.

// Even if you don't use any other components, an entire JS file will be introduced
import { Button } from 'rsuite';

function App() {
  return <Button>Hello World</Button>;
}
Copy the code

If we don’t need to import all the components, we can’t package the component’s code into a JS file in the first place. We can compile each file in the SRC directory of the component library directly using Babel or gulp and write it to the target lib directory.


// Compile the directory of source code with 'gulp-babel'
function buildLib() {
  returnGulp.src (source directory).pipe(Babel (babelrc())).pipe(gulp.dest(target lib directory)); }Copy the code

The compiled directory structure is identical to the source directory structure, but the component code has already been processed by Babel.

At this point, we can implement importing component libraries on demand. But the import code in the business code needs to be changed. Let’s take the RSuite component library as an example. If we only want to use Button components, we need to specify that we only import the index.js file in the lib\Button directory.

/ / only introduce node_modules/rsuite/lib/Button/index. The js file
import Button from 'rsuite/lib/Button';
Copy the code

This is quite a hassle, but there is a ready-made solution, the babel-plugin-import plug-in. Suppose our packaged directory structure looks like the following.

We just need to do the following in.babelrc.


// .babelrc
{
  "plugins": [["import", {
        "libraryName": "react-ui-components-library"."libraryDirectory": "lib/components"."camel2DashComponentName": false}}]]Copy the code

The Babel plug-in automatically converts import {Button} from ‘component library’ to import Button from ‘component library /lib/components/Button’.

So how does babel-plugin-import work?

Implementation mechanism of babel-plugin-import

Babel-plugin-import source code I did not carefully study, just look at the general, many details are not very understand, if there is an error, please also include more.

Before we look at the babel-plugin-import source code, we also need to understand the concept of AST, the visitor, which is recommended to read this manual, the Babel plug-in Manual

The visitors to the import node are defined in the source code of babel-plugin-import (Babel uses the import visitors to process the import code node if it encounters an import statement when working with the source code)

Let’s see, what does the import code node look like to Babel

The tree on the right of the image is the path parameter in the visitor function


ImportDeclaration(path, { opts }) {
    const { node } = path;

    if(! node)return;

    const { value } = node.source;
    const libraryName = this.libraryName;
    const types = this.types;
    // If value is equal to the name of the library we set in the plug-in
    if (value === libraryName) {
      node.specifiers.forEach(spec= > {
        // Record the imported modules
        if (types.isImportSpecifier(spec)) {
          this.specified[spec.local.name] = spec.imported.name;
        } else {
          this.libraryObjs[spec.local.name] = true; }});// Delete the original node, that is, delete the previous import codepath.remove(); }}Copy the code

At the appropriate time, the import node of the modified import path is inserted


importMethod(methodName, file, opts) {
    if (!this.selectedMethods[methodName]) {
      const libraryDirectory = this.libraryDirectory;
      const style = this.style;
      // Change the import path of the module, such as antd -> antd /lib/button
      const path = `The ${this.libraryName}/${libraryDirectory}/${camel2Dash(methodName)}`;
      // Insert the import node of the modified import path
      this.selectedMethods[methodName] = file.addImport(path, 'default');
      if (style === true) {
        file.addImport(`${path}/style`);
      } else if(style === 'css') {
        file.addImport(`${path}/style/css`); }}return this.selectedMethods[methodName];
}
Copy the code