How to create a NPM package that simply houses a declaration file - javascript

I have been trying to create an NPM package with just a declaration file inside. For context, I have an external plugin system for an entirely separate codebase and I wish to have a package you can install to provide definitions and intellisense for all the bridging variables, classes and functions a user can use.
The package really only has the an empty index.js file and the declaration file index.d.ts. The declaration file contains 2 exported modules. They are in the format of:
declare module 'base-plugin-declaration' {
...
namespace BasePlugin {}
export = BasePlugin;
}
declare module 'base-component-declaration' {
...
namespace BaseComponent {}
export = BaseComponent;
}
My package.json also looks like this:
{
"name": "vatom-spaces-external-plugin-declarations",
"version": "1.0.6",
"description": "Provides declarations for classes, functions and variables that external vatom plugins have access to",
"main": "./index.js",
"types": "./index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/VatomInc/external-plugin-declaration.git"
},
"keywords": [
"declaration",
"intellisense"
],
"author": "Liron Toledo",
"license": "ISC",
"bugs": {
"url": "https://github.com/VatomInc/external-plugin-declaration/issues"
},
"homepage": "https://github.com/VatomInc/external-plugin-declaration#readme"
}
My problem is that after publishing and installing this package. When I try in to import these modules. I get the following error when trying to build my project:
ERROR in ./src/index.js 1:0-49
Module not found: Error: Can't resolve 'base-plugin-declaration' in '/Users/lirontoledo/Documents/test-plugin/src'
resolve 'base-plugin-declaration' in '/Users/lirontoledo/Documents/test-plugin/src'
Parsed request is a module
using description file: /Users/lirontoledo/Documents/test-plugin/package.json (relative path: ./src)
Field 'browser' doesn't contain a valid alias configuration
resolve as module
/Users/lirontoledo/Documents/test-plugin/src/node_modules doesn't exist or is not a directory
looking for modules in /Users/lirontoledo/Documents/test-plugin/node_modules
single file module
using description file: /Users/lirontoledo/Documents/test-plugin/package.json (relative path: ./node_modules/base-plugin-declaration)
no extension
Field 'browser' doesn't contain a valid alias configuration
/Users/lirontoledo/Documents/test-plugin/node_modules/base-plugin-declaration doesn't exist
.js
Field 'browser' doesn't contain a valid alias configuration
/Users/lirontoledo/Documents/test-plugin/node_modules/base-plugin-declaration.js doesn't exist
.json
Field 'browser' doesn't contain a valid alias configuration
/Users/lirontoledo/Documents/test-plugin/node_modules/base-plugin-declaration.json doesn't exist
.wasm
Field 'browser' doesn't contain a valid alias configuration
/Users/lirontoledo/Documents/test-plugin/node_modules/base-plugin-declaration.wasm doesn't exist
/Users/lirontoledo/Documents/test-plugin/node_modules/base-plugin-declaration doesn't exist
/Users/lirontoledo/Documents/node_modules doesn't exist or is not a directory
/Users/lirontoledo/node_modules doesn't exist or is not a directory
/Users/node_modules doesn't exist or is not a directory
/node_modules doesn't exist or is not a directory
webpack 5.75.0 compiled with 1 error in 1125 ms
I suspect its because of my blank index.js file but I've never created a package before so I kind of don't know what I'm doing. Hopefully this is something easy to fix that I don't know about due to my inexperience.
I basically just want to be able to import the package without getting the aforementioned errors.
I tried fiddling with the main index.js file, adding exports to the d.ts file and even removing it entirely but I keep getting greeted with the same compiler error each time.

Related

Problem importing a module from an express app in a script outside of express

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.

How to build Typescript library with high compatibility for users?

What I want to do
I want to create a library that can be used as JS developer and as Typescript developer (frontend, no nodejs). That means that JS developer can use the library as inline script: <script type="text/javascript" src="mylib.js">var myLib = new MyLib();</script> and Typescript developers can use the library via imports.
What I need
I want to develop this library in Typescript and that's why I need a (build) solution that meets the following requirements:
The library should be developed in Typescript.
The build should contain all declarations automatically. Typescript developers should be able to import parts of this library.
For Javascript users the library should be used "the old-fashioned-way" as inline code in a <script>-Tag
The builds should be generated automatically if npm run build is called.
Generally the compatibility to older browsers of this library should be as high as possible.
What I tried so far
I created a typescript library and experimented with some settings in the tsconfig.json. The only way I found is to set the "module" attribute to "none", but then I can't use imports, exports in my code and have to merge all my Typescript files into one file. That would make the developement more difficult.
I looked on the repository e.g. of Konva.js, which does what I want to do. But I don't understand how it works. It seems the developer converts the code to umd and then uses rollup.js and at the end of the build there exists a javascript file as long as all Typescript code that is needed.
Comment
I'm looking for a solution for some days, but it's hard to find the perfect term that represents what I'm looking for. That's why I ask it here and hope someone can help me.
Browser does not understand typescript directly so that why using the typescript compiler we convert our typescript code to javascript. So when we build a typescript project or module we usually have a typescript configuration file tsconfig.json which a typescript compiler uses when compiling typescript to javascript.
This is only a snapshot of a few configurations in the tsconfig.json file. First, so you want to target most of the browser so what you will do set the target property to es5. Because most browsers older and new have very good support for it. Now your typescript compiler will emit javascript in the es5 version. There is the other property call outDir which will contain your outputted javascript code which you can import directly in your other javascript file in production or others can import it using tags.
In your package.json file, you can set the build script so it should run a typescript compiler to compile typescript code.
About point number 2, what you can do is you can specify the entry point of your typescript module like it can be main.ts or index.ts in package.json so when other typescript developer uses this library then there build tool would automatically get the import defined in the entry point of your library. So What would go in this entry point file is a question? It would contain export of your all the module of the library. In this way, all modules of your library would be available to other typescript users.
You can also check the konvasjs tsconfig file. what they are doing they are outputting their javascript files in the lib directory which is used in production. for development, they are working with normal typescript files.
I went deeper into package builder and found out: iife is the term I was looking for. A iife build allows to load a library directly via the script tag without anything else.
After hours of testing I found a perfect solution to build my library that fits my needs: rollup.js
The core npm packages are:
rollup: package builder.
#rollup/plugin-typescript: TS plugin in order to compile TS.
#rollup-plugin-generate-package-json: Automatically copy package.json, change it and move it to the output directory.
My solution creates a package with this structure:
|dist
|- js
|-- MyLibrary.js
|- es
|-- // ES files
|- package.json
The MyLibrary.js file can be imported easily within a <script> tag and the Typescript related files are in the "es" folder. The package automatically directs to the es/index.js that means Typescript developers should be able to use auto-complete for type suggestions in their IDE.
You can find a sample repository here:
https://github.com/julianpoemp/rolltsup-sample
package.json:
{
"name": "my-lib",
"version": "0.0.1",
"description": "",
"main": "src/main.js",
"dependencies": {
"rollup": "^2.38.5"
},
"devDependencies": {
"#rollup/plugin-commonjs": "^17.1.0",
"#rollup/plugin-typescript": "^8.1.1",
"rollup-plugin-generate-package-json": "^3.2.0",
"tslib": "^2.1.0",
"typescript": "^4.1.3"
},
"scripts": {
"build": "rollup --config rollup.config.js && rollup --config rollup.config_es.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
rollup.config.js
import typescript from '#rollup/plugin-typescript';
// rollup.config.js
export default {
input: 'src/index.ts',
output: {
file: 'dist/js/myLibrary.js',
name: 'MyLibrary',
format: 'iife'
},
plugins: [
typescript({
target: "ES5",
declaration: true,
outDir: "dist/js",
rootDir: "src"
})
]
};
rollup.config_es.js
import typescript from '#rollup/plugin-typescript';
import generatePackageJson from 'rollup-plugin-generate-package-json'
// rollup.config.js
export default {
input: 'src/index.ts',
output: {
dir: 'dist/es',
name: 'MyLibrary',
format: 'es'
},
plugins: [
typescript({
target: "ES5",
declaration: true,
outDir: "dist/es",
rootDir: "src"
}),
generatePackageJson({
outputFolder: "dist",
baseContents: (pkg) => {
pkg.main = "es/index.js";
pkg.scripts = undefined;
return pkg;
}
})
]
};

require() not working in module type nodejs script

In my package.json file I've specified that my nodejs app is of type module, because if I do not do that, it seems that I can not use import statements. This is how it looks like now:
{
"name": "...",
"version": "1.0.0",
"description": "....",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "...."
},
"author": "",
"license": "ISC",
"devDependencies": {
"lodash": "^4.17.15"
},
"type": "module"
}
But if I add the "type": "module" to my package.json file, I can't use require statements anymore, because I get a ReferenceError: require is not defined error.
If I remove the "type": "module" line from package.json, and rewrite all of my imports to requires, everything works without an error.
I can't seem to find any indication, that import and require can not be mixed or used together in the same script, am I missing something here, or I am having some other bug? How could I resolve to use these two kind of statements in the same script?
Why I would need this, is because I want to require some config files based on dynamic paths, and only if the files exists, which I think I can not do with import.
DISCLAIMER: I am rather new to nodejs server side programming, so it is possible that I am approaching this situation very wrongly, if that's the case, please advice me something, based on the Why I've mentioned above.
NOTE: I am running this node script from the server terminal, and not from the browser.
But if I add the "type": "module" to my package.json file, I can't use require statements anymore, because I get a ReferenceError: require is not defined error.
Right. It's either/or. Either you use ESM (JavaScript modules, type = "module") or you use CJS (CommonJS-like Node.js native modules, require).
But, if you're using type="module":
You can still use CJS modules, you just import them via import instead of require (or via import() [dynamic import] if necessary). See details here and here.
You can use createRequire to effectively get a require function you can use in your ESM module, which brings us to...
Why I would need this, is because I want to require some config files based on dynamic paths, and only if the files exists, which I think I can not do with import.
That's right. You have to use createRequire for that instead (or readFile and JSON.parse), more here.
createRequire version:
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const yourData = require("./your.json");
Usually you need Babel to transpile your Node.js code that uses ES Modules.
But if you don't want to use Babel: ES Modules is experimental feature of latest Node.js.
You need 3 things:
latest Node.js
Add "type": "module" to the package.json
Add experimental flag when running node.js node --experimental-modules app.js

How to load a user-specific file from within a node module?

I am creating a node module that applications can import (via npm install). A function within my module will accept the location of a .json file that is set in the users' application (as specified by filePath below):
...
function (filePath){
messages = jsonfile.readFileSync(filePath);
}
...
How do I allow my function to accept this file path and process it in a way that my module will be able to find it, given that my function will never know where the users' application file will be stored?
If you're writing a node library then your module will be required by the user's application and thus saved in the node_modules folder. The thing to notice is that your code just becomes code run in the user's application, therefore paths will be relative to the user's application.
For example: Let's make two modules, echo-file and user-app with their own folders and their own package.jsons as their own projects. Here is a simple folder structure with two modules.
workspace
|- echo-file
|- index.js
|- package.json
|- user-app
|- index.js
|- package.json
|- userfile.txt
echo-file module
workspace/echo-file/package.json
{
"name": "echo-file",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},
"author": "",
"license": "ISC"
}
workspace/echo-file/index.js (the entry point of your module)
const fs = require('fs');
// module.exports defines what your modules exposes to other modules that will use your module
module.exports = function (filePath) {
return fs.readFileSync(filePath).toString();
}
user-app module
NPM allows you to install packages from folders. It will copy the local project into your node_modules folder and then the user can require it.
After initializing this npm project, you can npm install --save ../echo-file and that will add it as a dependency to the user's application.
workspace/user-app/package.json
{
"name": "user-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},
"author": "",
"license": "ISC",
"dependencies": {
"echo-file": "file:///C:\\Users\\Rico\\workspace\\echo-file"
}
}
workspace/user-app/userfile.txt
hello there
workspace/user-app/index.js
const lib = require('echo-file'); // require
console.log(lib('userfile.txt')); // use module; outputs `hello there` as expected
How do I allow my function to accept this file path and process it in a way that my module will be able to find it, given that my function will never know where the users' application file will be stored?
So long story short: file paths will be relative to the user's app folder.
When your module is npm installed, it copies to node_modules. When a file path is given to your module, it will be relative to the project. Node follows the commonJS module definition. EggHead also has a good tutorial on it.
Hope this helps!
how about use absolute path ?
if you write in yourapp/lib/index.js.
path.join(__dirname, '../../../xx.json');

Unable to require a React component

I am currently in the process of extracting modules from a monolithic React project so that they can be stored separately in my npm registry, but I can't seem to export and import them properly. Before trying to extract them, I was using:
const Component = require("./component.js");
and using webpack to bundle everything. That was working fine. I then moved the component to a separate project, which I bundled with webpack. I can't seem to get it to work as an npm dependency however. Here's the basic code for the component:
// Some require statements
...
var Component = React.createClass({...});
module.exports = Component;
The build process outputs the bundle to build/bundle.js, and the package.json looks like this:
{
"name": "component",
"version": "0.0.2",
"description": "...",
"main": "build/bundle.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build-dev": "webpack",
"build-min": "NODE_ENV=production webpack && uglifyjs ./build/bundle.js -c -m -o ./build/bundle.min.js --source-map ./build/bundle.min.js.map",
"prepublish": "npm run build-min"
},
"publishConfig": {
"registry": "registry"
},
"author": "esaron",
"license": "UNLICENSED",
"dependencies": {
...
},
"devDependencies": {
...
}
}
And I'm importing it with:
const Component = require("component");
When I try to load the page, I see the following error in the console:
bundle.js:1299 Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of exports.
And when debugging, sure enough, the call to require is giving me
Component = Object {}
I get the same results if I require the bundle.js directly after copying it into the project, so I feel like I must just not be setting up my build and publish the right way, and after searching for a while, I wasn't able to find out what I was doing wrong.
You should not be bundling your components when they are separated out into their own packages. If they are using ES6, it's a good idea to transpile them using Babel as a prepublish step, but you do not need to bundle them.
Think about what the bundle step is doing to your component. It is going through your entry point and pulling in any required dependencies into a single file. That means that your bundle.js result will have pulled in all of react, react-dom, and anything else you required from your component.
Only your main application (which will be requiring the component packages) needs a bundle step. Here, it will resolve all dependencies including those that are nested and pull them together into your app's bundle.js, ensuring that you do not end up with duplicate copies of libraries like react pulled into your app.

Categories

Resources