marko-js / webpack

A loader for marko files and a plugin for universal bundling
MIT License
13 stars 4 forks source link

Customizable asset output #43

Open tigt opened 3 years ago

tigt commented 3 years ago

Description

(Old, original issue for historical purposes: #11)

Right now, @marko/webpack writes markoc’s produced component assets to the HTML like so in /src/loader/get-asset-code.ts:

if (assets.js) {
  const nonceAttr = nonce ? ` nonce=${JSON.stringify(nonce)}` : "";
  assets.js.forEach(js => {
    this.write(
      `<script src=${JSON.stringify(__webpack_public_path__+js)}${nonceAttr} async></script>`
    );
  });
}
if (assets.css) {
  assets.css.forEach(css => {
    this.write(
      `<link rel="stylesheet" href=${JSON.stringify(__webpack_public_path__+css)}>`
    );
  });
}

There’s nothing wrong with this, but its one-size-fits-all approach may be unsuitable for a number of reasons…

Why

Possible Implementation & Open Questions

In the Webpack plugin configuration, an author could pass their own function. Their passed function could be .toString()’d to comply with the reasons get-asset-code.ts mostly just exports a big function that templates JavaScript source code.

Maybe with a signature like this:

interface CustomRenderFunction {
  assets?: array<Asset>;
  nonce?: string;
  runtimeId?: string;
  out: {
    write: function;
    flush: function;
    end: function;
  }
}

interface Asset {
  path: string;
  type: 'css' | 'js';
  // etc…
}

Is this something you're interested in working on?

Yes

kentokage commented 3 years ago

<script defer> instead of <script async> on pages that don’t stream HTML

I second this in making this configurable, there should be an option for neither defer or async, where it fetches and executes immediately before the HTML begins the parse.

update: correction to my statement, I actually want neither defer or async, but would like this to be configurable.

DylanPiercey commented 3 years ago

@knyto2 i'm curious what the benefit you see to using defer (even in the non streaming case) over async?

kentokage commented 3 years ago

@DylanPiercey Sorry, I meant that there should be an option to not have either async or defer, where the the javascript will be fetched and executed immediately (which I'm assuming marko is beginning to stream in this case). As a result, the script elements are outside of html. As a workaround, I've added tags to force my component to be rendered inside the desired element. I see that in lasso, it is not loading the scripts async.

Also, shouldn't the CSS come before the JS assets? I am seeing a first paint issue where my components are being rendered, but looks the CSS is still coming over the wire, so it looks like html without the css.

DylanPiercey commented 3 years ago

Using async actually allows the Marko runtime to load immediately, and components initialize as they are streamed out. Script async does not effect blocking style sheets though, so what you are seeing must be from something else.

If we didn’t use async that’d mean flushing scripts at the end of the body which would mean they couldn’t be downloaded or evaluated until the entire document is ready. Using defer has the same issue, but the scripts don’t have to go at the end of the body. Using async tells the browser to download and execute right away without blocking the pages rendering, which is what the Marko runtime is designed to do.

DylanPiercey commented 3 years ago

I'm thinking that the Marko webpack plugin should expose hooks similar to HTMLWebpackPlugin that allow for changing the asset writing code.

tigt commented 3 years ago

Didn’t @mlrawlings have a branch doing pretty much exactly that, or am I misremembering?

DylanPiercey commented 3 years ago

I think you are misremembering. We have talked about doing that before though for other reasons.