"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); var Cabinet = require('./cabinet'); var _require = require('./constant'), STEPS = _require.STEPS; var tapHook = require('./tap-hook'); var _require2 = require('./log'), trace = _require2.trace, error = _require2.error; var BuildStatisticsPlugin = /*#__PURE__*/ function () { function BuildStatisticsPlugin(_ref) { var path = _ref.path; _classCallCheck(this, BuildStatisticsPlugin); // 统计完后待写入的文件地址 this._path = path; // 用来存放compiler hooks触发时,hooks名字和触发时刻的映射关系 this._cache = new Map(); // Cabinet是一个柜子,柜子里有好多抽屉(drawer), // 我们可以将物品(gadget)根据抽屉类别,放置到不同的抽屉中。 // 以下我们取drawer为compilation对象,里面放置多个gadget,{event, timestamp} this._cabinet = new Cabinet(); // watch到文件变更标志位,此时不写文件 this._isFileChanged = false; } _createClass(BuildStatisticsPlugin, [{ key: "apply", value: function apply(compiler) { var _this = this; // webpack 事件: 开始 tapHook(compiler, 'compile', function () { trace('开始compile'); var event = 'compiler.compile'; _this._cache.set(event, +new Date()); }); // webpack 事件: 载入文件 + 代码生成 + 优化 tapHook(compiler, 'compilation', function (compilation) { trace('创建一个新的compilation'); var event = 'compiler.compilation'; // 如果项目中使用了extract-text-webpack-plugin, // 则compiler.compilation在一次编译中可能会触发多次 // 因此,将多次触发相关的信息,根据compilation对象分类,放在一个Cabinet中 if (!_this._cache.has(event)) { _this._cache.set(event, _this._cabinet); } // webpack 事件: 载入文件开始 trace('开始载入文件'); _this._cabinet.put({ drawer: compilation, gadget: { event, timestamp: +new Date() } }); // webpack 事件: 载入文件结束 tapHook(compilation, 'finishModules', function (modules) { trace('载入文件结束'); _this._cabinet.put({ drawer: compilation, gadget: { event: 'compilation.finishModules', timestamp: +new Date() } }); }); // webpack 事件: 代码生成,得到所有的目标文件名和文件内容 tapHook(compilation, 'additionalAssets', function (callback) { trace('生成目标文件名和文件内容'); _this._cabinet.put({ drawer: compilation, gadget: { event: 'compilation.additionalAssets', timestamp: +new Date() } }); callback(); }); // webpack 事件: 优化,压缩代码 tapHook(compilation, 'afterOptimizeChunkAssets', function (chunks) { trace('对文件内容进行压缩'); _this._cabinet.put({ drawer: compilation, gadget: { event: 'compilation.afterOptimizeChunkAssets', timestamp: +new Date() } }); }); }); // webpack 事件: 结束,写文件 tapHook(compiler, 'done', function (stats) { trace('完成'); var event = 'compiler.done'; _this._cache.set(event, +new Date()); // 处理_cache中的数据,回调_done函数 // [{event,timestamp}] var statistics = _this._getBuildStatistics(); trace('各事件发生的时刻:%j', statistics); // [{stage,timestamp,startTime,endTime,elapse}] var stages = _this._convertToStages(statistics); trace('各阶段耗时:%j', stages); trace('hash:%s', stats.hash); // watch到文件变更时不写文件 // note: compiler.watchMode 在webpack 1.x中不支持,因此这里只判断 _isFileChanged // 否则应该判断 compiler.watchMode && this._isFileChanged if (_this._isFileChanged) { trace('文件变更时不写文件'); return; } trace('准备写文件'); var filePath = _this._path; var content = JSON.stringify({ hash: stats.hash, stages }); _this._writeToFile(filePath, content); }); // webpack 事件:watch到文件变更 tapHook(compiler, 'invalid', function (fileName, changeTime) { trace('监测到文件变更'); // 重新统计 _this._cache = new Map(); _this._cabinet = new Cabinet(); // 设置文件变更标志位,watch到文件变更时不写文件 _this._isFileChanged = true; }); } // 各事件节点的时间 // [{event,timestamp}] }, { key: "_getBuildStatistics", value: function _getBuildStatistics() { var _this2 = this; // note: Map的keys是按加入顺序保存的 var eventList = Array.from(this._cache.keys()); return eventList.reduce(function (memo, event) { var value = _this2._cache.get(event); // value可能是一个时间戳,用于存储compiler hooks中的信息 // 也可能是一个Cabinet,用于存储compilation hooks中的信息 var isCabinet = value instanceof Cabinet; // compiler event: compile, done if (!isCabinet) { memo.push({ event, timestamp: value }); return memo; } // compilation event: finishModule, additionalAssets, optimizeChunkAssets var statistics = _this2._getCabinetStatistics(value); return memo.concat(statistics); }, []); } // cabinet内,各事件节点的时间 // [{event,timestamp}] }, { key: "_getCabinetStatistics", value: function _getCabinetStatistics(cabinet) { try { // Cabinet中存储的是drawer到gadgetList的映射 // 其中drawer是compilation对象,gadgetList为[{event, timestamp}] var compilations = cabinet.getDrawerList(); // note: 第一个compilation就是入口文件对应的那个compilation // 由于extract-text-webpack-plugin会创建childCompiler,因此会出现多个compilation。 var firstCompilation = compilations[0]; var gadgetList = cabinet.getGadgetListFromDrawer(firstCompilation); return gadgetList; } catch (err) { error('获取compilation信息出错,%s', err.stack); return []; } } // 计算每个阶段的起始时间与耗时,从第二个节点开始往后分别记为一个阶段 // [{stage,timestamp,startTime,endTime,elapse}] }, { key: "_convertToStages", value: function _convertToStages(statistics) { var result = []; var startTime = statistics[0].timestamp; // 从第二个节点开始往后分别记为一个阶段,因此从索引 1 开始处理 for (var i = 1; i < statistics.length; i++) { var _statistics$i = statistics[i], event = _statistics$i.event, timestamp = _statistics$i.timestamp; result.push({ // 将事件名映射成阶段名 stage: STEPS[event], startTime, endTime: timestamp, elapse: timestamp - startTime }); startTime = timestamp; } return result; } }, { key: "_writeToFile", value: function _writeToFile(filePath, content) { try { var dirPath = path.resolve(filePath, '../'); if (!fs.existsSync(dirPath)) { trace('创建文件夹:%s', dirPath); mkdirp.sync(dirPath); } trace('写入文件:%s', filePath); fs.writeFileSync(filePath, content); } catch (err) { error('写入文件时发生错误:%s', err.stack); } } }]); return BuildStatisticsPlugin; }(); module.exports = BuildStatisticsPlugin;