【发布时间】:2021-08-17 20:55:42
【问题描述】:
我正在尝试编写一个包含一些 React 内容的 NPM 包,目前它只是一个组件和一个钩子。要构建我正在使用 Webpack 的包。我已将react 和react-dom 添加到外部部分,以确保它不包含在捆绑包中。我还在package.json 中将react 标记为peerDependency,并将其包含为devDependency。尝试在另一个项目中使用捆绑包时,我仍然收到错误Invalid hook call。我想我已经尝试了所有可以通过 Google 搜索到的方法(例如使用 package 来解决这个问题),但没有成功。
我的 Webpack 配置现在看起来像这样:
const path = require('path');
const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';
const config = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'umd',
},
plugins: [],
module: {
rules: [
{
test: /\.(tsx?)$/i,
loader: 'ts-loader',
exclude: ['/node_modules/'],
},
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.graphql', '.json'],
},
externals: {
react: 'react',
'react-dom': 'react-dom',
},
};
module.exports = () => {
if (isProduction) {
config.mode = 'production';
} else {
config.mode = 'development';
}
return config;
};
package.json 的要点如下:
{
...
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
...
"scripts": {
...
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
...
"prepublishOnly": "npm run build"
},
...
"peerDependencies": {
"react": "16.8.0 || ^17.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
},
"dependencies": {
"@apollo/client": "^3.3.19",
"apollo-server-errors": "^2.5.0",
"joi": "^17.4.0",
"lodash": "^4.17.21"
},
"devDependencies": {
...
"react": "^17.0.2",
"react-dom": "^17.0.2",
...
}
}
钩子非常简单,它只是尝试使用useContext 由组件创建的上下文,以确保此逻辑没有问题我尝试仅使用setState 与相同结果。钩子看起来有点像这样:
function useClient(): Client {
return useContext(getContext());
}
getContext 只是一个创建或重用现有React.Context 的函数(深受 Apollo Client 的启发):
const cache = new (canUseWeakMap ? WeakMap : Map)<
typeof createContext,
Context<Client | undefined>
>();
function getContext() {
let context = cache.get(createContext);
if (!context) {
context = createContext<Client | undefined>(undefined);
context.displayName = 'ClientContext';
cache.set(createContext, context);
}
return context;
}
export default getContext;
我尝试使用钩子的组件是一个简单的功能组件:
const HelloWorld: FC<HelloWorldProps> = () => {
const client = useClient();
return (
<div>Hello World!</div>
);
};
我错过了什么?非常感谢您的帮助!
编辑:
我在一个仅包含基础知识的小型示例应用程序中重现了该问题,使用setState 的外部包,然后在create-react-app 中使用该包,结果相同:
【问题讨论】:
-
您是否在本地使用符号链接进行尝试?
-
使用
development模式,然后检查webpack构建的输出(通常在dist文件夹中)。您能否验证该文件的内容是否正确地捆绑了您的组件(并外部化了react)? -
这很奇怪。作为
exported函数useExternal不应将其名称弄乱。函数o不是一个有效的钩子,因为它没有use名称前缀。此外,在development模式下,Webpack 根本不应该破坏名称。双重奇怪。确保 (i) 它实际上是mode: development。在production模式下 (ii) 通过手动提供TerserPlugin和keep_fnames: true来禁用名称修改 - 请参阅 webpack.js.org/plugins/terser-webpack-plugin/#terseroptions 。此外,请确保 (iii) 您的函数实际上已导出到输出文件中。 -
显然,webpack
v5的一些默认设置存在问题。我之前遇到过类似的问题。这里有一个很长的讨论:github.com/webpack/webpack/issues/11277#issuecomment-723476599。尝试设置devtool: 'source-map'。如果没有其他办法,降级到webpack@4,那肯定行。 -
另外,尝试将
const useExternal = () => useState('external package');重写为function useExternal() { return useState('external package'); }。这可能会修复production中的名称错误。Function.name为ArrowFunctionExpression正常工作是一个相当新的概念。不确定webpack是否正确。
标签: reactjs typescript webpack react-hooks