Publish a react component to npm and reuse it - npm

I am trying to publish a basic React component to my npm registry and trying to reuse it. I think I am not following proper way to distribute my react component. Here's what I have:
This is the directory structure:
MyReactPOC
-> main.jsx
-> .npmrc
-> package.json
-> webpack.config.js
main.jsx
import React from 'react';
class MyComponent extends React.Component {
render() {
return (
<div>
<p>Hello from MyComponent!!</p>
</div>
);
}
}
export default MyComponent
package.json
{
"name": "#pankaj/my-component",
"version": "1.0.7",
"description": "POC for importing a component",
"main": "./dist/bundle.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "webpack --config webpack.config.js"
},
"repository": {
"type": "git",
"url": "my git repo"
},
"author": "Pankaj",
"license": "ISC",
"dependencies": {
"react": "~15.5.4",
"react-dom": "~15.5.4"
},
"devDependencies": {
"babel-cli": "~6.24.1",
"babel-core": "~6.24.1",
"babel-loader": "~6.4.1",
"babel-preset-es2015": "~6.24.1",
"babel-preset-react": "~6.24.1",
"webpack": "~2.4.1"
}
}
webpack.config.js
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './main.jsx',
output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js' },
module: {
loaders: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}
]
},
};
I import the module in another project using import MyComponent from '#pankaj/my-component'.
When I use this component like
I get the following error:
React.createElement: type is invalid -- expected a string (for
built-in components) or a class/function (for composite components)
but got: object. You likely forgot to export your component from the
file it's defined in.
Please help me understand the right way to distribute the react components so that they can be used by other projects within my org.
Here is how I use this component:
ComponentUse.js
import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from '#pankaj/my-component';
ReactDOM.render(
<MyComponent/>,
document.getElementById('root')
);
I have an index.html that has the 'root' div.

Every react component needs a return statement. Add a return statement in your render function and it should work.
...
render() {
return (<div>...</div>)
}
You cannot directly render to the Dom from your react component, instead return it so that react can work with it.
In webpack, specify your output file as a library using output.library https://webpack.js.org/concepts/output/#output-library

I wrote a full Medium story because I had the same issue as you and there is no information about it.
Check it out: https://medium.com/#BrodaNoel/how-to-create-a-react-component-and-publish-it-in-npm-668ad7d363ce
The main fix is to add libraryTarget: 'umd' in the webpack.config.js file

If you export with es6 syntax with babel, your component will be in MyComponent.default namespace. To avoid this you should install:
npm i --save-dev babel-plugin-add-module-exports in your .babelrc?
and add it to the babel conf:
{
"presets": [ "es2015", "react"],
"plugins": ["add-module-exports"]
}

Related

Why are some images not loading on capacitor ios app using vue and vite

I am working on a capacitor app and it works fine both on browser and android phones but for one page when it's routed to it gets stuck on ios.
I have tried to check and saw it's because some files are not loading
This is my vite config
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
import nodeResolve from '#rollup/plugin-node-resolve'
import json from '#rollup/plugin-json'
import alias from '#rollup/plugin-alias'
import vuetify from '#vuetify/vite-plugin'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
vuetify({
autoImport: true,
}),
alias(),
],
define: {
'process.env': {}
},
build: {
rollupOptions: {
transformAssetsUrls: {
img: ['src', 'data-src']
},
plugins: [
nodeResolve({
browser: true,
preferBuiltins: false
}),
json()
]
}
},
resolve: {
alias: {
'./runtimeConfig': './runtimeConfig.browser',
'#': path.resolve(__dirname, 'src'),
},
},
server: {
fs: {
// Allow serving files from one level up to the project root
strict: false
}
},
css: {
preprocessorOptions: {
// global scss import
scss: {
charset: false,
additionalData: `
#import "#/styles/variables";
#import "#/styles/mixins.scss";
`
}
}
}
})
And this is how I am loading the images located inside src/assets folder:
I am using vue 3, vite 2.7 and capacitor 3
Update:
I'm not sure what was happening but the issue was occurring when I used vue <component /> tag. I fixed it by rewriting that part
You probably fixed this but since we fixed our, i'd like to share our solution.
We're building an app for iOS and Android using Quasar.js v2, vue3 and capacitor.
We had a similar problem when we built to production for iOS and Android.
In order To fix this we had to add viteConf.base = '/' in the build object in the quasar config file (which replaces vite.config.js), doc for base shared options here
build: {
extendViteConf (viteConf, { isServer, isClient }) {
viteConf.base = '/'
}...
Dependencies in package.json
"#capacitor-community/firebase-analytics": "^1.0.1",
"#capacitor/app": "^1.0.7",
"#capacitor/clipboard": "^1.0.2",
"#capacitor/device": "^1.1.1",
"#capacitor/keyboard": "^1.2.2",
"#capacitor/push-notifications": "^1.0.9",
"#capacitor/share": "^1.0.7",
"#capgo/capacitor-updater": "^3.3.12",
"#quasar/app-vite": "^1.0.5",
"#quasar/extras": "^1.14.3",
"quasar": "2",
"vue": "3",
Hope this can help

Vue test utils for Vue3 : document is not defined

I try to bootstrap a simple app based on the following Vue3, Vite, Vitest
I also installed the vue 3 compatible version of vue test utils to test vue components.
I have an error trying to replicate the basic example in the docs :
import { mount } from "#vue/test-utils";
import { expect, test } from 'vitest'
// The component to test
const MessageComponent = {
template: "<p>{{ msg }}</p>",
props: ["msg"],
};
test("displays message", () => {
const wrapper = mount(MessageComponent, {
props: {
msg: "Hello world",
},
});
// Assert the rendered text of the component
expect(wrapper.text()).toContain("Hello world");
});
FAIL src/tests/hello-world.test.ts > displays message
ReferenceError: document is not defined
❯ Proxy.mount node_modules/#vue/test-utils/dist/vue-test-utils.cjs.js:7840:14
7838| addToDoNotStubComponents(component);
7839| registerStub({ source: originalComponent, stub: component });
7840| var el = document.createElement('div');
| ^
7841| if (options === null || options === void 0 ? void 0 : options.attachTo) {
7842| var to = void 0;
Re-running tests... [ src/tests/hello-world.test.ts ]
My package.json
{
"name": "vite-vue3-poc",
"version": "0.0.0",
"scripts": {
"serve": "vite preview",
"build": "vite build",
"coverage": "vitest --coverage",
"dev": "vite",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"#mdi/font": "5.9.55",
"prettier": "^2.5.1",
"roboto-fontface": "*",
"vue": "^3.2.25",
"vuetify": "^3.0.0-alpha.0",
"webfontloader": "^1.0.0"
},
"devDependencies": {
"#vitejs/plugin-vue": "^2.0.0",
"#vue/cli-plugin-babel": "5.0.0-beta.7",
"#vue/cli-service": "5.0.0-beta.7",
"#vue/test-utils": "^2.0.0-rc.18",
"#vuetify/vite-plugin": "^1.0.0-alpha.3",
"sass": "^1.38.0",
"sass-loader": "^10.0.0",
"vite": "^2.7.2",
"vitest": "^0.1.23",
"vue-cli-plugin-vuetify": "~2.4.5",
"vuetify-loader": "^2.0.0-alpha.0"
}
}
vite.config.js
import { defineConfig } from "vite";
import vue from "#vitejs/plugin-vue";
import vuetify from "#vuetify/vite-plugin";
import path from "path";
/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config)
// https://vitejs.dev/config/
export default defineConfig({
test: {
/* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true,
},
plugins: [
vue(),
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
vuetify({
autoImport: true,
}),
],
define: { "process.env": {} },
resolve: {
alias: {
"#": path.resolve(__dirname, "src"),
},
},
});
Finally fixed it by manually installing jsdom and declaring it in vite.config.ts
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
...
}
Like others have pointed out, you need to set environment: 'jsdom' in vitest.config.ts. Alternatively, you could set environment: 'happy-dom'.
In the example provided by the Vitest documentation, they used to use happy-dom instead of jsdom. From what I gather, happy-dom is a faster alternative to jsdom. I'm using happy-dom in my project, and I'm happy with it! :)
EDIT: I changed my wording to reflect the fact that the Vitest example used to use happy-dom. As of this writing, it uses jsdom.
No need to install jsdom manually. By setting environment: "jsdom" in the test property, Vitest automatically asks you if you want to install it.
This config helped me
Your vite.config.ts
import { fileURLToPath, URL } from "node:url"
import { defineConfig } from "vite"
import type { UserConfig as VitestUserConfigInterface } from "vitest/config"
import vue from "#vitejs/plugin-vue"
const vitestConfig: VitestUserConfigInterface = {
test: {
globals: true,
environment: "jsdom",
},
}
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": fileURLToPath(new URL("./src", import.meta.url)),
},
},
test: vitestConfig.test,
})

TypeError: Cannot read property 'PlatformLocalStorage' of undefined

I am running jest test on react-native and getting this following errors
Test suite failed to run
TypeError: Cannot read property 'PlatformLocalStorage' of undefined
2 | import thunk from 'redux-thunk';
3 | import { persistStore, persistReducer } from 'redux-persist';
> 4 | import AsyncStorage from '#react-native-community/async-storage';
| ^
5 |
"dependencies":{
"react": "16.8.3",
"react-native": "0.59.9",
},
"devDependencies": {
"jest": "^24.1.0",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.10.0",
},
"jest": {
"setupFilesAfterEnv": [
"<rootDir>/__tests__/setup/test-setup.js"
],
"setupFiles": [
"<rootDir>/__tests__/setup/test-setup.js"
],
"transformIgnorePatterns": [
"/node_modules(?![\\/]rn-fetch-blob[\\/]|[\\/]react-native[\\/]|[\\/]react-native-config[\\/]|[\\/]react-native-navigation[\\/]|[\\/]react-native-status-bar-height[\\/]|[\\/]react-native-animatable[\\/]|[\\/]react-native-restart[\\/]|[\\/]react-native-linear-gradient[\\/]|[\\/]react-native-i18n[\\/]|[\\/]react-native-modal[\\/]|[\\/]react-native-smooth-pincode-input[\\/]|[\\/]react-native-swipe-gestures[\\/]|[\\/]react-native-root-toast[\\/]|[\\/]react-native-root-siblings[\\/]|[\\/]static-container[\\/]|[\\/]react-native-elements[\\/]|[\\/]react-native-ratings[\\/]|[\\/]react-native-progress[\\/]|[\\/]react-native-dialog[\\/]|[\\/]react-native-pdf[\\/]|[\\/]react-native-webview[\\/]|[\\/]react-native-check-box[\\/]|[\\/]react-native-share[\\/]|[\\/]react-native-haptic-feedback[\\/]|[\\/]react-native-responsive-screen[\\/]|[\\/]react-native-keyboard-aware-scroll-view[\\/]|[\\/]he[\\/]|[\\/]react-native-iphone-x-helper[\\/]|[\\/]check-prop-types[\\/]|[\\/]react-native-image-progress[\\/]|[\\/]react-native-theming[\\/]|[\\/]#react-native-community/async-storage[\\/])/"
],
"testPathIgnorePatterns": [
"<rootDir>/(build|docs|node_modules)/",
"<rootDir>/__tests__/setup"
],
"preset": "react-native"
},
"rnpm": {
"assets": [
"fonts"
]
}
}
Can some one please help me why this errors are coming when I run jest test in react-native app. i have updated several dev dependencies but no luck.
Mock the AsyncStorage library. If, like me, you've ported from the react-native variant to the react-native-community one you may need to update your mocks (if you had them already).
jest.mock("#react-native-community/async-storage", () => {
// a map/dict/kvs of types to return - the leaves of the def
// are jest functions
const apiMock = {
getItem: jest.fn(() => { // mock's AsyncStorage.getItem()
return JSON.stringify("some mock data");
}),
};
return apiMock;
});
The solution is to mock the Package.
In your project root directory, create
__mocks__/#react-native-async-storage directory.
Inside that folder, create async-storage.js file.
Inside that file, export Async Storage mock.
export default from '#react-native-async-storage/async-storage/jest/async-storage-mock'
The full instruction is here https://react-native-async-storage.github.io/async-storage/docs/advanced/jest

How to configure Expo to allow global module imports?

I am looking for a way to get rid of these
import { get_records } from ../../../../store/actions/async';
To be honest I tried a lot of tricks:
Using a package.json in the src/ with {"name": "#app"} and trying to use:
import { get_records } from '#app/store/actions/async';
Using the React Native AppRegistry name:
import { get_records } from 'main/src/store/actions/async';
The same questions goes for assets.
I've seen solution with Webpack and Babel but I don't use them with React Native.
How do you manage to do that?
If you are using Expo, you must be using babel because only ES6 version of the libraries are shipped. Even projects created with react-native init come with babel by default. Check your project folder, it should have a .babelrc file.
You can then use babel-plugin-root-import:
import { get_records } from '~/store/actions/async';
.babelrc:
{
"presets": ["babel-preset-expo"],
"plugins": [
[
"babel-plugin-root-import",
{
"rootPathSuffix": "src"
}
]
]
}
Actually I was using Babel without noticing.
npm install --save-dev babel-plugin-module-resolver
npm install --save-dev eslint-import-resolver-babel-module
.babelrc
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
},
"plugins": [
["module-resolver", {
"alias": {
"~mobile": "./src",
"~assets": "./assets"
}
}]
]
}
.eslintrc
{
"settings": {
"import/resolver": {
"babel-module": {
"alias": {
"~mobile": "./src",
"~assets": "./assets"
}
}
}
}
}
Then I use it like that:
import { addToFavorite, removeFromFavorite } from '~mobile/store/actions';
await Font.loadAsync({
'cabin-reg': require('~assets/fonts/Cabin/Cabin-Regular.ttf'),
'league-spartan': require('~assets/fonts/LeagueSpartan/leaguespartan-bold.ttf')
})

[Vue warn]: Unknown custom element: <nuxt-link> - When running jest unit tests

Problem
I'm using nuxt 1.4 with routing using Jest to do unit testing. My application doesn't throw errors and seems to work perfectly. However when running my unit test npm run unit (which runs jest) it throws an error in the terminal: [Vue warn]: Unknown custom element: <nuxt-link> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Expected
I would expect it to not throw this error since my application is working.
Files
package.json:
{
"name": "vue-starter",
"version": "1.0.0",
"description": "Nuxt.js project",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint",
"test": "npm run lint && npm run unit",
"unit": "jest",
"unit:report": "jest --coverage"
},
"dependencies": {
"babel-jest": "^22.4.1",
"jest-serializer-vue": "^1.0.0",
"node-sass": "^4.7.2",
"npm": "^5.7.1",
"nuxt": "^1.0.0",
"sass-loader": "^6.0.7",
"vue-jest": "^2.1.1"
},
"devDependencies": {
"#vue/test-utils": "^1.0.0-beta.12",
"babel-eslint": "^8.2.1",
"eslint": "^4.15.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-vue": "^4.0.0",
"jest": "^22.4.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"jest": {
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
"snapshotSerializers": [
"<rootDir>/node_modules/jest-serializer-vue"
]
}
}
The component that I test:
<template>
<div>
<nuxt-link class="name" :to="{ path: `entity/${item.id}`, params: { id: item.id }}">{{item.name}}</nuxt-link>
<button class="connect" #click="connect">{{ btnText }}</button>
</div>
</template>
<script>
// import nuxtLink from '../.nuxt/components/nuxt-link';
const connectionStatusMap = [
'Connect',
'Connected',
'Pending',
'Cancel',
];
export default {
/*components: {
'nuxt-link': nuxtLink,
},*/
props: {
item: {
type: Object
}
},
...
}
</script>
My test script:
import TestItem from '../components/TestItem';
import { shallow, mount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
...
it(`should show the entity`, () => {
const wrapper = mount(TestItem, {
propsData: { item },
localVue,
store,
// stubs: ['nuxt-link'],
})
expect(wrapper.find('.name').text()).toBe(item.name);
});
it(`should show allow me to connect if I'm not yet connected`, () => {
const wrapper = shallow(TestItem, {
propsData: { item },
localVue,
store,
stubs: ['nuxt-link'],
})
expect(wrapper.find('.connect').text()).toBe('Connect');
});
...
I tried
I tried creating a localVue and also stubbing the component as suggested in this github comment
I also tried shallow/mount but that did not seem to work either.
This is how I was able to get rid of the annoying warning:
Include RouterLinkStub, eg.:
import { shallowMount, createLocalVue, RouterLinkStub } from '#vue/test-utils';
Map NuxtLink stub to RouterLinkStub
const wrapper = shallowMount(TestItem, {
...
stubs: {
NuxtLink: RouterLinkStub
}
})
And in case you were checking nuxt-link text or something, change:
const link = wrapper.find('nuxt-link');
to
const link = wrapper.find(RouterLinkStub);
Found this gold on https://onigra.github.io/blog/2018/03/19/vue-test-utils-router-link-stub/
Good thing you don't need to know japanese to read code...
Although the answers provided could work. What I ended up using was based on this guide
const wrapper = mount(TestItem, {
propsData: { item },
localVue,
store,
stubs: {
NuxtLink: true,
// Any other component that you want stubbed
},
});
I managed to get it working using this workaround for Storybook:
import { mount, createLocalVue } from '#vue/test-utils'
import Component from '#/components/Component.vue'
const localVue = createLocalVue()
localVue.component('nuxt-link', {
props: ['to'],
template: '<slot>NuxtLink</slot>',
})
describe('Test Component', () => {
const wrapper = mount(Component, {
stubs: ['nuxt-link'],
localVue
})
})
I added below lines of code to get this working.
In your test file
import NuxtLink from "path to nuxt-link.js"
Mycomponent.components.NuxtLink = NuxtLink
In your jest conf file
transformIgnorePatterns: [
"path to nuxt-link.js"
],
Or you could add below line in mount options
mount(Mycomponent, {stubs: ["nuxt-link"]})
I have:
// path: ./test/jest.setup.js
import Vue from 'vue'
import VueTestUtils from '#vue/test-utils'
// Mock Nuxt components
VueTestUtils.config.stubs['nuxt-link'] = '<a><slot /></a>'
VueTestUtils.config.stubs['no-ssr'] = '<span><slot /></span>'
and
// path: ./jest.config.js
module.exports = {
// ... other stuff
setupFilesAfterEnv: ['./test/jest.setup.js']
}
... and this solves all my jest test in the nuxt app
To anyone getting the Unknow custom element: <router-link>
My issue was, I used mount instead of shallow when creating the component.
shallow usage:
Like mount, it creates a Wrapper that contains the mounted and
rendered Vue component, but with stubbed child components.
Source: https://vue-test-utils.vuejs.org/en/api/shallow.html
Here is a working example
import { shallow } from '#vue/test-utils';
import ContentCard from '../../components/ContentCard.vue';
import NuxtLink from '../../.nuxt/components/nuxt-link';
const createComponent = propsData => shallow(ContentCard, { propsData });
describe('ContentCard', () => {
let component;
beforeEach(() => {
ContentCard.components = ContentCard.components || {};
ContentCard.components.NuxtLink = NuxtLink;
});
describe('Properties', () => {
it('has an imgSrc property', () => {
component = createComponent({ imgSrc: 'X' });
expect(component.props().imgSrc).toBe('X');
});
});
});
...
import NuxtLink from '../.nuxt/components/nuxt-link.js'
...
TestItem.components = TestItem.components || {};
TestItem.components.NuxtLink = NuxtLink;
const wrapper = shallow(TestItem, {
...
});
...
// test/jestSetup.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import { config } from '#vue/test-utils'
Vue.use(Vuetify)
config.stubs.NuxtLink = { template: '<a><slot /></a>' }