簡體   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