2
\$\begingroup\$

The following is a webpack loader for Phaser asset files. The way this is supposed to work is that it looks at the json string, and finds all of values with "url" keys in the json object, and transforms that value to a function call with that value as the parameter. I need to assume any level of nesting of the json values.

module.exports = function(source){
  this.cacheable();  //comment this line out to run standalone outside webpack loader context
  var s = source;
  var re = /"url":\s*\".*,/g;
  var m;
  do {
    m = re.exec(source);
    if (!!m) {
      var path = m[0].split(':')[1].trim().slice(0, -1);
      s = s.replace(path, 'require(' + path + ')');
   } 
  } while (m);
  return s;
}

For example, the source could be the following as the parameter to the function (as a string)

{ 
    "level1": [{
            "type": "image",
            "key": "starwars",
            "url": "assets/pics/unknown-the_starwars_pic.png",
            "overwrite": false
        },{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ],
    "level2": [{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ]
}

and the return value would be a string of the following

{ 
    "level1": [{
            "type": "image",
            "key": "starwars",
            "url": require("assets/pics/unknown-the_starwars_pic.png"),
            "overwrite": false
        },{
            "type": "image",
            "key": "spaceship",
            "url": require("assets/pics/spaceship.png"),
            "overwrite": false
        }
    ],
    "level2": [{
            "type": "image",
            "key": "spaceship",
            "url": require("assets/pics/spaceship.png"),
            "overwrite": false
        }
    ]
}

How might I implement this in a more clear way that accounts for any nesting of the json objects?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Your current implementation doesn't work!

Look at this snippet, where I extracted the standalone function:

function exports(source) {
  var s = source;
  var re = /"url":\s*\".*,/g;
  var m;
  do {
    m = re.exec(source);
    if (!!m) {
      var path = m[0].split(':')[1].trim().slice(0, -1);
      s = s.replace(path, 'require(' + path + ')');
   } 
  } while (m);
  return s;
}
var source = {
      "level1": [{
            "type": "image",
            "key": "starwars",
            "url": "assets/pics/unknown-the_starwars_pic.png",
            "overwrite": false
        },{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ],
    "level2": [{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ]
};
document.write(exports(JSON.stringify(source)).replace(/overwrite/g, '<span style="color:red;">$&</span>'));

The result is that the overwrite key following the url one is part of the require(...).
It comes from the way you expressed the regexp:

/"url":\s*\".*,/

Since the regexp is greedy the ending .*, consumes almost the whole string, up to the very last ,. Then when you apply split(':') on it, you keep the overwrite attached at the end of your path.

To fix this issue, you'd better to use this regexp:

/"url":\s*\"[^"]*",/

Here is the same snippet, now with this new regexp:

function exports(source) {
  var s = source;
  var re = /"url":\s*"[^"]*",/g;
  var m;
  do {
    m = re.exec(source);
    if (!!m) {
      var path = m[0].split(':')[1].trim().slice(0, -1);
      s = s.replace(path, 'require(' + path + ')');
   } 
  } while (m);
  return s;
}
var source = {
      "level1": [{
            "type": "image",
            "key": "starwars",
            "url": "assets/pics/unknown-the_starwars_pic.png",
            "overwrite": false
        },{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ],
    "level2": [{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ]
};
document.write(exports(JSON.stringify(source)).replace(/overwrite/g, '<span style="color:red;">$&</span>'));

Well, as expected the overwrite is not included in the require(...).
But... now the require(...) itself is duplicated on the 2nd url occurrence, and omitted on the 3d one!

I din't try to look deeper on this second issue, because with the newly expressed regexp we clearly can do the job much more simply, like this:

function exports(source) {
  return source.replace(/"url"\s*:\s*"([^"]*)"/g, function(match, path) {
    return '"url":require("' + path + '")';
  });
}

Here is this version working:

function exports(source) {
  return source.replace(/"url"\s*:\s*"([^"]*)"/g, function(match, path) {
    return '"url":require("' + path + '")';
  });
}
var source = {
      "level1": [{
            "type": "image",
            "key": "starwars",
            "url": "assets/pics/unknown-the_starwars_pic.png",
            "overwrite": false
        },{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ],
    "level2": [{
            "type": "image",
            "key": "spaceship",
            "url": "assets/pics/spaceship.png",
            "overwrite": false
        }
    ]
};
document.write(exports(JSON.stringify(source)).replace(/overwrite/g, '<span style="color:red;">$&</span>'));

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.