¿Cómo comparte constantes en los módulos NodeJS?

240

Actualmente estoy haciendo esto:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

Y usándolo en bar.js:

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

¿Hay una mejor manera de hacer esto? Se siente incómodo declarar la constante en el objeto de exportaciones.

Torre
fuente
66
Si quieres exportarlo, lo pones en el exports. ¿Qué tiene de incómodo eso?
Alex Wayne
55
Estoy acostumbrado a C # y PHP. Supongo que solo tengo que acostumbrarme a definir cada constante dos veces. Quizás en el futuro lo tengamos export const FOO = 5;.
Torre
1
@Tower ¡El futuro es ahora (ES2015)! 2ality.com/2014/09/…
España
1
¿Es esto funcionalmente diferente del más conciso module.exports={FOO:5};?
Joe Lapp
3
No solo se siente incómodo, ya no es constante
Ini

Respuestas:

97

Puede exportarlo explícitamente al ámbito global con global.FOO = 5. Entonces simplemente necesita solicitar el archivo, y ni siquiera guardar su valor de retorno.

Pero realmente, no deberías hacer eso. Mantener las cosas correctamente encapsuladas es algo bueno. Ya tienes la idea correcta, así que sigue haciendo lo que estás haciendo.

Alex Wayne
fuente
51
Lamento hacer esto, pero -1 por conocer mejor pero no proporcionar una solución alternativa (mejor); (re: "Pero realmente, no deberías hacer eso. Mantener las cosas correctamente encapsuladas es algo bueno".)
Gracias
22
Si toda la comunidad de desarrollo de software pensara de esta manera, aún estaríamos usando punchards. Afortunadamente, hay algunos rebeldes que saben cuándo es mejor romper las reglas locas que nos imponemos. Si la encapsulación es útil, úsela. Si es una niñera nerviosa que te impide hacer tu trabajo, despide a la niñera nerviosa y sigue adelante.
sincronizar el
22
@naomik (tiempo de respuesta súper tardío) La verdadera razón por la que no proporcioné una mejor solución es porque el OP ya conoce la solución. Encapsule cosas en su propio módulo y solicítelas cuando sea necesario.
Alex Wayne
1
Entonces esta no es una respuesta real y debería ser un comentario explicativo que diga "lo estás haciendo lo suficientemente bien, las alternativas son malas" ..
Andrey Popov
1
Aplicación incorrecta de la encapsulación. Cuando una clase usa valores especiales como indicadores y les da un nombre, QUIERES compartirlo con cualquier código que haga uso de esa clase.
grantwparks
314

En mi opinión, la utilización Object.freezepermite un SECADOR y un estilo más declarativo. Mi patrón preferido es:

./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'

Advertencia de rendimiento desactualizado

El siguiente problema se solucionó en v8 en enero de 2014 y ya no es relevante para la mayoría de los desarrolladores:

Tenga en cuenta que tanto la configuración de escritura como falsa y el uso de Object.freeze tienen una penalización de rendimiento masiva en v8: https://bugs.chromium.org/p/v8/issues/detail?id=1858 y http://jsperf.com / performance-frozen-object

Tren de españa
fuente
44
Buen caso de uso para Object.freeze!
Estus Flask
¿Qué aspecto debería tener si necesito exportar constantes y funciones? ¿Debo poner funciones en el bloque de congelación también?
Tom
3
este enfoque es mejor porque el autocompletado IDE funciona con él.
David A
3
Esta es una gran respuesta, pero podría alejar a las personas de este enfoque debido a la advertencia obsoleta sobre el rendimiento de v8 al final. Por favor considere eliminar la advertencia.
sampathsris
44
Gracias @ krumia! Lo actualicé, pero dejé el texto de advertencia original solo para el contexto histórico (y porque algunos de estos comentarios no tendrían sentido sin él).
Tren de España el
163

Técnicamente, constno forma parte de la especificación ECMAScript. Además, utilizando el patrón "Módulo CommonJS" que ha notado, puede cambiar el valor de esa "constante" ya que ahora es solo una propiedad de objeto. (no estoy seguro si eso cambiará en cascada a otros scripts que requieren el mismo módulo, pero es posible)

Para obtener una constante real que también se puede compartir, revisar Object.create, Object.definePropertyy Object.defineProperties. Si establece writable: false, entonces el valor en su "constante" no puede modificarse. :)

Es un poco detallado (pero incluso eso se puede cambiar con un pequeño JS), pero solo debería hacerlo una vez para su módulo de constantes. Usando estos métodos, cualquier atributo que omita por defecto false. (a diferencia de la definición de propiedades mediante asignación, que predetermina todos los atributos true)

Por lo tanto, hipotéticamente, podría simplemente configurar valuey enumerable, omitiendo writabley configurableya que estarán predeterminados false, los he incluido para mayor claridad.

Actualización : he creado un nuevo módulo ( constantes de nodo ) con funciones auxiliares para este mismo caso de uso.

constants.js - Bueno

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

constants.js - Mejor

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
fuente
2
@AntoineHedgecock No es necesario, consulte la documentación en Object.defineProperty(). Todas las propiedades no especificadas se suponen falseen este contexto.
Dominic Barnes
66
También cabe destacar, Object.freeze ()
damianb
1
Esta es la mejor respuesta de esta pregunta. +1. Si pudiera, lo votaría más.
Ryan
1
Maravillosa respuesta, una solución muy elegante y segura.
Alex
1
@SpainTrain Esto parece haber sido arreglado por codereview.chromium.org/135903014
Grinde
100

ES6 camino.

exportar en foo.js

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

importar en bar.js

const {FOO} = require('foo');
Diego Mello
fuente
40
Si. Stack Overflow necesita una forma de depreciar las respuestas desactualizadas.
Rick Jolly el
77
Tenga en cuenta que es el constin el bar.jsque impone la inmutabilidad de la variable desestructurada, no el constin foo.js. Es decir, se puede utilizar let {FOO} =en bar.jsy mutar la variable "constante". AFAIK, para imponer la inmutabilidad de las exportaciones, uno todavía necesita módulos ES o Object.freeze.
Tren de España el
También se puede cambiar por FOOdentro foo.js.
lima_fil
16

Encontré la solución que Dominic sugirió para ser la mejor, pero todavía falta una característica de la declaración "const". Cuando declara una constante en JS con la palabra clave "const", la existencia de la constante se verifica en tiempo de análisis, no en tiempo de ejecución. Entonces, si escribe mal el nombre de la constante en algún lugar más adelante en su código, obtendrá un error cuando intente iniciar su programa node.js. Lo cual es una verificación de ortografía mucho mejor.

Si define la constante con la función define () como sugirió Dominic, no obtendrá un error si escribe mal la constante, y el valor de la constante mal escrita no estará definido (lo que puede provocar dolores de cabeza de depuración).

Pero supongo que esto es lo mejor que podemos obtener.

Además, aquí hay una especie de mejora de la función de Dominic, en 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;

De esta manera, puede usar la función define () en otros módulos, y le permite definir constantes tanto dentro del módulo constants.js como constantes dentro de su módulo desde el que llamó a la función. La declaración de las constantes del módulo se puede hacer de dos maneras (en script.js).

Primero:

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

Además, si desea que la función define () se invoque solo desde el módulo de constantes (no para hinchar el objeto global), debe definirla así en constants.js:

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

y úsalo así en script.js:

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

De la experiencia previa del proyecto, esta es una buena manera:

En las constantes.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

En main.js (o app.js, etc.), úselo de la siguiente manera:

// main.js

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

console.log(constants.key1);

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

pienso que const resuelve el problema para la mayoría de las personas que buscan esta respuesta. Si realmente necesita una constante inmutable, busque las otras respuestas. Para mantener todo organizado, guardo todas las constantes en una carpeta y luego requiero la carpeta completa.

archivo 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
}

PD. aquí el dealy noteserá el primer nivel en main.js

src / consts_folder / note.js

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

PD. objserá el segundo nivel en main.js

src / consts_folder / deal.js

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

PD. strserá el segundo nivel en main.js

Resultado final en el archivo main.js:

console.log(constants.deal); Ouput:

{deal: {str: 'I \' ma deal string '},

console.log(constants.note); Ouput:

note: {obj: {type: 'object', description: 'I \' ma note object '}}

Luis Martins
fuente
4

Como alternativa, puede agrupar sus valores "constantes" en un objeto local y exportar una función que devuelva un clon superficial de este objeto.

var constants = { FOO: "foo" }

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

Entonces no importa si alguien reasigna FOO porque solo afectará su copia local.

Germán
fuente
o simplemente module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow
3

Dado que Node.js usa los patrones CommonJS, solo puede compartir variables entre módulos con module.exportso configurando una variable global como lo haría en el navegador, pero en lugar de usar la ventana que usa global.your_var = value;.

alessioalex
fuente
2

Terminé haciendo esto exportando un objeto congelado con funciones getter anónimas, en lugar de las constantes mismas. Esto reduce el riesgo de errores desagradables introducidos debido a un simple error tipográfico del nombre constante, ya que se generará un error de tiempo de ejecución en caso de un error tipográfico. Aquí hay un ejemplo completo que también usa Símbolos ES6 para las constantes, asegurando la unicidad y las funciones de flecha ES6. Agradecería comentarios si algo en este enfoque parece 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
});
Elocuencia
fuente
0

Recomiendo hacerlo con webpack (se supone que está usando webpack).

Definir constantes es tan simple como configurar el archivo de configuración del paquete web:

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

De esta manera, los define fuera de su fuente y estarán disponibles en todos sus archivos.

galki
fuente
0

No creo que sea una buena práctica invadir el espacio GLOBAL desde los módulos, pero en escenarios donde podría ser estrictamente necesario implementarlo:

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

Tiene que considerarse el impacto de este recurso. Sin un nombre apropiado de esas constantes, el riesgo de SOBRESCRIBIR variables globales ya definidas es algo real.

colxi
fuente