Принцип реализации внедрения библиотеки компонентов по требованию

React.js
Принцип реализации внедрения библиотеки компонентов по требованию

предисловие

Наггетсы уже давно не булькают.Конечно на то есть причины.В марте мне сделали операцию и я попросила больничный на 3 месяца. После операции я лежала, из-за травмы позвоночника, под защитой корсета, в апреле начала вставать и заниматься спортом.

WechatIMG237.jpeg

Перед аварией компания готовилась сделать внутреннюю библиотеку пользовательского интерфейса, поэтому я изучил некоторые решения библиотеки пользовательского интерфейса с открытым исходным кодом, и вот краткое изложение и обмен.

Как каждая библиотека компонентов делает это?

они обычно используютwebpackилиrollupУпакованный для создания входного js-файла, этот файл обычно используется, когда вам не нужно импортировать библиотеки компонентов по запросу.

Напримерiviewбиблиотека компонентовdistв каталогеiview.jsдокумент.

import ViewUI from 'view-design';

// 引入了整个js文件,里面可能包含了一些你不需要的组件的代码
Vue.use(ViewUI);

Другой примерrsuiteбиблиотека компонентовlibв каталогеindex.jsдокумент.

// 即使你没有使用其他组件,也会引入一整个js文件
import { Button } from 'rsuite';

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

Если нам не нужно вводить все компоненты, мы не можем изначально упаковать код компонентов в js-файл. мы можем напрямую использоватьbabelили с помощьюgulpк исходному коду библиотеки компонентовsrcКаждый файл в каталоге компилируется напрямую и записывается в целевойlibсодержание.


// 使用`gulp-babel`对源代码的目录进行编译
function buildLib() {
  return gulp
    .src(源码目录)
    .pipe(babel(babelrc()))
    .pipe(gulp.dest(目标lib目录));
}

WX20200512-223522@2x.png

WX20200512-223544@2x.png

Скомпилированная структура каталогов точно такая же, как структура каталогов исходного кода, но код компонента был обработан Babel.

В настоящее время мы можем импортировать библиотеки компонентов по запросу. Но в бизнес-кодеimportКод должен быть изменен. мы начинаем сrsuiteНапример, библиотека компонентов. Если мы просто хотим использоватьButtonкомпонент, нам нужно указать, что только импортlib\ButtonПод содержаниемindex.jsдокумент.

// 只引入 node_modules/rsuite/lib/Button/index.js 文件
import Button from 'rsuite/lib/Button';

Это действительно довольно неприятно, но, к счастью, уже есть готовые решения,babel-plugin-importплагин. Предположим, наша упакованная структура каталогов выглядит так, как показано ниже.

WX20200512-225547@2x.png

нам просто нужно.babelrcСделайте следующие настройки.


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

Плагин babel автоматическиimport { Button } from '组件库'Перевести вimport Button from '组件库/lib/components/Button'.

Такbabel-plugin-importКак это делается?

Механизм реализации babel-plugin-import

Исходный код babel-plugin-import я не изучал внимательно, а читал только бегло, не очень хорошо понимаю многие детали, если есть ошибки, то дополняйте.

Прежде чем разобраться в исходном коде babel-plugin-import, нам также необходимо понять концепцию ast и посетителя, эти концепции рекомендуется прочитать каждому в этом руководстве.Руководство по плагину Babel

Исходный код babel-plugin-import определяетimportпосетитель узла (babelПри обработке исходного кода, если вы столкнулисьimportзаявление, будет использоватьimportпара посетителейimportузел кода для обработки)

Давайте сначала посмотрим,importКак выглядят узлы кода в глазах Babel

image.png

Дерево с правой стороны изображения — это дерево в функции посетителя.pathпараметр


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

    if (!node) return;

    const { value } = node.source;
    const libraryName = this.libraryName;
    const types = this.types;
    // 如果value等于我们在插件中设置的库的名称
    if (value === libraryName) {
      node.specifiers.forEach(spec => {
        // 记录引入的模块
        if (types.isImportSpecifier(spec)) {
          this.specified[spec.local.name] = spec.imported.name;
        } else {
          this.libraryObjs[spec.local.name] = true;
        }
      });
      // 删除原有的节点,就是删除之前的import代码
      path.remove();
    }
  }

В нужный момент будет вставлен измененный путь импортаimportузел


importMethod(methodName, file, opts) {
    if (!this.selectedMethods[methodName]) {
      const libraryDirectory = this.libraryDirectory;
      const style = this.style;
      // 修改模块的引入路径,比如 antd -> antd/lib/Button
      const path = `${this.libraryName}/${libraryDirectory}/${camel2Dash(methodName)}`;
      // 插入被修改引入路径的 import 节点
      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];
}