I have an Angular app built with Webpack that imports locale JSON files from the d3-format package:
import d3Format from "d3-format/locale/en-US.json";
This worked fine with D3 v5. But after upgrading to v7, I get the following error:
Module not found: Error: Can't resolve 'd3-format/locale/en-US.json'
The JSON file is still in the same location (and in fact the IDE resolves it correctly). But Webpack just doesn't find it when building the app. The main change that broke this import is that the package.json of d3-format now includes this bit (and if I remove it, the build works fine again!):
"exports": {
".": {
"umd": "./dist/d3-format.min.js",
"default": "./src/index.js"
},
"./locale/*": "./locale/*.json"
}
The import also works if I specify an import path like this:
import d3Format from "../../../../node_modules/d3-format/locale/en-US.json";
But this is ugly, I'd rather avoid that.
Related
I am trying to import a module called "uuid" to my index.js script which I installed through this command: npm install uuid.
First I tried using CommonJS syntax, but that gave me this error: Uncaught ReferenceError: require is not defined
const { v4: uuidv4 } = require('uuid');
// ReferenceError: require is not defined
My IDE recommended that I convert my file to an ES module instead:
import { v4 as uuidv4 } from 'uuid';
// SyntaxError: Cannot use import statement outside a module
By adding type="module" to my index.js script in the html only produces new errors:
TypeError: Failed to resolve module specifier "uuid". Relative references must start with either "/", "./", or "../".
I have searched for a while now but can't find anything. Upon trying to follow the error message instructions I try to generate a more direct path to the "uuid" module like this:
import { v4 as uuidv4 } from '../node_modules/uuid/dist/v4';
// GET http://localhost:3000/node_modules/uuid/dist/v4 net::ERR_ABORTED 404 (Not Found)
Even with this direct path it can't find the module which is weird as it is at least recognizing something when only specifying the module name "uuid".
If it's to any help here is my package.json dependencies
"dependencies": {
"express": "^4.18.1",
"nodemon": "^2.0.20",
"socket.io": "^4.5.2",
"socket.io-client": "^4.5.4",
"uuid": "^9.0.0"
}
You're trying to import uuid module from node_modules in the script running in the browser. Unfortunately, it's that simple. The node_modules work for Node.js only, browsers don't know about them.
There are multiple ways you can fix this:
Setup a static server that will serve uuid. Then, you can import it by its url (e.g import { v4 as uuidv4 } from './uuid';). This is generally not recommended.
Use CDN. It's similar to the previous method, but this time someone will set up a static server for you. This is often used in production because of good performance, and you don't need to do anything for this. It's simple: import { v4 as uuidv4 } from 'https://jspm.dev/uuid';
But not all libraries are hosted on CDNs. Also, you'd probably need to optimize your dependencies and code. So for complex applications, you should use a so called bundler. Here are a few of the most popular: Vite, Rollup, Webpack, Parcel. What bundlers do: they analyze your imports and then produce a single js file called an "entrypoint". Depending on configuration they can combine all dependencies into one file, or insert special code that will dynamically import your dependencies from other files when they are needed for code to execute.
Using lit 2.5.0 in combination with vite I run into an issue using the lit build-in directives.
Following the docs I added an import to my lit js component like:
import { when } from 'lit/directives/when';
But when I view the resulting site in the browser I always get a missing package error:
Missing "./directives/when" export in "lit" package
Any suggestions on how to fix this import issue?
You need to include the .js extension as well.
import { when } from 'lit/directives/when.js';
This is needed because that's how it's listed in the "exports" of the package which you can see in the package.json file. https://unpkg.com/lit#2.5.0/package.json
"./directives/when.js": {
"types": "./development/directives/when.d.ts",
"default": "./directives/when.js"
},
Rationale for this is to encourage usage of file extensions in import specifiers so as to not require bloated import maps in the browser to support extensionless imports. Described more in detail https://lit.dev/docs/tools/publishing/#include-file-extensions-in-import-specifiers and https://github.com/WICG/import-maps#extension-less-imports.
I'm building a custom react component library to share with other applications. I'm using rollup and following this blog and a few others: https://dev.to/alexeagleson/how-to-create-and-publish-a-react-component-library-2oe
The relevant snippet of my rollup config is as follows:
export default [
{
input: "src/index.ts",
output: [ { file: packageJson.main, format: "esm", sourcemap: true } ],
plugins: [ peerDepsExternal(), resolve(), terser() ],
external: ["react", "react-dom"]
}
]
In my package.json I've moved react and react-dom from 'devDependencies' to 'peerDependencies'. My abbreviated package.json:
{
"name": "custom components",
"version": "1",
"devDependencies": {
...stuff, (but not react/react-dom)
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"main": "dist/esm/index.js",
"files": ["dist"]
}
Then using npm link I've imported my component library into another app which is using CRA and react-testing-library.
So far this works. I'm able to render my common components as expected.
However it seems like moving react/react-dom out of devDependencies is making my jest tests fail in both projects. In both cases jest cannot find react. The Jest output in my CRA that imports the components is as follows:
Test suite failed to run
Cannot find module 'react' from 'index.js' //<-- this is the index of the component library
However, Jest was able to find:
.../MyComponent.tsx
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['web.js',...etc] (defaults)
And when I run my tests in my common component library:
Test suite failed to run
Cannot find module 'react' from 'MyComponent'
import React from 'react';
^
I'm not sure how exactly to get around this. If I move react/react-dom back to devDependencies, the tests will run successfully, but then any component using react state will fail to render due to multiple copies of React in the project.
Could this be an issue because I'm using npm link as opposed to actually publishing the application to npm and installing or is this an issue with my rollup config/jest config/package.json? Any help would be appreciated.
react and react-dom should be included in both devDependencies and peerDependencies of your library.
Including them in devDependencies makes them available when developing (and when running tests), but they won't be included in the library when you bundle them with Rollup (which is what you want).
Including them in peerDependencies tells any consuming applications "you must have these dependencies at the specified version range," but again, Rollup doesn't include them in the library bundle.
If you only include them in peerDependencies, they aren't installed when developing the library or running tests, although this might no longer be true if you're using npm v7.
You can run into the problem of having multiple versions of React in your project if your app includes them at a different version than your library, but if both the app and the library have the same version range you shouldn't have that problem.
I'm trying to do a library of components for React and publish on npm using webpack and babel to compile to Es5.
Almost everything worked, but for some reason, the project that consumes this lib cant auto import their components
I have a project on github with the setup I used:
https://github.com/dattebayorob/react-loading
//webpack.config.js
https://github.com/dattebayorob/react-loading/blob/master/webpack.config.js
//.babelrc
https://github.com/dattebayorob/react-loading/blob/master/.babelrc
//package.json
https://github.com/dattebayorob/react-loading/blob/master/package.json
I'm expecting to import components from my lib with 'CTRL+space' when typing then
Now, I can import from my lib manualy with import { Component } from 'my-react-lib'
Sometimes, when using Typescript in VSCode, you have to run the Typescript: Restart TS Server command in your command palette for auto import to work after creating new files. It's a bug.
On dattebayorob/react-loading/index.d.ts try:
export * from './src/components'
In package.json, you have "main": "./index.d.ts", but that's not a valid JS file, as it does not contain actual code, only type definitions.
In a library, usually you need to have an src/index.js file that imports / exports all components and in package.json you add the build artifact as main: "main": "dist/index.js".
Also, don't forget to explicitly specify the files: ["dist"] attribute in package.json so the src folder is not downloaded when your package is installed.
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.