Importando uma biblioteca Javascript frontend sem um sistema de compilação
Gosto de escrever Javascript sem um sistema de compilação e pela milionésima vez ontem me deparei com um problema em que precisava descobrir como importar uma biblioteca Javascript em meu código sem usar um sistema de compilação, e demorou SEMPRE para descobrir como importá-lo porque as instruções de configuração da biblioteca assumem que você está usando um sistema de compilação.
Felizmente, neste ponto, aprendi principalmente como navegar nessa situação e usar a biblioteca com sucesso ou decidir que é muito difícil e mudar para uma biblioteca diferente, então aqui está o guia que eu gostaria de ter para importar bibliotecas Javascript anos atrás.
Vou falar apenas sobre o uso de bibliotecas Javacript no frontend e apenas sobre como usá-las em uma configuração sem sistema de compilação.
Neste post vou falar sobre:
- os três principais tipos de arquivos Javascript que uma biblioteca pode fornecer (Módulos ES, o tipo de variável global “clássico” e CommonJS)
- como descobrir quais tipos de arquivos uma biblioteca Javascript inclui em sua construção
- maneiras de importar cada tipo de arquivo em seu código
os três tipos de arquivos Javascript
Existem 3 tipos básicos de arquivos Javascript que uma biblioteca pode fornecer:
- o tipo “clássico” de arquivo que define uma variável global. Este é o tipo de arquivo que você pode simplesmente
and it’ll Just Work. Great if you can get it but not always available - an ES module (which may or may not depend on other files, we’ll get to that)
- a “CommonJS” module. This is for Node, you can’t use it in a browser at all without using a build system.
where to find the files: the NPM build
Every Javascript library has a build which it uploads to NPM. You might be thinking (like I did originally) – Julia! The whole POINT is that we’re not using Node to build our library! Why are we talking about NPM? But if you’re using a link from a CDN like https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js, you’re still using the NPM build! All the files on the CDNs originally come from NPM. Because of this, I sometimes like tonpm install the library even if I’m not
planning to use Node to build my library at all – I’ll just create a new temp
folder, npm install there, and then delete it when I’m done. I like being able to poke
around in the files in the NPM build on my filesystem, because then I can be
100% sure that I’m seeing everything that the library is making available in
its build and that the CDN isn’t hiding something from me.
So let’s npm install a few libraries and try to figure out what types of
Javascript files they provide in their builds!
example library 1: chart.js
First let’s look inside Chart.js, a plotting library.$ cd /tmp/whatever
$ npm install chart.js
$ cd node_modules/chart.js/dist
$ ls *.*js
chart.cjs chart.js chart.umd.js helpers.cjs helpers.js
This library seems to have 3 basic options:
option 1: chart.cjs. The .cjs suffix tells me that this is a CommonJS
file, for using in Node. This means it’s impossible to use it directly in the
browser without some kind of build step.
option 2:chart.js. The .js suffix by itself doesn’t tell us what kind of
file it is, but if I open it up, I see import '@kurkle/color'; which is an
immediate sign that this is an ES module – the import ... syntax is ES
module syntax.
option 3: chart.umd.js. “UMD” stands for “Universal Module Definition”,
which I think means that you can use this file either with a basic , CommonJS,
or some third thing called AMD that I don’t understand.
how to use a UMD file
When I was using Chart.js I picked Option 3. I just needed to add this to my code:
e então eu poderia usar a biblioteca com o global Chart variável de ambiente. Não poderia ser mais fácil. acabei de copiar chart.umd.js em meu repositório Git para que eu não precisasse me preocupar em usar o NPM ou os CDNs caindo ou algo assim.
os arquivos de compilação nem sempre estão no dist diretório
Muitas bibliotecas colocarão sua construção no dist diretório, mas nem sempre! A localização dos arquivos de construção é especificada no arquivo da biblioteca package.json.
Por exemplo, aqui está um trecho do Chart.js package.json.
"jsdelivr": "./dist/chart.umd.js",
"unpkg": "./dist/chart.umd.js",
"main": "./dist/chart.cjs",
"module": "./dist/chart.js",
Acho que isso quer dizer que se você quiser usar um Módulo ES (module) você deve usar dist/chart.jsmas os CDNs jsDelivr e unpkg devem usar
./dist/chart.umd.js. Eu acho main é para Node.
chart.jsde package.json também diz "type": "module"que de acordo com esta documentação diz ao Node para tratar os arquivos como módulos ES por padrão. Acho que não nos diz especificamente quais arquivos são módulos ES e quais não são, mas nos diz que algo existe um módulo ES.
biblioteca de exemplo 2: @atcute/oauth-browser-client
@atcute/oauth-browser-client
é uma biblioteca para fazer login no Bluesky com OAuth no navegador.
Vamos ver que tipos de arquivos Javascript ele fornece em sua construção!
$ npm install @atcute/oauth-browser-client
$ cd node_modules/@atcute/oauth-browser-client/dist
$ ls *js
constants.js dpop.js environment.js errors.js index.js resolvers.js
Parece que o único arquivo raiz plausível aqui é index.jsque se parece com isto:
export { configureOAuth } from './environment.js';
export * from './errors.js';
export * from './resolvers.js';
Esse export sintaxe significa que é um Módulo ES. Isso significa que podemos usá-lo no navegador sem uma etapa de construção! Vamos ver como fazer isso.
como usar um módulo ES com importmaps
Usar um módulo ES não é tão fácil quanto adicionar um . Instead, if
the ES module has dependencies (like @atcute/oauth-browser-client does) the
steps are:
- Set up an import map in your HTML
- Put import statements like
import { configureOAuth } from '@atcute/oauth-browser-client';in your JS code - Include your JS code in your HTML like this:
import { BrowserOAuthClient } from "./oauth-client-browser.js" is that internally the module has more import statements like import {something} from @atcute/client, and we need to tell the browser where to get the code for @atcute/client and all of its other dependencies.
Here’s what the importmap I used looks like for @atcute/oauth-browser-client:
Fazer com que esses mapas de importação funcionem é bastante complicado. Sinto que deve haver uma ferramenta para gerá-los automaticamente, mas ainda não encontrei uma. Definitivamente, é possível escrever um script que gere automaticamente os importmaps usando o metarquivo do esbuild, mas não fiz isso e talvez haja uma maneira melhor.
Decidi configurar importmaps ontem para fazer github.com/jvns/bsky-oauth-example funcionar, então há alguns exemplos de código nesse repositório.
Além disso, alguém me indicou o download-esm de Simon Willison, que fará o download de um módulo ES e reescreverá as importações para apontar diretamente para os arquivos JS, para que você não precise de mapas de importação. Ainda não experimentei mas parece uma ótima ideia.
problemas com importmaps: muitos arquivos
Porém, tive alguns problemas ao usar importmaps no navegador – era necessário baixar dezenas de arquivos Javascript para carregar meu site, e meu servidor web em desenvolvimento não conseguia acompanhar por algum motivo. Continuei vendo os arquivos falharem ao carregar aleatoriamente e então tive que recarregar a página e esperar que desta vez eles tivessem sucesso.
Não foi mais um problema quando implantei meu site em produção, então acho que foi um problema com meu ambiente de desenvolvimento local.
Além disso, uma coisa um pouco irritante sobre os módulos ES em geral é que você precisa estar executando um servidor web para usá-los, tenho certeza de que isso é por um bom motivo, mas é mais fácil quando você pode simplesmente abrir seu index.html arquivo sem iniciar um servidor web.
Por causa da coisa de “muitos arquivos”, acho que usar módulos ES com importmaps dessa forma não é tão atraente para mim, mas é bom saber que é possível.
como usar um módulo ES sem importmaps
Se o módulo ES não tiver dependências então é ainda mais fácil – você não precisa dos importmaps! Você pode apenas:
- colocar
in your HTML. Thetype="module"is important. - put
import {whatever} from "https://example.com/whatever.js"inYOURCODE.js
alternative: use esbuild
If you don’t want to use importmaps, you can also use a build system like esbuild. I talked about how to do that in Some notes on using esbuild, but this blog post is about ways to avoid build systems completely so I’m not going to talk about that option here. I do still like esbuild though and I think it’s a good option in this case.what’s the browser support for importmaps?
CanIUse says that importmaps are in “Baseline 2023: newly available across major browsers” so my sense is that in 2024 that’s still maybe a little bit too new? I think I would use importmaps for some fun experimental code that I only wanted like myself and 12 people to use, but if I wanted my code to be more widely usable I’d useesbuild instead.
example library 3: @atproto/oauth-client-browser
Let’s look at one final example library! This is a different Bluesky auth
library than @atcute/oauth-browser-client.
$ npm install @atproto/oauth-client-browser
$ cd node_modules/@atproto/oauth-client-browser/dist
$ ls *js
browser-oauth-client.js browser-oauth-database.js browser-runtime-implementation.js errors.js index.js indexed-db-store.js util.js
Again, it seems like only real candidate file here is index.js. But this is a
different situation from the previous example library! Let’s take a look at
index.js:
There’s a bunch of stuff like this in index.js:
__exportStar(require("@atproto/oauth-client"), exports);
__exportStar(require("./browser-oauth-client.js"), exports);
__exportStar(require("./errors.js"), exports);
var util_js_1 = require("./util.js");
This require() syntax is CommonJS syntax, which means that we can’t use this
file in the browser at all, we need to use some kind of build step, and
ESBuild won’t work either.
Also in this library’s package.json it says "type": "commonjs" which is
another way to tell it’s CommonJS.
how to use a CommonJS module with esm.sh
Originally I thought it was impossible to use CommonJS modules without learning a build system, but then someone Bluesky told me about esm.sh! It’s a CDN that will translate anything into an ES Module. skypack.dev does something similar, I’m not sure what the difference is but one person mentioned that if one doesn’t work sometimes they’ll try the other one. For@atproto/oauth-client-browser using it seems pretty simple, I just need to put this in my HTML:
e depois coloque isso script.js.
import { BrowserOAuthClient } from "https://esm.sh/@atproto/oauth-client-browser@0.3.0"
Parece que simplesmente funciona, o que é legal! É claro que isso ainda é uma espécie de sistema de compilação - é só que esm.sh está executando a compilação em vez de mim. Minhas principais preocupações com essa abordagem são:
- Eu realmente não confio que CDNs continuem funcionando para sempre – geralmente gosto de copiar dependências em meu repositório para que elas não desapareçam por algum motivo no futuro.
- Ouvi falar de alguns problemas com CDNs com comprometimento de segurança, o que me assusta.
- Eu realmente não entendo o que esm.sh está fazendo.
esbuild também pode converter módulos CommonJS em módulos ES
Também aprendi que você também pode usar esbuild converter um módulo CommonJS em um módulo ES, embora existam algumas limitações – o import { BrowserOAuthClient } from sintaxe não funciona. Aqui está uma questão do github sobre isso.
Eu acho que o esbuild abordagem é provavelmente mais atraente para mim do que a
esm.sh abordagem porque é uma ferramenta que já tenho no meu computador, então confio mais nela. Ainda não experimentei muito isso.
resumo dos três tipos de arquivos
Aqui está um resumo dos três tipos de arquivos JS que você pode encontrar, opções de como usá-los e como identificá-los.
Inutilmente um .js ou .min.js a extensão do arquivo pode ser qualquer uma dessas três opções, portanto, se o arquivo for something.js você precisa fazer mais trabalho de detetive para descobrir com o que está lidando.
- Arquivos JS “clássicos”
- Como usar::
- Ways to identify it:
- The website has a big friendly banner in its setup instructions saying “Use this with a CDN!” or something
- A
.umd.jsextension - Just try to put it in a
- Como usar::
- Módulos ES
- Maneiras de usá-lo:
- Se não houver dependências, basta
import {whatever} from "./my-module.js"diretamente no seu código - Se houver dependências, crie um importmap e
import {whatever} from "my-module" - Use esbuild ou qualquer empacotador de módulo ES
- Se não houver dependências, basta
- Maneiras de identificá-lo:
- Procure um
importouexportdeclaração. (nãomodule.exports = ...isso é CommonJS) - Um
.mjsextensão - talvez
"type": "module"empackage.json(embora não esteja claro para mim a qual arquivo exatamente isso se refere)
- Procure um
- Maneiras de usá-lo:
- Módulos CommonJS
- Maneiras de usá-lo:
- Use https://esm.sh para convertê-lo em um módulo ES, como
https://esm.sh/@atproto/oauth-client-browser@0.3.0 - Use uma compilação de alguma forma (??)
- Use https://esm.sh para convertê-lo em um módulo ES, como
- Maneiras de identificá-lo:
- Procurar
require()oumodule.exports = ...no código - UM
.cjsextensão - talvez
"type": "commonjs"empackage.json(embora não esteja claro para mim a qual arquivo exatamente isso se refere)
- Procurar
- Maneiras de usá-lo:
é muito bom ter módulos ES padronizados
A principal diferença entre os módulos CommonJS e os módulos ES, na minha perspectiva, é que os módulos ES são, na verdade, um padrão. Isso me faz sentir muito mais confiante ao usá-los, porque os navegadores se comprometem para sempre com a compatibilidade com versões anteriores dos padrões da Web – se eu escrever algum código usando módulos ES hoje, posso ter certeza de que ainda funcionará da mesma maneira daqui a 15 anos.
Também me faz sentir melhor ao usar ferramentas como esbuild porque mesmo que o projeto esbuild morra, por estar implementando um padrão, parece provável que haverá outra ferramenta semelhante no futuro pela qual eu possa substituí-lo.
Muitas vezes quando falo sobre essas coisas recebo respostas como “Eu odeio javascript!!! Mas minha experiência é que existem muitas ferramentas excelentes para Javascript (acabei de aprender sobre https://esm.sh ontem, o que parece ótimo! Adoro esbuild!), e que se eu dedicar um tempo para aprender como as coisas funcionam, posso aproveitar algumas dessas ferramentas e tornar minha vida muito mais fácil.
Portanto, o objetivo deste post definitivamente não é reclamar do Javascript, é entender o cenário para que eu possa usar as ferramentas de uma maneira que me pareça boa.
perguntas que ainda tenho
Aqui estão algumas perguntas que ainda tenho. Adicionarei as respostas ao post se souber a resposta.
- Existe uma ferramenta que gera automaticamente mapas de importação para um módulo ES que configurei localmente? (aparentemente sim: jspm)
- Como posso converter um módulo CommonJS em um módulo ES no meu computador, da mesma forma que https://esm.sh faz? (aparentemente, o esbuild pode fazer isso, embora as exportações nomeadas não funcionem)
- Quando as pessoas normalmente constroem módulos CommonJS em código JS normal, qual código está fazendo isso? Obviamente, existem ferramentas como webpack, rollup, esbuild, etc, mas todas essas ferramentas implementam seus próprios analisadores JS/análise estática? Quantos analisadores JS existem por aí?
- Existe alguma maneira de agrupar um módulo ES em um único arquivo (como
atcute-client.js), mas para que no navegador eu ainda possa importar vários caminhos diferentes desse arquivo (como ambos@atcute/client/lexiconse
@atcute/client)?
todas as ferramentas
Aqui está uma lista de todas as ferramentas sobre as quais falamos nesta postagem:
Escrever esta postagem me fez pensar que, embora eu geralmente não queira ter uma compilação que execute toda vez que atualizo o projeto, talvez eu esteja disposto a realizar uma etapa de compilação (usando download-esm ou algo assim) que eu corro apenas uma vez
ao configurar o projeto e nunca mais executá-lo, exceto talvez se eu estiver atualizando minhas versões de dependência.
isso é tudo!
Obrigado ao Marco Rogers que me ensinou muitas coisas neste post. Provavelmente cometi alguns erros neste post e adoraria saber quais são – me avise no Bluesky ou Mastodon!












