Cómo implementa Webpack el almacenamiento en caché persistente
Esta vez le mostraré cómo Webpack implementa el almacenamiento en caché persistente y cuáles son las precauciones para que Webpack implemente el almacenamiento en caché persistente. El siguiente es un caso práctico, echemos un vistazo.
Prólogo
Recientemente estuve mirando cómo Webpack realiza el almacenamiento en caché persistente y descubrí que todavía hay algunos errores. Solo tengo tiempo para resolverlos y resumirlos. En este artículo, puede comprender aproximadamente:
¿Qué es el almacenamiento en caché persistente y por qué hacemos el almacenamiento en caché persistente?
¿Cómo realiza Webpack el almacenamiento en caché persistente?
Algunos puntos a tener en cuenta al utilizar webpack para el almacenamiento en caché.
Caché persistente
En primer lugar, debemos explicar qué es el caché persistente. En el contexto de la popularidad actual de las aplicaciones con separación de front-end y back-end, el front-end. final html, css y js a menudo existe en el servidor en forma de un archivo de recursos estático y obtiene datos a través de la interfaz para mostrar contenido dinámico. Esto implica la cuestión de cómo la empresa implementa el código de front-end, por lo que implica un problema de implementación de actualizaciones. ¿Debería implementarse primero la página o los recursos?
Primero implemente la página y luego implemente los recursos: durante el intervalo de tiempo entre las dos implementaciones, si un usuario accede a la página, el recurso anterior se cargará en la nueva estructura de la página y la versión anterior de el recurso se considerará como Cuando la nueva versión se almacena en caché, el resultado es que el usuario accede a una página con un estilo desordenado. A menos que se actualice manualmente, la página permanecerá en un estado desordenado hasta que caduque el caché de recursos.
Primero implemente recursos, luego implemente páginas: durante el intervalo de implementación, los usuarios con recursos almacenados en caché local de versiones anteriores visitan el sitio web. Dado que la página solicitada es una versión anterior, la referencia del recurso no ha cambiado y la página solicitada es una versión anterior. El navegador usará directamente el caché local. Sin embargo, cuando los usuarios que no tienen caché local o cuyo caché ha caducado visitan el sitio web, la página de la versión anterior cargará el recurso de la nueva versión, lo que provocará errores de ejecución de la página.
Por lo tanto, necesitamos una estrategia de implementación para garantizar que cuando actualicemos nuestro código en línea, los usuarios en línea puedan realizar la transición sin problemas y abrir nuestro sitio web correctamente.
Se recomienda leer primero esta respuesta: ¿Cómo desarrollar e implementar código front-end en una gran empresa?
Cuando termine de leer la respuesta anterior, comprenderá aproximadamente que la solución de almacenamiento en caché persistente más madura ahora es agregar un valor hash después del nombre del recurso estático, porque el valor hash se genera cada vez que el archivo se modifica es diferente, la ventaja de hacer esto es publicar archivos de forma incremental, evitando sobrescribir archivos anteriores y provocar que falle el acceso de los usuarios en línea.
Porque siempre que los nombres de los recursos estáticos (css, js, img) publicados cada vez sean únicos, entonces puedo:
Para archivos html: desactivar el almacenamiento en caché, poner el html en su propio servidor, desactive el caché del servidor, y su servidor solo proporcionará archivos html e interfaces de datos
Para js, css, imágenes y otros archivos estáticos: active cdn y caché, y convierta archivos estáticos Cuando los recursos se cargan en el proveedor de servicios CDN, podemos habilitar el almacenamiento en caché a largo plazo de los recursos. Debido a que la ruta de cada recurso es única, no provocará que el recurso se sobrescriba, lo que garantiza la estabilidad del acceso de los usuarios en línea.
Cada vez que se publica una actualización, los recursos estáticos (js, css, img) se transfieren primero al servicio cdn y luego se carga el archivo html. Esto no solo garantiza que los usuarios antiguos puedan acceder a él. normalmente, pero también permite que los nuevos usuarios vean la nueva página.
Lo anterior presenta brevemente las principales soluciones de almacenamiento en caché persistente de front-end, entonces, ¿por qué necesitamos realizar un almacenamiento en caché persistente?
Cuando un usuario visita nuestro sitio por primera vez usando un navegador, la página introduce una variedad de recursos estáticos. Si podemos lograr un almacenamiento en caché persistente, podemos hacerlo en /happylindz/blog.git.
p>cd blog/code/multiple-page-webpack-demo
npm install Antes de leer el siguiente contenido, le recomiendo encarecidamente que lea mi artículo anterior: Comprensión profunda del archivo webpack Mecanismo de empaquetado, comprender el mecanismo de empaquetado de los archivos webpack le ayudará a implementar mejor el almacenamiento en caché persistente.
El ejemplo se describe aproximadamente así: consta de dos páginas, páginaA y páginaB
// src/pageA.js
importa el componenteA desde './ common/componentA';
// Cuando se utiliza la biblioteca de terceros jquery, es necesario separarla para evitar que el archivo de empaquetado empresarial sea demasiado grande
import $ from 'jquery';
// Carga archivos css, algunos de los cuales son estilos públicos y otros son estilos únicos, que deben extraerse
import './css/common.css'
importar './css/pageA.css';
console.log(componentA);
console.log($.trim(' hacer algo '));
// src/pageB.js
// Tanto las páginas A como B utilizan el módulo público componenteA, que debe extraerse para evitar cargas repetidas
importar componenteA desde './common/componentA';
importar componenteB desde './common/componentB';
importar './css/common.css'
importar './css/pageB.css';
console.log(componentA);
console.log(componentB);
// El módulo de carga asíncrono usado asyncComponent debe desconectarse para cargar la primera pantalla más rápido
document.getElementById('xxxxx').addEventListener('click', () => {
import( / * webpackChunkName: "async" */
'./common/asyncComponent.js').then((async) => {
async( );
})
})
// El módulo público *** básicamente se ve así
exportar el "componente predeterminado" X"; el contenido de la página anterior es básicamente simple. Implica nuestros tres modos de dividir módulos: dividir bibliotecas públicas, cargar y dividir módulos públicos a pedido.
Luego, el siguiente paso es configurar el paquete web:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entrada: {
páginaA: [ruta. resolver (nombredir, './src/pageA.js')],
páginaB: ruta.resolve(nombredir, './src/pageB.js'),
} ,
salida: {
ruta: ruta.resolve(dirname, './dist'),
nombre de archivo: 'js/[nombre].[ chunkhash :8].js',
chunkFilename: 'js/[nombre].[chunkhash:8].js'
},
módulo: {
reglas: [
{
// Utilice reglas regulares para hacer coincidir los archivos CSS que convertirá este cargador
prueba : /. css$/,
uso: ExtractTextPlugin.extract({
respaldo: "style-loader",
uso: ["css-loader" "]
})
}
]
},
complementos: [
nuevo webpack.optimize.CommonsChunkPlugin({
nombre: 'común',
minChunks: 2,
}),
nuevo webpack.optimize.CommonsChunkPlugin({
nombre: 'proveedor',
minChunks: ({ recurso }) => (
recurso && recurso .indexOf( 'node_modules') >= 0 && recurso.match(/.js$/)
)
}),
nuevo ExtractTextPlugin({
nombre de archivo: `css/[nombre].[chunkhash:8].css`,
}),
]
} Primero, se usa un CommonsChunkPlugin para extraer módulos públicos ***, lo que equivale a decir jefe del paquete web. Si ve que un módulo se carga dos veces o más, ayúdeme a moverlo al fragmento común. Aquí, minChunks es 2. y la granularidad es el desmontaje más detallado, puede elegir cuántas veces utilizar los módulos antes de retirarlos según su situación real.
El segundo CommonsChunkPlugin se utiliza para extraer códigos de terceros, extraerlos y determinar si los recursos provienen de node_modules. Si es así, significa que son módulos de terceros y luego extraerlos.
Es equivalente a decirle al jefe del paquete web que si ve algunos módulos provenientes del directorio node_modules y sus nombres terminan en .js, muévalos al fragmento del proveedor. Si el fragmento del proveedor no existe, cree uno nuevo.
¿Cuáles son los beneficios de esta configuración? A medida que nuestro negocio crece, es probable que dependamos de más y más códigos de biblioteca de terceros. Si configuramos especialmente una entrada para almacenar código de terceros, entonces nuestro. webpack.config.js se convertirá en:
// No propicio para la expansión
module.exports = {
entrada: {
aplicación: './src/main.js',
proveedor: [
'vue',
'axio',
'vue-router',
'vuex',
// más
],
},
} El tercer complemento ExtractTextPlugin se utiliza para extraer CSS del archivo js empaquetado y generar un archivo CSS independiente. Imagine que cuando solo modifica el estilo y no modifica la lógica funcional de la página, definitivamente no lo hace. No desea que cambie el valor hash de su archivo js. Definitivamente desea que css y js estén separados entre sí y no se afecten entre sí.
Después de ejecutar webpack, puede ver el efecto de empaquetado:
├── css
│ ├── common.2beb7387.css
│ ├── páginaA.d178426d.css
│ └── páginaB.33931188.css
└── js
├── async. 03f28faf.js
├── common.2beb7387.js
├── pageA.d178426d.js
├── pageB.33931188.js p>
└── seller.22a1d956.js Puede ver que css y js se han separado y hemos dividido el módulo para garantizar la unicidad del fragmento del módulo. Cada vez que actualice el código, se generará. Diferentes valores hash.
Con la unicidad, debemos garantizar la estabilidad del valor hash. Imagine este escenario. Definitivamente no desea que la modificación de una determinada parte del código (módulo, CSS) provoque el valor hash. del archivo para que esté lleno, entonces obviamente no es prudente. Entonces, ¿cómo podemos minimizar el cambio del valor hash?
En otras palabras, ¿necesitamos descubrir los factores que causan fallas de caché en la compilación del paquete web y encontrar formas de resolverlos u optimizarlos?
El cambio en el valor de chunkhash se debe principalmente a las siguientes cuatro partes:
El código fuente que contiene el módulo
El código de tiempo de ejecución utilizado por webpack para comenzar a ejecutarse
El ID del módulo generado por el paquete web (incluido el ID del módulo y el ID del módulo dependiente al que se hace referencia)
chunkID
Siempre que alguna de estas cuatro partes cambie, la puntuación generada será diferente y el caché dejará de ser válido. Las cuatro partes se introducirán una por una a continuación:
1. Cambios en el código fuente:
Obviamente innecesario. es decir, el caché debe Es necesario actualizarlo; de lo contrario, habrá problemas
2. El código de tiempo de ejecución para iniciar y ejecutar el paquete web:
Ha leído mi artículo anterior: Si Si tiene un conocimiento profundo del mecanismo de empaquetado de archivos del paquete web, sabrá que en el paquete web es necesario ejecutar algún código de inicio durante el inicio.
(función(módulos) {
ventana["webpackJsonp"] = función webpackJsonpCallback(chunkIds, moreModules) {
// ...
};
function webpack_require(moduleId) {
// ...
}
webpack_require.e = función requireEnsure(chunkId, devolución de llamada) {
// ...
script.src = webpack_require.p + "" + chunkId + "." "páginaA","1":"páginaB","3":"proveedor"}[chunkId]||chunkId) + "." + {"0":"e72ce7d4","1":"69f6bbe3"," 2":"9adbbaa0","3":"53fa02a7"}[chunkId] + ".js";
};
})([]);El contenido aproximado es como Como se muestra arriba, son algunos códigos de inicio de webpack. Son funciones que le indican al navegador cómo cargar los módulos definidos por webpack.
Hay una línea de código que cambiará cada vez que se actualice, porque el código de inicio necesita conocer claramente la relación correspondiente entre los valores chunkid y chunkhash, de modo que la ruta al archivo js asincrónico pueda ser empalmados correctamente durante la carga asincrónica.
Entonces, ¿en qué archivo se coloca finalmente esta parte del código? Debido al módulo de fragmento común que acabamos de generar al configurarlo, esta parte del código de tiempo de ejecución se integrará directamente en él, lo que da como resultado que cada vez que actualicemos nuestro código comercial (página A, página B, módulo), el fragmento común se mantendrá. Está cambiando, pero esto obviamente no cumple con nuestras expectativas, porque solo necesitamos usar un fragmento común para almacenar el módulo público (aquí se refiere al componenteA), por lo que no modifiqué el componenteA, entonces, ¿por qué es necesario cambiar el fragmento?
Por lo tanto, necesitamos extraer esta parte del código de ejecución en un archivo separado.
module.exports = {
// ...
complementos: [
// ...
//Ponlo detrás de otros CommonsChunkPlugin
new webpack.optimize.CommonsChunkPlugin({
nombre: 'runtime',
minChunks: Infinity,
}),
]
}Esto equivale a decirle a webpack que me ayude a extraer el código de tiempo de ejecución y colocarlo en un archivo separado.
├── css
│ ├── common.4cc08e4d.css
│ ├── pageA.d178426d.css
│ └── pageB.33931188.css
└── js
├── async.03f28faf.js
├── common.4cc08e4d.js
├── pageA.d178426d.js
├── pageB.33931188.js
├── runtime.8c79fdcd.js
└── seller.cef44292.js genera un runtime.xxxx.js adicional. Cuando cambie el código comercial en el futuro, el valor hash del fragmento común no cambiará. En cambio, el valor hash del fragmento de tiempo de ejecución sí. cambiar Dado que esta parte del código es dinámica, puede usar chunk-manifest-webpack-plugin para insertarlos en HTML y reducir una solicitud de red.
3. ID de módulo del módulo generado por webpack
El complemento OccurrenceOrderPlugin se carga de forma predeterminada en webpack2. El complemento OccurrenceOrderPlugin se ordenará por los módulos que se han introducido más. Cuanto menor sea el moduleId del módulo que se ha introducido con más frecuencia, esto sigue siendo inestable a medida que aumenta la cantidad de código, aunque cuanto menor sea el moduleId del número de referencias de código, es menos probable. cambio, sigue siendo inevitablemente incierto.
De forma predeterminada, la identificación del módulo es el índice del módulo en la matriz de módulos. OccurenceOrderPlugin colocará los módulos con más referencias primero. El orden de los módulos será consistente cada vez que compile. Si agrega o elimina algunos módulos al modificar el código, esto puede afectar las ID de todos los módulos.
La solución recomendada es utilizar el complemento HashedModuleIdsPlugin. Este complemento generará una cadena de cuatro dígitos como identificación del módulo en función de la ruta relativa del módulo, que no solo oculta la información del módulo. información de ruta, pero también reduce el costo de la longitud de la identificación del módulo.
De esta manera, la única forma de cambiar moduleId es cambiar la ruta del archivo. Mientras el valor de la ruta del archivo permanezca sin cambios, la cadena de cuatro dígitos generada permanecerá sin cambios y el valor hash también. permanecer sin cambios. Agregar o eliminar módulos de código comercial no tendrá ningún impacto en moduleid.
module.exports = {
complementos: [
new webpack.HashedModuleIdsPlugin(),
// Ponlo al frente
// ...
]
} 4. chunkID
En situaciones reales, el orden del número de fragmentos es cambiado varias veces. La mayoría de ellos se corrigen entre compilaciones y no son fáciles de cambiar.
Lo que está involucrado aquí es solo la división de módulos relativamente básica. Hay algunas otras situaciones que no se han tenido en cuenta. Por ejemplo, si el componente de carga asincrónica contiene un módulo público, el módulo público puede ser. separados de nuevo. Forme un módulo de fragmento de *** público asincrónico.
Si desea obtener más información, puede leer este artículo: División de código de Webpack Dafa
Algunos puntos a tener en cuenta al realizar el almacenamiento en caché del paquete web
El problema del valor hash de archivo CSS no válido p>
No se recomienda utilizar el complemento DllPlugin para publicaciones en línea
El problema del valor hash del archivo CSS no válido:
ExtractTextPlugin tiene un problema grave, es decir , el [chunkhash] que usa para generar nombres de archivos se toma directamente del fragmento js que hace referencia al segmento de código css en otras palabras, si solo modifico el segmento de código css sin tocar el código js, entonces el archivo css final generado; El nombre seguirá sin cambios.
Entonces necesitamos cambiar chunkhash en ExtractTextPlugin a contenthash. Como sugiere el nombre, contenthash representa el valor hash del contenido del archivo de texto, es decir, solo el valor hash del archivo de estilo. De esta manera, los archivos js y css compilados tienen valores hash independientes.
module.exports = {
complementos: [
// ...
nuevo ExtractTextPlugin({
nombre de archivo: `css