index.cjs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. 'use strict';
  2. const path = require('path');
  3. const vite = require('vite');
  4. const fs = require('fs');
  5. const fs$1 = require('fs-extra');
  6. const chalk = require('chalk');
  7. const zlib = require('zlib');
  8. const Debug = require('debug');
  9. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
  10. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  11. const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
  12. const fs__default$1 = /*#__PURE__*/_interopDefaultLegacy(fs$1);
  13. const chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
  14. const zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib);
  15. const Debug__default = /*#__PURE__*/_interopDefaultLegacy(Debug);
  16. const isFunction = (arg) => typeof arg === "function";
  17. const isRegExp = (arg) => Object.prototype.toString.call(arg) === "[object RegExp]";
  18. function readAllFile(root, reg) {
  19. let resultArr = [];
  20. try {
  21. if (fs__default.existsSync(root)) {
  22. const stat = fs__default.lstatSync(root);
  23. if (stat.isDirectory()) {
  24. const files = fs__default.readdirSync(root);
  25. files.forEach(function(file) {
  26. const t = readAllFile(path__default.join(root, "/", file), reg);
  27. resultArr = resultArr.concat(t);
  28. });
  29. } else {
  30. if (reg !== void 0) {
  31. if (isFunction(reg.test) && reg.test(root)) {
  32. resultArr.push(root);
  33. }
  34. } else {
  35. resultArr.push(root);
  36. }
  37. }
  38. }
  39. } catch (error) {
  40. throw error;
  41. }
  42. return resultArr;
  43. }
  44. const debug = Debug__default.debug("vite-plugin-compression");
  45. const extRE = /\.(js|mjs|json|css|html)$/i;
  46. const mtimeCache = /* @__PURE__ */ new Map();
  47. function index(options = {}) {
  48. let outputPath;
  49. let config;
  50. const emptyPlugin = {
  51. name: "vite:compression"
  52. };
  53. const {
  54. disable = false,
  55. filter = extRE,
  56. verbose = true,
  57. threshold = 1025,
  58. compressionOptions = {},
  59. deleteOriginFile = false,
  60. success = () => {
  61. }
  62. } = options;
  63. let { ext = "" } = options;
  64. const { algorithm = "gzip" } = options;
  65. if (algorithm === "gzip" && !ext) {
  66. ext = ".gz";
  67. }
  68. if (algorithm === "brotliCompress" && !ext) {
  69. ext = ".br";
  70. }
  71. if (disable) {
  72. return emptyPlugin;
  73. }
  74. debug("plugin options:", options);
  75. return {
  76. ...emptyPlugin,
  77. apply: "build",
  78. enforce: "post",
  79. configResolved(resolvedConfig) {
  80. config = resolvedConfig;
  81. outputPath = path__default.isAbsolute(config.build.outDir) ? config.build.outDir : path__default.join(config.root, config.build.outDir);
  82. debug("resolvedConfig:", resolvedConfig);
  83. },
  84. async closeBundle() {
  85. let files = readAllFile(outputPath) || [];
  86. debug("files:", files);
  87. if (!files.length)
  88. return;
  89. files = filterFiles(files, filter);
  90. const compressOptions = getCompressionOptions(algorithm, compressionOptions);
  91. const compressMap = /* @__PURE__ */ new Map();
  92. const handles = files.map(async (filePath) => {
  93. const { mtimeMs, size: oldSize } = await fs__default$1.stat(filePath);
  94. if (mtimeMs <= (mtimeCache.get(filePath) || 0) || oldSize < threshold)
  95. return;
  96. let content = await fs__default$1.readFile(filePath);
  97. if (deleteOriginFile) {
  98. fs__default$1.remove(filePath);
  99. }
  100. try {
  101. content = await compress(content, algorithm, compressOptions);
  102. } catch (error) {
  103. config.logger.error("compress error:" + filePath);
  104. }
  105. const size = content.byteLength;
  106. const cname = getOutputFileName(filePath, ext);
  107. compressMap.set(filePath, {
  108. size: size / 1024,
  109. oldSize: oldSize / 1024,
  110. cname
  111. });
  112. await fs__default$1.writeFile(cname, content);
  113. mtimeCache.set(filePath, Date.now());
  114. });
  115. return Promise.all(handles).then(() => {
  116. if (verbose) {
  117. handleOutputLogger(config, compressMap, algorithm);
  118. success();
  119. }
  120. });
  121. }
  122. };
  123. }
  124. function filterFiles(files, filter) {
  125. if (filter) {
  126. const isRe = isRegExp(filter);
  127. const isFn = isFunction(filter);
  128. files = files.filter((file) => {
  129. if (isRe) {
  130. return filter.test(file);
  131. }
  132. if (isFn) {
  133. return filter(file);
  134. }
  135. return true;
  136. });
  137. }
  138. return files;
  139. }
  140. function getCompressionOptions(algorithm = "", compressionOptions = {}) {
  141. const defaultOptions = {
  142. gzip: {
  143. level: zlib__default.constants.Z_BEST_COMPRESSION
  144. },
  145. deflate: {
  146. level: zlib__default.constants.Z_BEST_COMPRESSION
  147. },
  148. deflateRaw: {
  149. level: zlib__default.constants.Z_BEST_COMPRESSION
  150. },
  151. brotliCompress: {
  152. params: {
  153. [zlib__default.constants.BROTLI_PARAM_QUALITY]: zlib__default.constants.BROTLI_MAX_QUALITY,
  154. [zlib__default.constants.BROTLI_PARAM_MODE]: zlib__default.constants.BROTLI_MODE_TEXT
  155. }
  156. }
  157. };
  158. return {
  159. ...defaultOptions[algorithm],
  160. ...compressionOptions
  161. };
  162. }
  163. function compress(content, algorithm, options = {}) {
  164. return new Promise((resolve, reject) => {
  165. zlib__default[algorithm](content, options, (err, result) => err ? reject(err) : resolve(result));
  166. });
  167. }
  168. function getOutputFileName(filepath, ext) {
  169. const compressExt = ext.startsWith(".") ? ext : `.${ext}`;
  170. return `${filepath}${compressExt}`;
  171. }
  172. function handleOutputLogger(config, compressMap, algorithm) {
  173. config.logger.info(`
  174. ${chalk__default.cyan("\u2728 [vite-plugin-compression]:algorithm=" + algorithm)} - compressed file successfully: `);
  175. const keyLengths = Array.from(compressMap.keys(), (name) => name.length);
  176. const maxKeyLength = Math.max(...keyLengths);
  177. compressMap.forEach((value, name) => {
  178. const { size, oldSize, cname } = value;
  179. const rName = vite.normalizePath(cname).replace(vite.normalizePath(`${config.build.outDir}/`), "");
  180. const sizeStr = `${oldSize.toFixed(2)}kb / ${algorithm}: ${size.toFixed(2)}kb`;
  181. config.logger.info(chalk__default.dim(path__default.basename(config.build.outDir) + "/") + chalk__default.blueBright(rName) + " ".repeat(2 + maxKeyLength - name.length) + " " + chalk__default.dim(sizeStr));
  182. });
  183. config.logger.info("\n");
  184. }
  185. module.exports = index;