Your website may not need front-end construction (2)

A while ago, a friend asked me if I could develop a website interface using modern syntax without compiling and building the front-end.

So, there is the plan mentioned in this article.

write in front

In this article, I still don’t want to discuss whether to build or not to build, which solution is more friendly to development and more suitable for a certain team. I just want to share a relatively light solution in an environment where the entire environment is building and it seems impossible to write a project without building. plan.

The code in this article is open source at soulteary/You-Dont-Need-Build-JavaScript . You can get it yourself if you need it. Welcome to "one-click three links".

In 2019, I wrote an article " Your website may not need front-end construction ". The plan in the article is open sourced on GitHub: soulteary/You-Dont-Need-Webpack . At that time, this technique was used to implement a lightweight display application within Meituan. Thanks to the browser's resource loading mechanism and script running mechanism, this clever solution achieved very good performance.

If you are interested in the story behind this plan, you can read "Ideas that Arose by Chance" at the end of the article.

However, times have changed. In 2024, perhaps the technology stack in the plan should have a more stable and interesting alternative. My choice is: the "SAN" framework and surrounding ecology produced by Baidu EFE team.

It only takes about a hundred or ten lines of code to create a simple "MIS backend appearance":

The effects of a hundred or so lines of SAN code

And unlike various slow backends, this compilation-free construction solution built with SAN has very fast page display speed:

Very fast rendering

Technology selection

Before talking about implementation, let’s first talk about technology selection.

Basic framework: Baidu’s San

baidu/san is a lightweight front-end open source framework. The official has a well-written introduction: " Introduction to San and Practice in Baidu APP ". If you are interested, you can read it by yourself. I will not go into details here.

If I use keywords to describe it, what I can directly think of is: good front-end compatibility, stable updates for nearly ten years, stable ecological surroundings, endorsement by big manufacturers and high-traffic applications, no commercialization demands, and relatively pure technology projects.

Of course, there are some objective and subjective reasons for choosing it as the basic selection for this article. The "subjective reasons and objective reasons are mentioned" at the end of the article will not be expanded upon here.

If you want to follow the plan of this article in depth and play with your application, there are two extended reading contents: the code of the Todos App of the simplest AMD module specification and the basic syntax part of the San online document .

AMD is used as the module specification because AMD has better browser compatibility than other popular specifications, and the EFE team happens to have a great loader option available: ESL.

Front-end loader: ESL (Enterprise Standard Loader)

ecomfe/esl is another product of Baidu EFE team. It can be regarded as an enhanced version of requirejs. It has a smaller size, higher performance and more robust program than requirejs.

The design and usage of the program are very simple, and a simple document is enough for you to understand how to use it: ESL configuration document . If you are not familiar with AMD modules, you can refer to this module definition document .

We want to build without hassle, and one of the conditions is that the front-end program can be loaded in a reliable order according to our needs, and parsed and executed. This small tool of less than 5KB is enough.

Front-end router: San Router

baidu/san-router is a supporting project of San, which is used to support functions such as dynamic routing, nested routing, lazy loading of routes, and navigation guards. If you don't want to implement multi-page routing, or want to add some interesting features to the current page, this simple and useful component comes in handy.

Its documentation is also relatively simple, with less than ten pages of documentation .

Front-end component library: Santd

ecomfe/santd is an Ant Design component library implementation provided by the EFE team that adapts to San syntax. If you want to quickly create an interface with acceptable styles, but don't want to mess with CSS styles too much, using this kind of ready-made style library can save a lot of time. The document of the style library is here . When you need any components, you can just copy and paste them out, which is very convenient.

Of course, there are some sub-dependencies in the implementation of this style library: including the date component library (dayjs) and the responsive compatibility shim (enquire). When messing around, we need to do some additional processing. However, we don't need to interact with them directly, so we don't need to look at their documentation.

Practice: Build a basic framework

In fact, it is very simple to make the basic framework of a front-end website that does not require compilation and construction. It is an HTML5 standard page structure, paired with some basic styles and script dependencies, and then other resources are loaded with a loader:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Show case</title>

    <link rel="stylesheet" href="...">

    <script src="..."></script>

    <script>
      require.config({
      
      baseUrl: "./app"});
    </script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      require(["main"], function (app) {
      
      
        app.init();
      });
    </script>
  </body>
</html>

Although we can use the loader to load all the code except the loader and use "JS to manage everything", this approach was very popular on Taobao in the early years, but it is not necessary in this scenario. Properly introducing the basic dependencies of the page directly into the page has at least three benefits:

  1. This allows the page to load the required resources earlier. Compared with the preemptive resource loading of JS programs, the page rendering speed is faster, and it can also maximize the use of the browser's resource loading and execution optimization.
  2. It reduces complex dependency management in JS programs, reduces closure scope binding (loader), reduces the "number of copies" of the program, saves running resources, and also improves the performance of the program during runtime.
  3. The program files loaded by the loader can also be written more simply, because these basic dependencies are shared globally and do not need to be declared and defined inside the module. Write less and make fewer mistakes.

Page program execution performance analysis

On the browser performance analysis page, we can see that due to relatively reasonable program splitting and direct loading, the loading and parsing speed of the program is very fast. Of course, this is also inseparable from the fact that SAN itself is very fast.

Let’s take the actual situation as an example. For example, if all the front-end resources used in this article are listed, they will be as follows ( soulteary/You-Dont-Need-Build-JavaScript/src/dev.html page example):

<!-- 组件库样式文件 -->
<link rel="stylesheet" href="lib/[email protected]/santd.min.css">
<!-- 组件库依赖的脚本程序 -->
<script src="lib/[email protected]/dayjs.min.js"></script>
<script src="lib/[email protected]/locale/zh-cn.min.js"></script>
<script src="lib/[email protected]/plugin/utc.min.js"></script>
<script src="lib/[email protected]/plugin/localeData.min.js"></script>
<script src="lib/[email protected]/plugin/customParseFormat.min.js"></script>
<script src="lib/[email protected]/plugin/weekOfYear.min.js"></script>
<script src="lib/[email protected]/plugin/weekYear.min.js"></script>
<script src="lib/[email protected]/plugin/advancedFormat.min.js"></script>
<script src="lib/[email protected]/enquire.min.js"></script>
<!-- SAN 框架 -->
<script src="lib/[email protected]/san.min.js"></script>
<!-- SAN 路由 -->
<script src="lib/[email protected]/san-router.min.js"></script>
<!-- SAN 组件库 -->
<script src="lib/[email protected]/santd.js"></script>
<!-- 加载器 -->
<script src="lib/[email protected]/esl.min.js"></script>

Although I do not recommend early optimization of any program, and modern browsers are already well optimized for loading such resources (number of concurrency and multiple caching mechanisms), the effortless and low-cost solutions that are on the horizon can Optimization point, we can easily optimize it:

<link rel="stylesheet" href="lib/[email protected]/santd.min.css">
<script src="lib/[email protected]/core.min.js"></script>
<script src="lib/[email protected]/santd.min.js"></script>
<script src="lib/[email protected]/esl.min.js"></script>

Based on the update frequency and basic dependencies of the program, we can merge different components. For example, merging programs other than component libraries and loaders into core dependencies core.min.js. This can reduce ten requests and increase the overall loading speed of the program, especially for non-standard applications. It is faster in HTTP2 environment.

The above shelf will encounter some minor problems during actual operation. The problems are basically in the component dependency library Santd and its dependency Dayjs.

Solve the problem of incompletely adapted modules

In JavaScript programs, there are many different modularization solutions, and the program files exported by different solutions are also different. If you do not rely on programming to explicitly declare the introduction of dependencies, and build them together, then different components may be "assembled" When connecting", there may be some minor problems, such as "the names do not match" (the module declaration names do not match).

If we eslplace in santdfront, then the component library will fully comply with the AMD module loading scheme when loading.

<script src="lib/[email protected]/esl.min.js"></script>
<script src="lib/[email protected]/santd.min.js"></script>

Then the component library will complete loading dayjs and its various components in the order in its program declaration:

typeof define === 'function' && define.amd ? define(['exports', 'san', 'dayjs', 'dayjs/plugin/utc', 'dayjs/plugin/localeData', 'dayjs/plugin/customParseFormat', 'dayjs/plugin/weekOfYear', 'dayjs/plugin/weekYear', 'dayjs/plugin/advancedFormat'], factory)

The above is the dependency reference on dayjs in Santd, but dayjs does not have a program format that can be directly used by browsers that conform to the AMD module like the San ecosystem by default. Although we can adapt and encapsulate dayjs, we still have to "compile and build".

I really don’t want to bother with and maintain “compile and build” at all, so is there a simpler way?

After dayjs and its components are executed by the browser, they will generate global objects. The necessary elements for running santd are actually complete, but because of the reasons mentioned above, "its object name does not match the reference object in the component."

Observe carefully that santd only performs two operations after loading the AMD module:

// 第一步:做模块的声明引入
var dayjs__default = 'default' in dayjs ? dayjs['default'] : dayjs;
// 问题:对齐导出组件的名称,dayjs 没啥问题,主要是它组件加载出问题了
utc = utc && Object.prototype.hasOwnProperty.call(utc, 'default') ? utc['default'] : utc;
localeData = localeData && Object.prototype.hasOwnProperty.call(localeData, 'default') ? localeData['default'] : localeData;
// ...

// 第二步:使用 dayjs
function getTodayTime(value) {
    
    
  var locale = value.locale();
  // 问题:浏览器引入的 dayjs 默认没有 amd 模块化,所以这样的模块加载方式会出错
  require("dayjs/locale/".concat(locale, ".js"));
  return dayjs__default().locale(locale).utcOffset(value.utcOffset());
}

One is the introduction of modules, and the other is the loading of modules within components require. I have mentioned related issues in the program comments, so I will not expand on them.

To make this program execute smoothly, we only need to do some string replacement:

var dayjs__default = dayjs;
dayjs.extend(window.dayjs_plugin_utc);
dayjs.extend(window.dayjs_plugin_localeData);
...

function getTodayTime(value) {
    
    
  var locale = value.locale();
  return dayjs__default().locale(locale).utcOffset(value.utcOffset());
}

In order to avoid omissions, we can write a small text replacement program to handle this matter. You can use any program you like to solve problems like the above. I wrote a simple program of about a hundred lines in Go, which includes the above processing and file string compression: optimizer/optimizer.go . Because this article mainly talks about the front end, I will not expand on this part. Interested students can read it by themselves.

After the shelf part is set up, we can start writing code in a way that does not involve front-end compilation and construction. Let’s first talk about writing the module entry program.

Practice: Writing an entry program

We have actually talked about the program entry program above. In the HTML page, the loader example in the shelf is written like this:

<script src="lib/[email protected]/esl.min.js"></script>
<script>
  require.config({
    
     baseUrl: "./app" });
</script>
<script>
  require(["main"], function (app) {
    
    
    app.init();
  });
</script>

After the above program is executed, it will request ./app/main.jsthe file under the current path of the website, and then after the file is loaded, the program .init()method will be called to complete the initialization of the application.

The actual resource loading status of the page

Just looking at the code is a bit abstract. Combining the above browser resource request details and resource loading order, is it more intuitive?

If you depend on multiple files, you can require( ... )add all the programs you need in and complete the specific logic in the subsequent (callback) function. You don't need to consider whether the dependencies have been downloaded. The loader will ensure that all your dependencies have been downloaded. Finally, execute your specific program logic.

Practice: Writing the Main function of the program

Next we will complete the first program referenced by the entry program main.js:

define(["./components/container"], function (Container, require) {
    
    
  var router = sanRouter.router;
  router.add({
    
     rule: "/", Component: Container, target: "#app" });

  return {
    
    
    init: function () {
    
    
      router.start();
    },
  };
});

The above program uses San Router to initialize a single-page application. You can refer to the documentation mentioned above to add more routes to the page. If you choose to make a multi-page application, then registering only one /root route is enough.

After the program is executed, the programs it depends on will be ./components/containerdownloaded and mounted as components of the page. If you don't like defineto declare dependencies in , you can also use the following methods, which are equivalent:

var Container = require("./components/container");

Practice: Writing your first page

The following content is mainly from Santd examples. We only need to wrap the example content in our template code to complete a modern SFC writing method that supports two-way binding, templates and logical separation of page programs:

define(function (require) {
    
    
  var template = require("tpl!./container.html");

  // --- santd 示例开始
  var Layout = santd.Layout;
  var Menu = santd.Menu;
  var Icon = santd.Icon;
  var Breadcrumb = santd.Breadcrumb;

  return san.defineComponent({
    
    
    components: {
    
    
      "s-layout": Layout,
      "s-header": Layout.Header,
      "s-content": Layout.Content,
      "s-sider": Layout.Sider,
      "s-menu": Menu,
      "s-sub-menu": Menu.Sub,
      "s-menu-item": Menu.Item,
      "s-icon": Icon,
      "s-breadcrumb": Breadcrumb,
      "s-brcrumbitem": Breadcrumb.Item,
    },
    initData() {
    
    
      return {
    
    
        inlineCollapsed: false,
      };
    },
    toggleCollapsed() {
    
    
      this.data.set("inlineCollapsed", !this.data.get("inlineCollapsed"));
    },
	// --- santd 示例结束

    template: template,
  });
});

ESL plug-in: template loading function

The following template loading function comes from baidu/san/example/todos-amd/src/tpl.js :

/* global ActiveXObject */
define(function (require) {
    
    
  return {
    
    
    load: function (resourceId, req, load) {
    
    
      var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");

      xhr.open("GET", req.toUrl(resourceId), true);

      xhr.onreadystatechange = function () {
    
    
        if (xhr.readyState === 4) {
    
    
          if (xhr.status >= 200 && xhr.status < 300) {
    
    
            var source = xhr.responseText;
            load(source);
          }

          /* jshint -W054 */
          xhr.onreadystatechange = new Function();
          /* jshint +W054 */
          xhr = null;
        }
      };

      xhr.send(null);
    },
  };
});

This function with XHR request as the core mainly does one thing, which is to pass HTML xhr.responseTextto our calling function in the form of text ( ), which is a more flexible and side-effect-free request template.

Of course, if you don't like to obtain the template in this way, we have other solutions, such as presetting the template in <textarea>an HTML block element that does not translate the content, or scriptdeclaring it in a container tag type="text/html"to avoid program execution. Complete saving of template.

Practice: Programming page templates

The page template below is also from the Santd example. Although it has a small number of lines, it implements a complete classic layout with top navigation and sidebars:

<div>
  <s-layout>
    <s-header class="header">
      <div class="logo"></div>
      <s-menu theme="dark" mode="horizontal" defaultSelectedKeys="{
     
     {['1']}}" style="line-height: 64px">
        <s-menu-item key="1">Nav 1</s-menu-item>
        <s-menu-item key="2">Nav 2</s-menu-item>
        <s-menu-item key="3">Nav 3</s-menu-item>
      </s-menu>
    </s-header>
    <s-layout>
      <s-sider width="{
     
     {200}}" style="{ 
        { 
        { 
        background: '#fff'}}}">
        <s-menu mode="inline" defaultSelectedKeys="{
     
     {['3']}}" defaultOpenKeys="{
     
     {['sub1']}}">
          <s-sub-menu key="sub1">
            <template slot="title">
              <s-icon type="form" />
              <span>Navigation One</span>
            </template>
            <s-menu-item key="1"> <span>option1</span></s-menu-item>
            <s-menu-item key="2"> <span>option2</span></s-menu-item>
            <s-menu-item key="3"> <span>option3</span></s-menu-item>
            <s-menu-item key="4"> <span>option4</span></s-menu-item>
          </s-sub-menu>
          <s-sub-menu key="sub2">
            <template slot="title">
              <s-icon type="copy" />
              <span>Navigation Two</span>
            </template>
            <s-menu-item key="5"> <span>option5</span></s-menu-item>
            <s-menu-item key="6"> <span>option6</span></s-menu-item>
            <s-menu-item key="7"> <span>option7</span></s-menu-item>
          </s-sub-menu>
        </s-menu>
      </s-sider>
      <s-layout style="{ 
        { 
        { 
        padding: '0 24px 24px'}}}">
        <s-breadcrumb style="{ 
        { 
        { 
        margin: '16px 0'}}}">
          <s-brcrumbitem href="/">Home</s-brcrumbitem>
          <s-brcrumbitem href="#">List</s-brcrumbitem>
          <s-brcrumbitem>App</s-brcrumbitem></s-breadcrumb
        >
        <s-content style="{ 
        { 
        { 
        padding: '24px', background: '#fff', minHeight: '280px'}}}">Content</s-content></s-layout
      >
    </s-layout>
  </s-layout>
</div>

There are many examples in Santd's documentation. You can combine them according to your own needs to make a website. The general operation is to refer to the official examples of "copy and paste", refresh, and "what you see is what you get".

Okay, now that I have written this, all the details in this plan have been introduced. If you are interested, you might as well download the code and play with it yourself.

other

Above we have talked about the selection and combination of correctly assembling these technical components, as well as how to weigh and solve the problem of component mismatch, which are all technical and practical details.

Let’s share some things related to this plan and the reasons for this selection preference.

Thoughts that arise by chance

In 2019, I served as a technical evangelist for Meituan at Meituan Technical College. As the only student in the team who was familiar with coding, the task of developing an internal technology portal that focused on information display fell on me. As an engineer who has worked in front-end positions on Taobao UED and Meituan platforms, considering that front-end construction tools and dependencies are updated every year, considering that no matter how high the construction efficiency is (cumulatively), it will take a lot of time, and the subsequent maintenance cost of the project is still quite high. High, I couldn't help but start to think about a simpler "what you see is what you get" solution that doesn't require maintenance of builds or maintenance of dependencies.

Of course, there are also some internal reasons. For example, even with the help of a group of friends, various approvals have been given green light. However, within the group, it is not easy to run through the complete system process of R&D resources of non-technical departments. . There are also many challenges in the system process, and even a lot of solid logic needs to be challenged. It needs to be tossed from the code warehouse to the service center, data and file storage, etc., including EE, SRE, security, and various service-related maintenance methods. Waiting, is no less than tossing a new R&D department online in the company's internal system.

At that time, a few friends kindly reminded me why it is so complicated. It is better to anchor the project to a stable service and then solve the domain name pointing and program hosting. After some searching, I found two suitable services. They are both company-level applications and have very high reliability.

However, after a comprehensive analysis of the services of these two friends, it is best to only borrow domain names for the former, while the latter only has the ability to access static pages and static resources. Of course, I'm not too embarrassed to stuff some front-end and back-end codes from unrelated technical sites into my friend's system-wide services. Because of the previous reasons, I had to think about the solution with the least resource dependence, including not building the front end (to avoid borrowing a friend's machine and causing trouble).

So, in this environment, I came up with a set of solutions. Among them, the front-end solution, after a short period of tossing, wrote an article " Your website may not need front-end construction ". The code examples in the article are also open source. GitHub: soulteary/You-Dont-Need-Webpack .

San subjective and objective reasons for selection

Let’s talk about the objective reasons first.

  1. In 2024, SAN is one of the few frameworks that ensures the development of modern syntax and is still working hard to maintain forward compatibility without any break changes. In the nearly ten-year update cycle, there have been stable updates and it is trustworthy.
  2. Many products from major manufacturers are built based on it, and there are a large number of traffic-verified application cases endorsed by it. If you should avoid pitfalls, others will do it for you, so you don’t need to worry too much.
  3. The team is relatively stable, and the project has no revenue pressure. If you look through the submission records and community follow-up records, it is not difficult to analyze that it is a pure project created by Geek Leader and a group of technical experts to generate electricity for love.

Of course, there are also two subjective reasons:

  1. After tossing around with various front-end projects, I became increasingly tired of building, especially if I take out the project after a year or two. If the environment needs to be re-initialized, various dependency abandonment reminders will appear on the screen. Moreover, it is also a waste of laptop performance. Even though the performance of my device is not bad, the memory is quite large (24G~64G).
  2. Whether it’s me or my classmates who are using this solution with me, you don’t need to rely on the complexity of front-end projects to play the promotion game, nor do you need the technology stack of such projects to find a job. Writing code can be simpler. Use whatever is simple and effective.

at last

If you think the article, solution, or the open source software SAN used in the article is good, you are welcome to click and connect. Of course, it would be better if it is a Pull Request for the project.

This article is just the beginning. Next, in the tossing articles about "interface", I will continue to update and improve this "no construction" solution.

–EOF


This article uses the "Attribution 4.0 International (CC BY 4.0)" license agreement. You are welcome to reprint, re-modify and use it, but the source must be indicated. Attribution 4.0 International (CC BY 4.0)

Author of this article: Su Yang

Creation time: January 4, 2024
Statistical word count: 10797 words
Reading time: 22 minutes
Link to read this article: https://soulteary.com/2024/01/04/your-website-may-not-need-front-end -builds-chapter-2.html

Guess you like

Origin blog.csdn.net/soulteary/article/details/135383532