Writing in the front

In the previous article, the author introduced the development process of open source component library from 0 to 1. Although we have some understanding of component library development, the introduction is very general due to the limitations of space and the author’s writing style. Eat your food one bite at a time, and do it down to earth. So the author decided to introduce each part as clearly as possible in chapters. If you’re curious about how component libraries work, this article should be instructive. If you want to develop a component library quickly and don’t want to get to the bottom of the implementation, you can try to start component library development directly using Varlet-CLI, which is the focus of this article. (As an aside, varlet-CLI development environment has been migrated from Webpack5 to Vite, and is undergoing internal testing. If you want to try it, you can install the package @varlet/cli@alpha to experience it.)

Varlet component library related links, hope to encourage and support

Github Repository Chinese document English document

Why do YOU need to develop a CLI

A mature component usually goes through prototyping -> development -> unit testing -> documentation -> documentation -> document release -> component library build -> component release. We also have constraints on code style in collaborative development. Each of these sections seems to have a best practice (unit testing, for example, could use jest + @vue/test-utils) to help us solve it, but throwing these best practices into our project would have made it very cumbersome. Piles of configuration files, in particular, can make maintenance costs skyrocket. So we need to pack them all up and provide a unified entrance externally to solve different problems. The advantage of the CLI is that it can be provided to the user in the form of commands that solve a problem or a class of problems, which is nice.

Some basic concepts about Node

Cli is developed on Node after all, so there are a few things you need to know beforehand, and here are some of them.

  • __dirnameFile Location
  • process.cwd()Location where the command is executed
  • path.resolveSplicing paths to generate absolute paths
  • path.joinSplicing paths to generate a non-absolute path
  • requireNode introduces the method of module, dynamic introduction, after the introduction of a cache, immediately execute the module
  • require.resolveReturns the file location of the module without executing the file
  • The file paths on Windows and other platforms are different and need to be adapted

tool

Cli development also has some useful tools, here are some of the authors most commonly used tools.

  • Commander is used to parse commands and parameters. You can configure different handlers for different commands. Functions will receive the parameters passed along with the command.
  • Execa is used to call other command line tools. For example, if you have installed a third-party command line tool, you can use execa to call it in your program to make it work.
  • fs-extraPromise version of theFs module, and provides a lot of tool functions, do file operation necessary.
  • Loading effect of the org console
  • Inquirer can handle questions and answers interactively from the console.
  • Chalk can add styles and colors to text output from the console.
  • hash-sumCan be generatedhashstamp
  • slashUsed to convert Windows backslash paths to forward slash paths\ - > /

The above is the most often used several packages, the specific API, here will not expand the introduction, feel no nutrition

Implementation approach

Here are the three main commands we are going to introduce, with some code that has been cut down by the author (not guaranteed to work, just to help you understand the actual code)

  • devStart the development server, and after you start the documentation site, preview the documentation in real time
  • buildDocument site packaging
  • compileComponent library code packaging

dev

We need to start a development server to debug the components, and we also need a documentation site to introduce our components, so we just use the documentation site as the debugging environment for the components. Here we choose Vite as the development server, first we need to use our front-end technology to write a site (this is free to play), with a routing view to render our component documentation. The documentation is compiled into vue files by markdown. Here, @varlet/markdown-vite-plugin is used to achieve this function. When the service starts, we need to dynamically generate routes based on the SRC directory of the user and configure it as alias, so that we can use the alias to access routes in the site and configure the mount routes.

Vite related configuration can view packages/varlet – cli/SRC/config/vite. Config. Ts

The following vite plug-ins are used for reference only

  • @vitejs/plugin-vueProcess vUE files
  • @vitejs/plugin-vue-jsxProcessing JSX, TSX
  • vite-plugin-htmlInject HTML files
  • @varlet/markdown-vite-pluginCompile the Markdown file into vUE
import { createServer } from 'vite'
import { ensureDirSync } from 'fs-extra'
import { SRC_DIR } from '.. /shared/constant'
import { buildSiteEntry } from '.. /compiler/compileSiteEntry'
import { getDevConfig } from '.. /config/vite.config'
import { getVarletConfig } from '.. /config/varlet.config'
import { merge } from 'lodash'

export async function dev(cmd: { force? :boolean }) {
  // Set the environment
  process.env.NODE_ENV = 'development'
  // Make sure the SRC directory exists
  ensureDirSync(SRC_DIR)
  // Traverse the SRC directory to generate the routing configuration
  await buildSiteEntry()
  // Get the configured Vite server configuration
  const devConfig = getDevConfig(getVarletConfig())
  // Merge vite's forced refresh dependency options
  const inlineConfig = merge(devConfig, cmd.force ? { server: { force: true}}, {})// Create a Vite server instance
  const server = await createServer(inlineConfig)
  // Start the server
  await server.listen()
}
Copy the code

build

Run the site packaging, because of the Vite, it is recommended to use its own rollup for packaging, the process code is as follows.

import { ensureDirSync } from 'fs-extra'
import { SRC_DIR } from '.. /shared/constant'
import { build as buildVite } from 'vite'
import { getBuildConfig } from '.. /config/vite.config'
import { getVarletConfig } from '.. /config/varlet.config'
import { buildSiteEntry } from '.. /compiler/compileSiteEntry'

export async function build() {
  // Set the environment
  process.env.NODE_ENV = 'production'
  // Make sure the SRC directory exists
  ensureDirSync(SRC_DIR)
  // Traverse the SRC directory to generate the routing configuration
  await buildSiteEntry()
  // Get the build configuration for vite
  const varletConfig = getVarletConfig()
  const buildConfig = getBuildConfig(varletConfig)
  // Start packing
  await buildVite(buildConfig)
}
Copy the code

compile

Component library packaging can also be done directly using rollup’s library mode, but the authors recommend implementing a packager on your own because it is lightweight and fast. Importantly, you can control the packaging process and it is well worth implementing.

Assuming that the folder we need to generate is ES, we just need to copy the SRC directory to ES, recursively traverse all files in ES, compile and generate files using corresponding packages for different files, the packages used are as follows

  • .vue -> @vue/compiler-sfc
  • .less -> less
  • .js、.ts、.jsx、.tsx -> @babel/core

It takes time to learn how to use these packages, but they are API level things that are not difficult, as long as you are willing to spend some time, the results are very good.

The process code is as follows

import { EXAMPLE_DIR_NAME, TESTS_DIR_NAME, DOCS_DIR_NAME, SRC_DIR, ES_DIR, STYLE_DIR_NAME } from '.. /shared/constant'
import { isDir, isLess, isScript, isSFC } from '.. /shared/fsUtils'
import { compileSFC } from './compileSFC'
import { compileScriptFile } from './compileScript'
import { compileLess } from './compileStyle'
import { copy, ensureFileSync, readdir, removeSync } from 'fs-extra'

// What type of file is processed with the corresponding package
export async function compileFile(file: string) {
  isSFC(file) && (await compileSFC(file))
  isScript(file) && (await compileScriptFile(file))
  isLess(file) && (await compileLess(file))
  isDir(file) && (await compileDir(file))
}

// Process the directory
export async function compileDir(dir: string) {
  const dirs = await readdir(dir)

  await Promise.all(
    dirs.map((filename) = > {
      const file = resolve(dir, filename)

      // Delete folders that do not need to be compiled; [TESTS_DIR_NAME, EXAMPLE_DIR_NAME, DOCS_DIR_NAME].includes(filename) && removeSync(file)// Skip compiling some folders
      if (filename === STYLE_DIR_NAME) {
        return Promise.resolve()
      }

      // Compile the rest
      return compileFile(file)
    })
  )
}

export async function compile() {
  // Set the environment
  process.env.NODE_ENV = 'compile'
  // Make sure the SRC directory exists
  ensureDirSync(SRC_DIR)
  // copy the source package to the target location for source security. SRC -> es
  await copy(SRC_DIR, ES_DIR)
  // Read the es folder
  const moduleDir: string[] = await readdir(ES_DIR)
  // Process files recursively
  await Promise.all(
    moduleDir.map((filename: string) = > {
      const file: string = resolve(ES_DIR, filename)

      return isDir(file) ? compileDir(file) : null}}))Copy the code

Write the entry

This completes the pseudo-code for the three commands, and then we register them in the entry file

#! The /usr/bin/env node flag indicates that this is an executable file and is indispensable
import { parse, command } from 'commander'
import { dev } from './commands/dev'
import { build } from './commands/build'
import { compile } from './commands/compile'

command('dev')
  .option('-f --force'.'Force dep pre-optimization regardless of whether deps have changed')
  .description('Run varlet development environment')
  .action(dev)

command('build')
  .description('Build varlet site for production')
  .action(build)

command('compile')
  .description('Compile varlet components library code')
  .action(compile)
  
parse()

Copy the code

So far a simple component library command line is complete, of course the actual version is much more complex than this, but as learning and understanding, but for the spirit, not the shape.

Write in the last

The CLI section is also the most rewritten section of the Varlet library. From the earliest Webpack4 -> Webpack5 -> vite, the process is getting easier and easier, the sense of development experience is getting better and better, and the maintenance pressure is getting smaller and smaller. It’s fun to try your hand at making your own command-line tools (not necessarily component-oriented libraries) in your spare time. The first command line I remember doing was an interactive tool for pulling git repositories. It was only a few dozen lines of code, but it really helped me. Hope this article can read the article you play a little help, so I will be very happy. I wish you a happy National Day in advance. I also hope that interested friends can pay more attention to our open source project. If you are interested in the content, you can leave a message.