Rails, webpacker: where/when 在类中使用import语句,在pack/application.js中require的目的是什么?

Rails, webpacker: where/when to use the import statement in classes, and what's the purpose of requiring in the pack/application.js?

我正在将旧应用程序升级到 Rails 6,它使用 webpacker 进行所有 JS 资产管理。

我正在使用 pikaday 日历库,并已通过 yarn add pikaday 添加它,验证它显示在 packages.json 中,然后将其添加到我的 app/javascript/packs/application.js通过 require("pikaday").

我有一个名为 Datepicker 的 JS class,我用它来抽象实际的 pikaday 日历。我这样做是因为有一天我可能会更改日期选择器的实现,这样我只需要更改一个 class 而不是更新我所有的 pikaday 调用。

不过,我在application.js包文件里是否require("pikaday")似乎并不重要;只要我在 class 中引用它 import Pikaday from "pikaday",就没有区别。

问题

我正在尝试了解正在发生的事情。

  1. 我需要在 app/javascript/pack/application.js 文件中添加 require("pikaday")import Pikaday from "pikaday" 吗?为什么或为什么不?

  2. 我很熟悉全局变量不好并且应该避免的原则,但是有没有办法避免在每个引用它的 JS 文件上都必须 import CLASS from "class_file"?在我的示例中,我想在多个地方使用 Datepicker class。我问的原因是因为我有 10+ class 这样的,在我想使用这些的每个 JS 文件的顶部有 10+ import 语句有点烦人。那里某些 class 是我一直想访问的。我玩过 webpacker ProvidePlugin 功能,但它抱怨 Datepicker 不是构造函数,所以我可能遗漏了一些东西,但我没有足够的知识知道什么。

app/javascript/custom/datepicker.js

import Pikaday from "pikaday"

export default class Datepicker {
  constructor(element, options) {
    if (options == null) { options = {} }

    // Store DOM element for reference
    this.element = element

    // Do not re-run on elements that already have datepickers
    if (this.element.datepicker === undefined) {
      options = Object.assign({},
        this.defaultOptions(),
        options
      )

      const picker = new Pikaday(options)

      // Store picker on element for reference
      this.element.datepicker = picker

      return picker
    } else {
      console.log("Datepicker already attached")
      return
    }
  }

  // Overridden by `options` in constructor
  defaultOptions() {
    return {
      field: this.element,
      format: "M/D/YYYY",
      bound: true,
      keyboardInput: false,
      showDaysInNextAndPreviousMonths: true
    }
  }
}

app/javascript/packs/application.js

require("@rails/ujs").start()
require("turbolinks").start()

require("moment")

// Note: if using `@rails/ujs`, you do not need to use `jquery-ujs`.
import "jquery"

// Does not matter if I require this or not, as long as it is imported in the
// class file, I can remove this require statement and everything still works.
require("pikaday")

// StimulusJS
// Webpack's `require` looks for `controllers/index.js` by default
require("controllers")

require("custom/datepicker")

你问了几个问题:

  1. 您只需要在引用导入变量的文件中 import Pikaday from 'pikaday'Pikaday。在这种情况下,您只需要在自定义日期选择器模块中进行此导入。您可以从 application.js 包文件中删除 require("pikaday")

    原因是 Webpack 会将您的 application.js 包作为 依赖关系图 的入口点;从那里开始,它将递归遍历每个 required/imported 模块,找到这些模块的依赖关系,依此类推,直到所有声明的模块都包含在包中。由于您在 application.js 包中声明了 import 'custom/datepicker',并且自定义日期选择器导入 pikaday,它将作为依赖项包含在包中。

  2. 您的自定义 Datepicker 被编译为 ES 模块(而不是 Webpack 的 ES 模块实现),因为您使用的是 ES 模块语法 export default ...。这对于 ProvidePlugin 的工作方式很重要。来自 Webpack 4 documentation of ProvidePlugin:

    For importing the default export of an ES2015 module, you have to specify the default property of module.

    这意味着 Datepicker 的插件条目的 Webpack 配置看起来像这样(使用 Rails Webpacker 环境 api):

    const { environment } = require('@rails/webpacker')
    const webpack = require('webpack')
    const {resolve} = require('path');
    
    environment.plugins.append('Provide', new webpack.ProvidePlugin({
      Datepicker: [resolve('app/javascript/custom/datepicker'), 'default']
    }))
    
    module.exports = environment
    

    意见:就是说,我鼓励您明确导入,例如import Datepicker from 'custom/datepicker' 在引用 Datepicker 的每个模块中。即使重复,与 ESlint 等工具集成也会变得容易得多,ESlint 与某些代码编辑器一起,可以提供有关编译错误的内联反馈——在每个模块中声明显式依赖项更容易设置。

我在这里使用自定义日期选择器和 ProvidePlugin 组合了一个 Pikaday 的工作演示:https://github.com/rossta/rails6-webpacker-demo/commit/be3d20107c2b19baa8b9560bce05e0559f90086d