O sistema de módulos
adopt e relay — o que é público, onde mora cada módulo, e como isso se compara a CommonJS e a ECMAScript.
Toda linguagem grande acaba precisando responder quatro perguntas sobre módulos: o que um arquivo oferece, onde mora o que ele pede, quantas vezes ele carrega e o que acontece quando dois se pedem em círculo. Esta seção responde as quatro, com o comportamento medido — não com a intenção.
As duas palavras:
adopt Arcane.Math as Math // o módulo inteiro, com apelido
adopt Arcane.Math.{sqrt, floor} // só o que interessa
adopt {sqrt as raiz} from Arcane.Math
adopt ./util as U // um arquivo vizinho
relay somar, Ponto // o que ESTE arquivo ofereceAo lado de CommonJS e ECMAScript#
A tabela existe para quem chega de uma das duas. Onde a DataForge difere, ela difere de propósito, e a coluna da direita diz por quê.
| Pergunta | CommonJS | ECMAScript (ESM) | DataForge |
|---|---|---|---|
| importar | require('x') | import x from 'x' | adopt x as X |
| exportar | module.exports = | export | relay |
| export padrão | sim (module.exports) | sim (export default) | não existe |
| quando resolve | em execução | antes de executar | em execução, e o check confere antes |
| carrega quantas vezes | uma | uma | uma |
| o que é público sem declarar | nada | nada | tudo |
| ciclo | devolve o parcial | vive com TDZ | erro |
| import dinâmico | require() em qualquer lugar | import() | não existe |
ordem de export | irrelevante | içado | depois da declaração |
Tudo é público até você dizer o contrário#
Um arquivo sem relay oferece tudo o que declara no topo. É o oposto do padrão de CommonJS e ESM, e a razão é o arquivo pequeno: obrigar um relay num módulo de três ações é cerimônia sem ganho.
interno := 42
action publica():
yield "oi"adopt ./aberto as A
out A.publica(), A.interno // oi 42 — os dois visíveisO primeiro `relay` fecha a porta. A partir dele, o arquivo declara o que exporta, e o resto vira interno:
interno := 42
action publica():
yield "oi"
relay publica // agora 'interno' não atravessa maisEssa é também a fronteira que o analisador usa: um módulo com relay diz o que é contrato, e A.interno passa a ser acusado antes de rodar.
`relay` vem depois, e isso não é detalhe#
Em JavaScript, export function f(){} funciona em qualquer posição, porque a declaração é içada. Aqui o relay lê os nomes que já existem:
relay usar // erro: 'usar' ainda não existe
action usar():
yield 1erro: 'usar' is not defined
dica: define it before the 'relay', or remove it from the listA forma certa é o relay no fim do arquivo, que é também onde ele se lê melhor — a última linha responde "o que este arquivo oferece?" sem obrigar a percorrer tudo.
Um módulo carrega uma vez, e o estado é compartilhado#
Como em CommonJS e em ESM. Dois arquivos que adotam o mesmo módulo recebem o mesmo módulo — e o estado dele é único no programa:
out "carregando contador.df"
total := 0
action somar():
total += 1
yield total
relay somaradopt ./contador as C
action usar():
yield C.somar()
relay usaradopt ./contador as C
adopt ./a as A
out C.somar() // 1
out A.usar() // 2 — o MESMO contador
out C.somar() // 3carregando contador.df
1
2
3A mensagem de carga aparece uma vez. É o que torna um módulo um bom lugar para configuração e um lugar perigoso para estado mutável: total acima é global ao programa, e duas rotas de um servidor escrevendo nele perdem atualizações — ver concorrência.
Por onde seguir#
Resolução
o algoritmo exato: onde a linguagem procura, e em que ordem
Carga e ciclos
quando o arquivo executa, e o que acontece num círculo
A superfície
como o analisador atravessa a fronteira e confere a chamada
Templates
a camada de visão, e por que ela não é a linguagem
Escrever uma biblioteca
do primeiro arquivo ao pacote publicado