Repository: svg/svgo
Pull request #1322 (2021-02-14 08:39:12)

Make optimize synchronous
Ref https://github.com/svg/svgo/issues/1015

Looks like `sax` is synchronous and we do not need to listen "end"
event. This allows to avoid all callbacks and make `optimize` method
synchronous.

cc @gregberge @deepsweet 


Patch:
diff --git a/lib/svgo.js b/lib/svgo.js
index 7d6c2fa..24a8867 100755
--- a/lib/svgo.js
+++ b/lib/svgo.js
@@ -21,56 +21,37 @@ var SVGO = function(config) {
     this.config = CONFIG(config);
 };
 
-SVGO.prototype.optimize = function(svgstr, info) {
-    info = info || {};
-    return new Promise((resolve, reject) => {
-        if (this.config.error) {
-            reject(this.config.error);
-            return;
+SVGO.prototype.optimize = function(svgstr, info = {}) {
+    const config = this.config;
+    if (config.error) {
+        throw Error(config.error);
+    }
+    const maxPassCount = config.multipass ? 10 : 1;
+    let prevResultSize = Number.POSITIVE_INFINITY;
+    let svgjs = null;
+    for (let i = 0; i < maxPassCount; i += 1) {
+        svgjs = SVG2JS(svgstr);
+        if (svgjs.error == null) {
+            svgjs = PLUGINS(svgjs, info, config.plugins);
         }
-
-        var config = this.config,
-            maxPassCount = config.multipass ? 10 : 1,
-            counter = 0,
-            prevResultSize = Number.POSITIVE_INFINITY,
-            optimizeOnceCallback = (svgjs) => {
-                if (svgjs.error) {
-                    reject(svgjs.error);
-                    return;
-                }
-
-                info.multipassCount = counter;
-                if (++counter < maxPassCount && svgjs.data.length < prevResultSize) {
-                    prevResultSize = svgjs.data.length;
-                    this._optimizeOnce(svgjs.data, info, optimizeOnceCallback);
-                } else {
-                    if (config.datauri) {
-                        svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri);
-                    }
-                    if (info && info.path) {
-                        svgjs.path = info.path;
-                    }
-                    resolve(svgjs);
-                }
-            };
-
-        this._optimizeOnce(svgstr, info, optimizeOnceCallback);
-    });
-};
-
-SVGO.prototype._optimizeOnce = function(svgstr, info, callback) {
-    var config = this.config;
-
-    SVG2JS(svgstr, function(svgjs) {
+        svgjs = JS2SVG(svgjs, config.js2svg);
         if (svgjs.error) {
-            callback(svgjs);
-            return;
+            throw Error(svgjs.error);
         }
-
-        svgjs = PLUGINS(svgjs, info, config.plugins);
-
-        callback(JS2SVG(svgjs, config.js2svg));
-    });
+        info.multipassCount = i;
+        if (svgjs.data.length < prevResultSize) {
+            prevResultSize = svgjs.data.length
+        } else {
+            if (config.datauri) {
+                svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri);
+            }
+            if (info && info.path) {
+                svgjs.path = info.path;
+            }
+            return svgjs;
+        }
+    }
+    return svgjs;
 };
 
 /**
diff --git a/lib/svgo/coa.js b/lib/svgo/coa.js
index d743cc9..ea6c339 100644
--- a/lib/svgo/coa.js
+++ b/lib/svgo/coa.js
@@ -382,24 +382,23 @@ function processSVGData(config, info, data, output, input) {
     var startTime = Date.now(),
         prevFileSize = Buffer.byteLength(data, 'utf8');
 
-    return svgo.optimize(data, info).then(function(result) {
-        if (config.datauri) {
-            result.data = encodeSVGDatauri(result.data, config.datauri);
-        }
-        var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
-            processingTime = Date.now() - startTime;
+    const result = svgo.optimize(data, info);
+    if (config.datauri) {
+        result.data = encodeSVGDatauri(result.data, config.datauri);
+    }
+    var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
+        processingTime = Date.now() - startTime;
 
-        return writeOutput(input, output, result.data).then(function() {
-            if (!config.quiet && output != '-') {
-                if (input) {
-                    console.log(`\n${PATH.basename(input)}:`);
-                }
-                printTimeInfo(processingTime);
-                printProfitInfo(prevFileSize, resultFileSize);
+    return writeOutput(input, output, result.data).then(function() {
+        if (!config.quiet && output != '-') {
+            if (input) {
+                console.log(`\n${PATH.basename(input)}:`);
             }
-        },
-        error => Promise.reject(new Error(error.code === 'ENOTDIR' ? `Error: output '${output}' is not a directory.` : error)));
-    });
+            printTimeInfo(processingTime);
+            printProfitInfo(prevFileSize, resultFileSize);
+        }
+    },
+    error => Promise.reject(new Error(error.code === 'ENOTDIR' ? `Error: output '${output}' is not a directory.` : error)));
 }
 
 /**
diff --git a/lib/svgo/svg2js.js b/lib/svgo/svg2js.js
index 6bba13e..85a38b5 100644
--- a/lib/svgo/svg2js.js
+++ b/lib/svgo/svg2js.js
@@ -19,16 +19,14 @@ var config = {
  * Convert SVG (XML) string to SVG-as-JS object.
  *
  * @param {String} data input data
- * @param {Function} callback
  */
-module.exports = function(data, callback) {
+module.exports = function(data) {
 
     var sax = SAX.parser(config.strict, config),
         root = new JSAPI({ elem: '#document', content: [] }),
         current = root,
         stack = [root],
-        textContext = null,
-        parsingError = false;
+        textContext = null;
 
     function pushToContent(content) {
 
@@ -163,23 +161,12 @@ module.exports = function(data, callback) {
 
     };
 
-    sax.onend = function() {
-
-        if (!this.error) {
-            callback(root);
-        } else {
-            callback({ error: this.error.message });
-        }
-
-    };
-
     try {
-        sax.write(data);
+        sax.write(data).close();
+        return root;
     } catch (e) {
-        callback({ error: e.message });
-        parsingError = true;
+        return { error: e.message };
     }
-    if (!parsingError) sax.close();
 
     function trim(elem) {
         if (!elem.content) return elem;
diff --git a/package.json b/package.json
index 1a142e5..df288bf 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,11 @@
       "name": "Lev Solntsev",
       "email": "lev.sun@ya.ru",
       "url": "http://github.com/GreLI"
+    },
+    {
+      "name": "Bogdan Chadkin",
+      "email": "trysound@yandex.ru",
+      "url": "http://github.com/TrySound"
     }
   ],
   "repository": {


Test patch:
diff --git a/examples/test.js b/examples/test.js
index e4f4fa4..bf5801c 100644
--- a/examples/test.js
+++ b/examples/test.js
@@ -82,20 +82,18 @@ FS.readFile(filepath, 'utf8', function(err, data) {
         throw err;
     }
 
-    svgo.optimize(data, {path: filepath}).then(function(result) {
+    const result = svgo.optimize(data, {path: filepath});
 
-        console.log(result);
+    console.log(result);
 
-        // {
-        //     // optimized SVG data string
-        //     data: '<svg width="10" height="20">test</svg>'
-        //     // additional info such as width/height
-        //     info: {
-        //         width: '10',
-        //         height: '20'
-        //     }
-        // }
-
-    });
+    // {
+    //     // optimized SVG data string
+    //     data: '<svg width="10" height="20">test</svg>'
+    //     // additional info such as width/height
+    //     info: {
+    //         width: '10',
+    //         height: '20'
+    //     }
+    // }
 
 });
diff --git a/test/plugins/_index.js b/test/plugins/_index.js
index 0209dd5..c66c83e 100644
--- a/test/plugins/_index.js
+++ b/test/plugins/_index.js
@@ -44,10 +44,9 @@ describe('plugins tests', function() {
                         js2svg  : { pretty: true }
                     });
 
-                    return svgo.optimize(orig, {path: file}).then(function(result) {
-                        //FIXME: results.data has a '\n' at the end while it should not
-                        normalize(result.data).should.be.equal(should);
-                    });
+                    const result = svgo.optimize(orig, {path: file});
+                    //FIXME: results.data has a '\n' at the end while it should not
+                    normalize(result.data).should.be.equal(should);
                 });
 
             });
@@ -69,4 +68,4 @@ function readFile(file) {
             resolve(data);
         });
     });
-}
\ No newline at end of file
+}
diff --git a/test/svg2js/_index.js b/test/svg2js/_index.js
index 7528d34..b93a83d 100644
--- a/test/svg2js/_index.js
+++ b/test/svg2js/_index.js
@@ -30,10 +30,8 @@ describe('svg2js', function() {
                     throw err;
                 }
 
-                SVG2JS(data, function(result) {
-                    root = result;
-                    done();
-                });
+                root = SVG2JS(data)
+                done();
             });
 
         });
@@ -423,9 +421,7 @@ describe('svg2js', function() {
                 }
 
                 try {
-                    SVG2JS(data, function(result) {
-                        root = result;
-                    });
+                    root = SVG2JS(data)
                 } catch (e) {
                     error = e;
                 }
@@ -473,9 +469,7 @@ describe('svg2js', function() {
             FS.readFile(filepath, 'utf8', function(err, data) {
                 if (err) throw err;
 
-                SVG2JS(data, function(result) {
-                    root = result;
-                });
+                root = SVG2JS(data);
                 done();
             });
         });
diff --git a/test/svgo/_index.js b/test/svgo/_index.js
index 327c5de..c8552a3 100644
--- a/test/svgo/_index.js
+++ b/test/svgo/_index.js
@@ -30,10 +30,9 @@ describe('indentation', function() {
                 js2svg  : { pretty: true, indent: 2 }
             });
 
-            svgo.optimize(orig, {path: filepath}).then(function(result) {
-                normalize(result.data).should.be.equal(should);
-                done();
-            });
+            const result = svgo.optimize(orig, {path: filepath});
+            normalize(result.data).should.be.equal(should);
+            done();
 
         });