It was first posted on my blog github.com/mcuking/blo…

background

In the usual front-end business development, it is often necessary to use some component library component development pages. However, these components alone are often difficult to fully meet business requirements, and business logic needs to be developed for different business scenarios. As the number of front-end projects under development increases, you will find that many business scenarios are frequently encountered and are essentially the same, perhaps with only a small amount of code modification, so that the original code can be used in the new project.

For example, in the scenario where the account is bound to the mobile phone number, in addition to using input, button and other components, a lot of logic such as mobile phone number verification, setting countdown, interface verification verification code and so on should be added. If the style of the input captcha is special, there may also be a generic input component based on secondary encapsulation of the input field specific to the captcha. When faced with the need to bind a phone number again, most of the front ends will simply copy the original project into the new project and make some minor adjustments.

This approach may encounter the following problems:

  • Reusable business scenario codes are scattered in all kinds of front-end business projects, information is not interconnected, and it is difficult to search across projects.

    It is often necessary to ask senior development colleagues to find out in which project a particular business scenario has been developed. If new developers don’t know they’ve developed before, they start from scratch, which can lead to wasted development resources.

  • Similar business scenarios have different code implementations in different business projects, so it is impossible to achieve a unified standard to jointly implement a best practice.

    A common development problem is that similar business scenarios have been written in different business projects, but are maintained by different people and not communicated with each other. This leads to the choice of having multiple versions of code for later projects developing similar business scenarios. Unable to jointly maintain a version of the code, and constantly optimize and adapt, ultimately to achieve the best practices in this business scenario.

The following content is the practice summary of the cross-project block reuse platform developed by the author to solve the above problems. At this point the reader may have a question: what is a block? Why block reuse and not component reuse?

To answer this question, let’s first clarify the definition of these concepts. The following is a direct reference to the definition of Ali Flying ice:

Component: has certain functions and high complexity, such as user selector and address selector. The project only needs to introduce the corresponding NPM package. The project does not care about and cannot modify the internal code of the component.

Block: Usually a UI module. When a block is used, the block code is copied into the project code. Any changes to the block code can be made in the project, so subsequent updates to the block do not affect the project.

For components, my company has a well-established process of abstracting reusable code into base/business components and then going through the NPM package distribution process and presenting it on the built-in component platform. Users simply find the components they need on the platform and download them to their project’s dependencies via a private NPM source.

As for blocks, it is difficult to abstract them into components and integrate them into NPM packages. As for the reuse of blocks, there is no suitable tool to use at present, so it mainly realizes a shareable reuse platform for blocks. In particular, this article’s blocks include uI-related code as well as reusable utils methods and so on.

This platform is developed based on Bit, so before explaining the implementation of block reuse platform, we need to introduce the principle of Bit first.

Bit Fundamentals

In case you’re wondering, the term component is used a lot in this section. You can interpret it as a Bit component — a piece of code that can be reused. The reason is that Bit itself does not distinguish between components and blocks. All reusable code fragments can be reused through Bit, but the author mainly uses it to achieve block sharing. Here’s the Bit schematic:

Bit is an open source CLI tool for collaboration across project components. Use bits to transform components scattered across projects into reusable packages that can be used across projects.

You can set up your own services for component collaboration, or you can use the Bit.dev cloud to host components for the sharing of private or common components.

Git Git Git Git Git Git Git Git Bit is really inspired by Git in its implementation. The difference is that Git has a file dimension, while Bit has a component dimension. To learn more, click on Bit Docs.

The definition and elements of a Bit component

As for the Bit component mentioned in the above definition, Bit also gives its own definition:

  • A React, Vue or Angular component

  • Common style files (e.g. CSS, SCSS)

  • Reusable methods

The Bit stores the following three elements for each component:

  • Source code (including code, tests, and documentation)

  • Depend on the map

    When a file is added to a Bit component, Bit analyzes the imported dependencies of the file (such as import or require statements in code). Dependency graphs enable components to exist independently of projects and can be moved across projects without losing any references.

    It is important to note that the dependencies tracked here only include dependencies installed using NPM and installed Bit components. That is, local files imported directly into the project are not included in dependencies, such as import {computeXXX} from ‘.. / utils’. Don’t worry, though, that when you run the publish component to remote command locally, Bit checks to see if the imported local file is also tracked, and can’t publish without it.

  • Tools and Configuration

    Bit also saves component-specific tools and configurations, such as compilers and test tools used by the component.

The following figure vividly illustrates the components of a Bit component.

The lifecycle of a Bit component

The release and use of Bit components are achieved through the open source CLI tool bit-bin. Readers can install this NPM package globally on their own computers and try to use it to release a component experience.

Publish components to remote repositories

  • Track: Initializes a Bit component by specifying the files that comprise the component. Changes to these files are also tracked. The command is bit add SRC /bindPhone/ XXX -i bindPhone.

  • Version: Marking the Version of a component solidifies the metadata and file content of that Version of the component. Bit tag bindPhone 1.0.0

  • Export: The exported component creates a unique ID for the component. This unique ID contains the Remote Scope and the local component name. The export directive pushes a copy of the component’s metadata and file content to a remote repository. The command is bit export [remoteScopeName].

Using the component

When the component is pushed to a remote repository on the server, it can be used by other local Bit WorkSpace. There are two methods used: Install, which installs the component into node_modules as a regular NPM package, and Import, which downloads the source code and dependencies of the component to the local directory.

The following illustration illustrates the Bit component lifecycle above.

Bit part concept explanation

Workspace

When you execute the bit init command in a front-end business project, the entire business project becomes a workspace, similar to the concept of a workspace in Git.

Scope (Warehouse)

When executing the bit init command in a front-end business project, a.bit directory is generated. This directory is called the Bit Scope, and a Git-like.git directory is a Git repository.

A scope may or may not exist in a Bit workspace. Components are passed between different scopes using the Bit export and Bit import commands. It is also possible to convert a single version of a component from a local scope (repository) to a local workspace using the bit tag and bit checkout commands.

Components are stored in scope using CAS(Content Addressable Storage). The storage principle of Scope will be explained in detail later. The Bit is very much inspired by Git’s mechanics and will be easier to understand if you are familiar with Git.

Remote Scope

Remote scopes are stored on the server and can also be called bare Scopes because the scope is outside the workspace. Remote Scope is primarily used to share components, where components are exported/imported.

Implement cross-project block reuse scheme

For Bit through the introduction of the above, I believe readers have a preliminary cognition, in fact, the author thinks that Bit is very suitable for reuse across projects blocks platform of the main reason is that the publisher without similar release NPM, requires a separate create project and released, but can be directly in business projects issued a reusable block of code. This is particularly true of blocks that are hard to abstract and code can be changed at will in a project.

What remains to be considered is how to implement the entire cross-project block reuse platform solution on a Bit basis. The following diagram is an architecture diagram of the whole solution, and the following sections will explain the different parts of the architecture diagram.

Bit Remote Scope

You can use the Bit init –bare command to initialize a remote repository on a remote server, or you can directly deploy the Docker image bit-Docker.

After deploying the remote repository, the user can upload or download the block codes from the local repository to the remote repository through SSH.

For more details, please refer to the official documentation bit-Server.

Bit CLI

The operations of uploading and downloading block code mentioned in the previous section are implemented through Bit open source CLI tool bit-bin, which can be directly used by readers in actual development.

However, if there are specific requirements, such as the need to record the number of downloads to the block platform when performing a bit import to download the block code, you may need to customize the bit-bin. The author suggests that we clone a copy of Bit source code Bit directly, and then customize it for secondary development, and provide it for development use by releasing private NPM packages in the company.

Block platform

After the above operation, the block code has been hosted in the Remote Scope on the server, but the block users cannot intuitively select the block by viewing the view constructed by the block code, nor can they debug the block code online to check the effect, which causes great trouble to the use of the block.

The official portal site bit.dev has these features but is not open source, so we need to make a site with similar features. By analyzing the functionality of the Bit.dev site, you can see two key points in the implementation of the site:

  1. Build the block code in real time, then take a screenshot of the page and display it in the block list. And you can debug block source code online, and then see the debugging results in real time;

  2. Parsing a block’s data (source code, dependencies, and so on) from files stored in a remote repository for use in the block platform.

On the first point, an online IDE is a major requirement, which I have already summarized in my previous article – build your own online IDE, so I won’t go into it here. The implementation of the second point is described.

Parsing block data from a remote repository

Remember that the Scope for Bit is a file that uses CAS (Content Addressable Storage) to store Bit components? Next, we will introduce the principle in detail.

Git internal storage: Git internal storage: Git internal storage: Git internal storage: Git internal storage

Git is essentially a file system. The historical versions of all files in the workspace, as well as the Commit, branch, tag, and other information, are stored as file objects in the.git directory. In the objects directory under.git you might see files like this:

The git/objects ├ ─ ─06│ └ ─ ─ 5 bcad11008c5e958ff743f2445551e05561f59 ├ ─ ─ 3 b │ └ ─ ─ 18 e512dba79e4c8300dd08aeb37f8e728b8dad ├ ─ ─ the info └ ─ ─ packCopy the code

There are three types of files in the Git Objects directory:

  • Commit: The Commit object, which records all directories and files of a Version

  • Tree: directory object, which records the directories and files contained in the directory

  • Blob: File object that records the contents of a file

Git Objects are handled and stored in Git internal file systems in the following ways:

  1. First create a header with the value “object type content length \0”;

  2. Concatenate the header with the contents of the file to compute its SHA-1 hash value (a string of 40 hexadecimal digits);

  3. Zlib is used to compress the connected content.

  4. Write the compressed content to the directory with the first two digits of the hash value/the file with the last 38 digits of the hash value.

In the Bit source code, the objects file in the Bit Scope is also divided into the following types:

  • Component: Records information about Bit components, including block names and historical versions

  • Version: Records the information about each released Version, such as the files, dependencies, email addresses and user names of publishers, and release time

  • Source: Records the file contents

  • Symlink: Temporarily unavailable

  • Scope: temporarily useless

Bit Objects handles and stores the above information in much the same way as Git:

  1. The sha-1 hash value (a string of 40 hexadecimal digits) is calculated based on the contents of the file.

  2. Then create a header with the value of “Sha-1 hash value of object type file content length \0”;

  3. Concatenate the header with the contents of the file;

  4. Zlib is used to compress the connected content.

  5. Write the compressed content to the directory with the first two digits of the hash value/the file with the last 38 digits of the hash value.

Git computes the SHA-1 hash value based on the header and the file content, while Bit computes the SHA-1 hash value based on the file content. Another point is that the Bit header also includes the SHA-1 hash value for the file content.

Now that we know how the data is processed and stored in these files, we can parse the data out of these files in reverse. Here’s how to parse the files:

const zlib = require('zlib');
const fs = require('fs-extra');

const SPACE_DELIMITER = ' ';

const NULL_BYTE = '\u0000';

const inflate = (buffer) = > {
    return new Promise((resolve, reject) = > {
        zlib.inflate(buffer, (err, res) = > {
            if (err) return reject(err);
            return resolve(res);
        });
    });
}

Const buf = buffer.from (json.stringify (obj)); // Convert object to buffer const buf = buffer.from (json.stringify (obj));
Parse (buf.toString()); // Convert buffer to const temp = json.parse (buf.tostring ());
const parse = (buffer) = > {
    // Use the separator '\u0000' to split the contents of the file into header and content parts

    const firstNullByteLocation = buffer.indexOf(NULL_BYTE);
    // The header section
    const headers = buffer.slice(0, firstNullByteLocation).toString();
    // Content section
    const contents = buffer.slice(firstNullByteLocation + 1, buffer.length);

    const [type] = headers.split(SPACE_DELIMITER);

    console.log('file type is:', headers);

    if (type === 'Source') {
        return contents.toString();
    }

    return JSON.parse(contents.toString());
}

const parseObject = async (path) => {
    const contents = await fs
        .readFile(path)
        .then(fileContents= > {
            return inflate(fileContents);
        })
        .then(buffer= > parse(buffer));

    console.log('file contents is:', contents);
    return contents;
}

parseObject('/Users/xxx/bit/common/objects/03/3cb8b37245cf0cfbde2495d5d88c1324234e96');
Copy the code

You can then call the parseObject method to parse the contents of different types of files, such as the sample Component file:

{
  name: 'button'.scope: 'common'.versions: {
    '1.0.0': '4873cd3d4efdd585ee9a960bdfb16f2ee986ab14'.'1.0.1': 'e1e8280f56c5bfca8640e186f5667286b2023927'
  },
  lang: 'javascript'.deprecated: false.bindingPrefix: '@bit'.remotes: [{url: 'file:///Users/xxx/bit/common'.name: 'common'.date: '1599218799176'}}]Copy the code

The Version file contains the following information:

{
  files: [{file: '0b8b28f212101ef236744a25bfa085a00d0e7a63'.relativePath: 'src/components/button/index.js'.name: 'index.js'.test: false}].mainFile: 'src/components/button/index.js'.bindingPrefix: '@bit'.log: {
    message: ' '.date: '1599218793164'.username: 'xxx'.email: '[email protected]'
  },
  ci: {},
  docs: [].dependencies: [].devDependencies: [].flattenedDependencies: [].flattenedDevDependencies: [].extensions: [].packageDependencies: { react: '^ 16.13.1' },
  devPackageDependencies: {},... }Copy the code

The Source file content is actually the Source code of the block, which is not shown here.

Json file in the local scope (i.e..bit directory) records the SHA-1 hash value of the Component file corresponding to the bit Component. As follows:

[{"id": {
      "scope": "common"."name": "button"
    },
    "isSymlink": false."hash": "2179ca06272f0962fafd793abdf27a553fd9b418" // The Component file of the corresponding Component}]Copy the code

Based on the above analysis, we can find out the method of parsing the block source code we need from the Objects of the remote warehouse Scope. The general steps are as follows:

  1. First, find the corresponding block name from scope index.json, and get the hash value of the corresponding Component file of the block.

  2. Use the parseObject method above to parse the contents of the Component file and find the hash value of the Version file corresponding to the latest Version of the block from the versions field in the Component file content.

  3. Use the above parseObject method to parse the content of the Version file. From the files field of the content of the Version file, you can find all the source file names, relative paths, hash values, etc. From fields such as Dependencies, devDependencies, and so on, you can obtain all dependencies of a block;

  4. The block source code/dependency data obtained in the previous step can be returned to the block platform in a certain format.

In this way, data such as source code and dependencies of a block can be parsed from the Bit remote repository and returned to the block platform. The code is not shown here because of space constraints.

At this point, the practice of the entire architecture is covered.

Auxiliary tool

This makes it easier for users to select and download blocks when writing code. Based on the vscode plug-in of Feibing, THE author wrote a SPECIAL VScode plug-in for block download. It is used to display all blocks on the block platform on the right side of the editor. Users can search and browse the block, click the block to download it into the project, and automatically introduce it into the code. The effect is shown below:

conclusion

If do analogy, blocks reuse as metallurgical equipment platform, and the front end of the business items like past mine, block’s mission is to reuse platform from so many front-end project smelting of reuse value of gold – blocks, and will be shown visually these gold to developers, make it as far as possible reuse these blocks, in order to improve development efficiency.

The resources

– Flying ice – About materials

-Git internal storage mechanism