Para que possamos entender como os módulos funcionam, considere como exemplo uma calculadora simples, onde criamos dois arquivos. O primeiro, util.mjs armazena e exporta algumas funções que podem ser re-utilizadas em diversos locais do projeto e tem o seguinte conteúdo:

/**
     * resultado aproximado de uma circunferência de diâmetro igual a 20.000
     *
     * @type 
     */
    export const PI = Math.PI // 3.141592653589793
    
    /**
     * recebe por parâmetro uma quantidade indefinida de números e efetua a soma dos
     * mesmos.
     *
     * @param {number[]} params
     * @returns  valor soma dos parâmetros
     */
    export function soma (...params) {
     return params.reduce((a, b) => a + b, 0)
    }

Perceba no código acima o uso da palavra export precedendo a constante PI e a função soma. Isso significa que ambos os itens podem ser acessados de fora de seus escopos (arquivo), desde que tenham sido importados.

Para fazer a importação utilizamos o segundo arquivo da nossa calculadora,o index.mjs, que é o ponto de partida da nossa aplicação. Vamos importar do arquivo util.mjs a função soma assim como vemos no código abaixo:

import { soma } from './util.mjs'
    
    console.log(soma(10, 20, 30))
    // 60

Na linha 1 utilizamos a palavra reservada import, e em chaves o nome da função que será importada, no caso, soma. Por fim, é utilizada a palavra from onde informamos o nome do pacote (no caso do Node.js) ou do arquivo.

É importante lembrar que todas as funções, classes, constantes, etc. são importados como constantes.

Arquivos mjs são arquivos JavaScript com suporte a módulos, ou seja, facilitam a distinção entre arquivos tradicionais e com suporte a módulos.

Sintaxe

No arquivo no qual declaramos o recurso que desejamos compartilhar usamos a instrução export.

export [o-que-queremos-compartilhar]

Agora, no arquivo em que desejamos utilizar o recurso compartilhado usamos a instrução import:

import [o-que-queremos-importar] from [nome-do-modulo]

Na prática

Exemplo 1

Podemos definir um item do módulo como default, ou seja, quando utilizarmos o import sem utilizar chaves estaremos recuperando este item, por exemplo:

export default const soma = (...params) => params.reduce((a, b) => a + b, 0)

Perceba que neste exemplo utilizamos o prefixo default após a palavra import, significando que este item é o padrão do pacote. É importante lembrar que apenas um único item pode ser exportado como default.

Abaixo vemos como acessar um item definido como default:

import sum from 'pacote'

Perceba que a palavra sum não está entre chaves. Além disso, é possível definir qualquer nome para o item porque como default ele não é exportado com um nome.

Exemplo 2

No exemplo a seguir importamos um único item do escopo de outro arquivo. Perceba que dentro das chaves é definido apenas o nome da função que será utilizada:

import { soma } from './util.mjs'

Exemplo 3

Podemos também importar uma quantidade indefinida de itens com o import. Para isso basta utilizar chaves e o nome dos itens separados por virgula, como vemos abaixo:

import { soma, PI } from './util.mjs'

Exemplo 4

Podemos importar junto com o default os itens nomeados que exportamos. Para isso basta fazer como no exemplo abaixo:

import sum, { soma, PI } from './util.mjs'

Exemplo 5

É possível definir um apelido para um item importado, o que podemos ver melhor no exemplo abaixo:

import { soma as somaDeTodosOsValores } from './util.mjs'

É importante lembrar que esta sintaxe só funciona nos itens entre as chaves, como no exemplo, soma e PI.

Exemplo 6

Podemos também importar de um arquivo JavaScript sem trazer nenhum item de seu escopo. Essa prática é comum quando precisamos que um script seja executado sem poluir o escopo atual, funcionando como se estivesse sendo executado em segundo plano:

import './outroScript.mjs'

Exemplo 7

É comum a necessidade de importar todos os itens em um pacote. Para isso os módulos contam com a seguinte sintaxe:

import * as utils from './util.mjs'
    
    console.log(utils)
    // { soma, PI }

É importante lembrar que neste tipo de importação o default não é retornado junto com o resto do pacote.

Como testar os módulos no navegador?

Para utilizar na maioria dos navegadores web, basta criar um arquivo HTML e um outro com formato .mjs. Isso significa que é um arquivo JavaScript e suporta os módulos. Em seguida, no JavaScript crie uma tag script, lembrando de informar o nome do seu arquivo mjs no src e o atributo type com o valor module, como podemos ver abaixo:

A partir deste momento, enquanto estiver em um navegador que dê suporte ao recurso será possível utilizar a sintaxe de módulos.

Compatibilidade

Engine Versão Minima
Node.JS ( V8) 6.4.0
Safari ( WebKit) 11.1
Chrome ( V8) 68
Microsoft Edge ( ChakraCore) 17
Firefox ( Gecko) 61

Nota: O Opera utiliza atualmente grande parte do código do Chrome como base para o seu próprio desenvolvimento e por isso ele não é mencionado nesta listagem.