Como exportar vários módulos ES6 de um pacote NPM

16

Criei um pacote NPM relativamente pequeno, composto por aproximadamente 5 classes ES6 diferentes contidas em um arquivo cada, todas elas se parecem com isso:

export default class MyClass {
    // ...
}

Em seguida, configurei um ponto de entrada para o meu pacote que se parece com isso:

export { default as MyClass } from './my-class.js';
export { default as MyOtherClass } from './my-other-class.js';

Depois, executei o ponto de entrada no webpack e no babel, terminando com um index.js transpilado e minificado

Instalar e importar o pacote funciona bem, mas quando faço o seguinte no código do meu cliente:

import { MyClass } from 'my-package';

Ele não importa apenas "MyClass"; importa todo o arquivo, incluindo todas as dependências de todas as classes (algumas das minhas classes têm enormes dependências).

Imaginei que é assim que o webpack funciona quando você tenta importar partes de um pacote já empacotado? Então, eu configurei minha configuração local do webpack para executar também node_modules/my-packageatravés do babel e tentei:

import { MyClass } from 'my-package/src/index.js';

Mas mesmo isso importa todas as classes exportadas pelo index.js. A única coisa que parece funcionar do jeito que eu quero é se eu fizer:

import MyClass from 'my-package/src/my-class.js';

Mas eu prefiro:

  1. Poder importar o arquivo transpilado e minificado para que eu não precise dizer ao webpack para executar o babel dentro de node_modules e
  2. Ser capaz de importar cada classe individual diretamente do meu ponto de entrada, em vez de precisar digitar o caminho para cada arquivo

Qual é a melhor prática aqui? Como outras pessoas conseguem configurações semelhantes? Notei que o GlideJS possui uma versão ESM de seu pacote, que permite importar apenas o que você precisa sem precisar executar o babel por exemplo.

O pacote em questão: https://github.com/powerbuoy/sleek-ui

webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        'sleek-ui': './src/js/sleek-ui.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
        library: 'sleek-ui', // NOTE: Before adding this and libraryTarget I got errors saying "MyClass() is not a constructor" for some reason...
        libraryTarget: 'umd'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                ]
            }
        ]
    }
};

package.json

  "name": "sleek-ui",
  "version": "1.0.0",
  "description": "Lightweight SASS and JS library for common UI elements",
  "main": "dist/sleek-ui.js",
  "sideEffects": false, // NOTE: Added this from Abhishek's article but it changed nothing for me :/
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/powerbuoy/sleek-ui.git"
  },
  "author": "Andreas Lagerkvist",
  "license": "GPL-2.0-or-later",
  "bugs": {
    "url": "https://github.com/powerbuoy/sleek-ui/issues"
  },
  "homepage": "https://github.com/powerbuoy/sleek-ui#readme",
  "devDependencies": {
    "@babel/core": "^7.8.6",
    "@babel/preset-env": "^7.8.6",
    "babel-loader": "^8.0.6",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "@glidejs/glide": "^3.4.1",
    "normalize.css": "^8.0.1"
  }
}
powerbuoy
fonte
11
Você adicionou o mainatributo (ponto de entrada) no package.json da sua lib? Verifique sua compilação. E como você está empacotando seu pacote lib?
Abhishek
A propriedade principal de um package.json é uma direção para o ponto de entrada do módulo que o package.json está descrevendo. Em um aplicativo Node.js., quando o módulo é chamado por meio de uma instrução require, as exportações do módulo do arquivo nomeado na propriedade principal serão retornadas ao aplicativo Node.js.
Abhishek
Sim, a propriedade principal aponta para o meu index.js, que exporta todas as outras classes. Estou empacotando o arquivo main / index.js usando o webpack e o babel. Está tudo explicado na pergunta.
powerbuoy 5/03
isso pode ajudá-lo - danielberndt.net/blog/2018/…
Abhishek
Você também pode implementar a compilação - github.com/mui-org/material-ui/blob/master/packages/material-ui/… Para ter um tamanho de compilação menor, é melhor fazer isso import { MyClass } from 'my-package/src/MyClass';. Você também pode remover o pacote de compilação src para reduzir o caminho do arquivo.
Abhishek

Respostas:

1

Imaginei que é assim que o webpack funciona quando você tenta importar partes de um pacote já empacotado?

Sim, a maneira como você o configurou é importar todas as classes no index.js, que são transpiladas em um arquivo (se estiver direcionado ao ES5, o que é mais comum *). Isso significa que, quando esse arquivo é importado para outro arquivo, ele vem na sua totalidade, com todas essas classes.

Se você deseja agitar a árvore adequadamente, evite transpilar para um pacote CommonJS (ES5). Minha sugestão é manter os módulos ES6, por si só ou em um local separado do pacote ES5. Este artigo deve ajudá-lo a entender completamente isso e recomendou as instruções. Essencialmente, tudo se resume a definir o ambiente Babel usando o preset-env (altamente recomendado se você ainda não o estiver usando!) Para preservar a sintaxe do ES6 . Aqui está a configuração Babel relevante, se você não deseja transpilar para o ES5:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}

O artigo detalha como configurar 2 pacotes configuráveis, cada um usando uma sintaxe de módulo diferente.

Também vale a pena notar, e também é mencionado no artigo, você pode definir o ponto de entrada do módulo ES no package.json. Isso informa ao Webpack / Babel onde os módulos ES6 podem ser encontrados, o que pode ser tudo o que você precisa para o seu caso de uso. Parece que a sabedoria convencional diz para fazer:

{
  "main": "dist/sleek-ui.js",
  "module": "src/main.js"
}

Mas a documentação do nó tem como:

{
  "type": "module",
  "main": "dist/sleek-ui.js",
  "exports": {
    ".": "dist/sleek-ui.js",
    "./module": "src/main.js"
  }
}

Se eu tivesse tempo, iria brincar com isso e ver o que funciona corretamente, mas isso deve ser suficiente para colocá-lo no caminho certo.


* Pacotes configuráveis ​​direcionados ao ES5 estão no formato CommonJS, que deve incluir todos os arquivos associados, porque o ES5 não possui suporte ao módulo nativo. Isso veio no ES2015 / ES6.

CaitlinWeb
fonte
Tentei adicionar targets.esmodules: truee, embora isso tenha feito alterações no script criado, ele não fez nenhuma alteração no que foi importado no final. A importação de uma única classe my-packageainda importa tudo. Eu também tentei as alterações package.json(junto com as outras alterações) e isso também não mudou nada. Bem, a adição type: modulerealmente quebrou minha compilação com "Deve usar a importação para carregar o Módulo ES: /sleek-ui/webpack.config.js require () of modules ES não é suportado." então eu tive que remover esse pedaço. Vou dar uma olhada no artigo vinculado.
powerbuoy 12/03
Ok, então o artigo realmente me disse para definir modules: false(não dentro targets), mas isso também não funcionou ... Acho que vou importar diretamente do arquivo de origem e continuar executando o babel através do node_modules até que possamos usar esse material nativamente.
powerbuoy 12/03
@powerbuoy A importação do arquivo de origem funciona. Talvez não tenha ficado claro no meu post, e se for esse o caso, posso editá-lo, mas você deseja importar apenas a turma import MyClass from 'my-package/myClass';. Um bom exemplo disso é o lodash-es .
CaitlinWeb 13/03
-1

Este é um caso de uso válido. O objetivo final é fazer isso, import { MyClass } from 'my-package'mas há uma maneira mais limpa de fazer isso.

Crie um arquivo de índice agregador no seu arquivo my-package. Basicamente, my-package/index.jse deve ficar assim:

import MyClass from './my-class.js'
import MyOtherClass from './my-other-class.js'

export { MyClass, MyOtherClass }

Então você pode fazer import { MyClass } from 'my-package'. Mole-mole.

Diverta-se!

idancali
fonte
Isso é exatamente o que eu já estou fazendo, o que achei bem claro na pergunta.
powerbuoy 12/03