Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable asset output #43

Open
tigt opened this issue Oct 25, 2020 · 7 comments
Open

Customizable asset output #43

tigt opened this issue Oct 25, 2020 · 7 comments

Comments

@tigt
Copy link
Contributor

tigt commented Oct 25, 2020

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

  • Hooking into the Webpack build process after the compiler-discovered component assets are output
  • Adding application-specific code around <init-components/> and <await-reorderer/> — I personally want to performance.mark() around them.
  • Serving modern vs. transpiled JS via <script type=module> and <script nomodule>
  • More-intelligent bundling of specific scripts based on heuristics/perf monitoring/etc.
  • The nonces aren’t always applicable — if script-src 'self' 'nonce-…', then non-inline scripts don’t need one. (That’s our CSP configuration.)
  • Sub-Resource Integrity
    • Different hash algorithms (currently SHA-256, 384, and 512); potentially dictated by regulation and security standards, so we can’t choose a one-size-fits-all algorithm
    • Multiple hash algorithms in a single integrity attribute, for future-proofing/fallbacks
  • Ordering CSS before JS and vice-versa
  • prefetch, preload, and modulepreload <link>s
  • Non-render-blocking CSS for components later in the page (usually involves <link rel=stylesheet>) midway through the <body>
  • <script defer> instead of <script async> on pages that don’t stream HTML
  • Inlining sufficiently-small scripts and styles
    • The old-fashioned way: <script>…</script> and <style>…</style>
    • A slightly fancier way for more scheduling control: <script defer src="data:application/javascript,…">
  • Loading other assets after @marko/webpack’s asset output, but before </head>
  • crossorigin attribute for preventing double-downloads, fine-tuning HTTP/2 coalescing with different origins
  • media attribute on <link>
  • Bundling component CSS/JS for older HTTP/1.1 connections
  • Deferring all JavaScript to experiment if it’s faster than the current method on certain devices/connections/pages/etc.
  • Lazy-loading stylesheets, async Webpack modules, etc.

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
Copy link

kentokage commented Dec 20, 2020

<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
Copy link
Contributor

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

@kentokage
Copy link

@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
Copy link
Contributor

DylanPiercey commented Dec 20, 2020

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
Copy link
Contributor

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

@tigt
Copy link
Contributor Author

tigt commented Dec 21, 2020

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

@DylanPiercey
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants