Контекст
С помощью демонстрации я сейчас рефакторинг, у меня есть папка src
, которая содержит 196 МБ. Около 142 МБ состоят из двух бинарных файлов.
Около 2000 из оставшихся 2137 файлов (что составляет около 46 МБ) состоит из файлов JavaScript, большинство из которых принадлежат официальным и полным дистрибутивам двух крупных фреймворков. Самый большой файл JavaScript весит около 23 МБ. Это неминифицированный код, изначально написанный на C++ и скомпилированный с помощью emscripten в asm.
Я хотел написать скрипт Node.js, который копирует все мои файлы из пути src
в путь dist
и минимизирует каждый файл JS или CSS, который встречается на этом пути. К сожалению, количество и/или размер задействованных файлов JS, кажется, ломают мой сценарий.
Давайте пройдемся по шагам, которые я предпринял...
Шаг 1
Я начал с написания небольшого скрипта сборки, который копировал все данные из моей папки src
в мою папку dist
. Я был удивлен, узнав, что этот процесс завершается за считанные секунды.
Ниже приведен мой код для этого скрипта. Обратите внимание, что вам понадобится Node 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);