Dependency not found when using package.json exports key - javascript

I have built a very simple component library for Vue in Vite and published it to NPM. When consuming components from the lib.js file it works just fine.
However, I now need to be able to import from my library (or rather the NPM package) a single JS file.
In the consuming site however, I am getting an error and I think it's all to do with the setup of my exports key in the package.json
package.json (Library)
{
"name": "#michaelpumo/components-vue",
"version": "0.0.22",
"types": "./dist/types/lib.d.ts",
"files": [
"dist"
],
"main": "./dist/lib.js",
"module": "./dist/lib.js",
"exports": {
".": {
"import": "./dist/lib.js",
"require": "./dist/lib.js"
},
"./tailwind": "./dist/tailwind.config.js"
}
}
In my consuming site, I'm trying to import the Tailwind file like so:
const { units, config } = require('#michaelpumo/components-vue/tailwind')
console.log(units) // This outputs correctly, so the require worked.
module.exports = { config, units }
This actually logs the contents of 'units' from the Tailwind file, so I know it's been found correctly.
However, in the terminal, I get an error:
ERROR Failed to compile with 1 errors
This dependency was not found:
* #michaelpumo/components-vue/tailwind in ./tailwind.config.js
I'm not sure what is going wrong here.
Here is the structure of my NPM package:
It might be worth mentioning that the consuming site is a Nuxt.js site but I'm not sure that makes any difference here.
Any help greatly appreciated.

Related

Can't import package produced by wasm-pack in nodejs + ESM + typescript

I am trying to import a library which I published and which is generated using wasm-pack. This is the source code of the Rust project. I tried building the package with all the target options but none of those seems to produce a package which I could successfully import in my nodejs project with ESM. Then I tried modifying the package.json file manually so that it follows what in my understanding an ESM library needs and it looks as follows:
{
"name": "fhe-wasm",
"version": "0.1.1",
"type": "module",
"exports": "./fhe_wasm.js",
"files": [
"fhe_wasm_bg.wasm",
"fhe_wasm.js",
"fhe_wasm.d.ts"
],
"module": "fhe_wasm.js",
"types": "fhe_wasm.d.ts",
"sideEffects": false
}
Unfortunately I still didn't manage to successfully import it and the and this is the error message:
I am a bit of a noob with ESM and webassembly so I would appreciate any help. Thank you
It is possible to import a package produced by wasm-pack in a Node.js + ESM + TypeScript environment, but you may need to do some additional configuration to ensure compatibility.
Here are a few steps you can follow:
Make sure that your Node.js version supports ESM.
In your TypeScript configuration file (tsconfig.json), set "module": "ESNext" and "target": "es6".
In your Node.js code, use the .mjs file extension instead of .js when importing the package.
In the package produced by wasm-pack, ensure that the generated JavaScript file uses ESM-style exports (i.e., export instead of module.exports).
If the package includes a TypeScript definition file (.d.ts), make sure that it is correctly set up for ESM-style imports.
If you follow these steps and still encounter issues, it may be helpful to consult the documentation for the specific package you are trying to import, as well as the documentation for Node.js and TypeScript.

Custom React Component Library - jest 'cannot find module react'- testing-library, rollup

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.

Chicken-and-egg problem with node-gyp: Pointing to a header file in a dependency

This is my first attempt at building an npm package that includes C++ code. I thought that I had everything set up properly because, while working in the project folder for the package itself, npm i or node-gyp rebuild was working just fine.
But now that I've published the package, and I'm trying to use that package as a dependency in another project, installation is failing during compilation, when trying to include an important C header file:
CXX(target) Release/obj.target/ar_signal_monitor/ar-signal-monitor-node.o
../ar-signal-monitor-node.cpp:1:10: fatal error: 'napi.h' file not found
#include <napi.h>
^~~~~~~~
1 error generated.
The header file is inside one of the package's dependencies, node-addon-api. My binding.gyp file looks like this:
{
"targets": [
{
"target_name": "ar_signal_monitor",
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"cflags": ["-Wall", "-std=c++11", "-pthread"],
"cflags_cc": ["-Wall", "-pthread"],
"sources": [
"ar-signal-monitor-node.cpp",
"ar-signal-monitor-node.h",
"ar-signal-monitor.cpp",
"ar-signal-monitor.h"
],
"include_dirs": [
"<!(node -e \"require('node-addon-api').include\")",
"node_modules/node-addon-api",
"/usr/include/node",
"/usr/local/include/node"
],
"libraries": [
"-lwiringPi"
],
"defines": ["NAPI_CPP_EXCEPTIONS"],
'conditions': [
["OS==\"mac\"", {
"defines": ["USE_FAKE_WIRING_PI"],
"libraries!": ["-lwiringPi"],
"xcode_settings": {"GCC_ENABLE_CPP_EXCEPTIONS": "YES"}
}],
["OS==\"win\"", {
"defines": ["USE_FAKE_WIRING_PI"],
"libraries!": ["-lwiringPi"]
}],
],
}
]
}
I'm trying to use "include_dirs" to tell the compiler where to find the <napi.h> header file. I think that perhaps the problem I have is that the installation process wants to compile my C++ code first, and only after that's successfully done then load the dependent npm packages where the header file I need for successful compilation lives.
Is there a way around this problem? Is it a different problem than I think it is? I tried first including node-addon-api in the client project, but that didn't help. (And even if it had worked, that wouldn't be any better than a temporary word-around.)
Full code for the project can be found here: https://github.com/kshetline/rpi-acu-rite-temperature
I'd been thinking I'd needed to do something different in my binding.gyp file, but the solution (or, at least, a solution) turned out to be something I could do in my package.json:
"scripts": {
"preinstall": "npm i node-addon-api",

import the current package export by the package.json name

I'm interested to know if there's a convention that allows a person to test the expected usage of a package from within the package. Consider the following package.json:
{
"name": "#place/fn",
"version: "1.0.0"
}
From within this very package, I'd like to have a test.js file, with exactly following code:
import mainThing, { anotherThing } from '#place/fn';
Is this possible with the right directory structure or package.json configuration? I'm also fine with CommonJS syntax if that would work better.
Ok, found an answer on this but there's a ton of other related things I found in the process that could probably help others.
First, now I understand what I wanted to do is install the current package as a local dependency. That can be done in the package.json using the following syntax found in this answer.
{
"name": "#place/fn",
"dependencies": {
"#place/fn": "file:./"
}
}
Now, I can run npm i to install that change and code the following test file:
import mainThing, { anotherThing } from '#place/fn';
When running the test file, the import will act as if it was installed into another package; better mimicking the intended use.
Another thing I discovered is that you can alias packages installed from npm using the syntax found in this answer.
"dependencies": {
"case-1.5.3": "npm:case#^1.5.3",
"kool": "npm:case#^1.6.1"
}
Which then allows you to do the following:
let Case = require('case-1.5.3');
let Kool = require('kool');
And finally, another thing I found is that Node#13 allows for a new key on the package.json which maps directories found in this answer. There are other packages (like module-alias) that do something similar with the big difference being the installed solutions (ie: module-alias) only work from within the package you are working with. It seems like the new exports key on package.json will do the mapping for use in other packages.
// ./node_modules/es-module-package/package.json
{
"name": "es-module-package",
"exports": {
"./my/": "./src/js/lib/my/"
}
}
import thing from 'es-module-package/my/thing.js';
// Loads ./node_modules/es-module-package/src/js/lib/my/thing.js
Ultimately the purpose of all of this was to test that the style of syntaxes I provide outside the package could be tested from within the package instead of installing into another package and testing there. Now I can check that a common lodash import syntax works for my package.
import get from 'lodash/get'
(I wish I knew what the terminology was for requiring a clean, perhaps aliased, path of a package instead of destructuring the main export.)
To access the main export of a package from withing the package itself, add a main entry point export to package.json:
{
"name": "#place/fn",
"version: "1.0.0",
"exports": {
".": "./index.js"
}
}
Replace ./index.js with the path to the main module.
Then, in test.js, you can import it with the expected syntax:
import mainThing, { anotherThing } from '#place/fn';
Be careful that when the exports field is defined, all subpaths of the package are no longer available to external importers unless explicitly declared in exports. Typically, you will want at least package.json to be exportable beside the main module:
"exports": {
".": "./index.js",
"./package.json": "./package.json"
}
When you import a package, it's up to the package.json file to tell you where to go... Say you had the following file structure
project
demo
demo-index.js
dist
project.cjs.js
project.es.js
project.umd.js
src
index.js
package.json
You want to code your package inside src, build it (using something like Vite or Bili) and then inside your demo folder, see if the built files (inside dist) work as intended.
So your package.json should have:
{
"name": "project",
"version": "1.0.0",
"main": "dist/project.cjs.js",
"module": "dist/project.es.js",
...
}
Now, inside demo/demo-index.js you could just import from ../src/index.js and use the uncompiled version.
What you can also do is import from your project root directory and get the compiled version:
// demo/demo-index.js
import Project from "../";
This will import via the package.json file and give you the file listed under "module", i.e. dist/project.es.js.

Testing TypeScript modules with package dependencies

I have a class written in TypeScript:
import * as uuid from "uuid";
export class IdGenerator {
getId() {
return uuid.v4();
}
}
This has a dependency on the uuid package that I have installed with npm.
I deploy this with other code written in TypeScript to the browser using webpack. Dependencies are resolved, all is well.
I want to test the class, so I write the following test:
import { IdGenerator } from "../src/IdGenerator";
describe("An id generator", () => {
const idGenerator = new IdGenerator();
it("generates an id", () => {
expect(idGenerator.getId()).not.toBeNull();
});
});
I use Chutzpah with the following configuration:
{
"Framework": "jasmine",
"TestHarnessReferenceMode": "AMD",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"EnableTestFileBatching": true,
"References": [
{
"Path": "node_modules/requirejs/require.js",
"IsTestFrameworkFile": true
}
],
"Compile": {
"Mode": "External",
"Extensions": [ ".ts" ],
"ExtensionsWithNoOutput": [ ".d.ts" ]
},
"Tests": [
{ "Path": "Components/test" }
]
}
When trying to test with Chutzpah, I get the following error:
Unhandled exception [...] in [...] /node_modules/requirejs/require.js [...] Script error for "uuid", needed by: Components/src/IdGenerator
So I add a reference to the path in chutzpah.json, which gets rid of this error but raises an error for one of uuid's dependencies. I add a reference for the dependency, then get a further dependency error, and so on.
Ideally I would like all dependencies to be resolved in the same way as they are with the bundle deployed to the browser.
Should I abandon the idea of trying to test my TypeScript files with package dependencies in this way and instead look into using dependency injection and mock the package dependencies in the tested TypeScript files? Perhaps also create separate JavaScript tests for the bundle? Or is there another approach that will allow the testing of my TypeScript code with the package dependencies?
This looks like it won't work since requireJS and Node are not compatible in this way. See here. From that answer:
It is not possible to use RequireJS on the client (browser) to access files from node_modules. Files under node_modules must first be copied to a location that is accessible (under the public folder) before the client can access them. The documentation says RequireJS can access Node modules, but this only works for server-side JavaScript (when you want to use RequireJS-style module syntax in Node).
To use your config module in the client app, you must first transform it into a RequireJS-compatible module and copy it under public. This article explains how to automate this with package managers and build tools, and contains the quote that finally fixed my broken understanding of RequireJS + Node:
In order to use node modules on the browser you will need a build
tool. This might bother you. If this is a deal breaker then by all
means continue wasting your life copying and pasting code and
arranging your script tags.

Categories

Resources