Условная сборка на основе среды с использованием Webpack

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

В RequireJS вы можете передать конфиг в файле плагина и условно требовать что-то на его основе.

Для веб-пакета, кажется, нет способа сделать это. Во-первых, для создания конфигурации среды выполнения для среды я использовал 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 - но есть ли способ, который кажется менее хакерским?

Есть идеи, как я могу это сделать? Благодарю.

10 ответов

Решение

Вы можете использовать плагин определения.

Я использую это, делая что-то такое же простое в вашем файле сборки веб-пакета, где 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 здесь.

Не уверен, почему ответ "webpack.DefinePlugin" является лучшим для определения импорта / требований на основе среды.

Проблема с этим подходом состоит в том, что вы все еще доставляете все эти модули клиенту -> проверьте, например, с помощью https://www.npmjs.com/package/webpack-bundle-analyzer. И ни в коем случае не уменьшать размер вашего bundle.js:)

Итак, что действительно работает хорошо и намного более логично: NormalModuleReplacementPlugin

Поэтому вместо условного требования on_client -> просто не включайте ненужные файлы в комплект.

надеюсь, это поможет

Использование 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}`,
        },
      },
    ],
  },
  // ...
};

В итоге я использовал что-то похожее на "Ответ Мэтта Деррика", но беспокоился о двух моментах:

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

Я придумал простой композитор, который создает объект конфигурации и внедряет его в модуль конфигурации.
Вот структура файла, которую я использую для этого:

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 здесь, потому что он находится в webpack):

const config = __APP_CONFIG__;

export default config;

В вашем app.js теперь вы можете использовать import config from './config'; чтобы получить объект конфигурации.

Другой способ использует файл 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"

Столкнувшись с той же проблемой, что и OP, и из-за необходимости лицензирования не включать определенный код в определенные сборки, я применил https://www.npmjs.com/package/webpack-conditional-loader следующим образом:

В моей команде сборки я установил переменную окружения для своей сборки. Например, 'demo' в 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. Например, в моем rout.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
...

Это работает с веб-пакетом 4.29.6.

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

Решение, которое я нашел, состоит в том, чтобы передать --env production или же -- env development, а затем установить режим внутри webpack.config.js, Тем не менее, это не помогает мне с env доступно там, где я хочу (см. выше), поэтому мне также нужно установить process.env.NODE_ENV явно, как рекомендуется здесь. Я создал минимальный репо с филиалами working а также not_working, Как можно подтвердить в not_working, с помощью webpack.DefinePlugin установить 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 });
};

Хотя это не лучшее решение, оно может работать для некоторых ваших потребностей. Если вы хотите запустить другой код на узле и в браузере, используйте это для меня:

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

я использую string-replace-loaderизбавиться от ненужного импорта из продакшн-сборки, и работает как положено: размер бандла становится меньше, а из него полностью убирается модуль для целей разработки (redux-logger). Вот упрощенный код:

  • В файле webpack.config.js:
      rules: [
  // ... ,
  !env.dev && {
    test: /src\/store\/index\.js$/,
    loader: 'string-replace-loader',
    options: {
      search: /import.+createLogger.+from.+redux-logger.+;/,
      replace: 'const createLogger = null;',
    }
  }
].filter(Boolean)
  • В файле src/store/index.js:
      // in prod this import declaration is substituted by `const createLogger = null`:
import { createLogger } from 'redux-logger';
// ...
export const store = configureStore({
  reducer: persistedReducer,
  middleware: createLogger ? [createLogger()] : [],
  devTools: !!createLogger
});

Используйте переменные окружения для создания развертываний dev и prod:

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

Другие вопросы по тегам