DEV Community

Mike E
Mike E

Posted on

How to inspect files packaged by webpack before they are emitted

I recently had a need to inspect files in my front-end project that are packaged by webpack before they were emitted. In my pursuit to accomplish this an elegant and robust manner, I came across webpack hooks which are an exceedingly powerful way to tap into the inner workings of webpack.

What is webpack?

webpack is a module bundler for JavaScript. Front-end applications contain many types of assets such as JavaScript, (HOPEFULLY) Typescript, JSON, HTML, CSS, and images. webpack (after you've configure it to process files in a certain manner) will generate static assets, representing your applications modules so that they can be interpreted by a browser.

What are webpack hooks?

A "hook" in software development is a place in code that allows you to tap into a module to either provide different behavior or to react when something happens. webpack provides the following types of hooks:

How about an example?

For an example scenario, let's pretend we want to make sure that, when we build our application, no file is outputted that contains the string MY_SUPER_SECRET. Perhaps we want to do this to provide a last line of defense from a developer including a sensitive value in our code and we want to prevent webpack from even compiling it. If that string is detected, we'd like webpack to:

  • throw an error.
  • not emit any files at any point during the build.

To do this, let's look at the shouldEmit compiler hook. This hook is called before assets are emitted and will allow us to error out and not emit assets if our validation fails.

To start, let's create a new plugin class and add it to the plugins block of our webpack.config:

// src/webpack.config.ts
import { AssetValidatorPlugin } from './plugins/asset-validator-plugin';

module.exports= {
    entry: {...},
    module:{...},
    plugins: [
        new AssetValidatorPlugin(),
        ...
    ]
};
Enter fullscreen mode Exit fullscreen mode

Note that, while I've defined my plugin in a separate class/file, you could include it in your webpack.config inline.

Now, let's take a look at the plugin:

// src/plugins/asset-validator-plugin.ts
import * as webpack from 'webpack';

export class AssetValidatorPlugin {
  apply(compiler: webpack.Compiler) {
    compiler.hooks.shouldEmit.tap('AssetValidatorPlugin', (compilation: webpack.compilation.Compilation) => {
      this.validateAssets(compilation);
    });
  }

  public validateAssets(compilation: webpack.compilation.Compilation) {
    const assets = Object.entries(compilation.assets);
    const regex = new RegExp('MY_SUPER_SECRET', 'g');

    // Loop through each asset and check to see if it contains any sensitive strings
    for (let i = 0; i < assets.length; i++) {
      const [fileName] = assets[i];
      const asset = compilation.getAsset(fileName);
      const source = asset.source.source();
      const contents = convertSourceToString(source);
      const matches = contents.match(regex);

      if (matches) {
        throw new Error(
          "Our tool has identified the presence of the string 'MY_SUPER_SECRET' in your compiled code. Compilation has been aborted."
        );
      }
    }

    return true;
  }
}

// This function is only needed because asset.source.source() can be a string or ArrayBuffer
const convertSourceToString = (source: string | ArrayBuffer): string => {
  if (typeof source === 'string') {
    return source;
  } else {
    return new TextDecoder().decode(source);
  }
};
Enter fullscreen mode Exit fullscreen mode

So, let's review what is included our plugin. We've defined our plugin class and, within, are able to tap into the compiler.hooks.shouldEmit hook. In the hook callback, we simply call a validateAssets() function we've defined which loops through all assets that are part of the compilation and use regular expression matching to see if the string exists. We throw an error if it does, short-circuiting the compilation and not emitting any files. If it doesn't contain our special string, we'll return true, compilation will continue, emitting the packaged files as expected.

If you have a need to pass any parameters to your plugin, that can easily be accomplished by defining a constructor in your plugin class like this:

constructor(options: MyPluginOptions) {
    this.options = options;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Hopefully you now have a better understanding of webpack hooks and how you can leverage them to provide additional behavior when your application's assets are packaged.

Top comments (1)

Collapse
 
dgreene1 profile image
Dan Greene

Nice writeup! I imagine it's much faster to introspect the files this way.