Razzle: RFC: process.env.RAZZLE_RUNTIME_XXXX

Criado em 8 mar. 2018  ·  27Comentários  ·  Fonte: jaredpalmer/razzle

Comportamento Atual

As pessoas lutam para saber como o Razzle lida com variáveis .env (ou seja, definindo-as no momento _build_ pelo webpack) da mesma forma que o create-react-app.

Comportamento esperado

O Razzle deve ter uma maneira de respeitar as variáveis ​​de tempo de execução para que os usuários possam implantar mais facilmente seus aplicativos no Now / Heroku / Azure etc.

Solução sugerida

Disponibilize PORT , HOST e quaisquer outras variáveis ​​env prefixadas com RAZZLE_RUNTIME_XXXXXXX durante o tempo de execução.

Antes

Disponível em tempo de compilação (ou seja, será restringido por webpack)

  • RAZZLE_XXXXXX
  • PORT
  • HOST

Depois de

Disponível em tempo de execução

  • PORT
  • HOST
  • RAZZLE_RUNTIME_XXXXXXX

Disponível em tempo de compilação (ou seja, será restringido por webpack)

  • RAZZLE_XXXXXX

Discussão

Outra alternativa é _apenas_ stringificar variáveis ​​prefixadas com RAZZLE_XXXX como o plug-in razzle-heroku faz. Isso também seria compatível com versões anteriores. Por um lado, isso tornaria mais fácil trabalhar com a forma como o Heroku nomeia suas variáveis ​​de ambiente de configuração (por exemplo, MONGO_URI ). Por outro lado, isso seria _muito_ fácil de bagunçar acidentalmente (isto é, referenciar uma variável de tempo de execução dentro do código isomórfico compartilhado que está undefined no cliente ... explodindo o aplicativo).

Relacionado

527 # 526 # 514 # 477 # 356 # 285

discussion

Comentários muito úteis

@jaredpalmer Posso estar faltando alguma coisa, mas isso ainda é um problema para coisas como PORT, apesar do que os documentos readme indicam. process.env.MY_THING funciona bem, mas process.env.PORT ainda é substituído no tempo de compilação e não são lidos no tempo de execução. Portanto, o exemplo do Heroku realmente não funciona.

Não vejo nenhum tratamento especial de PORT, HOST, etc, conforme discutido neste tópico.

Todos 27 comentários

Eu, pessoalmente, não me preocuparia muito com o fato de process.env estar indisponível no cliente. Já temos que saber que a 'janela' não está disponível no servidor; faz sentido para mim que process.env teria o comportamento oposto. (não há nenhum processo de nó para ter um env, e os navegadores não expõem seus env vars que eu conheça) Eu penso em process.env como um lugar onde os segredos do servidor podem acabar, então eu preferia que fosse indisponível no lado do cliente por padrão.

Se dependesse de mim, as variáveis ​​process.env.RAZZLE_INLINED_XXXXXX seriam compiladas durante a construção (e disponíveis no cliente como strings DefinePlugin em linha) e qualquer outra coisa só estaria disponível no lado do servidor.

NODE_ENV também pode ser sequencial, já que é usado principalmente como uma descrição da construção e não como algo variável como o ambiente em que o código está sendo executado. Existem também alguns motivos de desempenho para fazer isso.

Eu gosto da ideia de PORT e HOST serem variáveis ​​em tempo de execução. Posso estar executando os mesmos artefatos de construção em ambientes diferentes.

Posso confirmar a luta. O comportamento atual não foi o que eu esperava e, especialmente, me atrapalhou ao implantar up em aws. Eu agora personalizei o razzle.config.js forma que todos os vars específicos do Razzle sejam prefixados com RAZZLE_XXX e sejam sequenciados em tempo de compilação. O resto está disponível em process.env.XXX . Também concordo com @gregmartyn sobre a indisponibilidade de vars de tempo de execução em process.env .

Ao tentar configurar o Razzle em um projeto React existente, enfrentamos esse problema. A porta não pode ser substituída em tempo de execução e outras variáveis ​​process.env não estão disponíveis no servidor.

Comportamento atual em tempo de construção:

process.env.PORT
process.env.NODE_ENV;
process.env.ASSETS_MANIFEST;
process.env.HOSTING_SET_VARIABLE;

torna-se no cliente e no servidor:

3000;
'development';
'/Users/[...]/build/assets.json';
undefined;

Embora isso garanta que o cliente e o servidor possam usar exatamente os mesmos envs de processo, isso torna impossível substituir no tempo de execução ou usar outras variáveis ​​de ambiente.

Se você deseja ser totalmente compatível com as versões anteriores, deve ser transpilado para isso no tempo de construção (no servidor, o cliente pode permanecer o mesmo):

process.env.PORT || 3000;
process.env.NODE_ENV || 'development';
process.env.ASSETS_MANIFEST || '/Users/[...]/build/assets.json';
process.env.HOSTING_SET_VARIABLE;

Dessa forma, você pode usar process.env.PORT no cliente e no servidor. O padrão será 3000.

Se o razzle for _construído_ com PORT=80 , o cliente terá process.env.PORT transpilado para 80 e o servidor o terá transpilado para process.env.PORT || 80 . Isso resultará no mesmo comportamento de agora.

Se razzle for _run_ com PORT=81 (enquanto construído com 80 ), a variável de ambiente do cliente permanecerá 80 enquanto a variável do servidor resultará em 81 .

Esse comportamento pode, é claro, levar a um comportamento inesperado, mas fornece o uso mais flexível de process.env enquanto mantém compatibilidade total com versões anteriores. A porta ainda pode ser substituída no servidor em tempo de execução, e outras variáveis ​​de ambiente definidas por plataformas de hospedagem funcionarão no servidor no estado em que se encontram.

Acho que o problema subjacente aqui é que a Razzle está atualmente tentando agrupar várias funções distintas em um objeto e, fundamentalmente, alterar a funcionalidade existente.

Ele está tentando:

  1. Transforme as variáveis ​​process.env em constantes por motivos de desempenho (veja o exemplo de minificação em https://webpack.js.org/plugins/define-plugin/#usage e nodejs lentidão em https://github.com/nodejs/node/issues / 3104)
  2. Disponibilize essas constantes para o servidor e o cliente para que o código isométrico tenha menos com que se preocupar.

Problemas:

  • process.env é para ambiente _variables_. Transformar isso em um conjunto de constantes muda seu comportamento esperado.
  • O process.env geralmente contém informações confidenciais, incluindo senhas, portanto, não é uma boa fonte de dados para compartilhar com o cliente. Se for parcialmente compartilhado, e parcialmente não compartilhado, o dev deve saber que uma coisa - process.env - não apenas não se comporta como nativamente, mas que também tem um comportamento diferente dependendo dos prefixos das chaves.
  • Ele está sendo transformado em constantes no momento da construção, o que é diferente de como as variáveis ​​de ambiente se comportam em outros contextos. O Docker, por exemplo, faz uma distinção entre variáveis ​​de tempo de construção (úteis quando a própria construção requer informações de variáveis ​​dependendo de onde está sendo executado) e variáveis ​​de ambiente (úteis quando uma imagem pré-construída é implantada em ambientes diferentes). Elastic Beanstalk, Heroku, et al. faça o mesmo.
  • O prefixo "RAZZLE_RUNTIME_" sugerido não é autoexplicativo. Deve transmitir que está disponível no lado do cliente e que não é uma variável. Acho que "INLINED_" cobre isso, já que inlining é comumente usado para se referir a algo que acontece em tempo de construção. Não é perfeito, ("ISOMORPHIC_INLINED_"?) Mas também está tentando ser curto.

Minha preferência seria dividir essa funcionalidade em partes diferentes e não mexer no process.env de forma alguma.
O DefinePlugin poderia chamar um arquivo como razzle.config.js para obter as constantes de construção
Recomende usar um padrão semelhante ao que Redux faz com configureStore (janela .__ PRELOADED_STATE__) para passar dados do servidor para o cliente.

Para compatibilidade com versões anteriores, as notas de atualização podem fornecer um razzle.config.js de amostra que define à moda antiga.

Um adendo: eu disse "para não mexer no process.env de forma alguma." mas pude ver que há uma exceção para process.env.NODE_ENV pelos motivos de desempenho do meu comentário acima e que "NODE_ENV" em si geralmente assumiu o significado de "NODE_ENV = produção significa que esta é uma compilação otimizada"

@gregmartyn , temos que definir NODE_ENV para otimizações de desempenho e usar a predefinição do babel como funciona agora. Meu raciocínio inicial para mexer com o env foi tornar a mudança do CRA muito mais fácil, já que o SSR costuma ser adicionado depois que um projeto já foi iniciado. Também usamos o CRA em outros projetos, por isso simplifica (um pouco) nosso conjunto de ferramentas.

Sim; concordou que NODE_ENV é uma exceção útil. É algo próprio e intimamente ligado à construção, por isso não é surpreendente. Eu pulei o CRA de uma solução SSR personalizada, então não estou muito familiarizado com a forma como eles fazem as coisas. Parece que isso também é um problema: https://github.com/facebook/create-react-app/issues/2353

Acho que esse é um problema maior para o Razzle do que para o CRA, porque o código de tempo de execução em um aplicativo CRA não é executado no servidor. O CRA pode fazer o que quiser com process.env porque, no que diz respeito ao código do lado do cliente, ele estaria vazio de outra forma. O Razzle, por outro lado, começa expresso para seu SSR, e esse código razoavelmente espera que process.env tenha sua semântica usual com acesso ao conjunto completo de variáveis ​​de ambiente de tempo de execução do nó. Process.env tem significado real no servidor, portanto, é uma pena que o CRA o tenha cooptado para um caso de uso diferente. Eles poderiam ter usado algum outro nome em vez de "process.env" como "cra.inlines". Em vez disso, o código isomórfico é atingido por uma decisão tomada considerando apenas o lado do cliente.

Deve ser observado em vermelho em todos os lugares que as variáveis ​​de ambiente RAZZLE_XXX estão TODAS disponíveis no cliente.

Como utilizo variáveis ​​de ambiente confidenciais sem que sejam enviadas ao cliente?

Eles não são enviados para o cliente a menos que você os referencie em código isomórfico

@jaredpalmer talvez esse problema seja específico do afterjs, então? Estou apenas fazendo referência a eles no código do servidor.

Eu gostaria de adicionar um voto para a capacidade de definir variáveis ​​de ambiente sem o prefixo RAZZLE . No mínimo, process.env não deve ser eliminado do lado do servidor, o que impede você de usar algo como dotenv para carregar variáveis ​​env do lado do servidor. Isso parece uma suposição muito intrusiva de se fazer sobre o ambiente do aplicativo.

Não estou muito certo de como o razzle atualmente injeta variáveis ​​de ambiente no cliente e no servidor, mas certamente você não gostaria de coisas específicas do servidor no cliente. Infelizmente, isso é uma espécie de quebra de negócio para mim agora.

Estou reenviando minha solução proposta para um aplicativo de reação isomórfico de https://github.com/jaredpalmer/razzle/issues/477#issuecomment -363538712

O conceito principal é usar um espaço reservado no tempo de compilação que é injetado no tempo de execução antes da execução do servidor para definir as variáveis ​​de ambiente de tempo de execução de maneira adequada. Esta solução é para executar o servidor em um contêiner docker, mas provavelmente poderia ser adaptada para este RFC.

Observe que nesta solução as variáveis ​​de ambiente RAZZLE_XXXX são combinadas e injetadas junto com HOST, PORT e REDIS_URL.


Eu pessoalmente lutei com esse problema e passei várias horas tentando descobrir uma solução para ele.

Isso é inerente à compilação do webpack e não está relacionado ao razzle em si.

Depois de examinar como o create-react-app lida com isso e portar algum código javascript e ruby, entre dois projetos, implantei com sucesso um aplicativo razzle typescript react em um contêiner docker no heroku com a seguinte solução:

env.ts

Este script é usado como um módulo para lidar com env de tempo de execução.

export interface EnvironmentStore {
  NODE_ENV?: string;
  [key: string]: string | undefined;
}

// Capture environment as module variable to allow testing.
let compileTimeEnv: EnvironmentStore;
try {
  compileTimeEnv = process.env as EnvironmentStore;
} catch (error) {
  compileTimeEnv = {};
  // tslint:disable-next-line no-console
  console.log(
    '`process.env` is not defined. ' +
    'Compile-time environment will be empty.'
  );
}

// This template tag should be rendered/replaced with the environment in production.
// Padded to 4KB so that the data can be inserted without offsetting character
// indexes of the bundle (avoids breaking source maps).
/* tslint:disable:max-line-length */
const runtimeEnv = '{{RAZZLE_VARS_AS_BASE64_JSON__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________}}';
/* tslint:enable:max-line-length */

// A function returning the runtime environment, so that
// JSON parsing & errors occur at runtime instead of load time.
export const loadRuntimeEnv = (): EnvironmentStore => {
  let env;
  if (typeof env === 'undefined') {
    if (compileTimeEnv.NODE_ENV === 'production') {
      try {
        env = JSON.parse((Buffer.from(runtimeEnv.trim(), 'base64').toString()));
      } catch (error) {
        env = {};
        const overflowsMessage = runtimeEnv.slice(32, 33) !== null;
        // tslint:disable-next-line no-console
        console.error(
          'Runtime env vars cannot be parsed. Content is `%s`',
          runtimeEnv.slice(0, 31) + (overflowsMessage ? '…' : '')
        );
      }

    } else {
      env = compileTimeEnv;
    }
  }
  return env;
};

export default loadRuntimeEnv;

uso:

import { loadRuntimeEnv, EnvironmentStore } from './env';
const env: EnvironmentStore = loadRuntimeEnv();

const serverHost: string =env.RAZZLE_SERVER_HOST || 'localhost';

docker-start.js

Este script é usado como ponto de entrada em vez de server.js e é usado para injetar {{RAZZLE_VARS_AS_BASE64_JSON ___...}} placehoder com as variáveis ​​de ambiente de tempo de execução reais.

require('newrelic');
const logger = require('heroku-logger');
const path = require('path');
const fs = require('fs');

const PLACEHOLDER = /\{\{RAZZLE_VARS_AS_BASE64_JSON_*?\}\}/;
const MATCHER = /^RAZZLE_/i;

const InjectableEnv = {

    inject: function(file, ...args) {

        const buffer = fs.readFileSync(file, { encoding: 'utf-8' });
        let injectee = buffer.toString();

        const matches = injectee.match(PLACEHOLDER);
        if (!matches) {
            return;
        }

        const placeholderSize = matches[0].length;

        let env = InjectableEnv.create(args);
        const envSize = env.length;
        const newPadding = placeholderSize - envSize;
        if (newPadding < 0) {
            console.log('You need to increase your placeholder size');
            process.exit();
        }
        const padding = Array(newPadding).join(' ');
        env = InjectableEnv.pad(padding, env);

        const injected = injectee.replace(PLACEHOLDER, env);

        fs.writeFileSync(file, injected, { encoding: 'utf-8' });
    },

    create: function() {

        const vars = Object.keys(process.env)
            .filter(key => MATCHER.test(key))
            .reduce((env, key) => {
                env[key] = process.env[key];
                return env;
            }, {});

        vars.NODE_ENV = process.env.NODE_ENV;

        if (typeof process.env.HOST !== 'undefined' && typeof vars.RAZZLE_SERVER_HOST === 'undefined') {
          vars.RAZZLE_SERVER_HOST = process.env.HOST;
        }

        if (typeof process.env.PORT !== 'undefined' && typeof vars.RAZZLE_SERVER_PORT === 'undefined') {
          vars.RAZZLE_SERVER_PORT = process.env.PORT;
        }

        if (typeof process.env.REDIS_URL !== 'undefined' && typeof vars.RAZZLE_REDIS_URL === 'undefined') {
          vars.RAZZLE_REDIS_URL = process.env.REDIS_URL;
        }

        return Buffer.from(JSON.stringify(vars)).toString('base64');
    },

    pad: function(pad, str, padLeft) {
        if (typeof str === 'undefined')
            return pad;
        if (padLeft) {
            return (pad + str).slice(-pad.length);
        } else {
            return (str + pad).substring(0, pad.length);
        }
    }
}

const root = process.cwd();
const serverBundle = path.resolve(path.join(root, '/build/server.js'));

if (fs.existsSync(serverBundle)) {
    logger.info('Injecting runtime env');
    InjectableEnv.inject(serverBundle);
    logger.info('Launching server instance');
    require(serverBundle);
}

Dockerfile

# You should always specify a full version here to ensure all of your developers
# are running the same version of Node.
FROM node:8.9.4

ENV NODE_ENV=production \
    REACT_BUNDLE_PATH=/static/js/vendor.js \
    PATH=/app/node_modules/.bin:$PATH \
    NPM_CONFIG_LOGLEVEL=warn

RUN curl -o- -L https://yarnpkg.com/install.sh | bash

# use changes to package.json to force Docker not to use the cache
# when we change our application's nodejs dependencies:
COPY package.json yarn.lock /tmp/
RUN cd /tmp \
  && yarn install --production=false --pure-lockfile \
  && mkdir -p /app \
  && cp -a /tmp/node_modules /app \
  && yarn cache clean \
  && rm -rf *.*

# From here we load our application's code in, therefore the previous docker
# "layer" thats been cached will be used if possible
WORKDIR /app
ADD . /app

RUN yarn build

EXPOSE 3000

CMD ["node", "docker-start.js"]

Observe que o processamento da mensagem de estouro não foi concluído, mas espero que isso ajude

Referências:

Heroku Buildpack para criar-reagir-app
Camada interna do Heroku Buildpack para criar-reagir-app

Isso é inerente à compilação do webpack e não está relacionado ao razzle em si.

Razzle é quem está configurando DefinePlugin. Isso pode ser resolvido no Razzle.

Acho que sigo o que você está dizendo. Diga-me se eu entendi errado: no tempo de compilação, coloque espaços reservados em process.env que obtém a string substituída na inicialização da instância na compilação do servidor. Destina-se a lidar com os segredos do servidor. (Não vejo por que ele não pode ser executado na versão do cliente também) Problemas: Não funciona com HMR. É um hack - introduz um limite arbitrário de 4k. Em sua forma atual, ele não trata de env vars que devem ser compartilhados com o cliente - eles permanecem constantes de tempo de construção. É uma etapa extra de inicialização para contêineres.

Para refazer muito do que eu disse em https://github.com/jaredpalmer/razzle/issues/528#issuecomment -377058844

Acho que a solução é reconhecer que Razzle e CRA estão tentando incluir mais funcionalidades em process.env do que deveriam. Para fazê-lo funcionar com o Docker, estamos tentando ter um objeto com campos tendo um dos 4 estados possíveis: estático (tempo de construção) e dinâmico (aqui, hora de início do contêiner), segredos e não secrets. Poderíamos criar prefixos para todos os 4 desses estados (process.env.STATIC_PRIVATE_X, process.env.DYNAMIC_PUBLIC_Y, ...), mas acho que seria muito melhor com uma solução mais limpa.

Se process.env se comportasse da maneira que o faz nativamente - como um armazenamento de segredos de servidor - então as coisas seriam muito mais fáceis de entender. Há uma exceção: NODE_ENV como um embutido de tempo de construção, mas tudo bem porque é uma propriedade da construção. Não faria sentido definir NODE_ENV em tempo de execução.

Tudo o que resta é uma maneira de levar os dados ao cliente. Não vejo por que isso está usando process.env. Por que não usar, por exemplo, razzle.build.X para coisas estáticas e passar coisas dinâmicas para o cliente da mesma forma que o redux faz?

Há outro problema em que o process.env é lento no Node, mas é melhor resolvido com uma camada de cache que lê process.env uma vez.

@gregmartyn Concordo que isso é um hack ... e introduz um limite arbitrário de 4K. Essa ideia é baseada no que é feito atualmente com o CRA (consulte as referências publicadas) e se destina a variáveis ​​de ambiente de execução do lado do servidor.

Abriu um PR que acredito que deve ajudar com a raiz deste problema - env ​​vars não estando disponível no servidor em tempo de execução, estou interessado em saber se isso resolve alguns dos problemas aqui.

Eu também concordo que PORT & HOST também seria idealmente deixado sozinho no tempo de compilação do servidor.

@tgriesser legal! Isso é uma grande melhoria.
Além de PORT e HOST , eu adicionaria PUBLIC_PATH à lista de vars que não deveriam ser compilados.

Ainda estou descobrindo que as variáveis ​​de ambiente personalizadas sensíveis estão todas sendo compiladas no cliente. Estou fazendo referência a eles apenas em hotloader ? Como posso evitar que cheguem ao cliente.

Olá a todos, estou lidando com tudo isso no trabalho esta semana. Fique ligado. # 611 provavelmente será mesclado.

Seguir o novo guia no readme com config.js funcionou para mim no que diz respeito à remoção de variáveis ​​env sensíveis do pacote. Incrível: D

Veja as notas da v2

@jaredpalmer Posso estar faltando alguma coisa, mas isso ainda é um problema para coisas como PORT, apesar do que os documentos readme indicam. process.env.MY_THING funciona bem, mas process.env.PORT ainda é substituído no tempo de compilação e não são lidos no tempo de execução. Portanto, o exemplo do Heroku realmente não funciona.

Não vejo nenhum tratamento especial de PORT, HOST, etc, conforme discutido neste tópico.

Observe que tornar PORT uma variável real também é bloqueado por # 581. Tenho que corrigir isso e usar um razzle.config.js que cria um array DefinePlugin que remove PORT para fazê-lo funcionar. (mas funciona!)

se alguém quiser usar variáveis ​​.env em tempo de execução, use este pequeno pacote.
https://www.npmjs.com/package/razzle-plugin-runtimeenv

Alguém pode aconselhar como implantar o aplicativo Razzle no Azure? Estou realmente lutando com isso.

se alguém quiser usar variáveis ​​.env em tempo de execução, use este pequeno pacote.
https://www.npmjs.com/package/razzle-plugin-runtimeenv

Como funciona? Você poderia dar um exemplo?

Acho que as variáveis ​​de ambiente devem ser injetadas no tempo de execução, de fato. Se conteinerizarmos um aplicativo razzle, gostaríamos de criar uma imagem independente do ambiente que ele executa e ler as variáveis ​​de ambiente ao iniciar o servidor e servi-las ao aplicativo cliente.

Qualquer outra abordagem não está realmente usando variáveis ​​de ambiente, já que está acontecendo apenas durante o tempo de construção.

Como mencionei aqui:
https://github.com/HamidTanhaei/razzle-plugin-runtime/issues/1#issuecomment -525731273

você pode usar seus arquivos .env e .env.development no tempo de execução por razzle-plugin-runtime . adiciona a capacidade de usar suas variáveis ​​env em seu aplicativo em tempo de execução.

por exemplo, estou usando para configurar axios :
axios.defaults.baseURL = $ {process.env.RAZZLE_APP_API_BASE_PATH} $ {process.env.RAZZLE_APP_API_VERSION} ;
e você pode fornecer variáveis ​​ENV de produção para a produção como esta:
https://github.com/jaredpalmer/razzle#adding -temporary-environment-variables-in-your-shell

Alguém pode aconselhar como implantar o aplicativo Razzle no Azure? Estou realmente lutando com isso.

Resolvi o problema da Porta do Azure usando a solução compartilhada por https://github.com/jaredpalmer/razzle/issues/906#issuecomment -467046269

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

Questões relacionadas

kkarkos picture kkarkos  ·  3Comentários

mhuggins picture mhuggins  ·  3Comentários

krazyjakee picture krazyjakee  ·  3Comentários

jcblw picture jcblw  ·  4Comentários

sebmor picture sebmor  ·  4Comentários