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);
const wrappedWebpackConfig = TimeAnalyticsPlugin.wrap(webpackConfig,{ });
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 {
enable?: boolean | {
loader: boolean,
plugin: boolean,
};
outputFile?: string;
warnTimeLimit?: number;
dangerTimeLimit?: number;
loader?: {
groupedByAbsolutePath?: boolean;
topResources?: number;
exclude?: string[];
};
plugin?: {
exclude?: string[];
}
What is the difference with speed-measure-webpack-plugin?
Highlight:
-
This plugin handles some more situations, like custom hooks, so it could measure "mini-css-extract-plugin" correctly.
-
This plugin is strict, we assert many situations even in the production code. We prefer to throw an error when meeting undefined behavior.
Lowlight:
- 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?
- 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.
- Not want to use the same API. For example, the wrap function should better to be static.
Behavior
-
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.
-
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
- 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. - 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
- 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
-
In which condition, will this.callback()
be called? The doc says for multiple results, but it's kind of confused.
-
For ts
class A{
static foo(){
hello.call(this);
}
}
function hello(this:A){}