samedi 3 avril 2021

How can I replace variable names on the fly using webpack?

I come across a dilemma build a plugin for our front-end pipeline, I'll explain first what we want to achieve then the problem. At first, we have a set of variables which have to be replaced at build time with 'true' or 'false', then a set of variables names which have to be renamed and some other customisations. I'll give an example.

  • Every DCE_HAS_MODULE_PRE_ORDERS has to be replaced with false
  • Any occurence of a /ST__[A-Z_]+/g (ST__MODULE_SETTING) wil be renamed to ST1 or ST53, everywhere it's used.

Till now we've read the output file of webpack, replaced, then write again on disk, it works. But this does not allow us to use the full functionality of webpack, so we want to implement this using the inner-power of webpack.

This is the code which does the trick.

function prependStorefrontDCE() {
    let currentSTIdx = 1;


    const files = fs.readdirSync(outputDirPath).filter((e) => e.endsWith('.js'));
    files.forEach((filename) => {
        const filePath = outputDirPath + '/' + filename;
        let payload = fs.readFileSync(filePath, 'utf8');

        // @TODO: allow a way to define custom DCEs
        // set custom DCEs to false
        payload = payload.replace(/DCE_HAS_MODULE_PRE_ORDERS/g, 'false');

        {
            // Collect unique settings variables
            let stVariables = new Set(payload.match(/ST__[A-Z_]+/g));
            // Define a unique id for each one
            let stUniqueIds = {};
            stVariables.forEach(st => {
                stUniqueIds[st] = `ST${currentSTIdx++}`;
            });
            // Declare settings in upper scope
            stVariables = Array.from(stVariables).map(st => `let ${st} = null;`);

            stVariables.push('let DCE = null;');
            stVariables.push('ST__MODULES = {1: {}, 46: {}, 53: {}};');
            stVariables.push('let URL_HTTP_HOST = "";');

            let dceJS = stVariables.join('');

            // add closure JS
            payload = dceJS + payload;

            console.log(dceJS);

            // obfuscation for settings variables using the unique id
            payload = payload.replace(/ST__[A-Z_]+/g, (match) => stUniqueIds[match]);
        }
        {
            // Collect unique settings variables
            let stVariables = new Set(payload.match(/DCE_[A-Z_]+/g));
            // Define a unique id for each one
            let stUniqueIds = {};
            stVariables.forEach(st => {
                stUniqueIds[st] = `DCE${currentSTIdx++}`;
            });
            // Declare settings in upper scope
            stVariables = Array.from(stVariables).map(st => `let ${st} = true;`);

            let dceJS = stVariables.join('');

            // add closure JS
            payload = dceJS + payload;

            // obfuscation for settings variables using the unique id
            payload = payload.replace(/DCE_[A-Z_]+/g, (match) => stUniqueIds[match]);
        }

        // remove DCE from CSS
        payload = payload.replace(dceInCssRegex, '');

        // set all DCE booleans to true
        payload = payload.replace(dceHasRegex, 'true');
        payload = payload.replace(dceRequireRegex, 'true');

        // write file
        fs.writeFileSync(filePath, payload);
    });
}

And this is how we use it.

compiler.hooks.afterEmit.tap('after-build', async () => {
    if (IS_PRODUCTION) {
        // step 1/2
        mangleProps();
    }
    // step 2/2
    prependStorefrontDCE();
});

But in order de replace in the webpack modules I've tried many ways, but no one seems to work as expected. I've looked at a lot of plugins and tried hooks and methods but without any result. One which seems to answer half of requirements is DefinePlugin but it does not allow to find variables based on regex and replace them with custom.

Here is a piece of what I've tried.

compiler.hooks.run.tapAsync(
    'DCEs', (compilation) => {

        compilation.hooks.processAssets.tap(() => {
            // Get the assets of webpack
            const assets = compilation.getAssets();

            // When processing assets, use the function to replace variable name
            assets.forEach(asset => {
                console.log('\n UpdateAsset', asset.name, '\n');
                compilation.updateAsset(asset.name, (source) => {
                    return prependStorefrontDCE(source.source());
                });
            });
        });

    },
);



Aucun commentaire:

Enregistrer un commentaire