開發緣由:
因爲項目是vue的頁面級組件有差很少100個,舉個case:
因爲用了 webpack-preftech-pulgin 插件, 頁面裏會插入 100個css
<link href="xxx" as="script" rel="preftech">
標籤.
經過在chrome 裏面調試發現 當頁面裏有過多preftech 會阻塞主文件的下載速度,也就是會搶佔重要資源的下載速度。因此我採用另外一種實現方式,限制最大同時預下載(未來須要)的 資源的數量,減小由於資源下載過多,形成首頁渲染過慢。
包地址
插件主代碼html
// 插件地址 const fs = require('fs') const path = require('path'); const Entrypoint = require('webpack/lib/Entrypoint.js'); const assert = require('assert'); class HtmlWebpackPreFetch { constructor(options = { paralleMax: 3 }) { this.options = options } apply(complier) { complier.hooks.compilation.tap(this.constructor.name, compilation => { // hook HtmlWebpackPlugin 輸出 index.html以前的鉤子,爲了改造輸出 index.html 文件內容(來自某個webpack 插件) let hook = compilation.hooks.htmlWebpackPluginAfterHtmlProcessing; if (!hook) { const [HtmlWebpackPlugin] = complier.options.plugins.filter( (plugin) => plugin.constructor.name === 'HtmlWebpackPlugin'); assert(HtmlWebpackPlugin, 'Unable to find an instance of ' + 'HtmlWebpackPlugin in the current compilation.'); hook = HtmlWebpackPlugin.constructor.getHooks(compilation).beforeEmit; } // 對對應的鉤子 註冊事件,我是這個認爲😂 hook.tapAsync( this.constructor.name, (htmlPluginData, callback) => { try { const { outputOptions: { publicPath = '' } , chunks, chunkGroups} = compilation; // split chunks 找到非入口文件 const sourceList = chunkGroups.filter(chunkGroup => { return !(chunkGroup instanceof Entrypoint) && !chunkGroup.isInitial() }).map(chunkGroup => chunkGroup.chunks.reduce((p, q) => { return { files: [...p.files, q.files] } , {files: []} })).reduce((p, n) => { return { files: [...p.files, ...n.files] } } , {files: []}).files.map(path => (`${publicPath}${path}`)) // loadTemplate.js 實現併發加載模版文件 let loadJsTemplate = fs.readFileSync(path.join(__dirname, './loadTemplate.js'), { encoding: 'utf-8' }) // 獲取要下載的文件 和 配置參數 const templateOptions = JSON.stringify({ sourceList, ...this.options }) loadJsTemplate = loadJsTemplate.replace('/*injectOptions*/', templateOptions) let htmlTemplate = htmlPluginData.html const htmlTemplateList = htmlTemplate.split('</body>') htmlTemplateList.splice(1, 0, `<script>${loadJsTemplate}</script></body>`) htmlTemplate = htmlTemplateList.join(''); htmlPluginData.html = htmlTemplate callback(null, htmlPluginData); } catch (error) { callback(error); } } ); })}}module.exports = HtmlWebpackPreFetch
並行下載文件 loadTemplate.jsvue
(function(options) { var win = window; var doc = win.document; var sourceList = options.sourceList; var paralleMax = options.paralleMax; var attr = options.attr; var jsReg = /\.js/; var cssReg = /\.css/; // 構造隊列下載類 function QueueLoadPlugin (sourceList, paralleMax, attr) { this.sourceList = sourceList; this.paralleMax = paralleMax || 3; this.sourceIndex = 0; this.queueList = []; this.attr = attr || { rel: 'preload' //rel: 'prefetch' }; this.handleLoadLogic(); }; QueueLoadPlugin.prototype.loadSingleSouce = function(href, attr, cb) { var head = doc.head; var link = doc.createElement('link'); link.href = href; for(var key in attr) { if (attr.hasOwnProperty(key)) { link.setAttribute(key, attr[key]); } } if(jsReg.test(href)) { link.setAttribute('as','script'); } if(cssReg.test(href)) { link.setAttribute('as','style'); } link.onload = function() { cb(true) }; link.onerror = function() { cb(false) }; head.appendChild(link); }; QueueLoadPlugin.prototype.handleLoadLogic = function() { var paralleMax = this.paralleMax; var sourceList = this.sourceList; var sourceLg = sourceList.length; var sourceIndex = this.sourceIndex; if (sourceIndex === 0) { for(; sourceIndex < sourceLg && sourceIndex < paralleMax; sourceIndex ++) { var href = sourceList[sourceIndex]; this.queueList.push(sourceIndex); this.loadPartLogic(sourceIndex, href, this); } } else { if (sourceIndex < sourceLg) { const href = sourceList[sourceIndex]; this.loadPartLogic(sourceIndex, href, this); sourceIndex ++; } } this.sourceIndex = sourceIndex; }; // QueueLoadPlugin.prototype.loadPartLogic = function(idx, href, context) { var href = this.sourceList[idx]; this.loadSingleSouce(href, context.attr, function() { var sourceLg = sourceList.length; var curIndex = context.queueList.findIndex(d => d === idx); context.queueList.splice(curIndex, 1, 0); if(idx < sourceLg - 1) context.handleLoadLogic() }) }; new QueueLoadPlugin(sourceList, paralleMax, attr); })(/*injectOptions*/)