Treat self as a node module for npm - javascript

I have a javascript project which is released as a node module. For some reasons, I have some source code using relative paths to import other files in this project:
// <this_module_path>/action/foo.js
import execution from './execution';
import types from '../types';
and also using a module name as a root path to import other files in this project:
// <this_module_path>/action/doSomething.js
// Using module name to import
// equals to import './helpers.js' in this case (in the same folder)
import executionHelpers from 'this-module/action/helpers.js';
// equals to import '../types/helpers' in this case
import typeHelpers from 'this-module/types/helpers.js';
How can I have a such file to import other project files using its module name rather than relative paths?

NodeJS uses CommonJS to import javascript moduels. There is no clear timeline for adding ES6 import / export syntax to NodeJS. So you need to transpile your code using Babel to CommonJS module system before you can run it on NodeJS.
How to do this using CommonJS
Create a separate package for your this-module module. The package needs to be created inside node_modules directory of your main module. You can do this using npm init command inside the node_modules directory.
Inside that file, you need to create a Javascript file (conventionally called index.js and make it the main script of that package.
Your package.json should look something like this:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
}
In the index.js you can export your variables (such as helpers and types) and you can easily import them in your main package.

Related

Typescript module usage without ".js" ending

In typical JavaScript files we use require('some-module.js') to import functionality of this module where we need.
If we use TypeScript we also want to import this functionality using "import module from 'some-module.js'".
If we use nest.js we import using "import module from 'some-module'". The main thing that there is no ".js". How can i reach the same in for example express application. Maybe i should use webpack, babel or some special tsconfig.json configuration?
With Typescript's import and with Node's require, the extension is not necessary. Just the path to the file, or the name of the package you're importing. So in typescript you would do import * as module from 'some-module' and in js you would do const module = require('some-module'). You may want to check out this page on module resolution in Typescript.

Promoting Babelized ES module code to native Node 14+

I have lot of javascript written to be run by nodejs, but which we ran through the Babel loader at runtime, so that we could write ES syntax -- in particular using import rather than require.
We have a layout like:
package.json
node_modules/
...packages...
top/
server.js
fribbity.js
server.js looks like (I've elided the babel import boilerplate) :
import {fribbity} from 'top/fribbity'
const x = fribbity()
console.log(`fribbity = ${x}`)
while fribbity.js might be
export const fribbity = () => 17
I'd like to promote all this code to use native ES modules in Node 14+. I added "type": "module" to package.json. But now I've run into the module resolution rules. By default, Node now expects my import in server.js to be
import {fribbity} from './fribbity.js'
Are there settings I can apply in package.json, or on the node command line, that would enable node to resolve the imports as they were originally written? That is, preserving the deep import path style (import string begins with no slash or dot, and ends without the ".js" extension)? I've tried several false starts.

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.

Allow auto import my React library on vscode

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.

Import from subfolder of npm package

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.

Categories

Resources