背景
最近在发布j-weapons前端包的时候遇到了一个问题,引入CDN编译以后的JS文件,在浏览器中查看该脚本文件实际并没有执行。
问题过程
代码文件如下:
|
|
这里以axios为例,情况是如出一辙的。
控制台得到一个错误:
|
|
script src
文件并没有被执行。
赶紧看Network
对应资源文件的请求,如下图:
可以看到这里的Status
显示了(failed) net::ERR_BLOCKED_BY_ORB
(而General.Status Code
是200正常的)一番搜寻没找到有用的信息,AI介绍是浏览器应用层处理网络请求时的错误,既不属于HTTP也不是TCP的错误,暂不展开讨论这块。
接着看响应头,如图:
重点在Content-Type
,返回的竟然是text/plain
?
接着换另一个CDN:jsdelivr再试试:
|
|
同样,脚本也并没有被执行,查看响应头,如图:
Status Code
也是200,并没有出现(failed) net::ERR_BLOCKED_BY_ORB
,而响应类型变成了:Content-Type: application/node
。
我在本地用Nginx引入本地文件,试了一下,响应类型是
Content-Type: application/javascript
,能够正常执行脚本。证明文件本身没有问题,问题在于响应头的响应类型。
另外在Console
有一个错误:
|
|
这个错误就比较有意思。
MIME
它说CDN文件的MIME type,也就是指响应头里的Content-Type,是application/node
类型,不可以执行,并且启用了严格检查MIME type类型的模式,应该就是指响应头返回的X-Content-Type-Options: nosniff
。
稍加解释一下:
- MIME type也称为IANA媒体类型,是一种标准,用来表示文档、文件或一组数据的性质和格式,它在IETF的RFC 6838中进行了定义和标准化。它一般包含两个部分:类型和子类型,中间用
/
分割,不能有空格,例如:text/plain
、text/javascript
等。 - 而Content-Type则表示标头用于指定资源的原始媒体类型,就是指上面说的MIME类型,它是由服务端返回的。
它们都属于HTTP的知识。
MIME嗅探
假设我们有一个HTML标签:
|
|
当然你可能会看到有使用
type="application/javascript"
的,不过该MIME类型已被弃用。
一般情况下,我们是不需要加type="text/javascript"
的attributes,浏览器会默认将它视为text/javascript
。
浏览器在获取标签上的文件时,如果该文件的响应头没有Content-Type
或者是Content-type: application/octet-stream
的时候,就会通过读取文件的前几个字节(通常是512字节)分析文件内容的特征从而猜测MIME的类型,这个过程称为MIME嗅探。
MIME嗅探会带来安全问题,虽然我们给script src
标签指定了type="text/javascript"
,但是浏览器一般会根据服务端返回的MIME类型来处理资源,用户可能会因此在不知情的情况下,遭受到XSS攻击。
防御的措施一般有:
- 服务端始终响应正确的
Content-Type
,避免浏览器MIME嗅探的触发。 - 服务端携带
X-Content-Type-Options: nosniff
响应头,明确告知浏览器不要进行MIME嗅探。
总结
回归正题,也就是说报错是因为CDN返回的Content-Type
的MIME类型不是text/application
或者不是application/javascript
才导致script src
标签不执行的,猜测CDN是根据不同的文件后缀给出不同的Content-Type
:
- unpkg:
.cjs
的文件,Content-Type
是text/plain
,通过brose axios files文件右侧可以进一步确认。.map
,Content-Type
是application/json
。
- jsdelivr:
.cjs
,Content-Type
是application/node
。
这也解释了为什么我在本地用nginx打开页面是能够正常执行脚本的原因。
那为什么我打包以后的文件会是index.umd.cjs
的后缀的?
简单提一下,我用的是Vite打包工具,配置项如下:
|
|
正是fileName
和formats
含umd
的情况下,它会默认将entry
入口文件处理成index.umd.cjs
的后缀,解决这个问题只需要将fileName改成函数自定义返回即可,详情请自行查看文档。