I am trying to import an npm package into a Vue component.
The package (JSPrintManager - here) is just a JavaScript file with no exports. Consequently, I cannot use:
import { JSPM } from "jsprintmanager"
I have also had no success with the following:
import JSPM from "jsprintmanager"
import * as JSPM from "../../node_modules/jsprintmanager/JSPrintManager"
import * as JSPM from "../../node_modules/jsprintmanager/JSPrintManager.js"
Am I barking up the wrong tree?
If there is no way to import an npm package that is not a module, is there another way to load the relevant JavaScript file (currently residing in my node-modules directory) into my component?
I am using the Vue CLI
jspm.plugin.js
import * from '../../node_modules/jsprintmanager/JSPrintManager';
export default {
install(Vue) {
Vue.prototype.$JSPM = window.JSPM
}
}
main.js
import Vue from 'vue'
import JSPM from './jspm.plugin';
Vue.use(JSPM);
In any of your components you can now access JSPM as this.$JSPM
If you want to use it outside of your components (say, in store) and you want it to be the same instance as the one Vue uses, export it from Vue, in main.js
const Instance = new Vue({
...whatever you have here..
}).$mount('#app');
export const { $JSPM } = Instance
Now you can import { $JSPM } from '#/main' anywhere.
That would be the Vue way. Now, in all fairness, the fact your import is run for the side effect of attaching something to the window object which you then inject into Vue is not very Vue-ish. So the quick and dirty way to do it would be, in your component:
import * from '../../node_modules/jsprintmanager/JSPrintManager';
export default {
data: () => ({
JSPM: null
}),
mounted() {
this.JSPM = window.JSPM;
// this.JSPM is available in any of your methods
// after mount, obviously
}
}
The main point of the above "simpler" method is that you have to make the assignment after the page finished loading and running the JSPM code (and window.JSPM has been populated).
Obviously, if you disover it sometimes fails (due to size, poor connection or poor hosting), you might want to check window.JSPM for truthiness and, if not there yet, call the assignment function again after in a few seconds until it succeeds or until it reaches the max number of tries you set for it.
Related
In React when you wanna import components from other files we use:
import ComponentName from 'somePlace';
That works fine, but I wanna know if there is a way to import the content of a file instead of the exports. I wanna put all import statements for components in a single file (e.g. imports.js) and have a statement like:
import './import.js' to all documents;
so all my components are automatically imported everywhere.
Is there a way to do that?
Globally import modules? Not really, no. And neither should you need to.
A hacky "solution" would be assigning all imports to the global context (e.g. window in the browser) so it's accessible that way. That's possible, but definitely not recommended. It'll also prevent your bundler (most likely Webpack) from optimizing a lot of code.
Apart from the technical aspect, there are other reasons not to do so. If you specify the imports in each file, you know exactly what imports that file needs and under what variables it is imported as for that file.
If you still want to simplify importing the same components over and over again, you can have this setup:
imports.js
// For default exports
import ComponentA from 'wherever';
export { ComponentA };
// For regular exports
//import { ComponentB } from 'wherever';
export { ComponentB } from 'wherever';
// For whole modules
//import * as wherever from 'wherever';
export * as wherever from 'wherever';
someOtherFile.js
// Either import as a namespace
import * as Imports from './imports';
console.log([
Imports.ComponentA,
Imports.ComponentB,
Imports.wherever.someFieldFromTheWhereverModule,
]);
// Or partial import
import { ComponentA, ComponentB } from './imports';
I've got a file called "globalHelper.js" like this:
exports.myMethod = (data) => {
// method here
}
exports.myOtherMethod = () => { ... }
and so on...
I import my Helper in other files like this:
import helper from "../Helper/globalHelper";
Now there is the problem:
In the past, everything worked with that when running my build-script:
"build": "GENERATE_SOURCEMAP=false react-scripts build"
but for some reason, when I run "npm run build", I get the error:
Attempted import error: '../Helper/globalHelper' does not contain a default export (imported as 'helper')
However, when I simply start my development server (npm start), everything works just fine.
I already tried to import like import { helper } from "../Helper/globalHelper";, but that did not work either.
Does someone know how to solve that?
try exporting like this with ES6 syntax
export const myOtherMethod = () => { ... }
then
import { myOtherMethod } from '...your file'
The method you are using exports.functionName is CJS. you need to use require to get the methods.
The CommonJS (CJS) format is used in Node.js and uses require and
module.exports to define dependencies and modules. The npm ecosystem
is built upon this format.
If you want to use module method you can do this.
export { myOtherMethod1, myOtherMethod2 }
then you import like this.
import * as Module from '....file name'
Since you've not export default you should import the function with {} like :
import {helper} from "../Helper/globalHelper";
I have created a javascript library with webpack, that outputs a systemjs module. This module has a dependency on react, which I specified as an external.
The resulting javascript file starts like this:
System.register(["react"], function(__WEBPACK_DYNAMIC_EXPORT__) {
var __WEBPACK_EXTERNAL_MODULE_react__;
return { ....
Additionally I have an app, that uses SystemJS during runtime to load that module. In order to provide the react dependency, I have defined an importmap:
{
"imports": {
"react": "https://unpkg.com/react#16.11.0/umd/react.production.min.js"
}
}
And the part, where I import the module, looks like this:
const modulePromise = System.import(MODULE_URL);
modulePromise.then(module => {
console.log('module loaded successfully!');
});
The problem now is, that the console.log is never called, because I get a TypeError, that says, that "Component is not a property of undefined", which tells me, that somehow react has not correctly been passed to my module.
To be precise, in the browser network tab I see, that my module and the react import is indeed loaded, but somehow it is not correctly processed.
Has anyone an idea, what i might be doing wrong?
OK, so eventually I solved this myself, although a bit different.
First, I did not use the unpkg link anymore, but I actually include React as a library in my main app.
And I changed my importmap to:
<script type="systemjs-importmap">
{
"imports": {
"react": "app:react",
"react-dom": "app:react-dom"
}
}
</script>
Also in the main app I use System.set(...) from SystemJs to tell SystemJS where to find the 'app:react' and 'app:react-dom' dependencies:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import 'systemjs/dist/system.min';
...
System.set('app:react', { default: React, __useDefault: true });
System.set('app:react-dom', { default: ReactDOM, __useDefault: true });
And now, if I load my module with that external react dependency through SystemJS, it works.
I've looked at other answers with this problem, and it seems to be caused by trying to import vue-router into the test. This however, is not the case for my problem. Here is my test code:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import ListDetails from '../components/homepage/ListDetails'
import EntityList from '../components/homepage/EntityList'
import BootstrapVue from 'bootstrap-vue'
import Vuex from 'vuex'
import faker from 'faker'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
describe('ListDetails.vue', () => {
it('gets the correct page list when router list id param present', () => {
const selected_list = {
id: faker.random.number(),
name: faker.lorem.words(),
entries: []
}
testRouteListIdParam(selected_list)
})
})
Then in testRouteListIdParam, I have:
function testRouteListIdParam(selected_list) {
// just assume this sets up a mocked store properly.
const store = setUpStore(selected_list, true)
const $route = {
path: `/homepage/lists/${selected_list.id}`
}
const wrapper = mount(ListDetails, {
mocks: {
$route
},
store,
localVue
})
}
As soon as mount() happens, I get the error:
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
Any ideas why this would be happening? Again, I'm not using VueRouter anywhere in the unit tests, so I'm not sure why I'm getting the error. Could it be BootstrapVue or Vuex that are messing things up?
So this is a bug with vue-test-utils. If you are using VueRouter anywhere (even if it's not used in any unit test), you will get the above error.
A work around is to use process.env.NODE_ENV in your unit tests and set it to 'test', and wherever you're using VueRouter, check process.env.NODE_ENV like so:
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
at least until vue-test-utils bug is fixed, this should fix this problem
I think these docs are relevant to your situation:
Common gotchas
Installing Vue Router adds $route and $router as read-only properties on Vue prototype.
This means any future tests that try to mock $route or $router will fail.
To avoid this, never install Vue Router globally when you're running tests; use a localVue as detailed above.
The error you're seeing indicates that one of your components (and outside your test code) is installing VueRouter (e.g., Vue.use(VueRouter)).
To address the issue, search for the VueRouter installation in your component code path, including any of their imports, and refactor it so that the import is not required there. Typically, the installation of VueRouter exists only in main.js or its imports.
GitHub demo
I encountered this, and it was because I was importing vueRouter into a controller, outside of a vueComponent, where this.$router wasn't defined.
import router from '#/router';
router.push('/foo')
janedoe's answer can work but it's often risky to modify production code just to make some tests pass. I prefer to bypass the bug by doing this:
Run your test in watch mode
npx jest --watch src/components/Foo.test.ts
Locate Vue.use(VueRouter) in your code and diagnose what is the chain of components indirectly running the code by adding this just above
const error = new Error();
console.log(
error.stack
?.split('\n')
.filter((line) => line.includes('.vue'))
.join('\n'),
);
This logs a list of file path like
console.log
at Object.<anonymous> (/path/to/components/Bar.vue:1:1)
at Object.<anonymous> (/path/to/components/Foo.vue:1:1)
Chose a component in this list and, in your test file, mock it
jest.mock('/path/to/components/Bar.vue');
I've got a nice little ES6 React component file (simplified for this explanation). It uses a library that is browser-specific, store This all works beautifully on the browser:
/app/components/HelloWorld.js:
import React, { Component } from 'react';
import store from 'store';
export default class HelloWorld extends Component {
componentDidMount() {
store.set('my-local-data', 'foo-bar-baz');
}
render() {
return (
<div className="hello-world">Hello World</div>
);
}
}
Now I'm trying to get it to render on the server as follows, using babel-register:
/server/routes/hello-world.js:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import HelloWorld from '../../app/components/HelloWorld'
export default function(req, res) {
res.render('root', {
reactHTML: ReactDOMServer.renderToString(<HelloWorld />),
});
}
I get an error from the node server saying "window is not defined" due to importing 'store'. Ideally I could conditionally import by detecting the environment (node vs browser), but conditional imports aren't supported in ES6.
What's best way to get around this? I don't actually need to execute the browser code (in this case componentDidMount won't be called by ReactDOMServer.renderToString) just get it running from node.
One way would be using babel-rewire-plugin. You can add it to babel-register via the plugins option
require('babel/register')({
plugins: ['babel-rewire-plugin']
});
Then rewire your store dependency to a mocked store:
HelloWorld.__Rewire__('store', {
set: () => {} // no-op
});
You can now render HelloWorld from the server peacefully.
If you want to suppress the load of some npm module, you can just mock it.
Put this on your node.js application setup, before the HelloWorld.js import:
require.cache[require.resolve('store')] = {
exports: {
set() {} // no-op
}
};
This value will be used instead of the real module, which doesn't need on your purposes. Node.js module API is stable, so this behavior will not be broken and you can rely on it.