laitimes

Technical Practice Dry Goods | Exploration of front-end plug-in

author:Flash Gene

1

background

Nowadays, more and more customers are subscribing to the data analysis platform, and the industry is becoming more and more extensive, and simple standardized products can no longer meet the diverse business scenarios and needs of customers.

The data analysis platform has a variety of deployment environments: privatization, analysis cloud, SAAS, and different customers use different versions, even if we can quickly develop and iterate, in order to meet customer needs, we may need to pick the corresponding feature to a different version, otherwise customers can only upgrade to the latest version to use the corresponding function.

Therefore, we need to provide some plug-in solutions, and the logic implemented by the plug-in is customizable, can be updated in real time, and does not depend on the release of the main project. The custom charting feature in the data analytics platform is a plug-in solution that meets this need.

2

Chart plug-in scheme

The following is a screenshot of the custom chart editing page, as shown in the figure, the user needs to write HTML+CSS+JavaScript code to generate the corresponding chart, and the chart will be rendered through an iframe. If you want to reuse these codes to create charts, you can package the code into a json file and install it on the data analysis platform in the form of a plug-in.

Technical Practice Dry Goods | Exploration of front-end plug-in

At the same time, we have defined a set of communication mechanisms, relying on this communication mechanism, the parent page can be used to transfer data between the parent page and the iframe, as shown in the above figure, the table data in the right area is from the view data passed in from the parent page.

Although this implementation method has a high degree of freedom, it also requires editors to have a certain front-end knowledge base, which greatly improves the cost of use; due to the isolation limitation of iframes, it is difficult for us to provide some open capabilities for custom charts, such as data formatting, etc.; in addition, the loading of iframes will rebuild the context, which is not only slow and consumes browser resources. With these limitations in mind, we've introduced Custom Chart Lite again.

3

Chart plug-in scheme upgraded

Custom charts Lite is based on ECharts, the purpose is to make it faster and simpler for users to create charts, compared to the former, which only needs to write JavaScript code to implement the options required for ECharts drawing, for some simple charts can be completely modified based on official examples, which greatly reduces the mental burden of chart developers.

The screenshot below shows the editing interface of Lite for custom charts, and the option on the left is implemented with reference to the base line chart [1] of the official ECharts example.

Technical Practice Dry Goods | Exploration of front-end plug-in

Drawing custom charts Lite no longer uses iframes, but directly uses the built-in BaseChart, which is free from the limitations of iframes, and data interaction becomes very simple, and you can use many built-in capabilities, such as the data formats mentioned earlier that are difficult to support in iframe scenarios.

Of course, our scenario is far more than the expansion of chart capabilities, and the above-mentioned chart plug-in scheme can only serve the function of charts. Suppose we want to implement more customized business scenarios, such as supporting user-defined information feedback, data collection, etc., how to design a plug-in solution?

4

How to select the technology of the plug-in solution

We need to consider the following aspects to select the technology of the plug-in solution:

  • Environment isolation: The custom code introduced through the plug-in must be isolated from the main page to prevent contamination of styles and variables
  • Technical maturity: The technology needs to be very mature, the support for each browser should not be too poor, and the community should be active
  • Adaptability: It has very good adaptability for cross-platform and cross-framework, so that one set of code can be used at multiple ends
  • Communication: Too complex a communication method will increase the complexity of the implementation
  • DOM Structure Sharing: Useful for scenes where a pop-up is displayed in the center of the viewport
  • Dynamic loading and updating are supported

When we see "isolation", the first thing that comes to mind is the iframe solution, but iframes also have many disadvantages, for details, please refer to the article Why Not Iframe[2] in the article Qiankun Technology Selection of Micro Frontend Framework.

Through Alibaba's D2 front-end technology forum and front-end chat, we learned that many companies have used Web Components technology in production environments, and many websites have also used Web Components, such as youtube [3] and github [4], and many landing scenarios have also made us pay attention to this technology.

5

什么是 Web Components

Web Components is a suite of technologies that allow us to create custom elements that can be reused. It was proposed by Alex Russell at the Fronteers Conference[5] in 2011, formally launched by the W3C in 2012[6], and officially incorporated into the standard in 2014[7], and has since been gradually supported by browsers, among which Google's Polymer Project project started in 2015 has played a great role in temporarily supporting browser compatibility through polyfills. The Web Components used today is its second version, v1 (the previous version, v0).

Web Components consists of three core technologies: custom elements, shadow dom, and html templates. The technical details are not detailed here, but if you are interested, you can see more on MDN[8]. Let's start by looking at how to implement a custom element based on Web Components.

class MyElement extends HTMLElement {
    constructor() {
        super()
        // 创建一个 shadow Root
        const shadowRoot = this.attachShadow({ mode: 'open' })
        const container = document.createElement('div');
        container.setAttribute('id', 'container');
        container.innerText = "hello, my custom element"

        shadowRoot.appendChild(container)
   }
}

customElements.define('my-element', MyElement)           

We can introduce the corresponding js file into the html file, and use the custom element to open the html file to see the content rendered successfully.

<html>
   <head>
       <script src="./my-element.js"></script>
   </head>
  <body>
      <my-element></my-element>
  </body>
</html>           

Shadow Dom also has a special css pseudo-class selector, :host, through which you can select Shadow Root, and when we want to define a style for custom elements according to different environments, we can use the :host-context() pseudo-class selector. The following CSS code implements the function of "setting the background color of the custom element to red when it is in the H1 tag".

:host-context(h1) {
    background-color: red;
}           

Web Components can be used for much more than that, and can be found in the official example [9]. After understanding how Web Components is used, whether the technical solution can meet the requirements of existing business scenarios, such as supporting a custom feedback entry on the page, needs to be further verified.

6

基于 Web Components 的插件化方案验证

Since the data analytics platform is developed on top of React, in order to test in the same environment, we use create-react-app to quickly create a React project.

  • Add the my-element.js file to the public directory, in which we implement the my-element as a custom element, which is mainly to draw an icon, click icon to open a pop-up window, in which the parameters x and y will be displayed;
  • Ingest the file in index.html via script tags, while rendering my-element tags in specific containers in App.js and passing parameters via atrribute.

Let's take a look at the effect achieved:

Technical Practice Dry Goods | Exploration of front-end plug-in

The corresponding my-element.js is implemented as follows:

class MyElement extends HTMLElement {
    constructor () {
        super();
        this.init();
         
        this.open = false

        this.triggerOpen = this.triggerOpen.bind(this)
        this.triggerClose = this.triggerClose.bind(this)
    }
    
    init () {
        const shadowRoot = this.attachShadow({mode: 'open'});

        const style = document.createElement('style');
        style.textContent = `
        #container { height: 100% }

        .icon-wrapper {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 40px;
            width: 40px;
            border-radius: 100%;
            overflow: hidden;
            background-color: #fff;
            box-shadow: 0 2px 4px rgb(206, 224, 245);
            cursor: pointer;
        }

        .icon-wrapper:hover {
            box-shadow: 0 4px 6px rgba(57, 85, 163, 0.8);
        }

        .icon-wrapper svg {
            width: 20px;
            height: 20px;
        }

        .modal-wrapper {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, 0.3);

            visibility: hidden;
            transform: scale(0);
            transition: opacity 0.25s 0s, transform 0.25s;
        }
        .modal-wrapper.show {
            visibility: visible;
            transform: scale(1.0);
        }
        .modal-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 300px;
            background-color: white;
            border-radius: 2px;
            padding: 12px;
            max-height: 300px;
        }
        `

        const container = document.createElement('div');
        container.setAttribute('id', 'container');

        const iconWrapper = document.createElement('div')
        iconWrapper.setAttribute('class', 'icon-wrapper')
        iconWrapper.innerHTML= `
            <svg t="1667901570010" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21577" width="200" height="200">
                <path d="M511.908 955.75c-8.807 0-17.43-3.302-24.22-10.091L385.307 843.276c-13.394-13.394-13.394-34.861 0-48.255s34.861-13.395 48.256 0l78.346 78.346 78.347-78.346c6.422-6.422 15.045-10.092 24.22-10.092h238.893c18.898 0 34.127-15.229 34.127-34.128V204.76c0-18.715-15.229-34.128-34.127-34.128H170.816c-18.715 0-34.128 15.413-34.128 34.128V750.8c0 18.9 15.413 34.128 34.128 34.128h102.383c18.898 0 34.127 15.229 34.127 34.128s-15.229 34.127-34.127 34.127H170.816c-56.513 0-102.383-45.87-102.383-102.383V204.76c0-56.513 45.87-102.383 102.383-102.383h682.552c56.512 0 102.383 45.87 102.383 102.383V750.8c0 56.513-45.87 102.383-102.383 102.383H628.419l-92.291 92.475c-6.605 6.605-15.413 10.092-24.22 10.092z" p-id="21578"></path><path d="M324.206 511.908c-28.256 0-51.19-22.935-51.19-51.191s22.934-51.192 51.19-51.192 51.192 22.936 51.192 51.192-22.935 51.191-51.192 51.191z m204.766 0c-28.256 0-51.191-22.935-51.191-51.191s22.935-51.192 51.191-51.192 51.191 22.936 51.191 51.192-22.935 51.191-51.19 51.191z m204.949 0c-28.256 0-51.191-22.935-51.191-51.191s22.935-51.192 51.191-51.192c28.256 0 51.192 22.936 51.192 51.192s-23.12 51.191-51.192 51.191z" p-id="21579"></path>
            </svg>
        `

        const modalWrapper = document.createElement('div')
        modalWrapper.setAttribute('class', 'modal-wrapper')
        const content = document.createElement('div')
        content.setAttribute('class', 'modal-content')
        modalWrapper.appendChild(content)

        container.appendChild(iconWrapper)
        container.appendChild(modalWrapper)

        shadowRoot.appendChild(style);
        shadowRoot.appendChild(container);
    }

    connectedCallback() {
        // 添加事件监听
        const iconWrapper = this.shadowRoot.querySelector('#container .icon-wrapper')
        iconWrapper.addEventListener('click', this.triggerOpen)

        const maskWrapper = this.shadowRoot.querySelector('#container .modal-wrapper')
        maskWrapper.addEventListener('click', this.triggerClose)
    }

    disconnectedCallback () {
        // 卸载事件监听
        const wrapper = this.shadowRoot.querySelector('#container .icon-wrapper')
        wrapper && wrapper.removeEventListener('click', this.triggerOpen)

        const maskWrapper = this.shadowRoot.querySelector('#container .modal-wrapper')
        maskWrapper && maskWrapper.removeEventListener('click', this.triggerClose)
    }

    triggerOpen () {
        const modalWrapper = this.shadowRoot.querySelector('#container .modal-wrapper')
        if(modalWrapper) {
            const maskContent = modalWrapper.querySelector('.modal-content')
            maskContent.innerHTML = `
                <p>x: ${this.getAttribute('x')}</p>
                <p>y: ${this.getAttribute('y')}</p>
            `
            modalWrapper.classList.add('show')
        }
    }

    triggerClose () {
        const modalWrapper = this.shadowRoot.querySelector('#container .modal-wrapper')
        modalWrapper.classList.remove('show')
    }
}

customElements.define('my-element', MyElement)

           

The implementation of the above custom elements is based on the native JS syntax, which is very cumbersome to write, and when the complexity of the internal structure of the custom elements increases, the development efficiency will be reduced accordingly.

The community also has some solutions that can help us quickly build Web Components, such as Google's open source Lit[10], and Lit allows us to write Web Components in the same way as React-like components, which greatly improves the development experience. However, it should be noted that Lit is developed based on ES2019, and in order to adapt to lower versions of browsers, you need to pay attention to adding corresponding plugins and polyfills when packaging. Based on Lit, there are also many open source UI component libraries, such as Wired Elements[11] and Lithops UI[12], and you can also refer to the implementation of these libraries if you are interested.

7

summary

Web Components' technical solutions are already available for our current business scenarios:

  • Through the Shadow Dom, the style isolation can be achieved, and the DOM structure can be shared at the same time;
  • The data transfer method is also very simple, the example in the body part only introduces the attribute parameter passing method, this method only supports passing string types, when we need to pass complex data types, we can pass parameters through the way of property, the specific principle can refer to handling-data-with-web-components[13] article;
  • Implement custom elements through an imported js file, which can be dynamic, and the js file can be set up to negotiate the cache, so that the latest content can be fetched every time the page is visited;

As plug-ins continue to emerge, we will continue to explore the potential of Web Components to make plug-in even more possible.

8

Refer to the documentation

  • https://developer.mozilla.org/en-US/docs/Web/Web_Components[14]
  • https://qiankun.umijs.org/zh/guide[15]
  • https://www.yuque.com/kuitos/gky7yw/gesexv[16]
  • https://lit.dev/docs/[17]

Resources

[1] Base line chart: https://echarts.apache.org/examples/zh/editor.html?c=line-simple

[2] Why Not Iframe: https://www.yuque.com/kuitos/gky7yw/gesexv

[3] youtube: https://www.youtube.com/index

[4] github: https://github.com/

[5] Fronteers Conference: https://fronteers.nl/congres/2011/sessions/web-components-and-model-driven-views-alex-russell

[6] Draft: https://www.w3.org/TR/2012/WD-components-intro-20120522/

[7] Standard: https://www.w3.org/TR/components-intro/

[8] Presentation on MDN: https://developer.mozilla.org/en-US/docs/Web/Web_Components

[9] Official example: https://github.com/mdn/web-components-examples

[10] Read: https://lit.dev/docs/

[11] Wired Elements: https://wiredjs.com/

[12] Lithops UI: https://github.com/cenfun/lithops-ui

[13] handling-data-with-web-components: https://itnext.io/handling-data-with-web-components-9e7e4a452e6e

[14] https://developer.mozilla.org/en-US/docs/Web/Web_Components: https://developer.mozilla.org/en-US/docs/Web/Web_Components

[15] https://qiankun.umijs.org/zh/guide: https://qiankun.umijs.org/zh/guide

[16] https://www.yuque.com/kuitos/gky7yw/gesexv: https://www.yuque.com/kuitos/gky7yw/gesexv

[17] https://lit.dev/docs/: https://lit.dev/docs/

Author: W.P, a front-end development engineer at Guanyuan, studied at Northeastern University with both bachelor's and master's degrees. Practice team development specifications, improve development quality, dig out front-end knowledge details, and strive to create more easy-to-use ABI products.

Source-WeChat public account: Guanyuan Data Technical Team

Source: https://mp.weixin.qq.com/s/zIeuFnvzeT4pNrXuJ9IZEA

Dry

Read on