Pregunta ¿La recomendación de incluir CSS antes de JavaScript es inválida?


En innumerables lugares en línea, he visto la recomendación de incluir CSS antes de JavaScript. El razonamiento es en general, de esta forma:

Cuando se trata de ordenar su CSS y JavaScript, quiere su CSS   para venir primero. La razón es que el hilo de renderizado tiene todo el   información de estilo que necesita para representar la página. Si el JavaScript   incluye venir primero, el motor de JavaScript tiene que analizarlo todo antes   continuando con el siguiente conjunto de recursos. Esto significa que la representación   el hilo no puede mostrar completamente la página, ya que no tiene todo el   estilos que necesita.

Mi prueba real revela algo bastante diferente:

Mi arnés de prueba

Uso el siguiente script de Ruby para generar retrasos específicos para varios recursos:

require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
require 'date'

class Handler  < EventMachine::Connection
  include EventMachine::HttpServer

  def process_http_request
    resp = EventMachine::DelegatedHttpResponse.new( self )

    return unless @http_query_string

    path = @http_path_info
    array = @http_query_string.split("&").map{|s| s.split("=")}.flatten
    parsed = Hash[*array]

    delay = parsed["delay"].to_i / 1000.0
    jsdelay = parsed["jsdelay"].to_i

    delay = 5 if (delay > 5)
    jsdelay = 5000 if (jsdelay > 5000)

    delay = 0 if (delay < 0) 
    jsdelay = 0 if (jsdelay < 0)

    # Block which fulfills the request
    operation = proc do
      sleep delay 

      if path.match(/.js$/)
        resp.status = 200
        resp.headers["Content-Type"] = "text/javascript"
        resp.content = "(function(){
            var start = new Date();
            while(new Date() - start < #{jsdelay}){}
          })();"
      end
      if path.match(/.css$/)
        resp.status = 200
        resp.headers["Content-Type"] = "text/css"
        resp.content = "body {font-size: 50px;}"
      end
    end

    # Callback block to execute once the request is fulfilled
    callback = proc do |res|
        resp.send_response
    end

    # Let the thread pool (20 Ruby threads) handle request
    EM.defer(operation, callback)
  end
end

EventMachine::run {
  EventMachine::start_server("0.0.0.0", 8081, Handler)
  puts "Listening..."
}

El mini servidor anterior me permite establecer retrasos arbitrarios para los archivos de JavaScript (tanto del servidor como del cliente) y retrasos de CSS arbitrarios. Por ejemplo, http://10.0.0.50:8081/test.css?delay=500 me da un retraso de 500 ms transfiriendo el CSS.

Uso la siguiente página para probar.

<!DOCTYPE html>
<html>
  <head>
      <title>test</title>
      <script type='text/javascript'>
          var startTime = new Date();
      </script>
      <link href="http://10.0.0.50:8081/test.css?delay=500" type="text/css" rel="stylesheet">
      <script type="text/javascript" src="http://10.0.0.50:8081/test2.js?delay=400&amp;jsdelay=1000"></script> 
  </head>
  <body>
    <p>
      Elapsed time is: 
      <script type='text/javascript'>
        document.write(new Date() - startTime);
      </script>
    </p>    
  </body>
</html>

Cuando incluyo el CSS primero, la página tarda 1.5 segundos en renderizarse:

CSS first

Cuando incluyo el JavaScript primero, la página tarda 1.4 segundos en renderizarse:

JavaScript first

Obtengo resultados similares en Chrome, Firefox e Internet Explorer. En Opera, sin embargo, el orden simplemente no importa.

Lo que parece estar sucediendo es que el intérprete de JavaScript se niega a comenzar hasta que se haya descargado todo el CSS. Por lo tanto, parece que tener JavaScript incluye primero es más eficiente ya que el hilo de JavaScript obtiene más tiempo de ejecución.

¿Me falta algo? ¿La recomendación de colocar CSS incluye antes de JavaScript incluye no correcta?

Está claro que podríamos agregar Async o usar setTimeout para liberar el hilo de renderizado o poner el código de JavaScript en el pie de página, o usar un cargador de JavaScript. El punto aquí es sobre el pedido de bits esenciales de JavaScript y bits de CSS en la cabeza.


827
2018-02-14 03:24


origen


Respuestas:


Esta es una pregunta muy interesante. Siempre he puesto mi CSS <link href="...">s antes de mi JS <script src="...">s porque "una vez leí que es mejor". Entonces, tienes razón; ¡es hora de que hagamos una investigación real!

Configuré mi propio arnés de prueba en Nodo (código a continuación). Básicamente, yo:

  • Se aseguró de que no hubiera caché de HTTP, por lo que el navegador tendría que realizar una descarga completa cada vez que se carga una página.
  • Para simular la realidad, incluí jQuery y H5BP CSS (por lo que hay una cantidad decente de script / CSS para analizar)
  • Configure dos páginas: una con CSS antes del guión y otra con CSS después del guión.
  • Registrado cuánto tiempo tomó para el script externo en el <head> ejecutar
  • Registrado cuánto tiempo tomó para el script en línea en el <body> para ejecutar, que es análogo a DOMReady.
  • Retrasó el envío de CSS y / o secuencia de comandos al navegador en 500 ms.
  • Ejecutó la prueba 20 veces en los 3 principales navegadores.

Resultados

Primero, con el archivo CSS retrasado en 500 ms:

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 583ms  36ms  | 559ms  42ms  | 565ms 49ms
St Dev      | 15ms   12ms  | 9ms    7ms   | 13ms  6ms
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 584ms  521ms | 559ms  513ms | 565ms 519ms
St Dev      | 15ms   9ms   | 9ms    5ms   | 13ms  7ms

A continuación, configuré jQuery para retrasar 500ms en lugar de CSS:

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 597ms  556ms | 562ms  559ms | 564ms 564ms
St Dev      | 14ms   12ms  | 11ms   7ms   | 8ms   8ms
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 598ms  557ms | 563ms  560ms | 564ms 565ms
St Dev      | 14ms   12ms  | 10ms   7ms   | 8ms   8ms

Finalmente, establecí ambos jQuery y el CSS para retrasar en 500 ms:

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 620ms  560ms | 577ms  577ms | 571ms 567ms
St Dev      | 16ms   11ms  | 19ms   9ms   | 9ms   10ms
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 623ms  561ms | 578ms  580ms | 571ms 568ms
St Dev      | 18ms   11ms  | 19ms   9ms   | 9ms   10ms

Conclusiones

En primer lugar, es importante tener en cuenta que estoy operando bajo el supuesto de que tiene scripts ubicados en el <head> de su documento (a diferencia del final del <body>) Hay varios argumentos con respecto a por qué podría vincular a sus scripts en el <head> versus el final del documento, pero eso está fuera del alcance de esta respuesta. Esto es estrictamente sobre si <script>s debería ir antes <link>s en el <head>.

En los navegadores modernos de DESKTOP, parece que se vincula primero a CSS Nunca proporciona una ganancia de rendimiento. Colocar CSS después del script le proporciona una cantidad trivial de ganancia cuando el CSS y el script se retrasan, pero le proporciona grandes ganancias cuando CSS se demora. (Mostrado por el last columnas en el primer conjunto de resultados)

Dado que vincular a CSS por última vez no parece perjudicar el rendimiento, pero poder proporcionar ganancias bajo ciertas circunstancias, deberías enlazar a hojas de estilo externas después enlaza con scripts externos solo en navegadores de escritorio si el rendimiento de los navegadores antiguos no es una preocupación. Siga leyendo para conocer la situación móvil.

¿Por qué?

Históricamente, cuando un navegador encontró un <script> etiqueta que apunta a un recurso externo, el navegador detener analizar el HTML, recuperar el script, ejecutarlo y continuar analizando el HTML. Por el contrario, si el navegador encontró una <link> para una hoja de estilo externa, sería continuar analizar el HTML mientras se busca el archivo CSS (en paralelo).

Por lo tanto, el consejo ampliamente repetido para poner hojas de estilo primero - se descargarían primero, y el primer script para descargar podría cargarse en paralelo.

Sin embargo, los navegadores modernos (incluidos todos los navegadores que probé con anterioridad) han implementado análisis especulativo, donde el navegador "mira hacia adelante" en el HTML y comienza a descargar recursos antes de los scripts se descargan y ejecutan.

En navegadores antiguos sin análisis especulativo, poner scripts primero afectará el rendimiento ya que no se descargarán en paralelo.

Soporte del navegador

El análisis especulativo se implementó por primera vez en: (junto con el porcentaje de usuarios de navegadores de escritorio en todo el mundo que usan esta versión o superior a partir de enero de 2012)

  • Chrome 1 (WebKit 525) (100%)
  • IE 8 (75%)
  • Firefox 3.5 (96%)
  • Safari 4 (99%)
  • Opera 11.60 (85%)

En total, aproximadamente el 85% de los navegadores de escritorio que se usan en la actualidad admiten la carga especulativa. Colocar scripts antes de CSS tendrá una penalización de rendimiento en el 15% de los usuarios globalmente; YMMV basado en la audiencia específica de su sitio. (Y recuerde que el número se está reduciendo).

En los navegadores móviles, es un poco más difícil obtener números definitivos simplemente debido a cuán heterogéneo es el panorama del navegador móvil y del sistema operativo. Dado que el renderizado especulativo se implementó en WebKit 525 (publicado en marzo de 2008), y casi todos los navegadores móviles que merecen la pena se basan en WebKit, podemos concluir que "la mayoría de los navegadores móviles" debería apoyarlo De acuerdo a en modo capricho, iOS 2.2 / Android 1.0 usa WebKit 525. No tengo idea de cómo es Windows Phone.

Sin embargo, Ejecuté la prueba en mi dispositivo Android 4, y aunque vi números similares a los resultados de escritorio, lo conecté a la nueva y fantástica depurador remoto en Chrome para Android, y la pestaña Red mostraba que el navegador estaba esperando para descargar el CSS hasta que los JavaScripts se cargaran por completo, en otras palabras, incluso la versión más nueva de WebKit para Android no parece ser compatible con el análisis especulativo.  Sospecho que podría estar apagado debido a la CPU, la memoria y / o las restricciones de red inherentes a los dispositivos móviles.

Código

Perdona el descuido: esto fue Q & D.

app.js

var express = require('express')
, app = express.createServer()
, fs = require('fs');

app.listen(90);

var file={};
fs.readdirSync('.').forEach(function(f) {
    console.log(f)
    file[f] = fs.readFileSync(f);
    if (f != 'jquery.js' && f != 'style.css') app.get('/' + f, function(req,res) {
        res.contentType(f);
        res.send(file[f]);
    });
});


app.get('/jquery.js', function(req,res) {
    setTimeout(function() {
        res.contentType('text/javascript');
        res.send(file['jquery.js']);
    }, 500);
});

app.get('/style.css', function(req,res) {
    setTimeout(function() {
        res.contentType('text/css');
        res.send(file['style.css']);
    }, 500);
});


var headresults={
    css: [],
    js: []
}, bodyresults={
    css: [],
    js: []
}
app.post('/result/:type/:time/:exec', function(req,res) {
    headresults[req.params.type].push(parseInt(req.params.time, 10));
    bodyresults[req.params.type].push(parseInt(req.params.exec, 10));
    res.end();
});

app.get('/result/:type', function(req,res) {
    var o = '';
    headresults[req.params.type].forEach(function(i) {
        o+='\n' + i;
    });
    o+='\n';
    bodyresults[req.params.type].forEach(function(i) {
        o+='\n' + i;
    });
    res.send(o);
});

css.html

<!DOCTYPE html>
<html>
    <head>
        <title>CSS first</title>
        <script>var start = Date.now();</script>
        <link rel="stylesheet" href="style.css">
        <script src="jquery.js"></script>
        <script src="test.js"></script>
    </head>
    <body>
        <script>document.write(jsload - start);bodyexec=Date.now()</script>
    </body>
</html>

js.html

<!DOCTYPE html>
<html>
    <head>
        <title>CSS first</title>
        <script>var start = Date.now();</script>
        <script src="jquery.js"></script>
        <script src="test.js"></script>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <script>document.write(jsload - start);bodyexec=Date.now()</script>
    </body>
</html>

test.js

var jsload = Date.now();


$(function() {
    $.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});

jquery.js era jquery-1.7.1.min.js


671
2018-02-14 06:37



Hay dos razones principales para poner CSS antes de JavaScript.

  1. Los navegadores antiguos (Internet Explorer 6-7, Firefox 2, etc.) bloquearían todas las descargas posteriores cuando comenzaran a descargar un script. Entonces si tienes a.js seguido por b.css se descargan secuencialmente: primero a luego b. Si usted tiene b.css seguido por a.js se descargan en paralelo para que la página cargue más rápido.

  2. No se representa nada hasta que se descarguen todas las hojas de estilo; esto es cierto en todos los navegadores. Los scripts son diferentes: bloquean la representación de todos los elementos DOM que están debajo de la etiqueta de script en la página. Si coloca sus scripts en HEAD, significa que la página completa está bloqueada desde el procesamiento hasta que se descarguen todas las hojas de estilo y todos los scripts. Si bien tiene sentido bloquear todo el procesamiento de las hojas de estilo (para que obtenga el estilo correcto la primera vez y evitar el destello del contenido sin estilo FOUC), no tiene sentido bloquear la representación de las secuencias de comandos de toda la página. A menudo, los scripts no afectan a ningún elemento DOM o solo a una parte de los elementos DOM. Lo mejor es cargar scripts lo más bajo posible en la página, o incluso mejor cargarlos de forma asincrónica.

Es divertido crear ejemplos con Cuzillion. Por ejemplo, esta página tiene un script en HEAD así que toda la página está en blanco hasta que termine de descargarse. Sin embargo, si movemos la secuencia de comandos hasta el final del bloque BODY, el encabezado de la página se renderiza, ya que esos elementos DOM aparecen sobre la etiqueta SCRIPT, como puede ver en esta página.


293
2018-02-14 06:27



No enfatizaría demasiado en los resultados que tiene, creo que es subjetivo, pero tengo una razón para explicarle que es mejor incluir CSS antes de js.

Durante la carga de su sitio web, hay dos escenarios que vería:

CASO 1: pantalla en blanco> sitio web sin estilo> sitio web con estilo> interacción> sitio web con estilo e interactivo

CASO 2: pantalla en blanco> sitio web sin estilo> interacción> sitio web con estilo> sitio web con estilo e interactivo


Honestamente, no puedo imaginar a nadie eligiendo el Caso 2. Esto significaría que los visitantes que usan conexiones lentas a Internet se enfrentarán a un sitio web sin estilo, que les permite interactuar con él usando Javascript (ya que ya está cargado). Además, la cantidad de tiempo que se dedica a mirar un sitio web sin estilo se maximizaría de esta manera. ¿Por qué alguien querría eso?

También funciona mejor como jQuery estados 

"Cuando se utilizan scripts que se basan en el valor de las propiedades de estilo CSS,   es importante hacer referencia a las hojas de estilo externas o al estilo de inserción   elementos antes de hacer referencia a los scripts ".

Cuando los archivos se cargan en el orden incorrecto (primero JS, luego CSS), cualquier código Javascript que dependa de propiedades establecidas en archivos CSS (por ejemplo, el ancho o alto de un div) no se cargará correctamente. Parece que con el orden de carga incorrecto, las propiedades correctas son 'a veces' conocidas por Javascript (¿quizás esto es causado por una condición de carrera?). Este efecto parece más grande o más pequeño según el navegador utilizado.


40
2018-02-14 07:55



¿Se realizaron las pruebas en su computadora personal o en un servidor web? Es una página en blanco, o es un sistema en línea complejo con imágenes, bases de datos, etc.? ¿Sus scripts realizan una simple acción de evento hover, o son un componente central de cómo su sitio web procesa e interactúa con el usuario? Hay varias cosas que considerar aquí, y la relevancia de estas recomendaciones casi siempre se convierten en reglas cuando se aventura en el desarrollo web de alto calibre.

El propósito de la regla "poner hojas de estilo en la parte superior y guiones en la parte inferior" es que, en general, es la mejor manera de lograr el óptimo representación progresiva, que es crítico para la experiencia del usuario.

Aparte de todo: suponiendo que tu prueba sea válida, y realmente estás produciendo resultados contrarios a las reglas populares, realmente no sería una sorpresa. Cada sitio web (y todo lo que se necesita para que todo aparezca en la pantalla de un usuario) es diferente y Internet está en constante evolución.


25
2018-02-14 13:31



Incluyo archivos CSS antes de Javascript por una razón diferente.

Si mi Javascript necesita hacer un tamaño dinámico de algún elemento de página (para aquellos casos de esquina donde CSS es realmente un elemento principal atrás), cargar el CSS después de que el JS se esté reproduciendo puede generar condiciones de carrera, donde el elemento cambia de tamaño antes de los estilos CSS se aplican y, por lo tanto, se ve raro cuando los estilos finalmente entran en juego. Si cargo el CSS de antemano, puedo garantizar que las cosas se ejecuten en el orden deseado y que el diseño final sea el que quiero que sea.


21
2018-05-25 20:00



¿La recomendación de incluir CSS antes de JavaScript es inválida?

No si lo trata simplemente como una recomendación. ¿Pero si lo trata como una regla dura y rápida ?, sí, no es válido.

De https://developer.mozilla.org/en-US/docs/Web/Reference/Events/DOMContentLoaded

La hoja de estilo carga la ejecución del script de bloque, por lo que si tiene una <script>   después de <link rel="stylesheet" ...> la página no terminará el análisis   - y DOMContentLoaded no se disparará - hasta que se cargue la hoja de estilo.

Parece que necesita saber en qué se basa cada script y asegurarse de que la ejecución del script se demore hasta después del evento de finalización correcto. Si la secuencia de comandos se basa únicamente en el DOM, puede reanudarse en ondomready / domcontentloaded, si se basa en las imágenes que se cargarán o en las hojas de estilo que se aplicarán, entonces si leo la referencia anterior correctamente, ese código se debe aplazar hasta el evento onload.

No creo que el tamaño de un calcetín se ajuste a todos, a pesar de que esa es la forma en que se venden y sé que el tamaño de un zapato no sirve para todos. No creo que haya una respuesta definitiva para cargar primero, estilos o guiones. Es más una decisión caso por caso de lo que debe cargarse en qué orden y qué puede diferirse para luego no estar en el "camino crítico".

Para hablar con el observador que comentó que es mejor retrasar la capacidad de los usuarios para interactuar hasta que la hoja sea bonita. Hay muchos de ustedes y molestan a sus contrapartes que sienten lo contrario. Vinieron a un sitio para lograr un propósito y las demoras en su capacidad para interactuar con un sitio mientras esperan que cosas que no importan terminar de cargar son muy frustrantes. No digo que estés equivocado, solo que debes ser consciente de que existe otra facción que no comparte tu prioridad.

Esta pregunta se aplica particularmente a todos los anuncios que se colocan en sitios web. Me encantaría que los autores del sitio solo dieran divs de marcador de posición para el contenido del anuncio y se aseguraran de que su sitio estuviera cargado e interactivo antes de inyectar los anuncios en un evento onload. Incluso entonces me gustaría ver los anuncios cargados en serie en lugar de todos a la vez porque afectan mi capacidad de desplazar el contenido del sitio mientras se cargan los anuncios inflados. Pero ese es solo el punto de vista de una persona.

  • Conozca a sus usuarios y lo que valoran.
  • Conozca a sus usuarios y el entorno de navegación que utilizan.
  • Sepa qué hace cada archivo y cuáles son sus requisitos previos. Hacer que todo funcione tendrá prioridad sobre la velocidad y lo bonito.
  • Use herramientas que le muestren la línea de tiempo de la red cuando desarrolle.
  • Pruebe en cada uno de los entornos que usan sus usuarios. Puede ser necesario que dinámicamente (al lado del servidor, al crear la página) modifique el orden de carga en función del entorno de los usuarios.
  • En caso de duda, modifique el orden y mida nuevamente.
  • Es posible que los estilos y scripts entremezclados en el orden de carga sean óptimos; no todo uno sino todo lo otro.
  • Experimenta no solo qué orden cargar los archivos sino dónde. ¿Cabeza? ¿En cuerpo? Después del cuerpo? ¿DOM Ready / Loaded? ¿Cargado?
  • Considere las opciones de sincronización y aplazamiento cuando sea apropiado para reducir el retraso neto que experimentará el usuario antes de poder interactuar con la página. Prueba para determinar si ayudan o duelen.
  • Siempre habrá compensaciones para considerar al evaluar el orden de carga óptimo. Pretty vs. Responsive siendo solo uno.

10
2018-04-27 12:16



Actualizado el 2017-12-16

No estaba seguro acerca de las pruebas en OP. Decidí experimentar un poco y terminé destruyendo algunos de los mitos.

Sincrónico <script src...> bloqueará la descarga de los recursos   debajo de él hasta que se descargue y se ejecute

Esto ya no es verdad. Echa un vistazo a la cascada generada por Chrome 63:

<head>
<script src="//alias-0.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=1"></script>
<script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=2"></script>
<script src="//alias-2.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=3"></script>
</head>

Chrome net inspector -> waterfall

<link rel=stylesheet> no bloqueará la descarga y ejecución de   scripts debajo

Esto es incorrecto. La hoja de estilo no bloqueará la descarga, pero será bloquear la ejecución del script (pequeña explicación aquí) Echa un vistazo a la tabla de rendimiento generada por Chrome 63:

<link href="//alias-0.redacted.com/payload.php?type=css&amp;delay=666" rel="stylesheet">
<script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;block=1000"></script>

Chrome dev tools -> performance


Teniendo en cuenta lo anterior, los resultados en OP se pueden explicar de la siguiente manera:

CSS primero:

CSS Download  500ms:<------------------------------------------------>
JS Download   400ms:<-------------------------------------->
JS Execution 1000ms:                                                  <-------------------------------------------------------------------------------------------------->
DOM Ready   @1500ms:                                                                                                                                                      ◆

JS Primero:

JS Download   400ms:<-------------------------------------->
CSS Download  500ms:<------------------------------------------------>
JS Execution 1000ms:                                        <-------------------------------------------------------------------------------------------------->
DOM Ready   @1400ms:                                                                                                                                            ◆

6
2018-02-14 22:33