The self-developed framework has entered the global JS framework list, ranking closely behind React and Angular!

Foreword

An important goal was finally achieved! Strve, the JavaScript framework I developed independently, recently released a major version 6.0.2. It has been nearly two months since the last major version was released. During this period, a large number of optimizations were carried out, which greatly improved the performance and stability of the framework. In the last major version update, full support for JSX syntax was successfully implemented, making Strve more friendly in terms of code intelligent prompts and code formatting, further improving development efficiency.

Introduction

I believe some friends have not heard of what Strve is, so I will give a general introduction here.

Strve is a JavaScript library that converts strings into views (user interfaces). Strve is not only easy to use, but also flexible in breaking down different chunks of code. Developing user interfaces using template strings mainly takes advantage of the power of JavaScript and focuses only on JavaScript files. Strve is another easy-to-use JavaScript framework that provides many practical functions and ecological tools.

We can understand how to use Strve through some simple examples.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Strve.js</title>
  </head>

  <body>
    <script src="//i2.wp.com/cdn.jsdelivr.net/npm/[email protected]/dist/strve.full.prod.js"></script>
    <script>
      const {<!-- --> html, setData, createApp } = Strve;
      const state = {<!-- -->
        count: 0,
      };

      function add() {<!-- -->
        setData(() => {<!-- -->
          state.count + + ;
        });
      }

      function App() {<!-- -->
        return html`<h1 onClick=${<!-- -->add}>${<!-- -->state.count}</h1>`;
      }

      const app = createApp(App);
      app.mount('#app');
    </script>
  </body>
</html>

In the above code, we introduce the Strve library and use the createApp method to create an App component, and then mount it to the page through the mount method Above, the App component here is defined through template strings. In this way, the user interface can be written in JS code. Isn’t it very convenient? We found that in the template string, we use ${} to reference data, and use the onClick method to bind events. In this way, the function of a counter can be realized.

In addition to this simple example, Strve also supports many complex functions. We can use JSX syntax to write components, we can also use functional components to write components, we can also use components to write components, and we can even write some custom components. .

If you want to know more about Strve, you can check the official documentation at the end of the article later.

Performance evaluation

Now that we have released Strve, we definitely need to evaluate its performance. The tool we evaluate uses js-framework-benchmark. What is js-framework-benchmark? Here we will briefly introduce js-framework-benchmark, which is a project used to compare the performance of JavaScript frameworks. It aims to evaluate the performance of different frameworks in various scenarios by executing a series of benchmark tests. These benchmarks include rendering large amounts of data, updating data, handling complex UI components, and more. By running these benchmarks, you can compare the performance of different frameworks in various aspects and help developers choose the framework that best suits their needs. The js-framework-benchmark project provides a benchmark suite for several popular JavaScript frameworks. These frameworks include Angular, React, Vue, etc. Each framework is run under the same test scenario, and performance metrics such as execution time and memory usage are recorded. By comparing these metrics, the performance differences of different frameworks can be derived. The goal of this project is to help developers understand the performance characteristics of different JavaScript frameworks so that they can make more informed decisions when choosing a framework. At the same time, it can also promote competition among framework developers and promote the continuous improvement and optimization of the framework.

Before evaluating, we must understand that there are two modes in js-framework-benchmark. One is keyed and the other is non-keyed. In js-framework-benchmark, the “keyed” mode refers to assigning a unique identifier to the data item as the “key” attribute to achieve a one-to-one relationship between the data item and the DOM node. . When data changes, the DOM nodes associated with it are updated accordingly. The non-keyed mode means that when a data item changes, DOM nodes previously associated with other data items may be modified.

Because Strve supports keyed mode, we will use this mode to evaluate Strve’s performance.

The following operations were benchmarked:

  • Create Rows: The duration to create 1,000 rows after the page loads (without warm-up).
  • Replace all rows: Duration to replace all 1,000 rows in the table (5 warm-up iterations).
  • Partial update: For a table with 10,000 rows, update the text every 10 rows (5 warm-up iterations).
  • Select row: The duration for which a row is highlighted in response to clicking on it. (5 warm-up iterations).
  • Swap rows: The time to swap 2 rows in a table with 1,000 rows. (5 warm-up iterations).
  • Delete Rows: The duration for deleting rows from a table with 1,000 rows. (5 warm-up iterations).
  • Create multiple rows: duration to create 10,000 rows (no warm-up)
  • Append rows to a large table: Duration of appending 1,000 rows to a table with 10,000 rows (no warm-up).
  • Purge Rows: The duration to purge a table populated with 10,000 rows. (no warm-up)
  • Ready Memory: Memory usage after page load.
  • Running memory: Memory usage after adding 1,000 rows.
  • Update memory: Memory usage after hitting 5 updates on a table with 1000 rows.
  • Replacement memory: memory usage after 5 clicks to create 1000 rows.
  • Repeated memory clearing: Memory usage after creating and clearing 1,000 rows 5 times.
  • Update memory: Memory usage after hitting 5 updates on a table with 1000 rows.
  • Startup time: The duration of time to load and parse the javascript code and render the page.
  • Constantly Interactive: Lighthouse Indicator TimeToConstantlyInteractive: Pessimistic TTI – When both CPU and network are very idle. (No more CPU tasks taking longer than 50ms)
  • Script Boot Up Time: Lighthouse Metrics ScriptBootUpTtime: Total number of milliseconds required to parse/compile/evaluate all page scripts
  • Main Thread Work Cost: Lighthouse Metrics MainThreadWorkCost: Total time spent working on the main thread including styling/layout etc.
  • Total Byte Weight: Lighthouse Metric TotalByteWeight: The network transfer cost (after compression) of all resources loaded into the page.

For all benchmarks, duration is measured, including render time.

Because js-framework-benchmark is an automated testing tool, only code that meets standards can be tested. Strve supports JSX syntax, so we will use JSX syntax to write test code.

import {<!-- --> setData, createApp } from 'strve-js';
import {<!-- --> buildData } from './data.js';

let selected;
let rows = [];

function setRows(update = rows.slice()) {<!-- -->
  setData(
    () => {<!-- -->
      rows = update;
    },
    {<!-- -->
      name: TbodyComponent,
    }
  );
}

function add() {<!-- -->
  const data = rows.concat(buildData(1000));
  setData(
    () => {<!-- -->
      rows = data;
    },
    {<!-- -->
      name: TbodyComponent,
    }
  );
}

function remove(id) {<!-- -->
  rows.splice(
    rows.findIndex((d) => d.id === id),
    1
  );
  setRows();
}

function select(id) {<!-- -->
  setData(
    () => {<!-- -->
      selected = id;
    },
    {<!-- -->
      name: TbodyComponent,
    }
  );
}

function run() {<!-- -->
  setRows(buildData());
  selected = undefined;
}

function update() {<!-- -->
  for (let i = 0; i < rows.length; i + = 10) {<!-- -->
    rows[i].label + = ' !!!';
  }
  setRows();
}

function runLots() {<!-- -->
  setRows(buildData(10000));
  selected = undefined;
}

function clear() {<!-- -->
  setRows([]);
  selected = undefined;
}

function swapRows() {<!-- -->
  if (rows.length > 998) {<!-- -->
    const d1 = rows[1];
    const d998 = rows[998];
    rows[1] = d998;
    rows[998] = d1;
    setRows();
  }
}

function TbodyComponent() {<!-- -->
  return (
    <tbody>
      {<!-- -->rows.map((item) => (
        <tr class={<!-- -->item.id === selected ? 'danger' : ''} data-label={<!-- -->item.label} key={<!-- -->item.id}>
          <td class='col-md-1'>{<!-- -->item.id}</td>
          <td class='col-md-4'>
            <a onClick={<!-- -->() => select(item.id)}>{<!-- -->item.label}</a>
          </td>
          <td class='col-md-1'>
            <a onClick={<!-- -->() => remove(item.id)}>
              <span class='glyphicon glyphicon-remove' aria-hidden='true'></span>
            </a>
          </td>
          <td class='col-md-6'></td>
        </tr>
      ))}
    </tbody>
  );
}

function MainBody() {<!-- -->
  return (
    <fragment>
      <div class='jumbotron'>
        <div class='row'>
          <div class='col-md-6'>
            <h1>Strve-keyed</h1>
          </div>
          <div class='col-md-6'>
            <div class='row'>
              <div class='col-sm-6 smallpad'>
                <button type='button' class='btn btn-primary btn-block' id='run' onClick={<!-- -->run}>
                  Create 1,000 rows
                </button>
              </div>
              <div class='col-sm-6 smallpad'>
                <button
                  type='button'
                  class='btn btn-primary btn-block'
                  id='runlots'
                  onClick={<!-- -->runLots}
                >
                  Create 10,000 rows
                </button>
              </div>
              <div class='col-sm-6 smallpad'>
                <button type='button' class='btn btn-primary btn-block' id='add' onClick={<!-- -->add}>
                  Append 1,000 rows
                </button>
              </div>
              <div class='col-sm-6 smallpad'>
                <button
                  type='button'
                  class='btn btn-primary btn-block'
                  id='update'
                  onClick={<!-- -->update}
                >
                  Update every 10th row
                </button>
              </div>
              <div class='col-sm-6 smallpad'>
                <button type='button' class='btn btn-primary btn-block' id='clear' onClick={<!-- -->clear}>
                  Clear
                </button>
              </div>
              <div class='col-sm-6 smallpad'>
                <button
                  type='button'
                  class='btn btn-primary btn-block'
                  id='swaprows'
                  onClick={<!-- -->swapRows}
                >
                  Swap Rows
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <table class='table table-hover table-striped test-data'>
        <component $name={<!-- -->TbodyComponent.name}>{<!-- -->TbodyComponent()}</component>
      </table>
      <span class='preloadicon glyphicon glyphicon-remove' aria-hidden='true'></span>
    </fragment>
  );
}

createApp(() => MainBody()).mount('#main');

The following page is the page that will be benchmarked:


Let’s take a general look at the testing process. We will use animations to show the page effect, which will make it more intuitive.

Finally, Strve passed the stress test!

Benchmark results

Now that we pass the test, we need to submit it to the js-framework-benchmark official project for a comprehensive evaluation and comparison with other frameworks around the world.

The PR we submitted was merged by the author on September 18, 2023.

In the following time, the author conducted a series of tests. Finally, Chrome 118 was released last week, with official test results posted on GitHub.

Let’s open the following URL and take a look at Strve’s official test results:

https://krausest.github.io/js-framework-benchmark/2023/table_chrome_118.0.5993.70.html

After query, there are 142 frameworks in the global JavaScript framework list.

Performance test benchmarks are divided into three categories:

  • duration
  • Start indicator
  • memory allocation

[Duration]

In this benchmark, Strve averaged 1.42 and ranked 90.

React, Angular and Vue, with average values of 1.40, 1.38 and 1.20 respectively, ranked 85 and No.83 and No.51.

The smaller the average value, the higher the ranking. The greener the color, the better.

[Startup indicator]

In this test benchmark, Strve averaged 1.07.

For React, Angular and Vue, the averages are 1.68, 1.80 and 1.30 respectively.

The smaller the average value, the higher the ranking. The greener the color, the better.

[Memory Allocation]

In this test benchmark, Strve averaged 1.33.

For React, Angular and Vue, the averages are 2.46, 2.82 and 1.86 respectively.

The smaller the average value, the higher the ranking. The greener the color, the better.

New features

In the above test, we can see that Strve performed very well.

The major version number we released this time is 6.0.2. We named this landmark version Strve6, and “Strve6, starting from the core!” The slogan is exactly the core idea of Strve6. This version symbolizes our commitment to providing users with a better and more efficient development experience starting from the underlying technology.

In this version, we have made a trade-off between performance and experience. At the source code level, we upgraded the ordinary Diff algorithm to the Double-ended Diff algorithm, which greatly improved the performance. In addition, we have also made great improvements at the user experience level.

Here, we mentioned the double-ended Diff algorithm. We often mention this concept in interviews, but it is rarely used in actual projects. So, to better understand how the double-ended Diff algorithm improves performance, let’s look at a simple example with Strve.

Let’s iterate through an array and add an element to the head of the array each time the button is clicked.

[Common Diff Algorithm]

<script type="module">
  import {<!-- -->
    html,
    setData,
    createApp,
  } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/strve.full-esm.js';

  const state = {<!-- -->
    arr: [1, 2],
    count: 3,
  };

  function useUnshift() {<!-- -->
    setData(() => {<!-- -->
      state.count + + ;
      state.arr.unshift(state.count);
    });
  }

  function App() {<!-- -->
    return html`
      <fragment>
        <button onClick=${<!-- -->useUnshift}>Unshift</button>
        <ul>
          ${<!-- -->state.arr.map((todo) => html`<li>${<!-- -->todo}</li>`)}
        </ul>
      </fragment>
    `;
  }

  const app = createApp(App);
  app.mount('#app');
</script>

We can see the DOM tree on the right, and every time the button is clicked, the entire list will be re-rendered. This will definitely consume browser performance.

[Double-ended Diff algorithm]

<script type="module">
  import {<!-- -->
    html,
    setData,
    createApp,
  } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/strve.full-esm.js';

  const state = {<!-- -->
    arr: [1, 2],
    count: 3,
  };

  function useUnshift() {<!-- -->
    setData(() => {<!-- -->
      state.count + + ;
      state.arr.unshift(state.count);
    });
  }

  function App() {<!-- -->
    return html`
      <fragment>
        <button onClick=${<!-- -->useUnshift}>Unshift</button>
        <ul>
          ${<!-- -->state.arr.map((todo) => html`<li key=${<!-- -->todo}>${<!-- -->todo}</li>`)}
        </ul>
      </fragment>
    `;
  }

  const app = createApp(App);
  app.mount('#app');
</script>

We can see the DOM tree on the right, and each time the button is clicked, only the necessary elements are added instead of re-rendering the entire list. This is because we added the key attribute to each list item, and this key is unique. key This special attribute is mainly used as a hint for Strve’s virtual DOM algorithm and is used to identify vnode when comparing the old and new node lists. As long as the tag type and key value are equal, it means that the current element can be reused.

Hot Topics

The article is coming to an end, let us review some of the recent hot topics in the community.

  1. Why was this framework developed? What was the original intention?

Answer: Actually, my motivation is very simple and is completely influenced by JSX syntax. When I first came into contact with JSX syntax, I was deeply attracted by its magic. You can write HTML in JS. So, I thought if I could build a library or framework similar to JSX syntax! On the one hand, I can exercise my coding skills, and on the other hand, I can experience the entire process of developing a framework, which will also facilitate my future study of other frameworks (Vue.js, React.js, etc.) more comprehensively.

Doing something you love is very meaningful!

  1. Why was Strve chosen as the name of the framework?

Answer: Strve was originally positioned as a JavaScript library that can convert strings into views (user interfaces), so it is a new word formed by shortening the two words String and View.

  1. Compared with popular front-end frameworks, do you want to surpass them?

Answer: No, I mainly want to learn the implementation principles of popular front-end frameworks, and then implement a framework myself. There is a saying that goes well: “Only by standing on the shoulders of giants can you see further!”.

  1. I remember that I have written articles on the framework list before. Why do I write them again this time?

Answer: Previously, the Strve evaluation mode was to use "non-keyed". Now, the new version of Strve supports the "keyed" mode, so I re-wrote an article to introduce the new features of Strve.

  1. Strve 6.0.2 version is released. The ordinary Diff algorithm is upgraded to the double-ended Diff algorithm. Can you briefly explain the concept of the double-ended Diff algorithm?

Answer: The double-ended diff algorithm moves the head and tail pointers toward the middle to determine whether the head and tail nodes can be reused. If no reusable node is found, it will traverse to find the subscript of the corresponding node, and then move. After all processing is completed, the remaining nodes must be added and deleted in batches.

  1. Is Strve a JavaScript library or a JavaScript framework?

Answer: First, let’s take a look at the difference between a framework and a library? The library is more of a specific encapsulated collection, provided for developers to use, and it is a collection (methods and functions) specific to a certain aspect. The library has no control, and the control is in the hands of the user. Query needs in the library We can understand the library from the perspective of encapsulation when its functions are used in our own applications; a framework, as its name suggests, is a set of architectures that will provide users with a complete solution based on its own characteristics, and the control lies with the framework itself. , users need to find certain specifications stipulated by the framework for development. Strve can be a framework, because Strve provides ecological tools such as routing and plug-ins; Strve can also be a library, because Strve can serve as a rendering library alone.

  1. Strve Do you want to continue to maintain it?

Answer: Yes, I will continue to maintain it because I also want to learn and hope to help more front-end developers.

About

I started developing Strve in the second half of 2021, and it has been almost two years now. In the past two years, I have gained a lot from a coder who could only call APIs to now being able to independently develop a framework. I learned how to analyze the implementation principles of a framework, and also learned how to design a framework.

Strve source code repository: https://github.com/maomincoding/strve

Strve Chinese documentation: https://maomincoding.gitee.io/strve-doc-zh/

If you think Strve is good, please click Star for me, thank you!

Conclusion

Thank you readers for reading. I hope this article can be helpful to you. If you like this article, please like it and follow it!

Finally, I would like to share a paragraph with you:

most of the time

Don’t persist only if you have hope

But in the process of persisting, we slowly see hope

We are all people walking in the dark night

Even though I am exhausted, I still refuse to give up easily.

May what you insist on

One day I will embrace you in turn