Gatsby 插件:如何将组件作为插件选项?

mac*_*ost 5 javascript reactjs webpack gatsby

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

  plugins: [
    {
      resolve: `gatsby-plugin-modal-routing`,
      options: { someComponent: SomeComponentClassOrFunction }
    },
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

我不确定这是否是应用程序与插件的不同路径的某种问题,或者是否存在其他问题,但import无论如何使用似乎都不是Gatsby 式的解决方案。

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

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

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

Der*_*yen 5

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

我自己尝试这个时也有同样的想法。好家伙。

翻译:博士

// gatsby-node.js
const { DefinePlugin } = require('webpack')
const path = require('path')

exports.onCreateWebpackConfig = ({ actions }, { componentPath }) => {
  actions.setWebpackConfig({
    plugins: [
      new DefinePlugin({
        '___COMPONENT___': JSON.stringify(componentPath)
      })
    ]
  })
}
Run Code Online (Sandbox Code Playgroud)
// gatsby-ssr
export const onRenderBody = ({ setPreBodyComponents }) => {
  const Component = require(___COMPONENT___).default
  setPreBodyComponents([<Component />])
}
Run Code Online (Sandbox Code Playgroud)

长读

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

// gatsby-config.js

{
  resolve: 'my-custom-plugin',
  options: {
    componentPath: path.join(__dirname, './my-component.js')
  }
}
Run Code Online (Sandbox Code Playgroud)

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

盖茨比节点

// gatsby-ssr
export const onRenderBody = ({ setPreBodyComponents }) => {
  const Component = require(___COMPONENT___).default
  setPreBodyComponents([<Component />])
}
Run Code Online (Sandbox Code Playgroud)

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

盖茨比浏览器

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

import() 中的动态表达式

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

网络包文档

好的,我想这样的事情应该有效:

// gatsby-browser
import('./my-dir' + componentPath)
Run Code Online (Sandbox Code Playgroud)

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

那这个怎么办?

// gatsby-browser
import(process.cwd() + componentPath) // nope
Run Code Online (Sandbox Code Playgroud)

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


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

// gatsby-config.js

{
  resolve: 'my-custom-plugin',
  options: {
    componentPath: path.join(__dirname, './my-component.js')
  }
}
Run Code Online (Sandbox Code Playgroud)

最后

// 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
Run Code Online (Sandbox Code Playgroud)

但是由于我们可以直接在 中访问用户选项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)

        })
      ]
    })
  }
Run Code Online (Sandbox Code Playgroud)

回到 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
}
Run Code Online (Sandbox Code Playgroud)

盖茨比 SSR

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

// gatsby-node.js

function consume(component) {
  const Component = require(component)
}
Run Code Online (Sandbox Code Playgroud)

……它失败了。

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

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

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

现在它起作用了。

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

假设我们在两者中都需要这个组件,gatsby-ssr并且gatsby-browser- 也require(...)可以使用gatsby-browser吗?

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

有用。

import(..) 对比 require()

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

  • usingimport('./my-dir' + componentPath)将把里面的所有文件打包./my-dir成一个块。我们可以使用魔术注释来排除/包含内容。

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