<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kagol</title>
    <description>The latest articles on DEV Community by Kagol (@kagol).</description>
    <link>https://dev.to/kagol</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F602775%2F26682112-6715-4df5-ac0d-2a7547800372.jpg</url>
      <title>DEV Community: Kagol</title>
      <link>https://dev.to/kagol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kagol"/>
    <language>en</language>
    <item>
      <title>Build a grayscale release environment from scratch</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Wed, 30 Jun 2021 14:38:39 +0000</pubDate>
      <link>https://dev.to/kagol/build-a-grayscale-release-environment-from-scratch-1fc9</link>
      <guid>https://dev.to/kagol/build-a-grayscale-release-environment-from-scratch-1fc9</guid>
      <description>&lt;h1&gt;
  
  
  Guide
&lt;/h1&gt;

&lt;p&gt;Grayscale release, also known as canary release.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The term "canary" stems from the tradition of coal miners bringing caged canaries into mines. Miners use canaries to detect the concentration of carbon monoxide in the mine. The canaries would be poisoned by high concentration of carbon monoxide, in this case miners should evacute immediately. - DevOps Practice Guide&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we use "grayscale" or "canary" in software development. It refers to a small number of users' test before official release. So the problems can be detected earlier to prevent them from affecting most users.&lt;/p&gt;

&lt;p&gt;Pipeline with grayscale release module is a very important tool and an efficient practice in DevOps, but I knew little about this when I was a student. After onboarding, I discovered the power of pipelines.&lt;/p&gt;

&lt;p&gt;When faced with something new, it's an interesting path for us to get through all the key steps logically, and then complete a demo.&lt;/p&gt;

&lt;p&gt;The article mainly focus on zero-to-one construction process practice rather then theoretical content, suitable for beginner front-end developers interested in engineering.&lt;/p&gt;

&lt;h1&gt;
  
  
  01 Server
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Get a server
&lt;/h2&gt;

&lt;p&gt;As mentioned above, the grayscale release requires a small number of users' test before official release.Therefore, we need to make sure that two groups of users can be divided to use different functions at the same time. So we need to prepare two servers, each with different code versions.&lt;/p&gt;

&lt;p&gt;If you already have a server, you can emulate two servers by deploying services on different ports. If not, you can follow procedure below to purchase two cloud servers. The demo in this document will cost about 5 dollars on demand.&lt;/p&gt;

&lt;p&gt;You can get a HUAWEI cloud server according to this: &lt;a href="https://github.com/TerminatorSd/canaryUI/blob/master/HuaWeiCloudServer.md"&gt;https://github.com/TerminatorSd/canaryUI/blob/master/HuaWeiCloudServer.md&lt;/a&gt; (written in Chinese)&lt;/p&gt;

&lt;h2&gt;
  
  
  Install tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;First, make sure that git has been installed on your server. If not, run the following command to install it. After installation, generate an ssh public key and save it to your github, which is required for pulling code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yum install git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nginx
&lt;/h3&gt;

&lt;p&gt;It is easy to install Nginx on Linux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum install nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command "nginx -t" on the terminal to check whether the installation is successful. If ok, it displays the status and location of the Nginx configuration file.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HyTIMt0E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/moxcifft0yduqibkrmgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HyTIMt0E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/moxcifft0yduqibkrmgw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
Then, run the command "nginx" or "nginx -s reload" to start Nginx. If the following Nginx processes are displayed, it indicates that Nginx is started successfully.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sX7qIHAV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y72yftjyr47okkipkgnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sX7qIHAV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y72yftjyr47okkipkgnf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
Open a browser and visit the public IP address of your server. If you can see a page like this, Nginx is working properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ewLdTGQn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0h9slhoa7ljkc00hcv0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ewLdTGQn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0h9slhoa7ljkc00hcv0.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Jenkins
&lt;/h3&gt;

&lt;p&gt;You may have many questions about Jenkins at first. Like what is Jenkins? What can Jenkins do? Why should I use Jenkins, etc.  It's hard to say what Jenkins is, so just have a quick look at what Jenkins can do. Simply speaking, Jenkins can perform any operation on any server as you can. As long as you create a task on Jenkins in advance, specifing the task content and triggering time.&lt;br&gt;
(1) Installation &lt;br&gt;
Install the stable version: &lt;a href="http://pkg.jenkins-ci.org/redhat-stable/"&gt;http://pkg.jenkins-ci.org/redhat-stable/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget http://pkg.jenkins-ci.org/redhat-stable/jenkins-2.204.5-1.1.noarch.rpm
rpm -ivh jenkins-2.7.3-1.1.noarch.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the port used by Jenkins conflicts with other programs, edit the following file to modify the port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// line 56 JENKINS_PORT
vi /etc/sysconfig/jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(2) Start Jenkins&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service jenkins start/stop/restart
// location of password
/var/lib/jenkins/secrets/initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(3) Visit Jenkins&lt;br&gt;
Access port 8080 of the server, enter the password obtained from the previous step, and click Continue.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wu7wrv47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs0p5z5g1d73d0ys00lu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wu7wrv47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs0p5z5g1d73d0ys00lu.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an account and do login.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RmEQQHbn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bonip55475bxsslk1wf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RmEQQHbn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bonip55475bxsslk1wf.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
The Jenkins Ready page indicates this part has complete.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cw3ekp1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwlf7pqeinnqt1kevmvz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cw3ekp1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwlf7pqeinnqt1kevmvz.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  02 Code
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Two copies of the code
&lt;/h2&gt;

&lt;p&gt;We need to prepare two different pieces of code to verify whether the grayscale operation works. Here we choose Angular-CLI to create a project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// install angular-cli
npm install -g @angular/cli
// create a new project, press enter all the way
ng new canaryDemo
cd canaryDemo
// after running this command, access http://localhost:4200 to view the page information
ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access port 4200 of localhost to view the page. The content will be refreshed in real time when we change the title of src/index.html in the root directory to A-CanaryDemo. In this example, we use title to distinguish the code to be deployed for different services during grayscale release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hekcJLgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oahi84sfba9h74acy6aq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hekcJLgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oahi84sfba9h74acy6aq.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, generate two packages. The titles of the two packages are A-CanaryDemo and B-CanaryDemo respectively. The two folders will be used as the old and new codes for grayscale release later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng build --prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_T6cw9gE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr3t9jkks439wzngh23p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_T6cw9gE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr3t9jkks439wzngh23p.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Config Nginx
&lt;/h2&gt;

&lt;p&gt;At this point, the Nginx page is displayed when we access the IP address of the server. Now we want to access our own page, so we need to send the A-CanaryDemo package to certain location on the two servers. Here we put it in /var/canaryDemo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// xxx stands for the ip of server
scp -r ./dist/A-CanaryDemo root@xx.xx.xx.xx:/var/canaryDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the /var location on the server to see if the file already exists. If ok, modify the Nginx configuration to forward the request for accessing the server IP address to the uploaded page. As mentioned above, you can run the nginx -t command to view the location of the Nginx configuration file. In this step, you need to edit the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following information to lines 47 to 50. The traffic destined for the server IP address is forwarded to the index.html file in /var/canaryDemo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ua4-NBFs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d22hnn0x437gutaxy17w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ua4-NBFs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d22hnn0x437gutaxy17w.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
Save the modification and exit. Reload Nginx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nginx -s reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you access the IP address of the server, you can see that the page has changed to the one we have just modified locally and the title is A-CanaryDemo. After the operations on both servers are complete, the page whose title is A-CanaryDemo can be accessed on both servers. Just like two machines that are already providing stable services in the production environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e-aGY-nR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0olkcaawn9jlc17vns3y.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e-aGY-nR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0olkcaawn9jlc17vns3y.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  03 Defining a Grayscale Policy
&lt;/h1&gt;

&lt;p&gt;In this part, we need to define a grayscale policy, describing the traffic will be routed to grayscale side or normal side.&lt;br&gt;
For simplicity, a cookie named canary is used to distinguish between them. If the value of this cookie is devui, the grayscale edge machine is accessed; otherwise, the normal edge machine is accessed. The Nginx configuration result is as follows. In this example, 11.11.11.11 and 22.22.22.22 are the IP addresses of the two servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Canary Deployment
map $COOKIE_canary $group {
# canary account
~*devui$ server_canary;
default server_default;
}

upstream server_canary {
# IP addresses of the two hosts. The port number of the first host is set to 8000 to prevent an infinite loop in Nginx forwarding.
server 11.11.11.11:8000 weight=1 max_fails=1 fail_timeout=30s;
server 22.22.22.22 weight=1 max_fails=1 fail_timeout=30s;
}

upstream server_default {
server 11.11.11.11:8000 weight=2 max_fails=1 fail_timeout=30s;
server 22.22.22.22 weight=2 max_fails=1 fail_timeout=30s;
}

# Correspondingly, configure a forwarding rule for port 8000, which is disabled by default, you need to add port 8000 to the ECS console security group
server {
listen 8000;
server_name _;
root /var/canaryDemo;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
root /var/canaryDemo;
index index.html;
}
}

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# root /usr/share/nginx/html;
root /var/canaryDemo;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
proxy_pass http://$group;
# root /var/canaryDemo;
# index index.html;
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, both grayscale traffic and normal traffic are randomly assigned to machines on sides of A and B. After this, we need to create a Jenkins task to modify the Nginx file to implement grayscale release.&lt;/p&gt;

&lt;h1&gt;
  
  
  04 Grayscale Release
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Step Analysis
&lt;/h2&gt;

&lt;p&gt;Before creating a Jenkins task for grayscale release, let's sort out which tasks are required to achieve grayscale release and what each task is responsible for. Grayscale release generally follows this steps. (Assume that we have two servers AB that provide services for the production environment, which we call AB Edge):&lt;br&gt;
(1) Deploy new code to side A.&lt;br&gt;
(2) A small portion of traffic that meets the grayscale policy is switched to side A, and most of the remaining traffic is still forwarded to side B.&lt;br&gt;
(3) Manually verify whether the function of side A is ok.&lt;br&gt;
(4) After the verification, most traffic is transferred to side A and grayscale traffic is transferred to side B.&lt;br&gt;
(5) Manually verify whether the function of side B is ok.&lt;br&gt;
(6) After the verification, the traffic is evenly distributed to side A and side B as usual.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tasks Analysis
&lt;/h2&gt;

&lt;p&gt;Based on the preceding analysis, we obtain the six steps of grayscale release, where (3) and (5) require manual verification. Therefore, we use the two tasks as the partition point to create three Jenkins tasks (Jenkins tasks are established on the A-side machine) as follows:&lt;br&gt;
(1) Canary_A. This task includes two parts. Update the code of side A and modify the traffic distribution policy so that grayscale traffic reaches A and other traffic reaches B.&lt;br&gt;
(2) Canary_AB . The code of side B is updated. The grayscale traffic reaches B and other traffic reaches A.&lt;br&gt;
(3) Canary_B: All traffic is evenly distributed to A and B.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--njjz0jge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/394r3wpimk23bz33zbj6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--njjz0jge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/394r3wpimk23bz33zbj6.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Tasks
&lt;/h2&gt;

&lt;p&gt;Create three Jenkins tasks of the FreeStyle type. Remember to use English names. It is difficult to create folders later with Chinese. Do not need to enter the task details, just save it. Then, we will config the detailed information about each task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7eWLUO_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/se0iud16scxgnghk7pj0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7eWLUO_H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/se0iud16scxgnghk7pj0.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Config tasks
&lt;/h2&gt;

&lt;p&gt;Click to enter each task and excute an empty build (Otherwise, the modified build task may fail to be started.) , and then we will config each task in detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CGyzb8kC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qopgb3ftw9kk7pi19x5w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CGyzb8kC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qopgb3ftw9kk7pi19x5w.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
Front-end projects need to be built and packaged. However, the low-cost 1-core 2 GB ECSs are unable to complete this step. Therefore, the generated production package is managed by Git. Each code update synchronizes the latest production package to GitHub. The Jenkins task pulls down the production package and places it in a specified location to complete deployment.&lt;/p&gt;

&lt;p&gt;Now, config grayscale test A. As described above, we need to associate the task with the remote github repository. The github repository to be associated needs to be manually created to store the packaged B-CanaryDemo whic is named dist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vJhqRmLH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d50iv5dlyhok3p38twl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJhqRmLH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5d50iv5dlyhok3p38twl.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
Run the build task command once. (The git fetch step is unstable and sometimes takes a long time) . Click into this build to view the Console Output. You can find that the Jenkins task is executed at /var/lib/jenkins/workspace/Canary_A on the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hk1sjimO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/268zpobmhsr21h16nczr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hk1sjimO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/268zpobmhsr21h16nczr.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6AkdSsLY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bk69onsy14ucgc4fvx3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6AkdSsLY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bk69onsy14ucgc4fvx3.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Continue to edit the grayscale test task A and add the build shell, including the command to be executed each time the task is executed.&lt;br&gt;
(1) Pull the latest code first.&lt;br&gt;
(2) Copy the dist directory in the root directory to the location where the code is deployed. In this article, the specified location is /var/canaryDemo.&lt;br&gt;
(3) Modify the Nginx configuration so that grayscale traffic reach side A.&lt;br&gt;
In step (3), the way of modifying the grayscale traffic is to selectively comment out content in the Nginx configuration file. A grayscale test A can be implemented as follows:&lt;br&gt;
&lt;br&gt;
 &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream server_canary {
# grayscale traffic reach side A
server 11.11.11.11:8080 weight=1 max_fails=1 fail_timeout=30s;
# server 22.22.22.22 weight=1 max_fails=1 fail_timeout=30s;
}

upstream server_default {
# normal traffic reach side B. To distinguish the configuration of this section from the server_canary configuration, set the weight to 2
# server 11.11.11.11:8080 weight=2 max_fails=1 fail_timeout=30s;
server 22.22.22.22 weight=2 max_fails=1 fail_timeout=30s;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;User jenkins does not have sufficient permissions to execute commands. You can log in to the system as user root and change the ownership of the /var directory to user jenkins. Also remember to add the write permission on the /etc/nginx/ngix.conf file. The resulting shell command is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git pull
rm -rf /var/canaryDemo
scp -r dist /var/canaryDemo
sed -i 's/server 22.22.22.22 weight=1/# server 22.22.22.22 weight=1/' /etc/nginx/nginx.conf
sed -i 's/server 11.11.11.11:8000 weight=2/# server 11.11.11.11:8000 weight=2/' /etc/nginx/nginx.conf
nginx -s reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tAalkBbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyy791urr6zo8cuujbyq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tAalkBbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyy791urr6zo8cuujbyq.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, the grayscale test task A and grayscale test task B are configured in sequence.&lt;br&gt;
The task of grayscale test B is to pull the latest code to side A. (Because our Jenkins tasks are based on side A) Copy the code in dist to the specified access location of Nginx on side B, and modify the Nginx configuration on side A so that grayscale traffic reaches side B.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git pull
rm -rf canaryDemo
mv dist canaryDemo
scp -r canaryDemo root@xx.xx.xx.xx:/var
sed -i 's/# server 22.22.22.22 weight=1/server 22.22.22.22 weight=1/' /etc/nginx/nginx.conf
sed -i 's/# server 11.11.11.11:8000 weight=2/server 11.11.11.11:8000 weight=2/' /etc/nginx/nginx.conf
sed -i 's/server 22.22.22.22 weight=2/# server 22.22.22.22 weight=2/' /etc/nginx/nginx.conf
sed -i 's/server 11.11.11.11:8000 weight=1/# server 11.11.11.11:8000 weight=1/' /etc/nginx/nginx.conf
nginx -s reload
The task in this step involves sending code from the A-side server to the B-side server, which generally requires the password of the B-side server. To implement password-free sending, you need to add the content in ~/.ssh/id_rsa.pub on server A to ~/.ssh/authorized_keys on server B.
When B goes online, the Nginx configuration on side A is uncommented so that all traffic is evenly distributed to side A and side B.
sed -i 's/# server 22.22.22.22 weight=2/server 22.22.22.22 weight=2/' /etc/nginx/nginx.conf
sed -i 's/# server 11.11.11.11:8000 weight=1/server 11.11.11.11:8000 weight=1/' /etc/nginx/nginx.conf
nginx -s reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we have built a grayscale release environment from zero to one. After the code is updated, you can manually execute Jenkins tasks to implement grayscale deployment and manual tests to ensure smooth rollout of new functions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The article introduces the necessary process of building a grayscale release environment from four aspects: server preparation, code preparation, grayscale policy formulation and implementation. The core of grayscale release is to distribute traffic by modifying Nginx config files. The content is quite simple, but the whole process from zero to one is rather cumbersome.&lt;/p&gt;

&lt;p&gt;In addition, this demo is only the simplest one. In the real DevOps development process, other operations such as compilation and build, code check, security scanning, and automated test cases need to be integrated.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>devops</category>
    </item>
    <item>
      <title>DevUI Admin V1.0 Released!!!🎉🎉🥳</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Fri, 30 Apr 2021 03:38:03 +0000</pubDate>
      <link>https://dev.to/kagol/devui-admin-v1-0-released-4b7</link>
      <guid>https://dev.to/kagol/devui-admin-v1-0-released-4b7</guid>
      <description>&lt;p&gt;At the end of April, DevUI Admin, with widely expectation, comes to the first open source version(1.0.0) of Angular.&lt;/p&gt;

&lt;p&gt;DevUI Admin is an enterprise-level middle and back-end frontend/design solution. Based on DevUI Design, we build a back-end management template based on our own design specifications and basic components.&lt;/p&gt;

&lt;p&gt;After several months of incubation, DevUI Admin provides you a solution for building a front-end system that can be used in middle and back end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responsive: adapts to different screen sizes to provide users a more comfortable interface and user experience.&lt;/li&gt;
&lt;li&gt;Personalized Theme: supports dynamic switching between multiple theme styles and personalized settings.&lt;/li&gt;
&lt;li&gt;Layout Switching: the page layout is configurable and flexible.&lt;/li&gt;
&lt;li&gt;Internationalization: implements the internationalization to meet the requirements of multi-language.&lt;/li&gt;
&lt;li&gt;Mock data: local data debugging and a separation between front-end and back-end;&lt;/li&gt;
&lt;li&gt;Page templates: Based on DevUI practice and experience, typical business scenarios are extracted and various page templates are provided.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can learn more at devui.design or follow DevUI Admin on GitHub.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preview page: &lt;a href="https://devui.design/admin/" rel="noopener noreferrer"&gt;https://devui.design/admin/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Homepage: &lt;a href="https://devui.design/admin-page/home" rel="noopener noreferrer"&gt;https://devui.design/admin-page/home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/DevCloudFE/ng-devui-admin" rel="noopener noreferrer"&gt;https://github.com/DevCloudFE/ng-devui-admin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DevUI Design: &lt;a href="https://devui.design/home" rel="noopener noreferrer"&gt;https://devui.design/home&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Responsive
&lt;/h1&gt;

&lt;p&gt;DevUI Admin provides a grid-based responsive solution. After initializing an Admin project, you will obtain the page responsive capability. More importantly, we also provide &lt;code&gt;da-grid&lt;/code&gt; as a common component that you can use for responsive page building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16po8jbxedqpeynfjkil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16po8jbxedqpeynfjkil.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Personalized Theme
&lt;/h1&gt;

&lt;p&gt;Based on basic &lt;a href="https://devui.design/components/zh-cn/theme-guide" rel="noopener noreferrer"&gt;ng-devui&lt;/a&gt; capabilities, the DevUI Admin provides multiple themes for users to select. In addition to global color matching, font size and fillet size can be configured too. Users can select a theme based on their personal preferences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mwwndf4adapohquoi45.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mwwndf4adapohquoi45.PNG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Switch the layout.
&lt;/h1&gt;

&lt;p&gt;DevUI Admin supports multiple layouts. You can customize layout settings by simply modifying your layout config. More importantly, we provide &lt;code&gt;da-layout&lt;/code&gt; as a component that you can also use to extend more layout configurations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kv4b4gnsf2fe3uw51x0.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kv4b4gnsf2fe3uw51x0.PNG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Internationalization
&lt;/h1&gt;

&lt;p&gt;DevUI Admin uses &lt;code&gt;@ngx-translate/core&lt;/code&gt; to implement the internationalization. Internationalization-related words support modular management. You can select the corresponding internationalization configuration after initializing your Admin project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxyc7fewedqjx4w1wul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxyc7fewedqjx4w1wul.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Mock data
&lt;/h1&gt;

&lt;p&gt;Mock data is very important to separate front-end and back-end, so that front-end projects are not affected by back-end interfaces. In DevUI Admin, we have provided you with the method of Mock data. You can select the data Mock support by default when initializing your Admin project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Page Template
&lt;/h1&gt;

&lt;p&gt;In the DevUI Admin GitHub code repository, we provide you with multiple page templates by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgutsdatc6wjye8nu8767.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgutsdatc6wjye8nu8767.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Dashboard
  - Analysis Page
  - Monitoring page
  - Workbench
- Form page
  - Base Form
  - Form Layout
  - Advanced Forms
- List Page
  - Base List
  - Card list
  - Edit List
  - Advanced List
  - Tree List
- Exception page
  - 403
  - 404
  - 500
- Personal page
  - Personal Center
  - Personal settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can take a look to it after pulling the code from github.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cli Support
&lt;/h1&gt;

&lt;p&gt;Currently, the DevUI Admin can use the Angular CLI to initialize an admin project. You can use the Angular CLI to quickly create and configure your admin project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize the project.
$ ng new your-project &amp;amp;&amp;amp; cd your-project
$ ng add ng-devui-admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Follow-up
&lt;/h1&gt;

&lt;p&gt;In the future, we will keep envolving, focusing on performance and usability, optimizing the experience of DevUI Admin, and reducing developer costs. We look forward to receiving your comments and suggestions (&lt;a href="https://github.com/DevCloudFE/ng-devui-admin/issues" rel="noopener noreferrer"&gt;Issue List&lt;/a&gt;). We also look forward to your participation and cooperation.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Flame Pie Under a Waterfall: Three Steps to Quickly Locate Performance Problems on Your Website</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Thu, 15 Apr 2021 11:00:47 +0000</pubDate>
      <link>https://dev.to/kagol/flame-pie-under-a-waterfall-three-steps-to-quickly-locate-performance-problems-on-your-website-4388</link>
      <guid>https://dev.to/kagol/flame-pie-under-a-waterfall-three-steps-to-quickly-locate-performance-problems-on-your-website-4388</guid>
      <description>&lt;h1&gt;
  
  
  The introduction
&lt;/h1&gt;

&lt;p&gt;Performance is an issue.&lt;/p&gt;

&lt;p&gt;When every project grows to a certain scale, it is almost inevitable to encounter performance problems. When encountering performance problems, we are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With a dumb face, I know it’s stuck and slow, I don’t know why.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can quickly insight into performance bottlenecks and find effective optimization solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It depends on our understanding of performance and whether we have a good set of tools and methods.&lt;/p&gt;

&lt;p&gt;Next, I will share with you the three-step method that I often use when locating business performance problems. In order to facilitate memory, I will summarize it in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Flame pie under a waterfall.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not much to say, just drink a mouthful of water!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2P5a0b0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85b70bab2d344a27b525f24f47b84b11%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2P5a0b0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85b70bab2d344a27b525f24f47b84b11%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction to the Performance panel
&lt;/h1&gt;

&lt;p&gt;Before introducing the three-step method, let's briefly understand the Performance panel of Chrome Developer Tools and the basic composition of the performance analysis report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate performance analysis report
&lt;/h2&gt;

&lt;p&gt;Take the juejin personal homepage of the DevUI team as an example, use the Chrome browser to access: &lt;a href="https://juejin.cn/user/712139267650141"&gt;https://juejin.cn/user/712139267650141&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then press F12 to open Chrome's developer tools and select the Performance panel.&lt;/p&gt;

&lt;p&gt;At this time we will see a simple guide:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YpBknTV8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df4ae1be8e02485798f49350f7e57399%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YpBknTV8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df4ae1be8e02485798f49350f7e57399%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two buttons in the guide. The upper button is for manual recording, and the lower is for automatic recording. We click on the fool-like automatic recording, and the automatic recording will automatically refresh the page. After the page is loaded, a performance analysis report of the page is generated. Manual intervention is very convenient.&lt;/p&gt;

&lt;p&gt;The report will be generated after a few seconds. At a glance, it is colorful. I don't know where to start?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gddHCLKT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df59c1504dff4751a0e6e7898022f4c4%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gddHCLKT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df59c1504dff4751a0e6e7898022f4c4%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Composition of performance report
&lt;/h2&gt;

&lt;p&gt;We do a simple panel classification of the generated performance analysis report, and it looks very clear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IH1M1gbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/26b68ba524b14e949355a3f57ba7aecb%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IH1M1gbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/26b68ba524b14e949355a3f57ba7aecb%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Toolbar
&lt;/h3&gt;

&lt;p&gt;At the top of the performance report is a toolbar (or control panel) with a bunch of buttons. The first three that I use more often are the first three. The first two have been introduced in the guidelines, and the third is for use. To clear the report.&lt;/p&gt;

&lt;p&gt;There are also two hidden functions that are also very useful. One is to simulate a slow network speed, and the other is to simulate a slow CPU, which may be used for performance optimization of mobile applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CLyQk45m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f847a37efac64d15a6e379f76df43b2b%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CLyQk45m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f847a37efac64d15a6e379f76df43b2b%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview panel
&lt;/h3&gt;

&lt;p&gt;Below the toolbar is an overview panel that displays the FPS (Frames Per Second) during the entire page loading process, which is used to evaluate the smoothness of the page. A large red area indicates that the page may be stuck.&lt;/p&gt;

&lt;p&gt;Below the FPS is the time spent by the CPU to process each task, and further down is the time consumed by the network request. At the bottom of the overview panel is a screenshot of each frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread panel
&lt;/h3&gt;

&lt;p&gt;Below the overview panel is the thread panel. The network request waterfall chart is expanded by default, and the details of other threads are collapsed.&lt;/p&gt;

&lt;p&gt;Each thread panel is valuable for performance analysis, and the waterfall chart and flame chart are the most commonly used ones. I will focus on these two charts later. How to use these two charts to analyze the performance bottleneck of the website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory panel
&lt;/h3&gt;

&lt;p&gt;Next is the memory panel. The memory panel needs to be opened manually in the control panel. It is a categorized line graph of memory occupancy.&lt;/p&gt;

&lt;p&gt;Each polyline is the memory usage of a task over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JS stack&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;HTML node&lt;/li&gt;
&lt;li&gt;Event monitoring&lt;/li&gt;
&lt;li&gt;GPU memory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Details panel
&lt;/h3&gt;

&lt;p&gt;At the bottom is the details panel. The first thing you see is a pie chart. This pie chart shows the proportion of various types of tasks. This is very useful. Can you see at a glance what type of task is the performance bottleneck.&lt;/p&gt;

&lt;p&gt;Is it resource loading or script execution? Is it page rendering or image drawing? Or is the idle time too long?&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Look at the pie chart
&lt;/h1&gt;

&lt;p&gt;When I introduced the composition of the Performance panel just now, I mentioned 3 very useful performance analysis tools, namely the detailed pie chart, the request waterfall chart, and the main thread flame chart.&lt;/p&gt;

&lt;p&gt;I sum up these three pictures into one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Flame pie under a waterfall.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence is also a trick that I have tried and tested in performance analysis and optimization myself.&lt;/p&gt;

&lt;p&gt;The pie chart in the details panel is used to display the time-consuming proportions of various types of tasks.&lt;/p&gt;

&lt;p&gt;There are mainly the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue is resource loading&lt;/li&gt;
&lt;li&gt;Yellow is script execution&lt;/li&gt;
&lt;li&gt;Purple is page rendering&lt;/li&gt;
&lt;li&gt;Green is the drawing&lt;/li&gt;
&lt;li&gt;White is free time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take the example just now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zXsguxF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b6969cf9d9f4540bfd55ef8460e1acb%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zXsguxF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b6969cf9d9f4540bfd55ef8460e1acb%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be seen from the pie chart that script execution and idleness accounted for the most.&lt;/p&gt;

&lt;p&gt;The script execution time is long, we can probably guess that there may be a long task (Long task);&lt;/p&gt;

&lt;p&gt;And the idle ratio may be too long to wait for the server's response time.&lt;/p&gt;

&lt;p&gt;The pie chart can quickly form a basic judgment, and the specific reasons need to analyze the waterfall chart and the flame chart.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Look at the waterfall chart
&lt;/h1&gt;

&lt;p&gt;Let's take a look at the request waterfall chart. Both the waterfall chart and the flame chart are part of the thread panel. The horizontal axis of the waterfall chart is the time axis. There are many colorful color blocks on the waterfall chart. These color blocks are the request blocks. Each color represents One type of resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue is the HTML file&lt;/li&gt;
&lt;li&gt;Purple is the CSS file&lt;/li&gt;
&lt;li&gt;Yellow is the JavaScript file&lt;/li&gt;
&lt;li&gt;Green is the picture&lt;/li&gt;
&lt;li&gt;Gray is the background interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We mainly focus on those long color blocks. Long color blocks mean time-consuming and may be a performance bottleneck.&lt;/p&gt;

&lt;p&gt;Let's look at the waterfall chart on the juejin' personal homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SAVPRPRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a36050601ea4e57adfe2910f26cb457%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SAVPRPRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a36050601ea4e57adfe2910f26cb457%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize the characteristics of the waterfall chart
&lt;/h2&gt;

&lt;p&gt;Let's observe the characteristics of this picture first, and the ability to observe graphics. I believe you have already cultivated it in elementary school. Generally, we can summarize the following more obvious characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature 1: The big waterfall is divided into three small waterfalls&lt;/li&gt;
&lt;li&gt;Feature 2: The small waterfall on the far left is mostly yellow blocks, the small waterfall in the middle is mostly gray blocks, and the small waterfall on the far right is mostly green blocks.&lt;/li&gt;
&lt;li&gt;Feature 3: There is a gap between the first two waterfalls, and there is no color block in the middle&lt;/li&gt;
&lt;li&gt;Feature 4: The last two waterfalls are connected together by a "tail" of a gray block&lt;/li&gt;
&lt;li&gt;Feature 5: There is an extra-long gray color block on the top&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can summarize many similar characteristics, but what do these characteristics indicate? Can you help us locate performance bottlenecks?&lt;/p&gt;

&lt;p&gt;Answering these questions requires us to have a lot of understanding of the waterfall chart and the principle of the browser. Let's analyze it step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyze the meaning of the waterfall chart
&lt;/h2&gt;

&lt;p&gt;We analyze in the order from left to right and top to bottom. There are two color blocks on the far left, a gray color block and a blue color block. We click on these two color blocks respectively, and look at their details in the details panel. Detailed information.&lt;/p&gt;

&lt;p&gt;Look at the gray color block first&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rg2MG4Ip--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/290be79443c0404ebf4b813c1ef5401d%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rg2MG4Ip--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/290be79443c0404ebf4b813c1ef5401d%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have noticed that the initiator of this request is a Chrome plug-in: chrome://new-tab-page/omnibox.mojom-lite.js&lt;/p&gt;

&lt;p&gt;So we don’t pay attention, and then look at the blue color block&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oBkZLA2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad291ed132794851965f3e0b54b89ef5%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oBkZLA2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad291ed132794851965f3e0b54b89ef5%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we have already introduced, the blue color blocks represent HTML files. We can also verify this from the Mime Type of the details as text/html.&lt;/p&gt;

&lt;p&gt;We scroll the mouse wheel to enlarge the waterfall chart and see the details of the blue request block&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E0i6Xf8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15437f73032e451bb120ab81c8ffa33e%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E0i6Xf8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15437f73032e451bb120ab81c8ffa33e%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The composition of the request block
&lt;/h3&gt;

&lt;p&gt;By looking at the detailed map, we have new discoveries:&lt;/p&gt;

&lt;p&gt;Each request block consists of four parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left line: represents the time before the request is sent (Before Request Sent)&lt;/li&gt;
&lt;li&gt;Light-colored block: represents the request has been sent (Request Sent), until the server returns the first byte to the browser (TTFB, Time to First Byte)&lt;/li&gt;
&lt;li&gt;Dark block: all the content returned by the server is downloaded to the browser (Content Download)&lt;/li&gt;
&lt;li&gt;Right line: Waiting for main thread&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This HTML file is the starting point for the rendering of the entire web page. After successfully requesting and downloading this file, there will be the next story.&lt;/p&gt;

&lt;p&gt;The light-colored part of this request block accounts for a relatively large proportion. According to the previous introduction, the light-colored part represents the response speed of the server. The browser has sent the request early, but the server responded late (the first word Section arrives in the browser).&lt;/p&gt;

&lt;p&gt;In the middle, the network may be slow, or the server processing speed may be slow, which requires specific investigation. After all, this HTML file is not too big, only 111KBb, but it took 179ms.&lt;/p&gt;

&lt;p&gt;Compared with another file, layouts.default.js, which is 124KB larger than it, the request time is more than half shorter than it, only 74ms. (Later it was found that this data is unstable, this HTML file should not constitute a performance bottleneck)&lt;/p&gt;

&lt;p&gt;In addition, all subsequent requests depend on this HTML, and other requests will not happen without it. It is a blocking request and performance must be guaranteed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discover possible performance bottlenecks
&lt;/h3&gt;

&lt;p&gt;Let's continue to look at the request block on the right. The long gray block at the top is still a request for the Chrome plug-in. Let's ignore it. Look at the pile of yellow request blocks below. These are JavaScript files.&lt;/p&gt;

&lt;p&gt;After the HTML file is downloaded, it will start to parse the HTML tags line by line. When encountering the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag with the src attribute and who is set, it will download the JavaScript script file specified by src.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HKKWRbG5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fcb156ff079249a9b173e86d81f993a5%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HKKWRbG5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fcb156ff079249a9b173e86d81f993a5%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be seen from the waterfall chart that a total of 8 JavaScript files have been downloaded in parallel, and their domain names are all the same: &lt;code&gt;sf1-scmcdn2-tos.pstatp.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Doesn't it mean that the maximum number of concurrent requests for the same domain name in Chrome is 6?&lt;/p&gt;

&lt;p&gt;Not only JavaScript files, but there are also 3 image resources with the same domain name below, which are also requested in parallel, which means that 11 requests are initiated almost simultaneously.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The static resource server of juejin has been upgraded to HTTP/2.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTTP/2 multiplexing can realize a TCP connection to transmit multiple resources at the same time.&lt;/p&gt;

&lt;p&gt;We went to the Network panel to see the details of these JavaScript requests, and it was exactly the same as we guessed. We must give the juejin a thumbs up for this👍.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0BmHJTrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b46bad97b1cf46388eae69c2c8cb28ee%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0BmHJTrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b46bad97b1cf46388eae69c2c8cb28ee%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Send a comparison chart between a certain 86 website and the juejin, let’s get a feel for it&lt;/p&gt;

&lt;p&gt;An 86 website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aGTgXedd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6bf78ab7422d4334b013439ee622c40b%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aGTgXedd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6bf78ab7422d4334b013439ee622c40b%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;juejin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EdyZFCVQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/775b17cfce0e4100a777f3b2e03019c6%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EdyZFCVQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/775b17cfce0e4100a777f3b2e03019c6%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the former is more like a waterfall, I like the silky experience of the latter.&lt;/p&gt;

&lt;p&gt;Let's take a closer look at these 8 requests. I believe that you must have discovered a phenomenon in detail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What they have in common, except for the domain name just mentioned, the left and right lines of these request blocks are very short&lt;/li&gt;
&lt;li&gt;There are three very long request blocks, 1/5/8 respectively, which require special attention&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is a good phenomenon that the left and right lines of the request block are very short, indicating that there is no waiting time, and all the time is spent on transmitting data.&lt;/p&gt;

&lt;p&gt;We click on the 1/5/8 request block to see their details&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request block&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Size: 4KB&lt;br&gt;Time consuming: 635ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Size: 90KB&lt;br&gt;Time consuming: 635ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Size: 3.9MB&lt;br&gt;Time consuming: 633ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is very strange. 1/5 of the resource size and 8 are not in the same order of magnitude, but it takes more time than 8.&lt;/p&gt;

&lt;p&gt;In order to determine whether this was accidental or inevitable, I recorded the performance report of this juejin personal homepage twice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WdK8RYqD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1bfaa13151943e3aebffe2984382496%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WdK8RYqD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1bfaa13151943e3aebffe2984382496%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0B_wkkJ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6af7f771beb142ddaf2edeb9766c4ecf%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0B_wkkJ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6af7f771beb142ddaf2edeb9766c4ecf%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time it is basically the same as expected. 8 takes longer than the others. This JavaScript file is 3.9MB, which is too big and may be a performance bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Others
&lt;/h3&gt;

&lt;p&gt;Let us continue to analyze, there are three color blocks under the yellow JavaScript color block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purple: CSS style file&lt;/li&gt;
&lt;li&gt;Green: Picture file&lt;/li&gt;
&lt;li&gt;Gray: font file (189KB in size)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files are not large in size, and after generating performance reports many times, it is found that these requests are not as long as the eighth JavaScript file, so it is preliminary judged that these requests do not constitute a performance bottleneck.&lt;/p&gt;

&lt;p&gt;Then look at the middle waterfall. After generating performance reports several times, I found that the middle waterfall does not have any particularly time-consuming requests, but no matter how many reports are generated, one thing is certain, that is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is always a gap between these three waterfalls&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What do these blanks show?&lt;/p&gt;

&lt;p&gt;After reading the flame map, I believe you will suddenly be enlightened.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: Look at the flame graph
&lt;/h1&gt;

&lt;p&gt;Before looking at the official flame diagram, let’s take a look at the effect of a waterfall diagram and a flame diagram together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lv9wTW3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6b4db8b54ea46a29e68c37b121813b8%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lv9wTW3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6b4db8b54ea46a29e68c37b121813b8%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After reading this comparison picture of waterfall and flame, you must have seen a phenomenon&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If there are blank places in the waterfall chart, the flame chart will have colors; &lt;br&gt;
Where the waterfall chart has colors, the flame chart is blank.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But Why?&lt;/p&gt;

&lt;p&gt;To answer this question, you need to understand the principle of the browser's main thread to perform tasks, and what the flame graph does. Don't worry, let's analyze it step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the flame graph
&lt;/h2&gt;

&lt;p&gt;The flame graph is also part of the thread panel, which represents the task flow of the browser's main thread:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As the page loads and time goes by, what does the main thread do in turn&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The horizontal axis of the flame graph is time, and the vertical axis is each macro task.&lt;/p&gt;

&lt;p&gt;There are several micro tasks under each macro task, and there may be many sub tasks under each micro task, and so on.&lt;/p&gt;

&lt;p&gt;Because some tasks have a deep nesting level and some have a shallow nesting level, they appear to be inverted flames.&lt;/p&gt;

&lt;p&gt;The color of each type of task is different (no need to remember, just have a general impression):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse HTML Parse HTML: blue&lt;/li&gt;
&lt;li&gt;Parse Stylesheet: Blue&lt;/li&gt;
&lt;li&gt;Evaluate Script: yellow&lt;/li&gt;
&lt;li&gt;Recalculate Style: dark purple&lt;/li&gt;
&lt;li&gt;Paint: dark green&lt;/li&gt;
&lt;li&gt;Perform microtasks Microtasks: yellow&lt;/li&gt;
&lt;li&gt;Ajax request XHR Load: yellow&lt;/li&gt;
&lt;li&gt;Function Call: Yellow&lt;/li&gt;
&lt;li&gt;Trigger the timer Timer Fired: yellow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s take a look at the flame map of the juejin’ personal homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j5FNb2Vr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8628b108e00743b8bd21cd73f90ec79a%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j5FNb2Vr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8628b108e00743b8bd21cd73f90ec79a%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize the characteristics of the flame graph
&lt;/h2&gt;

&lt;p&gt;Then use the skills of &lt;code&gt;look at pictures and find patterns&lt;/code&gt; that we learned in elementary school to find out the characteristics of this picture, and at a glance, we can summarize at least the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature 1: Generally speaking, there are blanks on both sides, with three big flames in the middle&lt;/li&gt;
&lt;li&gt;Feature 2: The two big flames on both sides correspond to the two blanks in the waterfall chart (this explains why there are blanks between the three small waterfalls in the waterfall chart)&lt;/li&gt;
&lt;li&gt;Feature 3: Some macro tasks are particularly long, and the background color is a red hatching line (not gray), and there is a small red triangle in the upper right corner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spend more time, maybe we can find more, but these are the most obvious.&lt;/p&gt;

&lt;p&gt;In order to answer these questions, we need to observe the flame diagram at close range.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyze the meaning of the flame graph
&lt;/h2&gt;

&lt;p&gt;Since the flame graph represents what the main thread is doing at each point in time, the blank naturally means that the main thread is not doing work, so what is it doing?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's waiting&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What are you waiting for?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait for the server to return some necessary resources and data&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and so&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The blank space of the flame graph is that the browser is waiting for the server to return data&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Looking for long tasks
&lt;/h3&gt;

&lt;p&gt;Among all the tasks performed by the main thread, we especially need to pay attention to those long tasks that take a long time (Long tasks). The characteristics of these long tasks have been mentioned earlier:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The background color is a red shaded line&lt;br&gt;
There is a small red triangle in the upper right corner&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three long tasks were found in 1s&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_gH8A8nA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96b9784630bf40cc8666b482ad3e96c6%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_gH8A8nA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96b9784630bf40cc8666b482ad3e96c6%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyze long tasks
&lt;/h3&gt;

&lt;p&gt;The next step is to analyze long tasks and find specific modules/components/methods that take a long time.&lt;/p&gt;

&lt;p&gt;Let's zoom in on the largest flame on the far right to see what secrets are inside.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5IFwbd2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b57c374140164b0db16ccbc33df04a5d%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5IFwbd2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b57c374140164b0db16ccbc33df04a5d%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After zooming in, we quickly discovered that this long task that took 591ms, 90% of the time was spent on a method called &lt;code&gt;init&lt;/code&gt;, this method was executed a total of 6 times, of which 3/4/6 time-consuming Especially long&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The nth init method&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Time consuming: 197ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Time consuming: 93ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Time consuming: 111ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What exactly does this init method do?&lt;/p&gt;

&lt;p&gt;It may be hung in the Vue component. Could it be that some of the components are particularly large and the logic inside is too complicated. Here we need the front-end of juejin to give the answer.&lt;/p&gt;

&lt;p&gt;Look at the second largest flame on the left, and scroll the mouse wheel to enlarge it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bOwFIT-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/531975d1d5ae4247a2b0a6a77fea01aa%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bOwFIT-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/531975d1d5ae4247a2b0a6a77fea01aa%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We found that one of the forEach loops was particularly time-consuming. This loop seemed to be calculating something and took 150ms in total.&lt;/p&gt;

&lt;p&gt;This still needs to look at the specific source code to find the root cause of the problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AbwWSN8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcce04f337694bbba88f6dc1b1e4ec49%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AbwWSN8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcce04f337694bbba88f6dc1b1e4ec49%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cases of finding performance bottlenecks through flame graphs
&lt;/h2&gt;

&lt;p&gt;Finally, I will share with you a performance problem of a dependent library that I found in the XBoard Kanban project through the flame graph.&lt;/p&gt;

&lt;p&gt;Follow the same idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find long tasks&lt;/li&gt;
&lt;li&gt;Enlarge the flame map of the long mission&lt;/li&gt;
&lt;li&gt;Look down layer by layer until you find a time-consuming method with a name (most of the code on the live network is compressed and confused, and the name is not visible, the development environment will be more convenient to locate the method with performance problems)&lt;/li&gt;
&lt;li&gt;Click this method in the flame chart, see the link after Function in the details panel, click this link to jump directly to the specified method in the corresponding file&lt;/li&gt;
&lt;li&gt;Search the method name in the source code and find it&lt;/li&gt;
&lt;li&gt;Find a solution&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the time, there were a lot of long tasks on the XBoard board page, I found TOP3 among them&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nr6clrW_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/665fb2258bf9421e8c974da8a2b1fa80%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nr6clrW_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/665fb2258bf9421e8c974da8a2b1fa80%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I zoomed in on the first long task, and I quickly gained a reward. I found that there is a method called &lt;code&gt;drawQrCode&lt;/code&gt; that takes a long time, and it took 192ms in total.&lt;/p&gt;

&lt;p&gt;Then, by looking at the details, I found that this is a method of relying on a library. The dependent library defines a drawQrCode to draw a QR code. This QR code is actually not on the Kanban page, but needs to be hovered to a button by the mouse. Just load it out.&lt;/p&gt;

&lt;p&gt;So the solution at that time was to delay the execution of the drawQrCode method, namely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the home page is loaded, the drawQrCode method is not executed, and only executed when the mouse is moved to the corresponding button.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GRuH6pE4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ab9e680a443b4aae970cbccf6edbb601%257Etplv-k3u1fbpfcp-watermark.image" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GRuH6pE4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ab9e680a443b4aae970cbccf6edbb601%257Etplv-k3u1fbpfcp-watermark.image" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The relationship between waterfall graph and flame graph
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The waterfall chart and the flame chart are mutually complementary and mutually verifying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The waterfall chart represents that the browser initiates a request to the server, and then the browser executes the corresponding logic and page rendering through the script according to the data returned by the server.&lt;/p&gt;

&lt;p&gt;When the waterfall chart has a request block, it means that the browser is requesting data from the server. If the browser must rely on this data to do the next page rendering, it is likely that the browser will have nothing to do before the server returns the data, and then the flame chart If there is a blank on the pie chart, Idle will also appear on the pie chart.&lt;/p&gt;

&lt;p&gt;When the browser gets the data returned by the server, the main thread is processing the data and rendering the page, so it is very likely that it will not be able to send a request to the server, and the waterfall chart will appear blank at this time.&lt;/p&gt;

&lt;p&gt;and so&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;It is found that the waterfall chart is blank, and there may be a long task. It is necessary to find a specific time-consuming method and optimize it
&lt;/li&gt;
&lt;li&gt;It is found that the flame graph is blank. It is likely that some background interfaces are slow or there are very large static resources. You need to locate the reason for the slowness and find a way to optimize it.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article first briefly introduces how to generate a website performance analysis report, as well as the general composition of this report;&lt;/p&gt;

&lt;p&gt;Then I will share with you the three-step method I often use when locating business performance problems: using flame scones under the waterfall;&lt;/p&gt;

&lt;p&gt;From the pie chart, we can have a general understanding of the performance of the website. From the waterfall chart, we can quickly find slow interfaces and large resources. From the flame chart, we can get a detailed insight into which module/component/method is possible. Become a performance bottleneck.&lt;/p&gt;

&lt;p&gt;Finally, I recommend Google's official performance evaluation guide: &lt;a href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance"&gt;https://developers.google.com/web/tools/chrome-devtools/evaluate-performance&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About DevUI team
&lt;/h1&gt;

&lt;p&gt;DevUI is a team with both design and engineering perspectives, serving for the DevCloud platform of Huawei Cloud and several internal middle and background systems of Huawei, serving designers and front-end engineers.&lt;/p&gt;

&lt;p&gt;Official website: &lt;a href="//devui.design/"&gt;devui.design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ng component library: &lt;a href="https://github.com/devcloudfe/ng-devui"&gt;ng-devui&lt;/a&gt; (Welcome to star🌟)&lt;/p&gt;

</description>
      <category>performance</category>
      <category>javascript</category>
      <category>devui</category>
      <category>angular</category>
    </item>
    <item>
      <title>How does Quill convert Delta to DOM? 3/10</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Sun, 11 Apr 2021 23:53:21 +0000</pubDate>
      <link>https://dev.to/kagol/how-does-quill-convert-delta-to-dom-2664</link>
      <guid>https://dev.to/kagol/how-does-quill-convert-delta-to-dom-2664</guid>
      <description>&lt;h1&gt;
  
  
  The introduction
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/devui/how-does-quill-describe-editor-content-1i5g"&gt;last post&lt;/a&gt;, we showed how Quill uses Delta to describe editor content and its variations. We learned that Delta is just a normal JSON structure with only three actions and one attribute, but it is extremely expressive.&lt;/p&gt;

&lt;p&gt;So how does Quill apply Delta data and render it into the editor?&lt;/p&gt;

&lt;h1&gt;
  
  
  How to use setContents
&lt;/h1&gt;

&lt;p&gt;There is an API in Quill called setContents that renders Delta data into the editor. This post will focus on how this API is implemented.&lt;/p&gt;

&lt;p&gt;Take the Delta data from the previous post as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const delta = { "ops": [
    { "insert": "Hello " },
    { "insert": "World", "attributes": { "bold": true } },
    { "insert": "\n" } ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have created an instance of Quill using new Quill(), we can call its API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const quill = new Quill('#editor', {
  theme: 'snow'
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try calling the setContents method, passing in the Delta data we just had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quill.setContents(delta);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected formatted text appears in the editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghov7y831jm8thm4t2po.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghov7y831jm8thm4t2po.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Deep into setContents
&lt;/h1&gt;

&lt;p&gt;By looking at the source of setContents, we call the modify method, passing in a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setContents(delta, source = Emitter.sources.API) {
  return modify.call( this, () =&amp;gt; {
    delta = new Delta(delta);
    const length = this.getLength();
    const deleted = this.editor.deleteText(0, length);
    const applied = this.editor.applyDelta(delta);
    ... // The non-core code is omitted for ease of reading
    return deleted.compose(applied);
  }, source, );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call method is used to call modify to change its internal this pointer, which refers to the current Quill instance. Since the modify method is not defined in the Quill class, it needs to be done.&lt;/p&gt;

&lt;p&gt;Let's look at the anonymous function passed in the modify method instead of the modify method.&lt;/p&gt;

&lt;p&gt;This function does three main things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete all the original content in the editor&lt;/li&gt;
&lt;li&gt;Apply the incoming Delta data and render it to the editor&lt;/li&gt;
&lt;li&gt;Returns the Delta data after combining 1 and 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's focus on step 2, which involves the applyDelta method of the Editor class.&lt;/p&gt;

&lt;h1&gt;
  
  
  How the applyDelta method works
&lt;/h1&gt;

&lt;p&gt;As you might guess by its name, the purpose of this method is to apply and render the incoming Delta data into the editor.&lt;br&gt;
The implementation, as we can probably guess, is that the ops array in the loop Delta is applied to the editor one by one.&lt;br&gt;
Its source code is 54 lines long and looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;applyDelta(delta) {
  let consumeNextNewline = false;
  this.scroll.update();
  let scrollLength = this.scroll.length();
  this.scroll.batchStart();
  const normalizedDelta = normalizeDelta(delta);

  normalizedDelta.reduce((index, op) =&amp;gt; {
    const length = op.retain || op.delete || op.insert.length || 1;
    let attributes = op.attributes || {};
    // 1.Insert text
    if (op.insert != null) {
      if (typeof op.insert === 'string') {
        // Plain text content
        let text = op.insert;
        ... // For ease of reading, omit non-core code
        this.scroll.insertAt(index, text);
        ... // For ease of reading, omit non-core code
      } else if (typeof op.insert === 'object') {
        // Rich text content
        const key = Object.keys(op.insert)[0];
        // There should only be one key
        if (key == null) return index;
        this.scroll.insertAt(index, key, op.insert[key]);
      }
      scrollLength += length;
    }
    // 2.Formatting the text
    Object.keys(attributes).forEach(name =&amp;gt; {
      this.scroll.formatAt(index, length, name, attributes[name]);
    });
    return index + length;
  }, 0);
  ... // For ease of reading, omit non-core code
  this.scroll.batchEnd();
  this.scroll.optimize();
  return this.update(normalizedDelta);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we guessed, this method is to use the Delta reduce method to iterate over the incoming Delta data, separating the logic of content insertion and content deletion. The iteration of content insertion mainly does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To insert plain text or rich text content: insertAt&lt;/li&gt;
&lt;li&gt;Formats the text: formatAt&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, we have parsed the logic to apply and render the Delta data to the editor.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Here's a summary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There's no logic in the setContents method itself, it just calls the modify method, right&lt;/li&gt;
&lt;li&gt;The applyDelta method on the Editor object is called in the anonymous function passed to the modify method&lt;/li&gt;
&lt;li&gt;The applyDelta method iterates over the incoming Delta data and in turn inserts/formats/deletes the editor content described by the Delta data&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  About DevUI team
&lt;/h1&gt;

&lt;p&gt;DevUI is a team with both design and engineering perspectives, serving for the DevCloud platform of Huawei Cloud and several internal middle and background systems of Huawei, serving designers and front-end engineers.&lt;/p&gt;

&lt;p&gt;Official website: &lt;a href="//devui.design/"&gt;devui.design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ng component library: &lt;a href="https://github.com/devcloudfe/ng-devui" rel="noopener noreferrer"&gt;ng-devui&lt;/a&gt; (Welcome to star🌟)&lt;/p&gt;

&lt;p&gt;by &lt;a href="https://github.com/kagol/" rel="noopener noreferrer"&gt;Kagol&lt;/a&gt;&lt;/p&gt;

</description>
      <category>quill</category>
      <category>delta</category>
      <category>devui</category>
      <category>angular</category>
    </item>
    <item>
      <title>How does Quill describe editor content? 2/10</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Tue, 06 Apr 2021 23:25:07 +0000</pubDate>
      <link>https://dev.to/kagol/how-does-quill-describe-editor-content-1i5g</link>
      <guid>https://dev.to/kagol/how-does-quill-describe-editor-content-1i5g</guid>
      <description>&lt;h1&gt;
  
  
  The introduction
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/devui/how-quill-module-works-319f"&gt;last post&lt;/a&gt;, we introduced how the Quill module works. This time, let's talk about the data model of Quill and see how Quill describes the content in the editor.&lt;/p&gt;

&lt;p&gt;You will be surprised by the simplicity and expressive power of the data structure of the &lt;a href="https://github.com/quilljs/delta"&gt;Delta&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Quill profile
&lt;/h1&gt;

&lt;p&gt;Quill is an API-driven, easily extensible, and cross-platform modern Web rich text editor. At present, the number of Star on GitHub has exceeded 25K.&lt;/p&gt;

&lt;p&gt;Quill is also very easy to use, creating a basic editor in just a few lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  var quill = new Quill('#editor', {
    theme: 'snow'
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D1cEOsxg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdce3097kr1bseosdkou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D1cEOsxg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdce3097kr1bseosdkou.png" alt="basic editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How does Quill describe formatted text?
&lt;/h1&gt;

&lt;p&gt;Let's start with the simplest case: formatted text.&lt;/p&gt;

&lt;p&gt;When we insert some formatted content into the editor, the traditional approach is to insert the corresponding DOM directly into the editor, and compare the DOM tree to record the changes.&lt;/p&gt;

&lt;p&gt;There are a number of inconveniences associated with working directly with the DOM, such as the difficulty of knowing exactly what format certain characters or content are in in the editor, especially for custom rich text formats.&lt;/p&gt;

&lt;p&gt;Quill puts a layer of abstraction on top of the DOM, using a very neat data structure to describe the contents of the editor and its changes: Delta.&lt;/p&gt;

&lt;p&gt;Delta is a subset of JSON that contains only one ops attribute, whose value is an array of objects, each of which represents an operation on the editor (based on the initial state of the editor being null).&lt;/p&gt;

&lt;p&gt;For example, there is "Hello World" in the editor:&lt;br&gt;
 &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v_gvA7sr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p622ejdirlct4gda9uor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v_gvA7sr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p622ejdirlct4gda9uor.png" alt="formatted text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Described by Delta as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ops": [
    { "insert": "Hello " },
    { "insert": "World", "attributes": { "bold": true } },
    { "insert": "\n" }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meaning is obvious: insert "Hello "in the empty editor, insert a bold "World" after the previous operation, and finally insert a newline "\n".&lt;/p&gt;

&lt;h1&gt;
  
  
  How does Quill describe changes in content?
&lt;/h1&gt;

&lt;p&gt;Delta is very concise, but very expressive.&lt;/p&gt;

&lt;p&gt;It has only three actions and one attribute, which is enough to describe any rich text content and any variation in content.&lt;/p&gt;

&lt;p&gt;3 actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;insert&lt;/li&gt;
&lt;li&gt;retain&lt;/li&gt;
&lt;li&gt;delete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1 attribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if we change the bold "World" to the red text "World", this action is described by Delta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ops": [
    { "retain": 6 },
    { "retain": 5, "attributes": { "color": "#ff0000" } }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means: keep the first six characters of the editor, that is, leave "Hello " intact and the next five characters "World", and set "World" to the font color "#FF0000".&lt;/p&gt;

&lt;p&gt;If you wanted to delete the word "World", I'm sure you can guess how to use Delta, and yes you can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ops": [
    { "retain": 6 },
    { "delete": 5 }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  How does Quill describe rich text content?
&lt;/h1&gt;

&lt;p&gt;The most common type of rich text is an image. How does Quill use Delta to describe an image?&lt;/p&gt;

&lt;p&gt;In addition to a string format that describes ordinary characters, the insert attribute can also be an object format that describes rich text content, such as an image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ops": [
    { "insert": { "image": "https://quilljs.com/assets/images/logo.svg" } },
    { "insert": "\n" }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ops": [
    { "insert": { "formula": "e=mc^2" } },
    { "insert": "\n" }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quill offers great flexibility and extensibility to customize rich text content and formats such as slides, mind maps, and even 3D models.&lt;/p&gt;

&lt;p&gt;If you're excited to create your own custom editor content, don't be anxious. In the future post, I'll take you through the inner workings of Quill and help you create your own custom content and custom modules.&lt;/p&gt;

&lt;p&gt;Expect it!🎉🎉&lt;/p&gt;

&lt;h1&gt;
  
  
  About DevUI team
&lt;/h1&gt;

&lt;p&gt;DevUI is a team with both design and engineering perspectives, serving for the DevCloud platform of Huawei Cloud and several internal middle and background systems of Huawei, serving designers and front-end engineers.&lt;/p&gt;

&lt;p&gt;Official website: &lt;a href="https://devui.design/"&gt;devui.design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ng component library: &lt;a href="https://github.com/devcloudfe/ng-devui"&gt;ng-devui&lt;/a&gt; (Welcome to star🌟)&lt;/p&gt;

&lt;p&gt;by &lt;a href="https://github.com/kagol/"&gt;Kagol&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be continued...&lt;/p&gt;

</description>
      <category>quill</category>
      <category>delta</category>
      <category>devui</category>
      <category>angular</category>
    </item>
    <item>
      <title>How Quill module works? 1/10</title>
      <dc:creator>Kagol</dc:creator>
      <pubDate>Sun, 28 Mar 2021 04:36:16 +0000</pubDate>
      <link>https://dev.to/kagol/how-quill-module-works-319f</link>
      <guid>https://dev.to/kagol/how-quill-module-works-319f</guid>
      <description>&lt;h1&gt;
  
  
  The introduction
&lt;/h1&gt;

&lt;p&gt;This post is based on DevUI rich text editor development practice(&lt;code&gt;EditorX&lt;/code&gt;) and &lt;code&gt;Quill&lt;/code&gt; source code written.&lt;/p&gt;

&lt;p&gt;EditorX is a handy, easy-to-use, and powerful rich text editor developed by &lt;a href="https://devui.design/" rel="noopener noreferrer"&gt;DevUI&lt;/a&gt;. It is based on &lt;a href="https://quilljs.com/" rel="noopener noreferrer"&gt;Quill&lt;/a&gt; and has extensive extensions to enhance the editor's power.&lt;/p&gt;

&lt;p&gt;Quill is an open source rich text editor for the Web that is &lt;code&gt;API-driven&lt;/code&gt; and supports &lt;code&gt;format and module customization&lt;/code&gt;. It currently has more than &lt;code&gt;29K&lt;/code&gt; stars on GitHub.&lt;/p&gt;

&lt;p&gt;If you haven't had contact with Quill, it is recommended to go to the official website of &lt;a href="https://quilljs.com/" rel="noopener noreferrer"&gt;Quill&lt;/a&gt; first to understand its basic concept.&lt;/p&gt;

&lt;p&gt;By reading this post, you will learn:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is Quill module? How to configure Quill module?&lt;/li&gt;
&lt;li&gt;Why and how to create a custom Quill module?&lt;/li&gt;
&lt;li&gt;How does a Quill module communicate with Quill?&lt;/li&gt;
&lt;li&gt;Dive into Quill's modularity mechanism&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  A preliminary study of Quill module
&lt;/h1&gt;

&lt;p&gt;Anyone who has used Quill to develop rich text applications should be familiar with Quill's modules.&lt;/p&gt;

&lt;p&gt;For example, when we need to customize our own toolbar buttons, we will configure the toolbar module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var quill = new Quill('#editor', {
  theme: 'snow',
  modules: {
    toolbar: [['bold', 'italic'], ['link', 'image']]
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;modules&lt;/code&gt; parameter is used to configure the module.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;toolbar&lt;/code&gt; parameter is used to configure the toolbar module and is passed in a two-dimensional array representing the grouped toolbar buttons.&lt;/p&gt;

&lt;p&gt;The rendered editor will contain four toolbar buttons:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3oosr3tipmel5h5q6qm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3oosr3tipmel5h5q6qm5.png" alt="Alt Text" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see the Demo above, please anger the &lt;a href="https://codepen.io/kagol/pen/oNXZwQv" rel="noopener noreferrer"&gt;configuration toolbar module&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quill module is a normal JS class
&lt;/h2&gt;

&lt;p&gt;So what is a Quill module?&lt;/p&gt;

&lt;p&gt;Why do we need to know and use the Quill module?&lt;/p&gt;

&lt;p&gt;A Quill module is just &lt;code&gt;a normal JavaScript class&lt;/code&gt; with constructors, member variables, and methods.&lt;/p&gt;

&lt;p&gt;The following is the general source structure of the toolbar module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Toolbar {
  constructor(quill, options) {
    // Parse the toolbar configuration of the incoming module (that is, the two-dimensional array described earlier) and render the toolbar
  }


  addHandler(format, handler) {
    this.handlers[format] = handler;
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that the toolbar module is just a normal JS class. The constructor passes in the Quill instance and the options configuration, and the module class gets the Quill instance to control and manipulate the editor.&lt;/p&gt;

&lt;p&gt;For example, a toolbar module will construct a toolbar container based on the Options configuration, fill the container with buttons/drop-down boxes, and bind the button/drop-down box handling events. The end result is a toolbar rendered above the editor body that allows you to format elements in the editor or insert new elements in the editor through the toolbar buttons/drop-down boxes.&lt;/p&gt;

&lt;p&gt;The Quill module is very powerful, and we can use it to &lt;code&gt;extend the power of the editor&lt;/code&gt; to do what we want.&lt;/p&gt;

&lt;p&gt;In addition to toolbar modules, Quill also has some useful modules built in. Let's take a look at them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quill built-in modules
&lt;/h2&gt;

&lt;p&gt;There are 6 built-in modules in Quill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clipboard&lt;/li&gt;
&lt;li&gt;History&lt;/li&gt;
&lt;li&gt;Keyboard&lt;/li&gt;
&lt;li&gt;Syntax&lt;/li&gt;
&lt;li&gt;Toolbar&lt;/li&gt;
&lt;li&gt;Uploader&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clipboard, History, and Keyboard are the built-in modules required by Quill, which will be automatically opened. They can be configured but not cancelled. Among them:&lt;/p&gt;

&lt;p&gt;The Clipboard module handles copy/paste events, matching HTML element nodes, and HTML-to-delta conversions.&lt;/p&gt;

&lt;p&gt;The History module maintains a stack of actions that record every editor action, such as inserting/deleting content, formatting content, etc., making it easy to implement functions such as Undo/Redo.&lt;/p&gt;

&lt;p&gt;The Keyboard module is used to configure Keyboard events to facilitate the implementation of Keyboard shortcuts.&lt;/p&gt;

&lt;p&gt;The Syntax module is used for code Syntax highlighting. It relies on the external library highlight.js, which is turned off by default. To use Syntax highlighting, you must install highlight.js and turn it on manually.&lt;/p&gt;

&lt;p&gt;Other modules do not do much introduction, want to know can refer to the &lt;a href="https://quilljs.com/docs/modules" rel="noopener noreferrer"&gt;Quill module documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quill module configuration
&lt;/h2&gt;

&lt;p&gt;I just mentioned the Keyboard event module. Let's use another example to understand the configuration of the Quill module.&lt;/p&gt;

&lt;p&gt;Keyboard module supports a number of shortcuts by default, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The shortcut for bold is Ctrl+B;&lt;/li&gt;
&lt;li&gt;The shortcut key for hyperlinks is Ctrl+K;&lt;/li&gt;
&lt;li&gt;The undo/fallback shortcut is Ctrl+Z/Y.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, it does not support the strikeline shortcut. If we want to customize the strikeline shortcut, let's say &lt;code&gt;Ctrl+Shift+S&lt;/code&gt;, we can configure it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modules: {
  keyboard: {
    bindings: {
      strike: {
        key: 'S',
        ctrlKey: true,
        shiftKey: true,
        handler: function(range, context) {
          const format = this.quill.getFormat(range);
          this.quill.format('strike', !format.strike);
        }
      },
    }
  },
  toolbar: [['bold', 'italic', 'strike'], ['link', 'image']]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the above Demo, please &lt;a href="https://codepen.io/kagol/pen/mdJWwvE" rel="noopener noreferrer"&gt;configure the keyboard module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the course of developing a rich text editor with Quill, we will encounter various modules and create many custom modules, all of which are configured using the Modules parameter.&lt;/p&gt;

&lt;p&gt;Next we will try to create a custom module to deepen our understanding of Quill modules and module configuration.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create a custom module
&lt;/h1&gt;

&lt;p&gt;From the introduction of the last section, we learned that in fact, the Quill module is a normal JS class, there is nothing special, in the class initialization parameter will pass the Quill instance and the options configuration parameter of the module, then you can control and enhance the functions of the editor.&lt;/p&gt;

&lt;p&gt;When Quill's built-in modules failed to meet our needs, we needed to create custom modules to implement the functionality we wanted.&lt;/p&gt;

&lt;p&gt;For example, the EditorX rich text component has the ability to count the current word count in the editor. This function is implemented in a custom module. We will show you how to encapsulate this function as a separate &lt;code&gt;Counter&lt;/code&gt; module step by step.&lt;/p&gt;

&lt;p&gt;Create a Quill module in three steps:&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the module class
&lt;/h2&gt;

&lt;p&gt;Create a new JS file with a normal JavaScript class inside.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Counter {
  constructor(quill, options) {
    console.log('quill:', quill);
    console.log('options:', options);
  }
}

export default Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an empty class with nothing but the options configuration information for the Quill instance and the module printed in the initialization method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure module parameters
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modules: {
  toolbar: [
    ['bold', 'italic'],
    ['link', 'image']
  ],
  counter: true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of passing the configuration data, we simply enabled the module and found that no information was printed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Register the module
&lt;/h2&gt;

&lt;p&gt;To use a module, we need to register the module class by calling the quill-register method before the Quill is initialized (we'll see how this works later), and since we need to extend a module, the prefix needs to start with modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Quill from 'quill';
import Counter from './counter';
Quill.register('modules/counter', Counter);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can see that the information has been printed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add logic to the module
&lt;/h2&gt;

&lt;p&gt;At this point we add logic to the Counter module to count the words in the current editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(quill, options) {
  this.container = quill.addContainer('ql-counter');
  quill.on(Quill.events.TEXT_CHANGE, () =&amp;gt; {
    const text = quill.getText(); // Gets the plain text content in the editor
    const char = text.replace(/\s/g, ''); // Use regular expressions to remove white space characters
    this.container.innerHTML = `Current char count: ${char.length}`;
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the initialization method of the Counter module, we call the addContainer method provided by Quill to add an empty container for the contents of the word count module to the editor, and then bind the content change event of the editor, so that when we enter the content in the editor, the word count can be counted in real time.&lt;/p&gt;

&lt;p&gt;In the Text Change event, we call the Quill instance's getText method to get the plain Text content in the editor, then use a regular expression to remove the white space characters, and finally insert the word count information into the character count container.&lt;/p&gt;

&lt;p&gt;The general effect of the presentation is as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz3c0capuaw91m0xvqsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz3c0capuaw91m0xvqsd.png" alt="Alt Text" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see the above Demo, please anger the &lt;a href="https://codepen.io/kagol/pen/MWwpoRq" rel="noopener noreferrer"&gt;custom character statistics module&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Module loading mechanism
&lt;/h1&gt;

&lt;p&gt;After we have a preliminary understanding of the Quill module, we will want to know how the Quill module works. Next, we will start from the initialization process of Quill, through the toolbar module example, in-depth discussion of the Quill module loading mechanism. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tips: This summary involves the Quill source code analysis, welcome to leave a message to discuss if you don't understand the place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The initialization of the Quill class
&lt;/h2&gt;

&lt;p&gt;When we execute &lt;code&gt;new Quill()&lt;/code&gt;, we execute the Quill class's constructor method, which is located in the Quill source code's &lt;code&gt;core/quill.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The approximate source structure of the initialization method is as follows (remove module loading irrelevant code) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(container, options = {}) {
  this.options = expandConfig(container, options); // Extend configuration data, including adding topic classes, and so on
  ...
  this.theme = new this.options.theme(this, this.options); // 1. Initialize the theme instance using the theme class in Options

  // 2.Add required modules
  this.keyboard = this.theme.addModule('keyboard');
  this.clipboard = this.theme.addModule('clipboard');
  this.history = this.theme.addModule('history');

  this.theme.init(); // 3. Initialize the theme. This method is the core of the module rendering (the actual core is the AddModule method called in it), traversing all configured module classes and rendering them into the DOM
  ... 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Quill is initialized, it will use the &lt;code&gt;expandConfig&lt;/code&gt; method to extend the options passed in and add elements such as topic classes to initialize the topic. (A default BaseTheme theme can be found without configuring the theme)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;addModule&lt;/code&gt; method of the theme instance is then called to mount the built-in required module into the theme instance.&lt;/p&gt;

&lt;p&gt;Finally, the theme instance's &lt;code&gt;init&lt;/code&gt; method is called to render all modules into the DOM. (More on how this works later)&lt;/p&gt;

&lt;p&gt;If it is a snow theme, you will see a toolbar appear above the editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3oosr3tipmel5h5q6qm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3oosr3tipmel5h5q6qm5.png" alt="Alt Text" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it is a Bubble theme, then a toolbar float will appear when a text is selected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F523qwzgoj7lg5wd02pov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F523qwzgoj7lg5wd02pov.png" alt="Alt Text" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we take the toolbar module as an example to introduce the loading and rendering principle of Quill module in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loading of toolbar modules
&lt;/h2&gt;

&lt;p&gt;Taking the Snow theme as an example, the following parameters are configured when the Quill instance is initialized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  theme: 'snow',
  modules: {
    toolbar: [['bold', 'italic', 'strike'], ['link', 'image']]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quill in the constructor method to get to this. The theme is SnowTheme class instances, execute &lt;code&gt;this.theme.init()&lt;/code&gt; method is invoked when its parent class theme of the init method, this method is located in the &lt;code&gt;core/theme.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;init() {
  // Iterate through the Modules parameter in Quill Options to mount all the user-configured Modules into the theme class
  Object.keys(this.options.modules).forEach(name =&amp;gt; {
    if (this.modules[name] == null) {
      this.addModule(name);
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It iterates through all the modules in the options.modules parameter and calls the AddModule method of BaseTheme, which is located in the &lt;code&gt;themes/base.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addModule(name) {
  const module = super.addModule(name);
  if (name === 'toolbar') {
    this.extendToolbar(module);
  }
  return module;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method will first execute the AddModule method of its parent class to initialize all the modules. If it is a toolbar module, additional processing will be done to the toolbar module after the initialization of the toolbar module, which is mainly to build the ICONS and bind the shortcut key of hyperlink.&lt;/p&gt;

&lt;p&gt;Let's return to the &lt;code&gt;addModule&lt;/code&gt; method of BaseTheme, this method is &lt;code&gt;the core of module loading&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a method we saw earlier when we introduced the initialization of Quill, and called when we loaded the three built-in required modules. All modules load through this method, so it's worth exploring this method, which is located in &lt;code&gt;core/theme.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addModule(name) {
  const ModuleClass = this.quill.constructor.import(`modules/${name}`); // To import a module class, create a custom module by registering the class with Quill. Register the class with Quill
// Initialize the module class
  this.modules[name] = new ModuleClass(
    this.quill,
    this.options.modules[name] || {},
  );
  return this.modules[name];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;addModule&lt;/code&gt; method imports the module class by calling the &lt;code&gt;Quill.import&lt;/code&gt; method (if you have registered it through the &lt;code&gt;Quill.register&lt;/code&gt; method).&lt;/p&gt;

&lt;p&gt;We then &lt;code&gt;initialize the class&lt;/code&gt;, mounting the instance into the Modules member variable of the theme class (which at this point already has an instance of the built-in required module).&lt;/p&gt;

&lt;p&gt;In the case of a Toolbar module, the Toolbar class initialized in the addModule method is located in the &lt;code&gt;modules/toolbar.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Toolbar {
  constructor(quill, options) {
    super(quill, options);

    // Parse the modules.toolbar parameters to generate the toolbar structure
    if (Array.isArray(this.options.container)) {
      const container = document.createElement('div');
      addControls(container, this.options.container);
      quill.container.parentNode.insertBefore(container, quill.container);
      this.container = container;
    } else {
      ...
    }

    this.container.classList.add('ql-toolbar');

    // Bind toolbar events
    this.controls = [];
    this.handlers = {};
    Object.keys(this.options.handlers).forEach(format =&amp;gt; {
      this.addHandler(format, this.options.handlers[format]);
    });
    Array.from(this.container.querySelectorAll('button, select')).forEach(
      input =&amp;gt; {
        this.attach(input);
      },
    );
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a toolbar module is initialized, it parses the &lt;code&gt;modules.toolbar&lt;/code&gt; parameters, calls the &lt;code&gt;addControls&lt;/code&gt; method to generate the toolbar buttons and drop-down boxes (the basic idea is to iterate through a two-dimensional array and insert them into the toolbar as buttons or drop-down boxes), and binds events to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function addControls(container, groups) {
 if (!Array.isArray(groups[0])) {
  groups = [groups];
 }
 groups.forEach(controls =&amp;gt; {
  const group = document.createElement('span');
  group.classList.add('ql-formats');
  controls.forEach(control =&amp;gt; {
    if (typeof control === 'string') {
      addButton(group, control);
    } else {
      const format = Object.keys(control)[0];
      const value = control[format];
      if (Array.isArray(value)) {
        addSelect(group, format, value);
      } else {
        addButton(group, format, value);
      }
    }
  });
  container.appendChild(group);
 });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The toolbar module is then loaded and rendered into the rich text editor to facilitate editor operations.&lt;/p&gt;

&lt;p&gt;Now a summary of the module loading process is made:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The starting point for module loading is the &lt;code&gt;init&lt;/code&gt; method of the &lt;code&gt;Theme&lt;/code&gt; class, which loads all the modules configured in the &lt;code&gt;option.modules&lt;/code&gt; parameter into the member variable of the Theme class: &lt;code&gt;modules&lt;/code&gt;, and merges them with the built-in required modules.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;addModule&lt;/code&gt; method imports the module class through the &lt;code&gt;import&lt;/code&gt; method, and then creates an instance of the module through the &lt;code&gt;new&lt;/code&gt; keyword.&lt;/li&gt;
&lt;li&gt;When creating a module instance, the initialization method of the module is executed, and the specific logic of the module is executed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a diagram of the relationship between the module and the editor instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3ozzqj0ctr7nwhbk7z4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3ozzqj0ctr7nwhbk7z4.png" alt="Alt Text" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this post, We introduced the configuration method of Quill module briefly through two examples, so that we have a intuitive and preliminary impression of the Quill module.&lt;/p&gt;

&lt;p&gt;The character statistics module is then used as a simple example to show how to develop a custom Quill module that extends the functionality of the rich text editor.&lt;/p&gt;

&lt;p&gt;Finally, through analyzing the initialization process of Quill, the loading mechanism of Quill module is gradually cut into, and the loading process of toolbar module is elaborated in detail.&lt;/p&gt;

&lt;h1&gt;
  
  
  About DevUI team
&lt;/h1&gt;

&lt;p&gt;DevUI is a team with both design and engineering perspectives, serving for the DevCloud platform of Huawei Cloud and several internal middle and background systems of Huawei, serving designers and front-end engineers.&lt;/p&gt;

&lt;p&gt;Official website: &lt;a href="//devui.design/"&gt;devui.design&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ng component library: &lt;a href="//github.com/devcloudfe/ng-devui"&gt;ng-devui&lt;/a&gt; (Welcome to star🌟)&lt;/p&gt;

&lt;p&gt;by &lt;a href="//github.com/kagol/"&gt;Kagol&lt;/a&gt;&lt;/p&gt;

</description>
      <category>quill</category>
      <category>editor</category>
      <category>devui</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
