typescript 即使在webpack配置中添加了output.library,从库导入react组件时仍获得空对象

b1payxdu  于 2023-05-19  发布在  TypeScript
关注(0)|答案(1)|浏览(190)

我写了一个样本typescriptReact组件库文件夹结构如下所示,出口只有一个按钮组件。运行npm run build时,只生成一个js bundle。这个包有react库代码(在webpack配置中还没有使用externals属性),但没有导出的按钮组件和按钮标签(在包文件中搜索关键字button,没有结果)。还添加了bundle/build文件的代码。
不知道我哪里做错了。
在导出简单的javascript函数时,webpack bundle不会发生此问题。导出的JavaScript函数在生成的包中找到,但不在导出的React组件中找到。
更新:
在output.library.name我的webpack配置中添加www.example.com =“template-react-component-library”和output.library.type =“umd”后,我能够在webpack捆绑文件中看到按钮标记(导出组件的),但从template-react-component-library导入相同的按钮组件时,我得到一个空对象。此外,通过从具有相同webpack配置的库中暴露正常的javascript函数,我从库中导入javascript函数而不是空对象,并且也没有添加任何输出.库配置。
所以现在的问题是如何解决从库中导入组件时的空对象,以及为什么output.library在公开JavaScript函数和react组件时表现不同。
存储库链接:https://github.com/dhiren-eng/template-react-component-library

文件夹结构:

webpack.config.js:

const path = require("path")
module.exports = {
entry: "./src/index.ts",
output: {
    filename: "main.js",
    path: path.resolve(__dirname, 'build'),
    clean: true
},
module: {
    rules: [
        { test: /\.(jsx|js|tsx|ts)$/, exclude: /node_modules/, use: {loader: "babel-loader", options: {presets: ["@babel/preset-env","@babel/preset-react"]}} },
        { test: /\.(tsx|ts)$/, exclude: /node_modules/, use: ["ts-loader"] }
    ]
},
resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx']
}
}

tsconfig.json:

{
compilerOptions: {
"jsx": "react",
"module": "ESNext",
"declaration": true,
"declarationDir": "types",
"sourceMap": true,
"outDir": "build",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true
}}

安装在包下面。package.json:

{
  "name": "template-react-component-library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "build": "webpack --config webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
  "@babel/cli": "^7.21.5",
  "@babel/core": "^7.21.8",
  "@babel/preset-env": "^7.21.5",
  "@babel/preset-react": "^7.18.6",
  "@types/react": "^18.0.27",
  "babel-loader": "^9.1.2",
  "react": "^18.2.0",
  "ts-loader": "^9.4.2",
  "typescript": "^4.9.5",
  "webpack": "^5.76.2",
  "webpack-cli": "^5.0.1"
}

src/components/Button/Button.tsx:

import React from "react";

export interface ButtonProps { 
  label: string;
}

const Button = (props: ButtonProps) => {
  return <button>{props.label}</button>;
};

export default Button;

src/components/Button/index.ts:

export {default} from './Button'

src/components/index.ts:

export {default as Button} from './Button'

src/index.ts:

export {Button} from './components'

生成构建文件,main.js:

/*! For license information please see main.js.LICENSE.txt */
(()=>{"use strict";var t={408:(t,e)=>{Symbol.for("react.element"),Symbol.for("react.portal"),Symbol.for("react.fragment"),Symbol.for("react.strict_mode"),Symbol.for("react.profiler"),Symbol.for("react.provider"),Symbol.for("react.context"),Symbol.for("react.forward_ref"),Symbol.for("react.suspense"),Symbol.for("react.memo"),Symbol.for("react.lazy"),Symbol.iterator;var o={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},r=Object.assign,a={};function n(t,e,r){this.props=t,this.context=e,this.refs=a,this.updater=r||o}function c(){}function p(t,e,r){this.props=t,this.context=e,this.refs=a,this.updater=r||o}n.prototype.isReactComponent={},n.prototype.setState=function(t,e){if("object"!=typeof t&&"function"!=typeof t&&null!=t)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,t,e,"setState")},n.prototype.forceUpdate=function(t){this.updater.enqueueForceUpdate(this,t,"forceUpdate")},c.prototype=n.prototype;var s=p.prototype=new c;s.constructor=p,r(s,n.prototype),s.isPureReactComponent=!0;Array.isArray,Object.prototype.hasOwnProperty},294:(t,e,o)=>{o(408)}},e={};!function o(r){var a=e[r];if(void 0!==a)return a.exports;var n=e[r]={exports:{}};return t[r](n,n.exports,o),n.exports}(294)})();

运行webpack build后,将生成2个文件夹:

  1. tsconfig.json的outDir属性中提到的类型文件夹
  2. webpack生成的Build文件夹

bvhaajcl

bvhaajcl1#

好吧,我该从哪里开始呢?首先我要说的是,我非常赞成以ESM(Ecma Script Module或简称module)格式分发库,因为它是最有前途的现代模块格式。
这并不意味着你不能以其他格式分发你的库(例如:UMD),但我将在我的回答中关注ESM,因为它是在React应用程序中使用组件库的首选方式。

配置Webpack

现在我们知道我们希望Webpack生成ESM输出,让我告诉你Webpack's support for ESM is still experimental,这样可能会有问题。
让我们修改webpack.config.js来生成ESM:

output: {
    ...,
    library: {
      type: "module",
    },
  },
  externals: {
    react: "react",
  },
  experiments: {
    outputModule: true,
  },
  ...

使用此配置,输出将包含类似import * as React from 'react'的内容,这就是我们想要的。
现在有一个问题,在TS转译Webpack出于某种原因不尊重来自TS配置的"allowSyntheticDefaultImports": true,这应该使import React from 'react'工作,即使'react'不提供任何默认导出。但由于某种原因,输出仍然试图从react中检索default,而react并不存在。这导致了问题

Cannot read properties of undefined (reading 'createElement')

这意味着react没有正确解析。
我在这里没有找到任何理想的解决方案,但对我有效的是:
1.将externalsType: "import",添加到webpack.config.js。这将动态导入react,但这并不理想,因为它会向输出添加大约2kB的运行时间。
1.在源文件中使用import * as React from 'react'
这两种方法都可以工作,并且可以将您的组件与import { Button } from 'your-lib';一起使用。但他们俩也都很烂。

备选

这就引出了一个问题,如果没有更合适的库打包器的话。我建议Rollup,直接或通过Vite。与Webpack不同,它对ESM格式提供了出色的开箱即用支持。
添加rollup.config.js

import typescript from "@rollup/plugin-typescript";
import babel from "@rollup/plugin-babel";
import external from "rollup-plugin-peer-deps-external";
import terser from "@rollup/plugin-terser";

export default {
  input: "src/Button.tsx",
  output: {
    file: "build/main.js",
    format: "es",
    sourcemap: true,
  },
  plugins: [
    typescript(),
    babel({
      presets: ["@babel/preset-react"],
    }),
    external(),
    terser(),
  ],
  external: ["react"],
};

这个配置是你的Webpack配置的精确镜像,并且更容易配置和阅读。当然你必须安装所需的依赖项

yarn add -D rollup rollup-plugin-peer-deps-external @rollup/plugin-babel @rollup/plugin-terser @rollup/plugin-typescript tslib

另一个副作用是Rollup的输出,至少在您的情况下,要小得多,基本上相当于:

import t from "react";
const e = (e) => t.createElement("button", null, e.label);
export { e as Button };

而Webpack的要大得多。
PS:如果你遇到问题,因为Rollup配置使用importexport,只需添加"type": "module",到你的package.json,这将迫使你的项目文件使用ESM而不是CommonJS(及其module.exportsrequire())。

相关问题