Create React App: Transpile JSX of external package in node_modules - javascript

I'm trying to use the react-scratchblocks package on my react project.
I've created my project using the create-app-react command.
After importing the package I got the following error:
Failed to compile.
./node_modules/react-scratchblocks/src/Scratchblocks.js
SyntaxError: /Users/jorge/Documents/React/elimu-analyzer-frontend/node_modules/react-scratchblocks/src/Scratchblocks.js: Unexpected token (45:6)
43 | const scripts = this.parseScripts(this.props.code);
44 | return (
> 45 | <div className={this.classNames()}>
| ^
46 | {scripts.map((script,i) => (
47 | <Scratchblock key={i} className="script" script={script}/>
48 | ))}
I know that the jsx it's not been recognized, but what should I do to make this package work? Remember: i've used the create-rect-app to create my React project.
Thanks.
UPDATE 1:
module.exports = function () {
return {
overrides: [{
test: ["./node_modules/react-scratchblocks"],
presets: ["#babel/preset-react"]
}],
};
}
UPDATE 2:
Component that where I import the react-scratchblocks.
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import './styles.css';
import Fire from '../../config/Fire';
import Realtime from '../Realtime';
import Scratchblocks from 'react-scratchblocks'
function Content(props) {
const [menuOption, setMenuOption] = useState(1);
async function logout() {
await Fire.logout();
props.history.push('/');
console.log('oi');
}
if (menuOption === 0) {
return (
<div class='content'>
<Realtime />
</div>
);
}
else if (menuOption === 1) {
return (
<div class="content">
<button onClick={logout}> OUTRA OPÇÃO </button>
</div>
);
}
}
export default withRouter(Content);

Create React App (CRA) only transpiles standard JavaScript syntax inside node_modules.
This does not include JSX compilation. Package react-scratchblocks errors due to untranspiled JSX:
SyntaxError: .../Scratchblocks.js: Unexpected token (45:6)
Statement from maintainers (link):
We only compile valid JavaScript syntax in node_modules. JSX is not valid JavaScript. You are using JSX there.
The reason we've taken this stance is because compiling non-standard syntax tightly couples libraries to build tools.
It's also hard to draw a line once you allow experimental things. Most people will want to use not just JSX, but also experimental transforms like class properties. Or decorators. Now we have to argue with every library maintainer about which transforms we want to support, and which we don't.
Hence package authors would have needed to transpile JSX them selves before distribution.
To transpile JSX manually1 you can apply the Babel React preset to react-scratchblocks inside node_modules:
babel node_modules/react-scratchblocks \
-d node_modules/react-scratchblocks/dist \
--presets=#babel/preset-react
The build step might be outsourced into its own config file (transpile.js):
module.exports = {
...
overrides: [
{
test: ["./node_modules/react-scratchblocks"],
presets: ["#babel/preset-react"]
}
]
};
babel node_modules/react-scratchblocks \
-d node_modules/react-scratchblocks/dist \
--config-file ./transpile.js
Then adjust main entry inside node_modules/react-scratchblocks/package.json to point to the previously transpiled version in dist:
"main": "dist/index.js",
patch-package can further automate the process of fixing broken packages.
Remember this is only a workaround - the duty is on package developers to distribute an npm package with standard JavaScript features.
1 A different alternative would be to adjust Webpack config (only possible with ejected CRA).

I personally suggest you to use craco (see #craco/craco)
Craco is a powerful tool that allows you to edit built-in create-react-app configuration without forcing you to eject the project.
How to install it
run npm install #craco/craco --save-dev
run npm install craco-babel-loader --save-dev
create craco.config.js in the root folder of the project
update the scripts in package.json:
react-scripts start -> craco start
react-scripts build -> craco build
...
this is the content of the configuration craco file
const path = require('path')
const fs = require('fs')
const cracoBabelLoader = require('craco-babel-loader')
// manage relative paths to packages
const appDirectory = fs.realpathSync(process.cwd())
const resolvePackage = relativePath => path.resolve(appDirectory, relativePath)
module.exports = {
plugins: [
{
plugin: cracoBabelLoader,
options: {
includes: [
resolvePackage('node_modules/package-to-transpile'),
resolvePackage('node_modules/another-package-to-transpile'),
],
},
},
],
}
You can change a lot of other configurations, and I suggest you to give a look at craco npm package page
Credtis: https://stackoverflow.com/a/58603207/4277948

Related

Ignore variable dependency of node_module webpack

I have built a library that I want to use in a Next.JS project. Within this library a certain dependency is using an import via a string passed into a require statement within the source code where the import is taking place. This is causing webpack to not recognize the import. I don't want to change code within any node_modules as this is not a preferred approach but how can I ensure that my project using the library I built is able to compile and run?
Within file_using_string_passed_into_require_to_get_import.js:
let importName = "./potential_import_A.js"
if(condition){
importName = "./potential_import_B.js"
}
module.exports = require(importName)
This is the folder structure:
Project/
| node_modules
| my-library
| node_modules
| library-dependency
| file_using_string_passed_into_require_to_get_import.js
| potential_import_A.js
| potential_import_B.js
To create a local (unpublished) library package
Create a 'my-library' folder (outside your current project dir).
Do npm init (Folder must include the 'package.json' )
Include source code (potential_import_A), exporting any desired functions.
In the actual project folder:
cd into the folder of the project that needs to use your library.
Run npm install --save local/path/to/my-library.
The --save will add the package to your dependencies in the project's package.json file, as it does with 3rd party published packages. It will also add a copy of the source code to the node modules folder of the project, as always.
Importing your new library:
import/require the package as you would normally, from any project.
For example
import { myFunction } from "my-library"
I got it to work by excluding node_modules from the webpack build. Since I am using Next.JS this is within my next.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
webpack: (
config,
{
buildId, dev, isServer, defaultLoaders, nextRuntime, webpack,
},
) => {
if (isServer) {
config.target = 'node';
config.node = {
__dirname: true,
global: true,
__filename: true,
};
config.externals = [nodeExternals()], // in order to ignore all modules in node_modules folder
config.externalsPresets = {
node: true, // in order to ignore built-in modules like path, fs, etc.
};
}
return config;
},
};

jest Cannot find modules inside node_modules folder when using swc compiler (Next,js)

I have a project with Next.js and wanted to do some unit tests on it.
I followed the instruction provided here and managed to make some tests. it's also good to mention that I'm using the rust compiler and typescript for the project.
but there is an issue with one of the tests when loading a module from the node_modules folder. here is the output:
$ jest
info - Loaded env from /Projects/projectname/.env
PASS test/pages/index.test.tsx
PASS components/shared/ui/Button/Button.test.tsx
FAIL components/shared/ui/Card/Card.test.tsx
● Test suite failed to run
Cannot find module 'swiper/react' from 'components/attraction/Gallery/Gallery.tsx'
Require stack:
components/attraction/Gallery/Gallery.tsx
components/attraction/Gallery/index.ts
components/attraction/index.ts
components/index.ts
components/shared/ui/Card/Card.tsx
components/shared/ui/Card/Card.test.tsx
1 | import { IImageResponse } from '#/constants'
2 | import React, { useState } from 'react'
> 3 | import { Swiper, SwiperSlide } from 'swiper/react'
| ^
4 | import SwiperCore, { Pagination } from 'swiper'
5 | import styles from './Gallery.module.scss'
6 |
at Resolver.resolveModule (node_modules/jest-resolve/build/resolver.js:324:11)
at Object.<anonymous> (components/attraction/Gallery/Gallery.tsx:3:51)
anything other than the components inside the project gives me this error.
here is the jest config for the project:
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
// Handle module aliases (this will be automatically configured for you soon)
'^#/(.*)$': '<rootDir>/$1',
},
testEnvironment: 'jest-environment-jsdom',
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)
can anyone help as to why this is happening?

Jest encountered an unexpected token - SyntaxError: Unexpected token 'export'

I'm using jest to test a react TypeScript app.
This is the test I'm running:
import { render, screen } from '#testing-library/react'
import { toBeInTheDocument } from '#testing-library/jest-dom'
import ContextProvider from '../../context/ContextProvider'
import { BrowserRouter } from 'react-router-dom'
import BlogPage from './BlogPage'
describe('BlogPage', () => {
test('Render blog page', () => {
render(
<ContextProvider>
<BrowserRouter>
<BlogPage/>
</BrowserRouter>
</ContextProvider>
)
expect(screen.getByText('In this page you can see some of the last articles I wrote.')).toBeInTheDocument()
})
})
And this is the error I'm getting:
FAIL src/components/blogPage/BlogPage.test.js
● Test suite failed to run
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Details:
/home/German/Desktop/ger/code/projects/my-website/node_modules/react-markdown/index.js:6
export {uriTransformer} from './lib/uri-transformer.js'
^^^^^^
SyntaxError: Unexpected token 'export'
> 1 | import ReactMarkdown from 'react-markdown'
| ^
2 | import Accordion from 'react-bootstrap/Accordion'
3 |
4 | interface Props {
I understand this is because the library I'm using (react-markdown) doesn't have pre-compiled source code. The thing is I followed the docs (https://jestjs.io/docs/tutorial-react-native#transformignorepatterns-customization) and added the react-markdown folder to the transformIgnorePatterns config and I still get the error.
This is my jest.config.ts file:
import type { Config } from '#jest/types'
const config: Config.InitialOptions = {
verbose: true,
transform: {
'^.+\\.ts?$': 'ts-jest'
},
transformIgnorePatterns: [
'node_modules/(?!react-markdown/)'
]
}
export default config
I tried adding <rootDir> like <rootDir>/node_modules/(?!react-markdown/) and It didn't make a difference.
I also tried configuring jest directly from package.json instead of a jest.config file and It didn't make a difference either.
Then I found this question: Jest transformIgnorePatterns not working, which mentions you need to configure Babel.
I created my app with create-react-app so I didn't have Babel on my app. I installed it and created a babel.config.js file inside of which I put:
module.exports = {
"presets": [
"#babel/preset-env"
]
};
But I still get the error...
I'm running out of ideas. Any clue of how could I solve this?
Full code can be found here: https://github.com/coccagerman/my-website
react-markdown is shipped as js, add babel-jest as a transformer in your jest config
transform: {
'^.+\\.ts?$': 'ts-jest',
"^.+\\.(js|jsx)$": "babel-jest"
},

Configure jest with latest version of d3-path

For some reason, my jest configuration doesn't work with the latest version of d3-path#3.0.1. It worked fine with version 2.0.0. I guess it has something to do with d3-path switching to ESM, but I was already using ES6 in my own code, so I don't get why it suddenly doesn't work anymore. I have the following packages installed:
"dependencies": {
"d3-path": "^3.0.1"
},
"devDependencies": {
"#babel/core": "^7.15.8",
"#babel/preset-env": "^7.15.8",
"babel-jest": "^27.3.1",
"jest": "^27.3.1"
}
My babel.config.js:
module.exports = {
presets: [['#babel/preset-env', {targets: {node: 'current'}}]],
};
My index.js:
import { path } from 'd3-path'
export default () => path()
The test file:
import fn from '../src/index.js'
describe('test', () => {
it('works', () => {
fn()
expect(2 + 2).toBe(4)
})
})
The error message:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export {default as path} from "./path.js";
^^^^^^
SyntaxError: Unexpected token 'export'
> 1 | import { path } from 'd3-path'
To reproduce:
git clone https://github.com/luucvanderzee/jest-problem.git
cd jest-problem
npm i
npm run test
// The test runs without failure- this is because we're currently still using d3-path#2.0.0
npm uninstall d3-path && npm install d3-path // (upgrade to d3-path#3.0.1)
npm run test
// Now the test fails.
How should I configure jest and/or babel to solve this issue?
EDIT:
I already tried the following (from this page of the jest docs):
Creating a jest.config.js file with the following:
module.exports = {
transform: {}
}
Changing my "test" command from "jest" to "node --experimental-vm-modules node_modules/jest/bin/jest.js"
This gives me another error:
/home/luuc/Projects/javascript/jest-problem/test/test.test.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import fn from '../src/index.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module
I also don't get what is meant by
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Isn't the problem that the module is not transformed? Would adding an ignore pattern not just lead to the module not getting transformed?
Problem
The error happens because jest does not send the content of node_modules to be transformed by babel by default.
The following output line of npm run test indicates one way to solve the problem:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Solution
The configuration of jest should be updated in order to instruct it to transform the ESM code present in d3-path dependency.
To do so, add the following to a jest.config.js file in the root directory of the project:
module.exports = {
transformIgnorePatterns: ['node_modules/(?!(d3-path)/)']
}
npm run test runs fine after that.
The transformIgnorePatterns option is documented here.
Edit - including more modules
In order to include all modules starting with d3, the following syntax may be used:
transformIgnorePatterns: ['/node_modules/(?!(d3.*)/)']
TLDR;
transformIgnorePatterns: [
"/node_modules/(?!d3|d3-array|d3-axis|d3-brush|d3-chord|d3-color|d3-contour|d3-delaunay|d3-dispatch|d3-drag|d3-dsv|d3-ease|d3-fetch|d3-force|d3-format|d3-geo|d3-hierarchy|d3-interpolate|d3-path|d3-polygon|d3-quadtree|d3-random|d3-scale|d3-scale-chromatic|d3-selection|d3-shape|d3-time|d3-time-format|d3-timer|d3-transition|d3-zoom}|internmap|d3-delaunay|delaunator|robust-predicates)"
]
For the ones reaching this page after updating recharts dependency, here I found the solution, provided by them.

Conditional import of peerDependency in es6

I am working on a JavaScript i18n library which localize dates (among other types and objects).
It is currently relying on moment.js, which is defined as a peerDependency (localization is one of the features but not the only one, it might not be used)
// package.json
{
"name": "my-i18n-library",
// ...
"scripts": {
// ...
"clean": "rimraf build",
"build": "babel src -d build",
"prepare": "npm run clean && npm run build"
},
"peerDependency": {
"moment": "~2.20.1",
"date-fns": "~1.29.0"
},
// ...
}
// .babelrc
{
"presets": ["env", "stage-1", "react"]
}
Basically something like (a bit more error-proof but I simplified the logic) :
import Moment from 'moment.js'
import 'moment/min/locales'
class Localizer {
localizeDate(value, locale, displayFormat = "L", parseFormat = "LTS") {
return Moment(date, parseFormat, locale).format(displayFormat);
}
}
Problem is, if moment.js is a nice work, it is like a backpack of stones, you would not bring it on a 50 miles trail, especially if you only need to localize one date in a whole application. Bandwidth-wise, it's not worth it IMO (actually in a lot of people opinions as well).
So I am considering switching to lighter libraries such as date-fns, but I figured out an option I think is even better :
what if we could let the other choose which library suits him the most?
I was thinking to define different implementations of library-related localizers, and conditionally import them depending of which peerDependency is installed :
// /Date/DateFnsLocalizer.js
import { parse } from 'date-fns/parse'
import { format } from 'date-fns/format'
class DateFnsLocalizer {
localizeDate(value, locale, displayFormat = "L") {
return format(parse(date), displayFormat, { locale })
}
}
Is that even possible in JavaScript ?
// /Localizer.js
if (isModuleDefined('moment.js')) {
import BaseLocalizer from './Date/MomentLocalizer'
} else if (isModuleDefined('date-fns')) {
import BaseLocalizer './Date/DateFnsLocalizer'
} else if (isModuleDefined('some-other-lib')) {
import BaseLocalizer './Date/SomeOtherLibLocalizer'
} else {
throw new Error('No date library defined! Please install at least one of ["moment.js", "date-fns", "some-other-lib"]')
}
export default class Localizer extends BaseLocalizer
I think "import" statements have to be made as first statements in the file. Maybe using require instead... (as suggested in ES6: Conditional & Dynamic Import Statements) ? And is it possible to test a module existence without importing it (basically how to code that isModuleDefined() method?)
I have seen those ones as well :
ES6 variable import name in node.js?
How can I conditionally import an ES6 module?
But as we are currently using babel to transpile this library, if this architecture is possible, could it cause compilation troubles in other building tools such as webpack, gulp, grunt etc.?

Categories

Resources