I'm trying to achieve TypeScript code sharing in a create-react-app in non-eject mode, but I'm running into the infamous restriction that imports outside of src
are not allowed:
You attempted to import ../../common/src/lib.ts which falls outside of the project src/ directory. [...]
For the non-TypeScript case this has been asked & answered here, but I can't get any of the solutions to work with TypeScript. Specifically the issues with the proposed solutions are:
Setting baseDir
in ts-config.json
: Here create-react-app complains about: Your project's baseUrl
can only be set to src
or node_modules
. Create React App does not support other values at this time.
Approaches based on react-app-rewired: More promising. Disabling the ModuleScopePlugin
gets me past the "attempted import outside src" error, but the problem now is that the loader of typescript files doesn't play along:
I have verified that the .ts
itself is fine: Copying to ./src
and importing it from there works fine.
I have also added ../../common/src
folder to the includes
list in ts-config.json
.
My guess is that somehow the webpack loader configuration has a rule that prevents the TypeScript loader to transpile files that don't match its expected path patterns. How can this be fixed using react-app-rewired?
Symlinking sources doesn't work either -- again with the same problem. Probably because webpack internally resolves the symlinks and sees the file in a path where the normal loader rules don't apply.
Eject based solutions: I'd like not to eject for now, but I tried and I'm basically running into the same problem again.
I've also found other questions that sounded related but didn't answer the problem:
Re-using TypeScript typings in a mono repo seems to be a fairly reasonable pattern. Am I missing something or why is the create-react-app making this so difficult?
To reproduce: My code is basically 100% what you get from
npx create-react-app my-app --template typescript
with one import to external added.
New App From Scratch If you're building a new app and using create-react-app , the docs are great: You can start a new TypeScript app using templates. To use our provided TypeScript template, append --template typescript to the creation command.
npm run eject This command will remove the single build dependency from your project. Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc.) into your project as dependencies in package.
Just create a new application and you're good to go. When you run npm run eject command in your React application, you will be able to edit the configuration and script files. You also can upgrade or downgrade the dependencies version on the ejected package.
To import component outside src/ directory with React, we can declare a local dependency on package. json. { //... "dependencies": { "app-b-dashboard": "file:./packages/app-b-dashboard" //... } //... } to declare the app-b-dashboard dependency in the dependencies section of package.
You could use craco (create-react-app config override) to override the webpack config (abstracted as part of CRA) without ejecting.
Additionally you could use ts-loader
to reference non-transpiled ts code directly in external projects (e.g. if you wanted to reference/use shared code/lib as part of a mono-repo).
Assuming your CRA app is in the client
directory your project structure is like the following:
client/
|--src/
|--package.json
shared/
|--package.json
|--(ts files)
package.json
cd client
yarn add -D @craco/craco ts-loader
craco.config.js
file in the client/
(CRA) directorycraco.config.js
has the following contentconst 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;
}
}
};
react-scripts
commands in client/package.json
with 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"
}
UPDATE: Since react-app-rewired is only maintained passively and doesn't support CRA versions 2+ (we are are three major versions later at the time of writing), I would no longer recommend this approach.
After more hours of experimenting and reading up on GitHub issues, I finally have a working solution. Big thanks to BirukDmitry who made this very helpful post on GitHub. Step-by-step guide:
Install react-app-rewired and customize-cra
npm i react-app-rewired customize-cra --save-dev
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.
No adaptations needed for tsconfig.json
.
Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With