Warunkowa kompilacja oparta na środowisku wykorzystującym Webpack

95

Mam kilka rzeczy do rozwoju - np. Makiety, którymi nie chciałbym nadużywać mojego dystrybuowanego pliku kompilacji.

W RequireJS możesz przekazać konfigurację w pliku wtyczki i warunkowo wymagać jej wprowadzenia.

Wydaje się, że w przypadku pakietu webpack nie ma sposobu, aby to zrobić. Po pierwsze, aby utworzyć konfigurację środowiska uruchomieniowego dla środowiska, użyłem solution.alias do zmiany wymagania w zależności od środowiska, np .:

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

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

Następnie podczas tworzenia konfiguracji webpacka mogę dynamicznie przypisać, który plik envsettingswskazuje (tj webpackConfig.resolve.alias.envsettings = './' + env.).

Chciałbym jednak zrobić coś takiego:

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

Ale oczywiście nie chcę budować w tych pozorowanych plikach, jeśli środowisko nie jest fałszywe.

Mógłbym prawdopodobnie ręcznie zmienić wszystkie te wymagania w pliku pośredniczącym, używając ponownie funkcji solution.alias - ale czy istnieje sposób, który wydaje się mniej hakerski?

Jakieś pomysły, jak mogę to zrobić? Dzięki.

Dominic
źródło
Zauważ, że na razie użyłem aliasów do wskazania pustego (zastępczego) pliku w środowiskach, których nie chcę (np. Require ('mocks') wskaże na pusty plik na non-mock envs. działa
Dominic

Odpowiedzi:

60

Możesz użyć wtyczki Definiuj .

Używam go, robiąc coś tak prostego, jak to w pliku kompilacji pakietu internetowego, gdzie envjest ścieżka do pliku, który eksportuje obiekt ustawień:

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

a potem to w swoim kodzie

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

Usunie ten kod z pliku kompilacji, jeśli warunek jest fałszywy. Tutaj możesz zobaczyć działający przykład kompilacji pakietu Webpack .

Matt Derrick
źródło
Jestem trochę zdezorientowany tym rozwiązaniem. Nie wspomina, jak mam ustawić env. Patrząc na ten przykład, wydaje się, że obsługują tę flagę za pomocą łyków i yargów, których nie wszyscy używają.
Andre
1
Jak to działa z linterem? Czy musisz ręcznie definiować nowe zmienne globalne, które są dodawane we wtyczce Define?
znak
2
@mark tak. Dodaj coś takiego "globals": { "ENV": true }do swojego .eslintrc
Matt Derrick
jak uzyskać dostęp do zmiennej ENV w komponencie? Wypróbowałem powyższe rozwiązanie, ale nadal pojawia się błąd, że ENV nie jest zdefiniowane
jasan
20
NIE usuwa kodu z plików kompilacji! Przetestowałem to i kod jest tutaj.
Lionel
42

Nie jestem pewien, dlaczego odpowiedź „webpack.DefinePlugin” jest najważniejsza wszędzie przy definiowaniu importu / wymagań opartych na środowisku.

Problem z tego podejścia jest to, że są nadal dostarczając wszystkie te moduły do klienta -> sprawdzić z WebPACK-bundle-analyezer na przykład. I wcale nie zmniejszając rozmiaru pliku bundle.js :)

Więc to, co naprawdę działa dobrze i znacznie bardziej logiczne, to: NormalModuleReplacementPlugin

Więc zamiast wykonywać warunkowe wymaganie on_client -> po prostu nie dołączaj niepotrzebnych plików do pakietu w pierwszej kolejności

Mam nadzieję, że to pomoże

Roman Zhyliov
źródło
Miło nie wiedział o tej wtyczce!
Dominic
Czy w tym scenariuszu nie byłoby wielu kompilacji na środowisko? Na przykład, jeśli mam adres usługi internetowej dla środowisk dev / QA / UAT / produkcyjnych, potrzebowałbym 4 oddzielnych kontenerów, po 1 dla każdego środowiska. Idealnie byłoby mieć jeden kontener i uruchomić go ze zmienną środowiskową, aby określić, którą konfigurację załadować.
Brett Mathe,
Nie, nie bardzo. To jest dokładnie to, co robisz z wtyczką -> określasz swoje środowisko za pomocą zmiennych env i buduje tylko jeden kontener, ale dla konkretnego środowiska bez zbędnych inkluzji. Oczywiście zależy to również od tego, jak skonfigurujesz konfigurację pakietu internetowego i oczywiście możesz zbudować wszystkie kompilacje, ale nie o to chodzi i robi ta wtyczka.
Roman Zhyliov,
34

Użyj ifdef-loader. W swoich plikach źródłowych możesz robić takie rzeczy jak

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

Odpowiednia webpackkonfiguracja to

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}`,
        },
      },
    ],
  },
  // ...
};
Majowe Oakes
źródło
3
Głosowałem za tą odpowiedzią, ponieważ zaakceptowana odpowiedź nie usuwa kodu zgodnie z oczekiwaniami, a składnia podobna do preprocesora jest bardziej prawdopodobne, że zostanie zidentyfikowana jako element warunkowy.
Christian Ivicevic
1
Dzięki wielkie! To działa jak urok. Kilka godzin eksperymentów z ContextReplacementPlugin, NormalModuleReplacementPlugin i innymi rzeczami - wszystkie zakończyły się niepowodzeniem. A oto program ładujący ifdef, ratujący mój dzień.
jeron-diovis
28

Skończyło się na tym, że użyłem czegoś podobnego do odpowiedzi Matta Derricka , ale martwiłem się o dwa punkty:

  1. Pełna konfiguracja jest wstrzykiwana za każdym razem, gdy używam ENV(co jest złe w przypadku dużych konfiguracji).
  2. Muszę zdefiniować wiele punktów wejścia, ponieważ require(env)wskazuje na różne pliki.

To, co wymyśliłem, to prosty kompozytor, który buduje obiekt konfiguracyjny i wstrzykuje go do modułu konfiguracyjnego.
Oto struktura plików, której używam do tego:

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

main.jsPosiada wszystkie Domyślne rzeczy:

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

module.exports = mainConfig;

dev.jsI production.jstylko chwyt config rzeczy, które nadpisuje główny config:

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

module.exports = devConfig;

Ważną częścią jest ta, webpack.config.jsktóra komponuje konfigurację i używa DefinePlugin do wygenerowania zmiennej środowiskowej, __APP_CONFIG__która przechowuje złożony obiekt konfiguracyjny:

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

Ostatnim krokiem jest teraz config.js, wygląda to tak (Używając tutaj składni importu eksportu es6, ponieważ znajduje się w pakiecie internetowym):

const config = __APP_CONFIG__;

export default config;

W swoim app.jsmożesz teraz użyć, import config from './config';aby pobrać obiekt konfiguracyjny.

ofhouse
źródło
2
Naprawdę najlepsza odpowiedź tutaj
Gabriel
18

innym sposobem jest użycie pliku JS jako a proxyi pozwolenie temu plikowi załadować moduł, który nas interesuje commonjs, i wyeksportować go jako es2015 module, na przykład:

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

Następnie możesz normalnie używać modułu ES2015 w swojej aplikacji:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"
Alejandro Silva
źródło
19
Jedynym problemem związanym z if / else i require jest to, że oba wymagane pliki zostaną dołączone do wygenerowanego pliku. Nie znalazłem obejścia. Zasadniczo pakowanie odbywa się najpierw, a potem zniekształcanie.
alex
2
to nie jest konieczne prawda, jeśli użyjesz wtyczki w swoim pliku webpack, webpack.optimize.UglifyJsPlugin()optymalizacja webpacka nie załaduje modułu, ponieważ kod linii wewnątrz warunku jest zawsze fałszywy, więc webpack usuwa go z wygenerowanego pakietu
Alejandro Silva
@AlejandroSilva czy masz przykład repozytorium tego?
Kapucyn
1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master/… to węzeł + reaguj + redux pet proyect: P
Alejandro Silva
4

W obliczu tego samego problemu co OP i wymaganego, ze względu na licencję, aby nie włączać określonego kodu w niektórych kompilacjach, zaadaptowałem program ładujący pakiet warunkowy w następujący sposób:

W poleceniu kompilacji ustawiam zmienną środowiskową odpowiednio do mojej kompilacji. Na przykład „demo” w pliku package.json:

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

Mylący fragment, którego brakuje w dokumentacji, którą czytam, polega na tym , że muszę pokazać to podczas przetwarzania kompilacji , upewniając się, że moja zmienna env zostanie wstrzyknięta do procesu globalnego, a zatem w moim 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};

Mając to na swoim miejscu, mogę warunkowo wykluczyć wszystko, upewniając się, że każdy powiązany kod zostanie odpowiednio usunięty z wynikowego kodu JavaScript. Na przykład w moim pliku route.js zawartość demonstracyjna jest chroniona przed innymi kompilacjami, w ten sposób:

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

Działa to z pakietem webpack 4.29.6.

Paul Whipp
źródło
1
Istnieje również github.com/dearrrfish/preprocess-loader, który ma więcej funkcji
user9385381
1

Zmagałem się z ustawieniem env w moich konfiguracjach webpack. Co ja zwykle chcą, aby ustawić env jest tak, że może on zostać osiągnięty w środku webpack.config.js, postcss.config.jsjak i wewnątrz samej aplikacji punktu wejścia ( index.jszwykle). Mam nadzieję, że moje odkrycia mogą komuś pomóc.

Rozwiązaniem, które wymyśliłem, jest przekazanie --env productionlub --env development, a następnie ustawienie trybu wewnątrz webpack.config.js. Jednak to nie pomaga mi w envudostępnianiu tego, gdzie chcę (patrz powyżej), więc muszę również ustawić process.env.NODE_ENVjawnie, zgodnie z zaleceniami tutaj . Najważniejsza część, którą mam webpack.config.jsponiżej, znajduje się poniżej.

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

  if (mode === "production") {
    return merge(commonConfig, productionConfig, { mode });
  }
  return merge(commonConfig, developmentConfig, { mode });
};
Maks
źródło
0

Użyj zmiennych środowiskowych, aby utworzyć wdrożenia deweloperskie i produkcyjne:

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

Simon H.
źródło
2
Nie o to pytałem
Dominic
problem polega na tym, że webpack zignoruje warunek podczas budowania pakietu i i tak będzie zawierał kod załadowany do programowania ... więc nie rozwiązuje problemu
sergioviniciuss
-1

Chociaż nie jest to najlepsze rozwiązanie, może zadziałać w przypadku niektórych Twoich potrzeb. Jeśli chcesz uruchomić inny kod w węźle i przeglądarce, to zadziałało dla mnie:

if (typeof window !== 'undefined') 
    return
}
//run node only code now
Esqarrouth
źródło
1
OP pyta o decyzję dotyczącą czasu kompilacji, Twoja odpowiedź dotyczy czasu wykonywania.
Michael