【问题标题】:Gatsby Plug-in: How Can I Take a Component as a Plug-In Option?Gatsby 插件:如何将组件作为插件选项?
【发布时间】:2020-07-18 17:59:10
【问题描述】:

我正在尝试对现有的 Gatsby 插件进行改进,并且我想通过 gatsby-config.js 中的配置条目将 React 组件传递给插件:

  plugins: [
    {
      resolve: `gatsby-plugin-modal-routing`,
      options: { someComponent: SomeComponentClassOrFunction }
    },

但是,我遇到的问题是我不知道如何使它工作。

如果我尝试将组件本身作为插件配置的一部分传递,它似乎会与 JSON 进行序列化,从而导致类成为无用的对象。所以看来我必须传递一个路径字符串。

  plugins: [
    {
      resolve: `gatsby-plugin-modal-routing`,
      options: {
        modalComponentPath: path.join(__dirname, 'src/components/SomeComponent.js')
      }
    },

但是,如果我尝试传递路径,我无法弄清楚如何使用它来加载插件内的组件。我尝试过使用动态节点导入(即import(path).then(component => ...))...

  • 路径为 path.join-ed 和 __dirname
  • 带有相对路径 (src/components/SomeComponent)
  • 具有本地路径相对路径 (./src/components/SomeComponent)
  • 有和没有尾随.js

我不确定这是否是应用程序与插件的不同路径的某种问题,或者是否存在其他问题,但无论如何使用import 似乎是一个不像盖茨比的解决方案.

然后,我发现了 loadPageloadPageSync 函数,它们被传递到插件中......但它们也失败了。我尝试的每条路径都会导致组件返回......但它是一个“找不到页面”组件(可能是因为我试图传入的组件尚未作为页面添加)。

这似乎应该是一个简单的问题,至少对于以前使用过 Gatsby 插件的人来说:如果您希望插件将组件作为输入(作为函数/类或作为模块路径的字符串)...您如何在插件中实际使用该组件?

我正在寻找的只是一个基本模式或对现有 Gatsby 插件中的一行的引用,该插件采用一个组件,或者类似的简单东西(我可以查找任何细节)。

【问题讨论】:

    标签: javascript reactjs webpack gatsby


    【解决方案1】:

    这似乎应该是一个简单的问题

    我自己尝试这个时也有同样的想法。哦,男孩。

    TL:DR

    // gatsby-node.js
    const { DefinePlugin } = require('webpack')
    const path = require('path')
    
    exports.onCreateWebpackConfig = ({ actions }, { componentPath }) => {
      actions.setWebpackConfig({
        plugins: [
          new DefinePlugin({
            '___COMPONENT___': JSON.stringify(componentPath)
          })
        ]
      })
    }
    
    // gatsby-ssr
    export const onRenderBody = ({ setPreBodyComponents }) => {
      const Component = require(___COMPONENT___).default
      setPreBodyComponents([<Component />])
    }
    

    长读

    Gatsby 配置似乎没有传递函数(我可以发誓它曾经这样做过),因此将 React 组件直接传递给您的自定义插件是不可能的。它必须是您的组件的路径。

    // gatsby-config.js
    
    {
      resolve: 'my-custom-plugin',
      options: {
        componentPath: path.join(__dirname, './my-component.js')
      }
    }
    

    你没有说你是在 gatsby-node 还是 gatsby-browser/ssr 中使用组件,但我认为是后者,因为在 Node 中动态地要求东西非常简单:

    盖茨比节点

    // gatsby-node.js
    
    function consume(component) {
      const Component = require(component)
    }
    

    ...虽然它不理解 JSX 或 ESM,但这是另一个问题。

    盖茨比浏览器

    gatsby-browser/ssr 是用 webpack 运行的,所以模块格式没有问题。但是import(componentPath) 不行:

    import() 中的动态表达式

    不能使用完全动态的导入语句,例如import(foo)。因为 foo 可能是系统或项目中任何文件的任何路径。

    webpack doc

    好的,我想这样的事情应该可以工作:

    // gatsby-browser
    import('./my-dir' + componentPath)
    

    不,因为 webpack 会尝试从插件所在的任何位置解决这个问题,即 node_modulesplugins 目录,我们不会要求我们的用户将他们的自定义组件放在 node_modules 中。

    那么这个呢?

    // gatsby-browser
    import(process.cwd() + componentPath) // nope
    

    我们又回到了起点——webpack 不喜欢完整的动态路径!而且即使这样可行,这也是一个糟糕的主意,因为 webpack 会尝试捆绑整个工作目录。


    只有当我们可以预先将路径编码为静态字符串时,webpack 才能读取该代码 — 就像使用 webpack.DefinePlugin 定义环境变量一样。幸运的是,我们可以在 gatsby-node.js 中做到这一点:

    // gatsby-node.js
    const { DefinePlugin } = require('webpack')
    const path = require('path')
    
    exports.onCreateWebpackConfig = ({ actions }) => {
      actions.setWebpackConfig({
        plugins: [
          new DefinePlugin({
            '___CURRENT_DIR___': JSON.stringify(process.cwd())
          })
        ]
      })
    }
    

    最后

    // gatsby-browser
    
    // eslint throw error for unknown var, so disable it
    // eslint-disable-next-line
    import(___CURRENT_DIR___ + componentPath) // works, but don't do this
    

    但是由于我们可以直接在 gatsby-node 中访问用户选项,所以我们只需对整个路径进行编码:

      // gatsby-node.js
      const { DefinePlugin } = require('webpack')
    - const path = require('path')
    
    - exports.onCreateWebpackConfig = ({ actions }) => {
    + exports.onCreateWebpackConfig = ({ actions }, { componentPath }) => {
        actions.setWebpackConfig({
          plugins: [
            new DefinePlugin({
    -         '___CURRENT_DIR___': JSON.stringify(process.cwd())
    +         '___COMPONENT___': JSON.stringify(componentPath)
    
            })
          ]
        })
      }
    

    回到 gatsby-browser.js:

    // gatsby-browser
    
    // I pick a random API to test, can't imagine why one would import a module in this API
    export const onRouteUpdate = async () => {
      // eslint-disable-next-line
      const { default: Component } = await import(___COMPONENT___)
      console.log(Component) // works
    }
    

    盖茨比 SSR

    为了完整起见,让我们在gatby-ssr中尝试同样的技巧:

    // gatsby-ssr
    
    export const onRenderBody = async ({ setPreBodyComponents }) => {
      // const Component = require(___COMPONENT___).default
      const { default: Component } = await import(___COMPONENT___)
      setPreBodyComponents([<Component />])
    }
    

    ...它失败了。

    为什么?如果一个人足够好奇,他们可能会去挖掘 Gatsby 代码,看看 gatsby-ssr 与 gatsby-browser 的处理方式有何不同,但我就是不想这样做。

    别担心,我们还有一招。 Webpack 的 require 也可以动态导入模块,但不是异步的。由于gatsby-ssr 不在浏览器中运行,所以我不太关心异步性。

    export const onRenderBody = ({ setPreBodyComponents }) => {
      const Component = require(___COMPONENT___).default
      setPreBodyComponents([<Component />]) // works
    }
    

    现在它可以工作了。

    在 gatsby-ssr 和 gatsby-browser 之间共享代码

    假设我们在gatsby-ssrgatsby-browser 中都需要这个组件——require(...) 也可以在gatsby-browser 中使用吗?

    export const onRouteUpdate = async () => {
      // eslint-disable-next-line
      const { default: Component } = require(___COMPONENT___)
      console.log(Component) // yes
    }
    

    有效。

    import(..)require()

    虽然import() 确实动态加载内容,但它更像是一种代码拆分工具。除了异步之外,还有一些不同:

    • 使用import('./my-dir' + componentPath) 会将./my-dir 内的所有文件 捆绑到一个块中。我们可以使用神奇的注释来排除/包含内容。

    • require(...) 只会将所需的组件内联到调用它的任何块中。

    【讨论】:

      猜你喜欢
      • 2020-07-31
      • 2021-06-11
      • 1970-01-01
      • 2022-10-18
      • 1970-01-01
      • 2019-03-23
      • 1970-01-01
      • 1970-01-01
      • 2022-01-11
      相关资源
      最近更新 更多