Skip to content

Instantly share code, notes, and snippets.

@thepassle
Last active January 19, 2022 10:08
Show Gist options
  • Save thepassle/d46a7cabf2dd15ba3bb43397430e182f to your computer and use it in GitHub Desktop.
Save thepassle/d46a7cabf2dd15ba3bb43397430e182f to your computer and use it in GitHub Desktop.
css modules conclusion

Fwiw, the past couple of weeks I've been working on supporting import assertions and CSS/JSON modules in our app at work, I thought it might be worth to share the experience and also prompted by this, maybe its helpful to someone looking into doing the same.

We currently transform our app to Systemjs. My initial thought was to start using es-module-shims, for several reasons:

  • We use importmaps
  • It supports CSS/JSON modules
  • It would save a buildtime transformation to Systemjs

I initially thought that bundling from ESM to ESM would be at least somewhat noticeably faster than bundling from ESM to Systemjs, because of the code transform, but after running some benchmarks in our pipeline, it seemed to not make a significant difference.

image (5)

Then I went on to support import assertions in our rollup build. To do so, we had to add the @babel/plugin-syntax-import-assertions plugin to our build, because we still use some babel. No problems there.

I also had to add the acorn plugin Thomas linked to earlier, which works exactly as expected; but that then made rollup crash on trying to parse CSS syntax while expecting JS. The fix for this was to use rollup-plugin-import-assert, which works nicely (aside from some edgecases), but felt... wrong, since this rollup plugin just transforms the CSS to... JS in a similar way Justin commented earlier. CSS modules are a standard, so it feels like I shouldnt have to transform them to JS.

I also took a stab at implementing a different solution/rollup plugin to rollup-plugin-import-assert by leaving the css imports intact and just kind of try to bypass them, so that es-module-shims can take care of them instead at runtime, but I found that rollup rewrites the following import:

import styles from './styles.css' assert { type: 'css' };

To:

import styles from './styles.css';

Meaning that the import assertion never comes out the other side of the rollup build at all, and so will also never hit the browser. I didn't debug to see exactly where this was happening, and I also couldnt find a way in the plugin API to leave the import intact (even if the import was marked as external).

For now, we decided that in this case, such a build transformation perhaps wasnt the worst, since at least I wasnt adding non-standard syntax, and over time we can should be able to get rid of this transformation.

That, however, also meant that es-module-shims lost another one of its selling points for us; the support for CSS/JSON modules. That doesnt matter anymore now, because Rollup isnt able to handle CSS/import yet, and we transform it to JS instead.

This all means we now technically support import assertions in our build, but the import assertions themselves never make it to an actual browser, because the imports get transformed.

Thats just the buildstep though, I've not yet explored how to support import assertions for local development. Fortunately, we don't use typescript at work, but Tim van der Lippe pointed me to this issue on Chrome DevTools by Jack Franklin which points out that support will land in 4.5 which is currently in beta. ESLint also doesnt currently support it yet, I'm unaware if there is a plugin for ESLint to support it. Additionally, local development servers/demo environments will also have to adapt to support import assertions. Overall, it seems like a bit early to truly be able to support workflows with import assertions/css modules.

import { importAssertions } from 'acorn-import-assertions';
import fs from 'fs';
let count = 0;
const cssFilesCache = new Map();
export default {
input: 'index.js',
output: {
file: 'dist/bundle.js',
format: 'es'
},
acornInjectPlugins: [
importAssertions
],
plugins: [
{
resolveId(source, importer, options) {
if(source.endsWith('.css')) {
// @TODO css can be imported as bare module specifier as well
const cssFileLocation = new URL(source, `file:///${importer}`).pathname;
// We've already encountered this css file, we dont want duplicates
if(!cssFilesCache.has(cssFileLocation)) {
count++;
const name = `styles-${count}.css`;
cssFilesCache.set(cssFileLocation, name);
const css = fs.readFileSync(cssFileLocation, 'utf-8');
this.emitFile({
type: 'asset',
name,
fileName: name,
source: css
});
// This would output fine if the import assertion was left intact, e.g.:
// import styles from './styles-1.css' assert { type: 'css' };
// becomes
// import styles from './styles-1.css';
return {
id: `./styles-${count}.css`,
external: true
}
} else {
return {
id: `./${cssFilesCache.get(cssFileLocation)}`,
external: true
}
}
}
},
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment