介绍

描述:此优化针对三方文件依赖过多或逻辑文件过多情况进行,已知打包生成前端会有很多JS文件且Vue是按需加载,初始化并不会加载无关文件等,正常电脑部署访问则没问题,但云桌面情况且配置并不太好情况下会出现首次访问其他组件卡顿现象,原因出自加载文件上。

以下内容一切从简,简单、快捷、清晰。


使用场景

上述已讲造成卡顿问题点,归根还是由于按需加载的模块相关JS CS文件过大,再加上云桌面性能和网络等原因造成,针对此类相关问题即可采用此方法实现预加载文件功能


实现思路

摘要:首先我们目标明确,初始化加载最大JS文件,接下来的代码涉及思路先围绕此点进行

1. 合适时机
2. 计算大小
3. 读取并引入

首先已了解,JS文件是解析后的,我们并不能直接引入随机生成的JS文件,总不能每次打包后手动添加并修改文件名称,所以此时就需要在Webpack打包生成文件写入盘之前储存各JS文件名及大小详情如下。


chainWebpack 配置

在 VUe.config.js 中 chainWebpack中配置即可

// 引入相关逻辑文件
const GenerateFileListPlugin = require('XXX-XXX-XXX');
const AddMaxFileToHtmlPlugin = require('XXX-XXX-XXX');

module.exports = {
    // 其余无关配置省略

    configureWebpack: {
        plugins: [
            new AddMaxFileToHtmlPlugin()
        ]
    },

    chainWebpack(config) {
        config.plugin('generate-file-list').use(GenerateFileListPlugin);
    }
}


此处说明:两个文件可以都配置在 chainWebpack 中,不用疑惑为什么配在两个地方,多种方式,chain的执行机制是比config要早,但是决定逻辑执行并不是通过chain与config,这里只是放入函数的时机,并不代表就是函数执行的时机

GenerateFileListPlugin 生成 Size File

const fs = require('fs')
const path = require('path')

class GenerateFileListPlugin {
  constructor(outputPath) {
    this.outputPath = outputPath
  }

  apply(compiler) {
    const outputDir = path.resolve('dist')
    // 确保 dist 文件夹存在
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true }) // 使用 { recursive: true } 确保可以创建多层目录
    }
    compiler.hooks.emit.tapAsync('GenerateFileListPlugin', (compilation, callback) => {
      const files = Object.keys(compilation.assets)
        .filter(file => file.endsWith('.js'))
        .map(file => {
          console.log(compilation.assets[file])
          const size = compilation.assets[file].size()
          return { name: file, size }
        })

      const json = JSON.stringify({ files }, null, 2)
      fs.writeFileSync(path.resolve('dist', 'sizeJson.json'), json)
      callback()
    })
  }
}

module.exports = GenerateFileListPlugin

此文件用于Webpack打包文件时在写入盘之前触发并匹配所有JS文件对大小及名称进行记录,最后与其他文件一切写入到dist包中。


AddMaxFileToHtmlPlugin  写入代码

// plugins/AddMaxFileToHtmlPlugin.js
const fs = require('fs')
const path = require('path')

class AddMaxFileToHtmlPlugin {
  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync('AddMaxFileToHtmlPlugin', (compilation, callback) => {
      const distDir = path.resolve('dist')
      // 检查 dist 文件夹是否存在
      fs.stat(distDir, (err) => {
        if (err) {
          // 如果文件夹不存在,跳过后续操作
          return callback()
        }
        const jsonFilePath = path.resolve('dist', 'sizeJson.json')

        // 读取 sizeJson.json 文件
        fs.readFile(jsonFilePath, 'utf-8', (err, data) => {
          if (err) return callback(err)

          const jsonData = JSON.parse(data)
          const maxFile = jsonData.files.reduce((max, file) => (file.size > max.size ? file : max), jsonData.files[0])
          const htmlFilePath = path.resolve('dist', 'index.html')

          // 读取 index.html 文件
          fs.readFile(htmlFilePath, 'utf-8', (err, htmlContent) => {
            if (err) return callback(err)

            if (!htmlContent.includes(maxFile.name)) {
              const updatedHtml = htmlContent.replace('</body>', `<script src="${maxFile.name}"></script>\n</body>`)
              fs.writeFile(htmlFilePath, updatedHtml, (err) => {
                if (err) return callback(err)
                console.log(`Added ${maxFile.name} to index.html`)
                callback()
              })
            } else {
              callback()
            }
          })
        })
      })
    })
  }
}

module.exports = AddMaxFileToHtmlPlugin

基于 Generate 生成解析后文件大小,在此步 emit Webpack写入后执行,读取最大文件并将其写入到入口文件 index.html。


写入功能扩展

描述:逻辑代码中也可以写个多个JS文件,不只是JS文件,像未写入到盘之前的dist包中文件都可以,仅针对被Webpack解析加密的文件。

匹配多种类型文件

在原有代码基础上加上类型数组并循环即可,并不复杂,下放以 JS CS举例。
 

const targetExtensions = ['.js', '.css'];
const files = Object.keys(compilation.assets)
    .filter(file => targetExtensions.some(ext => file.endsWith(ext)))
    .map(file => {
        return { name: file, size }
    });

多个文件处理与限

不同情况下可能出现多个文件过大,以下就对不同类型文件各自引入多个。

1. 引入多个文件

很简单排序截取即可
 

jsonData.files
    .sort((a, b) => b.size - a.size)
    .slice(0, 3);

2. 多类型同时引入多个
描述:此处直接将其包为函数,传入数据源,匹配类型如:JS、Css等,以及数量如下

function returnTypeFiles(files, type, num) {
  return files.filter(item => {
      return item.name.split('/')[1] === type
    })
      .map(item => {
        return item
      })
      .sort((a, b) => b.size - a.size)
      .slice(0, num);
}

const _JsFiles = returnTypeFiles(files, 'js', 3)

注意:此处AddMaxFileToHtmlPlugin文件种写入也需有相应调整,目前上方文件代码种是单个写入,需将fs.readFile(htmlFilePath) 内代码替换为下方代码

fs.readFile(htmlFilePath, 'utf-8', (err, htmlContent) => {
    if (err) return callback(err);
    
    const _JsFiles = returnTypeFiles(files, 'js', 3);
    let _scripts = '';
    
    _JsFiles.forEach(_fileInfo => {
        if (!htmlContent.includes(_fileInfo.name)) {
            _scripts += `<script src="${_fileInfo.name}"></script>\n`;
        }
    });
    
    // 无匹配结果则跳过
    if (!_scripts) {
        callback();
        return; // 增加return防止后续代码执行
    }
    
    const updatedHtml = htmlContent.replace('</body>', `${_scripts}</body>`);
    
    fs.writeFile(htmlFilePath, updatedHtml, (err) => {
        if (err) return callback(err);
        console.log(`Added ${maxFile.name} to index.html`);
        callback();
    });
});

以上代码以JS举例,如要JS、CS、JSON同时引入,只需将数组合并即可,{..._JsFiles, ..._CsFiles, ..._JsonFiles}。

如有问题点欢迎各大佬指出。
 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐