如何在使用create-react-app(不使用eject)时导入共享的TypeScript代码?

24

我正在尝试在非弹出模式下的create-react-app中实现TypeScript代码共享,但我遇到了臭名昭著的限制:不允许在src之外导入文件:

You attempted to import ../../common/src/lib.ts which falls outside of the project src/ directory. [...]

对于非TypeScript的情况,已经有一个问答解决方案,但我无法使任何解决方案与TypeScript一起工作。具体来说,提出的解决方案的问题是:

  • ts-config.json中设置baseDir:这里create-react-app会抱怨:您的项目的baseUrl只能设置为srcnode_modules。 Create React App此时不支持其他值。

  • 基于react-app-rewired的方法:更有前途。禁用ModuleScopePlugin可以让我通过"attempted import outside src"错误,但现在的问题是typescript文件的loader不协作:

    enter image description here

    我已经验证了.ts本身没有问题:将其复制到./src并从那里导入可以正常工作。

    我还在ts-config.json中的includes列表中添加了../../common/src文件夹。

    我的猜测是,webpack loader配置具有阻止TypeScript加载器转译不符合预期路径模式的文件的规则。如何使用react-app-rewired修复此问题?

  • 符号链接源也无效--同样存在相同的问题。可能是因为webpack在内部解析符号链接并在普通loader规则不适用的路径中查看文件。

  • 弹出式解决方案:目前我不想弹出式,但我尝试过,基本上又遇到了同样的问题。

我还发现其他听起来相关但未回答问题的问题:


在monorepo中重用TypeScript类型定义似乎是一种相当合理的模式。我错过了什么,或者为什么create-react-app会使这变得如此困难?


要复现:我的代码基本上就是从

npx create-react-app my-app --template typescript

添加了一个进口到外部。


我认为单体代码库(monorepo)的解决方案最合理,因为它在幕后使用符号链接,对于未拆装的应用程序可以完美地工作。 https://classic.yarnpkg.com/en/docs/workspaces/ 您只需要确保在导入类型之前运行“tsc”。 - azium
@azium 嗯,我尝试了各种符号链接模式,但由于webpack在内部解析这些符号链接,所以我总是遇到上述错误。 - bluenote10
与其导入 ../something,您可以将模块作为本地的 node_modules 文件夹中的模块导入: import { YourType } from 'your-local-module' - azium
我已经在上游问题中提出了一个问题:create-react-app应该允许在src之外导入TypeScript - bluenote10
这个**示例使用create-react-app 4.0和typescript,在src之外的NearSrc.tsx文件中导入,演示了一个可行的解决方案(答案**)。 - oklas
5个回答

17
您可以使用craco(create-react-app配置覆盖)来覆盖webpack配置(作为CRA的一部分抽象),而不用eject。
此外,您还可以使用ts-loader在外部项目中直接引用未转换的ts代码(例如,如果您想要引用/使用共享代码/库作为单存储库的一部分)。
假设您的CRA应用程序位于client目录中,那么您的项目结构如下:
client/
|--src/
|--package.json
shared/
|--package.json
|--(ts files)
package.json

cd client
yarn add -D @craco/craco ts-loader
  1. client/(CRA)目录下创建一个craco.config.js文件
  2. 确保craco.config.js文件具有以下内容
const path = require("path");

module.exports = {
  webpack: {
    configure: webpackConfig => {

      // ts-loader is required to reference external typescript projects/files (non-transpiled)
      webpackConfig.module.rules.push({
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          transpileOnly: true,
          configFile: 'tsconfig.json',
        },
      })
      
      return webpackConfig;
    }
  }
};
  1. client/package.json 中的 react-scripts 命令替换为 craco
/* client/package.json */

"scripts": {
-   "start": "react-scripts start",
+   "start": "craco start",
-   "build": "react-scripts build",
+   "build": "craco build"
-   "test": "react-scripts test",
+   "test": "craco test"
}

请注意,CRACO 官方不支持 create-react-app 4.0,并且 其维护者似乎没有长期维护的意愿。令人烦恼的是,这些针对 CRA 限制默认值的解决方法在每个主要版本发布时都会失效。 - bluenote10
2
CRACO现在支持CRA 4.*,但我遇到了一个问题,ts-loader依赖于较新的webpack版本。我修改了CRA自己的规则,删除了include字段,这个字段将其限制在src目录下,而不是添加规则和安装ts-loader。如果索引发生变化,您可能需要进行一些额外的检查,但基本上可以使用以下方法代替推送新规则:delete webpackConfig.module.rules[1].oneOf[2].include; - ekuusela

9

更新:由于 react-app-rewired 仅被动维护,并且不支持 CRA 版本2+(在撰写本文时我们已经晚了三个主要版本),我不再推荐此方法。


经过更多的实验和阅读 GitHub 上的问题,我终于有了一个可行的解决方案。非常感谢 BirukDmitry 在 GitHub 上发布的这篇非常有帮助的文章。以下是详细步骤:

  1. Install react-app-rewired and customize-cra

     npm i react-app-rewired customize-cra --save-dev
    
  2. Configure react-app-rewird with a minimal config-overrides.js like this:

    const { removeModuleScopePlugin, override, babelInclude } = require("customize-cra");
    const path = require("path");
    
    module.exports = override(
      removeModuleScopePlugin(),        // (1)
      babelInclude([
        path.resolve("src"),
        path.resolve("../common/src"),  // (2)
      ])
    );
    

    Setting (1) is similar to what is needed for getting around the import-outside-src limitation in general (see linked question).

    Setting (2) is crucial though to enabled babel-transpilation (and thus, including TS type stripping I presume) for other paths as well. This is where you have to put add your paths from which you want to import.

  3. No adaptations needed for tsconfig.json.

  4. Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'.


1
“babelInclude”配置是我解决上述错误所缺少的一切。感谢分享! - Doug Wilhelm

3

@bluenote10的解决方案解决了问题!

在第二步中,只有一个问题阻止了Babel从../../common/src转换ts文件。这是@bluenote10修改后的代码(对我最终起作用)。


  1. Install react-app-rewired and customize-cra

     npm i react-app-rewired customize-cra --save-dev
    
  2. Configure react-app-rewird with a minimal config-overrides.js like this:

     const { removeModuleScopePlugin, override, babelInclude } = require("customize-cra");
     const path = require("path");
    
     module.exports = function (config, env) {
    
         return Object.assign( // We need Object.assign() to not to loose initial config 
           config,
           override(
             removeModuleScopePlugin(),       //1
             babelInclude([
               path.resolve('src'),
               path.resolve('../common/src'), //2
             ])
           )(config, env)
         )
       }
    
    

    Setting (1) is similar to what is needed for getting around the import-outside-src limitation in general (see linked question).

    Setting (2) is crucial though to enabled babel-transpilation (and thus, including TS type stripping I presume) for other paths as well. This is where you have to put add your paths from which you want to import.

  3. No adaptations needed for tsconfig.json.

  4. Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'.


1

针对 create-react-app 的 craco 或 rewired 的别名解决方案是 react-app-alias,适用于以下系统:cracoreact-app-rewiredcustomize-cra

根据所提到系统的文档,在 package.json 中将 react-scripts 替换掉,并进行下列配置:

react-app-rewired

// config-overrides.js
const {aliasWebpack, aliasJest} = require('react-app-alias')

const options = {} // default is empty for most cases

module.exports = aliasWebpack(options)
module.exports.jest = aliasJest(options)

craco
// craco.config.js
const {CracoAliasPlugin} = require('react-app-alias')

module.exports = {
  plugins: [
    {
      plugin: CracoAliasPlugin,
      options: {}
    }
  ]
}

所有

像这样在json中配置别名:

// tsconfig.paths.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "example/*": ["example/src/*"],
      "@library/*": ["library/src/*"]
    }
  }
}

并将此文件添加到主TypeScript配置文件的extends部分中:

// tsconfig.json
{
  "extends": "./tsconfig.paths.json",
  // ...
}

奇怪,react-app-rewired和customize-cra官方不支持CRA 4,对吧?这只是偶然发生的,还是只有有限的功能集可用? - bluenote10
3
这个解决方案并不能解决问题。问题在于我们想要在根目录之外而不是 src 目录附近导入共享代码。如果你有多个项目,并希望在这些项目之间共享组件,但又不想将你的组件发布到 npm 上。 - user158443
@oklas v1.1.0 是 cra 还是 ts 的版本? - user158443
@user158443,使用TypeScript的CRA(使用craco或rewired cra)从根文件夹外部导入模块可以在react-app-rewire-alias版本v1.1.0中使用。 - oklas
@bluenote10,这是CRA v4.0版本中的一些bug,不过通过某些额外的操作(更多细节在此处)也可以在v4.0中使用。该问题在CRA v4.1中被修复,并且自那以后就可以正常工作了。 - oklas

0

问题是../../common/src/mymodule在哪里?它是另一个项目吗?如果是另一个项目,为什么不链接它们?

  • 在公共代码项目中运行npm link

  • 在将使用公共代码的项目中:npm link common-project

如果没有两个不同的项目。为什么它必须在src之外?

你发布的链接The create-react-app imports restriction outside of src directory和你的情况/问题是完全不同的事情,而且他们非常困惑。让我给你一个关于那个问题和你的问题的想法。

正如我们已经知道的那样,CRA创建了一个SPA单页应用程序。这意味着只有一个单独的HTML文件。一切都发生在位于<project>/public/index.htmlindex.html中。如果我们编译代码,我们会注意到最终捆绑可能看起来像这样

build
--static
----js
----css
----media
--index.html
--...more hashed crap...
public
src

默认情况下,process.env.PUBLIC_URL设置为“/”。 什么?是的,让我给你看看。有句老话说一张图片胜过千言万语。 enter image description here

如果我们看一下这张图片,根据其路径,它位于./src/styles/imageSlack.jpg或其他位置。 那么如果我们console.log它会怎样呢?

enter image description here

什么!!他们在哪里这样做?你可以做的一件事是在你的代码中的任何地方 console.log(process.env.PUBLIC_URL) 来测试我的理论。现在这与那有一个很大的不同之处。

浏览器本身不知道Typescript。

因此,要么将你的代码设置在 src 中,我们就可以高高兴兴地结束了,要么遵循关注点分离原则,创建一个带有共享代码的npm包,并像其他模块一样进行导入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接