Construction conditionnelle basée sur l'environnement utilisant Webpack

93

J'ai des choses à développer - par exemple des moqueries avec lesquelles je ne voudrais pas gonfler mon fichier de construction distribué.

Dans RequireJS, vous pouvez passer une configuration dans un fichier de plugin et exiger conditionnellement des éléments en fonction de cela.

Pour webpack, il ne semble pas y avoir de moyen de le faire. Tout d' abord pour créer une configuration d'exécution pour un environnement que je l' ai utilisé resolve.alias à rejointoyer un besoin en fonction de l'environnement, par exemple:

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

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

Ensuite, lors de la création de la configuration Webpack, je peux attribuer dynamiquement le fichier envsettingsvers lequel pointe (ie webpackConfig.resolve.alias.envsettings = './' + env).

Cependant, j'aimerais faire quelque chose comme:

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

Mais évidemment, je ne veux pas créer ces fichiers simulés si l'environnement n'est pas simulé.

Je pourrais éventuellement rediriger manuellement tous ces éléments dans un fichier stub en utilisant à nouveau resolver.alias - mais y a-t-il un moyen qui semble moins piraté?

Des idées comment je peux faire ça? Merci.

Dominique
la source
Notez que pour l'instant, j'ai utilisé des alias pour pointer vers un fichier vide (stub) sur des environnements que je ne veux pas (par exemple, require ('mocks') pointera vers un fichier vide sur des envs non simulés. fonctionne.
Dominic

Réponses:

59

Vous pouvez utiliser le plugin define .

Je l'utilise en faisant quelque chose d'aussi simple que cela dans votre fichier de construction Webpack où se envtrouve le chemin vers un fichier qui exporte un objet de paramètres:

// 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 };

et puis ceci dans votre code

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

Il supprimera ce code de votre fichier de construction si la condition est fausse. Vous pouvez voir un exemple de construction Webpack fonctionnel ici .

Matt Derrick
la source
Je suis un peu confus par cette solution. Il ne mentionne pas comment je suis censé régler env. En regardant à travers cet exemple, il semble qu'ils gèrent cet indicateur via gulp et yargs que tout le monde n'utilise pas.
Andre
1
Comment cela fonctionne-t-il avec les linters? Devez-vous définir manuellement de nouvelles variables globales qui sont ajoutées dans le plugin Define?
marque
2
@marker oui. Ajoutez quelque chose comme "globals": { "ENV": true }à votre .eslintrc
Matt Derrick
comment accéder à la variable ENV dans un composant? J'ai essayé la solution ci-dessus mais j'obtiens toujours l'erreur qu'ENV n'est pas définie
jasan
18
Il ne supprime PAS le code des fichiers de construction! Je l'ai testé et le code est ici.
Lionel
42

Je ne sais pas pourquoi la réponse "webpack.DefinePlugin" est la meilleure partout pour définir les importations / exigences basées sur l'environnement.

Le problème avec cette approche est que vous fournissez toujours tous ces modules au client -> vérifiez avec webpack-bundle-analyezer par exemple. Et ne pas réduire du tout la taille de votre bundle.js :)

Donc, ce qui fonctionne vraiment bien et beaucoup plus logique est: NormalModuleReplacementPlugin

Donc, plutôt que de faire une demande conditionnelle on_client -> ne pas inclure les fichiers non nécessaires dans le bundle en premier lieu

J'espère que cela pourra aider

Roman Zhyliov
la source
Nice ne connaissait pas ce plugin!
Dominic
Avec ce scénario, n'auriez-vous pas plusieurs builds par environnement? Par exemple, si j'ai une adresse de service Web pour les environnements dev / QA / UAT / production, j'aurais alors besoin de 4 conteneurs séparés, 1 pour chaque environnement. Idéalement, vous auriez un conteneur et le lanceriez avec une variable d'environnement pour spécifier la configuration à charger.
Brett Mathe
Non, pas vraiment. C'est exactement ce que vous faites avec le plugin -> vous spécifiez votre environnement via env vars et il ne construit qu'un seul conteneur, mais pour un environnement particulier sans inclusions redondantes. Bien sûr, cela dépend également de la façon dont vous configurez votre configuration Webpack et vous pouvez évidemment créer toutes les versions, mais ce n'est pas ce que ce plugin est et fait.
Roman Zhyliov
33

Utilisez ifdef-loader. Dans vos fichiers source, vous pouvez faire des choses comme

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

Le pertinent webpack configuration est

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}`,
        },
      },
    ],
  },
  // ...
};
Peut Oakes
la source
2
J'ai voté pour cette réponse car la réponse acceptée ne supprime pas le code comme prévu et la syntaxe de type préprocesseur est plus susceptible d'être identifiée comme un élément conditionnel.
Christian Ivicevic
1
Merci beaucoup! Il fonctionne comme un charme. Plusieurs heures d'expériences avec ContextReplacementPlugin, NormalModuleReplacementPlugin et d'autres choses - tout a échoué. Et voici ifdef-loader, qui me sauve la journée.
jeron-diovis
27

J'ai fini par utiliser quelque chose de similaire à la réponse de Matt Derrick , mais j'étais préoccupé par deux points:

  1. La config complète est injectée à chaque fois que j'utilise ENV (ce qui est mauvais pour les grosses configs).
  2. Je dois définir plusieurs points d'entrée car require(env)pointe vers différents fichiers.

Ce que j'ai trouvé est un simple compositeur qui construit un objet de configuration et l'injecte dans un module de configuration.
Voici la structure de fichier que j'utilise pour cela:

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

Le main.jscontient tous les éléments de configuration par défaut:

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

module.exports = mainConfig;

Le seul dev.jset production.jsunique contenu de configuration qui remplace la configuration principale:

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

module.exports = devConfig;

La partie importante est celle webpack.config.jsqui compose la configuration et utilise le DefinePlugin pour générer une variable d'environnement __APP_CONFIG__qui contient l'objet de configuration composé:

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))
    })
  ]
};

La dernière étape est maintenant la config.js, elle ressemble à ceci (en utilisant la syntaxe d'import export es6 ici car elle est sous le pack web):

const config = __APP_CONFIG__;

export default config;

Dans votre, app.jsvous pouvez maintenant utiliser import config from './config';pour obtenir l'objet de configuration.

De la maison
la source
2
Vraiment la meilleure réponse ici
Gabriel
18

une autre façon consiste à utiliser un fichier JS en tant que fichier proxy, et à laisser ce fichier charger le module qui vous intéresse commonjset à l'exporter en tant que es2015 module, comme ceci:

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

Ensuite, vous pouvez utiliser normalement le module ES2015 dans votre application:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"
Alejandro Silva
la source
19
Le seul problème avec if / else et require est que les deux fichiers requis seront regroupés dans le fichier généré. Je n'ai pas trouvé de solution de contournement. Essentiellement, le regroupement se produit en premier, puis le mutilation.
alex le
2
ce n'est pas forcément vrai, si vous utilisez dans votre fichier webpack le plugin webpack.optimize.UglifyJsPlugin(), l'optimisation de webpack ne chargera pas le module, car le code de ligne à l'intérieur du conditionnel est toujours faux, donc webpack supprimez-le du bundle généré
Alejandro Silva
@AlejandroSilva en avez-vous un exemple de repo?
thevangelist
1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master / ... c'est un nœud + react + redux pet prooyect: P
Alejandro Silva
4

Face au même problème que l'OP et obligé, en raison de la licence, de ne pas inclure certains codes dans certaines builds, j'ai adopté le webpack-conditionitional-loader comme suit:

Dans ma commande de construction, j'ai défini une variable d'environnement appropriée pour ma construction. Par exemple 'demo' dans package.json:

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

Le peu déroutant qui manque dans la documentation que j'ai lue est que je dois rendre cela visible tout au long du processus de construction en m'assurant que ma variable env est injectée dans le processus global ainsi dans mon 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};

Avec cela en place, je peux conditionnellement exclure quoi que ce soit, en m'assurant que tout code associé est correctement secoué du JavaScript résultant. Par exemple, dans mes routes.js, le contenu de la démonstration est conservé hors des autres versions, ainsi:

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

Cela fonctionne avec webpack 4.29.6.

Paul Whipp
la source
1
Il y a aussi github.com/dearrrfish/preprocess-loader qui a plus de fonctionnalités
user9385381
1

J'ai eu du mal à définir env dans mes configurations Webpack. Ce que je veux habituellement est de mettre env afin qu'il puisse être atteinte à l' intérieur webpack.config.js, postcss.config.jset à l' intérieur de l'application de point d'entrée lui - même (index.js en général). J'espère que mes découvertes pourront aider quelqu'un.

La solution que j'ai trouvée est de passer dans --env productionou --env development, puis de définir le mode à l'intérieur webpack.config.js. Cependant, cela ne m'aide pas à rendre envaccessible où je le souhaite (voir ci-dessus), donc je dois également définir process.env.NODE_ENVexplicitement, comme recommandé ici . La partie la plus pertinente que j'ai webpack.config.jsci-dessous.

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

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

Utilisez des variables d'environnement pour créer des déploiements de développement et de production:

https://webpack.js.org/guides/environment-variables/

Simon H
la source
2
Ce n'est pas ce que je demandais
Dominic
le problème est que webpack ignorera la condition lors de la construction du bundle et inclura de toute façon le code chargé pour le développement ... donc cela ne résout pas le problème
sergioviniciuss
-1

Bien que ce ne soit pas la meilleure solution, cela peut répondre à certains de vos besoins. Si vous souhaitez exécuter un code différent dans le nœud et le navigateur, cela a fonctionné pour moi:

if (typeof window !== 'undefined') 
    return
}
//run node only code now
Esqarrouth
la source
1
OP demande une décision concernant le temps de compilation, votre réponse concerne le temps d'exécution.
Michael