这似乎应该是一个简单的问题
我自己尝试这个时也有同样的想法。哦,男孩。
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_modules 或 plugins 目录,我们不会要求我们的用户将他们的自定义组件放在 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-ssr 和gatsby-browser 中都需要这个组件——require(...) 也可以在gatsby-browser 中使用吗?
export const onRouteUpdate = async () => {
// eslint-disable-next-line
const { default: Component } = require(___COMPONENT___)
console.log(Component) // yes
}
有效。
import(..) 与 require()
虽然import() 确实动态加载内容,但它更像是一种代码拆分工具。除了异步之外,还有一些不同: