preface

When it comes to front-end components, Vue technology has Element UI and React technology has Ant Design. These are the basic component libraries that the current front-end siege lions are unavoidable to actually use. In the actual work, we also have to sort out a set of component libraries suitable for our own business style according to our own work content. The basic component part can be customized based on the above open source component library and multi-theme style schemes such as LESS framework. But more often than not, there are a set of business component libraries that are tailored to their business products based on these basic components.

When it comes to development component library, or we choose Monorepo single warehouse package in the form of (see article segmentfault.com/a/119000001… And so on) or other forms of Git multi-repository single package to maintain component code, ultimately it is inevitable to put the component into a document, to provide other colleagues to reference use.

In this article, I will talk about a series of thought processes I went through to produce a component document, addressing the “last mile” of component development.

Component documentation

Specification and construction research

Component documentation is the first step in any software engineering phase. What is included in the documentation of a component determines the overall experience for both the developer and the user of the component. It is not difficult to identify these specifications, and we can get a sense of them by referring to the documentation in the open source component library.

Because our team uses the React stack, we refer to the Ant Design component library.

For the most basic Button component, the top-down content structure of an official document looks like this:

  1. Displays the component title, followed by a brief description of the component.
  2. Lists the usage scenarios of components.
  3. Effect demonstration and code cases under different usage scenarios. Can jump CodeSandbox, CodePen and other online source editor site editing real-time view effect.
  4. Lists the properties, interface methods that a component can configure. The list contains property/method names, field types, default values, usage descriptions, and so on.
  5. Faqs for frequently asked questions.
  6. Some Case description links for designers.

These documents are rich, and as an open component library, they are especially good to use from almost every aspect of the design to development perspective. Driven by curiosity, I went to check the official website source library, such as Button component: github.com/ant-design/… . Markdown files named.zh-cn. Md and.en-us. Md with the same name as the component entry files are placed in the source library. Yi? No, there seems to be something missing. Where are the case demonstrations and sample code?

Are AntD official website documents manually developed and maintained by themselves? For a project of this size, there are Markdown files in the source code library, such as docs/react/introduce-cn. The documents on the official website must have been generated by the official repository according to a specification. So how is it made? The first time I was doing component documentation I was intrigued.

As an experienced front-end engineer, I opened the package.json file skillfully and easily found the site command (source repository package.json) by looking at the scripts command:

npm run site:theme && cross-env NODE_ICU_DATA=node_modules/full-icu ESBUILD=1 bisheng build --ssr -c ./site/bisheng.config.js
Copy the code

It turns out the site was built using Bisheng. Bisheng is an auto-generating tool for a document system. Bisheng-plugin-react converts JSX source blocks from Markdown documents into examples that can run demos. The sample code documentation for each component itself is maintained in the Demo directory under each component path.

Emmm bisheng is really very good and powerful, can also support multiple languages, combined with a certain document specification constraints, can quickly build a document master site. However, in the process of in-depth understanding of Bisheng, I found that its documents are relatively lack, packaging things and so many, the use of black box feeling serious, and our team’s component library requirements are actually very simple, one is to facilitate circulation, but only in internal circulation, not open source. So, is there an easier way to structure documents?

More documentation tool library research

A Google search with keywords such as React Components Documentation quickly brings up a library of tools related to React component Documentation. Here’s what I saw: Docz, StoryBook, React Styleguidist, Dumi under UMI construction system, etc.

Both libraries support parsing Markdown documents, and Docz and StoryBook support MDX format (a hybrid of Markdown and JSX), as well as component property lists and sample code demonstrations in document content formats.

Next, let’s take a quick look at each of these libraries’ support for component documentation.

Docz

In the process of understanding, found that Docz is actually a relatively old document system building tool. It is itself a major push for MDX format documents, which require little configuration to run. Support for local debugging and build generation of release-able artifacts, support for multi-package repositories, TypeScript environments, CSS processors, plug-in mechanisms, and more.

Docz only seems to support the React component (which is fine for us, of course), and its NPM package was last updated two years ago. In addition, although the cost of understanding documents in MDX format is very low, there is still a certain cost of acceptance and proficiency for colleagues who do not use them much. Alternate for now.

StoryBook

When I first learned about StoryBook, I was surprised by its 66.7K Star rating (Docz is 22K). Compared to Docz, StoryBook has a very rich community that does not depend on the technology stack system of components. React, Vue, Angular, Web Components and dozens of other stacks are now supported.

Instead of automatically parsing Markdown files, StoryBook builds its documentation system by exposing a set of interfaces to build documentation, allowing developers to manually write stories files for components that StoryBook automatically parses to generate documentation content. This approach comes with a cost of learning and understanding the interface, but it also has the effect of supporting cross-component technology stacks and enriching the community.

Official example: github.com/storybookjs… .

StoryBook was great, to be sure, but it was overkill for our team. In addition, stories files that required additional understanding of interface functions and writing components were difficult to drive within the team: everyone was busy, component development was spread over dozens of people, and it was complicated, and it wasn’t practical to document to just one person. Continue your research.

React Styleguidist

React Styleguidist’s Star volume is not as impressive as StoryBook’s (10K+), but the package has a large number of downloads and has been actively submitted recently. As the name suggests, it supports the React component environment. It generates documents by automatically parsing Mardown files. The way to achieve this is to automatically parse JSX declaration code blocks in the documents, find component source code according to the rule of name one-to-one correspondence, and then pack the declared code blocks through Webpack to generate corresponding demonstration examples.

After continuing with some basic React Styleguidist examples, one feature of React Styleguidist really struck me: it automatically parses component properties, resolves their types, default values, comment descriptions, and so on, and then places the parsed content auto-generated property table on top of the demo example. This is a bit of JSDOC. For a component developer, it is true that they need to care about the disclosure of component properties, annotations, and documentation, but that is enough without having to worry about how to fit into a documentation system.

React Styleguidist uses the react-Docgen tool to parse components in typescript, and react-Docgen-typescript to parse components in typescript. There are also a number of configuration items that allow you to change the presentation style, content format, and so on of various parts of the document site, and configuration customization support is quite flexible.

Of course, it does have some disadvantages, such as the built-in Webpack, which is an additional configuration burden in cases where you have already changed the build tool that compiles the component library to rollup.js.

In general, React Styleguidist seems to me to be a small but beautiful library of tools that works well for teams with a lot of people involved and most of our daily development work. Alternate for now.

dumi

We knew about Dumi because we already had part of our component documentation site built around it. Dumi also implements the document system by automatically parsing Markdown documents. Similarly, there is basically no configuration, and many flexible configurations support changing the display content and (theme) style of some parts of the document site. As a whole, dumI has upheld the style of UMI system: out of the box, excellent encapsulation. It can be used alone or configured with UMI framework.

React Styleguidist doesn’t have any other obvious advantages over React Styleguidist. It doesn’t have the function to automatically parse component properties. Can refer to, no longer consider.

Generation of component documents

After comparing multiple document libraries, I finally chose React Styleguidist. In my opinion, it is the function of react-DocGen to parse component properties, types, comment descriptions and so on that attracts me. On the one hand, this function can standardize a series of specifications in the process of component development by team members with less extra time. On the other hand, the access form of API interface can unify the format and style of output document content through unified construction and configuration, which is convenient for business access and use.

After deciding on the technical solution, it is how to implement it based on its encapsulation of a tool, which is easy to access the business warehouse.

Our team has its own unified CLI build tool, and another CLI configuration of React Styleguidist will have some familiarity cost in understanding, but I can access it based on the Node API of React Styleguidist. Integrate React Styleguidist functionality into our own CLI dev and build commands.

First, we use the React Styleguidist API to abstract the code that generates the React Styleguidist example:

// Define a uniform set of configurations to generate a react-styleguidist instance
import styleguidist from 'react-styleguidist/lib/scripts/index.esm';
import * as docgen from 'react-docgen';
import * as docgenTS from 'react-docgen-typescript';

import type * as RDocgen from 'react-docgen';

export typeDocStyleguideOptions = { cwd? :string;
  rootDir: string;
  workDir: string; customConfig? :object;
};

const DOC_STYLEGUIDE_DEFAULTS = {
  cwd: process.cwd(),
  rootDir: process.cwd(),
  workDir: process.cwd(),
  customConfig: {}};export const createDocStyleguide = (
  env: 'development' | 'production',
  options: DocStyleguideOptions = DOC_STYLEGUIDE_DEFAULTS,
) = > {
  // 0. Process configuration items
  constopts = { ... DOC_STYLEGUIDE_DEFAULTS, ... options };const {
    cwd: cwdPath = DOC_STYLEGUIDE_DEFAULTS.cwd,
    rootDir,
    workDir,
    customConfig,
  } = opts;

  // Flag whether all packages are being debugged
  let isDevAllPackages = true;

  // Parse the project root package information
  const pkgRootJson = Utils.parsePackageSync(rootDir);

  // 1. Parse components under the specified package to be debugged
  let componentsPattern: (() = > string[]) | string | string[] = [];
  if (path.relative(rootDir, workDir).length <= 0) {
    // When debug all packages is selected, components under all packages defined by the Packages field under the root path are read
    const { packages = [] } = pkgRootJson;
    componentsPattern = packages.map(packagePattern= > (
      path.relative(cwdPath, path.join(rootDir, packagePattern, 'src/**/[A-Z]*.{js,jsx,ts,tsx}')))); }else {
    // When a package is selected to debug, the component under the selected package is located
    componentsPattern = path.join(workDir, 'src/**/[A-Z]*.{js,jsx,ts,tsx}');
    isDevAllPackages = false;
  }

  2. Get the default WebPack configuration
  const webpackConfig = getWebpackConfig(env);

  // 3. Generate a Styleguidist instance
  const styleguide = styleguidist({
    title: `${pkgRootJson.name}`.// All components to parse
    components: componentsPattern,
    // Attribute resolution Settings
    propsParser: (filePath, code, resolver, handlers) = > {
      if (/\.tsx? /.test(filePath)) {
        // ts files using the typescript docgen parser
        const pkgRootDir = findPackageRootDir(path.dirname(filePath));
        const tsConfigParser = docgenTS.withCustomConfig(
          path.resolve(pkgRootDir, 'tsconfig.json'), {});const parseResults = tsConfigParser.parse(filePath);
        const parseResult = parseResults[0];
        return (parseResult as any) as RDocgen.DocumentationObject;
      }
      // Use the default react-docGen parser for others
      const parseResults = docgen.parse(code, resolver, handlers);
      if (Array.isArray(parseResults)) {
        return parseResults[0];
      }
      return parseResults;
    },
    / / webpack configuration
    webpackConfig: { ...webpackConfig },
    // Whether to expand the code sample initially
    / / expand: | collapse: folding | hide: don't show;
    exampleMode: 'expand'.// Component path displays content
    getComponentPathLine: (componentPath) = > {
      const pkgRootDir = findPackageRootDir(path.dirname(componentPath));
      try {
        const pkgJson = Utils.parsePackageSync(pkgRootDir);
        const name = path.basename(componentPath, path.extname(componentPath));
        return `import ${name} from '${pkgJson.name}'; `;
      } catch (error) {
        returncomponentPath; }},// Do not display sidebar when all packages are not debugged
    showSidebar: isDevAllPackages,
    // Log configuration
    logger: {
      // One of: info, debug, warn
      info: message= > Utils.log('info', message),
      warn: message= > Utils.log('warning', message),
      debug: message= > console.debug(message),
    },
    // Override custom configurations. customConfig, });return styleguide;
};
Copy the code

In this way, the server interface method and the build interface method of the instance can be called respectively under the dev and build commands to debug and build the static resources of the output document.

Run the // dev command to start debugging
// 0. Perform initial configuration
const HOST = process.env.HOST || customConfig.serverHost || '0.0.0.0';
const PORT = process.env.PORT || customConfig.serverPort || '6060';

// 1. Generate styleGuide instances
const styleguide = createDocStyleguide(
  'development',
  {
	cwd: cwdPath,
	rootDir: pkgRootPath,
	workDir: workPath,
	customConfig: {
	  ...customConfig,
	  // dev server host
	  serverHost: HOST,
	  // dev server port
	  serverPort: PORT,
	},
  },
);

// 2. Call the server interface method to start debugging
const { compiler } = styleguide.server((err, config) = > {
  if (err) {
	console.error(err);
  } else {
	const url = `http://${config.serverHost}:${config.serverPort}`;
	Utils.log('info'.`Listening at ${url}`); }}); compiler.hooks.done.tap('done'.(stats: any) = > {
  const timeStr = stats.toString({
	all: false.timings: true});const statStr = stats.toString({
	all: false.warnings: true.errors: true});console.log(timeStr);

  if (stats.hasErrors()) {
	console.log(statStr);
	return; }});Copy the code
// Execute the build command

// Generate a StyleGuide instance
const styleguide = MonorepoDev.createDocStyleguide('production', {
  cwd,
  rootDir,
  workDir,
  customConfig: {
	styleguideDir: path.join(pkgDocsDir, 'dist'),}});// Build the document content
await new Promise<void> ((resolve, reject) = > {
  styleguide.build(
	(err, config, stats) = > {
	  if (err) {
		reject(err);
	  } else {
		if(stats ! =null) {
		  const statStr = stats.toString({
			all: false.warnings: true.errors: true});console.log(statStr);
		  if (stats.hasErrors()) {
			reject(new Error('Docs build failed! '));
			return;
		  }
		  console.log('\n');
		  Utils.log('success'.`Docs published to ${path.relative(workDir, config.styleguideDir)}`); } resolve(); }});Copy the code

Finally, configure the dev and build commands separately in package.json under each package in the component multipackage repository. Support insensitive startup debugging and build output document resources are realized.

summary

This article mainly introduces a thought process in my research on the implementation of component documentation specification and build process. As mentioned in other documentation system build tools, there are many excellent open source tools that can support the implementation of the desired effect, which is the front-end siege lion’s luck and misfortune: We can stand on the shoulders of predecessors, but to choose a suitable one in so many excellent libraries, we need to do more trade-offs between understanding and benefits. The old adage is that what suits you is best.

I hope this article was helpful to you as you read it.

Author: ES2049 / JIN Zhikai

The article can be reproduced at will, but please keep this link to the original text.

You are welcome to join ES2049 Studio. Please send your resume to [email protected].