Reflexiones sobre la codificación de URL provocadas por un pequeño error

Escenas

Cuando estaba optimizando el rendimiento recientemente, descubrí que una imagen que quería cargar con anticipación no llegaba a la memoria caché del navegador. El entorno de desarrollo verificó claramente que era buena, pero cuando me conecté, descubrí que las dos mismas imágenes se cargaron debido a la falla y el rendimiento fue negativo .

Entonces, ¿qué pasó para causar la señorita?

Hablemos primero de la situación actual:

Para un proyecto ordinario vue, al desarrollar el entorno, escribí un estilo simple y establecí una imagen de fondo

.xx {
  background-image: url(https://xx.cdn/xx.jpg?a=/nooo/nooo)
}
复制代码

Luego, para que esta imagen se cargue antes, cuando esté a punto de mostrarse, se puede mostrar rápidamente ( porque el navegador encontrará que hay un caché en este momento ) para optimizar la experiencia del usuario, por lo que en una posición previa , escribí un JS simple para avanzar en la carga

const img = new Image()
img.src = 'https://xx.cdn/xx.jpg?a=/nooo/nooo'
复制代码

Fue bueno durante el desarrollo, y lo miraré después del empaquetado. El estilo ha cambiado.

.xx{background-image:url(https://xx.cdn/xx.jpg?a=%2Fnooo%2Fnooo)}
复制代码

El núcleo es que la parte de la dirección de la imagen se cambiaquerya=/nooo/noooencodea=%2Fnooo%2Fnooo , y el navegador considera directamente estas dos como imágenes diferentes, lo que da como resultado que no haya ningún golpe de caché.

Entonces, ¿quién lo hizo?

De hecho, al ver este estado, hay una conclusión directa en mi mente: se estima que cuando se empaquetó, qué loadercomplemento o complemento hizo queryla valueparte en el enlaceencodeURIComponent

encodeURIComponent('/nooo/nooo') === '%2Fnooo%2Fnooo'
// encodeURI 不会处理 '/'
encodeURI('/nooo/nooo') === '/nooo/nooo'
复制代码

Siguiendo esta línea de pensamiento, lo encontré, ¡pero no lo encontré en absoluto! ! !

Parece que todavía estoy pensando de manera demasiado simple, y solo puedo pasar por un período de investigación que varía de persona a persona ( no estoy webpacktan familiarizado con los relevantes, así que dediqué algún tiempoCSS ), y descubrí que en el último paso de optimización, el enlace fue bloqueado encode. . Y @vue/cli-servicela v4versión que uso aquí, la lógica de optimización en su código es la siguiente

// @vue/cli-service

const cssnanoOptions = {
  preset: ['default', {
    mergeLonghand: false,
    cssDeclarationSorter: false
  }]
}
if (rootOptions.productionSourceMap && sourceMap) {
  cssnanoOptions.map = { inline: false }
}

if (isProd) {
  webpackConfig
    .plugin('optimize-css')
      .use(require('@intervolga/optimize-cssnano-plugin'), [{
        sourceMap: rootOptions.productionSourceMap && sourceMap,
        cssnanoOptions
      }])
}
复制代码

也就是说调用了@intervolga/optimize-cssnano-plugin来优化CSS代码的,而这个包是调用cssnano来进行优化

// @intervolga/optimize-cssnano-plugin

const cssnano = require('cssnano');

const promise = postcss([cssnano(cssnanoOptions)]).
	...
复制代码

再看cssnano涉及到链接处理的包,最像的应该是postcss-normalize-url,不过文档上并没有说进行这个优化会导致encode,只能继续看其源码,找到一个最像的:其会调用normalize-url来优化链接

// cssnano/packages/postcss-normalize-url

import normalize from 'normalize-url';

normalizedURL = normalize(url, options);
复制代码

再来看normalize-url里面做了啥。里面完全没有任何encode的代码,而最可能导致被encode的操作是:

// normalize-url

const urlObj = new URL(urlString);

// Sort query parameters
if (opts.sortQueryParameters) {
  urlObj.searchParams.sort();
}
复制代码

难道sort会导致链接被encode?试了一下,确实会:

var a = new URL('https://xxx.com/add?a=/9&b=77')
console.log(a.search)
// ?a=/9&b=77

a.searchParams.sort()
console.log(a.search)
// ?a=%2F9&b=77
复制代码

其实,对于URLSearchParams的增、删、改之类的操作,都会导致参数被encode,猜测是因为这些操作会让其先decode,处理之后再encode回去导致的。

那,怎么解决?

原因知道了,解决方案也就很明确了,主要是两个方向:

1. 改自身代码

严格意义上来说,本身写https://xx.cdn/xx.jpg?a=/nooo/nooo就不太规范,应该将query里面的参数都进行encode,也就是把代码中涉及的地方,都改成标准的,类似https://xx.cdn/xx.jpg?a=%2Fnooo%2Fnooo

2. 改打包配置

既然是因为打包时sort引起的,那就不要sort即可,另外,额外研究后,可以发现cssnanov5.0.11时,将这个当做Bug来修复了,默认不开启sortQueryParameters

5.0.11 (2021-11-16)

Bug fixes

  • c38f14c3ce3d0: postcss-normalize-url: avoid changing parameter encoding

另外,其实@vue/cli-service最新的v5版本,也已经使用了新版本的cssnano,正常也不会有这样问题。

既然这样,那么不使用sort似乎也可以理解,而在@vue/cli-servicev4版本时,可以通过修改配置来关闭

// vue.config.js

module.exports = {
  chainWebpack: config => {
    // 注意只有正式环境才需要
    if (process.env.NODE_ENV === 'production') {
      config.plugin('optimize-css').tap(([options]) => {
        // 直接改掉配置,虽然不太喜欢这样hack的写法
        options.cssnanoOptions.preset[1].normalizeUrl = {
          sortQueryParameters: false,
        }
        return [options]
      })
    }
  }
}
复制代码

结束了?

我们似乎找到了原因,而且也找到了解决方案,从问题本身来说,已经结束了。

但是,URLSearchParams的操作导致的encodeencodeURIComponent是什么关系?是否等同呢?

要回答这个问题,也就引出了Percent-encoding

Percent-encoding

Percent-encoding, also known as URL encoding

说是Percent-encoding,直译过来就是百分号编码,其实就是URL编码,核心逻辑也很简单,基本上百分号加上两位十六进制的字符。其详细的编码过程,在whatwg上有,在percent-encoded-bytes 可以查看

URLSearchParamsencodeURIComponent都是Percent-encoding,所以它们的的基本逻辑是一致的,只不过它们的编码范围不一致

根据whatwgencodeURIComponent的编码范围为component percent-encode set

The component percent-encode set is the userinfo percent-encode set and U+0024 ($) to U+0026 (&), inclusive, U+002B (+), and U+002C (,).

URLSearchParams涉及的编码范围是application/x-www-form-urlencoded percent-encode set

The application/x-www-form-urlencoded percent-encode set is the component percent-encode set and U+0021 (!), U+0027 (') to U+0029 RIGHT PARENTHESIS, inclusive, and U+007E (~).

包括了encodeURIComponent的编码范围,还额外包含了 !、' 等字符

还有一个非常重要的区别:URLSearchParams会把空格编码成加号(+),而encodeURIComponent则会编码成%20!

URLSearchParams objects will percent-encode anything in the application/x-www-form-urlencoded percent-encode set, and will encode U+0020 SPACE as U+002B (+).

有了这个了解之后,再回头看之前的解决方案1(改自己代码),就要十分小心了,尤其是正好卡在URLSearchParamsencodeURIComponent之间有所区别的字符:

var a = new URL('https://xxx.com/add?a=%209&b=!77')
console.log(a.search)
// ?a=%209&b=!77

a.searchParams.sort()
console.log(a.search)
// ?a=+9&b=%2177
复制代码

上述例子中,我们以为已经编码了(以为是和encodeURIComponent等效),结果就是被打脸。

这么看起来,还是第二个解决方案比较靠谱一点

最后

简单总结一下就是:

  • @vue/cli-servicev4版本在CSS优化压缩时,会默认使用URLSearchParams来编码,v5版本正常不会有该问题,如果想要修复,可以直接关闭cssnanosortQueryParameters
  • URLSearchParamsencodeURIComponent编码逻辑基本一致,但是范围更广,且对空格会做特殊处理

Supongo que te gusta

Origin juejin.im/post/7084045590438346760
Recomendado
Clasificación