index.mjs 5.4 KB

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