如何同时或依次 minify/compress 数以千计的 JS 文件(包括一些大文件)而不会使控制台崩溃?
How to minify/compress thousands of JS files - including some large ones - at the same time or sequentially without crashing the console?
上下文
我目前正在重构a demo,我有一个包含 196 MB 的 src
文件夹。大约 142 MB 由两个二进制文件组成。
其余2137个文件中约有2000个(约46MB)由JavaScript个文件组成,其中大部分属于两大框架的官方完整发行版。最大的 JavaScript 文件大约有 23MB。它是最初用 C++ 编写并编译的原始代码 - emscripten - to asm.
我想编写一个 Node.js 脚本,将我的所有文件从 src
路径复制到 dist
路径并缩小每个 JS 或 CSS 文件一路上的相遇。不幸的是,涉及的 JS 文件的数量 and/or 似乎破坏了我的脚本。
让我们来看看我采取的步骤...
步骤 1
我开始编写一个小型构建脚本,将所有数据从我的 src
文件夹复制到我的 dist
文件夹。得知这个过程在几秒钟内完成,我感到很惊讶。
下面是我为这个脚本编写的代码。请注意,您需要节点 8 才能 运行 该代码。
const util = require('util');
const fs = require('fs');
const path = require('path');
const mkdir = util.promisify(require('mkdirp'));
const rmdir = util.promisify(require('rimraf'));
const ncp = util.promisify(require('ncp').ncp);
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const stat = util.promisify(fs.stat);
const moveFrom = path.join(__dirname,"../scr");
const moveTo = path.join(__dirname,"../dist");
var copyFile = function(source, target) {
return new Promise(function(resolve,reject){
const rd = fs.createReadStream(source);
rd.on('error', function(error){
reject(error);
});
const wr = fs.createWriteStream(target);
wr.on('error', function(error){
reject(error);
});
wr.on('close', function(){
resolve();
});
rd.pipe(wr);
});
};
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
var build = function(source, target) {
readdir(source)
.then(function(list) {
return rmdir(target).then(function(){
return list;
});
})
.then(function(list) {
return mkdir(target).then(function(){
return list;
});
}).then(function(list) {
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
}).catch(function(error){
console.error(error);
})
};
build(moveFrom, moveTo);
第 2 步
为了在遇到 CSS 文件时缩小它们,我添加了 CSS 缩小。
为此,我对我的代码进行了以下修改。
首先,我添加了这个功能:
var uglifyCSS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('ycssmin').cssmin(content), "utf8");
}).catch(function(error){
console.error(error);
});
}
然后,我修改了我的复制功能,像这样:
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
到目前为止,还不错。现阶段一切还 运行 顺利。
步骤 3
然后,我做了同样的事情来缩小我的 JS。
所以我又添加了一个新函数:
var uglifyJS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('uglify-js').minify(content).code, "utf8");
}).catch(function(error){
console.error(error);
});
}
然后,我又修改了我的复制功能:
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
case ".js":
return uglifyJS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
问题
在这里,事情出了问题。随着进程不断遇到越来越多的 JS 文件,它不断变慢,直到进程似乎完全停止。
似乎启动了太多并行进程并不断消耗越来越多的内存,直到没有更多内存剩余并且进程静静地结束。我尝试了除 UglifyJS 之外的其他压缩器,我对所有压缩器都遇到了同样的问题。所以问题似乎不是 UglifyJS 特有的。
有解决此问题的想法吗?
这是完整的代码:
const util = require('util');
const fs = require('fs');
const path = require('path');
const mkdir = util.promisify(require('mkdirp'));
const rmdir = util.promisify(require('rimraf'));
const ncp = util.promisify(require('ncp').ncp);
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const stat = util.promisify(fs.stat);
const moveFrom = path.join(__dirname,"../scr");
const moveTo = path.join(__dirname,"../dist");
var copyFile = function(source, target) {
return new Promise(function(resolve,reject){
const rd = fs.createReadStream(source);
rd.on('error', function(error){
reject(error);
});
const wr = fs.createWriteStream(target);
wr.on('error', function(error){
reject(error);
});
wr.on('close', function(){
resolve();
});
rd.pipe(wr);
});
};
var uglifyCSS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('ycssmin').cssmin(content), "utf8");
}).catch(function(error){
console.error(error);
});
}
var uglifyJS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('uglify-js').minify(content).code, "utf8");
}).catch(function(error){
console.error(error);
});
}
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
case ".js":
return uglifyJS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
var build = function(source, target) {
readdir(source)
.then(function(list) {
return rmdir(target).then(function(){
return list;
});
})
.then(function(list) {
return mkdir(target).then(function(){
return list;
});
}).then(function(list) {
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
}).catch(function(error){
console.error(error);
})
};
build(moveFrom, moveTo);
轻松解决:您的整个问题是您对并行化没有限制:
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
您同步分派异步操作。这意味着他们会立即 return,无需您等待。您要么需要使操作顺序进行,要么为操作设置一个界限 运行ning。这将生成一个函数列表:
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
然后按顺序运行:
const initialValue = Promise.resolve();
copyOperations.reduce((accumulatedPromise, nextFn) => {
return accumulatedPromise.then(nextFn);
}, initialValue);
现在,如果您想等待所有这些完成,您需要 return 一个承诺,因此代码的复制部分将如下所示:
.then(function(list) {
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
const allOperations = copyOperations.reduce((accumulatedPromise, nextFn) => {
return accumulatedPromise.then(nextFn);
}, Promise.resolve());
return allOperations;
})
当然,这只会一次复制一个文件,如果您需要同时完成更多操作,则需要更高级的机制。尝试 this promise pooling mechanism,您可以在其中设置阈值,例如 require('os').cpus().length;
使用 ES6 生成器的有界并行化示例
把上面的then
函数体替换成这个
const PromisePool = require('es6-promise-pool')
const maxProcesses = require('os').cpus().length;
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
const promiseGenerator = function *(){
copyOperations.forEach( operation => yield operation );
}
var pool = new PromisePool(promiseGenerator(), maxProcesses)
return pool.start()
.then(function () {
console.log('Complete')
});
Oligofren的建议似乎没有帮助。然而,删除 23 MB 的 JS 文件确实解决了这个问题。所以看起来问题不是大量文件(正如我所怀疑的那样),而是文件太大,NodeJs 无法处理。我想尝试使用 NodeJs 的内存设置(例如 node --stack-size
)可以解决这个问题。
无论如何,虽然我仍然需要一个解决方案来让一切正常工作而不删除 23 MB 的文件,但我想现在必须从要处理的文件中删除这个文件。无论如何,这几乎只是我正在研究的概念验证。
上下文
我目前正在重构a demo,我有一个包含 196 MB 的 src
文件夹。大约 142 MB 由两个二进制文件组成。
其余2137个文件中约有2000个(约46MB)由JavaScript个文件组成,其中大部分属于两大框架的官方完整发行版。最大的 JavaScript 文件大约有 23MB。它是最初用 C++ 编写并编译的原始代码 - emscripten - to asm.
我想编写一个 Node.js 脚本,将我的所有文件从 src
路径复制到 dist
路径并缩小每个 JS 或 CSS 文件一路上的相遇。不幸的是,涉及的 JS 文件的数量 and/or 似乎破坏了我的脚本。
让我们来看看我采取的步骤...
步骤 1
我开始编写一个小型构建脚本,将所有数据从我的 src
文件夹复制到我的 dist
文件夹。得知这个过程在几秒钟内完成,我感到很惊讶。
下面是我为这个脚本编写的代码。请注意,您需要节点 8 才能 运行 该代码。
const util = require('util');
const fs = require('fs');
const path = require('path');
const mkdir = util.promisify(require('mkdirp'));
const rmdir = util.promisify(require('rimraf'));
const ncp = util.promisify(require('ncp').ncp);
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const stat = util.promisify(fs.stat);
const moveFrom = path.join(__dirname,"../scr");
const moveTo = path.join(__dirname,"../dist");
var copyFile = function(source, target) {
return new Promise(function(resolve,reject){
const rd = fs.createReadStream(source);
rd.on('error', function(error){
reject(error);
});
const wr = fs.createWriteStream(target);
wr.on('error', function(error){
reject(error);
});
wr.on('close', function(){
resolve();
});
rd.pipe(wr);
});
};
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
var build = function(source, target) {
readdir(source)
.then(function(list) {
return rmdir(target).then(function(){
return list;
});
})
.then(function(list) {
return mkdir(target).then(function(){
return list;
});
}).then(function(list) {
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
}).catch(function(error){
console.error(error);
})
};
build(moveFrom, moveTo);
第 2 步
为了在遇到 CSS 文件时缩小它们,我添加了 CSS 缩小。
为此,我对我的代码进行了以下修改。
首先,我添加了这个功能:
var uglifyCSS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('ycssmin').cssmin(content), "utf8");
}).catch(function(error){
console.error(error);
});
}
然后,我修改了我的复制功能,像这样:
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
到目前为止,还不错。现阶段一切还 运行 顺利。
步骤 3
然后,我做了同样的事情来缩小我的 JS。
所以我又添加了一个新函数:
var uglifyJS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('uglify-js').minify(content).code, "utf8");
}).catch(function(error){
console.error(error);
});
}
然后,我又修改了我的复制功能:
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
case ".js":
return uglifyJS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
问题
在这里,事情出了问题。随着进程不断遇到越来越多的 JS 文件,它不断变慢,直到进程似乎完全停止。
似乎启动了太多并行进程并不断消耗越来越多的内存,直到没有更多内存剩余并且进程静静地结束。我尝试了除 UglifyJS 之外的其他压缩器,我对所有压缩器都遇到了同样的问题。所以问题似乎不是 UglifyJS 特有的。
有解决此问题的想法吗?
这是完整的代码:
const util = require('util');
const fs = require('fs');
const path = require('path');
const mkdir = util.promisify(require('mkdirp'));
const rmdir = util.promisify(require('rimraf'));
const ncp = util.promisify(require('ncp').ncp);
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const stat = util.promisify(fs.stat);
const moveFrom = path.join(__dirname,"../scr");
const moveTo = path.join(__dirname,"../dist");
var copyFile = function(source, target) {
return new Promise(function(resolve,reject){
const rd = fs.createReadStream(source);
rd.on('error', function(error){
reject(error);
});
const wr = fs.createWriteStream(target);
wr.on('error', function(error){
reject(error);
});
wr.on('close', function(){
resolve();
});
rd.pipe(wr);
});
};
var uglifyCSS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('ycssmin').cssmin(content), "utf8");
}).catch(function(error){
console.error(error);
});
}
var uglifyJS = function(source, target) {
readFile(source, "utf8")
.then(function(content){
return writeFile(target, require('uglify-js').minify(content).code, "utf8");
}).catch(function(error){
console.error(error);
});
}
var copy = function(source, target) {
stat(source)
.then(function(stat){
if(stat.isFile()) {
console.log("Copying file %s", source);
switch (path.extname(target)) {
case ".css":
return uglifyCSS(source, target);
case ".js":
return uglifyJS(source, target);
default:
return copyFile(source, target);
}
} else if( stat.isDirectory() ) {
return build(source, target);
}
}).catch(function(error){
console.error(error);
});
};
var build = function(source, target) {
readdir(source)
.then(function(list) {
return rmdir(target).then(function(){
return list;
});
})
.then(function(list) {
return mkdir(target).then(function(){
return list;
});
}).then(function(list) {
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
}).catch(function(error){
console.error(error);
})
};
build(moveFrom, moveTo);
轻松解决:您的整个问题是您对并行化没有限制:
list.forEach(function(item, index) {
copy(path.join(source, item), path.join(target, item));
});
您同步分派异步操作。这意味着他们会立即 return,无需您等待。您要么需要使操作顺序进行,要么为操作设置一个界限 运行ning。这将生成一个函数列表:
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
然后按顺序运行:
const initialValue = Promise.resolve();
copyOperations.reduce((accumulatedPromise, nextFn) => {
return accumulatedPromise.then(nextFn);
}, initialValue);
现在,如果您想等待所有这些完成,您需要 return 一个承诺,因此代码的复制部分将如下所示:
.then(function(list) {
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
const allOperations = copyOperations.reduce((accumulatedPromise, nextFn) => {
return accumulatedPromise.then(nextFn);
}, Promise.resolve());
return allOperations;
})
当然,这只会一次复制一个文件,如果您需要同时完成更多操作,则需要更高级的机制。尝试 this promise pooling mechanism,您可以在其中设置阈值,例如 require('os').cpus().length;
使用 ES6 生成器的有界并行化示例
把上面的then
函数体替换成这个
const PromisePool = require('es6-promise-pool')
const maxProcesses = require('os').cpus().length;
const copyOperations = list.map((item) => {
return copy(path.join(source, item), path.join(target, item));
});
const promiseGenerator = function *(){
copyOperations.forEach( operation => yield operation );
}
var pool = new PromisePool(promiseGenerator(), maxProcesses)
return pool.start()
.then(function () {
console.log('Complete')
});
Oligofren的建议似乎没有帮助。然而,删除 23 MB 的 JS 文件确实解决了这个问题。所以看起来问题不是大量文件(正如我所怀疑的那样),而是文件太大,NodeJs 无法处理。我想尝试使用 NodeJs 的内存设置(例如 node --stack-size
)可以解决这个问题。
无论如何,虽然我仍然需要一个解决方案来让一切正常工作而不删除 23 MB 的文件,但我想现在必须从要处理的文件中删除这个文件。无论如何,这几乎只是我正在研究的概念验证。