Bundling a plugin with Rollup but having duplicate Vue.js package imported in the client app's bundle (Nuxt) - vue.js

Dear Stack Overflow / Vue.js / Rollup community
This could be a noob question for the master plugin developers working with Vue and Rollup. I will write the question very explicitly hoping that it could help other noobs like me in the future.
I have simple plugin that helps with form validation. One of the components in this plugin imports Vue in order to programatically create a component and append to DOM on mount like below:
import Vue from 'vue'
import Notification from './Notification.vue' /* a very simple Vue component */
...
mounted() {
const NotificationClass = Vue.extend(Notification)
const notificationInstance = new NotificationClass({ propsData: { name: 'ABC' } })
notificationInstance.$mount('#something')
}
This works as expected, and this plugin is bundled using Rollup with a config like this:
import vue from 'rollup-plugin-vue'
import babel from 'rollup-plugin-babel'
import { terser } from 'rollup-plugin-terser'
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
export default {
input: 'src/index.js',
output: {
name: 'forms',
globals: {
vue: 'Vue'
}
},
plugins: [
vue(),
babel(),
resolve(),
commonjs(),
terser()
],
external: ['vue']
}
As you can see, Vue.js is getting externalised in this bundle. The aim (and the assumption) is that the client app that imports this plugin will be running on Vue, therefore there's no need to bundle it here (assumption).
The very simple src/index.js that the bundler uses is below:
import Form from './Form.vue'
export default {
install(Vue, _) {
Vue.component('bs-form', Form)
}
}
Rollup creates 2 files (one esm and one umd) and references them in in the plugins package.json file like below:
"name": "bs-forms",
"main": "./dist/umd.js",
"module": "./dist/esm.js",
"files": [
"dist/*"
],
"scripts": {
"build": "npm run build:umd & npm run build:es",
"build:es": "rollup --config rollup.config.js --format es --file dist/esm.js",
"build:umd": "rollup --config rollup.config.js --format umd --file dist/umd.js"
}
Everything works as expected up to this point and the bundles are generated nicely.
The client app (Nuxt SSR) imports this plugin (using npm-link since it's in development) with a very simple import in a plugin file:
/* main.js*/
import Vue from 'vue'
import bsForms from 'bs-forms'
Vue.use(bsForms)
This plugin file (main.js) is added to nuxt.config.js as a plugin:
// Nuxt Plugins
...
plugins: [{src: '~/plugins/main'}]
...
Everything still works as expected but here comes the problem:
Since the clients is a Nuxt app, the Vue is imported by default of course but the externalised Vue module (by the forms plugin) is also imported in the client. Therefore there is a duplication of this package in the client bundle.
I guess the client app can configure its webpack config in order to remove this duplicated module. Perhaps by using something like a Dedupe plugin or something? Can someone suggests how to best handle situation like these?
But what I really want to learn, is the best practice of bundling the plugin at the first place, so that the client doesn't have to change anything in its config and simply imports this plugin and move on.
I know that importing the Vue.js in the plugin may not be a great thing to do at the first place. But there could be other reasons for an import like this as well, for example imagine that the plugin could be written in Typescript and Vue.js / Typescript is written by using Vue.extend statements (see below) which also imports Vue (in order to enable type interface):
import Vue from 'vue'
const Component = Vue.extend({
// type inference enabled
})
So here's the long question. Please masters of Rollup, help me and the community out by suggesting best practice approaches (or your approaches) to handle situations like these.
Thank you!!!!

I had the same problem and I found this answer of #vatson very helpful
Your problem is the combination of "npm link", the nature of nodejs module loading and the vue intolerance to multiple instances from different places.
Short introduction how import in nodejs works. If your script has some kind of library import, then nodejs initially looks in the local node_modules folder, if local node_modules doesn't contain required dependency then nodejs goes to the folder above to find node_modules and your imported dependency there.
You do not need to publish your package on NPM. It is enough if you generate your package locally using npm pack and then install it in your other project npm install /absolute_path_to_your_local_package/your_package_name.tgz. If you update something in your package, you can reinstall it in your other project and everything should work.
Here is the source about the difference between npm pack and npm link https://stackoverflow.com/a/50689049/6072503.

I have sorted this problem with an interesting caveat:
The duplicate Vue package doesn't get imported when the plugin is used via an NPM package (installed by npm install -save <plugin-name> )
However, during development, if you use the package vie npm link (like npm link <plugin-name>) then Vue gets imported twice, like shown in that image in the original question.
People who encounter similar problems in the future, please try to publish and import your package and see if it makes any difference.
Thank you!

Related

SvelteKit breaks npm's import mechanism

I've written several npm library projects, and this is the way I import symbols in one JS file from another JS file, but it won't work in the script section of a svelte file:
My 'package.json' file has a name field (e.g. set to '#jdeighan/something`) and an 'exports' section with entries like "./utils": "./src/lib/utils.js". Then in any other JS file I can import symbols from utils.js with "import {somesymbol} from '#jdeighan/something/utils'. It's how to do imports from a library that you've installed with 'npm install', but it also (cleverly) works inside the project itself. But in a svelte file, this won't work - I get the error message "Failed to resolve import "#jdeighan/something/utils" from "src\routes+page.svelte". Does the file exist?". Here is what I have in my svelte file:
<script>
import {somesymbol} from '#jdeighan/something/utils';
</script>
I know that svelte has a handy $lib alias, but I'd prefer to use the npm standard mechanism, but it seems to be broken when using SvelteKit (not sure about using plain svelte)
I'd prefer to use the npm standard mechanism
This is absolutely not the standard mechanism. I have never seen people import from the current project by package name. While this is supported by Node itself, nothing else seems to support it, including e.g. the VS Code language server which will be unable to provide code navigation.
Using the name makes it less clear that the import is local and not a separate dependency and if the name were to be changed it would have to be adjusted everywhere.
I would recommend just not doing that. SvelteKit has $lib predefined as a default to provide essentially the same functionality in a convention-based way that actually works.
If you create a project with just these 3 files, then execute node foo.js in a console window, you get "Hello, World!":
package.json:
{
"name": "#jdeighan/something",
"type": "module",
"version": "1.0.0",
"exports": {
"./utils": "./utils.js"
}
}
foo.js:
import {message} from '#jdeighan/something/utils'
console.log(message);
utils.js
export let message = 'Hello, World!';

Importing react-native package when running Vite

I am new to Vite and Vitest. I am experimenting a little bit, trying to add Vitest to a React-native app. I know Vite doesn't really support React Native but I would like to trying running just the tests with Vitest.
I get an error when trying to import React-native modules:
Module .../node_modules/react-native/index.js:14 seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package "react-native" asking them to ship the file in .mjs extension or add "type": "module" in their package.json.
As a temporary workaround you can try to inline the package by updating your config:
// vitest.config.js
export default {
test: {
deps: {
inline: [
"react-native"
]
}
}
}
When adding the suggested config the tests break inside React-native instead, as if the modules in fact is not supported.
What is going on here? Is React-Native only published as commonjs modules, while only esm-modules is supported by Vite? Is there a way around it?
Thanks in advance,
M.

webpack-resolve-alias to resolve dependecy's sub-dependency

I'm sort of stuck with one problem and googling it for about three hours brought me nowhere.
Here's the problem. I'm trying to develop my own custom PDF viewer based on PDF.JS lib. Officially it is distributed on npm as pdfjs-dist package. However I need to extend a few classes that are not accessible in pdfjs-dist. So I npm install'ed or yarn add'ed the original repo https://github.com/mozilla/pdf.js.git. This way I have access to classes I need. Inside pdf.js there are core lib that is stored in pdf.js/src/ and a pdf-viewer app that is stored in pdf.js/web/.
Inside that pdf.js/web/ app core lib (pdf.js) is referenced via pdfjs-lib alias that is resolved to pdf.js/src/ during pdf.js inner build process by gulp.
For example pdf.js/web/base_viewer.js:
import { AnnotationLayerBuilder } from './annotation_layer_builder';
import { createPromiseCapability } from 'pdfjs-lib';
import { PDFPageView } from './pdf_page_view';
So now I'm trying to import that pdf.js/web/base_viewer.js in my app that is using latest Webpack 4 (I guess), and this pdfjs-lib sub-dependency is not resolved.
I've tried webpack's resolve-alias mechanism (https://webpack.js.org/configuration/resolve/):
resolve: {
alias: {
'pdfjs-lib': path.resolve(__dirname, './node_modules/pdf.js/src/')
},
}
...but looks like it resolves dependencies only of my own App, but not sub-dependencies of my dependency.
Just in case I'm building Vue 3.0 app using vue-cli and access webpack config this way: https://cli.vuejs.org/guide/webpack.html, but don't think it matters.
Any help from Webpack gurus here?
Thanks.

Snap.svg with Vue.js

I'm trying to use Snap.svg in my Vue.js app, however I'm confuse with how to do so.
I used Vue CLI 3 command to initialized my project, and install the snapsvg dependency with yarn.
Also, I read this article.
But I can't find the webpack.base.conf.js file !
When I try to import the dependency into my main.js file or into any component I've got no error but my App become empty.
What did I miss to properly import Snap.svg in my Vue.js app ?
Instead of hacking webpack,
use snapsvg-cjs
This project simply unwraps the excellent SnapSVG from its published AMD format and hosts it on NPM as CommonJS in a package called snapsvg-cjs. This package then works out-of-the-box with Webpack, without needing any import-loader hax.
npm install snapsvg-cjs;
the only line of code you'll need:
import "snapsvg-cjs";
In Vue cli 3, webpack is configured in vue.config.js
To use snap place the vue.config.js with the following content in your root directory
module.exports = {
chainWebpack: config => {
config.module
.rule("snapsvg")
.test(require.resolve("snapsvg"))
.use("imports-loader?this=>window,fix=>module.exports=0")
.loader("imports-loader")
.end();
}
};
add this line to your main.js file:
const snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);

Add dependencies to Aurelia project

I set up an Aurelia project using the minimal project given here.
Then I added the fetch-client using npm install aurelia-fetch-client --save command. It updated package.json to contain following:
"dependencies": {
"aurelia-fetch-client": "^1.1.0"
}
But when I added import {HttpClient} from 'aurelia-fetch-client'; to my app.js file and tried running the app, but got following error:
system.js:4 GET http://localhost:5000/aurelia-fetch-client 404 (Not Found)
How do I add that? Where does this project keep track of its dependencies? I have seen lots of tutorials which help setting up the fetch client in aurelia cli projects. How about the project given here?
First, follow Fabio Luz's advice above and actually install either aurelia-cli or a skeleton framework.
Then, I have found this next step to be one of the most common sources of confusion for most people who are learning Aurelia. After installing new modules via npm, you have to manually list them as a dependency in aurelia.json (in your aurelia_project folder). For example, you would list aurelia-fetch-client as follows:
"dependencies": [
"aurelia-binding",
"aurelia-bootstrapper",
"aurelia-dependency-injection",
"aurelia-event-aggregator",
...
"aurelia-fetch-client",
...
After it is listed as a dependency, it will be included in the vendor.js bundle (in the CLI, by running au run --watch) so that it can be accessed by your application when you import it in individual components.
import {HttpClient} from 'aurelia-fetch-client';
For me it worked like this (using the project generated by the CLI):
npm i whatwg-fetch --save
npm i aurelia-fetch-client --save
add "aurelia-fetch-client" to dependencies in aurelia_project/aurelia.json
example of app.js:
import {HttpClient} from 'aurelia-fetch-client';
let client = new HttpClient();
export class App{
activate(){
client.fetch('http://...json');
.then(response => response.json())
.then(data =>{
console.log(data)
});
}
}
You can also install dependencies with the CLI itself.
It doesn't always get it 100% correct but can point you in the right direction if struggling.
For example au install aurelia-fetch-client
It will download the dependency, add to packages.json and attempt to create an entry for the bundling.