在 Webpacker 中根据环境变量动态加载文件 (Rails 6)

Dynamically loading a file based on an environment variable in Webpacker (Rails 6)

我第一次使用 Stack Overflow,请多多关照 ;) 我会尽力而为!

上下文:

我正在使用 webpacker 开发 Rails 6 应用程序。这是一个将由几家公司共享的程序,为了应用 'one code, multiple setups' 范式,我们决定将所有与公司相关的配置文件移动到单独的文件夹中,并将公司名称作为变量放在我们的 .环境文件。我们需要更改一些配置变量以及一些地理围栏数据(以便我们的客户可以创建到某个地址的新交付)。基本上这就是它的样子:

Project folder 
| config 
  | companies
    | a_first_company
      | rails_config.rb
      | geofencing.js
    | a_second_company
      | rails_config.rb
      | geofencing.js
    | ....

在 .env 文件中:

COMPANY=a_first_company

并且在 rails 配置 (application.rb) 中,我们使用一个简单的:

require_relative "companies/#{ENV['COMPANY']}/rails_config"

但是现在,JS 部分来了!我 运行 遇到了麻烦。

问题:

我想在现有脚本中动态包含一个 JSON 对象。样本 geofencing.js 看起来像这样:

module.exports = {
  "countries": ["be"],
  "polygon": [
    50.8917729, 4.3004608,
    ...
    50.9162381, 4.3450928,
    50.8917729, 4.3004608
  ]
}

我正在尝试将其作为 geofencing 变量导入到我现有的地址自动完成脚本中:

/app/javascript/plugins/places.js

// I know it doesn't work that way, but basically that what I would like to do:
const geofencing = require(`/config/companies/${process.env.COMPANY}/geofencing`);
...

const initPlaceAutocomplete = () => {
  
    ...

    var placesAutocomplete = places(
            {
                // And use the variable here...
                insidePolygon: [geofencing.polygon],
                type: 'address',
                // And there...
                countries: geofencing.countries,
                templates: {
                    value: (suggestion) => {
                        return suggestion.name;
                    }
                },
                container: addressInput
            }
        );

     ...
}

export { initPlaceAutocomplete };

此文件导入到视图中 <%= javascript_pack_tag 'delivery_new' %>:

/app/javascript/packs/delivery_new.js

import { initPlaceAutocomplete } from '../plugins/places';
initPlaceAutocomplete();

...

解决方法(我还没找到):

我已经尝试了几种方法,比如在 webpack 配置中导入文件 (/config/webpack/environment.js),就像在 the ProvidePlugin documentation:

中一样
const {environment} = require('@rails/webpacker')
const path = require('path');
const webpack = require('webpack')

environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        Popper: ['popper.js', 'default'],
        geofencing: path.resolve(path.join(__dirname, '..', '..', 'config', 'companies', process.env['COMPANY'], 'geofencing'))
    })
)

module.exports = environment

...但是没用

我也尝试在不同的地方导入 'geofencing',在 Chrome 控制台中总是得到相同的结果:Uncaught ReferenceError: geofencing is not defined.

不过,我注意到我可以访问 places.js 脚本中的 process.env 变量:在文件中写入 console.log(process.env['COMPANY']); 时会提示我在开发控制台中输入公司名称我在 Chrome.

中重新加载页面

除此之外,我不得不说我迷路了。我基本上是 Webpack 的新手 'magic' ;)

如果您需要有关我的设置的更多信息,请告诉我。

在此先感谢您的帮助!

您应该能够要求模块在类似于您的示例的 require() 表达式中使用插值。尝试使用相对路径,例如(假设父目录是兄弟目录):

require(`../config/${process.env.COMPANY}/geofencing`)

以上表达式将 COMPANY 地理围栏模块添加到包中。

如果你希望它是动态的,webpack 可以在运行时解析插值,假设需要的路径被限定在一个目录范围内;您已经在此处使用“../config/”执行此操作。因此,webpack 将在捆绑包中包含 all COMPANY 地理围栏模块。至于用法,我可能会将 require 语句移到函数中:

function initPlaceAutocomplete(company) {
  const geofencing = require(`../config/${company}/geofencing`)
  // ...
}

// usage
import { initPlaceAutocomplete } from "../plugins/places"
initPlaceAutocomplete(process.env.COMPANY)

在这一点上,这可能是 dynamic imports, i.e., webpack supports the TC39 proposal for dynamically loading modules at runtime 的一个很好的用例。

您可以使用 import() 函数语法代替 require("../config...")。与 require() 不同,import() 函数语法是异步解析的。

现在 webpack 将捆绑所有 COMPANY 地理围栏模块,但作为单独的“块”以减少初始脚本的大小。 webpack 将插入代码以在运行时异步解析这些“块”。为了支持这一点,import() 表达式 returns 是一个 Promise,所以我在这里使用 async/await 语法作为结果。

async function initPlaceAutocomplete(company) {
  const geofencing = await import(`../companies/${company}/config`)
  
  // ...
}