Open axelnormand opened 5 years ago
Had the same use-case (but using babel to compile), but did not manage to make the src
alias working inside a monorepo yet, in any case (webpack config or this babel plugin) I'm missing the way to tell to use a path relative to the root of a package (and not the root of the mono-repo, __dirname, etc.).
However it's working with the moduleNameMapper
option of jest:
moduleNameMapper: {
'^src/(.*)': '<rootDir>/src/$1'
}
Would be great to have something like <rootDir>
for this plugin, could solve the src issue.
@axelnormand - Maybe I'm missing something, but what's the benefit from using both the plugin and the config in typescript? I feel like you can do pretty much the same thing only by using the typescript options.
Hi @tleunen and thanks for the cool plugin.
The tsconfig paths is for tsc
to successfully compile the imports. I only use tsc as a linting step.
This resolver plugin is for the outputted JS . I'm now using babel typescript plugin which does no typechecking. Using babel means dont need special tools for typescript compilation in other parts of the stack like jest. Also can use other babel plugins (styled components) easily
So I believe i need both or am i missing something?
For reference here's my code for reading tsconfig paths to set the aliases in this plugin.
Also a resolve fix so the correct src/foo
path followed in my monorepo.
Yarn Workspaces + Lerna monorepo structure has a common "components" project and an "app1" and "app2" project which import those common components
// Part of common babel config js file in my monorepo
/**
* Create alias setting for module-resolver plugin based off tsconfig.json paths
*
* Before in tsconfig.json in project:
*
"paths": {
"src/*": ["src/*"],
"@blah/components/*": ["../components/src/*"],
},
*
*
* After to pass as `alias` key in 'module-resolver' plugin:
*
"alias": {
"src": ["./src"],
"@blah/components": ["./../components/src"],
},
*
*/
const getResolverAlias = projectDir => {
const tsConfigFile = path.join(projectDir, 'tsconfig.json');
const tsConfig = require(tsConfigFile);
const tsConfigPaths =
(tsConfig.compilerOptions && tsConfig.compilerOptions.paths) || {};
// remove the "/*" at end of tsConfig paths key and values array
const pathAlias = Object.keys(tsConfigPaths)
.map(tsKey => {
const pathArray = tsConfigPaths[tsKey];
const key = tsKey.replace('/*', '');
// make sure path starts with "./"
const paths = pathArray.map(p => `./${p.replace('/*', '')}`);
return { key, paths };
})
.reduce((obj, cur) => {
obj[cur.key] = cur.paths; // eslint-disable-line no-param-reassign
return obj;
}, {});
return pathAlias;
};
/**
* Also add special resolving of the "src" tsconfig paths.
* This is so "src" used within the common projects (eg within components) correctly resolves
*
* eg In app1 project if you import `@blah/components/Foo` which in turn imports `src/theme`
* then for `@blah/components/Foo/Foo.tsx` existing module resolver incorrectly looks for src/theme`
* within `app1` folder not `components`
*
* This now returns:`c:\git\Monorepo\components\src\theme`
* Instead of: `c:\git\Monorepo\app1\src\theme`
*/
const fixResolvePath = (projectDir) => (
sourcePath,
currentFile,
opts,
) => {
const ret = resolvePath(sourcePath, currentFile, opts);
if (!sourcePath.startsWith('src')) return ret; // ignore non "src" dirs
// common root folder of all apps (ie "c:\git\Monorepo")
const basePath = path.join(projectDir, '../');
// currentFile is of form "c:\git\Monorepo\components\src\comps\Foo\Foo.tsx"
// extract which project this file is in, eg "components"
const currentFileEndPath = currentFile.substring(basePath.length);
const currentProject = currentFileEndPath.split(path.sep)[0];
// sourcePath is the path in the import statement, eg "src/theme"
// So return path to file in *this* project: eg "c:\git\Monorepo\components\src\theme"
// out of the box module-resolver was previously returning the app folder eg "c:\git\Monorepo\app1\src\theme"
const correctResolvedPath = path.join(
basePath,
currentProject,
`./${sourcePath}`,
);
return correctResolvedPath;
};
const getBabelConfig = (projectDir) => {
const isJest = process.env.NODE_ENV === 'test';
const presets = [
[
'@babel/env',
{
// normally don't transpile import statements so webpack can do tree shaking
// for jest however (NODE_ENV=test) need to transpile import statements
modules: isJest ? 'auto' : false,
// pull in bits you need from babel polyfill eg regeneratorRuntime etc
useBuiltIns: 'usage',
targets: '> 0.5%, last 2 versions, Firefox ESR, not dead',
},
],
'@babel/react',
'@babel/typescript',
];
const plugins = [
[
// Create alias paths for module-resolver plugin based off tsconfig.json paths
'module-resolver',
{
cwd: 'babelrc', // use the local babel.config.js in each project
root: ['./'],
alias: getResolverAlias(projectDir),
resolvePath: fixResolvePath(projectDir),
},
],
'babel-plugin-styled-components',
'@babel/proposal-class-properties',
'@babel/proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
];
return {
presets,
plugins,
};
};
module.exports = {
getBabelConfig,
};
Yup, forgot about a webpack compilation. I'm away for the next couple days, but I'll come back to this thread shortly after. Thanks for sharing your config.
Just came across the same issue. Are we out of luck for the time being or is there a non-invasive workaround?
With typescript becoming more and more popular every day. I'd love seeing something like this by default in the plugin. If anyone is interested in making a PR.
@tleunen interesting feature. How do you see it's implementation? Something like: 1) read tsconfig.json (stop on read error / should the filepath be configurable?) 2) grab baseUrl + paths (stop if they are empty) 3) ensure no conflicts with the plugin config (we can pick either of options and warn user or merge or fail with error) 4) PROFIT
I recently discovered the project tsconfig-paths
. It's almost perfect… but it's not a babel plugin 😅.
It seems very stable and has the right APIs to get this done pretty easily. I'm thinking it could be combined with this plugin's resolvePath
API.
I gave the above a try and it works!
// babelrc.js
const fs = require('fs');
const { createMatchPath, loadConfig } = require('tsconfig-paths');
const {
resolvePath: defaultResolvePath,
} = require('babel-plugin-module-resolver');
const configLoaderResult = loadConfig();
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
const configLoaderSuccessResult =
configLoaderResult.resultType === 'failed' ? null : configLoaderResult;
const matchPath =
configLoaderSuccessResult &&
createMatchPath(
configLoaderSuccessResult.absoluteBaseUrl,
configLoaderSuccessResult.paths,
);
const moduleResolver = configLoaderSuccessResult && [
'module-resolver',
{
extensions,
resolvePath: (sourcePath, currentFile, opts) => {
if (matchPath) {
return matchPath(sourcePath, require, fs.existsSync, extensions);
}
return defaultResolvePath(sourcePath, currentFile, opts);
},
},
];
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: true } }],
'@babel/preset-typescript',
],
plugins: [
// optionally include
...(moduleResolver ? [moduleResolver] : []),
],
};
@tleunen if you provide an API spec, I can send out a PR for the above.
(props, btw for the very pluggable plugin, makes this crazy stuff possible 😎)
I did eventually publish a babel plugin for the above: babel-plugin-tsconfig-paths-module-resolver
I'm loving using the new babel 7 typescript preset in my react monorepo, so nice and simple.
However thought I'd open a discussion of possible improvements to babel-plugin-module-resolver (that I could create a PR for) to help with resolving tsconfig.json "paths" setting automatically. Or maybe this is time to create a typescript specific module resolve plugin instead?
The premise is that I have a monorepo with "app1", "app2", and "components" projects. App1 i can do
import Foo from '@components/Foo'
. I also prefer absolute imports over relative imports so all projects canimport Foo from 'src/Foo'
overimport Foo from '../../Foo'
within themselves.I had to write some code to make babel-plugin-module-resolver resolve those imports by reading in the
tsconfig.json
file and translating the "paths" setting to suitable format for "alias" setting in babel-plugin-module-resolver.Then also i overrode resolvePath with a special case catching the "src" absolute imports. If app1 imports
@components/Foo
which in turn importssrc/theme
, it now correctly returns sayc:\git\monorepo\components\src\theme
instead ofc:\git\monorepo\app1\src\theme
Here is app1
tsconfig.json
snippet with the paths setting:I can provide my code if needed to explain things better.
Perhaps I make a PR to help future people wanting to use this plugin with typescript paths. There could be a setting saying
useTsConfigPaths: true
for instance to automatically resolve them.Not sure how one would fix the "src" alias in all projects problem too?
Thanks