美文网首页构建工具:Webpack
Webpack(二十三):合并资源请求, 实现combo功能

Webpack(二十三):合并资源请求, 实现combo功能

作者: CodeMT | 来源:发表于2020-10-13 09:36 被阅读0次

本文学习使用nodejs实现cssjs资源文件的合并请求功能,我们都知道在一个复杂的项目当中,可能会使用到很多第三方插件,虽然目前使用vue开发系统或者h5页面,vue组件够用,但是有的项目中会使用到类似于echarts这样的插件,或者第三方其他的插件,比如ztree.js这样的,那如果我们把所有js都打包到一个js文件当中,那么该js可能会变得非常大,或者我们可能会把他们单独打包一个个js文件去,然后在页面中分别一个个使用script标签去引入他们,但是这样会导致页面多个js请求。因此就想使用node来实现类似于combo功能,比如以下的js功能构造:

http://127.0.0.1:3001/jsplugins/??a.js,b.js

如上的js请求,会把a.js和b.js合并到一个请求里面去, 然后使用node就实现了combo功能。
首先我们来分析下上面的请求,该请求中的 ?? 是一个分隔符,分隔符前面是合并的文件路径,后面是合并资源文件名,多个文件名使用逗号(,)隔开,知道了该请求的基本原理之后,我们需要对该请求进行解析,解析完成后,分别读取该js文件内容,然后分别读取到内容后合并起来输出到浏览器中。

首先看下我们项目简单的目录架构如下:

### 目录结构如下:
demo1                                       # 工程名           
|   |--- node_modules                       # 所有的依赖包
|   |--- jsplugins
|   | |-- a.js
|   | |-- b.js
|   |--- app.js
|   |--- package.json

项目截图如下:

jsplugins/a.js 内容如下:

function testA() {
  console.log('A.js');  
}

jsplugins/b.js 内容如下:

function testB() {
  console.log('b.js');
}

当我们访问 http://127.0.0.1:3001/jsplugins/??a.js,b.js 请求后,资源文件如下:

如何实现呢?

app.js 一部分代码如下:

// 引入express模块
const express = require('express');

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

// 创建app对象
const app = express();

app.use((req, res, next) => {
  const urlInfo = parseURL(__dirname, req.url);
  console.log(urlInfo);
  if (urlInfo) {
    // 合并文件
    combineFiles(urlInfo.pathnames, (err, data) => {
      if (err) {
        res.writeHead(404);
        res.end(err.message);
      } else {
        res.writeHead(200, {
          'Content-Type': urlInfo.mime
        });
        res.end(data);
      }
    });
  }
});


// 定义服务器启动端口 
app.listen(3001, () => {
  console.log('app listening on port 3001');
});

如上代码,使用express实现一个简单的,端口号为3001的服务器,然后使用 app.use模块截取请求,比如我们现在在浏览器中访问 http://127.0.0.1:3001/jsplugins/??a.js,b.js 这个请求的时候,会对该请求进行解析,会调用 parseURL方法,该方法的代码如下:

let MIME = {
  '.css': 'text/css',
  '.js': 'application/javascript'
};

// 解析文件路径
function parseURL(root, url) {
  let base, 
    pastnames,
    separator;
  if (url.indexOf('??') > -1) {
    separator = url.split('??');
    base = separator[0];

    pathnames = separator[1].split(',').map((value) => {
      const filepath = path.join(root, base, value);
      return filepath;
    });
    return {
      mime: MIME[path.extname(pathnames[0])] || 'text/plain',
      pathnames: pathnames
    }
  }
  return null;
};

如上代码,给parseURL函数传递了两个参数,一个是 __dirname 和 req.url, 其中__dirname就是当前app.js文件的所在目录,因此会打印出该目录下全路径:/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件, req.url返回的是url中的所有信息,因此 req.url='/jsplugins/??a.js,b.js', 然后判断url中是否有 ?? 这样的,找到的话,就使用 ?? 分割,如下代码:

separator = url.split('??');
base = separator[0];

因此 base = '/jsplugins/', separator[1] = a.js,b.js了,然后再进行对 separator[1] 使用逗号(,) 分割变成数组进行遍历a.js和b.js了,遍历完成后,如代码 const filepath = path.join(root, base, value); 使用path.join()对路径进行合并,该方法将多个参数值字符串结合为一个路径字符串,path.join基本使用,看我这篇文章
(https://www.cnblogs.com/tugenhua0707/p/9944285.html#_labe1),

root = '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件'
base = '/jsplugins/';
value = 'a.js' 或 value = 'b.js';

因此 pathnames 的值最终变成如下的值:

[ '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/a.js',
  '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/b.js' ]

执行完parseURL后返回的是如下对象:

{ 
  mime: 'application/javascript',
  pathnames:
   [ '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/a.js',
     '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/b.js' 
   ] 
}

path.extname 的使用可以看如下这篇文章(https://www.cnblogs.com/tugenhua0707/p/9944285.html#_labe4),就是拿到路径的扩展名,那么拿到的扩展名就是 .js, 然后 mime = MIME[path.extname(pathnames[0])] || 'text/plain', 因此 mine = 'application/javascript' 了。

返回值后,就会执行如下代码:

if (urlInfo) {
  // 合并文件
  combineFiles(urlInfo.pathnames, (err, data) => {
    if (err) {
      res.writeHead(404);
      res.end(err.message);
    } else {
      res.writeHead(200, {
        'Content-Type': urlInfo.mime
      });
      res.end(data);
    }
  });
}

先合并文件,文件合并后,再执行回调,把合并后的js输出到浏览中,先看下 combineFiles 函数的方法代码如下:

//合并文件
function combineFiles(pathnames, callback) {
  const output = [];
  (function nextFunc(l, len){
    if (l < len) {
      fs.readFile(pathnames[l], (err, data) => {
        if (err) {
          callback(err);
        } else {
          output.push(data);
          nextFunc(l+1, len);
        }
      })
    } else {
      const data = Buffer.concat(output);
      callback(null, data);
    }
  })(0, pathnames.length);
}

首先该方法传了 pathnames 和callback回调,其中pathnames的值是如下:

[ '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/a.js',
  '/Users/tugenhua/个人demo/webpack-all-demo2/webpack-all-demo/webpack+node合并js资源请求文件/jsplugins/b.js' 
]

然后一个使用立即函数先执行,把 0, 和 长度参数传递进去,判断是否小于文件的长度,如果是的话,就是 fs中的读取文件方法 (readFile), 就依次读取文件,对 readFile读取文件的方法不熟悉的话,可以看这篇文章(https://www.cnblogs.com/tugenhua0707/p/9942886.html#_labe0), 读取完后使用 Buffer.concat进行拼接。最后把数据传给callback返回到回调函数里面去,执行回调函数,就把对应的内容输出到浏览器中了。

注意:
1. 使用 fs.readFile 方法,如果没有设置指定的编码,它会以字节的方式读取的,因此使用Buffer可以进行拼接。
2. 使用Buffer.concat拼接的时候,如果a.js或b.js有中文的话,会出现乱码,出现的原因是如果js文件是以默认的gbk保存的话,那么我们nodejs默认是utf8读取的,就会有乱码存在的,因此js文件如果是本地的话,尽量以utf8保存。如果不是utf8保存的话,出现了乱码,我们需要解决,下一篇文章就来折腾下 Buffer出现乱码的情况是如何解决的。

因此整个app.js 代码如下:

// 引入express模块
const express = require('express');

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

// 创建app对象
const app = express();

app.use((req, res, next) => {
  const urlInfo = parseURL(__dirname, req.url);
  console.log(urlInfo);
  if (urlInfo) {
    // 合并文件
    combineFiles(urlInfo.pathnames, (err, data) => {
      if (err) {
        res.writeHead(404);
        res.end(err.message);
      } else {
        res.writeHead(200, {
          'Content-Type': urlInfo.mime
        });
        res.end(data);
      }
    });
  }
});

let MIME = {
  '.css': 'text/css',
  '.js': 'application/javascript'
};

// 解析文件路径
function parseURL(root, url) {
  let base, 
    pastnames,
    separator;
  if (url.indexOf('??') > -1) {
    separator = url.split('??');
    base = separator[0];

    pathnames = separator[1].split(',').map((value) => {
      const filepath = path.join(root, base, value);
      return filepath;
    });
    return {
      mime: MIME[path.extname(pathnames[0])] || 'text/plain',
      pathnames: pathnames
    }
  }
  return null;
};

//合并文件
function combineFiles(pathnames, callback) {
  const output = [];
  (function nextFunc(l, len){
    if (l < len) {
      fs.readFile(pathnames[l], (err, data) => {
        if (err) {
          callback(err);
        } else {
          output.push(data);
          nextFunc(l+1, len);
        }
      })
    } else {
      const data = Buffer.concat(output);
      callback(null, data);
    }
  })(0, pathnames.length);
}
// 定义服务器启动端口 
app.listen(3001, () => {
  console.log('app listening on port 3001');
});

相关文章

  • Webpack(二十三):合并资源请求, 实现combo功能

    本文学习使用nodejs实现css或js资源文件的合并请求功能,我们都知道在一个复杂的项目当中,可能会使用到很多第...

  • 页面性能优化

    加载资源优化 1,静态资源的压缩合并,减少HTTP请求 资源的合并,可以减少http请求数量。资源的压缩,可以减少...

  • 前端性能优化汇总

    网络优化 减少请求文件数,资源合并(雪碧图、SVG(Iconfont)、JS/CSS文件合并) 减少资源体积,资源...

  • 前端性能优化常见方式

    1.资源的合并与压缩 原理:减少http请求,减小请求资源的大小 方式:html,css,js压缩,文件的合并,g...

  • 性能优化总结

    资源的合并与压缩 核心:减少http请求数量(合并)减少资源请求大小(压缩) html压缩:压缩这些在文本文件中有...

  • 合并俩个webpack的js配置文件

    首先需要安装webpack-merge实现配置文件合并npm install --save-dev webpack...

  • 学习作业-使用Webpack实现Vue项目的打包任务

    基本实现思路分析 基本功能需要实现对Vue项目中常用的各类资源文件的打包处理,使用Webpack的loader实现...

  • 前端性能优化-资源优化

    一、资源的压缩与合并 1.为什么要压缩和合并减少http请求数量减少请求资源大小 2.HTML压缩使用在线工具进行...

  • 前端的项目优化

    如何进行项目优化呢?1,文件合并 减少http请求,合并css js 文件2,文件体积压缩。利用webpack,G...

  • 网站性能优化

    1、资源压缩合并 (1)图片:雪碧图 (2)JS、CSS合并:利用项目构建工具webpack、grunt、gulp...

网友评论

    本文标题:Webpack(二十三):合并资源请求, 实现combo功能

    本文链接:https://www.haomeiwen.com/subject/ecophktx.html