How to add import shortcuts - alias - javascript

I've created a brand new react app
create-react-app demo
I need to create alias for some directories/components like:
import { Header } from '#uicomponents'
Where #uicomponents is shortcut for the path 'src/hello/this/is/the/path/to/ui/components '
All online tutorials telling that I can use alias imports using resolve.alias, but I'm wondering how to do this with brand-new react app?
Theres is no .babelrc or webpack.config.js files.
Any help please?

If you haven't ejected your application from CRA, then you can alias your source directory with using NODE_PATH in your .env file. NODE_PATH=/src/hello/this/is/the/path/to/ui/components.
If you alias without ejecting, it won't allow you to do the #uicomponents or have multiple aliases. Here's an issue on GitHub that talks about it and the relevant CRA page on environment variables.
If you have ejected you can set the alias in the Webpack configuration files and be able to import like you want:
...
resolve: {
alias: {
'#uicomponents': '/src/hello/this/is/the/path/to/ui/components'
}
},
...

UPDATED:
I recommend you to use Craco.
It allows you to customize webpack / babel / any other tool that used in react-scripts internally.
Webpack and Jest aliases is not an exception.
And recently I created a plugin called craco-alias specially for these purposes.
Links: GitHub, npm.
This plugin generates webpack and jest aliases for you automatically.
You just need to install Craco, write a small config for it, then create (or edit) your jsconfig.json / tsconfig.json (it works with TS as well) and specify aliases source in craco-alias config object.
It's easy - you can find all examples on README page.
Of course it works with every command (npm start, npm test, run build build) and allows to pass any CLI arguments.
But, the only problem is that plugin only supports path aliasing, and it doesn't work with module aliases properly.
I hope it will help somebody :)

The alias solution for craco or rewired create-react-app is react-app-alias for systems as: craco, react-app-rewired, customize-cra
According docs of mentioned systems replace react-scripts with craco in package.json and configure next:
// craco.config.js
const {CracoAliasPlugin} = require('react-app-alias')
module.exports = {
plugins: [
{
plugin: CracoAliasPlugin,
options: {}
}
]
}
Configure aliases in json like this:
// tsconfig.paths.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"example/*": ["example/src/*"],
"#library/*": ["library/src/*"]
}
}
}
And add this file in extends section of main typescript config file:
// tsconfig.json
{
"extends": "./tsconfig.paths.json",
// ...
}

Related

Vue JS note transpiling node module

I have a Vue app created with vue create app and have the following settings in the babel.config file:
module.exports = {
presets: [
'#vue/cli-plugin-babel/preset'
]
}
My Vue config file also looks like this:
module.exports = {
configureWebpack: {
optimization: {
splitChunks: false
}
},
css: {
extract: false,
}
}
At the moment when I run vue-cli-service build it compiles all my modules and styles into 1 bundled JS file.
There is an issue however with one of my NPM modules: socket.io-client
It appears that the way that Vue is compiling my app, it is not transpiling something within this package which is causing syntax errors in Internet Explorer 11.
I am fairly sure the problematic code in socket.io-client lies with one of it's dependencies called debug.
What I would like to do is have this package (socket.io-client) also transpiled so that I don't get the error in IE11.
I would have thought that Vue CLI would do this out of the box when you run the build but perhaps something has been setup wrong in my babel or Vue configuration? How could I resolve this issue?
Thanks!
You can use transpileDependencies option in your vue.config.js.
By default babel-loader ignores all files inside node_modules. If you want to explicitly transpile a dependency with Babel, you can list it in this option.
Example:
module.exports = {
...
transpileDependencies: [
'socket.io-client'
]
}
Found the issue here:
https://github.com/socketio/socket.io-client/issues/1328
Reverting the problematic package to previous version fixed it

How to import shared typescript code using create-react-app (no eject)?

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:
Create React App + Typescript In monorepo code sharing: Sounds basically like the same question, but it is not, because it is asking for the case of an ejected React app.
Sharing code between projects using TypeScript and webpack: Also addresses the code sharing problem, but not create-react-app specific, and I don't know if the solution can be transferred to create-react-app, because it requires manual webpack config control.
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.
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
create a craco.config.js file in the client/ (CRA) directory
ensure the craco.config.js has the following content
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;
}
}
};
Replace 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'.
#bluenote10 solution did the trick!
There was only one issue in step #2 that prevents Babel from transpilling ts files from ../../common/src
That is modified version of #bluenote10's code (that finally worked for me)
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 = 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.
No adaptations needed for tsconfig.json.
Import using relative paths, e.g., import * as mymodule from '../../common/src/mymodule'.
The alias solution for craco or rewired create-react-app is react-app-alias for systems as: craco, react-app-rewired, customize-cra
According docs of mentioned systems replace react-scripts in package.json and configure next:
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: {}
}
]
}
all
Configure aliases in json like this:
// tsconfig.paths.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"example/*": ["example/src/*"],
"#library/*": ["library/src/*"]
}
}
}
And add this file in extends section of main typescript config file:
// tsconfig.json
{
"extends": "./tsconfig.paths.json",
// ...
}
question where does ../../common/src/mymodule lives at ?
is it another project ?
If it is another project why dont you link'em
inside common code project run npm link
inside the project that will use the common code: npm link common-project
If there are not two different projects. why does it has to be outside of src ?
The link you posted The create-react-app imports restriction outside of src directory
and your case/problem are two totally different thing, plus they be tripping so hard. let me give you and idea of that problem and yours.
As we already know CRA creates a SPA single page app. which means that there is only one single html file. everything happens at the index.html which is located at <project>/public/index.html. If we compile the code we'll notice something the end bundle might look some like this
build
--static
----js
----css
----media
--index.html
--...more hashed crap...
public
src
and by default process.env.PUBLIC_URL is set to "/"
WHAT?? 🤨 yes look let me show you. there is an old say a picture explains more than 1000 words.
if we look at the image that according to its path it is located at ./src/styles/imageSlack.jpg or some.
So what if we console.log it.
WHAAAT!! where they do that at ? one thing you can do to test my theory is if you console.log(process.env.PUBLIC_URL) any where in yow code. now here is the big diference between this and that.
Browsers natively do not know Typescript.
So either you set yow code inside src and we end happy or
follow the separate of concerns principle and we create an npm package with the shared code and just imported as any other module.

Import from subfolder of npm package

I've been working on creating a small library of React components for use in several other projects. I am publishing the package internally (using a private GitHub repository) and then including in another project. However, when I go to import from a subdirectory of the package I am not able to do so as the paths don't match.
The projects using the package all utilize webpack to bundle/transpile code as I am trying to avoid doing any building in the component library if possible.
Directory Structure
- package.json
- src/
- index.js
- Button/
- index.js
- Button.jsx
- ButtonGroup.jsx
- Header/
- index.js
- Header.jsx (default export)
package.json
...
"main": "./src/index.js",
"scripts": "",
...
src/Button/index.js
import Button from './Button';
import ButtonGroup from './ButtonGroup';
export default Button;
export { Button, ButtonGroup};
src/index.js
Is this file actually necessary if only importing from subdirectories?
import Button from './Button';
import ButtonGroup from './Button/ButtonGroup';
import Header from './Header';
export { Button, ButtonGroup, Header };
Other Project
// This project is responsible for building/transpiling after importing
import { Button, ButtonGroup } from 'components-library/Button';
Example
Material-UI is a library of React components that is used by requiring in the following fashion: import { RadioButtonGroup } from 'material-ui/RadioButton. I've tried to figure out how this works for them but to no avail yet.
Similar Questions
How would I import a module within an npm package subfolder with webpack?
This is very nearly the correct approach I require, except that the import path used there involved the src/ directory, which I am trying to avoid (should be component-library/item, not component-library/src/item (which does work currently though))
Publishing Flat NPM Packages
This is exactly what I want except that I was hoping to not have a "build" phase in the package (rely on importing locations to build/transpile)
Questions
Can I skip the src/ directory somehow in the import path?
Can I skip any type of build phase in the package (so developers don't have to build before committing)?
How does a package similar to material-ui handle this?
Can I skip the src/ directory somehow in the import path?
Yes. Using the package.json "exports" field, which should be supported by Webpack in a near future (see this issue), but has already been supported by Node since Node 12 LTS following the Bare Module Specifier Resolution proposal:
package.json
...
"main": "./src/index.js",
"type": "module",
...
"exports": {
"./Button": "./src/Button/index.js",
"./Header": "./src/Header/index.js"
},
...
Now, the following code:
// This project is responsible for building/transpiling after importing
import { Button, ButtonGroup } from 'components-library/Button';
should be translated to:
import { Button, ButtonGroup } from 'components-library/src/Button/index.js';
which should correctly import the requested modules.
Caveat
Now, it would certainly be tempting to try a simpler version like:
...
"exports": {
"./Button": "./src/Button/",
"./Header": "./src/Header/"
},
...
so as the usual import statement
import { ... } from 'components-library/Button';
gets translated to
import { ... } from 'components-library/src/Button';
This looks nice, but it will not work in this case, because your submodules don't have each their own package.json file but rely on their index.js file to be found.
/!\ Unlike in CommonJS, there is no automatic searching for index.js or index.mjs or for file extensions.
src/index.js - Is this file actually necessary if only importing from subdirectories?
I don't think so, but you can keep it if you want.
Can I skip any type of build phase in the package?
Using the "exports" field does not require you to transpile your code.
The answer may depend on how you installed your components library. If you did it via either npm install <git-host>:<git-user>/<repo-name> or npm install <git repo url>,
You should be able to import {Button} from 'component-library/Button' as is, according to your first linked question. Similar to Node's require() resolution, Webpack should resolve subdirectories within component-library relative to component-library's entry point. You can find the docs on customizing the resolution behavior via the webpack.config.resolve property. material-ui seems to rely on resolving subdirectory imports from the module entry directory.
To distribute an ES module library, there's no need for building before distribution. However, projects such as create-react-app may need a pre-transpiled version.
Alternately, you can write import {Button} from 'components-library'.
Webpack will trace the dependencies back through each index without a fuss.
you have to install babel-plugin-module-resolver package
Specify the package relative path in your .babelrc file alias like this
{
"plugins": [
["module-resolver", {
"alias": {
"components-library": "./node_module/components-library"
}
}]
]
}
then you can import subdir of npm package like this
import { Button, ButtonGroup } from 'components-library/Button';
One of the possible solutions there is webpack aliasing system.
You can create another project, call it for example 'app-aliases', so your aliases will be reusable.
This project will has one js file with all of your packages paths:
const path = require('path');
module.exports = {
'#components': path.resolve(__dirname, 'node_modules/components-library/src'),
'#another': path.resolve(__dirname, 'node_modules/any/path/you/want'),
}
And then add it to the webpack configuration in any project which will be responsible for building/transpiling:
webpack.config.js
const appAliases = require('app-aliases');
const config = {
...
resolve: {
alias: {
...appAlises
}
}
}
In the runtime code you will be able to use it like this:
import {Button} from '#components/Button';
import {Something} from '#another'
If you are using typescript you will need to add the same aliases to the paths tsconfig property.
So answers to your questions are:
Yes, you can use any path in aliases
Yes, it is not necessary to build all of your projects
I see that now mui uses imports from directi packages (core for example), see https://material-ui.com/components/radio-buttons/ there is import Radio from '#material-ui/core/Radio';. But I hope they using re-export that I described below.
Also about node.js resolution mechanism.
When you import some library it tries to find node_modules/some-library/package.json and then main property inside it. This property should lead to your main entry point. Usually it is src/index.js (you should set it in package.json if it is no there yet). In this file you can re-export anything you want from internals file structure and will be able to use it without the full path.
Please see this repo for some examples.
I'am an angular developer never used react but what I could tell that material-ui are using monorepo where same concept exists in angular where we create one workspace and this workspace hold multiple project/packages as named in react. for more info Workspaces with Yarn
Material-ui using fake paths in tsconfig to make it appears like src folder doesn't exists this from the git you provided: tsconfig.json
This is possible but requires publishing a curated dist folder rather then the root of your project.
The whole thing is rather simple if you understand how module resolution works, and you just need a small script to prepare your distribution.
Lest I repeat all the details here, please see my answer for Importing from subfolders for a javascript package.

People ask for the # character in import form '#' in nodejs or vuejs [duplicate]

For example:
import Component from '#/components/component'
In the code I'm looking at it behaves like ../ going up one level in the directory relative to the file path, but I'd like to know more generally what it does. Unfortunately I can't find any documentation online due to the symbol searching problem.
The meaning and structure of the module identifier depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. From a JavaScript language perspective, the module identifier is completely opaque. So it really depends on which module loader/bundler you are using.
You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'
Edit: One reason it exists is because import Component from 'components/component' doesn't do that but instead search in the node_modules folder
Know it's old, but I wasn't exactly sure how it's defined, so looked it up, came by, dug a little deeper and finally found this in my Vue-CLI (Vue.js) generated Webpack config
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'#': path.join(__dirname, '..', dir)
}
},
so it's an alias which in this case points to the root of vue-cli generated src directory of the project
Update: As correctly mentioned by #aderchox in the comments, this is a general Webpack feature and not limited to Vue
To make Ben's answer more comprehensive:
First you need to add babel-plugin-root-import in your devDependencies in package.json (If using yarn: yarn add babel-plugin-root-import --dev).
Then in your .babelrc add the following lines into plugins key:
"plugins": [
[
"babel-plugin-root-import",
{
"rootPathPrefix": "#"
}
]
]
Now, you can use #. For example:
Instead of
import xx from '../../utils/somefile'
You Can
import xx from '#/utils/somefile'
As said above, this feature is not in JS by default. You have to use a babel plugin to enjoy it. And its job is simple. It allows you to specify a default root source for your JS files and helps you map your file imports to it.
To get started install through either npm:
npm install babel-plugin-root-import --save-dev
or
yarn add babel-plugin-root-import --dev
Create a .babelrc in the root of your app and configure these settings to your taste:
{
"plugins": [
["babel-plugin-root-import", {
"rootPathSuffix": "the-preferred/root/of-all-your/js/files",
"rootPathPrefix": "#"
}]
]
}
With the config above, you can simply import from that source like:
import Myfile from "#/Myfile"
without doing all this funky stuff:
"/../../../Myfile"
Note that you can also change the symbol to anything like "~" if that floats your boat.
I am using VS code to build react native Apps.
What you need is:
create a jsconfig.json under root path of your App
in your jsconfig.json, add the following code:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "commonjs",
"paths": {
"#/*": ["src/*"],
"#components/*": ["src/components/*"],
"#core/*": ["src/core/*"]
}
},
"exclude": ["node_modules"]
}
basically like "shortcut" : ["abs_path"]
In case you are using Typescript, you could achieve this by simply using your tsconfig.json like this:
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"#lib/*": ["app/lib/*"]
}
},
}
// # is an alias to /src
Inspired by Can Rau's answer I made a similar discovery in my src/views/Home.vue file. This file was created with the latest (July 2021, Ubuntu 20.04) versions: npx #vue/cli create myfirstvue --default.
I "inferred" it was /src but wanted to know why, because Ben's accepted answer said it would be the root of my project, which in fact is the parent, of /src.
Here is Home.vue:
...
<script>
// # is an alias to /src
import HelloWorld from '#/components/HelloWorld.vue'
</script>
It is defined by Vue Webpack template, which I learned from this other SO answer.
It is a way of remapping module paths, not part of the ES itself, you have to use babel import feature.

What does the # symbol do in javascript imports?

For example:
import Component from '#/components/component'
In the code I'm looking at it behaves like ../ going up one level in the directory relative to the file path, but I'd like to know more generally what it does. Unfortunately I can't find any documentation online due to the symbol searching problem.
The meaning and structure of the module identifier depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. From a JavaScript language perspective, the module identifier is completely opaque. So it really depends on which module loader/bundler you are using.
You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'
Edit: One reason it exists is because import Component from 'components/component' doesn't do that but instead search in the node_modules folder
Know it's old, but I wasn't exactly sure how it's defined, so looked it up, came by, dug a little deeper and finally found this in my Vue-CLI (Vue.js) generated Webpack config
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'#': path.join(__dirname, '..', dir)
}
},
so it's an alias which in this case points to the root of vue-cli generated src directory of the project
Update: As correctly mentioned by #aderchox in the comments, this is a general Webpack feature and not limited to Vue
To make Ben's answer more comprehensive:
First you need to add babel-plugin-root-import in your devDependencies in package.json (If using yarn: yarn add babel-plugin-root-import --dev).
Then in your .babelrc add the following lines into plugins key:
"plugins": [
[
"babel-plugin-root-import",
{
"rootPathPrefix": "#"
}
]
]
Now, you can use #. For example:
Instead of
import xx from '../../utils/somefile'
You Can
import xx from '#/utils/somefile'
As said above, this feature is not in JS by default. You have to use a babel plugin to enjoy it. And its job is simple. It allows you to specify a default root source for your JS files and helps you map your file imports to it.
To get started install through either npm:
npm install babel-plugin-root-import --save-dev
or
yarn add babel-plugin-root-import --dev
Create a .babelrc in the root of your app and configure these settings to your taste:
{
"plugins": [
["babel-plugin-root-import", {
"rootPathSuffix": "the-preferred/root/of-all-your/js/files",
"rootPathPrefix": "#"
}]
]
}
With the config above, you can simply import from that source like:
import Myfile from "#/Myfile"
without doing all this funky stuff:
"/../../../Myfile"
Note that you can also change the symbol to anything like "~" if that floats your boat.
I am using VS code to build react native Apps.
What you need is:
create a jsconfig.json under root path of your App
in your jsconfig.json, add the following code:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "commonjs",
"paths": {
"#/*": ["src/*"],
"#components/*": ["src/components/*"],
"#core/*": ["src/core/*"]
}
},
"exclude": ["node_modules"]
}
basically like "shortcut" : ["abs_path"]
In case you are using Typescript, you could achieve this by simply using your tsconfig.json like this:
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"#lib/*": ["app/lib/*"]
}
},
}
// # is an alias to /src
Inspired by Can Rau's answer I made a similar discovery in my src/views/Home.vue file. This file was created with the latest (July 2021, Ubuntu 20.04) versions: npx #vue/cli create myfirstvue --default.
I "inferred" it was /src but wanted to know why, because Ben's accepted answer said it would be the root of my project, which in fact is the parent, of /src.
Here is Home.vue:
...
<script>
// # is an alias to /src
import HelloWorld from '#/components/HelloWorld.vue'
</script>
It is defined by Vue Webpack template, which I learned from this other SO answer.
It is a way of remapping module paths, not part of the ES itself, you have to use babel import feature.

Categories

Resources