Build condizionale basata sull'ambiente che utilizza Webpack


93

Ho alcune cose per lo sviluppo, ad esempio mock con cui vorrei non gonfiare il mio file di build distribuito.

In RequireJS puoi passare una configurazione in un file plugin e richiedere condizionalmente le cose in base a quello.

Per webpack non sembra esserci un modo per farlo. In primo luogo, per creare una configurazione di runtime per un ambiente, ho usato risoluzioni.alias per riproporre un requisito in base all'ambiente, ad esempio:

// All settings.
var all = {
    fish: 'salmon'
};

// `envsettings` is an alias resolved at build time.
module.exports = Object.assign(all, require('envsettings'));

Quindi, quando creo la configurazione del pacchetto web, posso assegnare dinamicamente a quale file envsettingspunta (cioè webpackConfig.resolve.alias.envsettings = './' + env).

Tuttavia vorrei fare qualcosa del tipo:

if (settings.mock) {
    // Short-circuit ajax calls.
    // Require in all the mock modules.
}

Ma ovviamente non voglio creare in quei file finti se l'ambiente non è finto.

Potrei forse repoint manualmente tutte quelle richieste su un file stub usando di nuovo resol.alias - ma c'è un modo che sembra meno hacky?

Qualche idea su come posso farlo? Grazie.


Nota che per ora ho usato gli alias per puntare a un file vuoto (stub) su ambienti che non voglio (ad esempio require ('mocks') punterà a un file vuoto su envs non mock. Sembra un po 'hacky ma è funziona.
Dominic

Risposte:


59

Puoi usare il plugin di definizione .

Lo uso facendo qualcosa di semplice come questo nel file di compilazione del tuo webpack dove envè il percorso di un file che esporta un oggetto di impostazioni:

// Webpack build config
plugins: [
    new webpack.DefinePlugin({
        ENV: require(path.join(__dirname, './path-to-env-files/', env))
    })
]

// Settings file located at `path-to-env-files/dev.js`
module.exports = { debug: true };

e poi questo nel tuo codice

if (ENV.debug) {
    console.log('Yo!');
}

Eliminerà questo codice dal file di build se la condizione è falsa. Puoi vedere un esempio di build di Webpack funzionante qui .


Sono un po 'confuso da questa soluzione. Non menziona come dovrei impostare env. Guardando questo esempio sembra che stiano gestendo quella bandiera tramite gulp e yargs che non tutti usano.
Andre

1
Come funziona con i linter? Devi definire manualmente nuove variabili globali che vengono aggiunte nel plugin Define?
segna

2
@mark yes. Aggiungi qualcosa di simile "globals": { "ENV": true }al tuo .eslintrc
Matt Derrick

come potrei accedere alla variabile ENV in un componente? Ho provato la soluzione sopra ma ricevo ancora l'errore che ENV non è definito
jasan

18
NON elimina il codice dai file di build! L'ho testato e il codice è qui.
Lionel

42

Non sono sicuro del motivo per cui la risposta "webpack.DefinePlugin" è la migliore ovunque per la definizione delle importazioni / richieste basate sull'ambiente.

Il problema con questo approccio è che stai ancora consegnando tutti quei moduli al client -> controlla ad esempio con webpack-bundle-analyezer . E non ridurre affatto le dimensioni di bundle.js :)

Quindi ciò che funziona davvero bene e molto più logico è: NormalModuleReplacementPlugin

Quindi, piuttosto che fare un requisito condizionale on_client -> semplicemente non includere i file non necessari nel bundle in primo luogo

spero che aiuti


Bello non sapevo di quel plugin!
Dominic

Con questo scenario non avresti più build per ambiente? Ad esempio, se ho l'indirizzo del servizio Web per gli ambienti dev / QA / UAT / di produzione, avrei bisogno di 4 contenitori separati, 1 per ogni ambiente. Idealmente dovresti avere un contenitore e avviarlo con una variabile d'ambiente per specificare quale configurazione caricare.
Brett Mathe

No, non proprio. Questo è esattamente quello che fai con il plugin -> specifichi il tuo ambiente tramite env vars e crea un solo contenitore, ma per un ambiente particolare senza inclusioni ridondanti. Ovviamente ciò dipende anche da come imposti la configurazione del tuo webpack e ovviamente puoi costruire tutte le build, ma non è ciò di cui tratta e fa questo plugin.
Roman Zhyliov

34

Usa ifdef-loader. Nei tuoi file sorgente puoi fare cose come

/// #if ENV === 'production'
console.log('production!');
/// #endif

La webpackconfigurazione rilevante è

const preprocessor = {
  ENV: process.env.NODE_ENV || 'development',
};

const ifdef_query = require('querystring').encode({ json: JSON.stringify(preprocessor) });

const config = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: `ifdef-loader?${ifdef_query}`,
        },
      },
    ],
  },
  // ...
};

2
Ho votato positivamente questa risposta poiché la risposta accettata non rimuove il codice come previsto ed è più probabile che la sintassi simile al preprocessore venga identificata come un elemento condizionale.
Christian Ivicevic

1
Grazie mille! Esso funziona magicamente. Diverse ore di esperimenti con ContextReplacementPlugin, NormalModuleReplacementPlugin e altre cose - tutti falliti. Ed ecco ifdef-loader, che mi salva la giornata.
jeron-diovis

27

Ho finito per usare qualcosa di simile alla risposta di Matt Derrick , ma ero preoccupato per due punti:

  1. La configurazione completa viene iniettata ogni volta che la uso ENV(il che è dannoso per le configurazioni di grandi dimensioni).
  2. Devo definire più punti di ingresso perché require(env)punta a file diversi.

Quello che mi è venuto in mente è un semplice compositore che crea un oggetto di configurazione e lo inietta in un modulo di configurazione.
Ecco la struttura del file, che sto usando per questo:

config/
 └── main.js
 └── dev.js
 └── production.js
src/
 └── app.js
 └── config.js
 └── ...
webpack.config.js

Il main.jscontiene tutte le cose di configurazione predefinite:

// main.js
const mainConfig = {
  apiEndPoint: 'https://api.example.com',
  ...
}

module.exports = mainConfig;

L' dev.jse production.jsroba di configurazione solo attesa che sostituisce la configurazione principale:

// dev.js
const devConfig = {
  apiEndPoint: 'http://localhost:4000'
}

module.exports = devConfig;

La parte importante è webpack.config.jsche compone la configurazione e utilizza DefinePlugin per generare una variabile di ambiente __APP_CONFIG__che contiene l'oggetto di configurazione composto:

const argv = require('yargs').argv;
const _ = require('lodash');
const webpack = require('webpack');

// Import all app configs
const appConfig = require('./config/main');
const appConfigDev = require('./config/dev');
const appConfigProduction = require('./config/production');

const ENV = argv.env || 'dev';

function composeConfig(env) {
  if (env === 'dev') {
    return _.merge({}, appConfig, appConfigDev);
  }

  if (env === 'production') {
    return _.merge({}, appConfig, appConfigProduction);
  }
}

// Webpack config object
module.exports = {
  entry: './src/app.js',
  ...
  plugins: [
    new webpack.DefinePlugin({
      __APP_CONFIG__: JSON.stringify(composeConfig(ENV))
    })
  ]
};

L'ultimo passaggio è ora config.js, sembra così (utilizzando la sintassi di importazione esportazione es6 qui perché è sotto webpack):

const config = __APP_CONFIG__;

export default config;

Nel tuo app.jsora potresti usare import config from './config';per ottenere l'oggetto di configurazione.


2
Davvero la migliore risposta qui
Gabriel

18

un altro modo è usare un file JS come un file proxye lasciare che quel file carichi il modulo di interesse commonjsed esportarlo come es2015 module, in questo modo:

// file: myModule.dev.js
module.exports = "this is in dev"

// file: myModule.prod.js
module.exports = "this is in prod"

// file: myModule.js
let loadedModule
if(WEBPACK_IS_DEVELOPMENT){
    loadedModule = require('./myModule.dev.js')
}else{
    loadedModule = require('./myModule.prod.js')
}

export const myString = loadedModule

Quindi puoi utilizzare normalmente il modulo ES2015 nella tua app:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"

19
L'unico problema con if / else e require è che entrambi i file richiesti verranno raggruppati nel file generato. Non ho trovato una soluzione alternativa. Essenzialmente si verifica prima il raggruppamento, poi il mutilamento.
alex

2
non è necessario vero, se usi nel tuo file webpack il plugin webpack.optimize.UglifyJsPlugin(), l'ottimizzazione di webpack non caricherà il modulo, poiché il codice di linea all'interno del condizionale è sempre falso, quindi webpack lo rimuoverà dal bundle generato
Alejandro Silva

@AlejandroSilva hai un esempio di repo di questo?
thevangelist

1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master/… it's a node + react + redux pet proyect: P
Alejandro Silva

4

Di fronte allo stesso problema dell'OP e richiesto, a causa della licenza, di non includere alcuni codici in alcune build, ho adottato il caricatore condizionale webpack come segue:

Nel mio comando build ho impostato una variabile d'ambiente in modo appropriato per la mia build. Ad esempio "demo" in package.json:

...
  "scripts": {
    ...
    "buildDemo": "./node_modules/.bin/webpack --config webpack.config/demo.js --env.demo --progress --colors",
...

Il bit di confusione che manca dalla documentazione che ho letto è che devo renderlo visibile durante l'elaborazione della build assicurandomi che la mia variabile env venga iniettata nel processo globale, quindi nel mio webpack.config / demo.js:

/* The demo includes project/reports action to access placeholder graphs.
This is achieved by using the webpack-conditional-loader process.env.demo === true
 */

const config = require('./production.js');
config.optimization = {...(config.optimization || {}), minimize: false};

module.exports = env => {
  process.env = {...(process.env || {}), ...env};
  return config};

Con questo in atto, posso escludere in modo condizionale qualsiasi cosa, assicurandomi che qualsiasi codice correlato sia correttamente scosso dal JavaScript risultante. Ad esempio, nel mio routes.js il contenuto demo è tenuto fuori da altre build, quindi:

...
// #if process.env.demo
import Reports from 'components/model/project/reports';
// #endif
...
const routeMap = [
  ...
  // #if process.env.demo
  {path: "/project/reports/:id", component: Reports},
  // #endif
...

Funziona con webpack 4.29.6.


1
C'è anche github.com/dearrrfish/preprocess-loader che ha più funzionalità
user9385381

1

Ho lottato con l'impostazione di env nelle mie configurazioni webpack. Quello che di solito voglio è quello di serie ENV in modo che possa essere raggiunto dentro webpack.config.js, postcss.config.jse dentro l'applicazione punto di ingresso in sé (index.js di solito). Spero che le mie scoperte possano aiutare qualcuno.

La soluzione che ho trovato è passare --env productiono --env development, quindi impostare la modalità all'interno webpack.config.js. Tuttavia, ciò non mi aiuta a rendere envaccessibile dove voglio (vedi sopra), quindi devo anche impostare process.env.NODE_ENVesplicitamente, come consigliato qui . La parte più rilevante che ho di webpack.config.jsseguito.

...
module.exports = mode => {
  process.env.NODE_ENV = mode;

  if (mode === "production") {
    return merge(commonConfig, productionConfig, { mode });
  }
  return merge(commonConfig, developmentConfig, { mode });
};


-1

Sebbene questa non sia la soluzione migliore, potrebbe funzionare per alcune delle tue esigenze. Se vuoi eseguire codice diverso nel nodo e nel browser usando questo ha funzionato per me:

if (typeof window !== 'undefined') 
    return
}
//run node only code now

1
OP sta chiedendo una decisione in fase di compilazione, la tua risposta è sul tempo di esecuzione.
Michael,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.