简体   繁体   English

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

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

Context 上下文

With a demo I'm currently refactoring, I have a src folder that contains 196 MB. 通过我正在重构的演示 ,我有一个包含196 MB的src文件夹。 About 142 MB consist of two binary files. 大约142 MB由两个二进制文件组成。

About 2000 of the remaining 2137 files (which is about 46 MB) consists of JavaScript files, most of which belong to the official and complete distributions of the two large frameworks. 其余2137个文件中约有2000个(大约46 MB)由JavaScript文件组成,其中大部分属于两个大型框架的官方和完整发行版。 The largest JavaScript file is about 23MB. 最大的JavaScript文件大约是23MB。 It is unminified code originally written in C++ and compiled - with emscripten - to asm . 这是unminified代码原文为C ++编译和-与emscripten -到ASM

I wanted to write a Node.js script that copies all of my files from the src path to the dist path and minifies every JS or CSS file it encounters along the way. 我想编写一个Node.js脚本,它将我的所有文件从src路径复制到dist路径,并缩小它遇到的每个JS或CSS文件。 Unfortunately, the number and/or size of JS files involved seems to break my script. 不幸的是,涉及的JS文件的数量和/或大小似乎打破了我的脚本。


Let's go through the steps I took... 让我们来看看我采取的步骤......

Step 1 步骤1

I started with writing a small build script that copied all data from my src folder to my dist folder. 我开始编写一个小的构建脚本,将我的src文件夹中的所有数据复制到我的dist文件夹中。 I was surprised to learn that this process finishes in a matter of seconds. 我很惊讶地发现这个过程在几秒钟内完成。

Herebelow is my code for this script. 以下是我的脚本代码。 Note that you'll need Node 8 to run that code. 请注意,您需要使用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);

Step 2 第2步

Tto minify my CSS files whenever I encountered them, I added CSS minification. 每当我遇到它们时,我都会缩小我的CSS文件,我添加了CSS缩小功能。

For that, I made the following modifications to my code. 为此,我对我的代码进行了以下修改。

First, I added this function : 首先,我添加了这个功能:

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

Then, I modified my copy function, like this : 然后,我修改了我的复制功能,如下所示:

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

So far, so good. 到现在为止还挺好。 Everything still runs smoothly at this stage. 在这个阶段,一切仍然顺利进行。

Step 3 第3步

Then, I did the same to minify my JS. 然后,我做了同样的事情来缩小我的JS。

So again, I added a new function : 再次,我添加了一个新功能:

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

Then, I modified my copy function again : 然后,我再次修改了我的复制功能:

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

The problem 问题

Here, things go wrong. 这里出了问题。 As the process keeps encountering more and more JS files, it keeps slowing down until the process seems to stop completely. 随着进程不断遇到越来越多的JS文件,它会一直在减速,直到进程似乎完全停止。

It appears that too many parallel processes get started and keep consuming more and more memory until no more memory is left and the process just dies silently. 似乎有太多并行进程启动并继续消耗越来越多的内存,直到不再留下内存并且进程只是默默地死掉。 I tried other minifiers besides UglifyJS, and I experienced the same issue for all of them. 我尝试了除UglifyJS之外的其他缩小器,我遇到了所有这些问题。 So the problem doesn't appear to be specific to UglifyJS. 所以问题似乎并不是特定于UglifyJS。

Any ideas how to fix this issue? 有任何想法如何解决这个问题?

This is the complete code : 这是完整的代码:

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

Easy fix: your whole problem is that you have no bounds to your parellization: 简单的解决方案:你的整个问题是你没有限制你的parellization:

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

You synchronously dispatch async operations. 您同步调度异步操作。 That means they return immediately without you waiting. 这意味着他们会立即返回而无需您等待。 You either need to make the operations sequential or set a bound to the operations running. 您需要使操作顺序或设置绑定到正在运行的操作。 This will make a list of functions: 这将列出一系列功能:

const copyOperations = list.map((item) => {
        return copy(path.join(source, item), path.join(target, item));
});

Then make them run in sequence : 然后让它们按顺序运行

const initialValue = Promise.resolve();
copyOperations.reduce((accumulatedPromise, nextFn) => {
    return accumulatedPromise.then(nextFn);
}, initialValue);

Now, if you want to wait for all of them to finish you need to return a promise, so the copy section of your code will look like this: 现在,如果你想等待所有这些都完成,你需要返回一个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; 
})

This will of course just copy one file at a time, and should you require more operations to be done concurrently you need a fancier mechanism. 这当然只是一次复制一个文件,如果您需要同时执行更多操作,则需要更高级的机制。 Try out this promise pooling mechanism where you can set a threshold, like require('os').cpus().length; 尝试这种承诺池机制 ,你可以设置一个阈值,如require('os').cpus().length;

Example of bounded parallellization using ES6 generator 使用ES6发生器的有界并行化示例

just replace the body of the then function above with this 用这个替换上面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 's suggestion didn't seem to help. Oligofren的建议似乎没有帮助。 Removing the 23 MB JS file did, however, fix the issue. 但是,删除23 MB的JS文件确实解决了这个问题。 So it looks like the problem was not the large number of files (as I suspected) but a file too big for NodeJs to handle. 所以看起来问题不是大量的文件(我怀疑),而是一个对于NodeJ来说太大的文件。 I suppose playing around with NodeJs's memory settings (eg. node --stack-size ) could fix that. 我想玩NodeJs的内存设置(例如node --stack-size )可以解决这个问题。

Anyway, while I still need a solution to get everything to work without removing the 23 MB file, I guess removing this one file from the files to be processed will have to do for now. 无论如何,虽然我仍然需要一个解决方案来让一切工作而不删除23 MB文件,我想从现在要处理的文件中删除这一个文件。 It's pretty much just a proof-of-concept I was working on anyway. 它几乎只是我正在努力的概念验证。

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

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