Evitar los peligros del código insertado automáticamente

 

 

 

  • Patrones de diseño para interfaces de IA, con Vitaly Friedman
  • Planificación y proceso del sistema de diseño, con Nathan Curtis

  • Índice
    1. Cuándo evitar la inserción
    2. Activar la creación de archivos estáticos
    3. Crear el archivo estático cuando cambia la fuente
    4. Representing The File As An Object
    5. Saving The Static File To Disk
    6. Poner en cola los archivos estáticos
    7. Agrupar archivos
    8. async/ deferAtributos para recursos JS
    9. Tratar con varios servidores detrás de un equilibrador de carga
    10. Conclusión

    El uso excesivo de código CSS o JS en línea, en lugar de servir código a través de recursos estáticos, puede dañar el rendimiento del sitio. En este artículo, aprenderemos cómo cargar código dinámico a través de archivos estáticos, evitando los inconvenientes de demasiado código en línea.

     

    La inserción es el proceso de incluir el contenido de los archivos directamente en el documento HTML: los archivos CSS se pueden insertar dentro de un styleelemento y los archivos JavaScript se pueden insertar dentro de un scriptelemento:

    style/* CSS contents here *//stylescript/* JS contents here *//script

    Al imprimir el código que ya está en la salida HTML, la inserción evita solicitudes de bloqueo de procesamiento y ejecuta el código antes de que se represente la página. Como tal, es útil para mejorar el rendimiento percibido del sitio (es decir, el tiempo que tarda una página en volverse utilizable). Por ejemplo, podemos usar el búfer de datos entregado inmediatamente al cargar el sitio (alrededor de 14 kb) para insertar los estilos críticos , incluidos los estilos de contenido de la mitad superior de la página (como se había hecho en el sitio anterior de Smashing Magazine ), y los tamaños de fuente y los anchos y altos del diseño para evitar que el diseño se vuelva a representar con saltos cuando se entregue el resto de los datos. .

    Sin embargo, cuando se exagera, insertar código también puede tener efectos negativos en el rendimiento del sitio: debido a que el código no se puede almacenar en caché, el mismo contenido se envía al cliente repetidamente y no se puede almacenar en caché previamente a través de Service Workers, o almacenado en caché y accedido desde una red de entrega de contenido. Además, los scripts en línea no se consideran seguros cuando se implementa una Política de seguridad de contenido (CSP). Luego, es una estrategia sensata incorporar aquellas partes críticas de CSS y JS que hacen que el sitio se cargue más rápido, pero que de otro modo se evitan en la medida de lo posible.

     

    Con el objetivo de evitar la inserción en línea, en este artículo exploraremos cómo convertir código en línea en activos estáticos: en lugar de imprimir el código en la salida HTML, lo guardamos en el disco (creando efectivamente un archivo estático) y agregamos la etiqueta o scriptcorrespondiente . linkpara cargar el archivo.

    ¡Empecemos!

    Lectura recomendada : La seguridad de WordPress como proceso

    Cuándo evitar la inserción

    No existe una receta mágica para establecer si algún código debe estar integrado o no; sin embargo, puede ser bastante evidente cuándo algún código no debe estar integrado: cuando se trata de una gran cantidad de código y cuando no se necesita de inmediato.

    Como ejemplo, los sitios de WordPress integran las plantillas de JavaScript para representar el Administrador de medios (accesible en la página Biblioteca de medios en /wp-admin/upload.php), imprimiendo una cantidad considerable de código:

    Al ocupar 43 kb completos, el tamaño de este fragmento de código no es despreciable y, dado que se encuentra en la parte inferior de la página, no se necesita de inmediato. Por lo tanto, tendría mucho sentido entregar este código a través de activos estáticos o imprimirlo dentro de la salida HTML.

    Veamos a continuación cómo transformar código en línea en activos estáticos.

    Activar la creación de archivos estáticos

    Si los contenidos (los que se van a insertar) provienen de un archivo estático, entonces no hay mucho que hacer más que simplemente solicitar ese archivo estático en lugar de insertar el código.

    Sin embargo, para el código dinámico, debemos planificar cómo y cuándo generar el archivo estático con su contenido. Por ejemplo, si el sitio ofrece opciones de configuración (como cambiar la combinación de colores o la imagen de fondo), ¿cuándo se debe generar el archivo que contiene los nuevos valores? Tenemos las siguientes oportunidades para crear archivos estáticos a partir del código dinámico:

    1. Bajo petición
      Cuando un usuario accede por primera vez al contenido.
    2. Al cambiar
      Cuando la fuente del código dinámico (por ejemplo, un valor de configuración) ha cambiado.

    Consideremos primero la solicitud. La primera vez que un usuario accede al sitio, digamos a través de /index.html, el archivo estático (por ejemplo header-colors.css, ) aún no existe, por lo que debe generarse en ese momento. La secuencia de eventos es la siguiente:

     

    1. El usuario solicita /index.html;
    2. Al procesar la solicitud, el servidor verifica si el archivo header-colors.cssexiste. Como no es así, obtiene el código fuente y genera el archivo en disco;
    3. Devuelve una respuesta al cliente, incluida la etiqueta.link rel="stylesheet" type="text/css" href="/staticfiles/header-colors.css"
    4. El navegador recupera todos los recursos incluidos en la página, incluidos header-colors.css;
    5. Para entonces este archivo ya existe, por lo que se sirve.

    Sin embargo, la secuencia de acontecimientos también podría ser diferente y conducir a un resultado insatisfactorio. Por ejemplo:

    1. El usuario solicita /index.html;
    2. Este archivo ya está almacenado en caché por el navegador (o algún otro proxy, o a través de Service Workers), por lo que la solicitud nunca se envía al servidor;
    3. El navegador recupera todos los recursos incluidos en la página, incluidos header-colors.css. Sin embargo, esta imagen no se almacena en caché en el navegador, por lo que la solicitud se envía al servidor;
    4. El servidor header-colors.cssaún no se ha generado (por ejemplo, se acaba de reiniciar);
    5. Devolverá un 404.

    Alternativamente, podríamos generar header-colors.cssno al solicitar /index.html, sino al solicitarlo /header-colors.cssa sí mismo. Sin embargo, dado que este archivo inicialmente no existe, la solicitud ya se trata como un 404. Aunque podríamos solucionarlo alterando los encabezados para cambiar el código de estado a 200 y devolver el contenido de la imagen, Esta es una manera terrible de hacer las cosas, por lo que no consideraremos esta posibilidad (¡somos mucho mejores que esto!)

    Eso deja sólo una opción: generar el archivo estático después de que su fuente haya cambiado.

    Crear el archivo estático cuando cambia la fuente

    Tenga en cuenta que podemos crear código dinámico a partir de fuentes tanto dependientes del usuario como del sitio. Por ejemplo, si el tema permite cambiar la imagen de fondo del sitio y esa opción la configura el administrador del sitio, entonces el archivo estático se puede generar como parte del proceso de implementación. Por otro lado, si el sitio permite a sus usuarios cambiar la imagen de fondo de sus perfiles, entonces el archivo estático debe generarse en tiempo de ejecución.

    Resumiendo, tenemos estos dos casos:

    1. Configuración de usuario
      El proceso debe activarse cuando el usuario actualiza una configuración.
    2. Configuración del sitio
      El proceso debe activarse cuando el administrador actualiza una configuración para el sitio o antes de implementar el sitio.

    Si consideráramos los dos casos de forma independiente, para el n.° 2 podríamos diseñar el proceso en cualquier pila de tecnología que quisiéramos. Sin embargo, no queremos implementar dos soluciones diferentes, sino una solución única que pueda abordar ambos casos. Y debido a que desde el punto 1 el proceso para generar el archivo estático debe activarse en el sitio en ejecución, entonces es convincente diseñar este proceso en torno a la misma pila de tecnología en la que se ejecuta el sitio.

     

    Al diseñar el proceso, nuestro código deberá manejar las circunstancias específicas tanto del n.° 1 como del n.° 2:

    • Versionado
      Se debe acceder al archivo estático con un parámetro “versión”, para invalidar el archivo anterior al crear un nuevo archivo estático. Mientras que el número 2 podría simplemente tener la misma versión que el sitio, el número 1 necesita usar una versión dinámica para cada usuario, posiblemente guardada en la base de datos.
    • La ubicación del archivo generado
      #2 genera un archivo estático único para todo el sitio (p.ej. /staticfiles/header-colors.css), mientras que #1 crea un archivo estático para cada usuario (p.ej. /staticfiles/users/leo/header-colors.css).
    • Evento desencadenante
      Mientras que para el n.° 1 el archivo estático debe ejecutarse en tiempo de ejecución, para el n.° 2 también se puede ejecutar como parte de un proceso de compilación en nuestro entorno de prueba.
    • Deployment and distribution
      Static files in #2 can be seamlessly integrated inside the site’s deployment bundle, presenting no challenges; static files in #1, however, cannot, so the process must handle additional concerns, such as multiple servers behind a load balancer (will the static files be created in 1 server only, or in all of them, and how?).

    Let’s design and implement the process next. For each static file to be generated we must create an object containing the file’s metadata, calculate its content from the dynamic sources, and finally save the static file to disk. As a use case to guide the explanations below, we will generate the following static files:

    1. header-colors.css, with some style from values saved in the database
    2. welcomeuser-data.js, containing a JSON object with user data under some variable: window.welcomeUserData = {name: "Leo"};.

    Below, I will describe the process to generate the static files for WordPress, for which we must base the stack on PHP and WordPress functions. The function to generate the static files before deployment can be triggered by loading a special page executing shortcode [create_static_files] as I have described in a previous article.

    Further recommended reading: Making A Service Worker: A Case Study

    Representing The File As An Object

    We must model a file as a PHP object with all corresponding properties, so we can both save the file on disk on a specific location (e.g. either under /staticfiles/ or /staticfiles/users/leo/), and know how to request the file consequently. For this, we create an interface Resource returning both the file’s metadata (filename, dir, type: “css” or “js”, version, and dependencies on other resources) and its content.

    interface Resource { function get_filename(); function get_dir(); function get_type(); function get_version(); function get_dependencies(); function get_content();}

    In order to make the code maintainable and reusable we follow the SOLID principles, for which we set an object inheritance scheme for resources to gradually add properties, starting from the abstract class ResourceBase from which all our Resource implementations will inherit:

     

    abstract class ResourceBase implements Resource { function get_dependencies() { // By default, a file has no dependencies return array(); }}

    Following SOLID, we create subclasses whenever properties differ. As stated earlier, the location of the generated static file, and the versioning to request it will be different depending on the file being about the user or site configuration:

    abstract class UserResourceBase extends ResourceBase { function get_dir() { // A different file and folder for each user $user = wp_get_current_user(); return "/staticfiles/users/{$user-user_login}/"; } function get_version() { // Save the resource version for the user under her meta data. // When the file is regenerated, must execute `update_user_meta` to increase the version number $user_id = get_current_user_id(); $meta_key = "resource_version_".$this-get_filename(); return get_user_meta($user_id, $meta_key, true); }}abstract class SiteResourceBase extends ResourceBase { function get_dir() { // All files are placed in the same folder return "/staticfiles/"; } function get_version() { // Same versioning as the site, assumed defined under a constant return SITE_VERSION; }}

    Finally, at the last level, we implement the objects for the files we want to generate, adding the filename, the type of file, and the dynamic code through function get_content:

    class HeaderColorsSiteResource extends SiteResourceBase { function get_filename() { return "header-colors"; } function get_type() { return "css"; } function get_content() { return sprintf( " .site-title a { color: #%s; } ", esc_attr(get_header_textcolor()) ); }}class WelcomeUserDataUserResource extends UserResourceBase { function get_filename() { return "welcomeuser-data"; } function get_type() { return "js"; } function get_content() { $user = wp_get_current_user(); return sprintf( "window.welcomeUserData = %s;", json_encode( array( "name" = $user-display_name ) ) ); }}

    With this, we have modeled the file as a PHP object. Next, we need to save it to disk.

    Saving The Static File To Disk

    Saving a file to disk can be easily accomplished through the native functions provided by the language. In the case of PHP, this is accomplished through the function fwrite. In addition, we create a utility class ResourceUtils with functions providing the absolute path to the file on disk, and also its path relative to the site’s root:

    class ResourceUtils { protected static function get_file_relative_path($fileObject) { return $fileObject-get_dir().$fileObject-get_filename().".".$fileObject-get_type(); } static function get_file_path($fileObject) { // Notice that we must add constant WP_CONTENT_DIR to make the path absolute when saving the file return WP_CONTENT_DIR.self::get_file_relative_path($fileObject); }}class ResourceGenerator { static function save($fileObject) { $file_path = ResourceUtils::get_file_path($fileObject); $handle = fopen($file_path, "wb"); $numbytes = fwrite($handle, $fileObject-get_content()); fclose($handle); }}

    Luego, cada vez que la fuente cambia y es necesario regenerar el archivo estático, ejecutamos ResourceGenerator::savepasando el objeto que representa el archivo como parámetro. El siguiente código regenera y guarda en el disco los archivos “header-colors.css” y “welcomeuser-data.js”:

     

    // When need to regenerate header-colors.css, execute:ResourceGenerator::save(new HeaderColorsSiteResource());// When need to regenerate welcomeuser-data.js, execute:ResourceGenerator::save(new WelcomeUserDataUserResource());

    Una vez que existen, podemos poner en cola los archivos para cargarlos a través de las etiquetas scripty link.

    Poner en cola los archivos estáticos

    Poner en cola los archivos estáticos no es diferente a poner en cola cualquier recurso en WordPress: a través de funciones wp_enqueue_scripty wp_enqueue_style. Luego, simplemente iteramos todas las instancias de objetos y usamos un gancho u otro dependiendo de que su get_type()valor sea "js"o "css".

    Primero agregamos funciones de utilidad para proporcionar la URL del archivo e indicar si el tipo es JS o CSS:

    class ResourceUtils { // Continued from above... static function get_file_url($fileObject) { // Add the site URL before the file path return get_site_url().self::get_file_relative_path($fileObject); } static function is_css($fileObject) { return $fileObject-get_type() == "css"; } static function is_js($fileObject) { return $fileObject-get_type() == "js"; }}

    Una instancia de clase ResourceEnqueuercontendrá todos los archivos que deben cargarse; cuando se invoca, sus funciones enqueue_scriptsy enqueue_styleshará la puesta en cola, ejecutando las funciones correspondientes de WordPress ( wp_enqueue_scripty wp_enqueue_stylerespectivamente):

    class ResourceEnqueuer { protected $fileObjects; function __construct($fileObjects) { $this-fileObjects = $fileObjects; } protected function get_file_properties($fileObject) { $handle = $fileObject-get_filename(); $url = ResourceUtils::get_file_url($fileObject); $dependencies = $fileObject-get_dependencies(); $version = $fileObject-get_version(); return array($handle, $url, $dependencies, $version); } function enqueue_scripts() { $jsFileObjects = array_map(array(ResourceUtils::class, 'is_js'), $this-fileObjects); foreach ($jsFileObjects as $fileObject) { list($handle, $url, $dependencies, $version) = $this-get_file_properties($fileObject); wp_register_script($handle, $url, $dependencies, $version); wp_enqueue_script($handle); } } function enqueue_styles() { $cssFileObjects = array_map(array(ResourceUtils::class, 'is_css'), $this-fileObjects); foreach ($cssFileObjects as $fileObject) { list($handle, $url, $dependencies, $version) = $this-get_file_properties($fileObject); wp_register_style($handle, $url, $dependencies, $version); wp_enqueue_style($handle); } }}

    Finalmente, creamos una instancia de un objeto de clase ResourceEnqueuercon una lista de los objetos PHP que representan cada archivo y agregamos un gancho de WordPress para ejecutar la puesta en cola:

     

    // Initialize with the corresponding object instances for each file to enqueue$fileEnqueuer = new ResourceEnqueuer( array( new HeaderColorsSiteResource(), new WelcomeUserDataUserResource() ));// Add the WordPress hooks to enqueue the resourcesadd_action('wp_enqueue_scripts', array($fileEnqueuer, 'enqueue_scripts'));add_action('wp_print_styles', array($fileEnqueuer, 'enqueue_styles'));

    Eso es todo: al estar en cola, los archivos estáticos se solicitarán al cargar el sitio en el cliente. Hemos logrado evitar imprimir código en línea y cargar recursos estáticos.

    A continuación, podemos aplicar varias mejoras para obtener ganancias de rendimiento adicionales.

    Lectura recomendada : Introducción a las pruebas automatizadas de complementos de WordPress con PHPUnit

    Agrupar archivos

    Aunque HTTP/2 ha reducido la necesidad de agrupar archivos, aún hace que el sitio sea más rápido, porque la compresión de archivos (por ejemplo, a través de GZip) será más efectiva y porque los navegadores (como Chrome) tienen una mayor sobrecarga al procesar muchos recursos. .

    Hasta ahora, hemos modelado un archivo como un objeto PHP, lo que nos permite tratar este objeto como una entrada para otros procesos. En particular, podemos repetir el mismo proceso anterior para agrupar todos los archivos del mismo tipo y ofrecer la versión empaquetada en lugar de todos los archivos independientes. Para esto, creamos una función get_contentque simplemente extrae el contenido de cada recurso en $fileObjectsy lo imprime nuevamente, produciendo la agregación de todo el contenido de todos los recursos:

    abstract class SiteBundleBase extends SiteResourceBase { protected $fileObjects; function __construct($fileObjects) { $this-fileObjects = $fileObjects; } function get_content() { $content = ""; foreach ($this-fileObjects as $fileObject) { $content .= $fileObject-get_content().PHP_EOL; } return $content; }}

    Podemos agrupar todos los archivos en un archivo bundled-styles.csscreando una clase para este archivo:

    class StylesSiteBundle extends SiteBundleBase { function get_filename() { return "bundled-styles"; } function get_type() { return "css"; }}

    Finalmente, simplemente ponemos en cola estos archivos empaquetados, como antes, en lugar de todos los recursos independientes. Para CSS, creamos un paquete que contiene los archivos y header-colors.css, para lo cual simplemente creamos una instancia con el objeto PHP para cada uno de estos archivos (y de la misma manera podemos crear el archivo del paquete JS):background-image.cssfont-sizes.cssStylesSiteBundle

    $fileObjects = array( // CSS new HeaderColorsSiteResource(), new BackgroundImageSiteResource(), new FontSizesSiteResource(), // JS new WelcomeUserDataUserResource(), new UserShoppingItemsUserResource());$cssFileObjects = array_map(array(ResourceUtils::class, 'is_css'), $fileObjects);$jsFileObjects = array_map(array(ResourceUtils::class, 'is_js'), $fileObjects);// Use this definition of $fileEnqueuer instead of the previous one$fileEnqueuer = new ResourceEnqueuer( array( new StylesSiteBundle($cssFileObjects), new ScriptsSiteBundle($jsFileObjects) ));

    Eso es todo. Ahora solicitaremos solo un archivo JS y un archivo CSS en lugar de muchos.

     

    Una mejora final del rendimiento percibido implica priorizar los activos, retrasando la carga de aquellos que no se necesitan de inmediato. Abordemos esto a continuación.

    async/ deferAtributos para recursos JS

    Podemos agregar atributos asyncy defera la scriptetiqueta, para modificar cuándo se descarga, analiza y ejecuta el archivo JavaScript, para priorizar el JavaScript crítico y enviar todo lo que no sea crítico lo más tarde posible, disminuyendo así el tiempo de carga aparente del sitio.

    Para implementar esta característica, siguiendo los principios SOLID, debemos crear una nueva interfaz JSResource(que hereda de Resource) que contenga funciones is_asyncy is_defer. Sin embargo, esto cerraría la puerta a que stylelas etiquetas eventualmente también admitan estos atributos. Entonces, teniendo en cuenta la adaptabilidad, adoptamos un enfoque más abierto: simplemente agregamos un método genérico get_attributesa la interfaz Resourcepara mantenerla flexible para agregarlo a cualquier atributo (ya sea existente o aún por inventar) para las etiquetas scripty link:

    interface Resource { // Continued from above... function get_attributes();}abstract class ResourceBase implements Resource { // Continued from above... function get_attributes() { // By default, no extra attributes return ''; }}

    WordPress no ofrece una manera fácil de agregar atributos adicionales a los recursos en cola, por lo que lo hacemos de una manera bastante ingeniosa, agregando un gancho que reemplaza una cadena dentro de la etiqueta mediante la función add_script_tag_attributes:

    class ResourceEnqueuerUtils { protected static tag_attributes = array(); static function add_tag_attributes($handle, $attributes) { self::tag_attributes[$handle] = $attributes; } static function add_script_tag_attributes($tag, $handle, $src) { if ($attributes = self::tag_attributes[$handle]) { $tag = str_replace( " src='${src}'", " src='${src}' ".$attributes."", $tag ); } return $tag; }}// Initize by connecting to the WordPress hookadd_filter( 'script_loader_tag', array(ResourceEnqueuerUtils::class, 'add_script_tag_attributes'), PHP_INT_MAX, 3);

    Agregamos los atributos de un recurso al crear la instancia de objeto correspondiente:

    abstract class ResourceBase implements Resource { // Continued from above... function __construct() { ResourceEnqueuerUtils::add_tag_attributes($this-get_filename(), $this-get_attributes()); }}

    Finalmente, si welcomeuser-data.jsno es necesario ejecutar el recurso inmediatamente, podemos configurarlo como defer:

    class WelcomeUserDataUserResource extends UserResourceBase { // Continued from above... function get_attributes() { return "defer='defer'"; }}

    Debido a que se carga en forma diferida, una secuencia de comandos se cargará más tarde, adelantando el momento en el que el usuario puede interactuar con el sitio. En cuanto a las mejoras de rendimiento, ¡ya estamos listos!

     

    Queda una cuestión por resolver antes de que podamos relajarnos: ¿qué sucede cuando el sitio está alojado en varios servidores?

    Tratar con varios servidores detrás de un equilibrador de carga

    Si nuestro sitio está alojado en varios sitios detrás de un balanceador de carga y se regenera un archivo dependiente de la configuración del usuario, el servidor que maneja la solicitud debe, de alguna manera, cargar el archivo estático regenerado en todos los demás servidores; de lo contrario, los otros servidores servirán una versión obsoleta de ese archivo a partir de ese momento. Cómo hacemos esto? Hacer que los servidores se comuniquen entre sí no sólo es complejo, sino que, en última instancia, puede resultar inviable: ¿Qué sucede si el sitio se ejecuta en cientos de servidores, de diferentes regiones? Claramente, esta no es una opción.

    La solución que se me ocurrió es agregar un nivel de direccionamiento indirecto: en lugar de solicitar los archivos estáticos desde la URL del sitio, se solicitan desde una ubicación en la nube, como desde un depósito de AWS S3. Luego, al regenerar el archivo, el servidor cargará inmediatamente el nuevo archivo en S3 y lo entregará desde allí. La implementación de esta solución se explica en mi artículo anterior Compartir datos entre varios servidores a través de AWS S3 .

    Conclusión

    En este artículo, hemos considerado que insertar código JS y CSS no siempre es ideal, porque el código debe enviarse repetidamente al cliente, lo que puede afectar el rendimiento si la cantidad de código es significativa. Vimos, como ejemplo, cómo WordPress carga 43kb de scripts para imprimir el Media Manager, que son plantillas de JavaScript puro y perfectamente podrían cargarse como recursos estáticos.

    Por lo tanto, hemos ideado una manera de hacer que el sitio web sea más rápido transformando el código en línea dinámico JS y CSS en recursos estáticos, lo que puede mejorar el almacenamiento en caché en varios niveles (en el cliente, Service Workers, CDN), permite agrupar aún más todos los archivos. en un solo recurso JS/CSS para mejorar la proporción al comprimir la salida (como a través de GZip) y evitar una sobrecarga en los navegadores al procesar varios recursos simultáneamente (como en Chrome), y además permite agregar atributos asynco deferal scriptetiqueta para acelerar la interactividad del usuario, mejorando así el tiempo de carga aparente del sitio.

    Como efecto secundario beneficioso, dividir el código en recursos estáticos también permite que el código sea más legible, al tratar con unidades de código en lugar de grandes bloques de HTML, lo que puede conducir a un mejor mantenimiento del proyecto.

    La solución que desarrollamos fue hecha en PHP e incluye algunos bits de código específicos para WordPress, sin embargo, el código en sí es extremadamente simple, apenas unas pocas interfaces que definen propiedades y objetos que implementan esas propiedades siguiendo los principios SOLID, y una función para guardar un archivo al disco. Eso es practicamente todo. El resultado final es limpio y compacto, fácil de recrear para cualquier otro lenguaje y plataforma, y ​​no es difícil de introducir en un proyecto existente, lo que proporciona ganancias de rendimiento sencillas.

    (rb, ra, yk, il)Explora más en

    • WordPress
    • CSS
    • javascript
    • HTML





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Evitar los peligros del código insertado automáticamente

    Evitar los peligros del código insertado automáticamente

    Patrones de diseño para interfaces de IA, con Vitaly Friedman Planificación y proceso del sistema de diseño, con Nathan Curtis Índice

    programar

    es

    https://pseint.es/static/images/programar-evitar-los-peligros-del-codigo-insertado-automaticamente-958-0.jpg

    2024-04-04

     

    Evitar los peligros del código insertado automáticamente
    Evitar los peligros del código insertado automáticamente

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20