Moment: Builds de versão React-Native, falha profunda ao alterar localidades

Criado em 10 out. 2019  ·  4Comentários  ·  Fonte: moment/moment

Ao usar react-native 0.59.10 e definindo a localidade para momentJS, pegamos uma falha super brutal apenas para nossas compilações de lançamento. Esta falha não foi reproduzível para nós com o depurador anexado. Essa falha ocorreu para nós no iOS e no Android. instruções try-catch envolvendo todo o uso do momento não detectaram a falha!

Reproduzir

  1. Coletamos as localidades/idiomas que queremos experimentar por meio da lógica específica do aplicativo. Ex: ["fr-CA", "en-US", "fr", "en"]
  2. Fazemos um loop por eles, um de cada vez, em vez de usar o setter Array, para que possamos chamar alguma outra instrumentação e potencialmente capturar qualquer JS Exception que seja lançada e tentar o próximo candidato.
  3. Apesar de chamar moment.locale(localeCandidate) dentro de um bloco try-catch, o aplicativo ainda trava nessa linha⁇

Este foi um crash-on-launch, mas apenas para compilações de lançamento !! Isso tornou super complicado extrair mensagens de erro / logs úteis.

Vimos as seguintes mensagens de erro por meio de nossa integração Bugsnag e registro do console do sistema

  • iOS: Exception in HostFunction: Error loading module0from RAM Bundle: unspecified iostream_category error
  • Android: Exception in HostFunction: Module not found: 0
  • Um dia depois, no iOS e no Android, também vimos Requiring unknown module "./locale/en-us". - mas, estranhamente, esse erro não estava sendo processado em tempo hábil. Possivelmente um problema react-native / bugsnag.
  1. O rastreamento eventual encontrou um único ponto que causou a falha:
    https://github.com/moment/moment/blob/96d0d6791ab495859d09a868803d31a55c917de1/moment.js#L1852 -L1853
    Que eu acredito que vem daqui: https://github.com/moment/moment/blob/6a06e7a0db2c83fb92aa72bbf6bde955d4c75a16/src/lib/locale/locales.js#L55 -L56

Solução alternativa: comentar essas duas linhas interrompeu a falha!

Comportamentos esperados

  • O Moment não deve se comportar de maneira travada, nunca, mas especialmente não apenas em versões de lançamento!
  • As exceções do Moment devem ser capturáveis ​​(nossas instruções catch teriam evitado uma falha). -- (pode ser um problema nativo de reação ao mexer com require() na versão)

Smartphone (por favor, preencha as seguintes informações):

  • Dispositivo: iPhone X, iPhone 11 Pro, Samsung (? -- não capturou exatamente o que), Google Pixel 3, (react-native 0.59.10)
  • SO: iOS e Android
  • iOS JavaScript Core (para as versões iOS mencionadas abaixo) e jsc-android(+intl) 245459.0.0
  • Versões: iOS 12.4 e iOS 13.1, iOS 13.1.2 e iOS 13.2 (beta?), Android 28, outros.

Ambiente específico do momento

  • Horário do Pacífico e possivelmente horário de Londres
  • Bug de código exibido de forma consistente nas últimas 2 semanas (2019-10-09) em todos os momentos do dia.
  • Outras bibliotecas em uso: moment-timezone e moment-with-locales, TypeScript, react-native 0.59.10, Apollo-Client e outros

Execute o seguinte código em seu ambiente e inclua a saída:

        console.log([
            new Date().toString(),
            new Date().toLocaleString(),
            new Date().getTimezoneOffset(),
            navigator && navigator.userAgent, // react-native might not have a navigator
            moment.version,
        ]);

Saída:

[
  "Wed Oct 09 2019 18:52:16 GMT-0700 (PDT)",
  "09/10/2019 à 18:52:16", // This particular device is configured as fr-FR
  420,
  null,
  "2.24.0"
]

Contexto adicional

Ingressos Relacionados

  • #5214 - Diferente porque há menos contexto aqui, e usando uma API diferente e recebendo uma mensagem de erro diferente. Semelhante porque isso só acontece em compilações de lançamento, o que sugere react-native também, mas eles só o reproduziram em ios-simulator/ios.
  • #3872
  • #2979

Comentários muito úteis

As linhas referenciadas estão "automaticamente" tentando exigir módulos em tempo de execução, mas os documentos de carregamento de localidades indicam que, se você estiver usando um Gerenciador de Pacotes como JSPM, poderá carregar localidades por import "moment/locale/fr . Como precisamos que o gerenciador de pacotes react-native "saiba" que o arquivo deve ser importado, tentamos esse estilo de importação para que o Packager possa "ver" todos os arquivos que precisam ser empacotados.

Por fim, nossas linhas de importação ficaram assim:

import moment from "moment";
import "moment/min/locales"; // Import all moment-locales -- it's just 400kb
import "moment-timezone";

A implementação exata de require() é injetada pelo tempo de execução com o qual você está trabalhando, e isso é definitivamente algo que se comporta de maneira significativamente diferente entre as compilações Debug e Release.

No react-native, também existem vários tipos diferentes de JavaScript Bundling de modo de lançamento, incluindo all-in-one-file, all-in-separate-files e RAM Bundles. Cada um deles também altera como o require funciona. Debug require() conecta-se ao Metro Bundler rodando em um servidor http local. Isso provavelmente é muito semelhante ao webpack/jspm/outros servidores de depuração, e é provavelmente por isso que o alias require não causa problemas nesse ambiente.

Todos 4 comentários

As linhas referenciadas estão "automaticamente" tentando exigir módulos em tempo de execução, mas os documentos de carregamento de localidades indicam que, se você estiver usando um Gerenciador de Pacotes como JSPM, poderá carregar localidades por import "moment/locale/fr . Como precisamos que o gerenciador de pacotes react-native "saiba" que o arquivo deve ser importado, tentamos esse estilo de importação para que o Packager possa "ver" todos os arquivos que precisam ser empacotados.

Por fim, nossas linhas de importação ficaram assim:

import moment from "moment";
import "moment/min/locales"; // Import all moment-locales -- it's just 400kb
import "moment-timezone";

A implementação exata de require() é injetada pelo tempo de execução com o qual você está trabalhando, e isso é definitivamente algo que se comporta de maneira significativamente diferente entre as compilações Debug e Release.

No react-native, também existem vários tipos diferentes de JavaScript Bundling de modo de lançamento, incluindo all-in-one-file, all-in-separate-files e RAM Bundles. Cada um deles também altera como o require funciona. Debug require() conecta-se ao Metro Bundler rodando em um servidor http local. Isso provavelmente é muito semelhante ao webpack/jspm/outros servidores de depuração, e é provavelmente por isso que o alias require não causa problemas nesse ambiente.

Minhas propostas de correção:

A. Excluir o aliasedRequire inteiramente se não é assim que você deveria fazer as coisas + ajustar as instruções de instalação sobre isso?
B. Detectar react-native vs browser ( navigator não está disponível em react-native, mas existem outras técnicas aqui) e comportar-se de forma diferente dependendo da situação em que nos encontramos? por exemplo. se react-native && DEV então imprima um console.error se a localidade for teoricamente suportada, mas ainda não foi required (+ update docs).
C. Mova o aliasedRequire de uma variável local nessa função para uma "semi-global". moment.aliasedRequire , dessa forma poderíamos injetar uma função no-op/do-nothing para que aliasedRequire não pudesse mais fazer com que o react-native travasse com força.

Eu ficaria feliz em implementar qualquer uma dessas opções se um mantenedor puder me indicar qual delas eles gostariam que eu implementasse, e para Propostas B/C me ajudar a refinar qual implementação exata eles estariam inclinados a aceitar!

@marwahaha -- não tenho certeza de qual é o processo para o Moment. Você teria uma opinião sobre minhas propostas de correção? Eu ficaria feliz em implementar um PR assim que receber alguns conselhos sobre qual rota pode ser aceitável para os contribuidores/mantenedores?

Esta página foi útil?
0 / 5 - 0 avaliações