marko-js / marko

A declarative, HTML-based language that makes building web apps fun
https://markojs.com/
MIT License
13.28k stars 641 forks source link

List is rerendered on browser even with zero state changes #1285

Closed StarpTech closed 3 years ago

StarpTech commented 5 years ago

Marko Version: 4.15.2

My components looks as follow:

class {}
style.scss {
  .category-components {
    .breadcrumb {
      padding-left: 20px;
      font-size: 12px;
    }
  }
  .product-list {
    margin-top: 30px;
    &__item {
      &__image {
        padding: 20px;
        background-color: #eaeaea;
        width: 100%;
        height: 100%;
      }
      &__details {
        margin: 20px 0;
      }
    }
  }
}

<section class="category-components">
  <section class="product-list container">
    <div class="row">
      <for|product| of=input.products>
        <div class="col-4">
          <div class="product-list__item align-middle">
            <a href=`/components/${input.category.slug}/${product.slug}`>
              <div class="product-list__item__image d-flex align-items-center justify-content-center text-center">
                <img src="../../global-asset/images/product-example.png"/>
              </div>
            </a>
            <div class="product-list__item__details">
              <span class="product-list__item__name d-block font-weight-bold">
                <a href=`/components/${input.category.slug}/${product.slug}`>${product.name}</a>
              </span>
              <span class="product-list__item__name d-block">${product.type}</span>
              <span class="product-list__item__name d-block">${product.vendor}</span>
            </div>
          </div>
        </div>
      </for>
    </div>
  </section>
</section>

Expected Behavior

The component should be rendered only on server-side.

Actual Behavior

The component is rerendered on the client even when no state was changed.

Workaround

Remove the complete class or don't use dynamic attribute tags for the layout.

Environment

I'm using layout tags and the default marko & lasso setup.

Ubuntu 18.04

DylanPiercey commented 5 years ago

The problem here (as you've discovered) is that if a component has an inline class or component.js file then it is determined to be a stateful component and must be rerendered in the browser.

Using class as the heuristic isn't ideal but it's the best we've got at the moment. If you'd like to use the components lifecycle methods you can add a component-browser.js file which supports a limited portion of the Marko lifecycle and runtime API and allows Marko to skip sending down this component (unless it is under another component). More info here.

Let me know if this solves your issue.

StarpTech commented 5 years ago

No success the payload is still delivered to the browser.

component-browser.js

module.exports = {
  onFilterChange(filterFormState) {
    console.log(filterFormState);
  }
};

index.marko

<section class="category-components">
  <section class="product-list container">
    <div class="row">
      <for|product| of=input.products>
        <div class="col-4">
          <div class="product-list__item align-middle">
            <a href=`/components/${input.category.slug}/${product.slug}`>
              <div class="product-list__item__image d-flex align-items-center justify-content-center text-center">
                <img src="../../global-asset/images/product-example.png"/>
              </div>
            </a>
            <div class="product-list__item__details">
              <span class="product-list__item__name d-block font-weight-bold">
                <a href=`/components/${input.category.slug}/${product.slug}`>${product.name}</a>
              </span>
              <span class="product-list__item__name d-block">${product.type}</span>
              <span class="product-list__item__name d-block">${product.vendor}</span>
            </div>
          </div>
        </div>
      </for>
    </div>
  </section>
</section>

Could you point me to the place where that decision is made?

DylanPiercey commented 5 years ago

The way that it works is that the Marko compiler exposes some meta data that has info like is there a component-browser and a list of browser dependencies. It's up to the bundler implementation to actually load the right assets in the browser. You can see how we are doing this in lasso here.

Having said that the above does not look like the template should be sent down. Is there anything else in the template? Are you using a browser.json or anything like that?

StarpTech commented 5 years ago

Yes, I use a browser.json on page level but it only includes jquery and some sass files. The really strange is: When I remove everything in the <@footer> tag in my root page, the code isn't serialized. The code in the footer container is:

  <@footer>
    <section class="container">
      <app-footer/>
    </section>
  </@footer>

and app-footer is a marko file with 2 divs no js. It is worse than that, when I remove the whole @footer it is serialized again! Please don't tell me that marko tags or lasso tags force the bundler to build for the browser.

StarpTech commented 5 years ago

The bug is still there and looks like a deeper marko issue of dynamic attribute tags You can reproduce it as follow:

DylanPiercey commented 3 years ago

Although this is something we plan to automatically optimize away in the future we've mentioned this caveat in the docs (https://markojs.com/docs/server-side-rendering/#caveats).

Also using split-components allows for some additional optimization, and you can manually prune input for split components by following https://markojs.com/docs/server-side-rendering/#serialization