Bedingter Build basierend auf der Umgebung mit Webpack

93

Ich habe einige Dinge für die Entwicklung - zB Mocks, mit denen ich meine verteilte Build-Datei nicht aufblähen möchte.

In RequireJS können Sie eine Konfiguration in einer Plugin-Datei übergeben und bedingen, dass darauf basierend Dinge erforderlich sind.

Für Webpack scheint es keine Möglichkeit zu geben, dies zu tun. Erstens, um eine Laufzeitkonfiguration für eine Umgebung zu erstellen, habe ich resolve.alias verwendet , um eine Anforderung abhängig von der Umgebung neu zu vergeben, z.

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

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

Dann kann ich beim Erstellen der Webpack-Konfiguration dynamisch zuweisen, auf welche Dateipunkte envsettings(dh webpackConfig.resolve.alias.envsettings = './' + env).

Ich möchte jedoch etwas tun wie:

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

Aber natürlich möchte ich diese Mock-Dateien nicht einbauen, wenn die Umgebung nicht Mock ist.

Ich könnte möglicherweise alle erforderlichen Anforderungen mithilfe von resolve.alias erneut manuell in eine Stub-Datei umwandeln - aber gibt es einen Weg, der sich weniger hackig anfühlt?

Irgendwelche Ideen, wie ich das machen kann? Vielen Dank.

Dominic
quelle
Beachten Sie, dass ich im Moment Alias ​​verwendet habe, um auf eine leere (Stub-) Datei in Umgebungen zu verweisen, die ich nicht möchte (z. B. erfordern ('Mocks')) auf eine leere Datei in nicht gespielten Envs verweist. Scheint ein wenig hackig, aber es funktioniert.
Dominic

Antworten:

59

Sie können das Define-Plugin verwenden .

Ich verwende es, indem ich etwas so Einfaches wie das in Ihrer Webpack-Build-Datei mache, wobei envder Pfad zu einer Datei ist, die ein Objekt mit Einstellungen exportiert:

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

und dann das in deinem Code

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

Dieser Code wird aus Ihrer Build-Datei entfernt, wenn die Bedingung falsch ist. Hier sehen Sie ein funktionierendes Webpack-Build-Beispiel .

Matt Derrick
quelle
Ich bin ein wenig verwirrt von dieser Lösung. Es wird nicht erwähnt, wie ich einstellen soll env. Wenn man sich dieses Beispiel ansieht, scheint es, als würden sie diese Flagge über Schluck und Yargs handhaben, die nicht jeder benutzt.
Andre
1
Wie funktioniert das mit Lintern? Müssen Sie manuell neue globale Variablen definieren, die im Plugin "Definieren" hinzugefügt werden?
Markieren Sie
2
@mark ja. Fügen Sie etwas wie "globals": { "ENV": true }zu Ihrer .eslintrc hinzu
Matt Derrick
Wie würde ich auf die ENV-Variable in einer Komponente zugreifen? Ich habe die obige Lösung ausprobiert, aber ich erhalte immer noch den Fehler, dass ENV nicht definiert ist
Jason
16
Der Code wird NICHT aus den Build-Dateien entfernt! Ich habe es getestet und der Code ist hier.
Lionel
41

Ich bin mir nicht sicher, warum die Antwort "webpack.DefinePlugin" überall die beste ist, um umweltbasierte Importe / Anforderungen zu definieren.

Das Problem bei diesem Ansatz ist, dass Sie immer noch alle diese Module an den Client liefern -> überprüfen Sie dies beispielsweise mit dem Webpack-Bundle-Analyzer . Und die Größe von bundle.js überhaupt nicht reduzieren :)

Was also wirklich gut und viel logischer funktioniert, ist: NormalModuleReplacementPlugin

Anstatt eine on_client-Bedingung zu erfüllen, müssen Sie nicht benötigte Dateien in das Bundle aufnehmen

hoffentlich hilft das

Roman Zhyliov
quelle
Nice wusste nichts über dieses Plugin!
Dominic
Hätten Sie in diesem Szenario nicht mehrere Builds pro Umgebung? Wenn ich beispielsweise eine Webdienstadresse für Entwicklungs- / QA- / UAT- / Produktionsumgebungen habe, würde ich 4 separate Container benötigen, einen für jede Umgebung. Idealerweise haben Sie einen Container und starten ihn mit einer Umgebungsvariablen, um anzugeben, welche Konfiguration geladen werden soll.
Brett Mathe
Nein nicht wirklich. Genau das machen Sie mit dem Plugin -> Sie geben Ihre Umgebung über env vars an und es wird nur ein Container erstellt, jedoch für eine bestimmte Umgebung ohne redundante Einschlüsse. Natürlich hängt das auch davon ab, wie Sie Ihre Webpack-Konfiguration einrichten und natürlich können Sie alle Builds erstellen, aber es ist nicht das, worum es in diesem Plugin geht und was es tut.
Roman Zhyliov
32

Verwenden Sie ifdef-loader. In Ihren Quelldateien können Sie Dinge wie tun

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

Die entsprechende webpackKonfiguration ist

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}`,
        },
      },
    ],
  },
  // ...
};
Mai Oakes
quelle
2
Ich habe diese Antwort positiv bewertet, da die akzeptierte Antwort den Code nicht wie erwartet entfernt und die präprozessorähnliche Syntax eher als bedingtes Element identifiziert wird.
Christian Ivicevic
1
Vielen Dank! Es wirkt wie ein Zauber. Mehrere Stunden Experimente mit ContextReplacementPlugin, NormalModuleReplacementPlugin und anderen Dingen sind fehlgeschlagen. Und hier ist der ifdef-Lader, der meinen Tag rettet.
Jeron-Diovis
26

Am Ende habe ich etwas Ähnliches wie Matt Derricks Antwort verwendet , war aber über zwei Punkte besorgt:

  1. Die komplette Konfiguration wird bei jeder Verwendung ENVeingefügt (was für große Konfigurationen schlecht ist).
  2. Ich muss mehrere Einstiegspunkte definieren, da require(env)auf verschiedene Dateien verweisen.

Was ich mir ausgedacht habe, ist ein einfacher Komponist, der ein Konfigurationsobjekt erstellt und in ein Konfigurationsmodul einfügt.
Hier ist die Dateistruktur, die ich dafür verwende:

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

Das main.jsenthält alle Standard-Konfigurationsmaterialien:

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

module.exports = mainConfig;

Das dev.jsund production.jseinzige Konfigurationsmaterial, das die Hauptkonfiguration überschreibt:

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

module.exports = devConfig;

Der wichtige Teil ist der, webpack.config.jsder die Konfiguration erstellt und mit dem DefinePlugin eine Umgebungsvariable generiert, __APP_CONFIG__die das zusammengesetzte Konfigurationsobjekt enthält:

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

Der letzte Schritt ist jetzt der config.js, es sieht so aus (Verwenden der ES6-Import-Export-Syntax hier, da es sich unter dem Webpack befindet):

const config = __APP_CONFIG__;

export default config;

In Ihrem können app.jsSie jetzt import config from './config';das Konfigurationsobjekt abrufen.

ofhouse
quelle
2
Wirklich die beste Antwort hier
Gabriel
18

Eine andere Möglichkeit besteht darin, eine JS-Datei als zu verwenden proxyund diese Datei das gewünschte Modul laden zu lassen commonjsund es wie folgt zu exportieren es2015 module:

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

Dann können Sie das ES2015-Modul normalerweise in Ihrer App verwenden:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"
Alejandro Silva
quelle
19
Das einzige Problem mit if / else und require ist, dass beide erforderlichen Dateien in der generierten Datei gebündelt werden. Ich habe keine Problemumgehung gefunden. Im Wesentlichen erfolgt die Bündelung zuerst, dann die Entflechtung.
alex
2
Das ist nicht unbedingt wahr. Wenn Sie das Plugin in Ihrer Webpack-Datei verwenden webpack.optimize.UglifyJsPlugin(), wird das Modul durch die Optimierung des Webpacks nicht geladen, da der Zeilencode in der Bedingung immer falsch ist. Entfernen Sie ihn daher aus dem generierten Bundle
Alejandro Silva,
@AlejandroSilva hast du ein Repo-Beispiel dafür?
Thevangelist
1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master/… es ist ein Knoten + reagieren + Redux Haustier Proyect: P
Alejandro Silva
3

Angesichts des gleichen Problems wie das OP und aufgrund der Lizenzierung, bestimmten Code nicht in bestimmte Builds aufzunehmen, habe ich den Webpack-Conditional-Loader wie folgt übernommen:

In meinem Build-Befehl habe ich eine Umgebungsvariable festgelegt, die für meinen Build geeignet ist. Zum Beispiel 'Demo' in package.json:

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

Das verwirrende Bit, das in der von mir gelesenen Dokumentation fehlt, ist, dass ich dies während der gesamten Build-Verarbeitung sichtbar machen muss, indem ich sicherstelle, dass meine env-Variable in den globalen Prozess eingefügt wird, also in meiner 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};

Wenn dies vorhanden ist, kann ich alles bedingt ausschließen und sicherstellen, dass der zugehörige Code ordnungsgemäß aus dem resultierenden JavaScript herausgeschüttelt wird. Zum Beispiel wird in meiner route.js der Demo-Inhalt von anderen Builds ferngehalten, also:

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

Dies funktioniert mit Webpack 4.29.6.

Paul Whipp
quelle
1
Es gibt auch github.com/dearrrfish/preprocess-loader, das mehr Funktionen hat
user9385381
1

Ich habe Probleme damit, env in meinen Webpack-Konfigurationen festzulegen. Was ich in der Regel will , ist Satz env , so dass es im Innern erreicht werden kann webpack.config.js, postcss.config.jsund in dem Einstiegspunkt Anwendung selbst ( index.jsin der Regel). Ich hoffe, dass meine Erkenntnisse jemandem helfen können.

Die Lösung, die ich mir ausgedacht habe, besteht darin, --env productionoder zu übergeben --env developmentund dann den Modus im Inneren einzustellen webpack.config.js. Dies hilft mir jedoch nicht dabei, envzugänglich zu machen, wo ich es möchte (siehe oben), daher muss ich auch process.env.NODE_ENVexplizit festlegen , wie hier empfohlen . Der relevanteste Teil, den ich im webpack.config.jsFolgenden habe, folgt.

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

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

Verwenden Sie Umgebungsvariablen, um Entwicklungs- und Produktbereitstellungen zu erstellen:

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

Simon H.
quelle
2
Dies ist nicht das, was ich gefragt habe
Dominic
Das Problem ist, dass Webpack die Bedingung beim Erstellen des Bundles ignoriert und sowieso Code enthält, der für die Entwicklung geladen wurde ... also löst es das Problem nicht
sergioviniciuss
-1

Dies ist zwar nicht die beste Lösung, kann jedoch für einige Ihrer Anforderungen geeignet sein. Wenn Sie unterschiedlichen Code in Knoten und Browser ausführen möchten, funktioniert dies für mich:

if (typeof window !== 'undefined') 
    return
}
//run node only code now
Esqarrouth
quelle
1
OP fragt nach einer Entscheidung zur Kompilierungszeit. Ihre Antwort bezieht sich auf die Laufzeit.
Michael