# Build configuration plugins

ScandiPWA introduces concept of "build plugins". **This is a feature of** [**ScandiPWA extensions**](https://docs.create-scandipwa-app.com/extensions/extensions).&#x20;

{% hint style="info" %}
**To create a build plugin** - means to create extend the application build configuration. The build plugins can modify: [Webpack](https://webpack.js.org/), [Webpack dev-server](https://webpack.js.org/configuration/dev-server/), [Craco](https://github.com/gsoft-inc/craco/) configurations. You can also provide commands to run before starting the build.
{% endhint %}

## To create a build plugin

{% tabs %}
{% tab title="Instruction" %}

1. Define an empty object as a value of the `scandipwa.build` field in your extension's `package.json`
   1. Populate the configuration object with `cracoPlugins` field, set it to empty array.
2. Create a new folder in your extension's root, name it `build-config`:
   1. Create a new file in it, name it to represent it's build modification purpose
   2. Populate an array of `cracoPlugins` in your `package.json` with a path to your new file (relative to the extension root).
3. Inside of your plugin file, define a `module.exports` equal to an object. This object may have two keys:
   1. `plugin` (*required*) where the plugin implementation will be defined
   2. `options` (*optional*) where the plugin options will be defined
4. Implement a plugin in the object created at the step 3). To do it, follow [these guidelines](#build-plugin-implementation-syntax).

{% hint style="info" %}
You can also use the existing Craco extensions. You can find [the list of them here](https://github.com/gsoft-inc/craco#community-maintained-plugins). You can import them and use as the plugin implementation passing into the `plugin` key value on the step 3).
{% endhint %}
{% endtab %}

{% tab title="Example" %}
This plugin implements a Create ScandiPWA App compilation into valid Magento 2 theme. We plan to modify the Webpack and Craco configurations.

Following instruction steps 1) and 2) we created following entry in our `package.json`:

```javascript
{
    "scandipwa": {
        "type": "extension",
        "build": {
            "cracoPlugins": [
                "build-config/magento.js"
            ]
        }
    },
    ...
}
```

The new file `magento.js` was created in `build-config` folder in our extension root.

In our `magento.js` plugin file, we define a `module.exports` an object, with plugin key, where we implement extensions of Webpack and Craco configurations. We make sure:

* The `appBuild` should is set to `magento/Magento_Theme`.
* The `appHtml` to `public/index.php`.
* The options field `filename` of the `HtmlWebpackPlugin` is set to `../templates/root.phtml`.
* The `file-loader` is excluding the `.php` from processing, so it does not appear in the build folder along-side of others application assets.

{% hint style="info" %}
We used `getLoader` and `loaderByName` utility functions of Craco. Learn more about them in [official Craco guide](https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.md#utility-functions).
{% endhint %}

{% hint style="warning" %}

### Heads up!

Create ScandiPWA App uses the custom fork of Craco called `@scandipwa/craco`.
{% endhint %}

The final file will look something like this:

```javascript
const path = require('path');
const FallbackPlugin = require('@scandipwa/webpack-fallback-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { sources } = require('@scandipwa/scandipwa-scripts/lib/sources');
const { getLoader, loaderByName } = require('@scandipwa/craco');

module.exports = {
    plugin: {
        overrideCracoConfig: ({
            cracoConfig
        }) => {
            // For Magento, use magento/Magento_Theme folder as dist
            cracoConfig.paths.appBuild = path.join(process.cwd(), 'magento', 'Magento_Theme', 'web');

            // For Magento use PHP template (defined in /public/index.php)
            cracoConfig.paths.appHtml = FallbackPlugin.getFallbackPathname('./public/index.php', sources);

            // Always return the config object.
            return cracoConfig;
        },
        overrideWebpackConfig: ({ webpackConfig }) => {
            // For Magento setup, change output file name
            webpackConfig.plugins.forEach((plugin) => {
                if (plugin instanceof HtmlWebpackPlugin) {
                    plugin.options.filename = '../templates/root.phtml';
                }
            });

            const { isFound: isFileLoaderFound, match: fileLoader } = getLoader(
                webpackConfig,
                loaderByName('file-loader')
            );

            if (isFileLoaderFound) {
                // Add .php to ignore files (otherwise php will compile into /media as file)
                fileLoader.loader.exclude.push(/\.php$/);
            }

            return webpackConfig;
        }
    }
};
```

{% endtab %}
{% endtabs %}

## To define a before run script

{% tabs %}
{% tab title="Instruction" %}

1. Define an empty object as a value of the `scandipwa.build` field in your extension's `package.json`
   1. Populate the configuration object with `before` field, set it to empty string.
2. Create a new folder in your extension's root, name it `build-config`:
   1. Create a new file in it, name it to represent before-run script's purpose
   2. Set the `before` field in your `package.json` equal to a path to your new file (relative to the extension root).
3. Inside of your plugin file, define a `module.exports` equal to a function.
4. Implement a script based on your needs. No guidelines here, it's up to you!
   {% endtab %}

{% tab title="Example" %}
The purpose of our script is to make sure there is a file named `composer.json` in the root current theme.

Following instruction steps 1) and 2) we created following entry in our `package.json`:

```javascript
{
    "scandipwa": {
        "type": "extension",
        "build": {
            "before": "build-config/composer.js"
        }
    },
    ...
}
```

The new file `composer.js` was created in `build-config` folder in our extension root.

Inside of this file we will use the `process.cwd()` to determine the location of current theme. We will use `process.exit()` to exit the program and stop build from running.

The final file will look something like this:

```javascript
const fs = require('fs');
const path = require('path');

module.exports = () => {
    const composerPath = path.join(process.cwd(), 'composer.json');
    
    if (!fs.existsSync(composerPath)) {
        console.log('The file "composer.json" should be present it the theme\'s root');
        process.exit();
    }
};
```

{% endtab %}
{% endtabs %}

## Build plugin implementation syntax

The plugin syntax of Create ScandiPWA App is based on the [Craco](https://github.com/gsoft-inc/craco) (**C**reate **R**eact **A**pp **C**onfiguration **O**verride) plugin syntax.

Each plugin is an object, which might contain following keys (you can combine them):

* `overrideCracoConfig` -  allows to customize the Craco configuration object.&#x20;
* `overrideWebpackConfig` - allows to customize the Webpack config.
* `overrideDevServerConfig` - allows to customize the Webpack dev-server config

The values of this object keys are functions, which implement an override for the configuration. The function **may be asynchronous**.

### Craco configuration plugin

{% tabs %}
{% tab title="Instruction" %}
The function `overrideCracoConfig` allows a plugin override the configuration object **before** it's process by Craco.

The function **must return a valid Craco configuration object***.* The function will be called with a single object argument having the following structure:

```javascript
{
    cracoConfig, // The original CRACO configuration object
    pluginOptions, // The plugin options provided by the consumer
    context: {
        env, // The current NODE_ENV (development, production, etc..)
        paths // An object that contains all the paths used by CRA
    }
}
```

{% hint style="info" %}
The `cracoConfig` is passed into you plugin implementation by reference, which means you can modify it's values directly.
{% endhint %}
{% endtab %}

{% tab title="Example" %}
This plugin intent is to modify the paths of an application. The `appBuild` should be set to `magento/Magento_Theme` and the `appHtml` to `public/index.php`.

```javascript
({ cracoConfig }) => {
    // For Magento, use magento/Magento_Theme folder as dist
    cracoConfig.paths.appBuild = path.join(process.cwd(), 'magento', 'Magento_Theme', 'web');
    
    // For Magento use PHP template (defined in /public/index.php)
    cracoConfig.paths.appHtml = FallbackPlugin.getFallbackPathname('./public/index.php', sources);
    
    // Always return the config object.
    return cracoConfig;
}
```

{% endtab %}
{% endtabs %}

### Webpack configuration plugin

{% tabs %}
{% tab title="Instruction" %}
The function `overrideWebpackConfig` allows plugin override the Webpack configuration object **after** it's been customized by Craco.

The function **must return a valid Webpack configuration object**. The function will be called with a single object argument having the following structure:

```javascript
{
    webpackConfig, // The webpack config object already customized by craco
    cracoConfig, // The configuration object read from the craco.config.js file provided by the consumer
    pluginOptions, // The plugin options provided by the consumer
    context: {
        env, // The current NODE_ENV (development, production, etc..)
        paths //An object that contains all the paths used by CRA
    }
}
```

{% hint style="info" %}
The `webpackConfig` is passed into you plugin implementation by reference, which means you can modify it's values directly.
{% endhint %}
{% endtab %}

{% tab title="Example" %}
The plugin modifies the options field `filename` of the `HtmlWebpackPlugin`. It set's it to `../templates/root.phtml`.

```javascript
({ webpackConfig }) => {
    // For Magento setup, change output file name
    webpackConfig.plugins.forEach((plugin) => {
        if (plugin instanceof HtmlWebpackPlugin) {
            plugin.options.filename = '../templates/root.phtml';
        }
    });

    // Always return the config object.    
    return webpackConfig;
}
```

{% endtab %}
{% endtabs %}

### Webpack dev-server configuration plugin

{% tabs %}
{% tab title="Instruction" %}
The function `overrideDevServerConfig` let a plugin override the dev server config object after it's been customized by Craco.

The function **must return a valid Webpack dev-server configuration object**. The function will be called with a single object argument having the following structure:

```javascript
{
    devServerConfig, // The dev server config object already customized by craco
    cracoConfig, // The configuration object read from the craco.config.js file provided by the consumer
    pluginOptions, // The plugin options provided by the consumer
    context: {
        env, // The current NODE_ENV (development, production, etc..)
        paths, // An object that contains all the paths used by CRA
        allowedHost // Provided by CRA
    }
}
```

{% endtab %}

{% tab title="Example" %}
This plugin dumps the configuration object of Webpack dev-server for debugging purposes.

```javascript
({ devServerConfig }) => {
    // Dump the configuration file and format it
    console.log(JSON.stringify(devServerConfig, null, 4));
    
    // Always return the config object.
    return devServerConfig;
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.create-scandipwa-app.com/extensions/build-configuration-plugins.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
