繁体   English   中英

如何在不崩溃控制台的情况下同时或按顺序缩小/压缩数千个JS文件(包括一些大文件)?

[英]How to minify/compress thousands of JS files - including some large ones - at the same time or sequentially without crashing the console?

上下文

通过我正在重构的演示 ,我有一个包含196 MB的src文件夹。 大约142 MB由两个二进制文件组成。

其余2137个文件中约有2000个(大约46 MB)由JavaScript文件组成,其中大部分属于两个大型框架的官方和完整发行版。 最大的JavaScript文件大约是23MB。 这是unminified代码原文为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);

简单的解决方案:你的整个问题是你没有限制你的parellization:

list.forEach(function(item, index) {
        copy(path.join(source, item), path.join(target, item));
});

您同步调度异步操作。 这意味着他们会立即返回而无需您等待。 您需要使操作顺序或设置绑定到正在运行的操作。 这将列出一系列功能:

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);

现在,如果你想等待所有这些都完成,你需要返回一个promise,所以你的代码的副本部分将如下所示:

.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; 
})

这当然只是一次复制一个文件,如果您需要同时执行更多操作,则需要更高级的机制。 尝试这种承诺池机制 ,你可以设置一个阈值,如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文件确实解决了这个问题。 所以看起来问题不是大量的文件(我怀疑),而是一个对于NodeJ来说太大的文件。 我想玩NodeJs的内存设置(例如node --stack-size )可以解决这个问题。

无论如何,虽然我仍然需要一个解决方案来让一切工作而不删除23 MB文件,我想从现在要处理的文件中删除这一个文件。 它几乎只是我正在努力的概念验证。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM