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.
Related
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.
I have a JavaScript project that must be bundled using Rollup.js which depends on a package A which in turn depends on a package B:
"mypackage" ---import--> "A" ----import----> "B"
Let's say that my package import a function "connect" from package A, which in turn import a "connect" function exported by the module B. Something like:
//mypackage index.js
import { connect } from 'A'
//A index.js
import { connect } from 'B'
//B index.js
export function connect() {}
Since my package requires a bundled version of the package B (let's say "B.bundle.js"), how can i configure Rollup.js in order to replace for each dependency of my project requiring B (A in this case) to use my local bundled version (i.e. B.bundle.js, which of course exports the "connect" function too)?
When Rollup.js creates the bundled version of my project i would like to achieve something like the following:
//A index.js after being processed by Rollup
import { connect } from './src/B.bundle.js'
Is something like this possible with Rollup or with a plugin? Sorry for the question, but I'm new to rollup and bundling in general.
I solved this issue using some configuration of my package package.json and the rollup plugin #rollup/plugin-node-resolve.
In the package.json of my package I inserted the browser option that specifies how modules should be resolved when my package is used in the browser context. From the npm doc on the browser option of the package.json:
If your module is meant to be used client-side the browser field should be used instead of the main field. This is helpful to hint users that it might rely on primitives that aren't available in Node.js modules. (e.g. window)
So considering the example provided in the original question the npm package contains something like this:
{
"name": "mypackage",
"version": "1.5.1",
"description": "A brand new package",
"main": "index.js",
"browser": {
"B": "./B.bundle.js"
},
}
This means that when mypackage is used in the context of the browser the module B import will load from the file located in "./B.bundle.js".
Now, with rollup i need to specify that the bundle i am creating is intended for browser context. The plugin that handle imports of node modules is #rollup/plugin-node-resolve. There is an option is this plugin that specify that the context is browser. From the plugin documentation about the option browser:
If true, instructs the plugin to use the browser module resolutions in package.json and adds 'browser' to exportConditions if it is not present so browser conditionals in exports are applied. If false, any browser properties in package files will be ignored. Alternatively, a value of 'browser' can be added to both the mainFields and exportConditions options, however this option takes precedence over mainFields.
So that in my rollup config file i have something like:
// rollup.config.js
import commonjs from "#rollup/plugin-commonjs";
import resolve from "#rollup/plugin-node-resolve";
import nodePolyfills from "rollup-plugin-node-polyfills";
export default {
input: "index.js",
output: {
file: "dist/mypackage.bundle.js",
format: "es",
},
plugins: [
nodePolyfills(),
resolve({
browser: true, //<- tells to rollup to use browser module resolution
}),
commonjs(),
],
},
The answer of #vrajpaljhala seems feasable for the purpose but imho #rollup/plugin-replace its kind of too tricky and raw approach to the problem because it involves direct replacmeent of strings surrounded by "". We may face some very "hard to discover" errors if the package name we are going to replace is a common word that can be present as a string in the code but not in the context of an import statement.
We had the exact same requirement and we resolved it using #rollup/plugin-replace package.
Basically, our project uses a monorepo structure but we are not using any tools like learna or workspace for managing it. So we have two packages which are interdependent. Our ui-kit uses icons package for different icons and icons package uses the Icon component from ui-kit package. We just replaced the imports for ui-kit with local path with the following:
import replace from '#rollup/plugin-replace';
...
...
export default {
...
...
plugins: [
replace({
'ui-kit': JSON.stringify('../../../../src/components/Icon'),
delimiters: ['"', '"'],
}),
]
...
...
}
I have a vanilla Express app whose package.json looks like this:
{
"name": "express-app",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
...
},
"devDependencies": {
...
},
"scripts": {
...
},
}
And I want to write a script outside the express app that imports a module from the express app. The module resides in ./src/model/Example.js That module also imports various other modules.
The script is located at ./scripts/test.mjs and does:
import Example from '../src/models/Example.js';
However, I'm getting this hitting the first import from Example.js:
SyntaxError: Cannot use import statement outside a module
How do I structure this such that the script can import the module correctly?
It is because your project in vanilla JS, the import statement is not going to work. You can use parceljs a zero config package bundler which supports ES6 and above syntax to use it first add it as a dev dependency by running
npm install parcel-bundler --save-dev
And then add to scripts to your package
{
"scripts": {
"dev": "parcel <your entry file>",
"build": "parcel build <your entry file>"
}
}
replace the entry file with your main file in your code (example: app.js)
did you try
const Example = require('../src/models/Example.js')
The modern way to do that is on yow package.json
Add a property call type and set the value of module
{
“name”;”project name”,
…
“type”;”module”,
…
“dependencies”:”….”,
}
by default nodejs treads yow code as
type: commons
It is why it says you cannot use import out side a module, bcuz yow package.json implicitly marks yow codes as type commonjs.
So after you set type module then you need to change the extension of them JavaScript files, from .js to .mjs. Yes you guessed right the m is from module.
You can do this or configure Babel, webpack, while you find the holy grail and what ever else is need it
Did you try to use a monorepo approach using something like Lerna.
https://lerna.js.org/
So that you could create modules and dependencies directly inside your repository.
I have created some npm modules and compile them to:
commonJS (using exports.default =) and
esm (using export default)
I set up my package.json like so:
main: "index.cjs.js",
module: "index.esm.js"
When I npm install the package and I simple import it like:
import myPackage from 'my-package'
It will automatically choose the main file, not the module.
My question:
Is there a way to import the module file instead when doing import myPackage from 'my-package' in a JavaScript file?
Why I choose the commonJS file for "main":
I noticed that using Node, importing an esm file is not possible because of export default, it has to be commonJS. I have some simple helper JS functions like this and this, and I would want them to be usable to the widest audience. That's why I chose cjs for the "main" path in package.json.
Why I define a separate "module" in package.json:
Lots of famous libraries like Vue.js are already doing this. See further information on this Stackoverflow thread:
What is the "module" package.json field for?
Why do I want to be able to import the "module" file instead of the "main" file:
There is currently a bug in Rollup where it will not properly show JSDoc comments when coding after having imported a cjs file, but it does work when importing a es file.
The workaround I want to avoid:
Just set "main" to the esm file in package.json, right? But then all users who are using my packages in Node apps will not be able to use it anymore...
→ I'm really confused about all this as well, but I think I did enough research to make sense of all it. That being said, if anyone knows a better approach or any other advice, please do tell me in the comments down below!!
Just don't use extension for main file and have es6 and CommonJS version as two separate files with the same name and in the same directory, but with different extension, so:
index.js // transpiled CommonJS code for old nodejs
index.mjs // es6 module syntax
and in package.json:
{
"main": "index"
}
If node is launched with --experimental-modules flag, it would use *.mjs file, otherwise *.js.
Nodejs does not support "module" but does support the newer "exports" spec.
https://nodejs.org/api/packages.html#exports
https://github.com/nodejs/node/blob/v16.14.0/lib/internal/modules/esm/resolve.js#L910
"exports": {
"import": "./main-module.js",
"require": "./main-require.cjs"
},
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.