"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Eu escrevi um empacotador de módulo. notas, etc.

Eu escrevi um empacotador de módulo. notas, etc.

Publicado em 2024-08-05
Navegar:224

I wrote a module bundler. notes, etc

Eu criei um empacotador JavaScript simples e acabou sendo muito mais fácil do que eu esperava. Vou compartilhar tudo o que aprendi neste post.

Ao escrever aplicativos grandes, é uma boa prática dividir nosso código-fonte JavaScript em arquivos js separados; no entanto, adicionar esses arquivos ao seu documento HTML usando várias tags de script apresenta novos problemas, como

  • poluição do namespace global.

  • condições da corrida.

Os empacotadores de módulos combinam nosso código-fonte de diferentes arquivos em um grande arquivo, ajudando-nos a aproveitar os benefícios das abstrações e, ao mesmo tempo, evitar as desvantagens.

Os empacotadores de módulos geralmente fazem isso em duas etapas.

  1. Encontrar todos os arquivos fonte JavaScript, começando pelo arquivo de entrada. Isso é conhecido como resolução de dependência e o mapa gerado é chamado de gráfico de dependência.
  2. Usando o gráfico de dependência para gerar um pacote: uma grande string de código-fonte JavaScript que pode ser executada em um navegador. Isso pode ser gravado em um arquivo e adicionado ao documento HTML usando uma tag de script.

RESOLUÇÃO DE DEPENDÊNCIA

Como mencionado anteriormente, aqui nós

  • pegue um arquivo de entrada,
  • ler e analisar seu conteúdo,
  • Adicione-o a uma série de módulos
  • encontre todas as suas dependências (outros arquivos que importa),
  • Leia e analise o conteúdo das dependências
  • Adicionar dependências ao array
  • Encontre dependências de dependências e assim por diante até chegarmos ao último módulo

Veja como faríamos isso (código JavaScript adiante)

Crie um arquivo bundler.js em seu editor de texto e adicione o seguinte código:

const bundler = (entry)=>{
          const graph = createDependencyGraph(entry)

          const bundle = createBundle(graph)
          return bundle
}

A função bundler é a entrada principal do nosso bundler. Ele pega o caminho para um arquivo (arquivo de entrada) e retorna uma string (o pacote). Dentro dele, ele gera um gráfico de dependência usando a função createDependencyGraph.

const createDependencyGraph = (path)=>{
          const entryModule = createModule(path)

          /* other code */
}

A função createDependencyGraph segue o caminho para o arquivo de entrada. Ele usa a função createModule para gerar uma representação do módulo deste arquivo.

let ID = 0
const createModule = (filename)=>{
          const content = fs.readFileSync(filename)
          const ast = babylon.parse(content, {sourceType: “module”})

          const {code} = babel.transformFromAst(ast, null, {
              presets: ['env']
            })

           const dependencies = [ ]
           const id = ID  
           traverse(ast, {
                   ImportDeclaration: ({node})=>{
                       dependencies.push(node.source.value)
                   }
            }
            return {
                           id,
                           filename,
                           code,
                           dependencies
                       }
}

A função createAsset pega o caminho para um arquivo e lê seu conteúdo em uma string. Essa string é então analisada em uma árvore de sintaxe abstrata. Uma árvore de sintaxe abstrata é uma representação em árvore do conteúdo de um código-fonte. Pode ser comparado à árvore DOM de um documento HTML. Isso torna mais fácil executar algumas funcionalidades no código, como pesquisa, etc.
Criamos um ast a partir do módulo usando o analisador babylon.

Em seguida, com a ajuda do transpilador babel core, convertemos o conteúdo do código para uma sintaxe pré-es2015 para compatibilidade entre navegadores.
Depois o ast é percorrido usando uma função especial do babel para encontrar cada declaração de importação do nosso arquivo fonte (dependências).

Em seguida, colocamos essas dependências (que são strings de texto de caminhos de arquivos relativos) em uma matriz de dependências.

Também criamos um id para identificar exclusivamente este módulo e
Finalmente retornamos um objeto que representa este módulo. Este módulo contém um id, o conteúdo do nosso arquivo em formato de string, uma matriz de dependências e o caminho absoluto do arquivo.

const createDependencyGraph = (path)=>{
          const entryModule = createModule(path)

          const graph = [ entryModule ]
          for ( const module of graph) {
                  module.mapping = { }
module.dependencies.forEach((dep)=>{
         let absolutePath = path.join(dirname, dep);
         let child = graph.find(mod=> mod.filename == dep)
         if(!child){
               child = createModule(dep)
               graph.push(child)
         }
         module.mapping[dep] = child.id
})
          }
          return graph
}

De volta à nossa função createDependencyGraph, agora podemos iniciar o processo de geração do nosso gráfico. Nosso gráfico é um array de objetos com cada objeto representando cada arquivo fonte usado em nossa aplicação.
Inicializamos nosso gráfico com o módulo de entrada e depois fazemos um loop nele. Embora contenha apenas um item, adicionamos itens ao final do array acessando o array de dependências do módulo de entrada (e outros módulos que iremos adicionar).

A matriz de dependências contém caminhos de arquivos relativos de todas as dependências de um módulo. A matriz é repetida e para cada caminho de arquivo relativo, o caminho absoluto é primeiro resolvido e usado para criar um novo módulo. Este módulo filho é empurrado para o final do gráfico e o processo começa novamente até que todas as dependências sejam convertidas em módulos.
Além disso, cada módulo fornece um objeto de mapeamento que simplesmente mapeia cada caminho relativo de dependência para o id do módulo filho.
Uma verificação se um módulo já existe é realizada em cada dependência para evitar duplicação de módulos e dependências circulares infinitas.
Finalmente retornamos nosso gráfico que agora contém todos os módulos de nossa aplicação.

PACOTE

Com o gráfico de dependências pronto, a geração de um pacote envolverá duas etapas

  1. Envolvendo cada módulo em uma função. Isso cria a ideia de cada módulo ter seu próprio escopo
  2. Envolvendo o módulo em um tempo de execução.

Envolvendo cada módulo

Temos que converter nossos objetos de módulo em strings para que possamos gravá-los no arquivo bundle.js. Fazemos isso inicializando moduleString como uma string vazia. Em seguida, percorremos nosso gráfico anexando cada módulo à string do módulo como pares de valores-chave, com o id de um módulo sendo a chave e um array contendo dois itens: primeiro, o conteúdo do módulo envolvido na função (para dar-lhe escopo conforme declarado anteriormente ) e em segundo lugar um objeto contendo o mapeamento de suas dependências.

const wrapModules = (graph)=>{
         let modules = ‘’
           graph.forEach(mod => {
    modules  = `${http://mod.id}: [
      function (require, module, exports) {
        ${mod.code}
      },
      ${JSON.stringify(mod.mapping)},
    ],`;
  });
return modules
}

Observe também que a função que envolve cada módulo recebe objetos require, export e module como argumentos. Isso ocorre porque eles não existem no navegador, mas como aparecem em nosso código, iremos criá-los e passá-los para esses módulos.

Criando o tempo de execução

Este é o código que será executado imediatamente após o carregamento do pacote, ele fornecerá aos nossos módulos os objetos require, module e module.exports.

const bundle = (graph)=>{
        let modules = wrapModules(graph)
        const result = `
    (function(modules) {
      function require(id) {
        const [fn, mapping] = modules[id];

        function localRequire(name) {
          return require(mapping[name]);
        }

        const module = { exports : {} };

        fn(localRequire, module, module.exports);

        return module.exports;
      }

      require(0);
    })({${modules}})`;
  return result;
}

Usamos uma expressão de função invocada imediatamente que leva nosso objeto de módulo como argumento. Dentro dela definimos nossa função require que obtém um módulo de nosso objeto de módulo usando seu id.
Ele constrói uma função localRequire específica para um módulo específico para mapear a string do caminho do arquivo para o id. E um objeto de módulo com uma propriedade de exportação vazia
Ele executa nosso código de módulo, passando o objeto localrequire, module e exports como argumentos e, em seguida, retorna module.exports exatamente como um módulo node js faria. Finalmente chamamos require em nosso módulo de entrada (índice 0).

Para testar nosso bundler, no diretório de trabalho do nosso arquivo bundler.js crie um arquivo index.js e dois diretórios: um src e um diretório público.

No diretório público crie um arquivo index.html e adicione o seguinte código na tag body:


Agrupador de módulos
Declaração de lançamento Este artigo está reproduzido em: https://dev.to/frontendokeke/i-wrote-a-module-bundler-notes-etc-4ofa?1 Se houver alguma violação, entre em contato com [email protected] para excluí-la
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3