Vite keeps refreshing the entire page - vite

I migrated from CRA to Vite and it keeps refreshing the entire page.
I saw this issue #7131 on GitHub and that's not my problem.
Here's my vite.config.js:
import { defineConfig, loadEnv } from 'vite'
import react from '#vitejs/plugin-react'
const path = require(`path`)
const aliases = {
'#Form': 'src/Components/Form/Exports',
'#List': 'src/Components/List/Exports',
'#Browse': 'src/Components/Browse/Browse',
'#Tree': 'src/Components/Tree/Exports',
'#Tab': 'src/Components/Tab/Exports',
'#Dashboard': 'src/Components/Dashboard/Dashboard',
'#Panel': 'src/Panel/Panel',
}
const resolvedAliases = Object.fromEntries(
Object.entries(aliases).map(([key, value]) => [key, path.resolve(__dirname, value)]),
)
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.')
console.log(env)
const htmlPlugin = () => {
return {
name: "html-transform",
transformIndexHtml(html) {
return html.replace(/%(.*?)%/g, function (match, p1) {
return env[p1]
})
},
}
}
return {
resolve: {
alias: resolvedAliases
},
plugins: [react(), htmlPlugin()],
server: {
host: '0.0.0.0',
hmr: {
clientPort: 443
}
}
}
})
Since my project contains hundreds of components, and it use OAuth and third party authenticators, each reload takes a considerable amount of time (maybe 5 seconds).
This hugely slows down the development.
What should I do to fix this?
What should I do to prevent this?

Related

next-i18next Jest Testing with useTranslation

Testing libs...always fun. I am using next-i18next within my NextJS project. We are using the useTranslation hook with namespaces.
When I run my test there is a warning:
console.warn
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
> 33 | const { t } = useTranslation(['common', 'account']);
| ^
I have tried the setup from the react-i18next test examples without success. I have tried this suggestion too.
as well as just trying to mock useTranslation without success.
Is there a more straightforward solution to avoid this warning? The test passes FWIW...
test('feature displays error', async () => {
const { findByTestId, findByRole } = render(
<I18nextProvider i18n={i18n}>
<InviteCollectEmails onSubmit={jest.fn()} />
</I18nextProvider>,
{
query: {
orgId: 666,
},
}
);
const submitBtn = await findByRole('button', {
name: 'account:organization.invite.copyLink',
});
fireEvent.click(submitBtn);
await findByTestId('loader');
const alert = await findByRole('alert');
within(alert).getByText('failed attempt');
});
Last, is there a way to have the translated plain text be the outcome, instead of the namespaced: account:account:organization.invite.copyLink?
Use the following snippet before the describe block OR in beforeEach() to mock the needful.
jest.mock("react-i18next", () => ({
useTranslation: () => ({ t: key => key }),
}));
Hope this helps. Peace.
use this for replace render function.
import { render, screen } from '#testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'
jest.mock('react-i18next', () => ({
I18nextProvider: jest.fn(),
__esmodule: true,
}))
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
} as any,
router: {
locale: locale,
route: '/',
...router,
},
} as any)
const Component = appWithTranslation(() => <DarkModeToggleBtn />)
const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) => render(
<Component {...props} />
)
describe('', () => {
it('', () => {
renderComponent()
expect(screen.getByRole("button")).toHaveTextContent("")
})
})
I used a little bit more sophisticated approach than mocking to ensure all the functions work the same both in testing and production environment.
First, I create a testing environment:
// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";
declare global {
var i18nInstance: i18n;
}
export default class extends JSDomEnvironment {
async setup() {
await super.setup();
/* The important part start */
const i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "cimode",
resources: {},
});
this.global.i18nInstance = i18nInstance;
/* The important part end */
}
}
I add this environment in jest.config.ts:
// jest.config.ts
export default {
// ...
testEnvironment: "testing/env.ts",
};
Sample component:
// component.tsx
import { useTranslation } from "next-i18next";
export const Component = () => {
const { t } = useTranslation();
return <div>{t('foo')}</div>
}
And later on I use it in tests:
// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";
it("renders Component", () => {
/* The important part start */
setI18n(global.i18nInstance);
/* The important part end */
let root: ReactTestRenderer;
act(() => {
root = create(<Component />);
});
expect(root.toJSON()).toMatchSnapshot();
});
I figured out how to make the tests work with an instance of i18next using the renderHook function and the useTranslation hook from react-i18next based on the previous answers and some research.
This is the Home component I wanted to test:
import { useTranslation } from 'next-i18next';
const Home = () => {
const { t } = useTranslation("");
return (
<main>
<div>
<h1> {t("welcome", {ns: 'home'})}</h1>
</div>
</main>
)
};
export default Home;
First, we need to create a setup file for jest so we can start an i18n instance and import the translations to the configuration. test/setup.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import homeES from '#/public/locales/es/home.json';
import homeEN from '#/public/locales/en/home.json';
i18n.use(initReactI18next).init({
lng: "es",
resources: {
en: {
home: homeEN,
},
es: {
home: homeES,
}
},
fallbackLng: "es",
debug: false,
});
export default i18n;
Then we add the setup file to our jest.config.js:
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]
Now we can try our tests using the I18nextProvider and the useTranslation hook:
import '#testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';
import Home from '.';
describe("Index page", (): void => {
afterEach(cleanup);
it("should render properly in Spanish", (): void => {
const t = renderHook(() => useTranslation());
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home / >
</I18nextProvider>
);
expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
});
it("should render properly in English", (): void => {
const t = renderHook(() => useTranslation());
act(() => {
t.result.current.i18n.changeLanguage("en");
});
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home/>
</I18nextProvider>
);
expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
});
});
Here we used the I18nextProvider and send the i18n instance using the useTranslation hook. after that the translations were loaded without problems in the Home component.
We can also change the selected language running the changeLanguage() function and test the other translations.

Jest error "SyntaxError: Need to install with `app.use` function" when using vue-i18n plugin for Vue3

I am using vue-i18n plugin for my Vue3(typescript) application. Below is my setup function in component code
Home.vue
import {useI18n} from 'vue-i18n'
setup() {
const {t} = useI18n()
return {
t
}
}
Main.ts
import { createI18n } from 'vue-i18n'
import en from './assets/translations/english.json'
import dutch from './assets/translations/dutch.json'
// internationalization configurations
const i18n = createI18n({
messages: {
en: en,
dutch: dutch
},
fallbackLocale: 'en',
locale: 'en'
})
// Create app
const app = createApp(App)
app.use(store)
app.use(router)
app.use(i18n)
app.mount('#app')
Code works and compiles fine. But jest test cases fails for the component when it's mounting
Spec file
import { mount, VueWrapper } from '#vue/test-utils'
import Home from '#/views/Home.vue'
import Threat from '#/components/Threat.vue'
// Test case for Threats Component
let wrapper: VueWrapper<any>
beforeEach(() => {
wrapper = mount(Home)
// eslint-disable-next-line #typescript-eslint/no-empty-function
jest.spyOn(console, 'warn').mockImplementation(() => { });
});
describe('Home.vue', () => {
//child component 'Home' existance check
it("Check Home component exists in Threats", () => {
expect(wrapper.findComponent(Home).exists()).toBe(true)
})
// Threat level list existance check
it("Check all 5 threat levels are listed", () => {
expect(wrapper.findAll('.threat-level .level-wrapper label')).toHaveLength(5)
})
})
Below is the error
Please help me to resolve this.
The vue-18n plugin should be installed on the wrapper during mount with the global.plugins option:
import { mount } from '#vue/test-utils'
import { createI18n } from 'vue-i18n'
import Home from '#/components/Home.vue'
describe('Home.vue', () => {
it('i18n', () => {
const i18n = createI18n({
// vue-i18n options here ...
})
const wrapper = mount(Home, {
global: {
plugins: [i18n]
}
})
expect(wrapper.vm.t).toBeTruthy()
})
})
GitHub demo
You can also define the plugin globally in the setup/init file:
import { config } from '#vue/test-utils'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
// vue-i18n options here ...
})
config.global.plugins = [i18n]
config.global.mocks.$t = (key) => key

How to make Mapbox load on gatsby site? Build succeeds but map not displaying, despite working in dev mode

I'm trying to use a Mapbox map on a gatsby website. I used the code from their react Mapbox tutorial: https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/ and put it into a map component.
I call the map component on the footer and it works perfectly during development mode, but when I run gatsby build and then gatsby serve it refuses to show the map despite showing the container :
https://postimg.cc/mPcRfYhV
I've tried the other suggestion which is from the docs too, that is to use in gatsby-node.js
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox|mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
for the test property I've tried using /mapbox|#mapbox|mapbox-gl|mapboxgl/ but nothing seems to make it work like it does in dev mode.
any ideas?
The component:
import React, { useEffect, useRef, useState } from "react";
import mapboxgl from 'mapbox-gl';
import "mapbox-gl/dist/mapbox-gl.css";
const styles = {
width: "220px",
height: '130px',
margin: '2em 0'
};
const Map = () => {
const [map, setMap] = useState(null);
const mapContainer = useRef(null);
useEffect(() => {
mapboxgl.accessToken = process.env.MY_KEY
const initializeMap = ({ setMap, mapContainer }) => {
const map = new mapboxgl.Map({
container: mapContainer.current,
style: "mapbox://styles/mapbox/streets-v11",
center: [-6.2603, 53.3498],
zoom: 9
});
map.on("load", () => {
setMap(map);
map.resize();
});
new mapboxgl.Marker({color:'#1E3873'}).setLngLat([-6.2603, 53.3498]).addTo(map)
};
if (!map) initializeMap({ setMap, mapContainer });
}, [map]);
return <div ref={el => (mapContainer.current = el)} style={styles} />;
};
export default Map;
The Map component is called on the footer component in a normal way by just importing the component and using <Map />.
Picture of it working fine when using gatsby develop
Picture of it not working when using gatsby build gatsby serve
SOLUTION EDIT: for some reason mapbox-gl 2.0 doesn't work, or I can't get it to work. Someone else had the same issue and suggested what worked for them which was to use mapbox-gl 1.13.0 . I tried it and it works.
npm uninstall mapbox-gl
npm i mapbox-gl#1.13.0
Leave the other configurations the same and it should work.
For some reason mapbox-gl 2.0 doesn't work properly. Someone else had the same issue and suggested what worked for them which was to use mapbox-gl 1.13.0 . I tried it and it works.
npm uninstall mapbox-gl
npm i mapbox-gl#1.13.0
Remember to use the following code in gatsby-node.js :
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox|mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
For future readers, I was able to make it work w/ mapbox v2 in Gatsby 2 with import mapboxgl from "!mapbox-gl"; as described in mapboxgl docs https://docs.mapbox.com/mapbox-gl-js/api/#transpiling-v2
Among the configuration, you are missing some stuff, because in the guide, there is a class-based component and you are using a functional one, change it to:
import React, { useEffect, useRef, useState } from "react";
import mapboxgl from 'mapbox-gl';
import "mapbox-gl/dist/mapbox-gl.css";
const styles = {
width: "220px",
height: '130px',
margin: '2em 0'
};
const Map = () => {
const [map, setMap] = useState(null);
const mapContainer = useRef(null);
useEffect(() => {
mapboxgl.accessToken = process.env.MY_KEY
const initializeMap = ({ setMap, mapContainer }) => {
const map = new mapboxgl.Map({
container: mapContainer.current,
style: "mapbox://styles/mapbox/streets-v11",
center: [-6.2603, 53.3498],
zoom: 9
});
map.on("load", () => {
setMap(map);
map.resize();
});
new mapboxgl.Marker({color:'#1E3873'}).setLngLat([-6.2603, 53.3498]).addTo(map)
};
if (!map) initializeMap({ setMap, mapContainer });
return () => {
map.remove()
}
}, []);
return <div ref={mapContainer} style={styles} />;
};
export default Map;
Notice the ref and the map initializer. I think that it's not properly set when running a gatsby build and, since you have passing it as a reference in el => (mapContainer.current = el) it's not being rehydrated. React guides should differ a little bit when dealing with Gatsby due to their SSR (Server-Side Rendering).
Regarding the webpack issue.
Try:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
Or:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox/,
use: loaders.null(),
},
],
},
})
}
}
Or ignoring both with:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox/,
use: loaders.null(),
},
{
test: /mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
Keep in mind that the testing rule is a regular expression (that's why is written between slashes) that should match a folder inside node_modules so it should be an exact path.
This is a needed workaround for every dependency that uses window, or other global objects as a dev-dependency for themselves. This is because, the interpreter when running gatsby develop is the browser (browser-side rendering), where there's a window available and while running gatsby build, the code is compiled in the server (Node), where there isn't a window obviously.
The snippet above adds a null loader during the webpack's transpilation, or in other words, forces webpack to ignore the module to avoid a code-breaking.

Cache-busting page-data.json files in Gatsby

I have a gatsby generated website on which I have replaced the contents of the homepage.
Unfortunately the previous version was serving up /page-data/index/page-data.json with the incorrect cache-control headers, resulting in /page-data/index/page-data.json being cached on client browsers (and stale data being shown unless force-refreshed). I have also discovered that page-data.json files are not hashed (see https://github.com/gatsbyjs/gatsby/issues/15080).
I've updated the cache-control headers so that versions from now on will not be cached but this does not help with clients that have the cached version now.
What can I do to force clients to request the latest version of this file?
I got there in the end... This is in my gatsby-node.js
const hash = md5(`${new Date().getTime()}`)
const addPageDataVersion = async file => {
const stats = await util.promisify(fs.stat)(file)
if (stats.isFile()) {
console.log(`Adding version to page-data.json in ${file}..`)
let content = await util.promisify(fs.readFile)(file, 'utf8')
const result = content.replace(
/page-data.json(\?v=[a-f0-9]{32})?/g,
`page-data.json?v=${hash}`
)
await util.promisify(fs.writeFile)(file, result, 'utf8')
}
}
exports.onPostBootstrap = async () => {
const loader = path.join(__dirname, 'node_modules/gatsby/cache-dir/loader.js')
await addPageDataVersion(loader)
}
exports.onPostBuild = async () => {
const publicPath = path.join(__dirname, 'public')
const htmlAndJSFiles = glob.sync(`${publicPath}/**/*.{html,js}`)
for (let file of htmlAndJSFiles) {
await addPageDataVersion(file)
}
}
Check out this tutorial, this is the solution I've been using.
https://examsworld.co.in/programming/javascript/how-to-cache-bust-a-react-app/
It's basically a wrapper component that checks to see if the browser's cached version matches the build's version number in package.json. If it doesn't, it clears the cache and reloads the page.
This is how I'm using it.
gatsby-browser.js
export const wrapRootElement = ({ element }) => (
<CacheBuster>
{({ loading, isLatestVersion, refreshCacheAndReload }) => {
if (loading) return null
if (!loading && !isLatestVersion) {
// You can decide how and when you want to force reload
refreshCacheAndReload()
}
return <AppProvider>{element}</AppProvider>
}}
</CacheBuster>
)
CacheBuster.js
import React from 'react'
import packageJson from '../../package.json'
global.appVersion = packageJson.version
// version from response - first param, local version second param
const semverGreaterThan = (versionA, versionB) => {
const versionsA = versionA.split(/\./g)
const versionsB = versionB.split(/\./g)
while (versionsA.length || versionsB.length) {
const a = Number(versionsA.shift())
const b = Number(versionsB.shift())
// eslint-disable-next-line no-continue
if (a === b) continue
// eslint-disable-next-line no-restricted-globals
return a > b || isNaN(b)
}
return false
}
class CacheBuster extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: () => {
console.info('Clearing cache and hard reloading...')
if (caches) {
// Service worker cache should be cleared with caches.delete()
caches.keys().then(function(names) {
for (const name of names) caches.delete(name)
})
}
// delete browser cache and hard reload
window.location.reload(true)
},
}
}
componentDidMount() {
fetch('/meta.json')
.then(response => response.json())
.then(meta => {
const latestVersion = meta.version
const currentVersion = global.appVersion
const shouldForceRefresh = semverGreaterThan(
latestVersion,
currentVersion
)
if (shouldForceRefresh) {
console.info(
`We have a new version - ${latestVersion}. Should force refresh`
)
this.setState({ loading: false, isLatestVersion: false })
} else {
console.info(
`You already have the latest version - ${latestVersion}. No cache refresh needed.`
)
this.setState({ loading: false, isLatestVersion: true })
}
})
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state
const { children } = this.props
return children({ loading, isLatestVersion, refreshCacheAndReload })
}
}
export default CacheBuster
generate-build-version.js
const fs = require('fs')
const packageJson = require('./package.json')
const appVersion = packageJson.version
const jsonData = {
version: appVersion,
}
const jsonContent = JSON.stringify(jsonData)
fs.writeFile('./static/meta.json', jsonContent, 'utf8', function(err) {
if (err) {
console.log('An error occured while writing JSON Object to meta.json')
return console.log(err)
}
console.log('meta.json file has been saved with latest version number')
})
and in your package.json add these scripts
"generate-build-version": "node generate-build-version",
"prebuild": "npm run generate-build-version"
Outside of going to each client browser individually and clearing their cache there isn't any other means of invalidating all of your client's caches. If your webpage is behind a CDN you can control, you may be able to force invalidation at the CDN-level so new clients will always be routed to the up to date webpage even if the CDN had a pre-existing, outdated copy cached.

Loading files from all directories inside specific directory

Good time of the day,
I've been trying to add 'modularity' to my application by splitting the Vuex store into many different locations.
So far, i'm totally fine with loading 'local' modules (inside the store folder) with the following piece of code:
const localRequireContext = require.context('./modules', false, /.*\.js$/);
const localModules = localRequireContext.keys().map((file) => [file.replace(/(^.\/)|(\.js$)/g, ''), localRequireContext(file)]).reduce((localModules, [name, module]) => {
if (module.namespaced === undefined) {
module.namespaced = true;
}
return { ...localModules, [name]: module };
}, {});
const createStore = () => {
return new Vuex.Store({
modules: localModules
})
};
export default createStore;
However, what i'm trying to achieve seems to be rather impossible for me (i'm not new to the web app development, but actually never had a chance to play around with 'core' libraries of Node.js, Webpack, etc).
I have the following structure
root
|-assets
|-components
|-config
|-lang
|-layouts
|-libs
|-middleware
|-modules
|----company
|--------module
|-----------store
|--------------index.js (module index)
|-pages
|-plugins
|-store
|----index.js (main index)
So what i'm trying to achieve, is to get to the ~/modules folder, go inside each of company directory (namespace for module), open the module directory (the name of the module), navigate to the store folder and import index.js file, with roughly the following content:
import module from '../config/module';
export const namespace = [module.namespace, module.name].join('/');
export const state = () => ({
quotes: null,
team: null,
articles: null
});
export const getters = {
quotes: (state) => state.quotes,
team: (state) => state.team,
articles: (state) => state.articles
};
As i've already said, i'm not much of a 'guru' when it comes to these complicated (for me) things, so any help is really appreciated!
So far, i went the 'dumb road' and just tried to use the following:
const modulesRequireContext = require.context('../modules/**/**/store', false, /.*\.js$/);
But, no luck it is - Cannot find module 'undefined'
The final file (in my mind) should look something like this:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const localRequireContext = require.context('./modules', false, /.*\.js$/);
const localModules = localRequireContext.keys().map((file) => [file.replace(/(^.\/)|(\.js$)/g, ''), localRequireContext(file)]).reduce((localModules, [name, module]) => {
if (module.namespaced === undefined) {
module.namespaced = true;
}
return { ...localModules, [name]: module };
}, {});
const modulesRequireContext = require.context('CORRECT_WAY_OF_SEARCHING_IN_SUB_DIRECTORIES', false, /.*\.js$/);
const addedModules = modulesRequireContext.keys().map((file) => [file.replace(/(^.\/)|(\.js$)/g, ''), modulesRequireContext(file)]).reduce((addedModules, [name, module]) => {
return { ...addedModules, [module.namespace]: module };
}, {});
let modules = { ...localModules, ...addedModules };
const createStore = () => {
return new Vuex.Store({
modules: modules
})
};
export default createStore;

Resources