最近在做一个工具自动把外部的Web Worker文件引用内联化,即碰到代码里有new Worker('worker.js')
,就去读取worker.js
的代码,然后把原Web Woker引用内联化为:new Worker(window.URL.createObjectURL(new Blob(["/* worker.js的内容 */"])))
。这个功能看似简单,我一开始想用正则表达式来匹配出Web Worker的实例化参数,但发现有很多极端情况需要去做特殊考虑,最终在了解并尝试了JavaScript AST(Abstract Syntax Tree抽象语法树)Parser后,所有的问题迎刃而解。
首先我们看最简单的情况:
var myWorker = new Worker('worker.js');
很简单,用正则:new Worker\('(.*\.js)'\)
就能获取我们想拿的worker.js
。
那么如果引用是放在双引号中呢,这个正则就不奏效了。
var myWorker = new Worker("worker.js");
好吧,其实也难不倒我们,我们可以用正则的后向引用来解决。new Worker\((['"])(.*\.js)\1\)
可以匹配双引号和单引号两种情况。
那么如果文件后伴有查询字符串呢?
var myWorker = new Worker('worker.js?v=1.0');
可能就要用这个正则来匹配了:new Worker\((['"])(.*\.js)(\?.*)?\1\)
。不过我突然发现事情没这么简单了,看看下面这些变态的写法。
var myWorker = new Worker('worker.js?t=' + new Date().getTime());
var myWorker2 = new Worker('worker.js?isIE=' + (isIE() ? 'true' : 'false'));
var myWorker3 = new Worker(
'worker.js' +
'?v=1.0')
虽然这很少见,不过这些依然是合法的JavaScript语句,并且的确引用了worker.js
这个外部文件,理应被内联化。
好吧,即使我决定不考虑这些变态的写法,但我又发现下面的字符串也会被我的正则匹配到,即使发生概率很低,但绝对是一个bug。这颠覆了我用正则表达式来实现的想法。
var str = "new Worker('worker.js')"
看起来我需要的并不是一个正则表达式,而是要一个JavaScript解析器来解析JS代码找出真正的new Worker
表达式并拿到其中的纯字面量参数。
之前听说过JavaScript抽象语法树(AST)可以解析JS代码,在AST explorer实测后发现这正式我需要的,AST Parser可以准确找出所有的new Worker
表达式,并找到所有的参数和位置。
[
...
{
...
"init": {
"type": "NewExpression",
"start": 15,
"end": 64,
"callee": {
"type": "Identifier",
"start": 19,
"end": 25,
"name": "Worker"
},
"arguments": [
{
"type": "BinaryExpression",
"start": 26,
"end": 63,
"left": {
"type": "Literal",
"start": 26,
"end": 40,
"value": "worker.js?t=",
"raw": "'worker.js?t='"
},
"operator": "+",
"right": {
...
}
}
]
}
},
{
...
"init": {
"type": "NewExpression",
"start": 82,
"end": 141,
"callee": {
"type": "Identifier",
"start": 86,
"end": 92,
"name": "Worker"
},
"arguments": [
{
"type": "BinaryExpression",
"start": 93,
"end": 140,
"left": {
"type": "Literal",
"start": 93,
"end": 110,
"value": "worker.js?isIE=",
"raw": "'worker.js?isIE='"
},
"operator": "+",
"right": {
...
}
}
]
}
},
{
...
"init": {
"type": "NewExpression",
"start": 159,
"end": 201,
"callee": {
"type": "Identifier",
"start": 163,
"end": 169,
"name": "Worker"
},
"arguments": [
{
"type": "BinaryExpression",
"start": 175,
"end": 200,
"left": {
"type": "Literal",
"start": 175,
"end": 186,
"value": "worker.js",
"raw": "'worker.js'"
},
"operator": "+",
"right": {
"type": "Literal",
"start": 193,
"end": 200,
"value": "?v=1.0",
"raw": "'?v=1.0'"
}
}
]
}
}
]
最终我选择了Acorn作为AST解析器,并完成了这个Web Worker内联化工具:https://github.com/js1016/worker-inlinify。