"Fossies" - the Fresh Open Source Software Archive

Member "angular-cli-8.3.23/packages/angular_devkit/build_angular/plugins/webpack/analytics.ts" (15 Jan 2020, 10446 Bytes) of package /linux/www/angular-cli-8.3.23.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /**
    2  * @license
    3  * Copyright Google Inc. All Rights Reserved.
    4  *
    5  * Use of this source code is governed by an MIT-style license that can be
    6  * found in the LICENSE file at https://angular.io/license
    7  */
    8 import { analytics } from '@angular-devkit/core';
    9 import {
   10   Compiler,
   11   Module,
   12   Stats,
   13   compilation,
   14 } from 'webpack';
   15 import { Source } from 'webpack-sources';
   16 
   17 const NormalModule = require('webpack/lib/NormalModule');
   18 
   19 interface NormalModule extends Module {
   20   _source?: Source | null;
   21   resource?: string;
   22 }
   23 
   24 const webpackAllErrorMessageRe = /^([^(]+)\(\d+,\d\): (.*)$/gm;
   25 const webpackTsErrorMessageRe = /^[^(]+\(\d+,\d\): error (TS\d+):/;
   26 
   27 /**
   28  * Faster than using a RegExp, so we use this to count occurences in source code.
   29  * @param source The source to look into.
   30  * @param match The match string to look for.
   31  * @param wordBreak Whether to check for word break before and after a match was found.
   32  * @return The number of matches found.
   33  * @private
   34  */
   35 export function countOccurrences(source: string, match: string, wordBreak = false): number {
   36   if (match.length == 0) {
   37     return source.length + 1;
   38   }
   39 
   40   let count = 0;
   41   // We condition here so branch prediction happens out of the loop, not in it.
   42   if (wordBreak) {
   43     const re = /\w/;
   44     for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) {
   45       if (!(re.test(source[pos - 1] || '') || re.test(source[pos + match.length] || ''))) {
   46         count++;  // 1 match, AH! AH! AH! 2 matches, AH! AH! AH!
   47       }
   48 
   49       pos -= match.length;
   50       if (pos < 0) {
   51         break;
   52       }
   53     }
   54   } else {
   55     for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) {
   56       count++;  // 1 match, AH! AH! AH! 2 matches, AH! AH! AH!
   57       pos -= match.length;
   58       if (pos < 0) {
   59         break;
   60       }
   61     }
   62   }
   63 
   64   return count;
   65 }
   66 
   67 
   68 /**
   69  * Holder of statistics related to the build.
   70  */
   71 class AnalyticsBuildStats {
   72   public isIvy = false;
   73   public errors: string[] = [];
   74   public numberOfNgOnInit = 0;
   75   public numberOfComponents = 0;
   76   public initialChunkSize = 0;
   77   public totalChunkCount = 0;
   78   public totalChunkSize = 0;
   79   public lazyChunkCount = 0;
   80   public lazyChunkSize = 0;
   81   public assetCount = 0;
   82   public assetSize = 0;
   83   public polyfillSize = 0;
   84   public cssSize = 0;
   85 }
   86 
   87 
   88 /**
   89  * Analytics plugin that reports the analytics we want from the CLI.
   90  */
   91 export class NgBuildAnalyticsPlugin {
   92   protected _built = false;
   93   protected _stats = new AnalyticsBuildStats();
   94 
   95   constructor(
   96     protected _projectRoot: string,
   97     protected _analytics: analytics.Analytics,
   98     protected _category: string,
   99   ) {}
  100 
  101   protected _reset() {
  102     this._stats = new AnalyticsBuildStats();
  103   }
  104 
  105   protected _getMetrics(stats: Stats) {
  106     const startTime = +(stats.startTime || 0);
  107     const endTime = +(stats.endTime || 0);
  108     const metrics: (string | number)[] = [];
  109     metrics[analytics.NgCliAnalyticsMetrics.BuildTime] = (endTime - startTime);
  110     metrics[analytics.NgCliAnalyticsMetrics.NgOnInitCount] = this._stats.numberOfNgOnInit;
  111     metrics[analytics.NgCliAnalyticsMetrics.NgComponentCount] = this._stats.numberOfComponents;
  112     metrics[analytics.NgCliAnalyticsMetrics.InitialChunkSize] = this._stats.initialChunkSize;
  113     metrics[analytics.NgCliAnalyticsMetrics.TotalChunkCount] = this._stats.totalChunkCount;
  114     metrics[analytics.NgCliAnalyticsMetrics.TotalChunkSize] = this._stats.totalChunkSize;
  115     metrics[analytics.NgCliAnalyticsMetrics.LazyChunkCount] = this._stats.lazyChunkCount;
  116     metrics[analytics.NgCliAnalyticsMetrics.LazyChunkSize] = this._stats.lazyChunkSize;
  117     metrics[analytics.NgCliAnalyticsMetrics.AssetCount] = this._stats.assetCount;
  118     metrics[analytics.NgCliAnalyticsMetrics.AssetSize] = this._stats.assetSize;
  119     metrics[analytics.NgCliAnalyticsMetrics.PolyfillSize] = this._stats.polyfillSize;
  120     metrics[analytics.NgCliAnalyticsMetrics.CssSize] = this._stats.cssSize;
  121 
  122     return metrics;
  123   }
  124   protected _getDimensions(stats: Stats) {
  125     const dimensions: (string | number | boolean)[] = [];
  126 
  127     if (this._stats.errors.length) {
  128       // Adding commas before and after so the regex are easier to define filters.
  129       dimensions[analytics.NgCliAnalyticsDimensions.BuildErrors] = `,${this._stats.errors.join()},`;
  130     }
  131 
  132     dimensions[analytics.NgCliAnalyticsDimensions.NgIvyEnabled] = this._stats.isIvy;
  133 
  134     return dimensions;
  135   }
  136 
  137   protected _reportBuildMetrics(stats: Stats) {
  138     const dimensions = this._getDimensions(stats);
  139     const metrics = this._getMetrics(stats);
  140     this._analytics.event(this._category, 'build', { dimensions, metrics });
  141   }
  142 
  143   protected _reportRebuildMetrics(stats: Stats) {
  144     const dimensions = this._getDimensions(stats);
  145     const metrics = this._getMetrics(stats);
  146     this._analytics.event(this._category, 'rebuild', { dimensions, metrics });
  147   }
  148 
  149   protected _checkTsNormalModule(module: NormalModule) {
  150     if (module._source) {
  151       // PLEASE REMEMBER:
  152       // We're dealing with ES5 _or_ ES2015 JavaScript at this point (we don't know for sure).
  153 
  154       // Just count the ngOnInit occurences. Comments/Strings/calls occurences should be sparse
  155       // so we just consider them within the margin of error. We do break on word break though.
  156       this._stats.numberOfNgOnInit += countOccurrences(module._source.source(), 'ngOnInit', true);
  157 
  158       // Count the number of `Component({` strings (case sensitive), which happens in __decorate().
  159       // This does not include View Engine AOT compilation, we use the ngfactory for it.
  160       this._stats.numberOfComponents += countOccurrences(module._source.source(), ' Component({');
  161       // For Ivy we just count ngComponentDef.
  162       const numIvyComponents = countOccurrences(module._source.source(), 'ngComponentDef', true);
  163       this._stats.numberOfComponents += numIvyComponents;
  164 
  165       // Check whether this is an Ivy app so that it can reported as part of analytics.
  166       if (!this._stats.isIvy) {
  167         if (numIvyComponents > 0 || module._source.source().includes('ngModuleDef')) {
  168           this._stats.isIvy = true;
  169         }
  170       }
  171     }
  172   }
  173 
  174   protected _checkNgFactoryNormalModule(module: NormalModule) {
  175     if (module._source) {
  176       // PLEASE REMEMBER:
  177       // We're dealing with ES5 _or_ ES2015 JavaScript at this point (we don't know for sure).
  178 
  179       // Count the number of `.ɵccf(` strings (case sensitive). They're calls to components
  180       // factories.
  181       this._stats.numberOfComponents += countOccurrences(module._source.source(), '.ɵccf(');
  182     }
  183   }
  184 
  185   protected _collectErrors(stats: Stats) {
  186     if (stats.hasErrors()) {
  187       for (const errObject of stats.compilation.errors) {
  188         if (errObject instanceof Error) {
  189           const allErrors = errObject.message.match(webpackAllErrorMessageRe);
  190           for (const err of [...allErrors || []].slice(1)) {
  191             const message = (err.match(webpackTsErrorMessageRe) || [])[1];
  192             if (message) {
  193               // At this point this should be a TS1234.
  194               this._stats.errors.push(message);
  195             }
  196           }
  197         }
  198       }
  199     }
  200   }
  201 
  202   // We can safely disable no any here since we know the format of the JSON output from webpack.
  203   // tslint:disable-next-line:no-any
  204   protected _collectBundleStats(json: any) {
  205     json.chunks
  206       .filter((chunk: { rendered?: boolean }) => chunk.rendered)
  207       .forEach((chunk: { files: string[], initial?: boolean, entry?: boolean }) => {
  208         const asset = json.assets.find((x: { name: string }) => x.name == chunk.files[0]);
  209         const size = asset ? asset.size : 0;
  210 
  211         if (chunk.entry || chunk.initial) {
  212           this._stats.initialChunkSize += size;
  213         } else {
  214           this._stats.lazyChunkCount++;
  215           this._stats.lazyChunkSize += size;
  216         }
  217         this._stats.totalChunkCount++;
  218         this._stats.totalChunkSize += size;
  219       });
  220 
  221     json.assets
  222       // Filter out chunks. We only count assets that are not JS.
  223       .filter((a: { name: string }) => {
  224         return json.chunks.every((chunk: { files: string[] }) => chunk.files[0] != a.name);
  225       })
  226       .forEach((a: { size?: number }) => {
  227         this._stats.assetSize += (a.size || 0);
  228         this._stats.assetCount++;
  229       });
  230 
  231     for (const asset of json.assets) {
  232       if (asset.name == 'polyfill') {
  233         this._stats.polyfillSize += asset.size || 0;
  234       }
  235     }
  236     for (const chunk of json.chunks) {
  237       if (chunk.files[0] && chunk.files[0].endsWith('.css')) {
  238         this._stats.cssSize += chunk.size || 0;
  239       }
  240     }
  241   }
  242 
  243   /************************************************************************************************
  244    * The next section is all the different Webpack hooks for this plugin.
  245    */
  246 
  247   /**
  248    * Reports a succeed module.
  249    * @private
  250    */
  251   protected _succeedModule(mod: Module) {
  252     // Only report NormalModule instances.
  253     if (mod.constructor !== NormalModule) {
  254       return;
  255     }
  256     const module = mod as {} as NormalModule;
  257 
  258     // Only reports modules that are part of the user's project. We also don't do node_modules.
  259     // There is a chance that someone name a file path `hello_node_modules` or something and we
  260     // will ignore that file for the purpose of gathering, but we're willing to take the risk.
  261     if (!module.resource
  262         || !module.resource.startsWith(this._projectRoot)
  263         || module.resource.indexOf('node_modules') >= 0) {
  264       return;
  265     }
  266 
  267     // Check that it's a source file from the project.
  268     if (module.resource.endsWith('.ts')) {
  269       this._checkTsNormalModule(module);
  270     } else if (module.resource.endsWith('.ngfactory.js')) {
  271       this._checkNgFactoryNormalModule(module);
  272     }
  273   }
  274 
  275   protected _compilation(compiler: Compiler, compilation: compilation.Compilation) {
  276     this._reset();
  277     compilation.hooks.succeedModule.tap('NgBuildAnalyticsPlugin', this._succeedModule.bind(this));
  278   }
  279 
  280   protected _done(stats: Stats) {
  281     this._collectErrors(stats);
  282     this._collectBundleStats(stats.toJson());
  283     if (this._built) {
  284       this._reportRebuildMetrics(stats);
  285     } else {
  286       this._reportBuildMetrics(stats);
  287       this._built = true;
  288     }
  289   }
  290 
  291   apply(compiler: Compiler): void {
  292     compiler.hooks.compilation.tap(
  293       'NgBuildAnalyticsPlugin',
  294       this._compilation.bind(this, compiler),
  295     );
  296     compiler.hooks.done.tap('NgBuildAnalyticsPlugin', this._done.bind(this));
  297   }
  298 }