choz
choz

Reputation: 17858

Webpack Loader/Plugin - Replace a variable in a string to output a new string

I am writing a custom webpack loader to remove unnecessary code that Terser can't pick up.

Here's the sample source output from webpack loader;

const SvgsMap = {
  map1: () => {
    return '1';
  },
  map2: () => {
    return '2';
  },
  map3: () => {
    return '3';
  },
  // ...redacted
  map100: () => {
    return '100';
  },
}

Note that above comes into the loader as a string. And I have a whitelist of string[] as which of them that should be included in the build output;

const whitelistsArr = ["map1"]

I am currently writing a webpack loader to pre-process this before getting into bundled. Which currently uses Node VM that I assume could parse it to javascript object, in which then I can remove some of the unused properties in SvgsMap, then output it back again as a string.

My question is;

  1. Am I doing it the right way with Loader to remove them? Or is it actually a webpack plugin job to do this? Any other alternatives?
  2. I am hitting a rock doing this with VM, It seems like it's unable to mutate the existing code and output it back as a string. Am I wrong here?

Any suggestion is appreciated.


Here's my loader's code so far;

const path = require( 'path' );
const { loader } = require( 'webpack' );
const vm = require( 'vm' );

const whitelists = ['frame21Web'];

const loaderFn = function ( source ) {
  /** @type {loader.LoaderContext} */
  // eslint-disable-next-line babel/no-invalid-this
  const self = this;
  const filename = path.basename( self.resourcePath );
  const templateWithoutLoaders = filename.replace( /^.+!/, '' ).replace( /\?.+$/, '' );
  const vmContext = vm.createContext( { module: {} } );
  let newSource = '';
  try {
    const vmScript = new vm.Script( source, { filename: templateWithoutLoaders } );
    const cachedData = vmScript.createCachedData();
    console.log(cachedData.toString()); // Doesn't seem to output as a string.

  }
  catch (err) {
    console.error(err);
  }
  console.log( 'loader', filename, source );
  process.exit( 0 );
  return source;
};

module.exports = loaderFn;

Upvotes: 0

Views: 837

Answers (1)

theOneWhoKnocks
theOneWhoKnocks

Reputation: 658

There may be a couple answers to this question. Difficult to know without knowing the reasoning behind the removal.

If you have control of the file, you could use a combination of Webpack's Define plugin, and some if/else logic. For example

// in your WP config file

  new webpack.DefinePlugin({
    IS_CLIENT: JSON.stringify(true), // will only be true when compiled via WP
  });
// in your module

  if (process.env.IS_CLIENT) {
    SvgsMap.map1 = () => '1';
  }

The above pattern allows for adding/removing chunks of code for your Client bundle, while also allowing for use on the Server.

The other option would be to write a custom Babel plugin (not a WP plugin). I found this article helpful in the past, when I had to write some plugins. Babel gives you more control of how the parts of a JS file are processed, and you can use that plugin outside of WP (like while running Unit tests).

Upvotes: 2

Related Questions