Node:通過 Uglify 壓縮小程序代碼


小程序的官方壓縮,很不如人意,可以用 uglify 對其中的 js 進行混淆壓縮.
參考: uglify-jsuglify-es 文檔

1. 安裝 Uglify

首先安裝 uglify:

npm install uglify-es -D

注意,這里用的是 uglify-es,而不是 uglify-js,原因是 uglify-js 只支持 ECMAScript 5 (ES5),若想壓縮 ES2015+ (ES6+)代碼,應該使用 uglify-es這個npm 包。
裝好后,可以測試一下是否能正常運行,准備一個 test.js,寫入幾行測試代碼,然后構建一個 package.json,對test.js 進行壓縮


{
  "devDependencies": {
    "uglify-es": "^3.3.9"
  },
  "scripts": {
    "start": "uglifyjs test.js"
  }
}

執行 npm start,即可看到控制台輸出壓縮后代碼。

2. 配置 Uglify API

創建 uglify.config.js,根據 uglify 文檔,做基礎配置:

const UglifyJS = require('uglify-es'),
  fs = require('fs');
const options = {
  // 解析配置
  parse: {},
  // 壓縮配置
  compress: {
    drop_console: true,
  },
  // 混淆配置
  mangle: {},
  // 輸出配置
  output: {
    comments: false,    // 移除注釋
  },
  sourceMap: {},
  ecma: 8,  // specify one of: 5, 6, 7 or 8
  keep_fnames: false,   // 防止丟棄或損壞函數名
  keep_classnames: false,
  toplevel: false,    // 混淆最高作用域中的變量和函數名
  warnings: false,
}
// 讀取文件代碼
let code = fs.readFileSync('./test.js', "utf8");
// uglify 壓縮
let result = UglifyJS.minify(code, options);
// 寫入到指定文件
fs.writeFileSync('./test.min.js', result.code)

在 package.json 中增加一行命令:"build": "node uglify.config.js"
然后npm run build執行后,在同級目錄下,生成一個 test.min.js。

接下來要考慮的,是怎么進行工程化。
單個js文件的壓縮簡單,但是文件多起來,就沒法做到每次壓縮都手動進行,所以要考慮做一個遍歷,對小程序代碼文件夾中的所有文件進行分析,壓縮js文件,復制非js文件,最后統一輸出到 指定目錄下。

首先是,查詢目錄,遍歷文件:


// 這里引入 path 模塊
const path = require('path');

const handle = (src, dist) => {
  // 解析一個目錄,遍歷目錄下一級所有資源
  //   若是目錄則繼續解析,
  //   若是文件則判斷壓縮或者復制
  let paths = fs.readdirSync(src); //查詢當前目錄下內容
  paths.forEach (p => {
    // 解析輸入、輸出路徑
    let full_src = path.resolve(src, p);
    let full_dist = path.resolve(dist, p);
    // console.log(`>${p}: ${full_src} --> ${full_dist}`)

    fs.stat(full_src, (err, stats) => {
      if (err) throw err;
      if (stats.isFile()) {
        // 文件
        if (/.js$/.test(full_src)) {
          console.log('正在壓縮js:' + full_src)
          let code = fs.readFileSync(full_src, "utf8");
          let result = UglifyJS.minify(code, options);
          fs.writeFileSync(full_dist, result.code)
        } else {
          let readable = fs.createReadStream(full_src);
          let writable = fs.createWriteStream(full_dist);
          readable.pipe(writable);
        }
      } else if (stats.isDirectory()) { 
        //目錄 遞歸
        checkDirectory(full_src, full_dist, handle);
      }
    });
  });
}
const checkDirectory = (src, dist, callback) => {
  // 查詢輸出目錄
  fs.access(dist, fs.constants.F_OK, (err) => {
    if (err) {
      // 不存在此目錄,則創建
      fs.mkdirSync(dist);
      // 隨后執行目錄解析操作
      callback(src, dist);
    } else {
      callback(src, dist);
    }
  });
};

// 執行
checkDirectory('./src', './dist', handle);

能運行起來,但存在一個問題,所有的資源都進行了操作。這時候需要一個 ignore 索引:


const ignore = [
  './node_modules',
  './dist',
  './package.json',
  './package-lock.json',
]
const checkIgnore = (src, full_path) => {
  let ignoreList = ignore.map(i => path.resolve(i))
  if (ignoreList.indexOf(src) >= 0) return true;
  if (full_path.indexOf('.') === 0) return true;
  if (/\.md$/.test(full_path)) return true;
}

壓縮前,如果能清空輸出目錄就更清晰了:


const delPath = (url, isRoot = false) => {
  if (/^(\/)|^(\.\.\/)|^(\.\/)$/.test(url)) {
    console.warn("發現危險操作");
    throw new Error("發現危險操作..");
  }
  url = path.resolve(url)
  if (!fs.existsSync(url)) {
    console.warn("路徑不存在");
    return "路徑不存在";
  }
  let info = fs.statSync(url);
  if (info.isDirectory()) { 
    //目錄
    let paths = fs.readdirSync(url);
    // console.log(paths)
    if (paths.length > 0) {
      for (let i = 0; i < paths.length; i++) {
        delPath(`${url}/${paths[i]}`); 
        if (i == paths.length - 1 && !isRoot) { 
          //刪掉當前目錄
          delPath(`${url}`);
        }
      }
    } else { 
      //刪除空目錄
      !isRoot && fs.rmdirSync(url),console.log('刪除目錄:', url)
    }
  } else if (info.isFile()) {
    //刪除文件
    fs.unlinkSync(url); 
    // console.log('刪除:', url)
  }
}

然后調整一下配置結構,提取配置項:


const UglifyJS = require('uglify-es'),
  fs = require('fs'),
  path = require('path');

const config = {
  entry: './src',
  output: './dist',
  ignore: [
    './node_modules',
    './dist',
    './index.js',
    './package.json',
    './package-lock.json',
  ],
  options: {
    // 解析配置
    parse: {},
    // 壓縮配置
    compress: {
      drop_console: true,
    },
    // 混淆配置
    mangle: {},
    // 輸出配置
    output: {
      comments: false,    // 移除注釋
    },
    sourceMap: {},
    ecma: 8,  // specify one of: 5, 6, 7 or 8
    keep_fnames: false,   // 防止丟棄或損壞函數名
    keep_classnames: false,
    toplevel: true,    // 混淆最高作用域中的變量和函數名
    warnings: false,
  }
}

const handle = (src, dist) => {
  // 解析一個目錄,遍歷目錄下一級所有資源
  //   若是目錄則繼續解析,
  //   若是文件則判斷壓縮或者復制
  let paths = fs.readdirSync(src); //查詢當前目錄下內容
  paths.forEach (p => {
    // 解析輸入、輸出路徑
    let full_src = path.resolve(src, p);
    let full_dist = path.resolve(dist, p);
    // console.log(`>${p}: ${full_src} --> ${full_dist}`)

    // 判斷 ignore
    if (checkIgnore(full_src, p)) return console.log('忽略:', p);

    fs.stat(full_src, (err, stats) => {
      if (err) throw err;
      if (stats.isFile()) {
        // 文件
        if (/.js$/.test(full_src)) {
          console.log('正在壓縮js:' + full_src)
          let code = fs.readFileSync(full_src, "utf8");
          let result = UglifyJS.minify(code, config.options);
          fs.writeFileSync(full_dist, result.code)
        } else {
          let readable = fs.createReadStream(full_src);
          let writable = fs.createWriteStream(full_dist);
          readable.pipe(writable);
        }
      } else if (stats.isDirectory()) { 
        //目錄 遞歸
        checkDirectory(full_src, full_dist, handle);
      }
    });
  });
}
const checkDirectory = (src, dist, callback) => {
  // 查詢輸出目錄
  fs.access(dist, fs.constants.F_OK, (err) => {
    if (err) {
      // 不存在此目錄,則創建
      fs.mkdirSync(dist);
      // 隨后執行目錄解析操作
      callback(src, dist);
    } else {
      callback(src, dist);
    }
  });
};

const checkIgnore = (src, full_path) => {
  let ignoreList = config.ignore.map(i => path.resolve(config.entry, i))
  if (ignoreList.indexOf(src) >= 0) return true;
  if (full_path.indexOf('.') === 0) return true;
  if (/\.md$/.test(full_path)) return true;
}

const delPath = (url, isRoot = false) => {
  if (/^(\/)|^(\.\.\/)|^(\.\/)$/.test(url)) {
    console.warn("發現危險操作");
    throw new Error("發現危險操作..");
  }
  url = path.resolve(url)
  if (!fs.existsSync(url)) {
    console.warn("路徑不存在");
    return "路徑不存在";
  }
  let info = fs.statSync(url);
  if (info.isDirectory()) { 
    //目錄
    let paths = fs.readdirSync(url);
    // console.log(paths)
    if (paths.length > 0) {
      for (let i = 0; i < paths.length; i++) {
        delPath(`${url}/${paths[i]}`); 
        if (i == paths.length - 1 && !isRoot) { 
          //刪掉當前目錄
          delPath(`${url}`);
        }
      }
    } else { 
      //刪除空目錄
      !isRoot && fs.rmdirSync(url),console.log('刪除目錄:', url)
    }
  } else if (info.isFile()) {
    //刪除文件
    fs.unlinkSync(url); 
    // console.log('刪除:', url)
  }
}

// 執行
delPath(config.output, true)
checkDirectory(config.entry, config.output, handle);

以及 package.json:


{
  "devDependencies": {
    "uglify-es": "^3.3.9"
  },
  "scripts": {
    "start": "uglifyjs test.js",
    "build": "node uglify.config.js"
  }
}

后續可以針對 wxss、wxml 做一些處理

未完,待續...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM