How to handle CSS clashes when rendering two frontend applications

Alejandro Arevalo
VoucherCodes Tech Blog
3 min readOct 5, 2022
Photo by Pankaj Patel on Unsplash

The VoucherCodes website has been around for approximately 14 years, so we’ve been through a fair few tech stack upgrades since then. Each tech stack migration comes with its own challenges to solve, meaning you have to support two systems simultaneously for a short time.

A current limitation of being in this state is having to create a Vue component twice for usage in both stacks. So recently, we decided to develop a header in our new stack and have our old stack request and render it. While there were many challenges, we thought we’d share how we handled CSS clashes when the browser essentially renders two different stacks.

The problem

The component we wanted to migrate will be developed in the most modern stack that includes TailwindCSS. The old stack where we needed to inject this component is using SCSS instead of TailwindCSS. This comes with an obvious problem, how does the old stack know which styles to apply when reading Tailwind classes?

After some discussion, we got to a few solutions, these ranged from copy/pasting the whole TailwindCSS file and adding it to the old application — not ideal — to the actual solution. What we settled on was to auto-generate a TailwindCSS file based only on the content of the components that we wanted, which would reduce the response size when requesting the header.

Generating the TailwindCSS file

Flow of the node script task

To generate a CSS file with only the Tailwind styles used by that component, we decided to create a script that would run on build and perform the following actions:

First, we create an array of filenames of the components used within the parent component and recursively iterate through the whole tree of components to fetch all children.

Then, we create a temporary copy of the “tailwind.config.js” file and replace the content property, usually an array of the files in our application, with the newly generated array of files.

const output = (
await readFileAsyncExtended(PATHS.temportalTailwindcssConfig))
.replace(/content: [^\]]*],/, `content: ${JSON.stringify(content)},`
);
await writeFileAsync(PATHS.temportalTailwindcssConfig, output);

After that, we use the TailwindCSS CLI to generate a CSS file using the new config. This would ensure it only generates classes used in the component and not the whole app.

execSync(`
npx tailwindcss
-i ${PATHS.tailwindcssSource}
-o ${PATHS.temportalTailwindcssFile}
--config ${PATHS.temportalTailwindcssConfig}
--no-autoprefixer
`);

Prefixing the classes

This was the most challenging part. If we tried to use that file in the old app, we would run into naming conflicts because some of the TailwindCSS classes had the same name as the ones we already had. So to avoid a collision, we called into action our friend PostCSS.

With PostCSS and the “postcss-prefix-selector” plugin, we added a prefix to all the TailwindCSS classes to ensure we don’t run into naming conflicts once the component is in the old stack.

Hashing

Once the CSS is prefixed and optimised, we just have to save that into a file and give it a name. Because we wanted to serve this file using a CDN, we needed to create a hash based on the file’s content. That would ensure we invalidate the cache once new content is generated.

const hash = createHash('md5').update(prefixedFinalCss).digest('hex');

Having to add a hash in the filename generated a new problem. How can we know the name of the file we want to request? To solve this, we used a manifest to store our hashed name and then sent that to the old stack.

await writeFileAsync(
PATHS.manifest,JSON.stringify({'header.css':tailwindcssOutputFile})
);

Now what?

After all that, we have to make sure we clean up after ourselves and delete any temporary files we created for this.
Finally, we stored the resultant file in a CDN and fetched it from the old application using the name saved in our manifest. Job done!

Have you heard? We’re hiring at VoucherCodes! Check out our careers page here.

--

--