esbuild
esbuild copied to clipboard
css import assertions
I am trying to get css import assertions to work. I found a comment that says to use --bundle --external:*.css --format=esm --target=esnext
, but that has the following issue:
the build folder is /build
. When I use those arguments, it seems to look for /build/Page.css
for example, instead of the actual path to the css, which is not in the build directory. That also isn't ideal from a production point of view, as the css does need to be copied to the build folder, not just left as an external resource. Am I going to have to write a custom handler that copies the css manually and renames it so it is unique and so on?
I ended up just creating a plugin. I need it for web components because if you just use a text loader for css, you have to append a script tag to every component instance, which is slow and unnecessary duplication. Some people seem to do what my plugin does, but in the web component constructor, which will get called every time a new instance is created so that is also a waste of resources. My plugin converts
import styles from './Page.css' assert { type: 'css' };
at build time by reading the css into a string and then creating a new CSSStyleSheet
with that string.
import esbuild from 'esbuild';
import { readFile } from 'fs/promises';
const plugin = {
name: 'css import assertions',
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const css = await readFile(args.path, 'utf8');
const contents = `
const styles = new CSSStyleSheet();
styles.replaceSync(\`${css.replaceAll(/[`$]/gm, '\\$&')}\`);
export default styles;`;
return { contents };
});
}
}
esbuild.build({
entryPoints: ['src/renderer.ts'],
watch: true,
sourcemap: true,
bundle: true,
plugins: [plugin],
outfile: 'build/renderer.js'
}).catch(() => process.exit(1));
I think the right way to do what the original post is asking for will be to use --loader:.css=copy
once it's implemented: #2255. Doing what your plugin ended up doing is different, but is also something esbuild should probably have a built-in way of doing. Using a plugin is appropriate for now though.
Yeah, the problem is that Safari doesn't support constructable style sheets.
This might be a separate issue, but is there any way the plugin API could get access to assertion metadata?
This might be a separate issue, but is there any way the plugin API could get access to assertion metadata?
No. The specification forbids import assertions from triggering any behavior changes. From the import assertion specification:
moduleRequest.[[Assertions]] must not influence the interpretation of the module or the module specifier; instead, it may be used to determine whether the algorithm completes normally or with an abrupt completion.
Import assertions are only supposed to be used to assert qualities of the import. So for example it would be a valid use of import assertions for esbuild to interpret assert { type: 'css' }
to mean "cause a build error if the loader isn't css
". Exposing import assertions to plugins would mean people would use import assertions to change import behavior, which directly violates the specification.
Since this issue was created, import assertions have been demoted from stage 3 (candidate for implementation) back to stage 2 (draft work in progress): https://github.com/tc39/proposal-import-attributes. So I’m closing ties issue as I don’t think esbuild should implement this anymore given that new development.
For what it’s worth I fully support their decision, and I’m happy they are reconsidering it. It was a shame to see them burn valuable syntax space for such a limited purpose. Hopefully their next iteration gives us something more powerful and flexible that’s intended for bundlers like esbuild to make better use of!
It's back at stage 3. Using with
again. Rollup added it as well: https://github.com/rollup/rollup/pull/4646. Worth reconsidering?
I don't think it's ready to implement, sorry. If I recall correctly, it's not just a simple switch from assert
to with
. I believe they also changed import ~~assertions~~ attributes to be part of the module identity. This means that importing the same module with different import attributes could potentially now import that module twice instead of once. From Rollup's PR description, it sounds like perhaps Rollup isn't implementing that part correctly (although there is a warning). I think given the history of this feature, it makes sense for esbuild to wait to implement this until it's implemented in a real JS environment. That way esbuild can match the behavior of a real implementation instead of just guessing how it should work.
@evanw
it makes sense for esbuild to wait to implement this until it's implemented in a real JS environment
This is now supported in Node 20.10.0+ and Deno 1.37+. Can it be reopened?
Do you mean like this? https://github.com/evanw/esbuild/releases/tag/v0.19.7 Support for bundling code that uses the with
keyword was already added to esbuild months ago. One of my motivations for shipping this was precisely that it's now supported in node.