Умовна побудова на основі середовища за допомогою Webpack


93

У мене є кілька речей для розробки - наприклад, макети, якими я хотів би не здути мій розподілений файл збірки.

У RequireJS ви можете передати конфігурацію у файлі плагіна і на основі цього вимагати умови.

Для webpack, здається, не існує способу зробити це. По-перше, для створення конфігурації середовища виконання для середовища я використовував resol.alias для переназначення вимоги залежно від середовища, наприклад:

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

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

Тоді під час створення конфігурації веб-пакета я можу динамічно призначати, на який файл envsettingsвказує (тобто webpackConfig.resolve.alias.envsettings = './' + env).

Однак я хотів би зробити щось на зразок:

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

Але, очевидно, я не хочу вбудовувати ці макетні файли, якщо середовище не є макетним.

Можливо, я міг би вручну переназначити всі необхідні файли на файл заглушки за допомогою resol.alias знову - але чи є спосіб, який відчуває себе менш хакі?

Будь-які ідеї, як я можу це зробити? Дякую.


Зауважте, що наразі я використовував псевдоніми для вказівки на порожній (заглушений) файл у середовищах, які я не хочу (наприклад, require ('mocks') вказуватиме на порожній файл у непрофільних середовищах. Здається, це трохи невдало, але це працює.
Домінік

Відповіді:


59

Ви можете використовувати плагін define .

Я використовую це, роблячи щось таке просте, як це у вашому файлі збірки webpack, де envє шлях до файлу, який експортує об’єкт налаштувань:

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

а потім це у вашому коді

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

Він вилучить цей код із вашого файлу збірки, якщо умова хибна. Ви можете побачити робочий приклад збірки Webpack тут .


Я трохи розгублений цим рішенням. У ньому не згадується, як я маю встановити env. Переглядаючи цей приклад, здається, ніби вони обробляють цей прапор залпом і яргами, якими користуються не всі.
Андре

1
Як це працює з лінтерами? Чи потрібно вручну визначати нові глобальні змінні, які додаються до плагіна Визначити?
позначка

2
@mark так. Додайте щось на зразок "globals": { "ENV": true }свого .eslintrc
Метт Деррік,

як отримати доступ до змінної ENV у компоненті? Я спробував рішення вище, але все одно отримую помилку, що ENV не визначено
jasan

18
Він НЕ вилучає код із файлів збірки! Я тестував, і код тут.
Лайонел

42

Не впевнені, чому відповідь "webpack.DefinePlugin" є найпопулярнішою у всьому світі для визначення імпорту / необхідності на основі середовища.

Проблема з цим підходом є те , що ви по - , як і раніше поставляти всі ці модулі для клієнта -> перевірити з WebPack-розшарування-analyezer , наприклад. І зовсім не зменшуючи розмір вашого bundle.js :)

Отже, що насправді працює добре і набагато логічніше, це: NormalModuleReplacementPlugin

Тож замість того, щоб робити умовне вимагання on_client -> просто не включати непотрібні файли до набору спочатку

Сподіваюся, це допомагає


Ніцца не знав про цей плагін!
Домінік

Чи не буде у вас за цим сценарієм кілька збірок на одне середовище? Наприклад, якщо у мене є адреса веб-служби для середовищ dev / QA / UAT / production, мені тоді знадобляться 4 окремі контейнери, по 1 для кожного середовища. В ідеалі ви б мали один контейнер і запускали його зі змінною середовища, щоб вказати, яку конфігурацію завантажувати.
Brett Mathe

Ні, не дуже. Це саме те, що ви робите з плагіном -> ви вказуєте своє середовище за допомогою env vars, і він створює лише один контейнер, але для конкретного середовища без зайвих включень. Звичайно, це також залежить від того, як ви налаштовуєте конфігурацію веб-пакета, і, очевидно, ви можете збирати всі збірки, але це не те, про що цей плагін і робить.
Роман Жильов

34

Використовуйте ifdef-loader. У вихідних файлах ви можете робити подібні речі

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

Відповідна webpackконфігурація

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
Я підтримав цю відповідь, оскільки прийнята відповідь не вилучає код, як очікувалося, і синтаксис, подібний до препроцесора, швидше за все буде визначений як умовний елемент.
Крістіан Івічевич

1
Дуже дякую! Це працює як шарм. Кілька годин експериментів з ContextReplacementPlugin, NormalModuleReplacementPlugin та іншими речами - все не вдалося. І ось ifdef-loader, що рятує мій день.
jeron-diovis

27

У підсумку я використав щось подібне до відповіді Метта Дерріка , але мене хвилювали два моменти:

  1. Повна конфігурація вводиться кожного разу, коли я використовую ENV (що погано для великих ).
  2. Мені потрібно визначити кілька точок входу, оскільки require(env)вказує на різні файли.

Що я придумав, це простий композитор, який створює об'єкт конфігурації та вводить його в модуль конфігурації.
Ось структура файлів, яку Iam використовує для цього:

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

main.jsМістить всі зміни по замовчуванням речі:

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

module.exports = mainConfig;

dev.jsіproduction.js тільки тримати конфігураційний матеріал , який перекриває основну конфігурацію:

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

module.exports = devConfig;

Важливою частиною є те, webpack.config.jsщо складає конфігурацію та використовує DefinePlugin для генерації змінної середовища__APP_CONFIG__ що містить складений конфігураційний об'єкт:

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

Останній крок - це config.js, це виглядає так (Використовуючи синтаксис експорту імпорту es6 тут, оскільки він знаходиться під веб-пакетом):

const config = __APP_CONFIG__;

export default config;

У Вашому app.jsви можете тепер використовувати , import config from './config';щоб отримати об'єкт конфігурації.


2
Дійсно найкраща відповідь тут
Габріель

18

інший спосіб - використовувати файл JS як файл proxy, і дозволити цьому файлу завантажити цікавий модуль commonjsта експортувати його як 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

Тоді ви можете зазвичай використовувати модуль ES2015 у своєму додатку:

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

19
Єдина проблема з if / else і require полягає в тому, що обидва необхідні файли будуть згруповані у згенерований файл. Я не знайшов обхідного шляху. По суті, спочатку відбувається зв’язування, а потім перекручування.
alex

2
це не обов'язково, якщо ви використовуєте у своєму файлі веб-пакета плагін webpack.optimize.UglifyJsPlugin(), оптимізація веб-пакета не завантажить модуль, оскільки код рядка всередині умовного значення завжди хибний, тому веб-пакет видаляє його із згенерованого пакету
Алехандро Сільва,

@AlejandroSilva, чи є у вас репо-приклад цього?
євангеліст

1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master/… це вузол + реакція + зменшення домашніх тварин проекту: P
Alejandro Silva

4

Зіткнувшись з тією ж проблемою, що і OP, і через ліцензування не потрібно включати певний код у певні збірки, я застосував webpack-conditional-loader наступним чином:

У моїй команді build я встановив змінну середовища відповідно до моєї збірки. Наприклад, `` демо '' в package.json:

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

Заплутаний біт, якого не вистачає в прочитаній документації, полягає в тому, що я повинен зробити це видимим під час обробки збірки , забезпечивши, щоб моя змінна env потрапляла в глобальний процес, таким чином, у моєму 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};

Завдяки цьому я можу умовно виключити будь-що, переконавшись, що будь-який відповідний код належним чином витрушений з отриманого JavaScript. Наприклад, у моєму route.js демонстраційний вміст утримується від інших збірок, таким чином:

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

Це працює з webpack 4.29.6.


1
Існує також github.com/dearrrfish/preprocess-loader, який має більше можливостей
user9385381

1

Я намагався встановити env у конфігураціях мого веб-пакета. Те , що я зазвичай хочу, щоб встановити окр так , що вона може бути досягнута всередині webpack.config.js, так postcss.config.jsі всередині самої точки входу додатки (index.js зазвичай). Я сподіваюся, що мої висновки можуть комусь допомогти.

Рішення, яке я придумав, - це перейти в --env productionабо --env development, а потім встановити режим всередині webpack.config.js. Однак це не допомагає мені зробити envдоступним там, де я хочу (див. Вище), тому мені також потрібно встановити process.env.NODE_ENVявно, як рекомендується тут . Найбільш відповідна частина, яку я маю, webpack.config.jsдотримується нижче.

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

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

0

Використовуйте змінні середовища для створення розгортань розробників і розробників:

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


2
Це не те, про що я просив
Домінік,

проблема полягає в тому, що webpack буде ігнорувати умову під час створення пакета і включатиме код, завантажений для розробки в будь-якому випадку ... так що це не вирішує проблему
sergioviniciuss

-1

Хоча це не найкраще рішення, воно може відповідати вашим потребам. Якщо ви хочете запустити інший код у вузлі та браузері, використовуючи це для мене:

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

1
OP запитує про рішення щодо часу компіляції, ваша відповідь - про час виконання.
Майкл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.