I have a ready React application that I would like to distribute as a link to any HTML page, such as:
<script src=".../app.js"></script>
and later call some function, for example App.create(document.getElementById('root')) that would render my app in the provided HTML element.
The problem is that my app uses process.env variables for storing for example API URL, so ideally in fact I would like to call
App.create(document.getElementById('root'), {
apiUrl: ...
})
// create function, index.ts
export const create = (root: HTMLElement, options: Options) => {
ReactDOM.render(<App />, root);
};
Later in the app code there is a eg. line const apiUrl = process.env.API_URL
My question: Is there a way to make api url an argument of App.create instead of process.env variable without introducing major changes to the code? Esentially is it possible to move .env contents to App.create(.env)
Related
I am working on a Svelte project, but it contains some web components.
The issue I am facing is that some web components defined by the customElements.define() Typescript function are not usable unless I import them specifically in the user component.
For example, a web component is defined in the following way:
// File name is oneWebComponent.js
export class OneWebComponent extends HTMLElement {
...
customElements.define('one-web-component', OneWebComponent);
}
Then, there is another JS file, which contains a "factory" function that creates different types of Web Components:
export const createElement = (tagName) => {
return document.createElement(tagName);
}
If I call it like createElement('one-web-component'), the resulting component is not really the one defined in OneWebComponent. I know that because the functions defined there are not callable (error: XXX is not a function).
But if I import oneWebComponent.js in the factory file like below, it works correctly:
// This line is newly added:
import './oneWebComponent.js';
export const createElement = (tagName) => {
return document.createElement(tagName);
}
That means, if I have multiple types of web components, and the factory function is called in multiple places, I will have to import each type of web component in each place, which is tedious.
I wonder if there is a way to just make the components defined by customElements.define() globally usable?
That means, no imports needed, just passing the tag name into document.createElement() and it will create the web component correctly.
Am I missing any configs?
Thanks in advance!
As #Bergi pointed out, the customElements.define() function is not executed if the file is not imported anywhere.
So I only needed to import all of the web components in the top level of the app, a file like app.svelte or main.js for example, then the function will run, and the tags are defined and usable across the app.
In my vue project I have a default image that should show up in places around the app when an image is missing or hasn't been uploaded yet. I have various class files and component calls that want to use the same image.
What is the best way to create a global variable that I can access from anywhere? I can't put it in the store because my class files (I'm using vuex-orm) are loaded before the store is declared. I would prefer not to put it in the window because I want to have a single word variable that I can call (defaultImg as opposed to window.my_project.globals.defaultImg). I could put it in .env but I want this to be checked into the repository and the same across environments so that doesn't seem right either.
What is the best way to provide a simple global string to all files in my vue project?
Are you using Vue 2 or 3? TL;DR best approach is to create a component.
Quick Fix
You could take the approach described in the documentation for creating plugins:
https://vuejs.org/guide/reusability/plugins.html#introduction
If you didn't want to go down the convoluted route of creating a separate plugin file just to store a single string you could probably just do something like this in your entry file where you initialize the app:
Vue 3:
app.config.globalProperties.$defaultImg = '/path/to/image.png'
Vue 2:
Vue.prototype.$defaultImg = '/path/to/image.png'
And use this in your templates like
<img :src="$defaultImage">
Best Solution
However, I think the best and the most 'Vue' way would be to create an image component which displays a given image, or the default image if src is nullish:
Vue 2 & 3
<template>
<img :src="srcOrDefault">
<template>
Vue 3 Composition API:
<script>
const defaultImg = '/path/to/image.png'
const props = defineProps({
src: String
});
const srcOrDefault = computed(() => {
return props.src || defaultImg
})
</script>
Vue 2 & Vue 3 Options API:
<script>
const defaultImg = '/path/to/image.png'
export default {
props: {
src: String
},
computed: {
srcOrDefault() {
return this.src || defaultImg
}
}
}
</script>
In Vue 2/3, and in an ES6 environment when I want to access a Vuex store in an external JS file (outside of the component), I would normally use something like this:
// example.js
import { store } from '../store/index';
console.log(store.state.currentUser);
This works great, however, in my current environment (Rails 5 without webpack), we can't use import statements at all.
Question: Is there any way, in regular ES5 JavaScript, to access Vuex stores outside of components?
It's worth noting that I've got a successful setup of Vuex going on our frontend, we just can't access it outside of our defined Vue components.
In my Rails setup, I have this:
// app/assets/javascripts/lib/vuex/store.js
const store = new Vuex.Store({
modules: {
activity: activityStore,
}
});
// application.js
//= require vue/dist/vue.min
//= require vuex/dist/vuex.min.js
$(document).ready(function () {
Vue.use(Vuex);
});
In this instance, store is just a global javascript object. Use it the same as you would any other JS object.
As long as you're inside a JS file that is properly compiled, and your base Vue is installed properly, you can just do store.state.activity.activities, or if you have mutations, store.commit('myMutation', 'Hello test').
My specific example was a huge function calling a webhook. The webhook can take a while, and I want to send messages to the user as I get new messages.
Example:
// webhook.js
async webhookFunction() {
// new message recieved
store.commit('newActivity', 'Your object has been updated');
}
In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}
I currently have the following Vue page code:
<template>
// Button that when clicked called submit method
</template>
<script>
import { moveTo } from '#/lib/utils';
export default {
components: {
},
data() {
},
methods: {
async submit() {
moveTo(this, COMPLETE_ACTION.path, null);
},
},
};
</script>
and then I have a test file for this page. My issue is that Im trying to check and assert that the moveTo method is called with the correct parameters using Jest. It keeps showing expected undefined but received an object. Here are the key points from the test file:
import * as dependency from '#/lib/utils';
dependency.moveTo = jest.fn();
// I then trigger the button call which calls the submit method on the page
expect(dependency.moveTo).toHaveBeenCalledWith(this, COMPLETE_ACTION.path, null);
Im unsure what this is in this context and what I should actually be passing in. Just to note I am using the mount helper from vue test utils.
I solved my issue and it was the this param within the test. This was undefined in the test and was expecting to match against a VueComponent.
I used my wrapper and then accessed the VueComponent by referencing the vm property as per the documentation: https://vue-test-utils.vuejs.org/api/wrapper/#properties
In turn I updated the following line and added wrapper.vm
expect(dependency.moveTo).toHaveBeenCalledWith(wrapper.vm, COMPLETE_ACTION.path, null);
You need to mock the module itself. In your case you are making an assertion on a spy function that is never called.
You can add a module mock by creating a "mocks/ subdirectory immediately adjacent to the module". For a node module "If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the mocks directory adjacent to node_modules".
In your case (there are other approaches) you need to create a __mocks__ folder adjacent to the node_modules one and create a file at __mocks__/lib/utils/index.js and export the mocked function:
export const moveTo = jest.fn()