Suporte para importações ES6 no módulo ES5

8

Para meus alunos do primeiro ano, eu forneci uma biblioteca simples baseada no ES5, escrita usando o Revealing Module Pattern. Aqui está um trecho do módulo / espaço para nome "principal", que abrigará outras extensões:

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

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

Isso funciona para praticamente 99,9% dos estudantes que são novos no desenvolvimento da Web e não estão usando coisas sofisticadas como o ES6 em combinação com o Webpack ou o Babel.

Os 0,1% agora solicitaram que eu forneça uma versão baseada no ES6, que pode ser importada corretamente. Eu ficaria feliz em fornecer isso, mas estou meio confuso sobre a melhor maneira de abordar isso.

Obviamente, quero manter o caminho do ES5, para que meus alunos possam apenas incluir o arquivo usando uma tag de script e digitar Library.SomeExtension.aFunction();sempre que desejarem. Além disso, algumas das extensões dependem do jQuery, que é injetado de maneira semelhante ao snippet acima.

Agora estou procurando uma maneira sustentável de obter o melhor dos dois mundos, com uma base de código e o jQuery como dependência. Quero dar 99,9% a window.Library, enquanto quero dar aos 0,1% uma maneira de usar import Library from 'library'.

Posso fazer isso com um único arquivo JS que faz as duas coisas? Ou precisaria de uma versão especial do ES6 (não é um problema)? E acima de tudo: como eu reorganizaria meu código (todos semelhantes ao snippet acima) de maneira a suportar ambas as situações?

Todos e quaisquer indicadores serão muito apreciados!

EDIT: Assim como uma nota lateral, eu já tenho um gulpfile.jsno lugar que decorre esta biblioteca através de Babel, minifiersentre outras coisas. Portanto, ter que estender isso para resolver o problema acima não é um problema!

Lennard Fonteijn
fonte
Provavelmente, melhor resolvido usando o ES6 e um transpiler / bundler que gera o módulo revelador global. Em seguida, ofereça seu módulo de origem a 0,1%. então sim, um único arquivo de origem, mas dois arquivos distribuídos.
Bergi 26/11/19
Espero uma resposta para Posso fazer isso com um único arquivo JS que faz as duas coisas? - se isso for possível, o que pode não ser, como seria o script?
Snow
@Snow Atualmente, brinca com os recursos fornecidos nas respostas. Preciso gastar um pouco mais de tempo para conseguir algo que eu esteja satisfeito, mas parece possível fazer se você for um pouco criativo ao criar seus scripts! No meu caso particular, eu realmente quero manter a Library.SubLibrary.functionNameinstalação e também em arquivos separados, mesmo se alguém a importar como ES6 ou qualquer outra coisa. (Isso ocorre principalmente porque as extensões também podem se usar e, usando uma configuração padrão do ES6, isso causaria referências circulares).
Lennard Fonteijn 30/11/19
Sim, é trivial fazer isso usando arquivos separados: use um exporte o outro atribua window. Eu estava pensando que seria ótimo se as duas coisas pudessem ser feitas em um único arquivo, já que isso pareceria muito mais elegante, mas a exportpalavra - chave parece funcionar apenas dentro de a type=module. Não sei se há uma solução alternativa ou se é apenas impossível.
Neve
As dependências circulares do @LennardFonteijn funcionam totalmente bem com os módulos ES6, apenas certifique-se de declarar apenas a funcionalidade exportada e de não executar o código de inicialização (que depende da ordem de avaliação do módulo) no nível superior. Eu recomendaria não usar objetos de namespace aninhados, isso é bastante unidiomatic no ES6.
Bergi 02/12/19

Respostas:

5

Depois de algumas noites movendo o código e instalando tantos pacotes gulp que nem me lembro de todos os que tentei, acabei decidindo por algo com o qual estou quase feliz.

Primeiro, para responder minha própria pergunta, que também foi apoiada por @Bergi nos comentários:

Resposta curta: Não, você não pode, de forma alguma (no momento), misturar a sintaxe do ES6 com o ES5 (módulos) em um único arquivo, porque o navegador irá errar no uso de export.

Resposta longa: tecnicamente, você pode definir seu tipo de elemento de script como "module", o que fará com que os navegadores aceitem a exportpalavra - chave. No entanto, seu código simplesmente será executado duas vezes (uma vez no carregamento e uma vez após a importação). Se você pode viver com isso, então o não se torna um sim, meio.

Então, o que eu acabei fazendo? Deixe-me colocar à frente que eu realmente queria manter a configuração do módulo IIFE que eu tinha (por enquanto) no arquivo ES5 de saída. Atualizei minha base de código para o ES6 puro e tentei todos os tipos de combinações de plugins (incluindo o Rollup.js mencionado por @David Bradshaw). Mas eu simplesmente não conseguia fazê-los funcionar, ou eles manipulariam completamente a saída de tal maneira que os navegadores não pudessem mais carregar o módulo ou abandonariam o suporte ao mapa de origem. Com o projeto que os alunos estão concluindo no momento, já perdi meu tempo livre o suficiente para 0,1% dos meus alunos. Então, melhor sorte no próximo ano para uma solução "melhor".

  1. Baseei meu padrão UMD (como mencionado por @Sly_cardinal) no jQuery e joguei esse código em um Library.umd.js. Para a parte ES6, fiz um Library.es6.jscom apenas um export default Library.

  2. Nos dois arquivos, coloquei um comentário /*[Library.js]*/com várias linhas em que queria injetar o Library.js não minificado e concatenado.

  3. Eu modifiquei a minha tarefa Gulp existente para apenas construir o Library.jscom concate Babele armazenado em um local temporário. Eu escolhi sacrificar o suporte ao mapa de origem nesse processo, pois o código concatenado era perfeitamente legível na saída. Tecnicamente, o suporte ao mapa de origem "funcionou", mas ainda assim descartaria muito o depurador.

  4. Adicionei uma tarefa Gulp adicional para ler o conteúdo da temperatura e Library.js, em seguida, para cada um dos arquivos da Etapa 1, localizaria e substituiria o comentário de várias linhas pelo conteúdo. Salve os arquivos resultantes e jogue-os na geração final concat(por exemplo, pacote com jQuery) tersere no mapa de origem.

O resultado final são dois arquivos:

  • Um com suporte ao navegador UMD / ES5
  • Um com suporte ES6

Enquanto estou feliz com o resultado, não gosto da cadeia de tarefas Gulp e da perda de suporte ao mapa de origem na primeira metade do processo. Como dito anteriormente, tentei vários plugins gulp para obter o mesmo efeito (por exemplo gulp-inject), mas eles não funcionaram corretamente ou fizeram coisas estranhas no mapa de origem (mesmo que eles alegassem suportá-lo).

Para um próximo empreendimento, posso procurar algo diferente do Gulp ou criar meu próprio plugin, que faz o que foi descrito acima, mas corretamente: P

Lennard Fonteijn
fonte
0

Você pode usar o Rollup.js para agrupar seu código como uma biblioteca ES5, bem como um módulo ES6 que o acompanha.

O rollup possui suporte interno para o Gulp, portanto, algo assim seria um ponto de partida razoável (com base no exemplo do Gulp do Rollup):

    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
            })
          ]);
      });
    });

Você pode ver mais informações sobre os diferentes formatos de saída disponíveis: https://rollupjs.org/guide/en/#outputformat

Geralmente, é mais fácil se a fonte da sua biblioteca for gravada usando módulos ES e depois agrupada / transpilada até o pacote ES5.

Sly_cardinal
fonte
Isso parece ótimo e exatamente o que eu preciso! Vai dar-lhe uma volta e relatório de volta :)
Lennard Fonteijn