npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

time-analytics-webpack-plugin

v0.1.20

Published

analytize the time of loaders and plugins

Downloads

17,729

Readme

time-analytics-webpack-plugin

Profiling is the base of optimise.

This plugin will tell the time of loaders and plugins quickly.

NOTE: This plugin is still in an early eage, the API is not forzen and might be changed. Consider about this, maybe you want to enable type-check so that you could know the option is changed.

Output

By default, the result will be logged into console, but it's able to set the options to make it write to some file.

┌── time-analytics-webpack-plugin
│ Webpack compile takes 212.00251600146294ms
├── Plugins
│ Plugin TerserPlugin takes 257.66442596912384ms
│ Plugin MiniCssExtractPlugin takes 1.021947979927063ms
│ Plugin DefinePlugin takes 0.03626999258995056ms
│ All plugins take 258.72264394164085ms
├── Loaders
│ Loader babel-loader takes 161.88674998283386ms
│ Loader mini-css-extract-plugin takes 190.57009398937225ms
│ Loader css-loader takes 12.007494986057281ms
│ All loaders take 364.4643389582634ms

Note that all loaders take even more time than the whole time! How could this be possible?

This is due to how to calcuate the time:

  • For webpack compilet time, it means the time difference between hooks Compiler.compile and Compiler.done.

  • For loaders, each time some resource is executed by some loader, we record the start time and the end time. However,

    • loader might be async, we only record the time when the returned function of this.async() is called. -
      • If there is any OS call(like read/write file), we have to include that time.
      • Event loop might make it not accurate, because a callback will not be called immediately when it's ready(only if execution stack is empty and all ready tasks before this task in task queue is executed).
    • loader might be parallel.

How to use it

Wrap the config, and use the wrapped config.

const wrappedWebpackConfig = TimeAnalyticsPlugin.wrap(webpackConfig);

// Or use options to control behaviors
const wrappedWebpackConfig = TimeAnalyticsPlugin.wrap(webpackConfig,{ /* options */});

Or wrap a function that will return a configuration

const wrappedWebpackConfigFactory = TimeAnalyticsPlugin.wrap(webpackConfigFactory);

Options

Type should be the document. Please provide any feedback if you think it's not enough.


interface TimeAnalyticsPluginOptions {
    /**
     * If fase, do nothing
     * 
     * If true, output all loader and plugin infos.
     * 
     * If object, loader and plugin could be turn off.
     * 
     * Control loader and plugin with fine grained in `loader` and `plugin` options (not this option)
     * 
     * @default true
     */
    enable?: boolean | {
        /**
         * @default true
         */
        loader: boolean,
        /**
         * @default true
         */
        plugin: boolean,
    };

    /**
     * If provided, write the result to a file.
     * 
     * Otherwise the stdout stream.
     */
    outputFile?: string;
    /**
     * Display the time as warning color if time is more than this limit.
     * 
     * The unit is ms.
     * 
     * @default 3000
     */
    warnTimeLimit?: number;
    /**
     * Display the time as danger color if time is more than this limit.
     * 
     * The unit is ms.
     * 
     * @default 8000
     */
    dangerTimeLimit?: number;
    loader?: {
        /**
         * If true, output the absolute path of the loader.
         * 
         * By default, the plugin displays loader time by a assumed loader name
         * 
         * Like `babel-loader takes xxx ms.`
         * 
         * The assumption is the loader's name is the first name after the last `node_modules` in the path. 
         * 
         * However, sometimes, it's not correct, like the loader's package is `@foo/loader1` then the assumed name is "@foo", 
         * or some framework like `next` will move the loader to some strange place.
         * 
         * @default false
         */
        groupedByAbsolutePath?: boolean;
        /**
         * If true, display the most time consumed resource's info
         * 
         * @default 0
         * @NotImplementYet
         */
        topResources?: number;
        /**
         * The loaders that should not be analytized.
         * 
         * Use the node package's name.
         */
        exclude?: string[];
    };
    plugin?: {
        /**
         * The plugins that should not be analytized.
         * 
         * The name is the plugin class itself, not the package's name.
         */
        exclude?: string[];
    }

What is the difference with speed-measure-webpack-plugin?

Highlight:

  1. This plugin handles some more situations, like custom hooks, so it could measure "mini-css-extract-plugin" correctly.

  2. This plugin is strict, we assert many situations even in the production code. We prefer to throw an error when meeting undefined behavior.

Lowlight:

  1. I have no idea about some code in speed-measure-webpack-plugin. Is it legacy code or really useful for some situation?

Why not fork speed-measure-webpack-plugin?

  1. speed-measure-webpack-plugin is written in js, it's usually not a big deal to convert to ts, but this situation is kind of differnt, which uses many hack ways. It's more easier to rewrite the whole plugin in ts, this would also make it easier to maintain.
  2. Not want to use the same API. For example, the wrap function should better to be static.

Behavior

  1. only the plugin wrapped in the origin webpack config could be analytized

    • the webpack internal plugin is not measured. But this might not be a limitation, if you want to measure internal plugin, you could submit an issue.
      • Maybe check normailzed configutation, does webpcak add all plugins at this time?
    • If one plugin adds more plugins internally, the added plugin will be ignored.
  2. thread-loader is confused, I do see internal babel-loader rarely, but usually it's not in the output. However, if we not hack Compiler for custom hooks, it seems we could measure the internal loaders. Pretty interesting, but need time to investigate.

How does it work?

To measure time, we must know when the loader/plugins starts and ends.

However, this is tricky, because when writing a loader, you do not want others influence your code. So how could we take over other's loader and plugin?

For loaders, the webpack will import the loader's module each time it's used. So we need to

  1. For cjs, take over require method. So that when webpack is requiring the loader module, we could do some extra work before and after the loader execute.
  2. For mjs, the plugin does not work for now. But it is harder, because we are not able to take over import function, the only way I could come up with is create a temp file for the new wrap loader and change the target to it.

For plugins, we wrap the plugin with a custom plugin, which will create proxy for most of property of the compiler passed into.

For custom hooks in plugins:

In speed-measure-webpack-plugin, when using mini-css-extract-plugin, there is a strange error which is like "the plugin is not called".

The reason is the mini-css-extract-plugin's plugin will add a unique symbol to compilation object, and in the pitch loader of mini-css-extract-plugin, it will check the symbol.

Seems pretty reasonable! However, webpack is using a reference equal map in getCompilationHooks. But we are using Proxy to take over everything, the reference of a proxy is not the same as the origin target.

So how to resolve it?

We hack the WeakMap, when the key is Compiler or Compilation, we will add an obejct and use it as key instead.

Thanks

speed-measure-webpack-plugin. An awesome plugin, which inspires this repo.

Q&A

  1. why publish ts source file?

So it would be easier to debug. It's not a big deal to download a bit more files if they will not appear in production code.

Questions

  1. In which condition, will this.callback() be called? The doc says for multiple results, but it's kind of confused.

  2. For ts

class A{
    static foo(){
        hello.call(this); // no error, this is a bug? Because in static method, `this` should be the class itself("typeof A") rather than the class instance.
    }
}
function hello(this:A){}