javascript - imagenes - Solución: cargue paquetes de Webpack 2 compilados de forma independiente de forma dinámica
webpack angular (0)
Me gustaría compartir cómo agrupar una aplicación que actúa como un host de complemento y cómo puede cargar complementos instalados de forma dinámica .
- Tanto la aplicación como los complementos se incluyen con Webpack
- La aplicación y los complementos se compilan y distribuyen de forma independiente .
Hay varias personas en la red que buscan una solución a este problema:
- Creación de proyectos múltiples y carga dinámica de módulos con paquete web
- Cargar paquetes prefabricados de webpack en tiempo de ejecución
- ¿Cómo exponer objetos del paquete de Webpack e inyectar libs externos en el paquete compilado?
- La dinámica requiere
https://github.com/webpack/webpack/issues/118
La solución que se describe aquí está basada en el comentario de @sokra del 17 de abril de 2014 sobre el problema # 118 de Webpack y está ligeramente adaptada para funcionar con Webpack 2. https://github.com/webpack/webpack/issues/118
Puntos principales:
Un complemento necesita una ID (o "URI") mediante la cual se registra en el servidor back-end, y que es exclusivo de la aplicación.
Para evitar colisiones de ID de módulo / módulo para cada complemento, se usarán funciones de cargador
JSONP
individuales para cargar los fragmentos del complemento.La carga de un complemento se inicia mediante elementos
<script>
creados dinámicamente (en lugar derequire()
) y permite que la aplicación principal eventualmente consuma las exportaciones del complemento a través de una devolución de llamadaJSONP
.
Nota: Puede encontrar que la redacción "JSONP" de Webpack es engañosa ya que en realidad no se transfiere ningún JSON
pero el complemento del Javascript está envuelto en una "función de cargador". No se produce relleno en el lado del servidor.
Construyendo un plugin
La configuración de compilación de un plugin utiliza las opciones output.library
y output.libraryTarget
.
Ejemplo de configuración del complemento:
module.exports = {
entry: ''./src/main.js'',
output: {
path: path.resolve(__dirname, ''./dist''),
publicPath: ''/'' + pluginUri + ''/'',
filename: ''js/[name].js'',
library: pluginIdent,
libraryTarget: ''jsonp''
},
...
}
Depende del desarrollador del complemento elegir una ID única (o "URI") para el complemento y ponerlo a disposición en la configuración del complemento. Aquí uso la variable pluginURI
:
// unique plugin ID (using dots for namespacing)
var pluginUri = ''com.companyX.pluginY''
Para la opción de library
también debe especificar un nombre único para el complemento. Webpack usará este nombre cuando genere las funciones del cargador JSONP
. Obtengo el nombre de la función del URI del complemento:
// transform plugin URI into a valid function name
var pluginIdent = "_" + pluginUri.replace(//./g, ''_'')
Tenga en cuenta que cuando se configura la opción de library
, Webpack obtiene un valor para la opción output.jsonpFunction
automáticamente.
Al compilar el complemento, Webpack genera 3 archivos de distribución:
dist/js/manifest.js
dist/js/vendor.js
dist/js/main.js
Tenga en cuenta que vendor.js
y main.js
están envueltos en las funciones del cargador JSONP cuyos nombres se toman de output.jsonpFunction
y output.library
respectivamente.
Su servidor de back-end debe servir los archivos de distribución de cada complemento instalado. Por ejemplo, mi servidor back-end sirve el contenido del directorio dist/
de un plugin bajo el URI del plugin como el primer componente de ruta:
/com.companyX.pluginY/js/manifest.js
/com.companyX.pluginY/js/vendor.js
/com.companyX.pluginY/js/main.js
Es por eso que publicPath
se establece en ''/'' + pluginUri + ''/''
en la configuración del plugin de ejemplo.
Nota: Los archivos de distribución se pueden servir como recursos estáticos. El servidor de fondo no está obligado a hacer ningún relleno (la "P" en JSONP
). Los archivos de distribución ya están "rellenos" por Webpack en tiempo de compilación.
Cargando complementos
Se supone que la aplicación principal recupera la lista de los complementos instalados (URI) del servidor back-end.
// retrieved from server
var pluginUris = [
''com.companyX.pluginX'',
''com.companyX.pluginY'',
''org.organizationX.pluginX'',
]
A continuación, carga los complementos:
loadPlugins () {
pluginUris.forEach(pluginUri => loadPlugin(pluginUri, function (exports) {
// the exports of the plugin''s main file are available in `exports`
}))
}
Ahora la aplicación tiene acceso a las exportaciones del complemento. En este punto, el problema original de cargar un plugin compilado independientemente está básicamente resuelto :-)
Se carga un complemento cargando sus 3 fragmentos ( manifest.js
, vendor.js
, main.js
) en secuencia. Una vez que se carga main.js, se invocará la devolución de llamada.
function loadPlugin (pluginUri, mainCallback) {
installMainCallback(pluginUri, mainCallback)
loadPluginChunk(pluginUri, ''manifest'', () =>
loadPluginChunk(pluginUri, ''vendor'', () =>
loadPluginChunk(pluginUri, ''main'')
)
)
}
La invocación de devolución de llamada funciona definiendo una función global cuyo nombre es igual a output.library
que en la configuración del complemento. La aplicación deriva ese nombre del pluginUri
(tal como lo hicimos en la configuración del plugin ya).
function installMainCallback (pluginUri, mainCallback) {
var _pluginIdent = pluginIdent(pluginUri)
window[_pluginIdent] = function (exports) {
delete window[_pluginIdent]
mainCallback(exports)
}
}
Un trozo se carga creando dinámicamente un elemento <script>
:
function loadPluginChunk (pluginUri, name, callback) {
return loadScript(pluginChunk(pluginUri, name), callback)
}
function loadScript (url, callback) {
var script = document.createElement(''script'')
script.src = url
script.onload = function () {
document.head.removeChild(script)
callback && callback()
}
document.head.appendChild(script)
}
Ayudante:
function pluginIdent (pluginUri) {
return ''_'' + pluginUri.replace(//./g, ''_'')
}
function pluginChunk (pluginUri, name) {
return ''/'' + pluginUri + ''/js/'' + name + ''.js''
}