Ant Design начинается с нуля и знакомит вас с парадигмой фронтенд-разработки крупных заводов.

React.js

Пой днем ​​и мечтай ночью. - Джебран

  • Публичный аккаунт WeChat"Полный стек JavaScript
  • Наггетс»Мастер единства
  • Билибили»Мастер единства

Адрес склада Ант-Дизайн

Делать передок - это либо кидать, либо кидать по дороге.

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

Реализация спецификации

Поскольку это общий компонент или библиотека, он неотделим от следующих моментов:

  1. Сборка среды разработки
  2. Спецификация кода и тестирование
  3. код git фиксирует
  4. Бэйл
  5. выпуск

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

Сборка среды разработки

Давайте сначала посмотрим на структуру проекта

  • _site создал предварительный проект компонента
  • исходный код компонентов компонентов
  • файлы, сгенерированные пакетом dist
  • документы документация
  • файл типа es
  • исходный код пакета lib npm
  • файлы, связанные с проектом предварительного просмотра компонента определения сайта
  • тесты
  • определение типа ввода

В конструкции проекта для разработки библиотеки UI-компонентов есть две болевые точки:

  1. Создание ресурсов предварительного просмотра библиотеки компонентов пользовательского интерфейса для реализации предварительного просмотра процесса разработки библиотеки компонентов.
  2. Скомпилируйте и упакуйте код библиотеки компонентов для создания онлайн-кода

Увидев две вышеупомянутые проблемы в сочетании с нашей разработкой, мы можем сделать вывод, что предварительные проекты и упаковка требуют двух разных механизмов упаковки и компиляции, но, как правило, в проекте может использоваться только один метод упаковки, а именно: существует только одна конфигурация веб-пакета или один набор дифференцированных файлов среды компиляции. Итак, мы рассматриваем возможность использования двух разных методов упаковки в этих двух сценариях, и окончательное решение, которое мы выбираем:bisheng,antd-tools, вот объяснение,bishengЭто платформа, которая использует React для простого преобразования файлов Markdown, которые соответствуют соглашениям, для создания веб-страниц SPA; antd-tools определяет решения для обработки, связанные с упаковкой библиотеки компонентов ant-design.

bisheng

Процесс обработки bisheng выглядит следующим образом (поиск в общедоступной учетной записи WeChat:Полный стек JavaScript, смотрите видео объяснение)

базовая конфигурация

const path = require('path');
const CSSSplitWebpackPlugin = require('css-split-webpack-plugin').default;
const replaceLib = require('@ant-design/tools/lib/replaceLib');

const isDev = process.env.NODE_ENV === 'development';
const usePreact = process.env.REACT_ENV === 'preact';

function alertBabelConfig(rules) {
  rules.forEach(rule => {
    if (rule.loader && rule.loader === 'babel-loader') {
      if (rule.options.plugins.indexOf(replaceLib) === -1) {
        rule.options.plugins.push(replaceLib);
      }
      // eslint-disable-next-line
      rule.options.plugins = rule.options.plugins.filter(
        plugin => !plugin.indexOf || plugin.indexOf('babel-plugin-add-module-exports') === -1,
      );
      // Add babel-plugin-add-react-displayname
      rule.options.plugins.push(require.resolve('babel-plugin-add-react-displayname'));
    } else if (rule.use) {
      alertBabelConfig(rule.use);
    }
  });
}

module.exports = {
  port: 8001,
  hash: true,
  source: {
    components: './components',
    docs: './docs',
    changelog: ['CHANGELOG.zh-CN.md', 'CHANGELOG.en-US.md'],
  },
  theme: './site/theme',
  htmlTemplate: './site/theme/static/template.html',
  themeConfig: {
    categoryOrder: {
      'Ant Design': 0,
      原则: 7,
      Principles: 7,
      视觉: 2,
      Visual: 2,
      模式: 3,
      Patterns: 3,
      其他: 6,
      Other: 6,
      Components: 1,
      组件: 1,
    },
    typeOrder: {
      Custom: -1,
      General: 0,
      Layout: 1,
      Navigation: 2,
      'Data Entry': 3,
      'Data Display': 4,
      Feedback: 5,
      Other: 6,
      Deprecated: 7,
      自定义: -1,
      通用: 0,
      布局: 1,
      导航: 2,
      数据录入: 3,
      数据展示: 4,
      反馈: 5,
      其他: 6,
      废弃: 7,
    },
    docVersions: {
      '0.9.x': 'http://09x.ant.design',
      '0.10.x': 'http://010x.ant.design',
      '0.11.x': 'http://011x.ant.design',
      '0.12.x': 'http://012x.ant.design',
      '1.x': 'http://1x.ant.design',
      '2.x': 'http://2x.ant.design',
    },
  },
  filePathMapper(filePath) {
    if (filePath === '/index.html') {
      return ['/index.html', '/index-cn.html'];
    }
    if (filePath.endsWith('/index.html')) {
      return [filePath, filePath.replace(/\/index\.html$/, '-cn/index.html')];
    }
    if (filePath !== '/404.html' && filePath !== '/index-cn.html') {
      return [filePath, filePath.replace(/\.html$/, '-cn.html')];
    }
    return filePath;
  },
  doraConfig: {
    verbose: true,
  },
  lessConfig: {
    javascriptEnabled: true,
  },
  webpackConfig(config) {
    // eslint-disable-next-line
    config.resolve.alias = {
      'antd/lib': path.join(process.cwd(), 'components'),
      'antd/es': path.join(process.cwd(), 'components'),
      antd: path.join(process.cwd(), 'index'),
      site: path.join(process.cwd(), 'site'),
      'react-router': 'react-router/umd/ReactRouter',
      'react-intl': 'react-intl/dist',
    };

    // eslint-disable-next-line
    config.externals = {
      'react-router-dom': 'ReactRouterDOM',
    };

    if (usePreact) {
      // eslint-disable-next-line
      config.resolve.alias = Object.assign({}, config.resolve.alias, {
        react: 'preact-compat',
        'react-dom': 'preact-compat',
        'create-react-class': 'preact-compat/lib/create-react-class',
        'react-router': 'react-router',
      });
    }

    if (isDev) {
      // eslint-disable-next-line
      config.devtool = 'source-map';
    }

    alertBabelConfig(config.module.rules);

    config.module.rules.push({
      test: /\.mjs$/,
      include: /node_modules/,
      type: 'javascript/auto',
    });

    config.plugins.push(new CSSSplitWebpackPlugin({ size: 4000 }));

    return config;
  },

  devServerConfig: {
    public: process.env.DEV_HOST || 'localhost',
    disableHostCheck: !!process.env.DEV_HOST,
  },

  htmlTemplateExtraData: {
    isDev,
    usePreact,
  },
};

Этот файл определяет, как преобразовать указанный файл Markdown в веб-страницу предварительного просмотра в соответствии с какими правилами.

После определения файла нам нужно только выполнитьnpm startВы можете запустить предварительный проект, выполнитьnpm startФактически выполняется следующая команда

rimraf _site && mkdir _site && node ./scripts/generateColorLess.js && cross-env NODE_ENV=development bisheng start -c ./site/bisheng.config.js

antd-tools

antd-tools отвечает за упаковку компонентов, публикацию, защиту отправки, проверку и т. д.

antd-tools run dist
antd-tools run compile
antd-tools run clean
antd-tools run pub
antd-tools run guard

Функция каждой команды подробно описывается при объяснении соответствующего процесса.

Спецификация кода и тестирование

Этот проект используетTypescript, модульные тесты компонентов используютjestкомбинироватьenzyme. Мы берем кнопку в качестве примера, чтобы объяснить конкретный вариант использования. (Поиск в общедоступной учетной записи WeChat:Полный стек JavaScript, смотрите видео объяснение)

it('should change loading state instantly by default', () => {
  class DefaultButton extends Component {
    state = {
      loading: false,
    };

  enterLoading = () => {
    this.setState({ loading: true });
  };

  render() {
    const { loading } = this.state;
    return (
      <Button loading={loading} onClick={this.enterLoading}>
      Button
  </Button>
  );
}
   }
   const wrapper = mount(<DefaultButton />);
wrapper.simulate('click');
expect(wrapper.find('.ant-btn-loading').length).toBe(1);
});

Управление коммитами кода Git

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

Чтобы контролировать качество, нам нужно проверить, написан ли код в согласованном стиле кода, прежде чем код будет зафиксирован локально, если он не проходит проверку, фиксация не допускается.

мы используемhuskyЧтобы указать операцию при коммите, достаточно скачать хаски и настроить в package.json

"husky": {
  "hooks": {
    "pre-commit": "pretty-quick --staged"
  }
}

Хуки определяют хуки, с которыми мы хотим иметь дело во времени, намерение очевидно, мы хотим выполнить указанную операцию перед фиксацией. Проверка кода, которую мы используемpretty-quick.

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

упакованные компоненты

Что касается упаковки компонентов, мы инкапсулируем отдельную библиотеку инструментов для работы с - antd-tools. Мы следуем информации, раскрытой мне package.json, для анализа всего процесса. Соответствующие команды запуска следующие:

"build": "npm run compile && npm run dist",
"compile": "antd-tools run compile",
"dist": "antd-tools run dist",

compileиdistСм. конфигурацию команды в корневом пути проекта..antd-tools.config.js

function finalizeCompile() {
  if (fs.existsSync(path.join(__dirname, './lib'))) {
    // Build package.json version to lib/version/index.js
    // prevent json-loader needing in user-side
    const versionFilePath = path.join(process.cwd(), 'lib', 'version', 'index.js');
    const versionFileContent = fs.readFileSync(versionFilePath).toString();
    fs.writeFileSync(
      versionFilePath,
      versionFileContent.replace(
        /require\(('|")\.\.\/\.\.\/package\.json('|")\)/,
        `{ version: '${packageInfo.version}' }`,
      ),
    );
    // eslint-disable-next-line
    console.log('Wrote version into lib/version/index.js');

    // Build package.json version to lib/version/index.d.ts
    // prevent https://github.com/ant-design/ant-design/issues/4935
    const versionDefPath = path.join(process.cwd(), 'lib', 'version', 'index.d.ts');
    fs.writeFileSync(
      versionDefPath,
      `declare var _default: "${packageInfo.version}";\nexport default _default;\n`,
    );
    // eslint-disable-next-line
    console.log('Wrote version into lib/version/index.d.ts');

    // Build a entry less file to dist/antd.less
    const componentsPath = path.join(process.cwd(), 'components');
    let componentsLessContent = '';
    // Build components in one file: lib/style/components.less
    fs.readdir(componentsPath, (err, files) => {
      files.forEach(file => {
        if (fs.existsSync(path.join(componentsPath, file, 'style', 'index.less'))) {
          componentsLessContent += `@import "../${path.join(file, 'style', 'index.less')}";\n`;
        }
      });
      fs.writeFileSync(
        path.join(process.cwd(), 'lib', 'style', 'components.less'),
        componentsLessContent,
      );
    });
  }
}

function finalizeDist() {
  if (fs.existsSync(path.join(__dirname, './dist'))) {
    // Build less entry file: dist/antd.less
    fs.writeFileSync(
      path.join(process.cwd(), 'dist', 'antd.less'),
      '@import "../lib/style/index.less";\n@import "../lib/style/components.less";',
    );

    // eslint-disable-next-line
    console.log('Built a entry less file to dist/antd.less');
  }
}

мы углубляемсяantd-tools, переупакованный в node_modules/@ant-design/tools, процесс обработки передаетсяgulpда, см.gulpfile.js

// 编译处理
function compile(modules) {
  rimraf.sync(modules !== false ? libDir : esDir);
  const less = gulp
    .src(['components/**/*.less'])
    .pipe(
      through2.obj(function(file, encoding, next) {
        this.push(file.clone());
        if (
          file.path.match(/(\/|\\)style(\/|\\)index\.less$/) ||
          file.path.match(/(\/|\\)style(\/|\\)v2-compatible-reset\.less$/)
        ) {
          transformLess(file.path)
            .then(css => {
              file.contents = Buffer.from(css);
              file.path = file.path.replace(/\.less$/, '.css');
              this.push(file);
              next();
            })
            .catch(e => {
              console.error(e);
            });
        } else {
          next();
        }
      })
    )
    .pipe(gulp.dest(modules === false ? esDir : libDir));
  const assets = gulp
    .src(['components/**/*.@(png|svg)'])
    .pipe(gulp.dest(modules === false ? esDir : libDir));
  let error = 0;
  const source = ['components/**/*.tsx', 'components/**/*.ts', 'typings/**/*.d.ts'];
  // allow jsx file in components/xxx/
  if (tsConfig.allowJs) {
    source.unshift('components/**/*.jsx');
  }
  const tsResult = gulp.src(source).pipe(
    ts(tsConfig, {
      error(e) {
        tsDefaultReporter.error(e);
        error = 1;
      },
      finish: tsDefaultReporter.finish,
    })
  );

  function check() {
    if (error && !argv['ignore-error']) {
      process.exit(1);
    }
  }

  tsResult.on('finish', check);
  tsResult.on('end', check);
  const tsFilesStream = babelify(tsResult.js, modules);
  const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir));
  return merge2([less, tsFilesStream, tsd, assets]);
}

// 生成打包文件处理
function dist(done) {
  rimraf.sync(getProjectPath('dist'));
  process.env.RUN_ENV = 'PRODUCTION';
  const webpackConfig = require(getProjectPath('webpack.config.js'));
  webpack(webpackConfig, (err, stats) => {
    if (err) {
      console.error(err.stack || err);
      if (err.details) {
        console.error(err.details);
      }
      return;
    }

    const info = stats.toJson();

    if (stats.hasErrors()) {
      console.error(info.errors);
    }

    if (stats.hasWarnings()) {
      console.warn(info.warnings);
    }

    const buildInfo = stats.toString({
      colors: true,
      children: true,
      chunks: false,
      modules: false,
      chunkModules: false,
      hash: false,
      version: false,
    });
    console.log(buildInfo);

    // Additional process of dist finalize
    const { dist: { finalize } = {} } = getConfig();
    if (finalize) {
      console.log('[Dist] Finalization...');
      finalize();
    }

    done(0);
  });
}

Операция по упаковке компонентов завершается таким образом.Подробности можно найти в публичном аккаунте WeChat:Полный стек JavaScript

выпуск пакета

У всех нас есть ощущение, что каждый раз, когда мы отправляем посылку, мы пугаемся Достаточно ли мы подготовились? Является ли сборка этой сборкой? Модификация подтверждена? Как бы мы ни были осторожны, все равно будут некоторые ошибки, поэтому мы можем определить некоторые правила соглашения перед публикацией пакета, и только когда эти правила будут приняты, мы сможем опубликовать. Это то, что нам нужноnpmпредоставленный крючокprepublishдля обработки предварительных операций, которые определены вantd-toolsЛогика, указанная в . Мы также видим то, что мы видели вышеgulpfile.js.

gulp.task(
  'guard',
  gulp.series(done => {
    function reportError() {
      console.log(chalk.bgRed('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'));
      console.log(chalk.bgRed('!! `npm publish` is forbidden for this package. !!'));
      console.log(chalk.bgRed('!! Use `npm run pub` instead.        !!'));
      console.log(chalk.bgRed('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'));
    }
    const npmArgs = getNpmArgs();
    if (npmArgs) {
      for (let arg = npmArgs.shift(); arg; arg = npmArgs.shift()) {
        if (/^pu(b(l(i(sh?)?)?)?)?$/.test(arg) && npmArgs.indexOf('--with-antd-tools') < 0) {
          reportError();
          done(1);
          return;
        }
      }
    }
    done();
  })
);

определение скриптов в package.json

"prepublish": "antd-tools run guard",
"pub": "antd-tools run pub",

когда мы выполняемnpm publishВремяantd-tools run guardВыполнить, не позволяя нам напрямую использовать команду post, следует использоватьnpm run pubЧтобы выпустить приложение, для достижения соответствующего логического обнаружения перед выпуском.

Ну а здесь я расскажу как разрабатывается библиотека с нуля, думаю всем понятноAnt-DesignВесь процесс создания и упаковки компонентов будет хорошо подготовлен для работы с другими разрабатываемыми выпусками пакетов пользовательских библиотек.

Спасибо за ваше чтение и поддержку, я один, до свидания герои!