Soporte para importaciones ES6 en módulo ES5

8

Para mis alumnos de 1er año, proporcioné una biblioteca simple basada en ES5 escrita usando el Patrón del módulo revelador. Aquí hay un fragmento del módulo / espacio de nombres "principal", que albergará otras extensiones:

window.Library = (function ($) {
    if (!$) {
        alert("The Library is dependent on jQuery, which is not loaded!");
    }

    return {};
})(window.jQuery);

Esto funciona para casi el 99.9% de los estudiantes que son nuevos en el desarrollo web y no están usando cosas elegantes como ES6 en combinación con Webpack o Babel.

El 0.1% ahora me ha solicitado que proporcione una versión basada en ES6, que se puede importar correctamente. Estaré encantado de proporcionar esto, pero estoy un poco atascado en cómo abordarlo mejor.

Obviamente quiero mantener el estilo ES5, para que mis alumnos puedan incluir el archivo usando una etiqueta de script y escribir Library.SomeExtension.aFunction();donde quieran. Además de eso, algunas de las extensiones dependen de jQuery, que se inyecta de manera similar al fragmento anterior.

Ahora estoy buscando alguna forma mantenible para obtener lo mejor de ambos mundos, con una base de código, con jQuery como dependencia. Quiero dar un 99.9% a window.Library, mientras que quiero darle al 0.1% una forma de usar import Library from 'library'.

¿Puedo lograr esto con un solo archivo JS que hace ambas cosas? ¿O necesitaría una versión especial de ES6 (no es un problema)? Y sobre todo: ¿cómo reorganizaría mi código (todo similar al fragmento anterior) de tal manera que pueda soportar ambas situaciones?

Cualquiera y todos los punteros serán muy apreciados!

EDITAR: Solo como nota al margen, ya tengo un gulpfile.jslugar en el que se ejecuta esta biblioteca Babel, minifiersy otras cosas. ¡Tener que extender eso para resolver el problema anterior no es un problema!

Lennard Fonteijn
fuente
Probablemente se resuelva mejor usando ES6 usted mismo y un transpiler / bundler que genera el módulo revelador global. Luego ofrezca su módulo fuente al 0.1%. entonces sí, un único archivo fuente, pero dos archivos distribuidos.
Bergi
Espero una respuesta a ¿Puedo lograr esto con un solo archivo JS que haga ambas cosas? - Si esto es posible, lo que podría no ser, ¿cómo sería el guión?
Nieve
@Snow Actualmente jugando con los recursos proporcionados en las respuestas. Tengo que pasar un poco más de tiempo para obtener algo con lo que estoy satisfecho, ¡pero parece posible hacerlo si eres un poco creativo creando tus scripts! En mi caso particular, realmente quiero mantener la Library.SubLibrary.functionNameconfiguración y también en archivos separados, incluso si alguien lo importa como ES6 o cualquier otra cosa. (Esto se debe principalmente a que las extensiones también pueden usarse entre sí y el uso de una configuración estándar de ES6 podría causar referencias circulares).
Lennard Fonteijn
Sí, es trivial lograr esto usando archivos separados: use uno exporty asigne el otro window. Estaba pensando que sería genial si ambos se pudieran hacer en un solo archivo, ya que eso se sentiría mucho más elegante, pero la exportpalabra clave solo parece funcionar dentro de a type=module. No sé si hay una solución, o si es simplemente imposible.
Nieve
@LennardFonteijn Las dependencias circulares funcionan totalmente bien con los módulos ES6, solo asegúrese de declarar la funcionalidad exportada y no ejecutar el código de inicialización (que depende del orden de evaluación del módulo) en el nivel superior. Recomendaría no usar objetos de espacio de nombres anidados, eso es bastante unidiomático en ES6.
Bergi

Respuestas:

5

Después de pasar algunas noches moviendo el código e instalando tantos paquetes de tragos que ni siquiera recuerdo todos los que probé, finalmente me decidí por algo con lo que estoy semi-feliz.

Primero, para responder mi propia pregunta, que también fue respaldada por @Bergi en los comentarios:

Respuesta corta: No, no puede, de ninguna manera (en este momento), mezclar la sintaxis de ES6 con ES5 (módulos) en un solo archivo porque el navegador generará un error en el uso de export.

Respuesta larga: técnicamente puede establecer su tipo de elemento de script en "módulo", lo que hará que los navegadores acepten la exportpalabra clave. Sin embargo, su código simplemente se ejecutará dos veces (una vez en carga y una vez después de la importación). Si puedes vivir con esto, entonces el no se convierte en un sí, un poco.

Entonces, ¿qué terminé haciendo? Permítanme poner al frente. Realmente quería mantener la configuración del Módulo IIFE que tenía (por ahora) en el archivo ES5 de salida. Actualicé mi base de código a ES6 puro y probé todo tipo de combinaciones de complementos (incluido Rollup.js mencionado por @David Bradshaw). Pero simplemente no pude hacer que funcionaran, o estropearían completamente la salida de tal manera que los navegadores ya no pudieran cargar el módulo o dejarían de admitir el mapa de origen. Con el proyecto que los estudiantes están haciendo en este momento llegando a su fin, ya desperdicié suficiente tiempo libre para el 0.1% de mis estudiantes. Así que mejor suerte el año que viene para una solución "más agradable".

  1. Basé mi patrón UMD (como lo mencionó @Sly_cardinal) en jQuery y lancé ese código a un Library.umd.js. Para la parte ES6, hice un Library.es6.jssolo con un export default Library.

  2. En ambos archivos, coloqué un comentario /*[Library.js]*/de varias líneas donde quería inyectar el Library.js no minificado concatenado.

  3. He modificado mi tarea existente Gulp a poco construir el Library.jscon concaty Babely se almacena en una ubicación temporal. Elegí sacrificar el soporte del mapa fuente en este proceso, ya que el código concatenado era perfectamente legible en la salida. Técnicamente, el soporte de mapas de origen "funcionó", pero aún así eliminaría demasiado el depurador.

  4. Agregué una tarea Gulp adicional para leer el contenido de la temperatura Library.js, luego para cada uno de los archivos del Paso 1, encontraría y reemplazaría el comentario de varias líneas con el contenido. Guarde los archivos resultantes y luego páselos a través de la generación final concat(por ejemplo, paquete con jQuery) tersery el mapa fuente.

El resultado final son dos archivos:

  • Uno con soporte para navegador UMD / ES5
  • Uno con soporte ES6

Si bien estoy contento con el resultado, no me gusta la cadena de tareas Gulp y la pérdida de compatibilidad con el mapa fuente en la primera mitad del proceso. Como se dijo anteriormente, probé numerosos complementos de gulp para lograr el mismo efecto (p. Ej. gulp-inject), Pero no funcionaron correctamente o hicieron cosas extrañas en el mapa fuente (incluso si decían admitirlo).

Para un próximo esfuerzo, podría buscar algo diferente a Gulp, o crear mi propio complemento que hace lo anterior, pero correctamente: P

Lennard Fonteijn
fuente
0

Puede usar Rollup.js para agrupar su código como una biblioteca ES5, así como un módulo ES6 que lo acompaña.

Rollup tiene soporte incorporado para Gulp, por lo que algo como esto sería un punto de partida razonable (basado en el ejemplo de Rollup Gulp):

    const gulp = require('gulp');
    const rollup = require('rollup');
    const rollupTypescript = require('rollup-plugin-typescript');

    gulp.task('bundle', () => {
      return rollup.rollup({
        input: './src/main.ts',
        plugins: [
          rollupTypescript()
        ]
      }).then(bundle => {
          return Promise.all([
              // Build for ES5
              bundle.write({
                file: './dist/library.js',
                format: 'umd',
                name: 'Library',
                sourcemap: true
            }),

            // Build for ES6 modules
            bundle.write({
                file: './dist/library.esm.js',
                format: 'esm',
                sourcemap: true
            })
          ]);
      });
    });

Puede ver más información sobre los diferentes formatos de salida disponibles: https://rollupjs.org/guide/en/#outputformat

En general, es más fácil si la fuente de su biblioteca se escribe utilizando módulos ES y luego se agrupa / transpila al paquete ES5.

Sly_cardinal
fuente
¡Esto se ve genial y exactamente lo que necesito! Le dará un giro e informará :)
Lennard Fonteijn