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

Posted

技术标签:

【中文标题】使用 Webpack 基于环境的条件构建【英文标题】:Conditional build based on environment using Webpack 【发布时间】:2015-04-18 18:59:06 【问题描述】:

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

在 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 手动将所有这些要求重新指向存根文件 - 但有没有一种方法可以让你感觉不那么老套?

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

【问题讨论】:

请注意,现在我已经使用别名指向我不想要的环境中的空(存根)文件(例如 require('mocks') 将指向非模拟上的空文件envs。看起来有点hacky,但它确实有效。 【参考方案1】:

您可以使用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。

【讨论】:

我对这个解决方案有点困惑。它没有提到我应该如何设置env。纵观那个例子,似乎他们正在通过 gulp 和 yargs 处理该标志,但并不是每个人都在使用。 这如何与 linter 一起工作?是否必须手动定义在定义插件中添加的新全局变量? @mark 是的。将"globals": "ENV": true 之类的内容添加到您的 .eslintrc 如何访问组件中的 ENV 变量?我尝试了上面的解决方案,但仍然收到未定义 ENV 的错误 它不会从构建文件中剥离代码!我测试了一下,代码在这里。【参考方案2】:

不确定为什么“webpack.DefinePlugin”答案是定义基于环境的导入/要求的首选答案。

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

所以真正运作良好且更合乎逻辑的是:NormalModuleReplacementPlugin

因此,与其做一个 on_client 条件要求 -> 只是不首先将不需要的文件包含到包中

希望有帮助

【讨论】:

很高兴不知道那个插件! 在这种情况下,每个环境不会有多个构建吗?例如,如果我有 dev/QA/UAT/production 环境的 Web 服务地址,那么我需要 4 个单独的容器,每个环境 1 个。理想情况下,您将拥有一个容器并使用环境变量启动它以指定要加载的配置。 不,不是。这正是你对插件所做的 -> 你通过 env vars 指定你的环境,它只构建一个容器,但对于没有多余包含的特定环境。当然,这也取决于你如何设置 webpack 配置,显然你可以构建所有构建,但这不是这个插件的目的和作用。 @RomanZhyliov 如果我需要根据客户端错误导入 npm 包怎么办。我觉得这个插件不行吧?【参考方案3】:

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

【讨论】:

我赞成这个答案,因为接受的答案没有按预期去除代码,并且类似预处理器的语法更有可能被识别为条件元素。 非常感谢!它就像一个魅力。用 ContextReplacementPlugin、NormalModuleReplacementPlugin 和其他东西进行了几个小时的实验——都失败了。这是 ifdef-loader,节省了我的时间。【参考方案4】:

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

    每次我使用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.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'; 来获取配置对象。

【讨论】:

这里真的是最好的答案【参考方案5】:

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

【讨论】:

if/else 和 require 的唯一问题是两个必需的文件都会被捆绑到生成的文件中。我还没有找到解决方法。基本上捆绑首先发生,然后是修饰。 这不是必须的,如果你在你的webpack文件中使用插件webpack.optimize.UglifyJsPlugin(),webpack的优化不会加载模块,因为条件里面的行代码总是假的,所以webpack从生成的包中删除它 @AlejandroSilva 你有这方面的回购例子吗? @thevangelist 是的:github.com/AlejandroSilva/mototracker/blob/master/… 这是一个 node+react+redux 宠物项目:P【参考方案6】:

面对与 OP 相同的问题并要求,由于许可,在某些版本中不包含某些代码,我采用了 webpack-conditional-loader 如下:

在我的构建命令中,我为我的构建设置了一个适当的环境变量。例如 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。

【讨论】:

还有github.com/dearrrfish/preprocess-loader,功能更多【参考方案7】:

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

我想出的解决方案是传入--env production--env development,然后在webpack.config.js 中设置模式。 但是,这并不能帮助我在我想要的地方访问env(见上文),所以我还需要明确设置process.env.NODE_ENV,正如推荐的here。 我在webpack.config.js 中最相关的部分如下。

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

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

这不是我要问的 问题是 webpack 在构建 bundle 时会忽略该条件,并且无论如何都会包含为开发加载的代码......所以它不能解决问题【参考方案9】:

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

if (typeof window !== 'undefined') 
    return

//run node only code now

【讨论】:

OP 询问编译时间决定,您的答案是关于运行时间。

以上是关于使用 Webpack 基于环境的条件构建的主要内容,如果未能解决你的问题,请参考以下文章

webpack4.0各个击破—— Module篇

vue-cli结构分析

在基于 TypeScript/eslint/Webpack 的构建环境中检测未使用的导出符号

webpack 构建后运行命令

vue-cli webpack 开发环境跨域

webpack