I'm currently playing around with React Native. I'm trying to structure my app, however it's starting to get messy with imports.
--app/
-- /components
-- Loading.js
-- index.ios.js
Now, within my index.ios.js i'm able to simply do:
import Loading from './components/Loading';
However, when I start to create more components, with a deeper directory struture, it starts to get messy:
import Loading from '.../../../../components/Loading';
I understand the preferred solution would be to make private npm modules for things, but that's overkill for a small project.
You could do a global.requireRoot type solution on the browser, but how do I implement this with import?
Had the same issue with React.
So i wrote some plugin for babel which make it possible to import the modules from the root perspective - the paths are not shorter - but it's clear what you import.
So instead of:
import 'foo' from '../../../components/foo.js';
You can use:
import 'foo' from '~/components/foo.js';
Here is the Plugin (tested and with a clear README)
The react documentation explain how to do that:
https://create-react-app.dev/docs/importing-a-component/#absolute-imports
just add a jsconfig.json in your project root:
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
If you are using Webpack you can configure it via the resolve property to resolve a your import path.
Webpack 1
resolve: {
root: [
path.resolve(__dirname + '/src')
]
}......
Webpack 2
resolve: {
modules: [
path.resolve(__dirname + '/src'),
path.resolve(__dirname + '/node_modules')
]
}.....
After that you can use
import configureStore from "store/configureStore";
instead of the:
import configureStore from "../../store/configureStore";
Webpack will configure your import path from the passed resolve param.
The same stuff you can do with System.js loader but with it's own config param (it's can be map or path. Check it in the System.js documentation) (if you would like to use it. It's mostly for a Angular 2 case. But I suggest: don't use standard System.js even if you are working with ng2. Webpack is much better).
I just checked out a React project which is more than 6 months old and for some reasons my imports no longer worked. I tried the first answer:
import 'foo' from '~/components/foo.js';
Unfortunately this did not work.
I added an .env file in the root of my project at the same level as my package.json. I added the following line to that file and this fixed my imports in my project.
NODE_PATH=src/
If you're using Create-React-App, you just need to change the environmental variable NODE_PATH to contain the root of your project.
In your config.json do the following change to set this variable before running the react-scripts commands:
"scripts": {
"start": "cross-env NODE_PATH=. react-scripts start",
"build": "cross-env NODE_PATH=. react-scripts build",
"test": "cross-env NODE_PATH=. react-scripts test",
"eject": "react-scripts eject"
},
We're using the npm library cross-env because it works on unix and windows. The syntax is cross-env var=value command.
Now instead of import module from '../../my/module' we can do import module from 'src/my/module'
Extra details on implementation
Its important to note that cross-env's scope is limited to the command it executes, so cross-env var=val command1 && command2 will only have var set during command1. Fix this if needed by doing cross-env var=val command1 && cross-env var=val command2
create-react-app gives precedence to folders in node_modules/ over whats in NODE_PATH, which is why we're setting NODE_PATH to "." instead of "./src". Using "." requires all absolute imports to start with "src/" which means there should never be a name conflict, unless you're using some node_module called src.
(Note of caution: The solution described above replaces the variable NODE_PATH. Ideally we would append to it if it already exists. NODE_PATH is a ":" or ";" separated list of paths, depending on if its unix or windows. If anyone finds a cross-platform solution to do this, I can edit my answer.)
With webpack you can also make paths starting with for example ~ resolve to the root, so you can use import Loading from '~/components/Loading';:
resolve: {
extensions: ['.js'],
modules: [
'node_modules',
path.resolve(__dirname + '/app')
],
alias: {
['~']: path.resolve(__dirname + '/app')
}
}
The trick is using the javascript bracket syntax to assign the property.
In Webpack 3 the config is slightly diffrent:
import webpack from 'webpack';
import {resolve} from 'path';
...
module: {
loaders: [
{
test: /\.js$/,
use: ["babel-loader"]
},
{
test: /\.scss$|\.css$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
},
resolve: {
extensions: [".js"],
alias: {
["~"]: resolve(__dirname, "src")
}
},
If you're using Create React App you can add paths.appSrc to resolve.modules in config/webpack.config.dev.js and config/webpack.config.prod.js.
From:
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(...
To:
resolve: {
modules: [paths.appSrc, 'node_modules', paths.appNodeModules].concat(...
Your code would then work:
import Loading from 'components/Loading';
Related
TL;DR
How can you tell SWC to compile CSS files imported in React components?
How can you tell SWC to compile absolute imports in tests and in React components?
Here is a minimal reproducible example.
Context
We're migrating from Babel to SWC. (I asked a question a little while ago. I'm improving on that question's answer.)
We're migrated the command from:
"test": "NODE_ENV=test riteway -r #babel/register 'src/**/*.test.js' | tap-nirvana",
to
"test": "SWC_NODE_PROJECT=./jsconfig.json riteway -r #swc-node/register src/**/*.test.js | tap-nirvana",
where the jsconfig.json looks like this:
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "./src",
"jsx": "react-jsx"
}
}
If we write try to compile a test for a self-contained component (no absolute imports, no CSS) it works:
import { describe } from 'riteway';
import render from 'riteway/render-component';
function HomePageComponent({ user: { email } }) {
return <p>{email}</p>;
}
describe('home page component', async assert => {
const user = { email: 'foo' };
const $ = render(<HomePageComponent user={user} />);
assert({
given: 'a user',
should: 'render its email',
actual: $('p').text(),
expected: user.email,
});
});
The test compiles fine.
With Babel we had a .babelrc like this:
{
"env": {
"test": {
"plugins": [
[
"module-resolver",
{
"root": [
"."
],
"alias": {
"components": "./src/components",
"config": "./src/config",
"features": "./src/features",
"hocs": "./src/hocs",
"hooks": "./src/hooks",
"pages": "./src/pages",
"redux": "./src/redux",
"styles": "./src/styles",
"tests": "./src/tests",
"utils": "./src/utils"
}
}
]
]
}
},
"presets": [
[
"next/babel",
{
"ramda": {}
}
]
],
"plugins": [
["styled-components", { "ssr": true }]
]
}
Where the styles where taken care of by styled-components and the absolute imports where defined via the module-resolver plugin. (We switched away from styled-components to CSS modules, which is why we import from .module.css CSS files. Anyways ...)
If we write the test how we wanted to write it with their actual imports like this:
import { describe } from 'riteway';
import render from 'riteway/render-component';
import { createPopulatedUserProfile } from 'user-profile/user-profile-factories';
import HomePageComponent from './home-page-component';
describe('home page component', async assert => {
const user = createPopulatedUserProfile();
const $ = render(<HomePageComponent user={user} />);
assert({
given: 'a user',
should: 'render its email',
actual: $('p').text(),
expected: user.email,
});
});
It fails with:
$ SWC_NODE_PROJECT=./jsconfig.json riteway -r #swc-node/register src/features/home/home-page-component.test.js | tap-nirvana
/Users/janhesters/dev/my-project/src/features/home/home.module.css:1
(function (exports, require, module, __filename, __dirname) { .container {
^
SyntaxError: Unexpected token '.'
when we leave in the CSS import in home-page-component.js, or with:
$ SWC_NODE_PROJECT=./jsconfig.json riteway -r #swc-node/register src/features/home/home-page-component.test.js | tap-nirvana
node:internal/modules/cjs/loader:936
throw err;
^
Error: Cannot find module 'user-profile/user-profile-factories'
Require stack:
- /Users/janhesters/dev/my-project/src/features/home/home-page-component.test.js
- /Users/janhesters/dev/my-project/node_modules/riteway/bin/riteway
respectively, when we get rid of the CSS import.
How can we help SWC understand CSS (or mock CSS modules) and how can we help it understand absolute imports?
We already set the baseUrl in jsconfig.json ...
About absolute path
You already add baseUrl in the jsconfig.json file but didn't add the paths, you should modify your config file like mine:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"#screens": ["./screens"],
"#shared": ["./shared"],
"#shared/*": ["./shared/*"]
},
The paths are the alias of module-resolver, and I guess your root shouldn't be ".", it should be exactly like your jsconfig.json file, I mean the baseUrl value.
"plugins": [
[
"module-resolver",
{
"root": ["./src"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".json"],
"alias": {
"#screens": "./src/screens",
"#shared": "./src/shared"
}
}
]
],
If you have Webpack, you should have alias config in your resolve key off Webpack config object, like mine:
const resolve = {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: {
'#screens': path.join(__dirname, 'src/screens/'),
'#shared': path.join(__dirname, 'src/shared/'),
},
modules: ['src', 'node_modules'],
descriptionFiles: ['package.json'],
};
About CSS
Actually, you are using CSS file as CSS-Modules not like recommended NexJS doc, in docs developer should import CSS like import './styles.css' and then use it as string in JSX like <div className="main"
But
You are importing it like a module (CSS-Module):
// you did it
import styles from './styles.css';
<div className={styles.main}
As you know it is built-in support by this doc, it is supported by NextJS, but SWC cannot understand it. I put in hours to find a way for it, but it seems SWC doesn't support CSS-Module yet. you should create your own plugin for SWC to support CSS-Module.
How can we help SWC understand CSS (or mock CSS modules)? - SWC doesn't understand css natively, and neither did Babel. As you noted, when you were using Babel, the plugin styled-components took care of this. You'll need to do the same with SWC. I can't find an existing SWC plugin that does this, but you can roll your own. Obviously this is a pain, but such is the cost of using new tooling.
How can we help SWC understand absolute imports? - The .swrc options for baseUrl and paths should do what you want, but that, too, seems to have some issues.
You may have better luck creating issues directly in the #swc-node GitHub repo, but given the comments there it feels like you might be SOL for a while. Might be faster/easier to rewrite your tests using one of the libraries that Next supports out of the box.
I was able to solve all issues without writing any plugins. I pushed the solution to my example repo from the question.
Firstly, use the official SWC project's register. This means, you have to compile your tests like this:
"test": "riteway -r #swc/register 'src/**/*.test.js' | tap-nirvana",
You can install it by running:
yarn add --dev #swc/core #swc/register
We need this version of register, because it takes into account an .swcrc file, which the other one did not.
Secondly, you need both "path" and "baseUrl" to fix the absolute imports because for some reason SWC doesn't infer all paths with only a "baseUrl" provided. And you need to adapt your .swcrc to handle React.
{
"jsc": {
"baseUrl": "./src",
"paths": {
"*.css": ["utils/identity-object-proxy.js"],
"utils/*": ["utils/*"]
},
"parser": {
"jsx": true,
"syntax": "ecmascript"
},
"transform": {
"react": {
"runtime": "automatic"
}
}
},
"module": {
"type": "commonjs"
}
}
Thirdly, to solve .css you need to remap all imports to .css files to an identity object proxy, which you can see in the .swcrc example above. An identity object proxy is an object that, when you reference any property, returns the stringified key that you're trying to reference. You can create one yourself like this:
const identityObjectProxy = new Proxy(
{},
{
get: function getter(target, key) {
if (key === '__esModule') {
return false;
}
return key;
},
},
);
export default identityObjectProxy;
For the .css remap to go into effect you need to make all your imports to .css files absolute imports. (import styles from './styles.module.css won't work!)
I want to create RSS script using Next.js.
So I put up a script in a subfolder inside the root folder scripts/ & named it build-rss.js
next.config.js
module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.svg$/,
issuer: { and: [/\.(js|ts|md)x?$/] },
use: [
{
loader: '#svgr/webpack',
options: {
prettier: false,
svgo: true,
svgoConfig: { plugins: [{ removeViewBox: false }] },
titleProp: true,
},
},
],
})
if (!options.dev && options.isServer) {
const originalEntry = config.entry
config.entry = async () => {
const entries = { ...(await originalEntry()) }
entries['./scripts/build-rss'] = './scripts/build-rss.js'
return entries
}
}
if (!options.isServer) {
config.resolve.fallback.fs = false
}
return config
},
}
When I try to run my script npm run build:development which in package.json represents:
"scripts": {
"clean": "rimraf .next",
"dev": "next dev",
"export": "next export",
"start": "next start",
"lint": "next lint",
"build:development": "next build && npm run export && npm run rss:development",
"build": "next build && npm run export && npm run rss",
"rss:development": "node ./.next/server/scripts/build-rss.js",
"rss": "node ./.next/serverless/scripts/build-rss.js"
}
It throws an error saying:
Error: Cannot find module '../../webpack-runtime.js'
But I checked. The file does exist.
The blunder is this used to work earlier. Probably few versions ago when my other project used the same combination.
I have made a complete reproduction showcasing the error → https://github.com/deadcoder0904/next-script-rss-error
Just clone it, install it & try the script npm run build:development in the terminal to see the error.
Based on our conversation:
entry: path.join(__dirname, '..', 'path/to/file')
That's what a webpack entry looks like. It can also be an array or an object:
entry: [
path.join(__dirname, '..', 'path/to/file'),
// other entries here
]
Whereas you're already getting the whole webpack config:
webpack: (config, options)
So doing:
const originalEntry = config.entry
config.entry = async () => {
const entries = { ...(await originalEntry()) }
entries['./scripts/build-rss'] = './scripts/build-rss.js'
return entries
}
Makes no sense to me if you can just do:
config.entry.push('./scripts/build-rss')
// config.entry['./scripts/build-rss'] = './scripts/build-rss.js'
Unless I miss something with how nextjs is loading the webpack config.
Even then I'd suggest that you use path.join in order to ensure it's loaded to the correct location, because that relative root will execute from wherever webpack is compiled from.
Along with that in your first project you used nextjs v10 and now you're using nextjs v11, which has an upgrade from webpack 4 to 5, which is a major upgrade. I don't know the details, I can only speculate, but under no conditions should you assume that "because your previous project was working this one should using the same stuff", it won't necessarily (especially not in this case).
The first intuitive thing I thought was that webpack should by default bundle everything to a single output file, unless the configuration for that was changed by nextjs (I don't know). So using a script you added to entries didn't make sense to me, because it wouldn't exist. But you're saying that it does exist so I can only assume that webpack is configured to do code splitting and outputs each entry to a different file. In which case I have no idea. As far as I'm aware in webpack 5 (I don't know about webpack 4) code splitting is disabled in dev and enabled in production so your problem is likely a discrepancy between dev and production.
Perhaps the last thing you can try is to change your !options.dev, because right now you're only adding that script when it's production but you're trying to run the script using development.
If you really just have to get it working you can downgrade your nextjs to the previous version you were using (v10), even though that's not really a solution.
Other than that I'm out of ideas.
Not sure if you are still looking for an answer, but simply changing the webpack entry as follows seems to have fixed the problem.
entries['scripts/build-rss'] = './scripts/build-rss.js';
// instead of entries['./scripts/build-rss']
I had the same Error! I deleted the .next Folder and did an npm run dev, It started to work for me!
How to set import shortcuts/aliases in create-react-app?
From this:
import { Layout } from '../../Components/Layout'
to this:
import { Layout } from '#Components/Layout'
I have a webpack 4.42.0 version.
I don't have a webpack.config.js file in the root directory. I've tried to create one myself with this code inside:
const path = require('path')
module.exports = {
resolve: {
alias: {
'#': path.resolve(__dirname, 'src/'),
}
}
};
But it doesn't seem to work. I've seen the NODE_PATH=. variant in .env file. But I believe, it is deprecated - better not to use. And also, I have a posstcss.config.js file. Because I've installed the TailwindCss and I import the CSS library there. I've tried to paste the same code there, but it also didn't work.
It is finally possible with Create React App v.3
Just put:
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
into jsconfig.json or tsconfig.json if you use Typescript
Here is wonderful article about this.
Simplest way to archive this follow below steps. (same way as #DennisVash showed as but in simple form)
Installation - install and setup CRACO.
yarn add #craco/craco
# OR
npm install #craco/craco --save
Create a craco.config.js file in the root directory and configure CRACO:
/* craco.config.js */
const path = require(`path`);
module.exports = {
webpack: {
alias: {
'#': path.resolve(__dirname, 'src/'),
'#Components': path.resolve(__dirname, 'src/components'),
'#So_on': path.resolve(__dirname, 'src/so_on'),
}
},
};
Update the existing calls to react-scripts in the scripts section of your package.json file to use the craco CLI:
/* package.json */
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
Done! Setup is completed.
Now let's test it.
// Before
import Button from "./form/Button"
import { Layout } from '../../Components/Layout'
// After
import Button from "#/form/Button"
import { Layout } from '#Components/Layout'
Documentation Craco
Thank you. :)
// Absolute path: paths which are relative to a specific path
import Input from 'components' // src/components
import UsersUtils from 'page/users/utils' // src/page/users/utils
// Alias path: other naming to specific path
import Input from '#components' // src/components
import UsersUtils from '#userUtils' // src/page/users/utils
In order for webpack's aliases to work, you need to configure the default webpack.config.js of create-react-app.
The official way is to use the eject script.
But the recommended way is to use a library without ejecting (find the most modern library for that).
VSCode IntelliSense
In addition, you should add jsconfig.json file for path IntelliSense in VSCode (or tsconfig.json), see followup question.
Now such code with IntelliSense will work:
// NOTE THAT THOSE ARE ALIASES, NOT ABSOLUTE PATHS
// AutoComplete and redirection works
import {ColorBox} from '#atoms';
import {RECOIL_STATE} from '#state';
If you want to use:
// this:
import MyUtilFn from 'utils/MyUtilFn';
// Instead of this:
import MyUtilFn from '../../../../utils/MyUtilFn';
use the node module plugin for resolving the urls https://www.npmjs.com/package/babel-plugin-module-resolver. By installing it and adding it to your webpack/babel.rc file.
Step 1
yarn add --dev babel-plugin-module-resolver
add this plugin
Step 2
in babel.config.js file
ALIAS NAME ALIAS PATH
#navigation ./src/navigation
#components ./src/components
#assets ./assets
[
"module-resolver",
{
root: ["./src"],
alias: {
"^~(.+)": "./src/\\1",
},
extensions: [
".ios.js",
".android.js",
".js",
".jsx",
".json",
".tsx",
".ts",
".native.js",
],
},
];
Step 3
import example
import SomeComponent from '#components/SomeComponent.js';
Step 4
restart server
yarn start
Reference link: How to use import aliases with React native and VSCode
My project structure is as follows:
- workspace
- customPackage
- customIndex.js
- myProject
- index.js
- myProject2
- index.js
In dev, I want to import the package from my local workspace like following:
//index.js
import something from '../customePackage/customIndex.js'
where as in production I need the import to work from npm modules like the following:
//index.js
import something from 'customPackage';
The purpose being to be able to use the local changes in the package(without going through the commit cycle). Finally after testing the package can be pushed and used normally via npm package.
How to do this in an efficient way without having to make code changes every time?
You can use Resolve#alias with Webpack:
resolve: {
alias: {
"customPackage": process.env.NODE_ENV === "production" ?
"customPackage" :
path.resolve(__dirname, "../customePackage/customIndex.js")
}
}
Then in your source, you only need to do:
import something from 'customPackage';
And it will point to the correct package. Obviously you need to set the NODE_ENV environment variable, or change that depending on your build environment.
if you are already using webpack you can make two different entry points:
entry: {
bundle: './Scripts/index.tsx',
bundle2: './Scripts/index2.tsx'
},
output: {
publicPath: "/js/",
path: path.join(__dirname, '/wwwroot/js/'),
filename: '[name].js'
},
then in index you import main module and in index2 import your test module. So you will have different bundle files bundle.js and bundle2.js.
I'm trying to npm link a module to a project using webpack as its bundler. Of course, after trying many things, I keep getting this error:
ERROR in ./src/components/store/TableView.jsx
Module not found: Error: Cannot resolve module 'react-bootstrap-table'
Here are the exact steps I take when doing this:
1.) cd ../forks/react-bootstrap-table
2.) npm link
(success, checked ~/.nvm/.../node_modules/react-bootstrap-table for symlink and it's there)
3.) cd ../../projRoot/
4.) npm link react-bootstrap-table
(no errors thrown?, says successful link)
5.) node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js
Solutions I've tried:
- https://webpack.github.io/docs/troubleshooting.html
- How to make a linked component peerDepdencies use the equivalent node_modules of the script being linked to?
- And many purple links on google serps
webpack.config.js
const webpack = require('webpack')
const path = require('path')
const ROOT_PATH = path.resolve(__dirname)
module.exports = {
devtool: process.env.NODE_ENV === 'production' ? '' : 'source-map',
entry: [
'webpack/hot/only-dev-server',
'./src/index.js'
],
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['react-hot','babel']
},
{
test: /\.scss$/,
loaders: ['style','css','sass'],
exclude: /node_modules/
},
{
test: /\.css$/,
loaders: ['style','css']
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader'
}
]
},
resolve: {
extensions: ['', '.js', '.jsx'],
fallback: path.resolve(__dirname, './node_modules')
},
resolveLoader: {
fallback: path.resolve(__dirname, './node_modules')
},
output: {
path: process.env.NODE_ENV === 'production' ? path.resolve(ROOT_PATH, 'app/dist') : path.resolve(ROOT_PATH, 'app/build'),
publicPath: '/',
filename: 'bundle.js'
},
devServer: {
contentBase: path.resolve(ROOT_PATH),
historyApiFallback: true,
hot: true,
inline: true,
progress: true,
stats: 'errors-only',
host: '192.168.1.115'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
Notes:
1. this is the only symlink in the project
2. I run npm install inside forked version (also tried without, doesn't work)
3. I use NVM, but I have used symlinks before without webpack successfully.
I've been at this for a few days now, any help will be much appreciated.
I was facing a similar issue with webpack and ended up by adding this my webpack.config.js:
module.exports = {
resolve: {
symlinks: false
}
};
Here is the link to webpack docs. Since your question there happened a lot to webpack and their api, so I do not know how much relevance my answer still has according to your question. But for people facing this or a similar issue today this could be a solution. As to be seen, there are still people complaining about:
Webpack GitHub Issue 1643
Webpack GitHub Issue 1866
Also make sure you have bundle and yarn installed and executed in the linked package
Okay guys, this is specific to my use case, but make sure to follow all the instructions to completely build the library you are symlinking. Initially, I a npm install and gulp build, but that wasn't enough. I had to run a few extra commands to get the library to fully build.
Now it works! If you are still having issues, go through the documentation for each library you are symlinking, and use my webpack config as a template for resolving external libraries.
Just in case it's useful for others, the solution of adding the resolve.symlinks configuration to false suggested by #Beat was not enough in my case, I had to perform the following steps to solve it:
In the library:
Setup the libraries that are generating issues as peerDependencies in the package.json instead of dependencies or devDependencies, e.g. in my case react:
"peerDependencies": {
"react": "^16.8.6",
...
}
run npm install
build the library (in my case, with a rollup -c npm script
In my main app:
change the version of my library to point to my local project with a relative path in package.json, e.g.
"dependencies": {
"my-library": "file:../../libraries/my-library",
...
}
Add resolve.symlinks = false to my main app's webpack configuration
Add --preserve-symlinks-main and --preserve-symlinks to my package.json start script, e.g:
"scripts": {
"build": "set WEBPACK_CONFIG_FILE=all&& webpack",
"start": "set WEBPACK_CONFIG_FILE=all&& webpack && node --preserve-symlinks-main --preserve-symlinks dist/server.js",
}
run npm install
run npm run start