What do you think of the source code of 7K Star's famous open source project?

Hello, I'm Pitang. Recently, a small partner in the programmer bus learning exchange group wants to know how to look at the source code. Recently, I have some experience and understanding. I also wrote an article on actually running through NaiveUI source code: How is the component library recommended by Youdu developed? Source code experience, to share with you.

Psychological cognition should be in place

First of all, we should realize that looking at the source code is a relatively boring process with a relatively long time span. Therefore, the first step to look at the source code is to find projects that are highly related to the field you want to know or your business field, and are well-known and active in maintenance in this field.

For example, tiktok is a front end, and there are many new contents in the front end area, such as Unbundled Vite, new framework Svelte, new assembly language WebAssembly, CSS engineering project TailwindCSS, and component library such as open source library with loud voice. Semi Design , or the popular Vue3 component library NaiveUI in the community.

I've always been obsessed with component library and CSS, and I'm one of the leaders in charge of front-end engineering CSS infrastructure in the group, so let me study the source code of a component library, such as NaiveUI, so I'm very interested, motivated and justified, and this is also one of the core driving forces driving you to gnaw the next source code.

Understanding climax MVP

Secondly, we see that the source code should have certain skills, such as complexity React , it can be regarded as a simple operating system. If you come up to understand the source code by simply and rudely starting from the code entry, you will want to vomit no matter how hard you insist.

What's the technique here? Just like our Internet entrepreneurship, if you have a big and comprehensive idea, but your first step is certainly not to find an empty house, prepare food for half a year, prepare several computers, and then develop them intensively for a few months, and then pray to amaze the world as soon as they come out. One is that this situation is very rare. The other is that you have to have the capital and patience to persist for several months.

In the field of entrepreneurship, a well-known and far-reaching skill is MVP, that is, the minimum feasible product. You need to make a very small product that can just be used and test your ideas, then quickly push it to the market, receive user feedback, then follow up user feedback, and constantly iterate the product to meet user needs until PMF is reached (products match the market). At this time, you can basically find investment, scale it, and then raise funds and go to NASDAQ to ring the bell.

Therefore, the second thing we need to pay attention to in the field of source code is that you need to find the smallest MVP that an open source project can run, remove other complex dependencies, and the most core processes and mechanisms. This MVP that can help you understand the core of the project, I call it climax MVP -- that is, if you can run through the MVP of a project, your heart will I am very happy. I feel that my achievements are slow and my excitement has reached a climax. Next, other contents and branches basically reuse this set of MVP, add bricks and tiles to it, and supplement some compatible details.

Before starting

So for NaiveUI, what is its climax MVP?

Let's first open its official website:

Click on to start using:

As a component library, it generally needs to talk about its own values, design principles, customization methods, and content related to ICON icons, but it is not very helpful for you to understand the source code, so you need to omit these interference items.

Let's take a look at its source code repository:

Make sure that the library is written in what language, so that you can measure whether your current knowledge reserve can support you to understand the source code before reading the source code. Of course, if you do not have the corresponding support reserve, but insist on reading the source code, you should first consider language learning reserve in advance according to the language it uses.

See through the essence

Let's go back to NaiveUI's official website:

It can be seen that for a "component library", the most basic is actually "component", and behind the composition of "component", a series of more basic elements are required, such as color, spacing, border, background, font, etc.

So is our goal clear? Take down a "button" component and understand all the necessary processes, knowledge and details behind such a button. For other components, basically 90% of the logic can be reused, and only the remaining 10% of the specific functional requirements can be understood.

Similar to the iceberg below:

Under the iceberg belongs to that 90%. We comb the core process of the whole component library based on a seemingly simple "button" component, which can help us understand the whole source code quickly and accurately. Therefore, our climax MVP is to understand the whole process of a "button" component.

Understand context

After understanding what our climax MVP goal is, the next step is to read all the relevant descriptions of the Button in the document with this goal. You can see that the Button contains the following contents:

Through the directory on the right, we can see that a button first has basic contents, including default, primary, info, success, warning and error, and then needs to be processed:

  • Border related: dashed lines
  • Dimension related: dimension, administration
  • Colors: Custom Colors
  • Status: text, disabled, loading
  • event

The above shows the effects and operations that can be achieved by the Button. After understanding, we can then understand the API related to the Button. Through the API and the effects that can be achieved, we can roughly understand the inputs and outputs received by the Button.

What does an example of using a Button look like

<template>

  <n-space>

    <n-button>Default</n-button>

    <n-button type="primary">Primary</n-button>

    <n-button type="info">Info</n-button>

    <n-button type="success">Success</n-button>

    <n-button type="warning">Warning</n-button>

    <n-button type="error">Error</n-button>

  </n-space>

</template>

Learn how to open a project

Generally, the convenience of open source projects is that they will have detailed documents. At the same time, they are eager for contributors to join, so they will have perfect contribution guidelines, such as NaiveUI Contribution Guide As follows:

Through the contribution guide, you can understand how to install dependencies, deal with some startup project problems, and run the project for debugging. This is usually your first experience of understanding the whole code running process.

Understand the project structure of the target project

Usually when you go to this step, you should know the following:

  • You have understood your goal. What is the climax MVP
  • You understand your target content as a functional feature, what is its input and output
  • Do you understand what the technology stack of this project is and how to run the project

Corresponding to NaiveUI, our three points are as follows:

  • Climax MVP: runs through a Button and can be used. It maintains the same characteristics as the existing Button, receives the same input and produces the same output
  • Button contains border, size, color, status, event and other related contents. Input these parameters to output the output under corresponding conditions
  • The technical stack of the project is Vue3 and TypeScript, the construction tool is Vite, and the CSS BEM framework is used CSS Render At the same time, the package management tool uses pnpm

After understanding these three points, we need to compare the source code to understand the entire file directory and understand the dependencies of each directory, as shown in the figure below.

We can first understand what each folder does:

  • src: This is mainly used to store the component code related to the component library and export some contents related to internationalization, style and theme customization. It is generally the core development directory of an open source project
  • scripts: some script logic related to running code, building and publishing
  • Theme: is the default theme built into NaiveUI. Component libraries like this generally allow users to customize themes. When using various UI properties, all components of NaiveUI follow this set of themes, that is, you can modify the content in theme or completely customize a set of themes yourself
  • . github,. husky, etc. are configurations that do not require too much attention and can be directly added to your MVP template project
  • playground is the supporting code used to run the code in various environments, such as SSR
  • demo is an example of a website that introduces src related content to show the actual effect of components. In fact, NaiveUI is the official website of the document we saw earlier
  • Others, such as build and design notes, are construction products, or some theme design notes, which basically do not belong to the Department that needs to read the source code. Students who are interested can have a look

Then there are some files for various engineering configurations, such as:

  • . Prettier RC: Prettier related
  • . gitignore: Git related
  • . eslintrc.js: ESLint related
  • babel.config.js: Babel related
  • jest.config.js: jest test related
  • postcss.config.js: Handling CSS related
  • tsconfig.xx.json handling TypeScript related
  • vite.config.js: vite build tool related configuration

And some CHANGELOG.xx.md, which is strongly related to the project and used to understand the whole idea development context, as well as the configuration contribution guide for runthrough code mentioned earlier.

A little confused. 🀯

Create your climax MVP project

After understanding the project directory structure of NaiveUI, we can start to create our climax MVP project, but before that, we can make another simplification, that is, we can not:

  • For directory

    • We don't need. github,. husky, playground and scripts. We just need to test the most basic environment and run through it during development
    • theme is only the design system followed by the whole NaiveUI. It will be followed in other parts, but it will not be directly referenced, so we can not
    • In this way, we only have demo and src, and further, we can make the demo into src. For the whole src, we change its responsibility into the climax MVP website portal, and then the following code of the original remaining src is used to import it into the src portal file
  • For profiles:

    • Test related, Jest et al. We don't need it
    • TypeScript is related. We can iterate later without introducing unnecessary complexity and type constraints
    • We don't need ESLint and Prettier. We can rely on the default formatting of the editor. Of course, it doesn't matter to introduce these two into our initial climax MVP project

After simplification, our climax MVP project only needs the following files:

  • Build the project and provide Vite related content of the development server: vite.config.js
  • babel.config.js for providing syntax translation
  • Project dependency file package.json
  • The main code src for the runthrough project and the index.html entry template

The directory structure is as follows:

.

β”œβ”€β”€ babel.config.js

β”œβ”€β”€ index.html

β”œβ”€β”€ node_modules

β”œβ”€β”€ package.json

β”œβ”€β”€ public

β”œβ”€β”€ src

β”œβ”€β”€ vite.config.js

└── yarn.lock

It's very concise. There's no redundant and complicated content, right? It's also very easy to understand.

Copy the contents of the remaining files to be created from the NaiveUI project directory, and then install the corresponding dependencies.

Run through process

After we created our climax MVP project according to the source code library, we should be able to run now, but the content is just a simple Button, because in order to run the project quickly, our entry file src/App.vue will be as follows:

<template>

  <t-button>hello tuture</t-button>

</template>



<script>

import { defineComponent } from "vue";

import { TButton } from "./components";



export default defineComponent({

  name: "App",

  components: {

    TButton,

  },

});

</script>

The corresponding src/components/TButton.vue is as follows:

<template>

  <button>{$slots.default}</button>

</template>



<script>

import { defineComponent } from "vue";

import { TButton } from "./components";



export default defineComponent({

  name: "Button"

});

</script>

Next, we will try to understand the NaiveUI code again, migrate these trunk codes to our climax MVP project again, and then ensure that we can continue to run during the migration process. Although we may encounter that sometimes a dependency requires a large number of pre dependencies, so we need to migrate a large section of code to run the project.

Find the core entrance

To complete all the pre dependencies of a Button, we only need to go to the project directory file corresponding to NaiveUI and find the code corresponding to the Button, as follows:

In fact, the code for parsing the component file is as follows:

  • Pre import dependency
  • Define componentsdefinecomponent

    • The component handles the incoming and use of props and the definition and use of its own state
    • Template code
  • Export components

All the contents related to TS definitions in the code above are unnecessary, so we can delete the contents related to type definitions such as ButtonProps, NativeButtonProps, MergedProps and XButton.

We can also delete the import part related to type definition:

import type { ThemeProps } from '../../_mixins'

import type { BaseWaveRef } from '../../_internal'

import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils'

import type { ButtonTheme } from '../styles'

import type { Type, Size } from './interface'

After deleting these irrelevant codes, what is left in our code?

Import dependency:

import {
  h,
  ref,
  computed,
  inject,
  nextTick,
  defineComponent,
  PropType,
  renderSlot,
  CSSProperties,
  ButtonHTMLAttributes
} from 'vue'
import { useMemo } from 'vooks'
import { createHoverColor, createPressedColor } from '../../_utils/color/index'
import { useConfig, useFormItem, useTheme } from '../../_mixins'
import {
  NFadeInExpandTransition,
  NIconSwitchTransition,
  NBaseLoading,
  NBaseWave
} from '../../_internal'
import { call, createKey } from '../../_utils'
import { buttonLight } from '../styles'
import { buttonGroupInjectionKey } from './ButtonGroup'
import style from './styles/button.cssr'
import useRtl from '../../_mixins/use-rtl'

Component declaration section:

const Button = defineComponent({
  name: 'Button',
  props: buttonProps,
  setup(props) {
    // Define component status
    const selfRef = ref<HTMLElement | null>(null)
    const waveRef = ref<BaseWaveRef | null>(null)
    const enterPressedRef = ref(false)
    
    // Use Props or inject global state
    const NButtonGroup = inject(buttonGroupInjectionKey, {})
    const { mergedSizeRef } = useFormItem(...)
    const mergedFocusableRef = computed(() => {...})
    
    // Define component event handling
    const handleMouseDown = (e: MouseEvent): void => {...}
    const handleClick = (e: MouseEvent): void => {...}
    const handleKeyUp = (e: KeyboardEvent): void => {...}
    const handleKeyDown = (e: KeyboardEvent): void => {...}
    const handleBlur = (): void => {...}
    
    // Process the theme of the component and obtain the corresponding style of the Button component in the whole global design system
    const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
    const themeRef = useTheme(...)
    const rtlEnabledRef = useRtl(...)
    
     // The theme styles related to its own state and global state, the values of various CSS attributes and the content related to events are processed and returned to the template for use
     return {
      selfRef,
      waveRef,
      mergedClsPrefix: mergedClsPrefixRef,
      mergedFocusable: mergedFocusableRef,
      mergedSize: mergedSizeRef,
      showBorder: showBorderRef,
      enterPressed: enterPressedRef,
      rtlEnabled: rtlEnabledRef,
      handleMouseDown,
      handleKeyDown,
      handleBlur,
      handleKeyUp,
      handleClick,
      customColorCssVars: computed(() => {...}),
      cssVars: computed(() => {...})
    }
   },
   render() {
   // Handle the style rendering related to various components and the content related to event processing. The style rendering here corresponds to the state that the Button mentioned in the document can present and the operations that can be handled
    const { $slots, mergedClsPrefix, tag: Component } = this
    return (
      <Component
        ref="selfRef"
        class={[
          `${mergedClsPrefix}-button`,
          `${mergedClsPrefix}-button--${this.type}-type`,
          {
            [`${mergedClsPrefix}-button--rtl`]: this.rtlEnabled,
            [`${mergedClsPrefix}-button--disabled`]: this.disabled,
            [`${mergedClsPrefix}-button--block`]: this.block,
            [`${mergedClsPrefix}-button--pressed`]: this.enterPressed,
            [`${mergedClsPrefix}-button--dashed`]: !this.text && this.dashed,
            [`${mergedClsPrefix}-button--color`]: this.color,
            [`${mergedClsPrefix}-button--ghost`]: this.ghost // required for button group border collapse
          }
        ]}
        tabindex={this.mergedFocusable ? 0 : -1}
        type={this.attrType}
        style={this.cssVars as CSSProperties}
        disabled={this.disabled}
        onClick={this.handleClick}
        onBlur={this.handleBlur}
        onMousedown={this.handleMouseDown}
        onKeyup={this.handleKeyUp}
        onKeydown={this.handleKeyDown}
      >
        {$slots.default && this.iconPlacement === 'right' ? (
          <div class={`${mergedClsPrefix}-button__content`}>{$slots}</div>
        ) : null}
        <NFadeInExpandTransition></NFadeInExpandTransition>
        {$slots.default && this.iconPlacement === 'left' ? (
          <span class={`${mergedClsPrefix}-button__content`}>{$slots}</span>
        ) : null}
        {!this.text ? (
          <NBaseWave ref="waveRef" clsPrefix={mergedClsPrefix} />
        ) : null}
        {this.showBorder ? ( ...)}
        {this.showBorder ? (...)}
     </Component>
   )
  }
})

Further simplify the code

From the remaining code above, we can see that for understanding the component library, most of our content is to customize themes, and then show the work of different themes according to various incoming props, so you will see that the Button component is filled with a large number of CSS variables, such as this.color, this.ghost, this.text and this.cssVars, Therefore, our core is to understand how these topics are customized, which variables and dependencies are included, and how these variables and dependencies affect how buttons can carry different styles and functions.

Therefore, we can delete some contents in the above code:

  • We only need to see how an independent Button works, so the nbutton group and Button group can be omitted
  • We also don't need to deal with some unique adaptations, such as RTL (right to left typesetting), etc

So we need to delete these codes further:

import { buttonGroupInjectionKey } from './ButtonGroup'

import useRtl from '../../_mixins/use-rtl'



const NButtonGroup = inject(buttonGroupInjectionKey, {})

And other contents related to the use of buttonGroup.

Understanding input

In the previous step, we basically achieved all the most concise contents of the Button required in our final climax MVP project except for all irrelevant contents, that is, our core entry code and dependent parts have been determined, so we need to deal with all inputs next, And delete the dependent logic irrelevant to Button processing in these inputs.

We can see that the Button mainly has the following inputs:

  • import input at the top of the file
  • Use the hook useFormItem, or global state to inject input related to inject(...)

We can see that import related inputs are mainly divided into two categories:

  • Import of some libraries, such as vue: we only need to query the documents of the corresponding libraries to understand the role of the API
  • It directly depends on other relative path imports of its own project: we need to continue to explore other parts of NaiveUI source code library

The input related to hook useFormItem or global state injection inject(...) also depends on other relative paths of its own item in import.

We need to perform dependency analysis along the following dependencies:

import { createHoverColor, createPressedColor } from '../../_utils/color/index'

import { useConfig, useFormItem, useTheme } from '../../_mixins'

import {

  NFadeInExpandTransition,

  NIconSwitchTransition,

  NBaseLoading,

  NBaseWave

} from '../../_internal'

import { call, createKey } from '../../_utils'

import { buttonLight } from '../styles'

import { buttonGroupInjectionKey } from './ButtonGroup'

import style from './styles/button.cssr'

Some of these dependencies are leaf dependencies, and there are no other dependencies, such as:

import { createHoverColor, createPressedColor } from "../../_utils/color/index";



// Some of them

import { useFormItem } from "../../_mixins";



// Some of the following

import {

  NFadeInExpandTransition,

  NIconSwitchTransition,

} from "../../_internal";



import { call, createKey, getSlot, flatten } from "../../_utils";

These leaf dependencies can directly establish the corresponding directory structure and file name against the original warehouse, and then copy the code.

For those non leaf dependencies, we need to continue to resolve their dependencies and repeat the previous two operations:

  • Delete TS or other codes and dependencies irrelevant to the Button
  • Find the dependency on which it depends and continue the above process

Finally, create the same structure according to the directory structure of the source code, and copy the code that handles irrelevant content.

For example, for non leaf dependent style s:

import style from "./styles/button.cssr.js";

We need to go to the corresponding file and check its dependencies:

import { c, cB, cE, cM, cNotM } from '../../../_utils/cssr'

import fadeInWidthExpandTransition from '../../../_styles/transitions/fade-in-width-expand.cssr'

import iconSwitchTransition from '../../../_styles/transitions/icon-switch.cssr'

It is found that it depends on the cssr Library (self built) used for the definition of BEM specification and some fadeInWidthExpandTransition and iconSwitchTransition dependencies for processing animation. Then continue to enter these dependencies, such as:

import { c, cB, cE, cM, cNotM } from '../../../_utils/cssr'

Its dependencies are as follows:

 /* eslint-disable @typescript-eslint/restrict-template-expressions */

import CSSRender, { CNode, CProperties } from 'css-render'

import BEMPlugin from '@css-render/plugin-bem'

It is found that there are no other dependencies that need to be searched recursively. They are all imported third-party libraries. You can check the documentation of the corresponding third-party library to understand the meaning of API.

Repeat the above dependency analysis until convergence. Finally, we will get the following file organization chart:

.
β”œβ”€β”€ App.vue
β”œβ”€β”€ _internal
β”‚   β”œβ”€β”€ fade-in-expand-transition
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── src
β”‚   β”‚       └── FadeInExpandTransition.jsx
β”‚   β”œβ”€β”€ icon
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── src
β”‚   β”‚       β”œβ”€β”€ Icon.jsx
β”‚   β”‚       └── styles
β”‚   β”‚           └── index.cssr.js
β”‚   β”œβ”€β”€ icon-switch-transition
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── src
β”‚   β”‚       └── IconSwitchTransition.jsx
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ loading
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── src
β”‚   β”‚       β”œβ”€β”€ Loading.jsx
β”‚   β”‚       └── styles
β”‚   β”‚           └── index.cssr.js
β”‚   └── wave
β”‚       β”œβ”€β”€ index.js
β”‚       └── src
β”‚           β”œβ”€β”€ Wave.jsx
β”‚           └── styles
β”‚               └── index.cssr.js
β”œβ”€β”€ _mixins
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ use-config.js
β”‚   β”œβ”€β”€ use-form-item.js
β”‚   β”œβ”€β”€ use-style.js
β”‚   └── use-theme.js
β”œβ”€β”€ _styles
β”‚   β”œβ”€β”€ common
β”‚   β”‚   β”œβ”€β”€ _common.js
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── light.js
β”‚   β”œβ”€β”€ global
β”‚   β”‚   └── index.cssr.js
β”‚   └── transitions
β”‚       β”œβ”€β”€ fade-in-width-expand.cssr.js
β”‚       └── icon-switch.cssr.js
β”œβ”€β”€ _utils
β”‚   β”œβ”€β”€ color
β”‚   β”‚   └── index.js
β”‚   β”œβ”€β”€ cssr
β”‚   β”‚   β”œβ”€β”€ create-key.js
β”‚   β”‚   └── index.js
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ naive
β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   └── warn.js
β”‚   └── vue
β”‚       β”œβ”€β”€ call.js
β”‚       β”œβ”€β”€ flatten.js
β”‚       β”œβ”€β”€ get-slot.js
β”‚       └── index.js
β”œβ”€β”€ assets
β”‚   └── logo.png
β”œβ”€β”€ button
β”‚   β”œβ”€β”€ src
β”‚   β”‚   β”œβ”€β”€ Button.jsx
β”‚   β”‚   └── styles
β”‚   β”‚       └── button.cssr.js
β”‚   └── styles
β”‚       β”œβ”€β”€ _common.js
β”‚       β”œβ”€β”€ index.js
β”‚       └── light.js
β”œβ”€β”€ components
β”‚   └── Button.jsx
β”œβ”€β”€ config-provider
β”‚   └── src
β”‚       └── ConfigProvider.js
└── main.js

32 directories, 45 files

A simple Button should contain 45 files and 32 directories for support. We can basically determine that 90% of the contents in the component library are common. We only need to understand all the underlying dependencies and design concepts required by a Button. To understand the component library, we only need to make another effort to understand the special design of the remaining 10% of the components, You can understand the source code of the whole component library.

All the dependent codes of a Button sorted out by the above core can be viewed in my Github warehouse: https://github.com/pftom/naiv....

reel silk from cocoons -- make a painstaking investigation

When we can get all the "necessary" and "simplest" dependencies behind the perfect operation of a Button, we can run the project and understand the simplest and necessary code by consulting materials and drawing a mind map.

We first run the code, and then understand the code logic layer by layer. For example, what are the preceding hook functions:

What is the core useTheme hook for:

What are the user-defined hook functions, and what CSS variables do they contain:

What are the return values of setup in Vue3 component:

What is the render function logic for rendering

By consulting Vue3 documents, combing the whole code flow, and then understanding how each branch works, we can slowly understand how the Button component runs. Thanks to the most refined and simplified processing of the code, the whole process of looking at the code will be a little slower, but the overall content that needs to be understood is much better than that we got a whole source code, hundreds of thousands of files, and it's much better to interrupt debugging from the entrance.

Write at the end

I believe you should also have read various Daniel's source code interpretation articles before reading Pitang's source code reading article, but I believe everyone has their own unique source code reading skills. Although I take how to understand NaiveUI's source code as an example, I believe all the processes of reading source code are the same. Follow the following steps:

  • Establish good psychological cognition
  • Understand the climax MVP, including the content required to locate the minimum feasibility code of the source code. Sort out the structure before looking at the source code to ensure that the MVP can run
  • Then we can help ourselves gnaw the source code by breaking points, drawing mind maps, consulting documents and so on

This is the experience that Pitang summed up when looking at the source code of Vite and NaiveUI. I believe it can provide some guidance for students who are wandering on the way to look at the source code but have no way. You can use this skill to look at other source codes, such as Webpack? qiankun? Ant Design? Or the tiktok recently released Semi Design. Encourage each other πŸ’ͺ

❀️/ Thank you for your support/

The above is all the content of this sharing. I hope it will help you^_^

If you like, don't forget to share, like and collect~

Welcome to official account occupation bus, the three brothers from bytes, shrimp skins and silver merchants, sharing programming experience, technical dry cargo and career planning, helping you to walk in the big factory without any detours.

Tags: Front-end source code

Posted on Thu, 25 Nov 2021 18:16:19 -0500 by aboldock