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.
Related
Preamble
I'm very new to parcel and using bundlers so I could be doing something completely wrong.
I followed the guidance provided at Building a library with Parcel since I am trying to bundle the server-side code which will be executed by node.js
My package.json contains the following(removed uneccessary details):
{
"source":"bin/server.js",
"module":"dist/server.js"
"scripts":{
"build": "parcel build"
}
"type":"module"
}
Main Problem
When I build my application with npm rum build it generates everything just fine, no errors are thrown and it only takes about 2 seconds.
Then when I try to run the application with "node .\bin\server.js" it throws the following error.
import {exit as $a7IyA$exit, on as $a7IyA$on} from "process";
------------------------------^^--------------------------------------------------
SyntaxError: The requested module 'process' does not provide an export named 'on'
For additional context, the application does run as expected before I bundle with parcel and the server.js file does include the following as the first import (where error is thrown).
import {exit as $a7IyA$exit, on as $a7IyA$on} from "process";
Update:
After reviewing the Targets documentation, this does make me more confident that Parcel can be used for backend bundling and I tried updating my package.json to the following:
{
"targets": {
"server": {
"source": "bin/server.js",
"context": "node",
"distDir": "./dist",
"includeNodeModules": true,
"outputFormat": "esmodule"
}
},
"scripts":{
"build": "parcel build"
}
"type":"module"
}
This did get me further, but I'm now getting the following error:
Uncaught ReferenceError ReferenceError: $2az4q$relative is not defined
I have an ES5 object/function that I'm trying to use inside an NPM package. That object is in a namespace such as
MY_NAMESPACE.myObject = function(){...}
where MY_NAMESPACE is just an object. For the web, I'd just link a JS file where the object/function is and then do
let whatever = new MY_NAMESPACE.myObject();
I have saved the source as my_function.js.
I created a npm package like so, in order to install it in my app
{
"name": "whatever",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "whatever",
"license": "Apache-2.0",
"exports" :{
"./my_function.js" : "./my_function.js"
}
}
When I install the package locally, I can see that my_function.js is in node_modules/whatever
How can I now reference/import my_function.js inside my app, then be able to call
let whatever = new MY_NAMESPACE.myObject();
Some of the awful Node.JS documentation mentions .mjs files to add ES modules but can't find examples/tutorials... I'm also trying to not add anything like module.exports to the my_function.js because that file is updated constantly and used in a web/front end environment as well.
So basically, I'm trying to attach a .js file inside a NPM package and would like to have its content available in my app. I'm hoping that, adding something to the index.js of the package would render the objects declared in the file, available across my app... I just don't know where to go from here.
One pattern to expose the function would be using module.exports like this:
# my_function.js
MY_NAMESPACE = {};
MY_NAMESPACE.myObject = function(){...};
module.exports = MY_NAMESPACE;
And then it can be consumed by another module in the same directory as:
# consumer JS file
let MY_NAMESPACE = require('./my_function');
let test = new MY_NAMESPACE.myObject();
If you really want to package up my_function.js in a separate node.js package (for example, if you need to share it between projects), there are a few additional steps to take. This documentation is a good starting point.
I’ve released a NPM package which is a plugin for a framework where only the main entry of my package.json is needed for usage in the framework and its environment. I also want to be able to use a subpath of the plugin in order for users to be able to use the plugin outside of this framework as well, which will require that the main entry point is never initialized as there are framework specific dependencies used there which I don't want to initialize when using this plugin outside of the framework.
My project structure looks like this:
.
├── index.js
├── submodule.js
└── package.json
Source code for the sake of this example looks like this:
// index.js
export default function () {
return "foo";
}
// submodule.js
export default function () {
return "bar";
}
// package.json
{
"name": "my-package",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"exports": {
".": "./index.js",
"./submodule": "./submodule.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT"
}
According to Node.js documentation on this matter, this setup should allow me to use my package like this:
import myPackage from ’my-package’
import mySubModule from ’my-package/submodule’
In order to be able to test my package locally I run npm link in the project root of my npm package and then in another project i run npm link my-package. Now that I try to run the project (using parcel-bundler) that imports my-package and my-package/submodule like in the example above I get the following exception:
Cannot resolve dependency 'my-package/submodule'
I'm using NVM with Node v.12.18.4 and NPM v.7.15.0. I have also tried with Node v.14.17.0 but the issue persists. What am I missing?
It seems that the project setup is correct and that the problem lies in the parcel-bundler which does not support package.json#exports yet. There's currently an open issue on this matter.
I'm trying to export a Vue component as a package, and using vue cli to build the dist. I intend to publish it on npm, but I'm currently using a symbolic link for testing purpose. However even with a simple hello-world project I can't make a valid package.
I created a project:
vue create hello-world
Then I modified the package.json:
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --target lib --name vue-hello-world ./src/components/HelloWorld.vue",
"lint": "vue-cli-service lint"
},
"main": "./dist/vue-hello-world.common.js",
Then I call
npm run build
and it compiles without error.
Then I make an import in a vue component in another project (I used a symbolic link in node_modules):
import HelloWorld from "hello-world";
On page render I get the following error:
[Vue warn]: Failed to resolve async component: function MediaViewerPdf() {
return Promise.all(/*! import() */[__webpack_require__.e(62), __webpack_require__.e(46)]).then(__webpack_require__.bind(null, /*! ./viewers/MediaViewerPdf.vue */ "./vuejs/components/mediaViewer/viewers/MediaViewerPdf.vue"));
}
Reason: ReferenceError: require is not defined
Any idea what's happening?
Remarks:
using vue inspect, I checked that in webpack config that:
target: "web"
I already set resolve.symlinks at false on the importing project.
EDIT: I have confirmed that it doesn't come from the symbolic link, I have exactly the same error with package directly on node_modules.
Repo with whole code: https://github.com/louis-sanna/vue-hello-world
So I asked the question on the vue-cli repo and I got the solution! https://github.com/vuejs/vue-cli/issues/4245
Turns out NODE_ENV was already set at development in my shell, so it was the mode used to make the build.
Just need to set the mode explicitly and it works:
vue-cli-service build --target lib --name vue-hello-world ./src/components/HelloWorld.vue --mode production
You may need to add it to vue.config.js:
config
.mode("production")
This happens due to the fact that Vue CLI Webpack setup by default does not import commonjs modules, as described in your "main" field in package.json. So the problem is with the project that attempts import, not with the project that exports the component.
There are two ways to attempt to solve this problem.
From the importing project side
You can remedy this by installing babel plugins for the project that imports your components and setting babel.config.js
module.exports = {
presets: [
'#vue/app'
],
plugins: [
'#babel/plugin-transform-modules-commonjs', // leave to import .common.js files
'#babel/plugin-transform-modules-umd' // leave to import .umd.js files
]
}
But doing this alone will not be sufficient: you also will need to import CSS that is generated with the library by adding this in your entry file
import 'hello-world/dist/vue-hello-world.css';
Note that I have only tested this using yarn link, but I have confidence that this will work with an imported separate npm module just fine.
From the library side
The intent (I suppose) of the original question - how do I bundle a library so my users don't have to do this little dance each time they want to use it?
Option 1: don't bundle it - provide .vue files and sources. Just include everything in 'src' directory of your module, write a readme with explanation and be done with it. Let the importing project figure the compilation and bundling out.
Option 2: use rollup with Vue plugin to roll components into bundle. There is an example on how to do that. In that example you can see that your project will be able to import .esm build
https://github.com/vuejs/rollup-plugin-vue/tree/master/cookbook/library
Not sure how you are creating the symbolic link, but you should use npm link for that. If you are still having problems (like I did myself) I would suggest you try npm-link-better:
npm install -g npm-link-better
cd vue-hello-world
npm link
cd ../hello-world
nlc -w vue-hello-world
For building component libraries, I suggest you have a look at vue-cli-plugin-component. This plugin already sets up the vue-cli project pretty well.
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');