使用 Webpack 基于环境的条件构建

Conditional build based on environment using Webpack

我有一些要开发的东西 - 例如模拟,我不想用它来膨胀我的分布式构建文件。

在 RequireJS 中,您可以在插件文件中传递配置,并根据该配置有条件地要求内容。

对于 webpack,似乎没有办法做到这一点。首先为环境创建运行时配置,我使用 resolve.alias 根据环境重新指向要求,例如:

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

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

然后在创建 webpack 配置时,我可以动态分配 envsettings 指向的文件(即 webpackConfig.resolve.alias.envsettings = './' + env)。

但是我想做类似的事情:

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

但如果环境不是模拟的,显然我不想在这些模拟文件中构建。

我可以再次使用 resolve.alias 手动将所有这些要求重新指向存根文件 - 但有没有一种感觉不那么骇人听闻的方法?

有什么办法可以做到吗?谢谢。

您可以使用 define plugin.

我通过在你的 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 build example here.

另一种方法是使用 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"

我最终使用了类似于的东西,但担心两点:

  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.jsproduction.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 import export 语法,因为它在 webpack 下):

const config = __APP_CONFIG__;

export default config;

在您的 app.js 中,您现在可以使用 import config from './config'; 获取配置对象。

使用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}`,
        },
      },
    ],
  },
  // ...
};

不确定为什么 "webpack.DefinePlugin" 答案在定义基于环境 imports/requires 的所有地方都是最重要的答案。

该方法的问题 是您仍在将所有这些模块交付给客户端 -> 例如检查 webpack-bundle-analyezer。并且根本不减小 bundle.js 的大小:)

所以真正有效且更合乎逻辑的是:NormalModuleReplacementPlugin

因此,与其执行 on_client 条件要求 -> 只是首先不将不需要的文件包含到捆绑包中

希望对您有所帮助

虽然这不是最佳解决方案,但它可能可以满足您的某些需求。如果你想 运行 在节点和浏览器中使用对我有用的不同代码:

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

使用环境变量创建开发和生产部署:

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

我一直在努力在我的 webpack 配置中设置 env。我通常想要的是设置 env,以便它可以在 webpack.config.jspostcss.config.js 内部和入口点应用程序本身内部(通常是 index.js)到达。我希望我的发现可以帮助别人。

我想到的解决办法是传入--env production--env development,然后在webpack.config.js里面设置mode。 但是,这并不能帮助我在需要的地方访问 env(见上文),因此我还需要按照建议 here 明确设置 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 });
};

面对与 OP 相同的问题,并且由于许可的原因,需要在某些构建中不包含某些代码,我采用了 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 中剔除。例如,在我的 routes.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。

我使用 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
});