# 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 %}
