Условная сборка на основе среды с использованием 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}`,
},
},
],
},
// ...
};
В итоге я использовал что-то похожее на "Ответ Мэтта Деррика", но беспокоился о двух моментах:
- Полная конфигурация вводится каждый раз, когда я использую
ENV
(Что плохо для больших конфигов). - Я должен определить несколько точек входа, потому что
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: