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

Importing things that aren't JavaScript #2447

Closed
Rich-Harris opened this issue Sep 7, 2018 · 3 comments
Closed

Importing things that aren't JavaScript #2447

Rich-Harris opened this issue Sep 7, 2018 · 3 comments

Comments

@Rich-Harris
Copy link
Contributor

Feature Use Case

Loading non-JS assets, particularly CSS

Feature Proposal

Suppose I have the following modules:

// main.js
import('./foo.js').then(foo => {
  foo.addStyledDivToPage();
});
// foo.js
import './foo.css';

export function addStyledDivToPage() {
  const div = document.createElement('div');
  div.className = 'foo';
  document.body.appendChild(div);
}
/* foo.css */
.foo {
  color: red;
  font-family: 'Comic Sans MS';
  font-size: 4em;
}

What we want is for the styles defined in foo.css to be present on the page before the <div> is appended, to avoid a FOUC. We can do that with a plugin that turns foo.css into JavaScript...

/* foo.css, post-transformation */
import injectStyles from '\0css-transformer-helper';

injectStyles(`.foo {
  color: red;
  font-family: 'Comic Sans MS';
  font-size: 4em;
}`);

...but there are plenty of reasons why that's bad — most significantly, it's more JavaScript that the browser needs to parse and evaluate. Instead we should emit a .css file to the build directory and do something like this...

/* foo.css, post-transformation */
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'public/foo.xyz123.css';
document.querySelector('head').appendChild(link);

...but that results in FOUC.

How we're dealing with this in Sapper

Because Sapper apps have a well-defined structure, we can extract CSS in a way that mostly makes sense: we find the imported CSS for each chunk, concatenate the .css files, and manipulate a route manifest in such a way that when we load a new code-split chunk we can load the .css file at the same time, and delay rendering until both the JS and CSS are ready.

It works pretty well — we get code-split CSS with sourcemaps, and we can inject the right <link> tags when using server-side rendering — but it's nothing if not hacky. It has the following problems:

  • It's magical, and Sapper-specific
  • It doesn't play well with plugins that might, for example, transform the CSS (autoprefixing, optimising, what-have-you)
  • It doesn't work with CSS depended upon by dynamically-imported modules. We have to treat these as 'unaccounted for' and add them to a catch-all 'main' bundle
  • We have to use filthy string-replacement techniques to update the manifest file after the dependency graph has been created

It feels like this is something that should be dealt with somehow by Rollup.

A rough sketch of a solution

If it were possible to define runtime loaders somehow, the main.js chunk above could be compiled to this:

function loadCss(href) {
  return new Promise((fulfil, reject) => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    link.onload = () => fulfil();
    link.onerror = reject;
    document.querySelector('head').appendChild(link);
  });
}

Promise.all([import('./foo.xyz123.js'), loadCss('public/foo.xyz123.css')]).then(values => values[0]).then(foo => {
  foo.addStyledDivToPage();
});

I don't believe this is possible with the current plugin architecture.

Static imports

In the case where foo.js is imported statically rather than dynamically...

// main.js
import { addStyledDivToPage } from './foo.js';
addStyledDivToPage();

...we would end up with FOUC unless there were some way to know which CSS files corresponded to which entry point. More broadly, this manifest would be necessary for any form of SSR.

CSS chunks

Ideally, CSS would be combined and chunked up in the same way JavaScript modules are. I haven't thought long and hard about this but my starting assumption would be that the best solution is simply to create zero or one CSS chunks for each JavaScript chunk.

At this point, things start to get a little bit more complicated. Presumably Rollup needs to have some native understanding of CSS — of @import, and referenced assets like url('./image.jpg'), as well as an understanding of how to read and compose sourcemaps, potentially from CSS transformers. I don't know how much of this stuff can be delegated to plugins.

Beyond CSS

CSS is the obvious use case but there are others. It could be useful to be able to 'import' images, for example, or binary data (think of Glimmer's .gbx files).


This obviously isn't a fully fleshed out proposal yet — right now I'm just dumping the contents of my brain onto the page in hopes that other people have also been thinking about this stuff and have thoughts on a way forward.

@tivac
Copy link
Contributor

tivac commented Sep 7, 2018

The biggest change I'd like to see is for static assets to be able to be included into the dependency graph and chunked alongside JS so that a plugin could handle the transforms, emit dependencies for them, and then capture them during the output phase and turn the actual bundles into files on disk or whatever else it needs.

I'm not actually sure I want rollup knowing much about CSS in particular. I think a generic "non-JS" asset that still gets first-class bundling support would be more useful and allow plugins to flourish.

I of course am super-biased in that I have an existing CSS-based rollup workflow that I'm pretty happy with. Adding non-JS asset support would mean even less code on my side though which would rule.

@kzc
Copy link
Contributor

kzc commented Sep 10, 2018

This new asset plugin by @tsne is worth a look:

https://github.com/tsne/rollup-plugin-assetize

I'm not involved with the project, nor have I tried it.

tivac added a commit to tivac/modular-css that referenced this issue Jan 29, 2019
Currently the algorithm puts CSS into the first chunk that references it, but doesn't store that data anywhere so it's hard to get all of the CSS back correctly.

Better would be to do actual code-splitting like rollup. Better still would be for rollup to support assets attached to modules and going through the code-splitting paths like I asked for in rollup/rollup#2447
@shellscape
Copy link
Contributor

Hey folks. This is a saved-form message, but rest assured we mean every word. The Rollup team is attempting to clean up the Issues backlog in the hopes that the active and still-needed, still-relevant issues bubble up to the surface. With that, we're closing issues that have been open for an eon or two, and have gone stale like pirate hard-tack without activity.

We really appreciate the folks have taken the time to open and comment on this issue. Please don't confuse this closure with us not caring or dismissing your issue, feature request, discussion, or report. The issue will still be here, just in a closed state. If the issue pertains to a bug, please re-test for the bug on the latest version of Rollup and if present, please tag @shellscape and request a re-open, and we'll be happy to oblige.

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

4 participants