<file_sep>import type { LoaderContext } from "webpack";

export interface PluginContext {
  setOutputCSS?: (css: string) => void
}

export interface ExtendedLoaderContext<T = any> extends LoaderContext<T>, PluginContext { }<file_sep>import chai from 'chai'
import sinonChai from 'sinon-chai'
import sinon from 'sinon'
import playwright from 'playwright'

chai.use(sinonChai)

let _browser: playwright.Browser

before(async () => {
  _browser = await playwright.chromium.launch();

  (global as any)._browser = _browser
})

after(async () => {
  await _browser.close()
})

beforeEach(async () => {
  sinon.reset()
})<file_sep># Example project React<file_sep>import sinon from 'sinon'

export const loaderContextWithoutOutputCSS = {
  resourcePath: '__path__.css',
}

export const loaderContext = {
  ...loaderContextWithoutOutputCSS,
  setOutputCSS: sinon.mock()
}
<file_sep># Writing the Loader
​
So what are we aiming for? First things first, we want to simply load our CSS from our JS.
​
Let's create our CSS file and call it `index.css`.
```css
.app {
	background: red;
}
```
​
And of course, use it in the index.js file:
```js
import React from 'react';
import { render } from 'react-dom';
import './index.css'
​
render(
  <div className="app"> Hello World! </div>,
  document.getElementById('root')
);
```
​
Run our application:
```bash
npm run serve
```
​
Now you probably see this error in the console:\
![Error](./assets/failed-to-load-css-error.png)\
​
This error makes a lot of sense, as webpack does not know how to handle CSS imports and we need to tell it how.
​
## Creating Webpack Loader
​
### What are loaders?
​
*webpack enables the use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.*
​
To put it simply, in our case, they are functions that take the CSS file as input and output a js file.
_**CSS -> JS**_
​
### Loader implementation
​
Let's create a file parallel to the `webpack.config.js` named `loader.js`.
Our goal is to append the style value we get from the CSS file inside the dom.
​
`loader.js`:
```js
​
// Appending the style inside the head
function appendStyle(value) {
    const style = document.createElement('style');
    style.textContent = value;
    document.head.appendChild(style);
}
​
// Make sure it is not an arrow function since we will need the `this` context of webpack
function loader(fileValue) {
  // We stringify the appendStyle method and creating a file that will invoked with the css file value in run time
  return `
    (${appendStyle.toString()})(${JSON.stringify(fileValue)})
  `
}
​
module.exports = loader;
```
​
Now we need to register it inside the webpack config.
​
`webpack.config.js`:
```js
const config = {
  //... rest of the config
    module: {
        rules: [
          // ... other rules not related to CSS
            {
                test: /\.css$/,
                loader: require.resolve('./loader')
            }
        ]
    }
  // ...
}
```
Restart the terminal, and we got it! 🎊\
![Loader success](./assets/loader-out-result.png)\
​
​
### What’s happening behind the scenes?
​
Webpack sees your CSS import inside `index.js`. It looks for a loader and gives it the JavaScript value we want to evaluate in runtime.
​
## Overcoming the `global` issue
​
Now we have our style, but everything is global. Every other language solves the global issue with scoping or namespacing.
​
We will implement the namespace solution. This is going to give us scoping, and each file going to have its own namespace.
​
For example, our import is going to look like this:
```
AppComponent123__myClass
```
​
If another component has the same class name, it won't matter behind the scenes since the namespace will be different.
​
Let's go the `loader.js` and add the following method:
```js
​
const crypto = require('crypto');
​
/**
 * The name is the class we are going to scope, and the file path is the value we are going to for the namespacing.
 * 
 * The third argument is the classes, a map that points the old name to the new one.
 */
function scope(name, filepath, classes) {
  name = name.slice(1); // Remove the dot from the name.
  const hash = crypto.createHash('sha1'); // Use sha1 algorithm.
  hash.write(filepath); // Hash the filepath.
  
  const namespace = hash.digest('hex').slice(0, 6); // Get the hashed filepath.
  const newName = `s${namespace}__${name}`;
​
  classes[name] = newName; // Save the old and the new classes.
​
  return `.${newName}`
}
```
​
After we are done scoping the class, let's return the loader method.
We need a way to connect the scoped class selector to the user's javascript code.
​
```js
function loader(fileValue) {
  const classes = {}; // Map that points the old name to the new one.
  const classRegex = /(\.([a-zA-Z_-]{1}[\w-_]+))/g; // Naive regex to match everything that start with dot.
  const scopedFileValue = fileValue.replace(classRegex, (name) => scope(name, this.resourcePath, classes)); // Replace the old class with the new one and add it to the classes object
​
 // Change the fileValue to scopedFileValue and export the classes.
  return `
    (${appendStyle.toString()})(${JSON.stringify(scopedFileValue)})
​
    export default ${JSON.stringify(classes)}
  ` // Export allows the user to use it in his javascript code
}
```
​
In the `index.js`, we can now use it as an object:
​
```js
import React from 'react';
import { render } from "react-dom";
import classes from './index.css'; // Import the classes object.
​
render(
  <div className={classes.app}>
    Hello World
  </div>,
  document.getElementById('root')
)
```
​
Now it works with namespaced selector 🎉\
![Class with namespaced selector](./assets/loader-namespaced-selector.png)\
​
<mark>Some important points about the changes we implemented.</mark>
​
* When the loader is used by webpack, the context will be the loader context (`this`) from webpack. You can read more about it [here](https://webpack.js.org/api/loaders/). It provides the resolved file path, which makes the namespace unique to the file.
​
* The way we extract the classes selectors from the CSS file is a naive implementation that isn't taking into account other use cases. The ideal way is to use a CSS parser.
* `this.resourcePath` refers to the local path, which means that in other machines, the path may look different.
​
The loader is now implemented, and we've got scoped classes at this point. However, everything is loaded from JavaScript, and so it is not yet possible to cache the CSS.
​
To do this, we will need to compose all the CSS into one file, and to do that, we will need to create a webpack plugin.
​
## [Next chapter](./css-plugin.md)
### [Previous chapter](./setup-the-solution.md)
<file_sep># CSS Webpack Plugin - Style the modern web
​
_Styling a modern application is no simple task - traditionally it is done by serving HTML with CSS for styling, and sprinkling the web app with Javascript to get the job done.
The question is how to modernize the approach of setting up an app, and the answer is to use a bundler like Webpack and JavaScript framework / library like React. But how do we handle the **CSS**, and why is it not that simple as you would expect?_

## Agenda - 
* [Part 1: Understanding the issue with native CSS.](./docs/native-css-issue.md)
* [Part 2: Setting up our webpack application without a CSS plugin.](./docs/setup-the-solution.md)
* [Part 3: Writing the Loader.](./docs/css-loader.md)
* [Part 4: Writing advanced Plugin.](./docs/css-plugin.md)
​
> If you are here just for implementation information, skip to part 3.
​
> Disclaimer - This is not a production-ready plugin. To see one that is, check out what my team and I are working on: [Stylable](https://stylable.io/)
​
## Development
​
This project is written as a Yarn workspace with TypeScript, and contains two main packages:
​
```
- css-loader
- css-plugin
```
​
For examples of different frameworks, see [`examples`](./examples).
​
### Bootstrap
​
```bash
yarn
yarn build
```
​
### Testing
​
The package `css-loader` has integration tests to ensure the correct output is received from the loader.
​
Inside the `css-plugin` package is an e2e test that creates a project, serves it with playwright, and makes sure that the UI works according to the output.
<file_sep>import { createApp } from 'vue';
import App from './app.vue';

const root = document.createElement('div')
root.id = 'root'

document.body.appendChild(root);

createApp(App).mount('#root')<file_sep>import { cssLoader } from './loader'

export * from './types';
export default cssLoader<file_sep>export const transformCSSClasses = {
  css: `.root { color: red; } div .label { font-size: 1rem; }`,
  expectedCSS: `.s7ad3f4__root { color: red; } div .s7ad3f4__label { font-size: 1rem; }`,
  expected: `\n\n  export default {"root":"s7ad3f4__root","label":"s7ad3f4__label"}`,
} as const

export const transformAndAddStyleScript = {
  css: `.root { color: red; } div .label { font-size: 1rem; }`,
  expected: `(function appendStyle(css) {\n    const style = document.createElement('style');\n    style.textContent = css;\n    document.head.appendChild(style);\n})(".s7ad3f4__root { color: red; } div .s7ad3f4__label { font-size: 1rem; }")\n\n  export default {"root":"s7ad3f4__root","label":"s7ad3f4__label"}`,
} as const
<file_sep>import { expect } from 'chai'
import { BASE_URL, getSimpleProject } from '../fixtures/simple-project'

describe('simple-project', () => {
  const project = getSimpleProject().beforeAndAfter()

  it('should serve simple project and find only css links tags', async () => {
    await project.startProject({ waitMatcher: new RegExp(`Accepting connections at ${BASE_URL}`) })

    const web = await project.initWebDriver()
    await web.goto(BASE_URL)

    expect(await web.styles()).to.deep.equal([])
    expect(await web.links()).to.deep.equal([{ href: `${BASE_URL}/main.css`, rel: 'stylesheet' }])
  })
})
<file_sep>export function appendStyle(css: string) {
  const style = document.createElement('style')

  style.textContent = css;

  document.head.appendChild(style)
}<file_sep>import cssLoader from '@webpack-css/loader'
import { expect } from 'chai'
import { loaderContext, loaderContextWithoutOutputCSS } from './mocks'
import { transformCSSClasses, transformAndAddStyleScript } from './fixtures/css-loader'

describe('cssLoader', () => {
  let ctx: any
  beforeEach(() => {
    ctx = loaderContext as any
  })

  it('should transform css classes', () => {
    const { expected, css, expectedCSS } = transformCSSClasses;

    expect(cssLoader.call(ctx, css)).to.equal(expected)
    expect(loaderContext.setOutputCSS).to.have.been.calledWith(expectedCSS)
  })

  it('should transform and add style script', () => {
    ctx = loaderContextWithoutOutputCSS
    const { expected, css } = transformAndAddStyleScript;

    expect(cssLoader.call(ctx, css)).to.equal(expected)
    expect(loaderContext.setOutputCSS).to.not.have.been.called
  })
})
<file_sep>{
  "extends": "./tsconfig.base.json",
  "files": [],
  "compilerOptions": {
    "types": [
      "node",
      "mocha",
      "chai",
    ]
  },
  "references": [
    {
      "path": "./packages/css-loader/src"
    },
    {
      "path": "./packages/css-loader/test"
    },
    {
      "path": "./packages/css-plugin/src"
    },
    {
      "path": "./packages/css-plugin/test"
    }
  ]
}<file_sep># Example project Vue<file_sep>import type { WebpackPluginInstance, Compiler, Compilation, NormalModule } from 'webpack'
import type { ExtendedLoaderContext } from '@webpack-css/loader'
import type { ICSSPluginOptions } from './types'
import { injectLoader } from './inject-loader'


export class CSSPlugin implements WebpackPluginInstance {
  private compiler!: Compiler
  private cssMap = new Map<string, string>()
  private options?: Required<ICSSPluginOptions>

  constructor(
    private readonly usersOptions: ICSSPluginOptions = {},
    private readonly injectLoader = true
  ) { }

  apply(compiler: Compiler) {
    this.compiler = compiler;

    if (this.injectLoader) {
      injectLoader(this.compiler)
    }

    this.compiler.hooks.afterPlugins.tap(CSSPlugin.name, this.createOptions.bind(this, this.usersOptions))
    this.compiler.hooks.thisCompilation.tap(CSSPlugin.name, this.handleCompilation.bind(this))
  }

  private handleCompilation(compilation: Compilation) {
    this.compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap(
      CSSPlugin.name,
      this.handleLoader.bind(this)
    )

    compilation.hooks.processAssets.tap(
      {
        name: CSSPlugin.name,
        stage: this.compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED
      },
      this.handleProcessAssets.bind(this, compilation)
    )
  }

  private handleLoader(webpackLoaderContext: object, module: NormalModule) {
    const ctx = webpackLoaderContext as ExtendedLoaderContext<any>;
    if (this.getOptions().inject === 'css') {
      ctx.setOutputCSS = (css: string) => {
        this.cssMap.set(module.resource, css)
      }
    }
  }

  private handleProcessAssets(compilation: Compilation) {
    if (this.getOptions().inject !== 'css') return;

    let assetName: string = this.getOptions().filename!

    for (const [name, entry] of compilation.entrypoints) {
      assetName ||= `${name}.css`
      entry.getEntrypointChunk().files.add(assetName)
    }

    const asset = new this.compiler.webpack.sources.RawSource(this.getStaticCSS(), false)
    compilation.emitAsset(assetName, asset)
  }

  private getStaticCSS() {
    let raw = ''
    for (const [path, css] of this.cssMap) {
      raw += `/* ${path} */\n${css}\n`
    }

    return raw
  }

  private createOptions(defaults: ICSSPluginOptions) {
    this.options = {
      inject: defaults.inject ?? this.compiler.options.mode === 'production' ? 'css' : 'js',
      filename: defaults.filename || ''
    }
  }

  private getOptions() {
    return this.options || this.usersOptions
  }
}
<file_sep>export interface ICSSPluginOptions {
  inject?: 'css' | 'js'
  filename?: string
}<file_sep>import { mkdir, writeFile } from 'fs/promises'
import { resolve } from 'path'

export async function createFiles(rootDir: string, files: Dir) {
  await mkdir(rootDir, { recursive: true })

  for (const [fileName, fileValue] of Object.entries(files)) {
    const currentPath = resolve(rootDir, fileName)
    if (typeof fileValue === 'string') {
      await writeFile(currentPath, fileValue)
    } else if (Boolean(fileValue) && typeof fileValue === 'object') {
      await createFiles(currentPath, fileValue)
    }
  }
}

export type Dir = {
  [name: string]: string | Dir
}<file_sep>export * from './create-files'
export * from './exec'
export * from './get-package'
export * from './setup-test'<file_sep>export const getPackageJSON = (defaults: object) => JSON.stringify(({
  ...defaults,
  "name": "example" + Math.random().toString(36).slice(2),
  "version": "1.0.0",
  "main": "index.js",
  "private": true,
  "license": "MIT",
  "scripts": {
    "clean": "rm -rf dist",
    "start": "webpack serve --open",
    "build": "webpack --mode=production --node-env=production",
    "build:dev": "webpack --mode=development",
    "build:prod": "webpack --mode=production --node-env=production",
    "watch": "webpack --watch",
    "serve": "serve ./dist -l 5000"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}))<file_sep>import { Dir, getPackageJSON, setup } from '../utils';

export const BASE_URL = 'http://localhost:5000';

export const getSimpleProject = () => {
  const name = 'simple-project';
  const files: Dir = {
    'package.json': getPackageJSON({ name }),
    src: {
      'index.css': `.root { background: red; }`,
      'index.tsx': `
      import classes from './index.css';
      import ReactDOM from 'react-dom';
      
      
      ReactDOM.render(
        <div className={classes.root} >Hello</div>,
        document.body.appendChild(document.createElement('main'))
      )
      `
    },
    'webpack.config.js': `

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CSSPlugin } = require('@webpack-css/plugin')
    
    module.exports = {
        mode: 'development',
        devtool: 'source-map',
        entry: './src/index.tsx',
        module: {
            rules: [
                {
                    test: /.tsx?$/,
                    loader: 'ts-loader',
                    options: {
                        transpileOnly: true
                    }
                },
                {
                    test: /.(png|jpg|jpeg|gif|svg)$/,
                    type: 'asset',
                },
            ],
        },
        resolve: {
            extensions: ['ts', 'tsx', '.js', '.json'],
        },
        plugins: [new CSSPlugin(), new HtmlWebpackPlugin()],
    };    
`
  }

  return setup({ files })
}<file_sep>
# Understanding the issue with native CSS.

## Our options

Native CSS is implemented in different ways:

* The first and the most simple way to include CSS is using inline styling, which means that you explicitly include a style in an HTML tag. `<span style="color:red;">...</span>`

* A different solution is to use an HTML tag called `<style>...</style>`, where its text content is the style itself, and it is used to select the different HTML elements.

* Another option is to load a CSS file via a link tag, and select the different HTML elements inside the file.

## The problems

Each one of the solutions above has its benefits and tradeoffs. It is very important to understand them to avoid unexpected behavior in your styling. You'll find that none of those solutions, however, solve one of the most problematic issues - that **CSS is global**.

The global issue is a pretty difficult one to overcome. Let's say you have a button with a class called `btn` and you style it. One day your co-worker works on a different page that has a button too, and he also decided to call it `btn`. The problem should be apparent - the styles would clash.

Another significant issue is **specificity**, where the specificity is equal between selectors, and the last declaration found in the CSS is applied to the element. To put it simply - your order matters.

## [Next chapter](./setup-the-solution.md)<file_sep>declare module '*.css' {
  const defaultExport: Record<string, string>;
  export default defaultExport;
}<file_sep>import { createHash } from 'crypto'

export function resolveNamespace(filepath: string) {
  const h = createHash('sha1')

  h.write(filepath)

  return 's' + h.digest('hex').slice(0, 6)
}<file_sep>module.exports = {
  'enable-source-maps': true,
  file: ['./mocha.setup.ts'],
  timeout: 20000
};<file_sep>import { transform } from './transform';
import { appendStyle } from "./append-style";
import type { ExtendedLoaderContext } from './types';

export function cssLoader(this: ExtendedLoaderContext<any>, content: string): string {
  let injectCssInJs = true
  const { classesMap, css } = transform(this.resourcePath, content)

  if (this.setOutputCSS) {
    injectCssInJs = false
    this.setOutputCSS(css)
  }

  return `${injectCssInJs ? `(${appendStyle})(${JSON.stringify(css)})` : ''}

  export default ${JSON.stringify(Object.fromEntries(classesMap.entries()))}`
}<file_sep>import postcss from 'postcss'
import selectorParser from 'postcss-selector-parser'
import { resolveNamespace } from './resolve-namespace'

export function transform(path: string, content: string) {
  const process = postcss.parse(content, { from: path })
  const classesMap = new Map()

  process.walkRules(node => {
    const ruleAst = selectorParser().astSync(node);

    ruleAst.walkClasses(selectorAst => {
      if (!classesMap.has(selectorAst.value)) {
        classesMap.set(selectorAst.value, `${resolveNamespace(path)}__${selectorAst.value}`)
      }

      selectorAst.value = classesMap.get(selectorAst.value)
    })

    node.selector = ruleAst.toString()
  })

  const css = process.toString()

  return {
    css,
    classesMap
  }
}<file_sep>import type { Browser, Page, BrowserContext } from 'playwright';

export class Driver {
  private page!: Page
  private context!: BrowserContext
  currentUrl: string | undefined

  private constructor(
    private browser: Browser
  ) { }

  static create(browser: Browser) {
    return new this(browser).init()
  }

  async init() {
    this.context = await this.browser.newContext()
    this.page = await this.context.newPage()

    return this
  }

  async goto(url: string) {
    this.currentUrl = url;
    
    return this.page.goto(url)
  }


  async links() {
    return await this.page.$$eval('link', links => links.map(link => ({
      href: link.href,
      rel: link.rel
    })))
  }

  async styles() {
    return await this.page.$$eval('style', styles => styles.map(s => s.textContent))
  }
}<file_sep># Setting up our webpack application without a CSS plugin.

## The solutions

Currently, there are many different solutions to these problems, from utility frameworks, CSS preprocessors, and others that all try to help with the issues that native CSS has.

In this article, I would like to solve some of those problems from scratch with you.

Let's set up our environment real quick. To do this, run these commands:

```bash
mkdir example-css-plugin
cd example-css-plugin
npm init -y
npm i -D webpack webpack-cli @webpack-cli/generators @babel/preset-react
npm i react react-dom
```

When the development dependencies have finished installing, run the webpack init command:

```bash
npx webpack init
```

For our setup, your answers should look like this: 

```
? Which of the following JS solutions do you want to use? ES6
? Do you want to use webpack-dev-server? Yes
? Do you want to simplify the creation of HTML files for your bundle? Yes
? Do you want to add PWA support? No
? Which of the following CSS solutions do you want to use? none
? Do you like to install prettier to format generated configuration? No
```

### **Make sure that this question is answered as such:  "Which of the following CSS solutions do you want to use" - none.**

## Configure React

Go to `.babelrc` and make sure that the presets array includes "@babel/preset-react".

```json
{
    "plugins": ["@babel/syntax-dynamic-import"],
    "presets": [
        [
            "@babel/preset-env",
            {
                "modules": false
            }
        ],
            "@babel/preset-react"
    ]
}
```

Now we need to go to `index.html` and make sure it has div with root id.

```html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>CSS Webpack Plugin example</title>
    </head>
    <body>
        <div id="root"></div>
    </body>    
</html>
```

After all of that, we are ready to write our app inside `src/index.js`:
```js
import React from 'react';
import { render } from "react-dom";

render(
  <div>
    Hello World
  </div>,
  document.getElementById('root')
)
```

## [Next chapter](./css-loader.md)
### [Previous chapter](./native-css-issue.md)<file_sep>
# Writing an advanced Plugin
​
As said, we implemented a loader that can inject CSS into our page. What if we want to do it with a signal file and not an injection, however?
​
Loading CSS as a file comes with many benefits, and the best of them is caching. The browser can cache the file and won't need to redownload it every time it is needed.
​
This operation is more complicated than the loader case since we will have more context about the webpack bundling process.
​
### What is a plugin?
​
*A webpack plugin is a JavaScript object that has an [apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) method. This apply method is called by the webpack compiler, giving access to the entire compilation lifecycle.*
​
​
## Creating the Plugin
​
Let's create a file called `plugin.js`, and create the plugin skeleton: 
​
```js
​
class CSSPlugin {
  cssMap = new Map() // We will save here the CSS content
​
  /**
   * Hook into the compiler
   * @param {import('webpack').Compiler} compiler 
   */
  apply(compiler) { }
}
​
module.exports = {
  CSSPlugin
}
​
```
​
​
Now let's implement the apply method: 
​
```js 
class CSSPlugin {
  cssMap = new Map() // We will save here the CSS content
​
  /**
   * Hook into the compiler
   * @param {import('webpack').Compiler} compiler 
   */
  apply(compiler) {
​
    // Hook into the global compilation.
    compiler.hooks.thisCompilation.tap('CSSPlugin', (compilation) => {
​
      // Hook into the loader to save the CSS content.
      compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap(
        'CSSPlugin',
        (context, module) => {
​
          // Setting up a method on the loader context that we will use inside the loader.
          context.setOutputCSS = (css) => {
​
            // the key is the resource path, and the CSS is the actual content.
            this.cssMap.set(module.resource, css)
          }
        }
      )
    })
   }
}
```
​
​
We hooked into the global compilation and then hooked into the loader (which was implemented in the previous chapter).
​
When the loader content is reachable, we add the `setOutputCSS` method to call it from the loader.
​
Here's how to call this method in `loader.js`:
​
```js
function loader(fileValue) {
  const classes = {}; // Map that points the old name to the new one.
  const classRegex = /(\.([a-zA-Z_-]{1}[\w-_]+))/g; // Naive regex to match everything that start with dot.
  const scopedFileValue = fileValue.replace(classRegex, (name) => scope(name, this.resourcePath, classes)); // Replace the old class with the new one and add it to the classes object
​
  this.setOutputCSS(scopedFileValue) // Pass the scoped CSS output
​
 // Export the classes.
  return `export default ${JSON.stringify(classes)}`
}
​
```
​
As you can see, we are not appending the style in the JavaScript. We use the method we added to the context.
​
After collecting all the scoped CSS content, we now need to hook into the asset process hook to let the compiler know that we have a new asset that it should handle.
​
Let's add it to the `apply` method:
​
```js
class CSSPlugin {
  // ...
​
  apply(compiler) {
      compiler.hooks.thisCompilation.tap(
        'CSSPlugin', 
        (compilation) => {
        // ...
​
        // Hook into the process assets hook
        compilation.hooks.processAssets.tap(
          {
            name: 'CSSPlugin',
            stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED
          },
          () => {
​
                // Loop over the CSS content and add it to the content variable
                let content = '';
                for (const [path, css] of this.cssMap) {
                  content += `/* ${path} */\n${css}\n`;
                }
​
                // Append the asset to the entries.
                for (const [name, entry] of compilation.entrypoints) {
                  assetName = `${name}.css`;
                  entry.getEntrypointChunk().files.add(assetName);
                }
​
                // Create the source instance with the content.
                const asset = new compiler.webpack.sources.RawSource(content, false);
​
                // Add it to the compilation
                compilation.emitAsset(assetName, asset);
          }
      )
  }
}
```
​
Now we'll run the build command:
​
```bash
npm run build
```
​
We should see `main.css` in the output folder, and also injected into the HTML:\
Output:\
![Output](./assets/css-plugin-output.png)\
`index.html`:\
![HTML File](./assets/css-plugin-html-file.png)\
And that's it!\
We finished the Plugin and have one CSS file for all of the CSS.
​
Note that we skipped dependencies, graph ordering, and filtering unused CSS for demonstration purposes.
​
You can see my full implementation with typescript and tests, [here](../README.md) in this repo.
​
If you have any question you can reach me via [linkedin](https://www.linkedin.com/in/tzach-bonfil-21b822187/). I hope I managed to help you. Have a good one!
​
### [Previous chapter](./css-loader.md)
<file_sep>import { spawn, SpawnOptions } from 'child_process';

interface ExecOptions extends SpawnOptions {
  processKiller?: Set<Function>
}

export function exec(command: string, options?: ExecOptions) {
  let totalData = ''
  let resolve: Function

  const emit = () => new Promise((res, _rej) => {
    options?.processKiller?.add(kill)

    resolve = () => {
      res(undefined)
      process.kill()
    };

    const process = spawn(
      command.split(' ').shift()!,
      command.split(' ').slice(1),
      options || {}
    )

    process.stderr!.on('data', (err: Buffer) => _rej(new Error(err.toString())))
    process.stdout!.on('close', () => {
      res(totalData)
      options?.processKiller?.delete(kill)
    })
    process.stdout!.on('data', (data: Buffer) => {
      totalData += data.toString()
    })

  })

  const waitForOutput = (stringOrRegex: string | RegExp) => new Promise((res, rej) => {
    const timeout = setTimeout(() => {
      rej(new Error(`waitForOutput: Timeout waiting for ${stringOrRegex}`))
      clearInterval(interval)
      clearTimeout(timeout)
    }, 10000);

    const interval = setInterval(() => {
      if (typeof stringOrRegex === 'string' ? totalData.includes(stringOrRegex) : stringOrRegex.test(totalData)) {
        res(undefined)
        clearInterval(interval)
        clearTimeout(timeout)
      }
    }, 50)
  })

  const kill = () => {
    resolve()
  }

  return {
    emit,
    kill,
    waitForOutput
  }
}<file_sep>import { dirname, resolve } from 'path'
import type { Browser } from 'playwright'
import rimraf from 'rimraf'
import { promisify } from 'util'
import { createFiles, Dir } from './create-files'
import { exec } from './exec'
import { Driver } from './page-driver'

interface SetupOptions {
  files: Dir
}

export function setup(
  {
    files
  }: SetupOptions
) {
  let _driver: Driver | undefined;
  const processKiller = new Set<Function>()
  const rootDir = resolve(
    dirname(dirname(dirname(require.resolve('@webpack-css/plugin')))),
    `temp-${Math.random().toString(36).slice(2)}`
  )

  const beforeAndAfter = () => {
    beforeEach(async () => {
      await createFiles(rootDir, files)
    })

    afterEach(async () => {
      await promisify(rimraf)(rootDir)

      for (const kill of processKiller) {
        kill()
      }
    })

    return payload
  }

  const run = (command: 'build' | 'start' | 'test' | 'install' | 'serve') => exec(
    `yarn ${command}`,
    { cwd: rootDir, processKiller }
  )

  const browser = () => (global as any)._browser as Browser

  const startProject = async (
    {
      waitMatcher
    }: {
      waitMatcher?: string | RegExp
    } = {}
  ) => {
    await run('install').emit()
    await run('build').emit();
    const serveProcess = run('serve');

    serveProcess.emit()

    if (waitMatcher) {
      await serveProcess.waitForOutput(waitMatcher)
    }
  }

  const webDriver = () => {
    return _driver
  }

  const initWebDriver = async () => {
    _driver = await Driver.create(payload.browser())

    return payload.webDriver()!
  }

  const payload = {
    files,
    rootDir,
    beforeAndAfter,
    run,
    browser,
    startProject,
    processKiller,
    webDriver,
    initWebDriver
  }

  return payload
}
<file_sep>import type { Compiler } from 'webpack';

export function injectLoader(compiler: Compiler) {
  if (!compiler.options.module.rules) {
    compiler.options.module.rules = [];
  }
  compiler.options.module.rules.unshift({
    test: /\.css$/,
    loader: require.resolve('@webpack-css/loader'),
    sideEffects: true,
  });
}
<file_sep>const { CSSPlugin } = require('@webpack-css/plugin')
const { VueLoaderPlugin } = require("vue-loader");
const HtmlWebpackPlugin = require("html-webpack-plugin");

/** @type {import('webpack').Configuration} */
module.exports = {
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: "vue-loader",
            },
            {
                test: /\.svg$/,
                type: 'asset'
            },
        ],
    },
    plugins: [
        new CSSPlugin(),
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
    ],
};