Did not go up for a long time to dig gold, found in the draft box saved several articles did not send, recently comb out

The issue of

  • Interviewer Series (1): How to achieve deep cloning
  • Interviewer Series (2): The implementation of Event Bus
  • Interviewer Series (3): Implementation of front-end routing
  • Interviewer Series (4): The advantages of two-way binding based on Proxy data hijacking
  • Interviewer Series (5): Why do you use a front-end framework

preface

Designing front-end components is one of the most fundamental tests a developer can do, because API calls to off-the-shelf component libraries like Material Design, Antd, and iView are available to everyone, but many people don’t know the design principles of many commonly used components.

One of the things that separates front end engineers from front end API callers is the ability to design a common front end component. How do you design a common front end component?

In the following sections, component libraries are usually referred to as individual components, rather than collections. In the context of collections, component libraries are referred to as individual components in collections, such as Antd iView.


The article directories

  1. Design principles for front-end component libraries
  2. Technology selection of component library
  3. How do I quickly start a component library project
  4. How do I design a rotograph component

1. Design principles for front-end component libraries

1.1 Fine-grained considerations

When we are learning design patterns will encounter a lot of kinds of design principle, one of the design principle is the single responsibility principle, applies in the component library’s development, we only focus on one thing a component in principle, the benefits of the single responsibility of components, obviously, because of the duty of a single can maximum likelihood reuse component, but it also brings a problem, too single Responsible components can also lead to excessive abstraction, resulting in fragmentation of the component library.

So for example, An AutoComplete component is actually a combination of Input and Select components, so we can reuse the previous related components, such as Antd If you reuse the Select component in AutoComplete, and if you reuse the Select component in Calendar, Form, and a number of other components, then the fine granularity of Select is appropriate because the fine granularity that Select maintains is easy to reuse.

Then there is another example, a Badge component. There is a red dot on the upper right corner of the Badge, which may be a number or an icon. Of course, the responsibility of the Badge is very simple. Of course, the red dot can also be abstracted as a separate component, but we usually do not use it as a separate component, because it is not reusable in other scenarios, because there is no similar scenario where the small red dot is needed. Therefore, as an independent component, it is too fine-grained. Therefore, we tend to treat it as an internal component of Badge. For example, in Antd, it exists as an internal component of Badge with the name ScrollNumber.

Therefore, the so-called single responsibility components should be built on the basis of reusable, for non-reusable single responsibility components we can only as internal components of independent components.

1.2 Consideration of generality

What we want to design is a common component library, which is different from our common business components, which are decoupled from the business but also serve the business development. So the question is, how to ensure the commonality of the components? Is high commonality a good thing?

Let’s say we’re designing a Select component, and we usually design it like this

When we have a requirement that looks like this, our previous selector component does not meet the requirement, because the Select component needs to have an extensible item button at the bottom

At this point, should we modify the previous selector component, or even create a new selector component that meets the requirements? When this happens, it simply means that the previous selector component is not universal enough and needs to be redesigned.

Antd’s Select component reserves dropdownRender for custom rendering, which relies on the following code in the RC-select component

Antd relies on a large number of underlying components starting with RC -, which are maintained by the React-Component team (also known as the Antd team) and implement the underlying logic of the components. Antd is implemented by adding the Ant Design language on top of this

Of course, there are many similar designs. Universal design is to give up the control of DOM in a certain sense, and transfer the decision of DOM structure to the developer. DropdownRender is to give up the control of the items in the Select drop-down menu, and THE Select of Antd In fact, there is another method, getInputElement, which is not shown in the document, and it should be a custom method for the Input component. The design of the entire Select component of Antd is very complex, which basically exposes all the control of DOM structure to the developer, and is only responsible for the underlying logic and the most basic The DOM structure.

This is the re-select final JSX structure that Antd relies on, whose DOM structure is simple but exposes a lot of custom rendering interfaces to developers.

return (
      <SelectTrigger
        onPopupFocus={this.onPopupFocus}
        onMouseEnter={this.props.onMouseEnter}
        onMouseLeave={this.props.onMouseLeave}
        dropdownAlign={props.dropdownAlign}
        dropdownClassName={props.dropdownClassName}
        dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
        defaultActiveFirstOption={props.defaultActiveFirstOption}
        dropdownMenuStyle={props.dropdownMenuStyle}
        transitionName={props.transitionName}
        animation={props.animation}
        prefixCls={props.prefixCls}
        dropdownStyle={props.dropdownStyle}
        combobox={props.combobox}
        showSearch={props.showSearch}
        options={options}
        multiple={multiple}
        disabled={disabled}
        visible={realOpen}
        inputValue={state.inputValue}
        value={state.value}
        backfillValue={state.backfillValue}
        firstActiveValue={props.firstActiveValue}
        onDropdownVisibleChange={this.onDropdownVisibleChange}
        getPopupContainer={props.getPopupContainer}
        onMenuSelect={this.onMenuSelect}
        onMenuDeselect={this.onMenuDeselect}
        onPopupScroll={props.onPopupScroll}
        showAction={props.showAction}
        ref={this.saveSelectTriggerRef}
        menuItemSelectedIcon={props.menuItemSelectedIcon}
        dropdownRender={props.dropdownRender}
        ariaId={this.ariaId}
      >
        <div
          id={props.id}
          style={props.style}
          ref={this.saveRootRef}
          onBlur={this.onOuterBlur}
          onFocus={this.onOuterFocus}
          className={classnames(rootCls)}
          onMouseDown={this.markMouseDown}
          onMouseUp={this.markMouseLeave}
          onMouseOut={this.markMouseLeave}
        >
          <div
            ref={this.saveSelectionRef}
            key="selection"
            className={` ${prefixCls}-selectionThe ${prefixCls}-selection--The ${multiple ? 'multiple' : 'single` '}}role="combobox"
            aria-autocomplete="list"
            aria-haspopup="true"
            aria-controls={this.ariaId}
            aria-expanded={realOpen}
            {. extraSelectionProps}
          >{ctrlNode} {this.renderClear()} {this.renderArrow(!! multiple)}</div>
        </div>
      </SelectTrigger>
    );
Copy the code

With so many customizations, isn’t it hard to use the Select component? Since there seems to be a need for developer customization everywhere, the universal design leaves the DOM structure up to the developer while retaining the default rendering structure. The default rendering structure is used when the developer does not display the custom.

    <Select defaultValue="lucy" style={{ width: 120 }} disabled>
      <Option value="lucy">Lucy</Option>
    </Select>
Copy the code

The shape of a component (DOM structure) is always variable, but its behavior (logic) is fixed, so one of the secrets of common components is to give the developer control of the DOM structure, and the component is responsible for only the behavior and the most basic DOM structure


2 Technology Selection

2.1 CSS Solution

Due to the numerous shortcomings of CSS itself, such as writing tedious (no support for nesting), style conflict (no concept of scope), lack of variables (not easy to change themes) and so on. In order to solve these problems, the community also came out one after another, from the earliest CSS prepocessor (SASS, LESS, Stylus) to the later up-and-comer PostCSS, and then to CSS Modules, Styled-Components, etc.

Antd chose less as the CSS preprocessor and Bootstrap chose Scss. There has been debate over the superiority of the two options for many years:

What are the advantages of SCSS over LESS?

However, one annoying aspect of either solution is that it requires additional CSS. For example, Antd needs to be shown like this:

import Button from 'antd/lib/button';
import 'antd/lib/button/style'; 
Copy the code

To solve this awkward situation,Antd uses the Babel plugin to Hack the situation away

The most popular React front-end component library has only JS and TS code. There is no CSS-related code. Why?

They use JSS as a solution for CSS-in-JS. The introduction of JSX has already coupled JS and HTML, and css-in-JS has coupled CSS into it, so that components do not need to display the CSS, but directly reference JS.

Isn’t this a throwback to the pre-prehistoric era of inline writing?

No, the prehistoric front end inline style is the state of the whole project coupling, of course, to be abandoned to the rubbish dump of history, the later style and logic separation, in fact, is the page for the dimension of the decoupling of JS CSS HTML process, today’s era is the era of componentization, JSX has js and HTML decoupled Framed into a component, CSS is still in a separate state, which leads to a complete componentized solution that requires the introduction of CSS every time a component is referenced.

Of course, I am currently using styled- Components, whose advantages are quoted as follows:

  1. First, all the Syntaxes for the Styled – Components are standard CSS syntaxes, while supporting common syntaxes such as SCSS nesting, covering all CSS scenarios.

  2. In the style facsimile scenario, Styled – Components support the injection of global CSS anywhere, as if writing normal CSS

  3. Styled – Components support custom classnames, one by using the Babel plug-in and the other by using Styled.div. WithConfig ({componentId: “Prefix-button-container”}) equivalent to adding className=”prefix-button-container”

  4. ClassName is more semantically easy, which is why class is named

  5. If it is more suitable for component libraries, you can simply refer to import “module”, otherwise you have three options: to refer to CSS separately like ANTD, you need to add CSS – Loader to node_modules; Import CSS files directly from the component. If any business item does not have CSS -loader, an error will be reported. Components use SCSS references. All service items must be configured with a SCSS-Loader for node_modules. None of these is as friendly as a direct reference to a component library

  6. When you write a set of component libraries, you need to send a separate package, and you need to have a unified configuration file. If the configuration file is js, all components refer to it directly, and you don’t need to pay attention to the outside world. Otherwise, if the SCSS configuration file is used, there are still three ways: each component references the SCSS file separately, each service item needs to add scSS-Loader to node_modules (if less is used, SCSS is not installed); For example, if your component is called button, SCSS might be called common-CSS, which no one else has heard of. Or the business side can refer to your common-CSS separately in the WebPack configuration, which is also not scientific, if you use 3 component libraries, it is not convenient to change the WebPack configuration every day.

  7. When you set half of the CSS style, the other half really needs to be dynamically passed into JS, you have to use CSS + CSs-in-JS mix, the project for a long time, maintenance, found that some css-in-JS unchanged, can be fixed in CSS, CSS fixed values are changed because of the new change. You have to take it out and put it in CSS-in-JS, and you’ll see how annoying it is.

2.2 JS solution

Choose Typescript because it’s hard…

Check out my answer to Zhihu’s question why don’t you use Typescript

Or check out this TypeScript architecture survey


3. How do I quickly start a component library project

The implementation part of the component is of course the core of the component library, but other parts of the modern front-end library are also essential, and we need a bunch of tools to help us develop, such as compilation tools, code detection tools, packaging tools, and so on.

3.1 Packaging Tools (Rollup vs WebPack)

There are a lot of packaging tools out there, and webPack is the most popular one that needs to be configured by configuration engineers, but it has a strong rival in library development with Rollup.

The main libraries on the market use Rollup as a packaging tool, including Angular React and Vue. The advantages of rollup as a base library are as follows:

  • Tree Shaking: Automatically removes unused code and outputs smaller files
  • Scope collieries: All modules are built in one function for more efficient execution
  • The Config file can be written in ESM module format to output multiple formats at a time:
  • Module specification: IIFE, AMD, CJS, UMD, ESM Development and Production versions:.js,.min.js

While some of the above features have been implemented by WebPack, Rollup was obviously introduced earlier, and Scope colliers is a great help as WebPack had to build a module system in the package code to accommodate the app (The module system is of little use to a single library),Scope’s ability to build modules within a function is more appropriate for library packaging.

3.2 Code Detection

Because of JavaScript’s weird features and large front-end projects, code detection tools have become standard for front-end developers. Douglas Crockford first created JSLint in 2002, but it’s unextensible and has a very strong Douglas In Crockford’s personal style,Anton Kovalyov released extensible JSHint in 2011 because he couldn’t stand JSLint’s inability to expand, and JSHint became a popular solution for front-end code detection.

Then, in 2013,Nicholas C. Zakas developed a new AST-based Version of ESLint to address the problem of insufficient flexibility in JSHint extensions, and with the popularity of ES6 dominating the front-end,ESLint was based on Esprima JavaScript parsing features are easy to extend, and JSHint’s inability to support ES6 syntax for a long time was overtaken by ESLint.

However, ESLint is in a weak position in the Typescript world. TSLint appeared long before ESLint officially supported Typescript, and currently TSLint appears to be TS’s de facto code detection tool.

Note: THIS article was written earlier, I didn’t realize that TS officially selected ESLint a while ago,TSLint fell out of favor, the official standard code detection tool for the future will be ESLint, but TSLint is still widely used and can still be used safely today

On the one hand, we need to choose the code detection tools. On the other hand, we also need to choose the code detection style. The most popular code detection style in the market should be Airbnb Eslint-config-airbnb is extremely strict and doesn’t give developers any choice. Of course, this strict code style is good for collaboration in large front-end projects, but as a library code inspection tool is not suitable, so we chose ESLint-Con Fig.standard is a more relaxed style of code detection.

3.3 commit specification

Which of the following two commit is more rigorous and easy to maintain?

When I first started using commit, I often made mistakes in diagrams. I didn’t realize my mistakes until I saw many star libraries commit. Writing a commit message not only helps others review it, but also effectively output CHANGELOG. The management of the project is actually critical.

The current popular solution is the Angular team specification, which roughly describes head as follows:

  • Type: Indicates the type of COMMIT
  • Feat: new features
  • Fix: Fix the problem
  • Refactor: Code refactoring
  • Docs: Modify the document
  • Style: Code format modification, note not CSS modification
  • Test: Test case modification
  • Chore: Other modifications such as build process, dependency management.
  • Scope: The scope of the commit effect, such as route, component, utils, build…
  • > > < span style = “style =” style = “style =” style = “style =” style = “style =” style = “style =” style =
  • Body: Commit Specific modification, can be divided into several lines, recommended to conform to 50/72 formatting
  • Footer: Some notes, usually links to BREAKING changes or fixed bugs.

Of course, specifications don’t necessarily follow, and WHEN I first learned of such specifications, I didn’t strictly follow them, because people always get lazy, and until the specification is integrated into the tool stream with Commitizen, every commit has to follow the specification.

I refer to this article: Commit your Git Commit Message gracefully

3.4 Test Tools

In business development, due to the frequent change of front-end requirements, the front-end requirements for testing are not as high as that of the back-end. Once the back-end business logic is finalized, there are few changes, so it is more suitable for testing.

However, the basic class library, as a module that is repeatedly dependent and has relatively stable requirements, must be tested, and the front-end test library can be said to be of various types. After comparison, I still choose the most popular Jest, which is also selected by the three frameworks at the same time, as the test tool, and its advantages are obvious:

  1. Out of the box, there are built-in assertions and test coverage tools, which you’ll have to configure manually if you use MoCha
  2. Snapshot feature,Jest can take advantage of its unique snapshot test feature to compare the snapshot files generated by UI code
  3. Speed advantage. Jest’s test cases are executed in parallel, and only the tests that correspond to the files that have changed are executed, increasing test speed

3.5 other

Of course, the above are the main tools to choose, and there are some such as:

  • Code prettier tool prettier, free human flesh beautification, while conducive to different people collaboration style consistent
  • Continuous integration tool Travis – CI, which frees human testing Lint, helps ensure reliability for each push

3.6 Quick start of scaffolding

So do we have to write all these configurations by ourselves? The implementation of the component is the core of the component library. Why do we spend so much time on configuration?

We usually use official framework scaffolding when building APP projects, such as create-react-app for React, Angular-CLI for Angular, etc. How about a quick-start scaffold for component development?

Yes, I recently developed a command line tool to quickly start component library development — CREate-Component

using

create-component init <name>
Copy the code

To get the project started quickly, we offer a wealth of optional configurations. Once you’ve chosen your technology and followed the prompts, the CREate-Component automatically generates scaffolding based on the configuration, inspired by vue-CLI and Angular-CLI.


4. How to design a rotograph component

A lot of theory, but what about actual combat? Design a generic component and try it!

4.1 Basic principles of the rotund Diagram

Carousel, known in Antd as the “merry-go-round”, is probably one of the most common components for front-end developers, and we see it all the time on both PC and mobile.

So how do we usually use round diagrams? The code for Antd is as follows


  <Carousel>
    <div><h3>1</h3></div>
    <div><h3>2</h3></div>
    <div><h3>3</h3></div>
    <div><h3>4</h3></div>
  </Carousel>

Copy the code

The question is we put four sets of divs in Carousel why do we only show one set at a time?

In the figure, the visible area circled by the red box is fixed, and the position of the visible area is fixed. We only need to move the position of the div behind to achieve the effect of the round play of the four sub-components 1, 2, 3, and 4, so that sub-components 2 can be seen in the visible area, and 1, 3 and 4 should be hidden, so we need to set the overflow property as Hidden to hide subcomponents in the non-viewable area.

Copy to check the dynamic graph: images2015.cnblogs.com/blog/979044…

Therefore, it is obvious that we will design a visual window component called Frame, and then put the four divs together into the SlideList component, and wrap each div around a SlideItem. The actual code looks like this:

 <Frame>
    <SlideList>
        <SlideItem>
            <div><h3>1</h3></div>  
        </SlideItem>
        <SlideItem>
            <div><h3>2</h3></div>  
        </SlideItem>
        <SlideItem>
            <div><h3>3</h3></div>  
        </SlideItem>
        <SlideItem>
            <div><h3>4</h3></div>  
        </SlideItem>
    </SlideList>
  </Frame>
Copy the code

We constantly use translateX to change the position of SlideList to achieve the rotation effect, as shown below. Each rotation is triggered by changing Transform: translateX()

4.2 Basic realization of round cast diagram

It’s relatively easy to get the basics right, so let’s use the mobile implementation as an example to implement a basic mobile rosette diagram.

First we need to determine the width of the visible window, because we need this width to calculate the length of the SlideList (the length of SlideList is usually a multiple of the visible window, for example, if we want to put three images, the SlideList should be at least 3 times of the visible window), otherwise we cannot move through translateX It.

We use getBoundingClientRect to get the true length of the visible region. The length of SlideList is then:

SlideListWidth = (len + 2) * width(len is the number of children passed in,width is the visible area width)

And we’ll talk about why it’s +2 later.

  /** * Set the size of the rotation area * @param x */private setSize(x? : number) {const { width } = this.frameRef.current! .getBoundingClientRect()const len = React.Children.count(this.props.children)
    const total = len + 2

    this.setState({
      slideItemWidth: width,
      slideListWidth: total * width,
      total,
      translateX: -width * this.state.currentIndex,
      startPositionX: x ! = =undefined ? x : 0})},Copy the code

Now that I have the total length, how do I do the round play? We need to trigger a round play based on user feedback, which is usually triggered by a finger swipe on mobile, which requires three events: onTouchStart, onTouchMove, onTouchEnd.

OnTouchStart is an event that is triggered when the finger touches the screen. In this event, we only need to record the horizontal axis x of the finger touching the screen, because we will determine whether the rotation is triggered by the distance that the finger swips across the screen

  ** @private * @param {React.TouchEvent} e * @memberof Carousel */
  private onTouchStart(e: React.TouchEvent) {
    clearInterval(this.autoPlayTimer)
    // Get the starting horizontal coordinate
    const { x } = getPosition(e)
    this.setSize(x)
    this.setState({
      startPositionX: x,
    })
  }
Copy the code

OnTouchMove, as the name suggests, is an event that’s in the swiping state, after onTouchStart, before onTouchEnd, and there are two main things that we do in this event. One is to determine the swiping direction, because the user may be swiping left or right, and the other is to make the rotograph follow the finger, which is Necessary user feedback.

 ** @private * @param {React.TouchEvent} e * @memberof Carousel */
  private onTouchMove(e: React.TouchEvent) {
    const { slideItemWidth, currentIndex, startPositionX } = this.state
    const { x } = getPosition(e)

    const deltaX = x - startPositionX
    // Determine the sliding direction
    const direction = deltaX > 0 ? 'right' : 'left'

    this.setState({
      direction,
      moveDeltaX: deltaX,
      // Change translateX to make the rotation component follow the finger
      translateX: -(slideItemWidth * currentIndex) + deltaX,
    })
  }
Copy the code

OnTouchEnd just as its name implies is to trigger events when the sliding, in the event we mainly do one thing, is to determine whether the trigger wheel, we will set a threshold value threshold, when the sliding distance exceeds this threshold will trigger shuffling, after all, there is no threshold slightly touched by the user figure is shuffling, wrong operation will cause Poor user experience.

  ** @private * @memberof Carousel */
  private onTouchEnd() {
    this.autoPlay()
    const { moveDeltaX, slideItemWidth, direction } = this.state
    const threshold = slideItemWidth * THRESHOLD_PERCENTAGE
    // Determine whether to rotate
    const moveToNext = Math.abs(moveDeltaX) > threshold

    if (moveToNext) {
        // If the rocast is triggered, then the rocast operation is performed
      this.handleSwipe(direction!)
    } else {
        // If the rotation does not trigger, the rotation diagram returns to its original position
      this.handleMisoperation()
    }
  }
Copy the code

4.3 Animation effect of round play diagram

Our common round diagram is certainly not a rigid switch, usually in the round will have a gradient or slow animation, which we need to add animation effect.

We usually have two choices when animating. One is to use cSS3’s built-in animation effects, and the other is to use the requestAnimationFrame API provided by the browser

Which is better? Css3 is easy to use, easy to use, easy to use, compatible,requestAnimationFrame is more flexible, can achieve cSS3 can not achieve animation, such as many slow animation CSS3 can not do, so we chose requestAnimationFrame without a doubt.

CSS3 animation is so strong, requestAnimationFrame and wool?

To implement easing effects with requestAnimationFrame requires a specific easing function, and here is a typical easing function

type tweenFunction = (t: number, b: number, _c: number, d: number) = > number
const easeInOutQuad: tweenFunction = (t, b, _c, d) = > {
    const c = _c - b;
    if ((t /= d / 2) < 1) {
      return c / 2 * t * t + b;
    } else {
      return -c / 2 * ((--t) * (t - 2) - 1) + b; }}Copy the code

The easing function takes four parameters, which are:

  • T: time
  • B: Initial position
  • _c: indicates the end position
  • D: speed

Through this function, we can calculate the position of each frame’s rosette diagram, as follows:

After getting the position of each frame, we need to recursive call requestAnimationFrame to move the position in sequence. We need to call requestAnimationFrame recursion to move the position in sequence. TweenQueue [0],}) to move the position of the rowhead graph. In this case, a quick execution of the 30 positions in the array is a slow animation.

  @param {number[]} tweenQueue * @param {number} newIndex * @memberof Carousel */
  private animation(tweenQueue: number[], newIndex: number) {
    if (tweenQueue.length < 1) {
      this.handleOperationEnd(newIndex)
      return
    }
    this.setState({
      translateX: tweenQueue[0],
    })
    tweenQueue.shift()
    this.rafId = requestAnimationFrame((a)= > this.animation(tweenQueue, newIndex))
  }
Copy the code

But we found a problem, when we move them by figure at the end of the day, there was a problem in animation, slide when we left the last round of seeding figure div4, this kind of circumstance should be picture scroll left, then the first round figure div1 into the viewing area, but paradoxically image fast sliding div1 appeared in the area but to the right…

This is because we set position 4 to position 1, so we can keep the loop going, but we also have the side effect that the image behavior is inconsistent with the user’s behavior (the user swipes left, the image goes right).

The current industry practice is to put the images end to end, for example, picture 1 is preceded by picture 4, and picture 4 is followed by picture 1, which is why we used to calculate the length +2, right

SlideListWidth = (len + 2) * width(len is the number of children passed in,width is the visible area width)

When we move image 4, we don’t have the above situation of swiping to the left and swiping to the right, because the reality is:

Image 4 — slide to -> Fake image 1 which means position 5 becomes position 6

After the end of the animation, we quickly put the pseudo image position is set to true picture 1, this is actually a distraction, that is to say, the animation is, in fact, during the implementation of picture 4 to 1, the process of the pseudo image when we slipped after the pseudo image change come true picture 1, because the two figures the same, so the conversion process of user does look not to come out…

In this way we can achieve seamless rotation diagram switching

4.4 Improvement Direction

We have realized the basic functions of the rotund diagram, but its universality still has defects:

  1. Tip point customization: My implementation is a dot, and ANTD is a bar, where it’s perfectly possible to leave dom structure decisions to the developer.

  1. Direction customization: epicycle diagram is only horizontal, in fact, there can also be vertical rotation

  1. Multi – sheet rotation: in addition to single – sheet rotation can also be multi – sheet rotation

These are the directions in which the rotund diagram can be expanded, and related to performance optimization

We have an implementation of this in our code. Our rotary-diagram actually has auto-rotary-function, but many times the page is not visible to the user. We can cancel the timer to terminate autoplay depending on whether the page is hidden or not.

Github project address

The demo above is for reference only. In the actual project development, it is best to use mature open source components, with the ability to build wheels and the consciousness of not building wheels


Refer to the link

  • react-slick
  • Complex component Design
  • Please stop csS-in-JS behavior
  • Use the rollup