DEV Community

Mike E
Mike E

Posted on

6 3

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (1)

Collapse
 
dgreene1 profile image
Dan Greene

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

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay