Hello, I’m PI Tang. Recently programmer bus learning communication group has a small partner want to know how to look at the source code, just recently have a little experience, before also wrote an actual run through NaiveUI source code article: especially dadu recommended component library is how to develop? Source code experience, to share with you.
Psychological cognition should be in place
The first thing to realize is that looking at the source code is a relatively boring beginning, while the time span is relatively long a process. So the first step in looking at source code is to find a project that is highly relevant to the domain you want to understand, or your business domain, and is well known in that domain and is actively maintained.
For example, I’m a front-end, and front-end is an area with lots of new things, like Unbundled solution Vite, new framework Svelte, new assembly language WebAssembly, CSS engineering solution TailwindCSS, Component libraries such as Semi Design, a popular open source library for Tiktok, or NaiveUI, a popular component library for Vue3 in the community.
And skin soup I’ve been on the direction of component library, CSS is obsession, and is within the group is responsible for the front-end engineering CSS recently one of the aspects of infrastructure, so let me go to study a component library source code, such as NaiveUI, so I am very interested in, motivation and reason, and that is driving your palate, one of the key drivers of source.
Understand the climax MVP
React can be considered a simple operating system. If you start with a simple code entry and start with an interrupt point all the way to understand the source code, then you will want to vomit no matter how hard you insist.
So what’s the trick here? Just like our Internet startup, if you have a big and comprehensive idea, but your first step is not to find an empty room, prepare for six months of food, prepare a few computers, and then develop intensively for a few months, and then hope to surprise the world once it comes out. One is that it’s very rare, and two is that you have to have the capital and patience to last a few months.
And a relatively well-known in the field of entrepreneurship and spread profound skill is the MVP, namely minimum feasibility product, you need to make a very small, just can use and test your product idea, and then quickly to the market, receives the user feedback, and then follow up the user feedback, iterative products constantly, meet the demand of users, Until you get to PMF (Product to market matching), at which point you can basically find investment, scale, and then raise capital and go to NASDAQ and ring the bell.
So the second thing you need to be aware of when looking at source code is that you need to find the smallest MVP that an open source project can run without all the other dependencies, the most core processes and mechanisms. The MVP that helps you understand the core of your project, and I call it the climax MVP — the idea that if you win an MVP like this, you feel incredibly happy inside, you feel like you’re doing something slowly, you feel like you’re getting excited, and then the rest of the content, the branches basically reuse that MVP and build on it, Complete some compatibility details, etc.
Prior to the start
So for NaiveUI, what was its climactic MVP?
Let’s go to its website first:
Click on to start using:
As a component library, it usually needs to talk about its values, design principles, customizations, and ICONS, but it doesn’t really help you understand the source code, so you need to eliminate these distractions.
Let’s take a look at its source repository again:
To ensure the libraries to use what language to write, so that before you look at the source code can measure whether your current knowledge base to support your read the source code, of course, if you do not have the corresponding support reserves, but also adhere to want to see the source code, so the first thing you should consider according to its use of language, for language learning reserves in advance.
Through the essence
Let’s go back to NaiveUI’s website:
As you can see, for a “component library”, in fact, the most basic is “component”, and the composition of “component” behind a series of more basic elements, such as color, spacing, border, background, font, etc.
So is our goal clear? Take one “button” component and understand all the necessary processes, knowledge, and details to be able to fully use such a button. For other components, almost 90% of the logic can be reused and only 10% of the specific functional requirements can be understood.
Something like this iceberg map:
Under the iceberg belongs to that 90%, we are based on a seemingly simple “button” component, to comb the core process of the whole component library, can help us quickly and accurately understand the whole source code, so our climax MVP is to understand a “button” component of the whole process.
Understand context
Now that we understand what our climax MVP goal is, it’s time to go through all the documentation about Button with that goal in mind. You can see that the Button contains the following:
A button will have basic contents including default, primary, INFO, success, Warning, and error, and then need to handle:
- Border correlation: dashed lines
- Dimensions related: dimensions, administration
- Color: Custom color
- Status: text, Disabled, loading
- The event
This shows what the Button can do, what it can do, and then what the API is for the Button. Through the API and what it can do, we can roughly understand what the input and output of the Button are.
An example using Button would look like this:
<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>
Copy the code
Know how to start a project
One of the convenient things about open source projects is that they are well documented, and they are desperate for contributors, so there are good contribution guidelines, such as NaiveUI’s contribution guidelines:
By contributing to the guide, you can learn how to install dependencies, deal with some of the startup project issues, and be able to get the project up and running for debugging, which is often your first taste of how the entire code works.
Understand the project structure of the target project
By the time you get to this stage, you should know the following:
- You’ve understood your goal, what is the climax MVP
- Do you understand your target content as a feature, what are its inputs and outputs
- Do you understand what the technology stack is and how to get the project off the ground
Our three points correspond to NaiveUI as follows:
- Climax MVP: Runs through a Button and can use it, retains the same features as an existing Button, receives the same inputs, and produces the same output
- Button contains border, size, color, state, event and other related content. Input these parameters to produce output under the corresponding conditions
- The technology stack of the project is Vue3, TypeScript, the building tool is Vite, and CSS BEM framework CSS Render is used, while the package management tool is PNPM
With these three points in mind, the next step is to look at the entire file directory against the source code and understand the dependencies of each directory, as shown in the figure below.
Let’s take a look at what each folder does:
src
: this is the main component library related component code, and export some internationalization, style, theme customization related content, generally an open source project core development directoryscripts
: Some scripting logic for running code, building, and publishingtheme
NaiveUI is the built-in default theme. This library allows you to customize the theme. You can modify the content of the theme, or you can completely customize the theme.github
、.husky
These are some configurations that don’t need much attention and can be added directly to your MVP template projectplayground
Is the support code for the code to run in various environments, such as SSR, etcdemo
Is the introduction ofsrc
Examples of sites where content is used to show components in action, in fact for NaiveUI which is the documentation website we saw earlier- Other such as
build
、design-notes
Is to build the product, or some theme design notes, etc., basically does not belong to the source code to read the department, see interested students can see
Then there are some files for various engineering configurations such as:
.prettierrc
Related: Prettier.gitignore
: Git.eslintrc.js
: ESLint relatedbabel.config.js
Babel relatedjest.config.js
: Jest test relatedpostcss.config.js
: Handles CSS relatedtsconfig.xx.json
Handle TypeScript dependenciesvite.config.js
: Configuration related to Vite build tools
There are some projection-specific Changelog.xx.md for understanding the context in which the whole idea is developing, and CONTRIBUTING guidelines for getting around the code we mentioned earlier.
I’m a little confused. 🤯
Create your climax MVP project
Now that we know the entire NaiveUI project directory structure, we can start to create our climax MVP project, but before that we can do one more wave of simplification. Here are some things we can do without:
-
For directories
.github
、.husky
、playground
、scripts
We don’t need this, we just need to test the most basic environment, and can run during developmenttheme
This is just the design system that NaiveUI follows throughout, and it will be followed in other parts, but not directly referenced, so we don’t have to- That leaves us with just
demo
和src
As a further step, we can put the demo inside SRC, the whole SRC we change its responsibilities to climax MVP site entry, and then the remaining code under SRC is used to import into the SRC entry file for use
-
For configuration files:
- Test related, Jest etc we don’t need
- Typescript-related, we can iterate later without introducing unnecessary complexity and type gymnastics
- ESLint and Prettier, etc., could be done without relying on the editor’s default formatting, though it wouldn’t be a bad idea to include them in our initial MVP project
After simplification, we only need the following files for our climax MVP project:
- Vite for building projects and providing development servers:
vite.config.js
- Used to provide grammatical translation
babel.config.js
- Project dependency file
package.json
- The main code used to run through the project
src
As well asindex.html
The entry template
The directory structure is as follows:
. ├ ─ ─ Babel. Config. Js ├ ─ ─ index. The HTML ├ ─ ─ node_modules ├ ─ ─ package. The json ├ ─ ─ public ├ ─ ─ the SRC ├ ─ ─ vite. Config. Js └ ─ ─ yarn.lockCopy the code
It’s very simple. There’s no more stuff, right? It’s also very easy to understand.
Copy the contents of the remaining files to be created from NaiveUI’s project directory and install the corresponding dependencies.
Run through the process
After we created our climax MVP project from the source library, we should now be running, but it’s just a simple Button, because in order to quickly run the project, our entry file SRC/app.vue will look like this:
<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>
Copy the code
And 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>
Copy the code
Then we will try again to understand NaiveUI code, again will these backbone code migration to the climax of our MVP in the project, and ensure the migration process can continue to run, although sometimes we may encounter a dependency needs a lot of pre – dependent, so need to transfer a large section of the code to the project running.
Find the core entrance
To complete all the front dependencies of a Button, just go to the NaiveUI project directory and find the corresponding code for a Button as follows:
In fact, parse the component file code, is the following sections:
-
Import dependencies in front
-
Define the component defineComponent
- The props pass and use in the component, and the definition and use of its own state
- Template code
-
Export components
We don’t need any of the TS definitions in the above code, so we can delete ButtonProps, NativeButtonProps, MergedProps, and XButton.
We can also delete those related to type definition in the import part:
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'Copy the code
After removing all this extraneous code, what’s left of our code?
Import dependencies:
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'Copy the code
Component declaration section:
const Button = defineComponent({
name: 'Button'.props: buttonProps,
setup(props) {
// Define the component state
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= >{... }// Handle the theme of the component to get the corresponding style of the Button component in the overall global design system
const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
const themeRef = useTheme(...)
const rtlEnabledRef = useRtl(...)
// Return its own state, global state-related theme styles, values of various CSS properties, and event-related content to the template
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 various component-related style rendering and event-handling related content. The style rendering here corresponds to the states and operations that Button can present and handle mentioned in the document
const { $slots, mergedClsPrefix, tag: Component } = this
return (
<Component
ref="selfRef"
class={[` ${mergedClsPrefix}-button`, ` ${mergedClsPrefix}-button--The ${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>)}})Copy the code
Further simplifying code
From the rest of the code above, we can see that most of what we’re doing for understanding the component library is doing custom themes, and then if we show different themes working based on the various props that are passed in, so you can see that the Button component is full of CSS variables, Such as this.color, this.ghost, this.text, this.cssVars, so our core is to understand how these themes are customized, what variables and dependencies are included, and how these variables and dependencies affect the different styles and functions a Button can host.
So there are some things in the above code that we can actually delete:
- We just need to see how a single Button works, so we can leave out the NButtonGroup part, the Button group part, okay
- We also don’t have to deal with unique adaptations like RTL (right-to-left typesetting)
So we need to take a step further and delete this code:
import { buttonGroupInjectionKey } from './ButtonGroup' import useRtl from '.. /.. /_mixins/use-rtl' const NButtonGroup = inject(buttonGroupInjectionKey, {})Copy the code
And anything else that uses buttonGroup.
Understand the input
Through the previous step, we basically removed all the irrelevant content and reached our final climax of all the minimal content of Button needed in the MVP project. That is to say, we have determined the core entry code itself and the dependent part, and then we need to process all the input. And remove the associated dependencies in these inputs that are irrelevant to Button processing logic.
We can see that Button mainly has the following inputs:
- Import input at the top of the file
- Using the hook
useFormItem
Or global state injectioninject(...)
Related input
As we can see, import-related inputs fall into two main categories:
- Some libraries, such as
vue
Import: We only need to query the corresponding library documentation to understand the effect on the API - Other relative path imports that rely directly on their own projects: this is where we need to move on to other parts of NaiveUI’s source base
The hook useFormItem or global state injects jects (…). Related inputs also depend on other relative path imports for their own items in the import.
We need to carry out 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'Copy the code
Some of these dependencies are leaf dependencies themselves and have no other dependencies, such as:
import { createHoverColor, createPressedColor } from ".. /.. /_utils/color/index"; Import {useFormItem} from ".. /.. /_mixins"; Import {NFadeInExpandTransition, NIconSwitchTransition,} from ".. /.. /_internal"; import { call, createKey, getSlot, flatten } from ".. /.. /_utils";Copy the code
These leaf dependencies can create directory structures and file names directly against the original repository, and then copy the code.
For those non-leaf dependencies, we need to work harder to continue parsing the dependencies, repeating the previous two operations:
- Remove TS or other code and dependencies that are not relevant to Button
- Look for its dependent dependencies and continue the process above
The last step is to create the same structure as the directory structure of the source code, and copy the code that handles the irrelevant content.
For example, for non-leaf dependent style:
import style from "./styles/button.cssr.js";
Copy the code
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'Copy the code
Found its dependence for BEM specification defines the CSSR library (build), and some fadeInWidthExpandTransition and dealing with the animation iconSwitchTransition dependence, so then to continue to enter the dependence, such as:
import { c, cB, cE, cM, cNotM } from '.. /.. /.. /_utils/cssr'Copy the code
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'
Copy the code
If you find that there are no other dependencies that need to be recursively searched, they are imported third-party libraries, so you can look up the documentation of the corresponding third-party libraries to understand the meaning of the API.
Repeat the above dependency analysis until convergence, and finally we get a file organization diagram as follows:
. ├ ─ ─ App. Vue ├ ─ ─ _internal │ ├ ─ ─ fade - in - expand - the transition │ │ ├ ─ ─ index. The js │ │ └ ─ ─ the SRC │ │ └ ─ ─ FadeInExpandTransition. JSX │ ├ ─ ─ icon │ │ ├ ─ ─ index. The js │ │ └ ─ ─ the SRC │ │ ├ ─ ─ icon. The JSX │ │ └ ─ ─ styles │ │ └ ─ ─ index. The CSSR. Js │ ├ ─ ─ icon - switch - the transition │ │ ├ ─ ─ index. The js │ │ └ ─ ─ the SRC │ │ └ ─ ─ IconSwitchTransition. JSX │ ├ ─ ─ index. The js │ ├ ─ ─ loading │ │ ├ ─ ─ index. Js │ │ └ ─ ─ the SRC │ │ ├ ─ ─ Loading. The JSX │ │ └ ─ ─ styles │ │ └ ─ ─ index. The CSSR. Js │ └ ─ ─ wave │ ├ ─ ─ index. The js │ └ ─ ─ The SRC │ ├ ─ ─ Wave. The JSX │ └ ─ ─ styles │ └ ─ ─ index. The CSSR. Js ├ ─ ─ _mixins │ ├ ─ ─ index. The js │ ├ ─ ─ the use - config. Js │ ├ ─ ─ Use - form - item. Js │ ├ ─ ─ the use - style. Js │ └ ─ ─ the use - theme. Js ├ ─ ─ _styles │ ├ ─ ─ common │ │ ├ ─ ─ _common. Js │ │ ├ ─ ─ index. The js │ │ ├─ ├─ ├─ ├─ ├─ dee-in-width │ ├─ dee-in-width │ ├─ dee-in-width │ ├─ dee-in-width │ ├─ Icon - switch. The CSSR. Js ├ ─ ─ _utils │ ├ ─ ─ color │ │ └ ─ ─ index. The js │ ├ ─ ─ CSSR │ │ ├ ─ ─ the create - key. Js │ │ └ ─ ─ index. The js │ ├ ─ ─ Index. Js │ ├ ─ ─ naive │ │ ├ ─ ─ index. The js │ │ └ ─ ─ a warn. Js │ └ ─ ─ vue │ ├ ─ ─ call. Js │ ├ ─ ─ flatten. Js │ ├ ─ ─ the get - slot. Js │ └ ─ ─ Index. Js ├ ─ ─ assets │ └ ─ ─ logo. The PNG ├ ─ ─ button │ ├ ─ ─ the SRC │ │ ├ ─ ─ button. The JSX │ │ └ ─ ─ styles │ │ └ ─ ─ button. The CSSR. Js │ └ ─ ─ Styles │ ├ ─ ─ _common. Js │ ├ ─ ─ index. The js │ └ ─ ─ light. Js ├ ─ ─ components │ └ ─ ─ Button. The JSX ├ ─ ─ the config - provider │ └ ─ ─ the SRC │ └─ ├ ─ exclude.org.txtCopy the code
A simple Button contains 45 files and 32 directories to support it. We can almost confirm that 90% of the content of the component library is common. We only need to understand all the underlying dependencies and design concepts needed by a Button. Understanding the specific design of the remaining 10 percent of the components makes it possible to understand the source code for the entire component library.
The entire dependency code of a Button can be accessed in my Github repository: github.com/pftom/naive…
The whole
Once we have all the “necessary” and “minimal” dependencies needed for a Button to run flawlessly, we can run the project while looking up data and drawing mind maps to understand the minimum necessary code.
Let’s first run the code and then understand the code logic layer by layer, such as what the preceding hook functions do:
The core useTheme hook is used for:
What are the CSS variables contained in the custom hook function?
What are the setup return values in the Vue3 component:
What is the final render function logic used to render:
By reviewing the Vue3 documentation, combing through the code flow, and then understanding how the branches work, we can begin to understand how Button components work. Thanks to the most simplified code processing, so the whole process of looking at the code will be a little slower, but the overall need to understand the content compared to the previous we get a whole source code, hundreds of thousands of files to start from the entrance to interrupt point debugging will be much better.
Write in the last
Believe that everyone in the skin soup before this source reading article, should also read a variety of Daniel source reading articles, but believe that each of us has our own unique source skills, while I here is made an example how to understand NaiveUI source, but to believe that all the source code is the process of follow the following steps:
- Establish good psychological cognition
- Understand the climax MVP, including the content needed to locate the minimum viable source code, and organize the structure before looking at the source code to make sure the MVP runs
- Then in the smallest, the most core source code for breaking points, drawing mind maps, looking up documents and other ways to help yourself to bite the source code
This is skin soup in the process of looking at Vite, NaiveUI source summed up the experience, I believe that can wander in the way of looking at the source code but no method of students to provide a little guidance, you can apply this skill to see other source code, such as Webpack? Qiankun? Ant Design? Or The Semi Design, which Tiktok recently launched. ‘💪
❤️/ Thanks for your support /
That is all the content of this sharing. I hope it will help you
Don’t forget to share, like and bookmark your favorite things
Welcome to the public number programmer bus, from byte, shrimp, zhaoyin three brothers, share programming experience, technical dry goods and career planning, help you to avoid detours into the factory.