Node:通过 Uglify 压缩小程序代码
小程序的官方压缩,很不如人意,可以用 uglify 对其中的 js 进行混淆压缩.
参考: uglify-js 、uglify-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 做一些处理
未完,待续...