Como você compartilha constantes nos módulos do NodeJS?

239

Atualmente estou fazendo isso:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

E usá-lo em bar.js:

var foo = require('foo');
foo.FOO; // 5

Existe uma maneira melhor de fazer isso? Parece estranho declarar a constante no objeto de exportação.

Torre
fonte
6
Se você deseja exportá-lo, coloque-o no exports. O que é estranho nisso?
Alex Wayne
5
Estou acostumado a c # e PHP. Acho que preciso me acostumar a definir cada constante duas vezes. Talvez no futuro teremos export const FOO = 5;.
Torre
1
@Tower O futuro é agora (ES2015)! 2ality.com/2014/09/…
Spain Train
1
Isso é funcionalmente diferente do mais conciso module.exports={FOO:5};?
Joe Lapp
3
Ele não só se sentir akward, não é constante mais
Ini

Respostas:

96

Você pode exportá-lo explicitamente para o escopo global com global.FOO = 5. Então você simplesmente precisa exigir o arquivo e nem mesmo salvar seu valor de retorno.

Mas realmente, você não deveria fazer isso. Manter as coisas adequadamente encapsuladas é uma coisa boa. Você já tem a idéia certa, então continue fazendo o que está fazendo.

Alex Wayne
fonte
51
Sinto muito por fazer isso, mas -1 por conhecer melhor, mas não fornecer uma solução (melhor) alternativa; (re: "Mas, na verdade, você não deve fazer isso. Manter as coisas adequadamente encapsuladas é uma coisa boa.")
Obrigado
22
Se toda a comunidade de desenvolvimento de software pensasse dessa maneira, ainda estaríamos usando punchards. Felizmente, existem alguns dissidentes por aí que sabem quando é melhor quebrar as regras insanas que impomos a nós mesmos. Se o encapsulamento for útil, use-o. Se for uma babá nervosa que o interrompe, atire na babá nervosa e continue com ela.
não sincronizado
22
@naomik (tempo de resposta super tardio) A verdadeira razão de eu não fornecer uma solução melhor é porque o OP já conhece a solução. Encapsule as coisas em seu próprio módulo e exija-as sempre que necessário.
Alex Wayne
1
Então isso não é uma resposta real e deve, antes, ser um comentário explicação afirmando que "você está fazendo bom o suficiente, as alternativas são ruins" ..
Andrey Popov
1
Aplicação incorreta de encapsulamento. Quando uma classe usa valores especiais como indicadores e os nomeia, você deseja compartilhar isso com qualquer código que faça uso dessa classe.
grantwparks
314

Na minha opinião, a utilização Object.freezepermite um estilo mais seco e declarativo. Meu padrão preferido é:

./lib/constants.js

module.exports = Object.freeze({
    MY_CONSTANT: 'some value',
    ANOTHER_CONSTANT: 'another value'
});

./lib/some-module.js

var constants = require('./constants');

console.log(constants.MY_CONSTANT); // 'some value'

constants.MY_CONSTANT = 'some other value';

console.log(constants.MY_CONSTANT); // 'some value'

Aviso de desempenho desatualizado

O seguinte problema foi corrigido na v8 em janeiro de 2014 e não é mais relevante para a maioria dos desenvolvedores:

Lembre-se de que a configuração de gravável como false e o uso do Object.freeze têm uma penalidade de desempenho maciça na v8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 e http://jsperf.com / objeto congelado desempenho

Trem da Espanha
fonte
4
Bom caso de uso para o Object.freeze!
Estus Flask
Como deve ser se eu precisar exportar constantes e funções? Devo colocar funções no bloco de congelamento também?
Tom
3
essa abordagem é melhor porque o preenchimento automático do IDE funciona com ele.
David A
3
Essa é uma ótima resposta, mas pode afastar as pessoas dessa abordagem devido ao aviso desatualizado sobre o desempenho da v8 no final. Por favor, considere remover o aviso.
Sampathsris
4
Obrigado @Krumia! Eu o atualizei, mas deixei o texto de aviso original apenas para o contexto histórico (e porque alguns desses comentários não faria sentido sem ele).
Spain Train
163

Tecnicamente, constnão faz parte da especificação ECMAScript. Além disso, usando o padrão "CommonJS Module" que você anotou, é possível alterar o valor dessa "constante", já que agora é apenas uma propriedade do objeto. (não tenho certeza se isso trará alterações em cascata a outros scripts que exigem o mesmo módulo, mas é possível)

Para obter uma constante real que você também pode compartilhar, veja Object.create, Object.definePropertye Object.defineProperties. Se você definir writable: false, o valor na sua "constante" não poderá ser modificado. :)

É um pouco detalhado, (mas mesmo isso pode ser alterado com um pouco de JS), mas você só precisa fazer isso uma vez para o seu módulo de constantes. Usando esses métodos, qualquer atributo que você deixar de fora é o padrão false. (ao contrário de definir propriedades por meio de atribuição, cujo padrão é todos os atributos true)

Então, hipoteticamente, você pode simplesmente definir valuee enumerable, deixando de fora writablee, configurablecomo eles serão o padrão false, eu os incluí para maior clareza.

Atualização - Criei um novo módulo ( constantes de nó ) com funções auxiliares para esse mesmo caso de uso.

constants.js - Bom

Object.defineProperty(exports, "PI", {
    value:        3.14,
    enumerable:   true,
    writable:     false,
    configurable: false
});

constants.js - Melhor

function define(name, value) {
    Object.defineProperty(exports, name, {
        value:      value,
        enumerable: true
    });
}

define("PI", 3.14);

script.js

var constants = require("./constants");

console.log(constants.PI); // 3.14
constants.PI = 5;
console.log(constants.PI); // still 3.14
Dominic Barnes
fonte
2
@AntoineHedgecock Não é necessário, verifique a documentação Object.defineProperty(). Todas as propriedades não especificadas são assumidas falseneste contexto.
Dominic Barnes
6
Também digno de nota, Object.freeze () #
damianb 11/03/2013
1
Esta é a melhor resposta para esta pergunta. +1. Se eu pudesse, iria votar mais.
Ryan
1
Resposta maravilhosa, uma solução muito elegante e segura.
21480 Alex
1
@SpainTrain Parece que isso foi corrigido por codereview.chromium.org/135903014
Grinde
100

ES6.

exportar em foo.js

const FOO = 'bar';
module.exports = {
  FOO
}

importar em bar.js

const {FOO} = require('foo');
Diego Mello
fonte
40
Sim. O Stack Overflow precisa de uma maneira de depreciar respostas desatualizadas.
21717 Rick
7
Observe que é o constin bar.jsque impõe a imutabilidade da variável destruída, não o constin foo.js. Ou seja, pode-se usar let {FOO} =em bar.jse transformar a variável "constante". AFAIK, para reforçar a imutabilidade das exportações, ainda é necessário um módulo ES ou Object.freeze.
Spain Train
Pode-se também mudar por FOOdentro foo.js.
Lima_fil 11/09/19
16

Eu achei a solução que Dominic sugeriu ser a melhor, mas ainda falta um recurso da declaração "const". Quando você declara uma constante em JS com a palavra-chave "const", a existência da constante é verificada no tempo de análise, não no tempo de execução. Portanto, se você digitou incorretamente o nome da constante em algum lugar posteriormente no seu código, receberá um erro ao tentar iniciar o programa node.js. Qual é uma verificação ortográfica muito mais melhor.

Se você definir a constante com a função define () como sugerido por Dominic, você não receberá um erro se tiver digitado incorretamente a constante, e o valor da constante incorreta será indefinido (o que pode levar a dores de cabeça na depuração).

Mas acho que é o melhor que podemos conseguir.

Além disso, aqui está um tipo de melhoria da função de Dominic, em constans.js:

global.define = function ( name, value, exportsObject )
{
    if ( !exportsObject )
    {
        if ( exports.exportsObject )
            exportsObject = exports.exportsObject;
        else 
            exportsObject = exports;        
    }

    Object.defineProperty( exportsObject, name, {
        'value': value,
        'enumerable': true,
        'writable': false,
    });
}

exports.exportObject = null;

Dessa forma, você pode usar a função define () em outros módulos, e permite definir constantes dentro do módulo constants.js e constantes dentro do módulo a partir do qual você chamou a função. A declaração de constantes do módulo pode ser feita de duas maneiras (em script.js).

Primeiro:

require( './constants.js' );

define( 'SOME_LOCAL_CONSTANT', "const value 1", this ); // constant in script.js
define( 'SOME_OTHER_LOCAL_CONSTANT', "const value 2", this ); // constant in script.js

define( 'CONSTANT_IN_CONSTANTS_MODULE', "const value x" ); // this is a constant in constants.js module

Segundo:

constants = require( './constants.js' );

// More convenient for setting a lot of constants inside the module
constants.exportsObject = this;
define( 'SOME_CONSTANT', "const value 1" ); // constant in script.js
define( 'SOME_OTHER_CONSTANT', "const value 2" ); // constant in script.js

Além disso, se você deseja que a função define () seja chamada apenas a partir do módulo constantes (para não inchar o objeto global), defina-a assim em constants.js:

exports.define = function ( name, value, exportsObject )

e use-o assim em script.js:

constants.define( 'SOME_CONSTANT', "const value 1" );
xmak
fonte
11

Da experiência anterior do projeto, esta é uma boa maneira:

No constants.js:

// constants.js

'use strict';

let constants = {
    key1: "value1",
    key2: "value2",
    key3: {
        subkey1: "subvalue1",
        subkey2: "subvalue2"
    }
};

module.exports =
        Object.freeze(constants); // freeze prevents changes by users

No main.js (ou app.js, etc.), use-o como abaixo:

// main.js

let constants = require('./constants');

console.log(constants.key1);

console.dir(constants.key3);
Manohar Reddy Poreddy
fonte
8

Eu acho que constresolve o problema para a maioria das pessoas que procura esse anwwer. Se você realmente precisa de uma constante imutável, procure as outras respostas. Para manter tudo organizado, salve todas as constantes em uma pasta e exijo a pasta inteira.

arquivo src / main.js

const constants = require("./consts_folder");

src / consts_folder / index.js

const deal = require("./deal.js")
const note = require("./note.js")


module.exports = {
  deal,
  note
}

Ps. aqui o deale noteserá o primeiro nível no main.js

src / consts_folder / note.js

exports.obj = {
  type: "object",
  description: "I'm a note object"
}

Ps. objserá o segundo nível no main.js

src / consts_folder / deal.js

exports.str = "I'm a deal string"

Ps. strserá o segundo nível no main.js

Resultado final no arquivo main.js.

console.log(constants.deal); Ouput:

{deal: {str: 'Eu sou uma string de transação'},

console.log(constants.note); Ouput:

note Observação: {obj: {type: 'object', descrição: 'Eu sou um objeto de anotação'}}

Luis Martins
fonte
4

Como alternativa, você pode agrupar seus valores "constantes" em um objeto local e exportar uma função que retorna um clone superficial desse objeto.

var constants = { FOO: "foo" }

module.exports = function() {
  return Object.assign({}, constants)
}

Então, não importa se alguém reatribuir o FOO, porque isso afetará apenas sua cópia local.

herman
fonte
ou apenas module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow 19/09/17
3

Como o Node.js está usando os padrões CommonJS, você só pode compartilhar variáveis ​​entre os módulos com module.exportsou definindo uma var global como faria no navegador, mas em vez de usar a janela usada global.your_var = value;.

alessioalex
fonte
2

Acabei fazendo isso exportando um objeto congelado com funções getter anônimas, em vez das próprias constantes. Isso reduz o risco de erros desagradáveis ​​introduzidos devido a um simples erro de digitação do nome const, pois um erro de tempo de execução será gerado no caso de um erro de digitação. Aqui está um exemplo completo que também usa os símbolos ES6 para as constantes, garantindo a exclusividade e as funções de seta ES6. Gostaria de receber feedback se algo nessa abordagem parecer problemático.

'use strict';
const DIRECTORY = Symbol('the directory of all sheets');
const SHEET = Symbol('an individual sheet');
const COMPOSER = Symbol('the sheet composer');

module.exports = Object.freeze({
  getDirectory: () => DIRECTORY,
  getSheet: () => SHEET,
  getComposer: () => COMPOSER
});
Eloquência
fonte
0

Eu recomendo fazê-lo com o webpack (pressupõe que você esteja usando o webpack).

Definir constantes é tão simples quanto definir o arquivo de configuração do webpack:

var webpack = require('webpack');
module.exports = {
    plugins: [
        new webpack.DefinePlugin({
            'APP_ENV': '"dev"',
            'process.env': {
                'NODE_ENV': '"development"'
            }
        })
    ],    
};

Dessa forma, você os define fora da sua fonte e eles estarão disponíveis em todos os seus arquivos.

Galki
fonte
0

Não acho que seja uma boa prática invadir o espaço GLOBAL a partir de módulos, mas em cenários onde poderia ser estritamente necessário implementá-lo:

Object.defineProperty(global,'MYCONSTANT',{value:'foo',writable:false,configurable:false});

Deve ser considerado o impacto desse recurso. Sem a nomeação adequada dessas constantes, o risco de SOBRESCREVER variáveis ​​globais já definidas é algo real.

colxi
fonte