May 25, 2026

Avoiding empty JavaScript files in CSS-only entrypoints from rspack builds

2026-05-25

I previously wrote about my preferred way of integrating Django with rspack. Minor aspects have changed, but most of it still applies.

The largest change is probably that I have switched from HtmlWebpackPlugin to rspack’s own HtmlRspackPlugin and have stopped configuring the template generated by it. Instead, I’m pulling out link, script, and style tags the Cthulhu way. My own mistakes while configuring rspack have led to missing or duplicated assets. Letting rspack do its thing and post-processing the result feels safer.

The rest basically stayed the same.

Poking around in rspack’s compiler hooks isn’t documented all that well, so I’m documenting this plugin here. It’s very specific, but being able to change rspack’s output may be useful to others and to my future self.

When using a CSS-only entrypoint or when listing CSS files in entry, rspack sometimes decides it wants to generate an empty JavaScript file. This wouldn’t be a problem in and of itself, but since we’re also letting rspack generate the associated script tag, we will make the browser download this empty script file unnecessarily. I didn’t find a good way to avoid these empty files, and the RemoveEmptyScriptsPlugin for Webpack doesn’t work with rspack anymore. The discussion hasn’t gone anywhere yet. So, I had to use my own plugin. Since I’m already copying the rspack.library.js file into each project, adding a plugin there is no headache.

class RemoveEmptyJsAssetsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap(this.constructor.name, (compilation) => {
      const emptyJsFiles = new Set(
        Object.entries(compilation.assets)
          .filter(([name, asset]) => /\.js$/.test(name) && asset.size() === 0)
          .map(([name]) => name),
      )

      for (const file of emptyJsFiles) {
        delete compilation.assets[file]
      }

      for (const filename of Object.keys(compilation.assets)) {
        if (!filename.endsWith(".html")) continue
        let html = compilation.assets[filename].source()
        for (const jsFile of emptyJsFiles) {
          const idx = html.indexOf(jsFile)
          if (idx < 0) continue
          const start = html.lastIndexOf("<script", idx)
          const end = html.indexOf("</script>", idx) + "</script>".length
          html = html.slice(0, start) + html.slice(end)
        }
        compilation.assets[filename] = new rspack.sources.RawSource(html)
      }
    })
  }
}

The plugin has to be added to the plugins list:

plugins: [
  new RemoveEmptyJsAssetsPlugin(),
  // ...
],

And presto, no more empty JavaScript files!

Ideally, rspack will handle this itself at some point and make the plugin obsolete. For now, it works without issues in several projects.